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