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 5package smtp 6 7import ( 8 "crypto/hmac" 9 "crypto/md5" 10 "errors" 11 "fmt" 12) 13 14// Auth is implemented by an SMTP authentication mechanism. 15type 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. 35type 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 41type 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. 54func PlainAuth(identity, username, password, host string) Auth { 55 return &plainAuth{identity, username, password, host} 56} 57 58func isLocalhost(name string) bool { 59 return name == "localhost" || name == "127.0.0.1" || name == "::1" 60} 61 62func (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 78func (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 86type 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. 94func CRAMMD5Auth(username, secret string) Auth { 95 return &cramMD5Auth{username, secret} 96} 97 98func (a *cramMD5Auth) Start(server *ServerInfo) (string, []byte, error) { 99 return "CRAM-MD5", nil, nil 100} 101 102func (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