From 7b8ed46537f8e7552a6fee53f3645f9af2567cec Mon Sep 17 00:00:00 2001 From: James Elliott Date: Mon, 26 Dec 2022 21:39:19 +1100 Subject: [PATCH] fix(notification): smtp auth not configured (#4647) This fixes an issue introduced by the pending migration to the new SMTP library in 0bb657e where the auth mechanism is never defined. This only affects commits that are yet to be versioned. --- internal/notification/smtp_auth.go | 86 ++++++++++++++++++++++++++ internal/notification/smtp_notifier.go | 9 ++- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 internal/notification/smtp_auth.go diff --git a/internal/notification/smtp_auth.go b/internal/notification/smtp_auth.go new file mode 100644 index 000000000..fb5d14f46 --- /dev/null +++ b/internal/notification/smtp_auth.go @@ -0,0 +1,86 @@ +package notification + +import ( + "fmt" + "net/smtp" + "strings" + + gomail "github.com/wneessen/go-mail" + "github.com/wneessen/go-mail/auth" + + "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/utils" +) + +// NewOpportunisticSMTPAuth is an opportunistic smtp.Auth implementation. +func NewOpportunisticSMTPAuth(config *schema.SMTPNotifierConfiguration) *OpportunisticSMTPAuth { + if config.Username == "" && config.Password == "" { + return nil + } + + return &OpportunisticSMTPAuth{ + username: config.Username, + password: config.Password, + host: config.Host, + } +} + +// OpportunisticSMTPAuth is an opportunistic smtp.Auth implementation. +type OpportunisticSMTPAuth struct { + username, password, host string + + satPreference []gomail.SMTPAuthType + sa smtp.Auth +} + +// Start begins an authentication with a server. +// It returns the name of the authentication protocol +// and optionally data to include in the initial AUTH message +// sent to the server. +// If it returns a non-nil error, the SMTP client aborts +// the authentication attempt and closes the connection. +func (a *OpportunisticSMTPAuth) Start(server *smtp.ServerInfo) (proto string, toServer []byte, err error) { + for _, pref := range a.satPreference { + if utils.IsStringInSlice(string(pref), server.Auth) { + switch pref { + case gomail.SMTPAuthPlain: + a.sa = smtp.PlainAuth("", a.username, a.password, a.host) + case gomail.SMTPAuthLogin: + a.sa = auth.LoginAuth(a.username, a.password, a.host) + case gomail.SMTPAuthCramMD5: + a.sa = smtp.CRAMMD5Auth(a.username, a.password) + } + + break + } + } + + if a.sa == nil { + for _, sa := range server.Auth { + switch gomail.SMTPAuthType(sa) { + case gomail.SMTPAuthPlain: + a.sa = smtp.PlainAuth("", a.username, a.password, a.host) + case gomail.SMTPAuthLogin: + a.sa = auth.LoginAuth(a.username, a.password, a.host) + case gomail.SMTPAuthCramMD5: + a.sa = smtp.CRAMMD5Auth(a.username, a.password) + } + } + } + + if a.sa == nil { + return "", nil, fmt.Errorf("unsupported SMTP AUTH types: %s", strings.Join(server.Auth, ", ")) + } + + return a.sa.Start(server) +} + +// Next continues the authentication. The server has just sent +// the fromServer data. If more is true, the server expects a +// response, which Next should return as toServer; otherwise +// Next should return toServer == nil. +// If Next returns a non-nil error, the SMTP client aborts +// the authentication attempt and closes the connection. +func (a *OpportunisticSMTPAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) { + return a.sa.Next(fromServer, more) +} diff --git a/internal/notification/smtp_notifier.go b/internal/notification/smtp_notifier.go index af4a954d0..0af580b73 100644 --- a/internal/notification/smtp_notifier.go +++ b/internal/notification/smtp_notifier.go @@ -22,7 +22,6 @@ func NewSMTPNotifier(config *schema.SMTPNotifierConfiguration, certPool *x509.Ce opts := []gomail.Option{ gomail.WithPort(config.Port), gomail.WithTLSConfig(utils.NewTLSConfig(config.TLS, certPool)), - gomail.WithPassword(config.Password), gomail.WithHELO(config.Identifier), } @@ -65,6 +64,7 @@ type SMTPNotifier struct { opts []gomail.Option } +// StartupCheck implements model.StartupCheck to perform startup check operations. func (n *SMTPNotifier) StartupCheck() (err error) { var client *gomail.Client @@ -85,6 +85,7 @@ func (n *SMTPNotifier) StartupCheck() (err error) { return nil } +// Send a notification via the SMTPNotifier. func (n *SMTPNotifier) Send(ctx context.Context, recipient mail.Address, subject string, et *templates.EmailTemplate, data any) (err error) { msg := gomail.NewMsg( gomail.WithMIMEVersion(gomail.Mime10), @@ -118,10 +119,16 @@ func (n *SMTPNotifier) Send(ctx context.Context, recipient mail.Address, subject var client *gomail.Client + n.log.Debugf("creating client with %d options: %+v", len(n.opts), n.opts) + if client, err = gomail.NewClient(n.config.Host, n.opts...); err != nil { return fmt.Errorf("notifier: smtp: failed to establish client: %w", err) } + if auth := NewOpportunisticSMTPAuth(n.config); auth != nil { + client.SetSMTPAuthCustom(auth) + } + if err = client.DialWithContext(ctx); err != nil { return fmt.Errorf("notifier: smtp: failed to dial connection: %w", err) }