From 0f6bfafc44729ee73d8ca3a5deae6404e034bbd4 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Oct 2016 13:42:31 +0800 Subject: [PATCH 1/5] Support authentication switch with mysql_native_password authentication. Besides, fix bug that cipher needs to refreshed from authentication switch request packet from server. --- README.md | 9 +++++++++ connection.go | 5 ----- driver.go | 15 ++++++++++----- dsn.go | 9 +++++++++ errors.go | 1 + infile.go | 3 ++- packets.go | 44 +++++++++++++++++++++++++++++++++++--------- 7 files changed, 66 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c64aae264..d8bf62f5e 100644 --- a/README.md +++ b/README.md @@ -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` + +``` +Type: bool +Valid Values: true, false +Default: false +``` +`allowNativeasswords=true` allows the usage of the mysql native password method.. + ##### `charset` ``` diff --git a/connection.go b/connection.go index d37e36dea..c3899de0e 100644 --- a/connection.go +++ b/connection.go @@ -135,11 +135,6 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { } func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) { - // Number of ? should be same to len(args) - if strings.Count(query, "?") != len(args) { - return "", driver.ErrSkip - } - buf := mc.buf.takeCompleteBuffer() if buf == nil { // can not take the buffer. Something must be wrong with the connection diff --git a/driver.go b/driver.go index 899f955fb..bdca2571d 100644 --- a/driver.go +++ b/driver.go @@ -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. @@ -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 } @@ -149,7 +149,7 @@ 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 @@ -157,7 +157,12 @@ func handleAuthResult(mc *mysqlConn, cipher []byte) error { 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 } diff --git a/dsn.go b/dsn.go index 73138bc57..d41679733 100644 --- a/dsn.go +++ b/dsn.go @@ -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 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 @@ -376,6 +377,14 @@ func parseDSNParams(cfg *Config, params string) (err error) { return errors.New("invalid bool value: " + value) } + // Use mysql native password authentication + 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 diff --git a/errors.go b/errors.go index 1543a8054..2393a67f5 100644 --- a/errors.go +++ b/errors.go @@ -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.") 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") diff --git a/infile.go b/infile.go index 0f975bbc2..3f1134237 100644 --- a/infile.go +++ b/infile.go @@ -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() diff --git a/packets.go b/packets.go index 602539942..7b3792069 100644 --- a/packets.go +++ b/packets.go @@ -372,6 +372,26 @@ func (mc *mysqlConn) writeClearAuthPacket() error { return mc.writePacket(data) } +// MySql native password authentication method +// 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 * ******************************************************************************/ @@ -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) { 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 From d6357d4bcab749b22655fdad332d014c9aa7aa72 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Oct 2016 14:01:06 +0800 Subject: [PATCH 2/5] revert connection.go which was modified by accident. --- connection.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/connection.go b/connection.go index c3899de0e..d37e36dea 100644 --- a/connection.go +++ b/connection.go @@ -135,6 +135,11 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { } func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) { + // Number of ? should be same to len(args) + if strings.Count(query, "?") != len(args) { + return "", driver.ErrSkip + } + buf := mc.buf.takeCompleteBuffer() if buf == nil { // can not take the buffer. Something must be wrong with the connection From 6be36f5f9b6768d0a647e1ad131ad1fb7eae7165 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 28 Oct 2016 12:14:17 +0800 Subject: [PATCH 3/5] Address comments. --- AUTHORS | 1 + README.md | 4 ++-- driver.go | 8 ++++---- dsn.go | 4 ++-- errors.go | 2 +- infile.go | 2 +- packets.go | 24 ++++++++++++------------ 7 files changed, 23 insertions(+), 22 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3774919d7..81e5325aa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -44,6 +44,7 @@ Stan Putrya Stanley Gunawan Xiaobing Jiang Xiuming Chen +Xiangyu Hu # Organizations diff --git a/README.md b/README.md index d8bf62f5e..3cf6d5349 100644 --- a/README.md +++ b/README.md @@ -144,14 +144,14 @@ 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` +##### `allowNativePasswords` ``` Type: bool Valid Values: true, false Default: false ``` -`allowNativeasswords=true` allows the usage of the mysql native password method.. +`allowNativePasswords=true` allows the usage of the mysql native password method. ##### `charset` diff --git a/driver.go b/driver.go index bdca2571d..812a743b9 100644 --- a/driver.go +++ b/driver.go @@ -132,7 +132,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { func handleAuthResult(mc *mysqlConn) error { // Read Result Packet - err, cipher := mc.readResultOK() + cipher, err := mc.readResultOK() if err == nil { return nil // auth successful } @@ -149,7 +149,7 @@ func handleAuthResult(mc *mysqlConn) 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 @@ -157,12 +157,12 @@ func handleAuthResult(mc *mysqlConn) error { 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() + _, err = mc.readResultOK() } return err } diff --git a/dsn.go b/dsn.go index d41679733..0bf03c0a5 100644 --- a/dsn.go +++ b/dsn.go @@ -45,7 +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 + AllowNativePasswords bool // Allows the 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 @@ -377,7 +377,7 @@ func parseDSNParams(cfg *Config, params string) (err error) { return errors.New("invalid bool value: " + value) } - // Use mysql native password authentication + // Use native password authentication case "allowNativePasswords": var isBool bool cfg.AllowNativePasswords, isBool = readBool(value) diff --git a/errors.go b/errors.go index 2393a67f5..713a7bbe9 100644 --- a/errors.go +++ b/errors.go @@ -24,7 +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.") + ErrNativePassword = errors.New("this user requires mysql native password authentication.") 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") diff --git a/infile.go b/infile.go index 3f1134237..547357cfa 100644 --- a/infile.go +++ b/infile.go @@ -173,7 +173,7 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) { // read OK packet if err == nil { - err, _ = mc.readResultOK() + _, err = mc.readResultOK() return err } diff --git a/packets.go b/packets.go index 7b3792069..a71ea4c76 100644 --- a/packets.go +++ b/packets.go @@ -372,11 +372,11 @@ func (mc *mysqlConn) writeClearAuthPacket() error { return mc.writePacket(data) } -// MySql native password authentication method +// Native password authentication method // 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) @@ -465,42 +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, []byte) { +func (mc *mysqlConn) readResultOK() ([]byte, error) { data, err := mc.readPacket() if err == nil { // packet indicator switch data[0] { case iOK: - return mc.handleOkPacket(data), nil + return nil, mc.handleOkPacket(data) case iEOF: if len(data) > 1 { pluginEndIndex := bytes.IndexByte(data, 0x00) plugin := string(data[1:pluginEndIndex]) - cipher := data[pluginEndIndex + 1: len(data) - 1] + cipher := data[pluginEndIndex+1 : len(data)-1] if plugin == "mysql_old_password" { // using old_passwords - return ErrOldPassword, cipher + return cipher, ErrOldPassword } else if plugin == "mysql_clear_password" { // using clear text password - return ErrCleartextPassword, cipher + return cipher, ErrCleartextPassword } else if plugin == "mysql_native_password" { // using mysql default authentication method - return ErrNativePassword, cipher + return cipher, ErrNativePassword } else { - return ErrUnknownPlugin, cipher + return cipher, ErrUnknownPlugin } } else { - return ErrOldPassword, nil + return nil, ErrOldPassword } default: // Error otherwise - return mc.handleErrorPacket(data), nil + return nil, mc.handleErrorPacket(data) } } - return err, nil + return nil, err } // Result Set Header Packet From 6dbcd3c22a106ceb7f7a25821b5b6e7e58c3fcf8 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Tue, 1 Nov 2016 18:49:29 +0800 Subject: [PATCH 4/5] Keep DSN params sorted --- README.md | 8 ++++---- dsn.go | 14 +++++++------- errors.go | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index caf59b7ed..3dcc23544 100644 --- a/README.md +++ b/README.md @@ -135,23 +135,23 @@ Default: false `allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network. -##### `allowOldPasswords` +##### `allowNativePasswords` ``` Type: bool Valid Values: true, false 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). +`allowNativePasswords=true` allows the usage of the mysql native password method. -##### `allowNativePasswords` +##### `allowOldPasswords` ``` Type: bool Valid Values: true, false Default: false ``` -`allowNativePasswords=true` allows the usage of the mysql native password method. +`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). ##### `charset` diff --git a/dsn.go b/dsn.go index 7b9a56962..c83c0cd68 100644 --- a/dsn.go +++ b/dsn.go @@ -46,8 +46,8 @@ 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 the native password authentication method + AllowOldPasswords bool // Allows the old insecure password 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 @@ -382,18 +382,18 @@ func parseDSNParams(cfg *Config, params string) (err error) { return errors.New("invalid bool value: " + value) } - // Use old authentication mode (pre MySQL 4.1) - case "allowOldPasswords": + // Use native password authentication + case "allowNativePasswords": var isBool bool - cfg.AllowOldPasswords, isBool = readBool(value) + cfg.AllowNativePasswords, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } - // Use native password authentication - case "allowNativePasswords": + // Use old authentication mode (pre MySQL 4.1) + case "allowOldPasswords": var isBool bool - cfg.AllowNativePasswords, isBool = readBool(value) + cfg.AllowOldPasswords, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } diff --git a/errors.go b/errors.go index 713a7bbe9..857854e14 100644 --- a/errors.go +++ b/errors.go @@ -22,9 +22,9 @@ var ( ErrInvalidConn = errors.New("invalid connection") ErrMalformPkt = errors.New("malformed packet") 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.") + 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") 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") From 5926162122351a41485ef8029901f009a58f496f Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Tue, 1 Nov 2016 18:50:04 +0800 Subject: [PATCH 5/5] DSN: Add code to format allowNativePasswords --- dsn.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dsn.go b/dsn.go index c83c0cd68..ac00dcedd 100644 --- a/dsn.go +++ b/dsn.go @@ -102,6 +102,15 @@ func (cfg *Config) FormatDSN() string { } } + if cfg.AllowNativePasswords { + if hasParam { + buf.WriteString("&allowNativePasswords=true") + } else { + hasParam = true + buf.WriteString("?allowNativePasswords=true") + } + } + if cfg.AllowOldPasswords { if hasParam { buf.WriteString("&allowOldPasswords=true")