Skip to content

Support authentication switch with mysql_native_password authentication #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 1, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ Default: false
```
`allowOldPasswords=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).

##### `allowNativeasswords`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should probably be allowNativePasswords


```
Type: bool
Valid Values: true, false
Default: false
```
`allowNativeasswords=true` allows the usage of the mysql native password method..
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowNativePasswords

And only one . at the end of the sentence.


##### `charset`

```
Expand Down
15 changes: 10 additions & 5 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
}

// Handle response to auth packet, switch methods if possible
if err = handleAuthResult(mc, cipher); err != nil {
if err = handleAuthResult(mc); err != nil {
// Authentication failed and MySQL has already closed the connection
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
// Do not send COM_QUIT, just cleanup and return the error.
Expand Down Expand Up @@ -130,9 +130,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
return mc, nil
}

func handleAuthResult(mc *mysqlConn, cipher []byte) error {
func handleAuthResult(mc *mysqlConn) error {
// Read Result Packet
err := mc.readResultOK()
err, cipher := mc.readResultOK()
if err == nil {
return nil // auth successful
}
Expand All @@ -149,15 +149,20 @@ func handleAuthResult(mc *mysqlConn, cipher []byte) error {
if err = mc.writeOldAuthPacket(cipher); err != nil {
return err
}
err = mc.readResultOK()
err, _ = mc.readResultOK()
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
// Retry with clear text password for
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
if err = mc.writeClearAuthPacket(); err != nil {
return err
}
err = mc.readResultOK()
err, _ = mc.readResultOK()
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
if err = mc.writeNativeAuthPacket(cipher); err != nil {
return err
}
err, _ = mc.readResultOK()
}
return err
}
Expand Down
9 changes: 9 additions & 0 deletions dsn.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Config struct {
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
AllowCleartextPasswords bool // Allows the cleartext client side plugin
AllowOldPasswords bool // Allows the old insecure password method
AllowNativePasswords bool // Allows mysql native password authentication method
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove "mysql". It's obvious were not using MongoDB's native password authentication method

ClientFoundRows bool // Return number of matching rows instead of rows changed
ColumnsWithAlias bool // Prepend table alias to column names
InterpolateParams bool // Interpolate placeholders into query string
Expand Down Expand Up @@ -376,6 +377,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return errors.New("invalid bool value: " + value)
}

// Use mysql native password authentication
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

case "allowNativePasswords":
var isBool bool
cfg.AllowNativePasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}

// Switch "rowsAffected" mode
case "clientFoundRows":
var isBool bool
Expand Down
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var (
ErrNoTLS = errors.New("TLS requested but server does not support TLS")
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")
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
ErrNativePassword = errors.New("this user requires mysql native password authentication.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use go fmt

ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
ErrPktSync = errors.New("commands out of sync. You can't run this command now")
Expand Down
3 changes: 2 additions & 1 deletion infile.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {

// read OK packet
if err == nil {
return mc.readResultOK()
err, _ = mc.readResultOK()
return err
}

mc.readPacket()
Expand Down
44 changes: 35 additions & 9 deletions packets.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,26 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
return mc.writePacket(data)
}

// MySql native password authentication method
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove "MySql"

// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))

// Calculate the packet length and add a tailing 0
pktLen := len(scrambleBuff)
data := mc.buf.takeSmallBuffer(4 + pktLen)
if data == nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn
}

// Add the scramble
copy(data[4:], scrambleBuff)

return mc.writePacket(data)
}

/******************************************************************************
* Command Packets *
******************************************************************************/
Expand Down Expand Up @@ -445,36 +465,42 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
******************************************************************************/

// Returns error if Packet is not an 'Result OK'-Packet
func (mc *mysqlConn) readResultOK() error {
func (mc *mysqlConn) readResultOK() (error, []byte) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flip the return values and name them so it's (cipher []byte, err error)

data, err := mc.readPacket()
if err == nil {
// packet indicator
switch data[0] {

case iOK:
return mc.handleOkPacket(data)
return mc.handleOkPacket(data), nil

case iEOF:
if len(data) > 1 {
plugin := string(data[1:bytes.IndexByte(data, 0x00)])
pluginEndIndex := bytes.IndexByte(data, 0x00)
plugin := string(data[1:pluginEndIndex])
cipher := data[pluginEndIndex + 1: len(data) - 1]

if plugin == "mysql_old_password" {
// using old_passwords
return ErrOldPassword
return ErrOldPassword, cipher
} else if plugin == "mysql_clear_password" {
// using clear text password
return ErrCleartextPassword
return ErrCleartextPassword, cipher
} else if plugin == "mysql_native_password" {
// using mysql default authentication method
return ErrNativePassword, cipher
} else {
return ErrUnknownPlugin
return ErrUnknownPlugin, cipher
}
} else {
return ErrOldPassword
return ErrOldPassword, nil
}

default: // Error otherwise
return mc.handleErrorPacket(data)
return mc.handleErrorPacket(data), nil
}
}
return err
return err, nil
}

// Result Set Header Packet
Expand Down