[FIX] SMTP Notifier Unhandled Error Conditions (#585)

- Only attempt to close the connection once it's established.
- Defer the client Quit/Close so that it always executes at the end.
- Fixes #585
pull/592/head
James Elliott 2020-01-28 15:00:43 +11:00 committed by Amir Zarrinkafsh
parent 722cbb63a0
commit 152b33e4fa
1 changed files with 32 additions and 36 deletions

View File

@ -14,7 +14,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// SMTPNotifier a notifier to send emails to SMTP servers // SMTPNotifier a notifier to send emails to SMTP servers.
type SMTPNotifier struct { type SMTPNotifier struct {
username string username string
password string password string
@ -29,7 +29,7 @@ type SMTPNotifier struct {
tlsConfig *tls.Config tlsConfig *tls.Config
} }
// NewSMTPNotifier create an SMTPNotifier targeting a given address // NewSMTPNotifier create an SMTPNotifier targeting a given address.
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier { func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier {
notifier := &SMTPNotifier{ notifier := &SMTPNotifier{
username: configuration.Username, username: configuration.Username,
@ -47,15 +47,15 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi
} }
func (n *SMTPNotifier) initializeTLSConfig() { func (n *SMTPNotifier) initializeTLSConfig() {
// Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded // 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 // The second part of this check happens in the Configure Cert Pool code block.
log.Debug("Notifier SMTP client initializing TLS configuration") log.Debug("Notifier SMTP client initializing TLS configuration")
insecureSkipVerify := false insecureSkipVerify := false
if n.disableVerifyCert { if n.disableVerifyCert {
insecureSkipVerify = true insecureSkipVerify = true
} }
//Configure Cert Pool //Configure Cert Pool.
certPool, err := x509.SystemCertPool() certPool, err := x509.SystemCertPool()
if err != nil || certPool == nil { if err != nil || certPool == nil {
certPool = x509.NewCertPool() certPool = x509.NewCertPool()
@ -89,9 +89,9 @@ func (n *SMTPNotifier) initializeTLSConfig() {
} }
} }
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred) // Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
func (n *SMTPNotifier) startTLS() (bool, error) { func (n *SMTPNotifier) startTLS() (bool, error) {
// Only start if not already encrypted // Only start if not already encrypted.
if _, ok := n.client.TLSConnectionState(); ok { if _, ok := n.client.TLSConnectionState(); ok {
log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS") log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
return ok, nil return ok, nil
@ -115,16 +115,16 @@ func (n *SMTPNotifier) startTLS() (bool, error) {
return ok, nil return ok, nil
} }
// Attempt Authentication // Attempt Authentication.
func (n *SMTPNotifier) auth() (bool, error) { func (n *SMTPNotifier) auth() (bool, error) {
// Attempt AUTH if password is specified only // Attempt AUTH if password is specified only.
if n.password != "" { if n.password != "" {
_, ok := n.client.TLSConnectionState() _, ok := n.client.TLSConnectionState()
if !ok { if !ok {
return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text") return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
} }
// Check the server supports AUTH, and get the mechanisms // Check the server supports AUTH, and get the mechanisms.
ok, m := n.client.Extension("AUTH") ok, m := n.client.Extension("AUTH")
if ok { if ok {
log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m) log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
@ -140,12 +140,12 @@ func (n *SMTPNotifier) auth() (bool, error) {
log.Debug("Notifier SMTP client attempting AUTH LOGIN with server") log.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
} }
// Throw error since AUTH extension is not supported // Throw error since AUTH extension is not supported.
if auth == nil { if auth == nil {
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) 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)
} }
// Authenticate // Authenticate.
err := n.client.Auth(auth) err := n.client.Auth(auth)
if err != nil { if err != nil {
return false, err return false, err
@ -196,7 +196,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error {
return nil return nil
} }
// Dial the SMTP server with the SMTPNotifier config // Dial the SMTP server with the SMTPNotifier config.
func (n *SMTPNotifier) dial() error { func (n *SMTPNotifier) dial() error {
log.Debugf("Notifier SMTP client attempting connection to %s", n.address) log.Debugf("Notifier SMTP client attempting connection to %s", n.address)
client, err := smtp.Dial(n.address) client, err := smtp.Dial(n.address)
@ -208,50 +208,46 @@ func (n *SMTPNotifier) dial() error {
return nil return nil
} }
// 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)
}
}
// Send an email // Send an email
func (n *SMTPNotifier) Send(recipient, subject, body string) error { func (n *SMTPNotifier) Send(recipient, subject, body string) error {
err := n.dial() if err := n.dial(); err != nil {
if err != nil {
_ = n.client.Close()
return err return err
} }
_, err = n.startTLS() // Always execute QUIT at the end once we're connected.
if err != nil { defer n.cleanup()
_ = n.client.Close()
// Start TLS and then Authenticate.
if _, err := n.startTLS(); err != nil {
return err
}
if _, err := n.auth(); err != nil {
return err return err
} }
_, err = n.auth() // Set the sender and recipient first.
if err != nil {
_ = n.client.Close()
return err
}
// Set the sender and recipient first
if err := n.client.Mail(n.sender); err != nil { if err := n.client.Mail(n.sender); err != nil {
log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err) log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
_ = n.client.Close()
return err return err
} }
if err := n.client.Rcpt(recipient); err != nil { if err := n.client.Rcpt(recipient); err != nil {
log.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err) log.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err)
_ = n.client.Close()
return err return err
} }
// compose and send the email body // Compose and send the email body to the server.
if err := n.compose(recipient, subject, body); err != nil { if err := n.compose(recipient, subject, body); err != nil {
_ = n.client.Close()
return err return err
} }
// Send the QUIT command and close the connection
err = n.client.Quit()
if err != nil {
_ = n.client.Close()
return err
}
log.Debug("Notifier SMTP client successfully sent email") log.Debug("Notifier SMTP client successfully sent email")
return nil return nil
} }