Skip to content

Commit

Permalink
Add SMTP authentication auto-discovery
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wneessen committed Nov 16, 2024
1 parent a4fa95f commit ac9117d
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 1 deletion.
20 changes: 20 additions & 0 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
36 changes: 35 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
//
Expand Down
5 changes: 5 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down

0 comments on commit ac9117d

Please sign in to comment.