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