-
-
Notifications
You must be signed in to change notification settings - Fork 100
/
auth.go
145 lines (125 loc) · 4.75 KB
/
auth.go
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 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 https://raw.githubusercontent.com/golang/go/master/LICENSE
// auth.go file is a modification of smtp golang package what is frozen and is not accepting new features.
package mail
import (
"crypto/hmac"
"crypto/md5"
"errors"
"fmt"
"strings"
)
// 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
}
// plainAuthfn 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.
//
// plainAuthfn 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 plainAuthfn(identity, username, password, host string) auth {
return &plainAuth{identity, username, password, host}
}
func (a *plainAuth) start(server *serverInfo) (string, []byte, error) {
// Must have TLS, or else localhost server. Unencrypted connection is permitted here too but is not recommended
// 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.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
}
/*
loginAuthfn authentication implements LOGIN Authentication, is the same PLAIN
but username and password are sent in different commands
*/
type loginAuth struct {
identity, username, password string
host string
}
func loginAuthfn(identity, username, password, host string) auth {
return &loginAuth{identity, username, password, host}
}
func (a *loginAuth) start(server *serverInfo) (string, []byte, error) {
if server.name != a.host {
return "", nil, errors.New("wrong host name")
}
resp := []byte(a.username)
return "LOGIN", resp, nil
}
func (a *loginAuth) next(fromServer []byte, more bool) ([]byte, error) {
if more {
if strings.Contains(string(fromServer), "Username") {
resp := []byte(a.username)
return resp, nil
}
if strings.Contains(string(fromServer), "Password") {
resp := []byte(a.password)
return resp, nil
}
// We've already sent everything.
return nil, errors.New("unexpected server challenge")
}
return nil, nil
}
type cramMD5Auth struct {
username, secret string
}
// cramMD5Authfn 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 cramMD5Authfn(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
}