implement the obsolete sasl login mechanism for smtp

so microsoft outlook "new" can login. that's the "new" email client that logs
in from cloud servers.
This commit is contained in:
Mechiel Lukkien 2023-11-22 21:44:55 +01:00
parent c66fa64b8b
commit 91b7d3dda8
No known key found for this signature in database
3 changed files with 76 additions and 3 deletions

View file

@ -53,6 +53,35 @@ func (a *clientPlain) Next(fromServer []byte) (toServer []byte, last bool, rerr
} }
} }
type clientLogin struct {
Username, Password string
step int
}
var _ Client = (*clientLogin)(nil)
// NewClientLogin returns a client for the obsolete SASL LOGIN authentication.
// See https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
func NewClientLogin(username, password string) Client {
return &clientLogin{username, password, 0}
}
func (a *clientLogin) Info() (name string, hasCleartextCredentials bool) {
return "LOGIN", true
}
func (a *clientLogin) Next(fromServer []byte) (toServer []byte, last bool, rerr error) {
defer func() { a.step++ }()
switch a.step {
case 0:
return []byte(a.Username), false, nil
case 1:
return []byte(a.Password), true, nil
default:
return nil, false, fmt.Errorf("invalid step %d", a.step)
}
}
type clientCRAMMD5 struct { type clientCRAMMD5 struct {
Username, Password string Username, Password string
step int step int

View file

@ -843,7 +843,7 @@ func (c *conn) cmdHello(p *parser, ehlo bool) {
if c.submission { if c.submission {
// ../rfc/4954:123 // ../rfc/4954:123
if c.tls || !c.requireTLSForAuth { if c.tls || !c.requireTLSForAuth {
c.bwritelinef("250-AUTH SCRAM-SHA-256 SCRAM-SHA-1 CRAM-MD5 PLAIN") c.bwritelinef("250-AUTH SCRAM-SHA-256 SCRAM-SHA-1 CRAM-MD5 PLAIN LOGIN")
} else { } else {
c.bwritelinef("250-AUTH ") c.bwritelinef("250-AUTH ")
} }
@ -949,8 +949,6 @@ func (c *conn) cmdAuth(p *parser) {
} }
}() }()
// todo: implement "AUTH LOGIN"? it looks like PLAIN, but without the continuation. it is an obsolete sasl mechanism. an account in desktop outlook appears to go through the cloud, attempting to submit email only with unadvertised and AUTH LOGIN. it appears they don't know "plain".
// ../rfc/4954:699 // ../rfc/4954:699
p.xspace() p.xspace()
mech := p.xsaslMech() mech := p.xsaslMech()
@ -1049,6 +1047,51 @@ func (c *conn) cmdAuth(p *parser) {
// ../rfc/4954:276 // ../rfc/4954:276
c.writecodeline(smtp.C235AuthSuccess, smtp.SePol7Other0, "nice", nil) c.writecodeline(smtp.C235AuthSuccess, smtp.SePol7Other0, "nice", nil)
case "LOGIN":
// LOGIN is obsoleted in favor of PLAIN, only implemented to support legacy
// clients, see Internet-Draft (I-D):
// https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
authVariant = "login"
// ../rfc/4954:343
// ../rfc/4954:326
if !c.tls && c.requireTLSForAuth {
xsmtpUserErrorf(smtp.C538EncReqForAuth, smtp.SePol7EncReqForAuth11, "authentication requires tls")
}
// Read user name. The I-D says the client should ignore the server challenge, we
// send an empty one.
// I-D says maximum length must be 64 bytes. We allow more, for long user names
// (domains).
username := string(xreadInitial())
// Again, client should ignore the challenge, we send the same as the example in
// the I-D.
c.writelinef("%d %s", smtp.C334ContinueAuth, base64.StdEncoding.EncodeToString([]byte("Password")))
// Password is in line in plain text, so hide it.
defer c.xtrace(mlog.LevelTraceauth)()
password := string(xreadContinuation())
c.xtrace(mlog.LevelTrace) // Restore.
acc, err := store.OpenEmailAuth(username, password)
if err != nil && errors.Is(err, store.ErrUnknownCredentials) {
// ../rfc/4954:274
authResult = "badcreds"
c.log.Info("failed authentication attempt", mlog.Field("username", username), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass")
}
xcheckf(err, "verifying credentials")
authResult = "ok"
c.authFailed = 0
c.setSlow(false)
c.account = acc
c.username = username
// ../rfc/4954:276
c.writecodeline(smtp.C235AuthSuccess, smtp.SePol7Other0, "hello ancient smtp implementation", nil)
case "CRAM-MD5": case "CRAM-MD5":
authVariant = strings.ToLower(mech) authVariant = strings.ToLower(mech)

View file

@ -256,6 +256,7 @@ func TestSubmission(t *testing.T) {
testAuth(nil, "", "", &smtpclient.Error{Permanent: true, Code: smtp.C530SecurityRequired, Secode: smtp.SePol7Other0}) testAuth(nil, "", "", &smtpclient.Error{Permanent: true, Code: smtp.C530SecurityRequired, Secode: smtp.SePol7Other0})
authfns := []func(user, pass string) sasl.Client{ authfns := []func(user, pass string) sasl.Client{
sasl.NewClientPlain, sasl.NewClientPlain,
sasl.NewClientLogin,
sasl.NewClientCRAMMD5, sasl.NewClientCRAMMD5,
sasl.NewClientSCRAMSHA1, sasl.NewClientSCRAMSHA1,
sasl.NewClientSCRAMSHA256, sasl.NewClientSCRAMSHA256,