Home | History | Annotate | Download | only in smtp
      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