Skip to content

Commit 83ed16b

Browse files
committed
Require explicitly allowing old passwords
+ close connection if authentication fails
1 parent e78fff0 commit 83ed16b

7 files changed

+67
-46
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ For Unix domain sockets the address is the absolute path to the MySQL-Server-soc
107107

108108
Possible Parameters are:
109109
* `allowAllFiles`: `allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files. [*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
110+
* `allowOldPasswords`: `allowAllFiles=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
110111
* `charset`: Sets the charset used for client-server interaction ("SET NAMES `value`"). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
111112
* `clientFoundRows`: `clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
112113
* `loc`: Sets the location for time.Time values (when using `parseTime=true`). The default is `UTC`. *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.

connection.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
type mysqlConn struct {
2222
cfg *config
2323
flags clientFlag
24-
cipher []byte
2524
netConn net.Conn
2625
buf *buffer
2726
protocol uint8
@@ -35,17 +34,18 @@ type mysqlConn struct {
3534
}
3635

3736
type config struct {
38-
user string
39-
passwd string
40-
net string
41-
addr string
42-
dbname string
43-
params map[string]string
44-
loc *time.Location
45-
timeout time.Duration
46-
tls *tls.Config
47-
allowAllFiles bool
48-
clientFoundRows bool
37+
user string
38+
passwd string
39+
net string
40+
addr string
41+
dbname string
42+
params map[string]string
43+
loc *time.Location
44+
timeout time.Duration
45+
tls *tls.Config
46+
allowAllFiles bool
47+
allowOldPasswords bool
48+
clientFoundRows bool
4949
}
5050

5151
// Handles parameters set in DSN

driver.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,42 @@ func (d *MySQLDriver) Open(dsn string) (driver.Conn, error) {
5252
mc.buf = newBuffer(mc.netConn)
5353

5454
// Reading Handshake Initialization Packet
55-
err = mc.readInitPacket()
55+
cipher, err := mc.readInitPacket()
5656
if err != nil {
57+
mc.Close()
5758
return nil, err
5859
}
5960

6061
// Send Client Authentication Packet
61-
err = mc.writeAuthPacket()
62-
if err != nil {
62+
if err = mc.writeAuthPacket(cipher); err != nil {
63+
mc.Close()
6364
return nil, err
6465
}
6566

6667
// Read Result Packet
6768
err = mc.readResultOK()
6869
if err != nil {
69-
return nil, err
70+
// Retry with old authentication method, if allowed
71+
if mc.cfg.allowOldPasswords && err == errOldPassword {
72+
if err = mc.writeOldAuthPacket(cipher); err != nil {
73+
mc.Close()
74+
return nil, err
75+
}
76+
if err = mc.readResultOK(); err != nil {
77+
mc.Close()
78+
return nil, err
79+
}
80+
} else {
81+
mc.Close()
82+
return nil, err
83+
}
84+
7085
}
7186

7287
// Get max allowed packet size
7388
maxap, err := mc.getSystemVar("max_allowed_packet")
7489
if err != nil {
90+
mc.Close()
7591
return nil, err
7692
}
7793
mc.maxPacketAllowed = stringToInt(maxap) - 1
@@ -82,10 +98,11 @@ func (d *MySQLDriver) Open(dsn string) (driver.Conn, error) {
8298
// Handle DSN Params
8399
err = mc.handleParams()
84100
if err != nil {
101+
mc.Close()
85102
return nil, err
86103
}
87104

88-
return mc, err
105+
return mc, nil
89106
}
90107

91108
func init() {

errors.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var (
2020
errInvalidConn = errors.New("Invalid Connection")
2121
errMalformPkt = errors.New("Malformed Packet")
2222
errNoTLS = errors.New("TLS encryption requested but server does not support TLS")
23-
errOldPassword = errors.New("It seems like you are using old_passwords, which is unsupported. See https://github.com/go-sql-driver/mysql/wiki/old_passwords")
23+
errOldPassword = errors.New("This server only supports the insecure old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
2424
errOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
2525
errPktSync = errors.New("Commands out of sync. You can't run this command now")
2626
errPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")

packets.go

+13-19
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,14 @@ func (mc *mysqlConn) splitPacket(data []byte) (err error) {
138138

139139
// Handshake Initialization Packet
140140
// http://dev.mysql.com/doc/internals/en/connection-phase.html#packet-Protocol::Handshake
141-
func (mc *mysqlConn) readInitPacket() (err error) {
141+
func (mc *mysqlConn) readInitPacket() (cipher []byte, err error) {
142142
data, err := mc.readPacket()
143143
if err != nil {
144144
return
145145
}
146146

147147
if data[0] == iERR {
148-
return mc.handleErrorPacket(data)
148+
return nil, mc.handleErrorPacket(data)
149149
}
150150

151151
// protocol version [1 byte]
@@ -154,25 +154,26 @@ func (mc *mysqlConn) readInitPacket() (err error) {
154154
"Unsupported MySQL Protocol Version %d. Protocol Version %d or higher is required",
155155
data[0],
156156
minProtocolVersion)
157+
return
157158
}
158159

159160
// server version [null terminated string]
160161
// connection id [4 bytes]
161162
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
162163

163164
// first part of the password cipher [8 bytes]
164-
mc.cipher = append(mc.cipher, data[pos:pos+8]...)
165+
cipher = data[pos : pos+8]
165166

166167
// (filler) always 0x00 [1 byte]
167168
pos += 8 + 1
168169

169170
// capability flags (lower 2 bytes) [2 bytes]
170171
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
171172
if mc.flags&clientProtocol41 == 0 {
172-
err = errOldProtocol
173+
return nil, errOldProtocol
173174
}
174175
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
175-
return errNoTLS
176+
return nil, errNoTLS
176177
}
177178
pos += 2
178179

@@ -188,7 +189,7 @@ func (mc *mysqlConn) readInitPacket() (err error) {
188189
// The documentation is ambiguous about the length.
189190
// The official Python library uses the fixed length 12
190191
// which is not documented but seems to work.
191-
mc.cipher = append(mc.cipher, data[pos:pos+12]...)
192+
cipher = append(cipher, data[pos:pos+12]...)
192193

193194
// TODO: Verify string termination
194195
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
@@ -206,7 +207,7 @@ func (mc *mysqlConn) readInitPacket() (err error) {
206207

207208
// Client Authentication Packet
208209
// http://dev.mysql.com/doc/internals/en/connection-phase.html#packet-Protocol::HandshakeResponse
209-
func (mc *mysqlConn) writeAuthPacket() error {
210+
func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
210211
// Adjust client flags based on server support
211212
clientFlags := clientProtocol41 |
212213
clientSecureConn |
@@ -225,7 +226,7 @@ func (mc *mysqlConn) writeAuthPacket() error {
225226
}
226227

227228
// User Password
228-
scrambleBuff := scramblePassword(mc.cipher, []byte(mc.cfg.passwd))
229+
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.passwd))
229230

230231
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.user) + 1 + 1 + len(scrambleBuff)
231232

@@ -308,10 +309,9 @@ func (mc *mysqlConn) writeAuthPacket() error {
308309

309310
// Client old authentication packet
310311
// http://dev.mysql.com/doc/internals/en/connection-phase.html#packet-Protocol::AuthSwitchResponse
311-
func (mc *mysqlConn) writeOldAuthPacket() error {
312+
func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
312313
// User password
313-
scrambleBuff := scrambleOldPassword(mc.cipher, []byte(mc.cfg.passwd))
314-
mc.cipher = nil
314+
scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.passwd))
315315

316316
// Calculate the packet lenght and add a tailing 0
317317
pktLen := len(scrambleBuff) + 1
@@ -323,7 +323,7 @@ func (mc *mysqlConn) writeOldAuthPacket() error {
323323
data[2] = byte(pktLen >> 16)
324324
data[3] = mc.sequence
325325

326-
// Add the scrambled password (it will be terminated by 0)
326+
// Add the scrambled password [null terminated string]
327327
copy(data[4:], scrambleBuff)
328328

329329
return mc.writePacket(data)
@@ -408,17 +408,11 @@ func (mc *mysqlConn) readResultOK() error {
408408
switch data[0] {
409409

410410
case iOK:
411-
// Remove the chipher in case of successfull authentication
412-
mc.cipher = nil
413411
return mc.handleOkPacket(data)
414412

415413
case iEOF:
416414
// someone is using old_passwords
417-
err = mc.writeOldAuthPacket()
418-
if err != nil {
419-
return err
420-
}
421-
return mc.readResultOK()
415+
return errOldPassword
422416

423417
default: // Error otherwise
424418
return mc.handleErrorPacket(data)

utils.go

+9
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ func parseDSN(dsn string) (cfg *config, err error) {
126126
return
127127
}
128128

129+
// Use old authentication mode (pre MySQL 4.1)
130+
case "allowOldPasswords":
131+
var isBool bool
132+
cfg.allowOldPasswords, isBool = readBool(value)
133+
if !isBool {
134+
err = fmt.Errorf("Invalid Bool value: %s", value)
135+
return
136+
}
137+
129138
// Time Location
130139
case "loc":
131140
cfg.loc, err = time.LoadLocation(value)

utils_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ func TestDSNParser(t *testing.T) {
2121
out string
2222
loc *time.Location
2323
}{
24-
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.UTC},
25-
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.UTC},
26-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.UTC},
27-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.UTC},
28-
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p timeout:30000000000 tls:<nil> allowAllFiles:true clientFoundRows:true}", time.UTC},
29-
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.Local},
30-
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.UTC},
31-
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.UTC},
32-
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false clientFoundRows:false}", time.UTC},
24+
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
25+
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
26+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
27+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
28+
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p timeout:30000000000 tls:<nil> allowAllFiles:true allowOldPasswords:true clientFoundRows:true}", time.UTC},
29+
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.Local},
30+
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
31+
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
32+
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
3333
}
3434

3535
var cfg *config

0 commit comments

Comments
 (0)