2019-04-25 04:52:08 +07:00
package notification
import (
2019-12-21 01:40:01 +07:00
"crypto/tls"
"errors"
2019-04-25 04:52:08 +07:00
"fmt"
"net/smtp"
2019-12-21 01:40:01 +07:00
"strings"
2019-04-25 04:52:08 +07:00
2019-12-24 09:14:52 +07:00
"github.com/authelia/authelia/internal/configuration/schema"
2019-12-21 01:40:01 +07:00
"github.com/authelia/authelia/internal/utils"
log "github.com/sirupsen/logrus"
2019-04-25 04:52:08 +07:00
)
// SMTPNotifier a notifier to send emails to SMTP servers.
type SMTPNotifier struct {
username string
password string
2019-12-21 01:40:01 +07:00
sender string
2019-04-25 04:52:08 +07:00
host string
port int
2019-12-21 01:40:01 +07:00
secure bool
address string
2019-04-25 04:52:08 +07:00
}
// NewSMTPNotifier create an SMTPNotifier targeting a given address.
func NewSMTPNotifier ( configuration schema . SMTPNotifierConfiguration ) * SMTPNotifier {
return & SMTPNotifier {
2019-12-21 01:40:01 +07:00
username : configuration . Username ,
password : configuration . Password ,
sender : configuration . Sender ,
2019-04-25 04:52:08 +07:00
host : configuration . Host ,
port : configuration . Port ,
2019-12-21 01:40:01 +07:00
secure : configuration . Secure ,
2019-04-25 04:52:08 +07:00
address : fmt . Sprintf ( "%s:%d" , configuration . Host , configuration . Port ) ,
}
}
2019-12-21 01:40:01 +07:00
// Send send a identity verification link to a user.
func ( n * SMTPNotifier ) Send ( recipient string , subject string , body string ) error {
msg := "From: " + n . sender + "\n" +
"To: " + recipient + "\n" +
"Subject: " + subject + "\n" +
"Content-Type: text/html\n" +
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
body
2019-04-25 04:52:08 +07:00
c , err := smtp . Dial ( n . address )
2019-11-02 21:32:58 +07:00
if err != nil {
return err
}
2020-01-06 06:03:16 +07:00
// Do StartTLS if available (some servers only provide the auth extnesion after, and encryption is preferred)
2019-12-21 01:40:01 +07:00
starttls , _ := c . Extension ( "STARTTLS" )
if starttls {
tlsconfig := & tls . Config {
InsecureSkipVerify : ! n . secure ,
ServerName : n . host ,
}
log . Debugf ( "SMTP server supports STARTTLS (InsecureSkipVerify: %t, ServerName: %s), attempting" , tlsconfig . InsecureSkipVerify , tlsconfig . ServerName )
err := c . StartTLS ( tlsconfig )
if err != nil {
return err
} else {
log . Debug ( "SMTP STARTTLS completed without error" )
}
} else {
log . Debug ( "SMTP server does not support STARTTLS, skipping" )
}
// Attempt AUTH if password is specified only
if n . password != "" {
2019-12-28 09:49:29 +07:00
if ! starttls {
log . Warn ( "Authentication is being attempted over an insecure connection. Using a SMTP server that supports STARTTLS is recommended, especially if the server is not on your local network (username and pasword are being transmitted in plain-text)." )
}
2019-12-21 01:40:01 +07:00
// Check the server supports AUTH, and get the mechanisms
authExtension , m := c . Extension ( "AUTH" )
if authExtension {
log . Debugf ( "Config has SMTP password and server supports AUTH with the following mechanisms: %s." , m )
mechanisms := strings . Split ( m , " " )
var auth smtp . Auth
// Adaptively select the AUTH mechanism to use based on what the server advertised
if utils . IsStringInSlice ( "PLAIN" , mechanisms ) {
auth = smtp . PlainAuth ( "" , n . username , n . password , n . host )
log . Debug ( "SMTP server supports AUTH PLAIN, attempting..." )
} else if utils . IsStringInSlice ( "LOGIN" , mechanisms ) {
auth = LoginAuth ( n . username , n . password )
log . Debug ( "SMTP server supports AUTH LOGIN, attempting..." )
}
// Throw error since AUTH extension is not supported
if auth == nil {
2019-12-28 09:49:29 +07:00
return fmt . Errorf ( "SMTP server does not advertise a AUTH mechanism that Authelia supports (PLAIN or LOGIN). Advertised mechanisms: %s." , m )
2019-12-21 01:40:01 +07:00
}
// Authenticate
err := c . Auth ( auth )
if err != nil {
return err
} else {
log . Debug ( "SMTP AUTH completed successfully." )
}
} else {
return errors . New ( "SMTP server does not advertise the AUTH extension but a password was specified. Either disable auth (don't specify a password/comment the password), or specify an SMTP host and port that supports AUTH PLAIN or AUTH LOGIN." )
}
} else {
log . Debug ( "SMTP config has no password specified for use with AUTH, skipping." )
}
2019-04-25 04:52:08 +07:00
// Set the sender and recipient first
if err := c . Mail ( n . sender ) ; err != nil {
return err
}
if err := c . Rcpt ( recipient ) ; err != nil {
return err
}
// Send the email body.
wc , err := c . Data ( )
if err != nil {
return err
}
_ , err = fmt . Fprintf ( wc , msg )
if err != nil {
return err
}
err = wc . Close ( )
if err != nil {
return err
}
// Send the QUIT command and close the connection.
err = c . Quit ( )
if err != nil {
return err
}
return nil
}