From ac9117dc500ef9057373f019d2b6deeaa4ac61cc Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 16 Nov 2024 14:29:34 +0100 Subject: [PATCH] Add SMTP authentication auto-discovery Implemented a mechanism to automatically discover and select the strongest supported SMTP authentication type. This feature simplifies the authentication process for users and enhances security by prioritizing stronger mechanisms based on server capabilities. Corresponding tests and documentation have been updated. --- auth.go | 20 ++++++++++++++++++++ client.go | 36 +++++++++++++++++++++++++++++++++++- client_test.go | 5 +++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/auth.go b/auth.go index 66254ee..e4adfe6 100644 --- a/auth.go +++ b/auth.go @@ -136,6 +136,21 @@ const ( // // https://datatracker.ietf.org/doc/html/rfc7677 SMTPAuthSCRAMSHA256PLUS SMTPAuthType = "SCRAM-SHA-256-PLUS" + + // SMTPAuthAutoDiscover is a mechanism that dynamically discovers all authentication mechanisms + // supported by the SMTP server and selects the strongest available one. + // + // This type simplifies authentication by automatically negotiating the most secure mechanism + // offered by the server, based on a predefined security ranking. For instance, mechanisms like + // SCRAM-SHA-256(-PLUS) or XOAUTH2 are prioritized over weaker mechanisms such as CRAM-MD5 or PLAIN. + // + // The negotiation process ensures that mechanisms requiring additional capabilities (e.g., + // SCRAM-SHA-X-PLUS with TLS channel binding) are only selected when the necessary prerequisites + // are in place, such as an active TLS-secured connection. + // + // By automating mechanism selection, SMTPAuthAutoDiscover minimizes configuration effort while + // maximizing security and compatibility with a wide range of SMTP servers. + SMTPAuthAutoDiscover SMTPAuthType = "AUTODISCOVER" ) // SMTP Auth related static errors @@ -170,6 +185,11 @@ var ( // ErrSCRAMSHA256PLUSAuthNotSupported is returned when the server does not support the "SCRAM-SHA-256-PLUS" SMTP // authentication type. ErrSCRAMSHA256PLUSAuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-256-PLUS") + + // ErrNoSupportedAuthDiscovered is returned when the SMTP Auth AutoDiscover process fails to identify + // any supported authentication mechanisms offered by the server. + ErrNoSupportedAuthDiscovered = errors.New("SMTP Auth autodiscover was not able to detect a supported " + + "authentication mechanism") ) // UnmarshalString satisfies the fig.StringUnmarshaler interface for the SMTPAuthType type diff --git a/client.go b/client.go index 9b3251e..f92c991 100644 --- a/client.go +++ b/client.go @@ -1100,7 +1100,16 @@ func (c *Client) auth() error { return fmt.Errorf("server does not support SMTP AUTH") } - switch c.smtpAuthType { + authType := c.smtpAuthType + if c.smtpAuthType == SMTPAuthAutoDiscover { + discoveredType, err := c.authTypeAutoDiscover(smtpAuthType) + if err != nil { + return err + } + authType = discoveredType + } + + switch authType { case SMTPAuthPlain: if !strings.Contains(smtpAuthType, string(SMTPAuthPlain)) { return ErrPlainAuthNotSupported @@ -1172,6 +1181,31 @@ func (c *Client) auth() error { return nil } +func (c *Client) authTypeAutoDiscover(supported string) (SMTPAuthType, error) { + preferList := []SMTPAuthType{SMTPAuthSCRAMSHA256PLUS, SMTPAuthSCRAMSHA256, SMTPAuthSCRAMSHA1PLUS, SMTPAuthSCRAMSHA1, + SMTPAuthXOAUTH2, SMTPAuthCramMD5, SMTPAuthPlain, SMTPAuthLogin} + if !c.isEncrypted { + preferList = []SMTPAuthType{SMTPAuthSCRAMSHA256, SMTPAuthSCRAMSHA1, SMTPAuthXOAUTH2, SMTPAuthCramMD5} + } + mechs := strings.Split(supported, " ") + + for _, item := range preferList { + if sliceContains(mechs, string(item)) { + return item, nil + } + } + return "", ErrNoSupportedAuthDiscovered +} + +func sliceContains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + // sendSingleMsg sends out a single message and returns an error if the transmission or // delivery fails. It is invoked by the public Send methods. // diff --git a/client_test.go b/client_test.go index 2ac8811..5138c17 100644 --- a/client_test.go +++ b/client_test.go @@ -2304,6 +2304,11 @@ func TestClient_auth(t *testing.T) { name string authType SMTPAuthType }{ + {"LOGIN via AUTODISCOVER", SMTPAuthAutoDiscover}, + {"PLAIN via AUTODISCOVER", SMTPAuthAutoDiscover}, + {"SCRAM-SHA-1 via AUTODISCOVER", SMTPAuthAutoDiscover}, + {"SCRAM-SHA-256 via AUTODISCOVER", SMTPAuthAutoDiscover}, + {"XOAUTH2 via AUTODISCOVER", SMTPAuthAutoDiscover}, {"CRAM-MD5", SMTPAuthCramMD5}, {"LOGIN", SMTPAuthLogin}, {"LOGIN-NOENC", SMTPAuthLoginNoEnc},