From 3c29f68cc131f41cca0802321f5e3b00fa6412d8 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Tue, 22 Oct 2024 15:38:51 +0200 Subject: [PATCH] Add support for unsecured SMTP LOGIN auth Implemented an option to allow SMTP LOGIN authentication over unencrypted channels by introducing a new `SMTPAuthLoginNoEnc` type. Updated relevant functions and tests to handle the new parameter for unsecured authentication. --- auth.go | 21 ++++++++++++++++++++- client.go | 7 ++++++- client_test.go | 4 ++-- smtp/auth_login.go | 13 +++++++------ 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/auth.go b/auth.go index 90a032a..5797731 100644 --- a/auth.go +++ b/auth.go @@ -48,6 +48,25 @@ const ( // https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 SMTPAuthLogin SMTPAuthType = "LOGIN" + // SMTPAuthLoginNoEnc is the "LOGIN" SASL authentication mechanism. This authentication mechanism + // does not have an official RFC that could be followed. There is a spec by Microsoft and an + // IETF draft. The IETF draft is more lax than the MS spec, therefore we follow the I-D, which + // automatically matches the MS spec. + // + // Since the "LOGIN" SASL authentication mechanism transmits the username and password in + // plaintext over the internet connection, by default we only allow this mechanism over + // a TLS secured connection. This authentiation mechanism overrides this default and will + // allow LOGIN authentication via an unencrypted channel. This can be useful if the + // connection has already been secured in a different way (e. g. a SSH tunnel) + // + // Note: Use this authentication method with caution. If used in the wrong way, you might + // expose your authentication information over unencrypted channels! + // + // https://msopenspecs.azureedge.net/files/MS-XLOGIN/%5bMS-XLOGIN%5d.pdf + // + // https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 + SMTPAuthLoginNoEnc SMTPAuthType = "LOGIN-NOENC" + // SMTPAuthNoAuth is equivalent to performing no authentication at all. It is a convenience // option and should not be used. Instead, for mail servers that do no support/require // authentication, the Client should not be passed the WithSMTPAuth option at all. @@ -65,7 +84,7 @@ const ( // SMTPAuthPlainNoEnc is the "PLAIN" authentication mechanism as described in RFC 4616. // // Since the "PLAIN" SASL authentication mechanism transmits the username and password in - // plaintext over the internet connection, bye default we only allow this mechanism over + // plaintext over the internet connection, by default we only allow this mechanism over // a TLS secured connection. This authentiation mechanism overrides this default and will // allow PLAIN authentication via an unencrypted channel. This can be useful if the // connection has already been secured in a different way (e. g. a SSH tunnel) diff --git a/client.go b/client.go index a3e8067..3f36a94 100644 --- a/client.go +++ b/client.go @@ -1106,7 +1106,12 @@ func (c *Client) auth() error { if !strings.Contains(smtpAuthType, string(SMTPAuthLogin)) { return ErrLoginAuthNotSupported } - c.smtpAuth = smtp.LoginAuth(c.user, c.pass, c.host) + c.smtpAuth = smtp.LoginAuth(c.user, c.pass, c.host, false) + case SMTPAuthLoginNoEnc: + if !strings.Contains(smtpAuthType, string(SMTPAuthLogin)) { + return ErrLoginAuthNotSupported + } + c.smtpAuth = smtp.LoginAuth(c.user, c.pass, c.host, true) case SMTPAuthCramMD5: if !strings.Contains(smtpAuthType, string(SMTPAuthCramMD5)) { return ErrCramMD5AuthNotSupported diff --git a/client_test.go b/client_test.go index 86bc60e..8fef617 100644 --- a/client_test.go +++ b/client_test.go @@ -605,7 +605,7 @@ func TestSetSMTPAuthCustom(t *testing.T) { sf bool }{ {"SMTPAuth: CRAM-MD5", smtp.CRAMMD5Auth("", ""), "CRAM-MD5", false}, - {"SMTPAuth: LOGIN", smtp.LoginAuth("", "", ""), "LOGIN", false}, + {"SMTPAuth: LOGIN", smtp.LoginAuth("", "", "", false), "LOGIN", false}, {"SMTPAuth: PLAIN", smtp.PlainAuth("", "", "", "", false), "PLAIN", false}, } si := smtp.ServerInfo{TLS: true} @@ -807,7 +807,7 @@ func TestClient_DialWithContextInvalidAuth(t *testing.T) { } c.user = "invalid" c.pass = "invalid" - c.SetSMTPAuthCustom(smtp.LoginAuth("invalid", "invalid", "invalid")) + c.SetSMTPAuthCustom(smtp.LoginAuth("invalid", "invalid", "invalid", false)) ctx := context.Background() if err = c.DialWithContext(ctx); err == nil { t.Errorf("dial succeeded but was supposed to fail") diff --git a/smtp/auth_login.go b/smtp/auth_login.go index a9bbdf5..b5f1065 100644 --- a/smtp/auth_login.go +++ b/smtp/auth_login.go @@ -10,9 +10,10 @@ import ( // loginAuth is the type that satisfies the Auth interface for the "SMTP LOGIN" auth type loginAuth struct { - username, password string - host string - respStep uint8 + username, password string + host string + respStep uint8 + allowUnencryptedAuth bool } // LoginAuth returns an [Auth] that implements the LOGIN authentication @@ -35,8 +36,8 @@ type loginAuth struct { // LoginAuth will only send the credentials if the connection is using TLS // or is connected to localhost. Otherwise authentication will fail with an // error, without sending the credentials. -func LoginAuth(username, password, host string) Auth { - return &loginAuth{username, password, host, 0} +func LoginAuth(username, password, host string, allowUnEnc bool) Auth { + return &loginAuth{username, password, host, 0, allowUnEnc} } // Start begins the SMTP authentication process by validating server's TLS status and hostname. @@ -47,7 +48,7 @@ func (a *loginAuth) Start(server *ServerInfo) (string, []byte, error) { // In particular, it doesn't matter if the server advertises LOGIN auth. // That might just be the attacker saying // "it's ok, you can trust me with your password." - if !server.TLS && !isLocalhost(server.Name) { + if !a.allowUnencryptedAuth && !server.TLS && !isLocalhost(server.Name) { return "", nil, ErrUnencrypted } if server.Name != a.host {