forked from go-sql-driver/mysql
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathauth.go
139 lines (112 loc) · 3.37 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
package mysql
import "bytes"
const (
mysqlClearPassword = "mysql_clear_password"
mysqlNativePassword = "mysql_native_password"
mysqlOldPassword = "mysql_old_password"
defaultAuthPluginName = mysqlNativePassword
)
var authPluginFactories map[string]func(*Config) AuthPlugin
func init() {
authPluginFactories = make(map[string]func(*Config) AuthPlugin)
authPluginFactories[mysqlClearPassword] = func(cfg *Config) AuthPlugin {
return &clearTextPlugin{cfg}
}
authPluginFactories[mysqlNativePassword] = func(cfg *Config) AuthPlugin {
return &nativePasswordPlugin{cfg}
}
authPluginFactories[mysqlOldPassword] = func(cfg *Config) AuthPlugin {
return &oldPasswordPlugin{cfg}
}
}
// RegisterAuthPlugin registers an authentication plugin to be used during
// negotiation with the server. If a plugin with the given name already exists,
// it will be overwritten.
func RegisterAuthPlugin(name string, factory func(*Config) AuthPlugin) {
authPluginFactories[name] = factory
}
// AuthPlugin handles authenticating a user.
type AuthPlugin interface {
// Next takes a server's challenge and returns
// the bytes to send back or an error.
Next(challenge []byte) ([]byte, error)
// Close cleans up the resources of the plugin.
Close()
}
type clearTextPlugin struct {
cfg *Config
}
func (p *clearTextPlugin) Next(challenge []byte) ([]byte, error) {
if !p.cfg.AllowCleartextPasswords {
return nil, ErrCleartextPassword
}
// NUL-terminated
return append([]byte(p.cfg.Passwd), 0), nil
}
func (p *clearTextPlugin) Close() {}
type nativePasswordPlugin struct {
cfg *Config
}
func (p *nativePasswordPlugin) Next(challenge []byte) ([]byte, error) {
// NOTE: this seems to always be disabled...
// if !p.cfg.AllowNativePasswords {
// return nil, ErrNativePassword
// }
return scramblePassword(challenge, []byte(p.cfg.Passwd)), nil
}
func (p *nativePasswordPlugin) Close() {}
type oldPasswordPlugin struct {
cfg *Config
}
func (p *oldPasswordPlugin) Next(challenge []byte) ([]byte, error) {
if !p.cfg.AllowOldPasswords {
return nil, ErrOldPassword
}
// NUL-terminated
return append(scrambleOldPassword(challenge, []byte(p.cfg.Passwd)), 0), nil
}
func (p *oldPasswordPlugin) Close() {}
func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
data, err := mc.readPacket()
if err != nil {
return err
}
var authData []byte
// packet indicator
switch data[0] {
case iOK:
return mc.handleOkPacket(data)
case iEOF: // auth switch
mc.authPlugin.Close()
if len(data) > 1 {
pluginEndIndex := bytes.IndexByte(data, 0x00)
pluginName := string(data[1:pluginEndIndex])
if apf, ok := authPluginFactories[pluginName]; ok {
mc.authPlugin = apf(mc.cfg)
} else {
return ErrUnknownPlugin
}
if len(data) > pluginEndIndex+1 {
authData = data[pluginEndIndex+1 : len(data)-1]
}
} else {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
mc.authPlugin = authPluginFactories[mysqlOldPassword](mc.cfg)
authData = oldCipher
}
case iAuthContinue:
// continue packet for a plugin.
authData = data[1:] // strip off the continue flag
default: // Error otherwise
return mc.handleErrorPacket(data)
}
authData, err = mc.authPlugin.Next(authData)
if err != nil {
return err
}
err = mc.writeAuthDataPacket(authData)
if err != nil {
return err
}
return handleAuthResult(mc, authData)
}