2019-04-25 04:52:08 +07:00
package notification
import (
2019-12-21 01:40:01 +07:00
"crypto/tls"
2019-12-30 09:03:51 +07:00
"crypto/x509"
2019-12-21 01:40:01 +07:00
"errors"
2019-04-25 04:52:08 +07:00
"fmt"
2019-12-30 09:03:51 +07:00
"io/ioutil"
2019-04-25 04:52:08 +07:00
"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
)
2020-01-28 11:00:43 +07:00
// SMTPNotifier a notifier to send emails to SMTP servers.
2019-04-25 04:52:08 +07:00
type SMTPNotifier struct {
2019-12-30 09:03:51 +07:00
username string
password string
sender string
host string
port int
trustedCert string
disableVerifyCert bool
disableRequireTLS bool
address string
client * smtp . Client
tlsConfig * tls . Config
2019-04-25 04:52:08 +07:00
}
2020-01-28 11:00:43 +07:00
// NewSMTPNotifier create an SMTPNotifier targeting a given address.
2019-04-25 04:52:08 +07:00
func NewSMTPNotifier ( configuration schema . SMTPNotifierConfiguration ) * SMTPNotifier {
2019-12-30 09:03:51 +07:00
notifier := & SMTPNotifier {
username : configuration . Username ,
password : configuration . Password ,
sender : configuration . Sender ,
host : configuration . Host ,
port : configuration . Port ,
trustedCert : configuration . TrustedCert ,
disableVerifyCert : configuration . DisableVerifyCert ,
disableRequireTLS : configuration . DisableRequireTLS ,
address : fmt . Sprintf ( "%s:%d" , configuration . Host , configuration . Port ) ,
2019-04-25 04:52:08 +07:00
}
2019-12-30 09:03:51 +07:00
notifier . initializeTLSConfig ( )
return notifier
2019-04-25 04:52:08 +07:00
}
2019-12-30 09:03:51 +07:00
func ( n * SMTPNotifier ) initializeTLSConfig ( ) {
2020-01-28 11:00:43 +07:00
// Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded.
// The second part of this check happens in the Configure Cert Pool code block.
2019-12-30 09:03:51 +07:00
log . Debug ( "Notifier SMTP client initializing TLS configuration" )
insecureSkipVerify := false
if n . disableVerifyCert {
insecureSkipVerify = true
}
2019-04-25 04:52:08 +07:00
2020-01-28 11:00:43 +07:00
//Configure Cert Pool.
2019-12-30 09:03:51 +07:00
certPool , err := x509 . SystemCertPool ( )
if err != nil || certPool == nil {
certPool = x509 . NewCertPool ( )
2019-11-02 21:32:58 +07:00
}
2019-12-30 09:03:51 +07:00
if n . trustedCert != "" {
log . Debugf ( "Notifier SMTP client attempting to load certificate from %s" , n . trustedCert )
if exists , err := utils . FileExists ( n . trustedCert ) ; exists {
pem , err := ioutil . ReadFile ( n . trustedCert )
if err != nil {
log . Warnf ( "Notifier SMTP failed to load cert from file with error: %s" , err )
} else {
if ok := certPool . AppendCertsFromPEM ( pem ) ; ! ok {
log . Warn ( "Notifier SMTP failed to import cert loaded from file" )
} else {
log . Debug ( "Notifier SMTP successfully loaded certificate" )
if n . disableVerifyCert {
log . Warn ( "Notifier SMTP when trusted_cert is specified we force disable_verify_cert to false, if you want to disable certificate validation please comment/delete trusted_cert from your config" )
insecureSkipVerify = false
}
}
}
} else {
log . Warnf ( "Notifier SMTP failed to load cert from file (file does not exist) with error: %s" , err )
2019-12-21 01:40:01 +07:00
}
2019-12-30 09:03:51 +07:00
}
n . tlsConfig = & tls . Config {
InsecureSkipVerify : insecureSkipVerify ,
ServerName : n . host ,
RootCAs : certPool ,
}
}
2020-01-28 11:00:43 +07:00
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
2019-12-30 09:03:51 +07:00
func ( n * SMTPNotifier ) startTLS ( ) ( bool , error ) {
2020-01-28 11:00:43 +07:00
// Only start if not already encrypted.
2019-12-30 09:03:51 +07:00
if _ , ok := n . client . TLSConnectionState ( ) ; ok {
log . Debugf ( "Notifier SMTP connection is already encrypted, skipping STARTTLS" )
return ok , nil
}
ok , _ := n . client . Extension ( "STARTTLS" )
if ok {
log . Debugf ( "Notifier SMTP server supports STARTTLS (disableVerifyCert: %t, ServerName: %s), attempting" , n . tlsConfig . InsecureSkipVerify , n . tlsConfig . ServerName )
err := n . client . StartTLS ( n . tlsConfig )
2019-12-21 01:40:01 +07:00
if err != nil {
2019-12-30 09:03:51 +07:00
return ok , err
2019-12-21 01:40:01 +07:00
} else {
2019-12-30 09:03:51 +07:00
log . Debug ( "Notifier SMTP STARTTLS completed without error" )
2019-12-21 01:40:01 +07:00
}
2019-12-30 09:03:51 +07:00
} else if n . disableRequireTLS {
log . Warn ( "Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)" )
2019-12-21 01:40:01 +07:00
} else {
2019-12-30 09:03:51 +07:00
return ok , errors . New ( "Notifier SMTP server does not support TLS and it is required by default (see documentation if you want to disable this highly recommended requirement)" )
2019-12-21 01:40:01 +07:00
}
2019-12-30 09:03:51 +07:00
return ok , nil
}
2019-12-21 01:40:01 +07:00
2020-01-28 11:00:43 +07:00
// Attempt Authentication.
2019-12-30 09:03:51 +07:00
func ( n * SMTPNotifier ) auth ( ) ( bool , error ) {
2020-01-28 11:00:43 +07:00
// Attempt AUTH if password is specified only.
2019-12-21 01:40:01 +07:00
if n . password != "" {
2019-12-30 09:03:51 +07:00
_ , ok := n . client . TLSConnectionState ( )
if ! ok {
return false , errors . New ( "Notifier SMTP client does not support authentication over plain text and the connection is currently plain text" )
2019-12-28 09:49:29 +07:00
}
2019-12-21 01:40:01 +07:00
2020-01-28 11:00:43 +07:00
// Check the server supports AUTH, and get the mechanisms.
2019-12-30 09:03:51 +07:00
ok , m := n . client . Extension ( "AUTH" )
if ok {
log . Debugf ( "Notifier SMTP server supports authentication with the following mechanisms: %s" , m )
2019-12-21 01:40:01 +07:00
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 )
2019-12-30 09:03:51 +07:00
log . Debug ( "Notifier SMTP client attempting AUTH PLAIN with server" )
2019-12-21 01:40:01 +07:00
} else if utils . IsStringInSlice ( "LOGIN" , mechanisms ) {
2019-12-30 09:03:51 +07:00
auth = newLoginAuth ( n . username , n . password , n . host )
log . Debug ( "Notifier SMTP client attempting AUTH LOGIN with server" )
2019-12-21 01:40:01 +07:00
}
2020-01-28 11:00:43 +07:00
// Throw error since AUTH extension is not supported.
2019-12-21 01:40:01 +07:00
if auth == nil {
2019-12-30 09:03:51 +07:00
return false , fmt . Errorf ( "notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)" , m )
2019-12-21 01:40:01 +07:00
}
2020-01-28 11:00:43 +07:00
// Authenticate.
2019-12-30 09:03:51 +07:00
err := n . client . Auth ( auth )
2019-12-21 01:40:01 +07:00
if err != nil {
2019-12-30 09:03:51 +07:00
return false , err
2019-12-21 01:40:01 +07:00
} else {
2019-12-30 09:03:51 +07:00
log . Debug ( "Notifier SMTP client authenticated successfully with the server" )
return true , nil
2019-12-21 01:40:01 +07:00
}
} else {
2019-12-30 09:03:51 +07:00
return false , errors . New ( "Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN" )
2019-12-21 01:40:01 +07:00
}
} else {
2019-12-30 09:03:51 +07:00
log . Debug ( "Notifier SMTP config has no password specified so authentication is being skipped" )
return false , nil
2019-12-21 01:40:01 +07:00
}
2019-12-30 09:03:51 +07:00
}
2019-12-21 01:40:01 +07:00
2019-12-30 09:03:51 +07:00
func ( n * SMTPNotifier ) compose ( recipient , subject , body string ) error {
log . Debugf ( "Notifier SMTP client attempting to send email body to %s" , recipient )
if ! n . disableRequireTLS {
_ , ok := n . client . TLSConnectionState ( )
if ! ok {
return errors . New ( "Notifier SMTP client can't send an email over plain text connection" )
}
}
wc , err := n . client . Data ( )
if err != nil {
log . Debugf ( "Notifier SMTP client error while obtaining WriteCloser: %s" , err )
return err
}
msg := "From: " + n . sender + "\n" +
"To: " + recipient + "\n" +
"Subject: " + subject + "\n" +
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
body
_ , err = fmt . Fprintf ( wc , msg )
if err != nil {
log . Debugf ( "Notifier SMTP client error while sending email body over WriteCloser: %s" , err )
2019-04-25 04:52:08 +07:00
return err
}
2019-12-30 09:03:51 +07:00
err = wc . Close ( )
if err != nil {
log . Debugf ( "Notifier SMTP client error while closing the WriteCloser: %s" , err )
2019-04-25 04:52:08 +07:00
return err
}
2019-12-30 09:03:51 +07:00
return nil
}
2019-04-25 04:52:08 +07:00
2020-01-28 11:00:43 +07:00
// Dial the SMTP server with the SMTPNotifier config.
2019-12-30 09:03:51 +07:00
func ( n * SMTPNotifier ) dial ( ) error {
log . Debugf ( "Notifier SMTP client attempting connection to %s" , n . address )
2020-02-20 08:09:46 +07:00
if n . port == 465 {
log . Warnf ( "Notifier SMTP client configured to connect to a SMTPS server. It's highly recommended you use a non SMTPS port and STARTTLS instead of SMTPS, as the protocol is long deprecated." )
conn , err := tls . Dial ( "tcp" , n . address , n . tlsConfig )
if err != nil {
return err
}
client , err := smtp . NewClient ( conn , n . host )
if err != nil {
return err
}
n . client = client
} else {
client , err := smtp . Dial ( n . address )
if err != nil {
return err
}
n . client = client
2019-04-25 04:52:08 +07:00
}
2019-12-30 09:03:51 +07:00
log . Debug ( "Notifier SMTP client connected successfully" )
return nil
}
2020-01-28 11:00:43 +07:00
// Closes the connection properly.
func ( n * SMTPNotifier ) cleanup ( ) {
err := n . client . Quit ( )
if err != nil {
log . Warnf ( "Notifier SMTP client encountered error during cleanup: %s" , err )
}
}
2019-12-30 09:03:51 +07:00
// Send an email
func ( n * SMTPNotifier ) Send ( recipient , subject , body string ) error {
2020-01-28 11:00:43 +07:00
if err := n . dial ( ) ; err != nil {
2019-04-25 04:52:08 +07:00
return err
}
2019-12-30 09:03:51 +07:00
2020-01-28 11:00:43 +07:00
// Always execute QUIT at the end once we're connected.
defer n . cleanup ( )
// Start TLS and then Authenticate.
if _ , err := n . startTLS ( ) ; err != nil {
2019-12-30 09:03:51 +07:00
return err
}
2020-01-28 11:00:43 +07:00
if _ , err := n . auth ( ) ; err != nil {
2019-12-30 09:03:51 +07:00
return err
}
2020-01-28 11:00:43 +07:00
// Set the sender and recipient first.
2019-12-30 09:03:51 +07:00
if err := n . client . Mail ( n . sender ) ; err != nil {
log . Debugf ( "Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s" , err )
return err
}
if err := n . client . Rcpt ( recipient ) ; err != nil {
log . Debugf ( "Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s" , err )
return err
}
2020-01-28 11:00:43 +07:00
// Compose and send the email body to the server.
2019-12-30 09:03:51 +07:00
if err := n . compose ( recipient , subject , body ) ; err != nil {
2019-04-25 04:52:08 +07:00
return err
}
2019-12-30 09:03:51 +07:00
log . Debug ( "Notifier SMTP client successfully sent email" )
2019-04-25 04:52:08 +07:00
return nil
}