Skip to content

Commit 4f478ef

Browse files
committedApr 6, 2015
Cleartext authentication plugin support
1 parent a197e5d commit 4f478ef

9 files changed

+115
-42
lines changed
 

‎AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Henri Yandell <flamefew at gmail.com>
2323
INADA Naoki <songofacandy at gmail.com>
2424
James Harr <james.harr at gmail.com>
2525
Jian Zhen <zhenjl at gmail.com>
26+
Joshua Prunier <joshua.prunier at gmail.com>
2627
Julien Schmidt <go-sql-driver at julienschmidt.com>
2728
Kamil Dziedzic <kamil at klecza.pl>
2829
Leonardo YongUk Kim <dalinaum at gmail.com>

‎README.md

+9
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ Default: false
123123
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
124124
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
125125

126+
##### `allowClearPasswords`
127+
128+
```
129+
Type: bool
130+
Valid Values: true, false
131+
Default: false
132+
```
133+
`allowClearPasswords=true` allows the usage of the cleartext client side plugin. This can be insecure but is required by the [PAM authentication plugin](http://dev.mysql.com/doc/refman/5.5/en/pam-authentication-plugin.html).
134+
126135
##### `allowOldPasswords`
127136

128137
```

‎connection.go

+16-15
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,22 @@ type mysqlConn struct {
3434
}
3535

3636
type config struct {
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-
tls *tls.Config
45-
timeout time.Duration
46-
collation uint8
47-
allowAllFiles bool
48-
allowOldPasswords bool
49-
clientFoundRows bool
50-
columnsWithAlias bool
51-
interpolateParams 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+
tls *tls.Config
45+
timeout time.Duration
46+
collation uint8
47+
allowAllFiles bool
48+
allowOldPasswords bool
49+
allowClearPasswords bool
50+
clientFoundRows bool
51+
columnsWithAlias bool
52+
interpolateParams bool
5253
}
5354

5455
// Handles parameters set in DSN after the connection is established

‎const.go

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const (
4545
clientSecureConn
4646
clientMultiStatements
4747
clientMultiResults
48+
clientPSMultiResults
49+
clientPluginAuth
4850
)
4951

5052
const (

‎driver.go

+9
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
107107
mc.Close()
108108
return nil, err
109109
}
110+
} else if mc.cfg != nil && mc.cfg.allowClearPasswords && err == ErrClearPassword {
111+
if err = mc.writeClearAuthPacket(); err != nil {
112+
mc.Close()
113+
return nil, err
114+
}
115+
if err = mc.readResultOK(); err != nil {
116+
mc.Close()
117+
return nil, err
118+
}
110119
} else {
111120
mc.Close()
112121
return nil, err

‎errors.go

+11-9
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ import (
1919

2020
// Various errors the driver might return. Can change between driver versions.
2121
var (
22-
ErrInvalidConn = errors.New("Invalid Connection")
23-
ErrMalformPkt = errors.New("Malformed Packet")
24-
ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")
25-
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")
26-
ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
27-
ErrPktSync = errors.New("Commands out of sync. You can't run this command now")
28-
ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
29-
ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
30-
ErrBusyBuffer = errors.New("Busy buffer")
22+
ErrInvalidConn = errors.New("Invalid Connection")
23+
ErrMalformPkt = errors.New("Malformed Packet")
24+
ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")
25+
ErrOldPassword = errors.New("This user requires 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")
26+
ErrClearPassword = errors.New("This user requires clear text authentication. If you still want to use it, please add 'allowClearPasswords=1' to your DSN.")
27+
ErrUnknownPlugin = errors.New("The authentication plugin is not supported.")
28+
ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
29+
ErrPktSync = errors.New("Commands out of sync. You can't run this command now")
30+
ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
31+
ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
32+
ErrBusyBuffer = errors.New("Busy buffer")
3133
)
3234

3335
var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)

‎packets.go

+46-5
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,11 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
196196
// return
197197
//}
198198
//return ErrMalformPkt
199-
return cipher, nil
199+
200+
// make a memory safe copy of the cipher slice
201+
var b [20]byte
202+
copy(b[:], cipher)
203+
return b[:], nil
200204
}
201205

202206
// make a memory safe copy of the cipher slice
@@ -214,6 +218,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
214218
clientLongPassword |
215219
clientTransactions |
216220
clientLocalFiles |
221+
clientPluginAuth |
217222
mc.flags&clientLongFlag
218223

219224
if mc.cfg.clientFoundRows {
@@ -228,7 +233,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
228233
// User Password
229234
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.passwd))
230235

231-
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.user) + 1 + 1 + len(scrambleBuff)
236+
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.user) + 1 + 1 + len(scrambleBuff) + 21 + 1
232237

233238
// To specify a db name
234239
if n := len(mc.cfg.dbname); n > 0 {
@@ -294,8 +299,13 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
294299
if len(mc.cfg.dbname) > 0 {
295300
pos += copy(data[pos:], mc.cfg.dbname)
296301
data[pos] = 0x00
302+
pos++
297303
}
298304

305+
// Assume native client during response
306+
pos += copy(data[pos:], "mysql_native_password")
307+
data[pos] = 0x00
308+
299309
// Send Auth packet
300310
return mc.writePacket(data)
301311
}
@@ -306,7 +316,7 @@ func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
306316
// User password
307317
scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.passwd))
308318

309-
// Calculate the packet lenght and add a tailing 0
319+
// Calculate the packet length and add a tailing 0
310320
pktLen := len(scrambleBuff) + 1
311321
data := mc.buf.takeSmallBuffer(4 + pktLen)
312322
if data == nil {
@@ -322,6 +332,25 @@ func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
322332
return mc.writePacket(data)
323333
}
324334

335+
// Client clear text authentication packet
336+
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
337+
func (mc *mysqlConn) writeClearAuthPacket() error {
338+
// Calculate the packet length and add a tailing 0
339+
pktLen := len(mc.cfg.passwd) + 1
340+
data := mc.buf.takeSmallBuffer(4 + pktLen)
341+
if data == nil {
342+
// can not take the buffer. Something must be wrong with the connection
343+
errLog.Print(ErrBusyBuffer)
344+
return driver.ErrBadConn
345+
}
346+
347+
// Add the clear password [null terminated string]
348+
copy(data[4:], mc.cfg.passwd)
349+
data[4+pktLen-1] = 0x00
350+
351+
return mc.writePacket(data)
352+
}
353+
325354
/******************************************************************************
326355
* Command Packets *
327356
******************************************************************************/
@@ -405,8 +434,20 @@ func (mc *mysqlConn) readResultOK() error {
405434
return mc.handleOkPacket(data)
406435

407436
case iEOF:
408-
// someone is using old_passwords
409-
return ErrOldPassword
437+
if len(data) > 1 {
438+
plugin := string(data[1:bytes.IndexByte(data, 0x00)])
439+
if plugin == "mysql_old_password" {
440+
// using old_passwords
441+
return ErrOldPassword
442+
} else if plugin == "mysql_clear_password" {
443+
// using clear text password
444+
return ErrClearPassword
445+
} else {
446+
return ErrUnknownPlugin
447+
}
448+
} else {
449+
return ErrOldPassword
450+
}
410451

411452
default: // Error otherwise
412453
return mc.handleErrorPacket(data)

‎utils.go

+8
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@ func parseDSNParams(cfg *config, params string) (err error) {
201201
return fmt.Errorf("Invalid Bool value: %s", value)
202202
}
203203

204+
// Use cleartext authentication mode (MySQL 5.5.10+)
205+
case "allowClearPasswords":
206+
var isBool bool
207+
cfg.allowClearPasswords, isBool = readBool(value)
208+
if !isBool {
209+
return fmt.Errorf("Invalid Bool value: %s", value)
210+
}
211+
204212
// Use old authentication mode (pre MySQL 4.1)
205213
case "allowOldPasswords":
206214
var isBool bool

‎utils_test.go

+13-13
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ var testDSNs = []struct {
2222
out string
2323
loc *time.Location
2424
}{
25-
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
26-
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:true interpolateParams:false}", time.UTC},
27-
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
28-
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
29-
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
30-
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true columnsWithAlias:false interpolateParams:false}", time.UTC},
31-
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.Local},
32-
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
33-
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
34-
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
35-
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
36-
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
37-
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
25+
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
26+
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:true interpolateParams:false}", time.UTC},
27+
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
28+
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
29+
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
30+
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true allowClearPasswords:false clientFoundRows:true columnsWithAlias:false interpolateParams:false}", time.UTC},
31+
{"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 tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.Local},
32+
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
33+
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
34+
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
35+
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
36+
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
37+
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false allowClearPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
3838
}
3939

4040
func TestDSNParser(t *testing.T) {

0 commit comments

Comments
 (0)
Please sign in to comment.