mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-15 02:12:55 +01:00
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.
This commit is contained in:
parent
f5531eae14
commit
3c29f68cc1
4 changed files with 35 additions and 10 deletions
21
auth.go
21
auth.go
|
@ -48,6 +48,25 @@ const (
|
||||||
// https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
|
// https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
|
||||||
SMTPAuthLogin SMTPAuthType = "LOGIN"
|
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
|
// 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
|
// 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.
|
// 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.
|
// SMTPAuthPlainNoEnc is the "PLAIN" authentication mechanism as described in RFC 4616.
|
||||||
//
|
//
|
||||||
// Since the "PLAIN" SASL authentication mechanism transmits the username and password in
|
// 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
|
// 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
|
// 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)
|
// connection has already been secured in a different way (e. g. a SSH tunnel)
|
||||||
|
|
|
@ -1106,7 +1106,12 @@ func (c *Client) auth() error {
|
||||||
if !strings.Contains(smtpAuthType, string(SMTPAuthLogin)) {
|
if !strings.Contains(smtpAuthType, string(SMTPAuthLogin)) {
|
||||||
return ErrLoginAuthNotSupported
|
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:
|
case SMTPAuthCramMD5:
|
||||||
if !strings.Contains(smtpAuthType, string(SMTPAuthCramMD5)) {
|
if !strings.Contains(smtpAuthType, string(SMTPAuthCramMD5)) {
|
||||||
return ErrCramMD5AuthNotSupported
|
return ErrCramMD5AuthNotSupported
|
||||||
|
|
|
@ -605,7 +605,7 @@ func TestSetSMTPAuthCustom(t *testing.T) {
|
||||||
sf bool
|
sf bool
|
||||||
}{
|
}{
|
||||||
{"SMTPAuth: CRAM-MD5", smtp.CRAMMD5Auth("", ""), "CRAM-MD5", false},
|
{"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},
|
{"SMTPAuth: PLAIN", smtp.PlainAuth("", "", "", "", false), "PLAIN", false},
|
||||||
}
|
}
|
||||||
si := smtp.ServerInfo{TLS: true}
|
si := smtp.ServerInfo{TLS: true}
|
||||||
|
@ -807,7 +807,7 @@ func TestClient_DialWithContextInvalidAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
c.user = "invalid"
|
c.user = "invalid"
|
||||||
c.pass = "invalid"
|
c.pass = "invalid"
|
||||||
c.SetSMTPAuthCustom(smtp.LoginAuth("invalid", "invalid", "invalid"))
|
c.SetSMTPAuthCustom(smtp.LoginAuth("invalid", "invalid", "invalid", false))
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err = c.DialWithContext(ctx); err == nil {
|
if err = c.DialWithContext(ctx); err == nil {
|
||||||
t.Errorf("dial succeeded but was supposed to fail")
|
t.Errorf("dial succeeded but was supposed to fail")
|
||||||
|
|
|
@ -10,9 +10,10 @@ import (
|
||||||
|
|
||||||
// loginAuth is the type that satisfies the Auth interface for the "SMTP LOGIN" auth
|
// loginAuth is the type that satisfies the Auth interface for the "SMTP LOGIN" auth
|
||||||
type loginAuth struct {
|
type loginAuth struct {
|
||||||
username, password string
|
username, password string
|
||||||
host string
|
host string
|
||||||
respStep uint8
|
respStep uint8
|
||||||
|
allowUnencryptedAuth bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginAuth returns an [Auth] that implements the LOGIN authentication
|
// 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
|
// LoginAuth will only send the credentials if the connection is using TLS
|
||||||
// or is connected to localhost. Otherwise authentication will fail with an
|
// or is connected to localhost. Otherwise authentication will fail with an
|
||||||
// error, without sending the credentials.
|
// error, without sending the credentials.
|
||||||
func LoginAuth(username, password, host string) Auth {
|
func LoginAuth(username, password, host string, allowUnEnc bool) Auth {
|
||||||
return &loginAuth{username, password, host, 0}
|
return &loginAuth{username, password, host, 0, allowUnEnc}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start begins the SMTP authentication process by validating server's TLS status and hostname.
|
// 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.
|
// In particular, it doesn't matter if the server advertises LOGIN auth.
|
||||||
// That might just be the attacker saying
|
// That might just be the attacker saying
|
||||||
// "it's ok, you can trust me with your password."
|
// "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
|
return "", nil, ErrUnencrypted
|
||||||
}
|
}
|
||||||
if server.Name != a.host {
|
if server.Name != a.host {
|
||||||
|
|
Loading…
Reference in a new issue