Source file src/net/smtp/auth.go
1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package smtp 6 7 import ( 8 "crypto/hmac" 9 "crypto/md5" 10 "errors" 11 "fmt" 12 ) 13 14 // Auth is implemented by an SMTP authentication mechanism. 15 type Auth interface { 16 // Start begins an authentication with a server. 17 // It returns the name of the authentication protocol 18 // and optionally data to include in the initial AUTH message 19 // sent to the server. 20 // If it returns a non-nil error, the SMTP client aborts 21 // the authentication attempt and closes the connection. 22 Start(server *ServerInfo) (proto string, toServer []byte, err error) 23 24 // Next continues the authentication. The server has just sent 25 // the fromServer data. If more is true, the server expects a 26 // response, which Next should return as toServer; otherwise 27 // Next should return toServer == nil. 28 // If Next returns a non-nil error, the SMTP client aborts 29 // the authentication attempt and closes the connection. 30 Next(fromServer []byte, more bool) (toServer []byte, err error) 31 } 32 33 // ServerInfo records information about an SMTP server. 34 type ServerInfo struct { 35 Name string // SMTP server name 36 TLS bool // using TLS, with valid certificate for Name 37 Auth []string // advertised authentication mechanisms 38 } 39 40 type plainAuth struct { 41 identity, username, password string 42 host string 43 } 44 45 // PlainAuth returns an [Auth] that implements the PLAIN authentication 46 // mechanism as defined in RFC 4616. The returned Auth uses the given 47 // username and password to authenticate to host and act as identity. 48 // Usually identity should be the empty string, to act as username. 49 // 50 // PlainAuth will only send the credentials if the connection is using TLS 51 // or is connected to localhost. Otherwise authentication will fail with an 52 // error, without sending the credentials. 53 func PlainAuth(identity, username, password, host string) Auth { 54 return &plainAuth{identity, username, password, host} 55 } 56 57 func isLocalhost(name string) bool { 58 return name == "localhost" || name == "127.0.0.1" || name == "::1" 59 } 60 61 func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) { 62 // Must have TLS, or else localhost server. 63 // Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo. 64 // In particular, it doesn't matter if the server advertises PLAIN auth. 65 // That might just be the attacker saying 66 // "it's ok, you can trust me with your password." 67 if !server.TLS && !isLocalhost(server.Name) { 68 return "", nil, errors.New("unencrypted connection") 69 } 70 if server.Name != a.host { 71 return "", nil, errors.New("wrong host name") 72 } 73 resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password) 74 return "PLAIN", resp, nil 75 } 76 77 func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) { 78 if more { 79 // We've already sent everything. 80 return nil, errors.New("unexpected server challenge") 81 } 82 return nil, nil 83 } 84 85 type cramMD5Auth struct { 86 username, secret string 87 } 88 89 // CRAMMD5Auth returns an [Auth] that implements the CRAM-MD5 authentication 90 // mechanism as defined in RFC 2195. 91 // The returned Auth uses the given username and secret to authenticate 92 // to the server using the challenge-response mechanism. 93 func CRAMMD5Auth(username, secret string) Auth { 94 return &cramMD5Auth{username, secret} 95 } 96 97 func (a *cramMD5Auth) Start(server *ServerInfo) (string, []byte, error) { 98 return "CRAM-MD5", nil, nil 99 } 100 101 func (a *cramMD5Auth) Next(fromServer []byte, more bool) ([]byte, error) { 102 if more { 103 d := hmac.New(md5.New, []byte(a.secret)) 104 d.Write(fromServer) 105 s := make([]byte, 0, d.Size()) 106 return fmt.Appendf(nil, "%s %x", a.username, d.Sum(s)), nil 107 } 108 return nil, nil 109 } 110