diff --git a/Readme.md b/Readme.md index 4f52089f2..f0e649abe 100644 --- a/Readme.md +++ b/Readme.md @@ -1324,6 +1324,7 @@ The following flags are sent by default on a new connection: - `RESERVED` - Old flag for the 4.1 protocol. - `SECURE_CONNECTION` - Support native 4.1 authentication. - `TRANSACTIONS` - Asks for the transaction status flags. +- `PLUGIN_AUTH` - Support basic auth plugin protocol, including plugin `mysql_native_password` and `mysql_old_password`. In addition, the following flag will be sent if the option `multipleStatements` is set to `true`: @@ -1339,7 +1340,6 @@ available to specify. - COMPRESS - INTERACTIVE - NO_SCHEMA -- PLUGIN_AUTH - REMEMBER_OPTIONS - SSL - SSL_VERIFY_SERVER_CERT diff --git a/lib/ConnectionConfig.js b/lib/ConnectionConfig.js index 147aa0abb..7c82d68b3 100644 --- a/lib/ConnectionConfig.js +++ b/lib/ConnectionConfig.js @@ -106,7 +106,7 @@ ConnectionConfig.getDefaultFlags = function getDefaultFlags(options) { '+LONG_PASSWORD', // Use the improved version of Old Password Authentication '+MULTI_RESULTS', // Can handle multiple resultsets for COM_QUERY '+ODBC', // Special handling of ODBC behaviour - '-PLUGIN_AUTH', // Does *NOT* support auth plugins + '+PLUGIN_AUTH', // Support basic auth plugins, including mysql_native_method and mysql_old_password '+PROTOCOL_41', // Uses the 4.1 protocol '+PS_MULTI_RESULTS', // Can handle multiple resultsets for COM_STMT_EXECUTE '+RESERVED', // Unused diff --git a/lib/Pool.js b/lib/Pool.js index 87a40114a..30af47abb 100644 --- a/lib/Pool.js +++ b/lib/Pool.js @@ -112,7 +112,9 @@ Pool.prototype.acquireConnection = function acquireConnection(connection, cb) { if (changeUser) { // restore user back to pool configuration + var previousClientPluginAuth = connection.config.clientPluginAuth; connection.config = this.config.newConnectionConfig(); + connection.config.clientPluginAuth = previousClientPluginAuth; connection.changeUser({timeout: this.config.acquireTimeout}, onOperationComplete); } else { // ping connection diff --git a/lib/protocol/Auth.js b/lib/protocol/Auth.js index 13bd3e812..da91364a2 100644 --- a/lib/protocol/Auth.js +++ b/lib/protocol/Auth.js @@ -150,3 +150,21 @@ Auth.int32Read = function(buffer, offset){ + (buffer[offset + 2] << 8) + (buffer[offset + 3]); }; + +Auth.tokenByPlugin = function(pluginName, pluginData, password) { + switch (pluginName) { + case 'mysql_native_password': + // mysql_native_password only need first 20 bytes scramble. + return this.token(password, pluginData.slice(0, 20)); + case 'mysql_old_password': + // mysql_old_password only need first 8 bytes scramble. + return this.scramble323(pluginData.slice(0, 8), password); + case 'mysql_clear_password': + return password; + default: + var err = new Error('The auth plugin: ' + pluginName + ' is not supported by this client driver.'); + err.code = 'UNSUPPORTED_AUTH_PLUGIN'; + err.fatal = true; + throw err; + } +}; diff --git a/lib/protocol/Parser.js b/lib/protocol/Parser.js index d30e04e08..9747d8810 100644 --- a/lib/protocol/Parser.js +++ b/lib/protocol/Parser.js @@ -309,6 +309,11 @@ Parser.prototype._nullByteOffset = function() { return offset; }; +Parser.prototype.parsePacketTerminatedBuffer = function() { + var length = this._packetEnd - this._offset; + return this.parseBuffer(length); +}; + Parser.prototype.parsePacketTerminatedString = function() { var length = this._packetEnd - this._offset; return this.parseString(length); diff --git a/lib/protocol/packets/AuthSwitchPacket.js b/lib/protocol/packets/AuthSwitchPacket.js new file mode 100644 index 000000000..d92b79aac --- /dev/null +++ b/lib/protocol/packets/AuthSwitchPacket.js @@ -0,0 +1,13 @@ +module.exports = AuthSwitchPacket; +function AuthSwitchPacket(options) { + options = options || {}; + this.scrambleBuff = options.scrambleBuff; +} + +AuthSwitchPacket.prototype.parse = function(parser) { + this.scrambleBuff = parser.parsePacketTerminatedBuffer(); +}; + +AuthSwitchPacket.prototype.write = function(writer) { + writer.writeBuffer(this.scrambleBuff); +}; diff --git a/lib/protocol/packets/ClientAuthenticationPacket.js b/lib/protocol/packets/ClientAuthenticationPacket.js index 595db77a0..709935580 100644 --- a/lib/protocol/packets/ClientAuthenticationPacket.js +++ b/lib/protocol/packets/ClientAuthenticationPacket.js @@ -4,14 +4,16 @@ module.exports = ClientAuthenticationPacket; function ClientAuthenticationPacket(options) { options = options || {}; - this.clientFlags = options.clientFlags; - this.maxPacketSize = options.maxPacketSize; - this.charsetNumber = options.charsetNumber; - this.filler = undefined; - this.user = options.user; - this.scrambleBuff = options.scrambleBuff; - this.database = options.database; - this.protocol41 = options.protocol41; + this.clientFlags = options.clientFlags; + this.maxPacketSize = options.maxPacketSize; + this.charsetNumber = options.charsetNumber; + this.filler = undefined; + this.user = options.user; + this.scrambleBuff = options.scrambleBuff; + this.database = options.database; + this.protocol41 = options.protocol41; + this.clientPluginAuth = options.clientPluginAuth; + this.authPluginName = options.authPluginName; } ClientAuthenticationPacket.prototype.parse = function(parser) { @@ -23,6 +25,9 @@ ClientAuthenticationPacket.prototype.parse = function(parser) { this.user = parser.parseNullTerminatedString(); this.scrambleBuff = parser.parseLengthCodedBuffer(); this.database = parser.parseNullTerminatedString(); + if (this.clientPluginAuth) { + this.authPluginName = parser.parseNullTerminatedString(); + } } else { this.clientFlags = parser.parseUnsignedNumber(2); this.maxPacketSize = parser.parseUnsignedNumber(3); @@ -41,6 +46,9 @@ ClientAuthenticationPacket.prototype.write = function(writer) { writer.writeNullTerminatedString(this.user); writer.writeLengthCodedBuffer(this.scrambleBuff); writer.writeNullTerminatedString(this.database); + if (this.clientPluginAuth) { + writer.writeNullTerminatedString(this.authPluginName); + } } else { writer.writeUnsignedNumber(2, this.clientFlags); writer.writeUnsignedNumber(3, this.maxPacketSize); diff --git a/lib/protocol/packets/ComChangeUserPacket.js b/lib/protocol/packets/ComChangeUserPacket.js index 327884235..683750852 100644 --- a/lib/protocol/packets/ComChangeUserPacket.js +++ b/lib/protocol/packets/ComChangeUserPacket.js @@ -2,11 +2,13 @@ module.exports = ComChangeUserPacket; function ComChangeUserPacket(options) { options = options || {}; - this.command = 0x11; - this.user = options.user; - this.scrambleBuff = options.scrambleBuff; - this.database = options.database; - this.charsetNumber = options.charsetNumber; + this.command = 0x11; + this.user = options.user; + this.scrambleBuff = options.scrambleBuff; + this.database = options.database; + this.charsetNumber = options.charsetNumber; + this.clientPluginAuth = options.clientPluginAuth; + this.authPluginName = options.authPluginName; } ComChangeUserPacket.prototype.parse = function(parser) { @@ -14,7 +16,12 @@ ComChangeUserPacket.prototype.parse = function(parser) { this.user = parser.parseNullTerminatedString(); this.scrambleBuff = parser.parseLengthCodedBuffer(); this.database = parser.parseNullTerminatedString(); - this.charsetNumber = parser.parseUnsignedNumber(1); + if (!parser.reachedPacketEnd()) { + this.charsetNumber = parser.parseUnsignedNumber(2); + if (this.clientPluginAuth === true) { + this.authPluginName = parser.parseNullTerminatedString(); + } + } }; ComChangeUserPacket.prototype.write = function(writer) { @@ -23,4 +30,7 @@ ComChangeUserPacket.prototype.write = function(writer) { writer.writeLengthCodedBuffer(this.scrambleBuff); writer.writeNullTerminatedString(this.database); writer.writeUnsignedNumber(2, this.charsetNumber); + if (this.clientPluginAuth) { + writer.writeNullTerminatedString(this.authPluginName); + } }; diff --git a/lib/protocol/packets/HandshakeInitializationPacket.js b/lib/protocol/packets/HandshakeInitializationPacket.js index b2510633b..497055138 100644 --- a/lib/protocol/packets/HandshakeInitializationPacket.js +++ b/lib/protocol/packets/HandshakeInitializationPacket.js @@ -20,11 +20,17 @@ function HandshakeInitializationPacket(options) { this.filler3 = options.filler3; this.pluginData = options.pluginData; this.protocol41 = options.protocol41; + this.clientPluginAuth = options.clientPluginAuth; + this.authPluginName = options.authPluginName; if (this.protocol41) { // force set the bit in serverCapabilities1 this.serverCapabilities1 |= Client.CLIENT_PROTOCOL_41; } + + if (this.clientPluginAuth) { + this.serverCapabilities2 |= Client.CLIENT_PLUGIN_AUTH >> 16; + } } HandshakeInitializationPacket.prototype.parse = function(parser) { @@ -38,16 +44,22 @@ HandshakeInitializationPacket.prototype.parse = function(parser) { this.serverStatus = parser.parseUnsignedNumber(2); this.protocol41 = (this.serverCapabilities1 & (1 << 9)) > 0; + this.clientPluginAuth = false; if (this.protocol41) { this.serverCapabilities2 = parser.parseUnsignedNumber(2); + this.clientPluginAuth = (this.serverCapabilities2 & (1 << 3)) > 0; this.scrambleLength = parser.parseUnsignedNumber(1); this.filler2 = parser.parseFiller(10); // scrambleBuff2 should be 0x00 terminated, but sphinx does not do this // so we assume scrambleBuff2 to be 12 byte and treat the next byte as a // filler byte. - this.scrambleBuff2 = parser.parseBuffer(12); + scrambleBuff2Length = Math.max(12, this.scrambleLength - 9); + this.scrambleBuff2 = parser.parseBuffer(scrambleBuff2Length); this.filler3 = parser.parseFiller(1); + if (this.clientPluginAuth) { + this.authPluginName = parser.parseNullTerminatedString(); + } } else { this.filler2 = parser.parseFiller(13); } @@ -80,8 +92,11 @@ HandshakeInitializationPacket.prototype.write = function(writer) { writer.writeUnsignedNumber(2, this.serverCapabilities2); writer.writeUnsignedNumber(1, this.scrambleLength); writer.writeFiller(10); + writer.writeNullTerminatedBuffer(this.scrambleBuff2); + if (this.clientPluginAuth) { + writer.writeNullTerminatedString(this.authPluginName); + } } - writer.writeNullTerminatedBuffer(this.scrambleBuff2); if (this.pluginData !== undefined) { writer.writeNullTerminatedString(this.pluginData); diff --git a/lib/protocol/packets/UseAuthSwitchPacket.js b/lib/protocol/packets/UseAuthSwitchPacket.js new file mode 100644 index 000000000..bd8a51333 --- /dev/null +++ b/lib/protocol/packets/UseAuthSwitchPacket.js @@ -0,0 +1,19 @@ +module.exports = UseAuthSwitchPacket; +function UseAuthSwitchPacket(options) { + options = options || {}; + this.firstByte = options.firstByte || 0xfe; + this.authPluginName = options.authPluginName || ''; + this.authPluginData = options.authPluginData || ''; +} + +UseAuthSwitchPacket.prototype.parse = function(parser) { + this.firstByte = parser.parseUnsignedNumber(1); + this.authPluginName = parser.parseNullTerminatedString(); + this.authPluginData = parser.parsePacketTerminatedBuffer(); +}; + +UseAuthSwitchPacket.prototype.write = function(writer) { + writer.writeUnsignedNumber(1, this.firstByte); + writer.writeNullTerminatedString(this.authPluginName); + writer.writeBuffer(this.authPluginData); +}; diff --git a/lib/protocol/packets/index.js b/lib/protocol/packets/index.js index 5c63df53a..4f4cf43df 100644 --- a/lib/protocol/packets/index.js +++ b/lib/protocol/packets/index.js @@ -1,3 +1,4 @@ +exports.AuthSwitchPacket = require('./AuthSwitchPacket'); exports.ClientAuthenticationPacket = require('./ClientAuthenticationPacket'); exports.ComChangeUserPacket = require('./ComChangeUserPacket'); exports.ComPingPacket = require('./ComPingPacket'); @@ -17,4 +18,5 @@ exports.ResultSetHeaderPacket = require('./ResultSetHeaderPacket'); exports.RowDataPacket = require('./RowDataPacket'); exports.SSLRequestPacket = require('./SSLRequestPacket'); exports.StatisticsPacket = require('./StatisticsPacket'); +exports.UseAuthSwitchPacket = require('./UseAuthSwitchPacket'); exports.UseOldPasswordPacket = require('./UseOldPasswordPacket'); diff --git a/lib/protocol/sequences/ChangeUser.js b/lib/protocol/sequences/ChangeUser.js index 26be6dbbd..d0fc42b6b 100644 --- a/lib/protocol/sequences/ChangeUser.js +++ b/lib/protocol/sequences/ChangeUser.js @@ -8,11 +8,12 @@ Util.inherits(ChangeUser, Sequence); function ChangeUser(options, callback) { Sequence.call(this, options, callback); - this._user = options.user; - this._password = options.password; - this._database = options.database; - this._charsetNumber = options.charsetNumber; - this._currentConfig = options.currentConfig; + this._user = options.user; + this._password = options.password; + this._database = options.database; + this._charsetNumber = options.charsetNumber; + this._currentConfig = options.currentConfig; + this._authPluginName = options.authPluginName; } ChangeUser.prototype.start = function(handshakeInitializationPacket) { @@ -20,10 +21,12 @@ ChangeUser.prototype.start = function(handshakeInitializationPacket) { scrambleBuff = Auth.token(this._password, scrambleBuff); var packet = new Packets.ComChangeUserPacket({ - user : this._user, - scrambleBuff : scrambleBuff, - database : this._database, - charsetNumber : this._charsetNumber + user : this._user, + scrambleBuff : scrambleBuff, + database : this._database, + charsetNumber : this._charsetNumber, + clientPluginAuth : this._currentConfig.clientPluginAuth, + authPluginName : this._authPluginName || this._currentConfig.authPluginName }); this._currentConfig.user = this._user; @@ -34,8 +37,31 @@ ChangeUser.prototype.start = function(handshakeInitializationPacket) { this.emit('packet', packet); }; +ChangeUser.prototype.determinePacket = function(firstByte) { + if (firstByte === 0xff) { + return Packets.ErrorPacket; + } + + if (firstByte === 0xfe) { + return Packets.UseAuthSwitchPacket; + } + + return Packets.OkPacket; +}; + ChangeUser.prototype['ErrorPacket'] = function(packet) { var err = this._packetToError(packet); err.fatal = true; this.end(err); }; + +ChangeUser.prototype['UseAuthSwitchPacket'] = function(packet) { + try { + var scrambleBuff = Auth.tokenByPlugin(packet.authPluginName, packet.authPluginData, this._password); + this.emit('packet', new Packets.AuthSwitchPacket({ + scrambleBuff: scrambleBuff + })); + } catch (err) { + this.end(err); + } +}; diff --git a/lib/protocol/sequences/Handshake.js b/lib/protocol/sequences/Handshake.js index cff6df6d1..3680b819c 100644 --- a/lib/protocol/sequences/Handshake.js +++ b/lib/protocol/sequences/Handshake.js @@ -15,7 +15,7 @@ function Handshake(options, callback) { this._handshakeInitializationPacket = null; } -Handshake.prototype.determinePacket = function(firstByte) { +Handshake.prototype.determinePacket = function(firstByte, parser) { if (firstByte === 0xff) { return Packets.ErrorPacket; } @@ -25,7 +25,11 @@ Handshake.prototype.determinePacket = function(firstByte) { } if (firstByte === 0xfe) { - return Packets.UseOldPasswordPacket; + if (parser.packetLength() === 1) { + return Packets.UseOldPasswordPacket; + } else { + return Packets.UseAuthSwitchPacket; + } } return undefined; @@ -36,6 +40,10 @@ Handshake.prototype['HandshakeInitializationPacket'] = function(packet) { this._config.protocol41 = packet.protocol41; + this._config.clientPluginAuth = packet.clientPluginAuth; + + this._config.authPluginName = packet.authPluginName; + var serverSSLSupport = packet.serverCapabilities1 & ClientConstants.CLIENT_SSL; if (this._config.ssl) { @@ -68,29 +76,39 @@ Handshake.prototype._tlsUpgradeCompleteHandler = function() { Handshake.prototype._sendCredentials = function() { var packet = this._handshakeInitializationPacket; this.emit('packet', new Packets.ClientAuthenticationPacket({ - clientFlags : this._config.clientFlags, - maxPacketSize : this._config.maxPacketSize, - charsetNumber : this._config.charsetNumber, - user : this._config.user, - database : this._config.database, - protocol41 : packet.protocol41, - scrambleBuff : (packet.protocol41) + clientFlags : this._config.clientFlags, + maxPacketSize : this._config.maxPacketSize, + charsetNumber : this._config.charsetNumber, + user : this._config.user, + database : this._config.database, + protocol41 : packet.protocol41, + clientPluginAuth : this._config.clientPluginAuth, + authPluginName : this._config.authPluginName, + scrambleBuff : (packet.protocol41) ? Auth.token(this._config.password, packet.scrambleBuff()) : Auth.scramble323(packet.scrambleBuff(), this._config.password) })); }; -Handshake.prototype['UseOldPasswordPacket'] = function() { - if (!this._config.insecureAuth) { - var err = new Error( - 'MySQL server is requesting the old and insecure pre-4.1 auth mechanism. ' + - 'Upgrade the user password or use the {insecureAuth: true} option.' - ); - - err.code = 'HANDSHAKE_INSECURE_AUTH'; - err.fatal = true; +Handshake.prototype['UseAuthSwitchPacket'] = function(packet) { + if ((packet.authPluginName === 'mysql_old_password' || packet.authPluginName === 'mysql_clear_password') && !this._config.insecureAuth) { + this.end(HandshakeInsecureAuthError()); + return; + } + try { + var scrambleBuff = Auth.tokenByPlugin(packet.authPluginName, packet.authPluginData, this._config.password); + this.emit('packet', new Packets.AuthSwitchPacket({ + scrambleBuff: scrambleBuff + })); + } catch (err) { this.end(err); + } +}; + +Handshake.prototype['UseOldPasswordPacket'] = function() { + if (!this._config.insecureAuth) { + this.end(HandshakeInsecureAuthError()); return; } @@ -104,3 +122,14 @@ Handshake.prototype['ErrorPacket'] = function(packet) { err.fatal = true; this.end(err); }; + +function HandshakeInsecureAuthError() { + var err = new Error( + 'MySQL server is requesting the old and insecure pre-4.1 auth mechanism or using clear password. ' + + 'Upgrade the user password or use the {insecureAuth: true} option.' + ); + + err.code = 'HANDSHAKE_INSECURE_AUTH'; + err.fatal = true; + return err; +} diff --git a/test/FakeServer.js b/test/FakeServer.js index d5fda2a51..36965177a 100644 --- a/test/FakeServer.js +++ b/test/FakeServer.js @@ -65,7 +65,8 @@ function FakeConnection(socket) { this._clientAuthenticationPacket = null; this._oldPasswordPacket = null; this._handshakeOptions = {}; - + this._authSwitchResponse = null; + this._duringChangeUserPhase = false; socket.on('data', this._handleData.bind(this)); } @@ -76,7 +77,10 @@ FakeConnection.prototype.handshake = function(options) { scrambleBuff1 : Buffer.from('1020304050607080', 'hex'), scrambleBuff2 : Buffer.from('0102030405060708090A0B0C', 'hex'), serverCapabilities1 : 512, // only 1 flag, PROTOCOL_41 - protocol41 : true + serverCapabilities2 : 8, // only 1 flag, PLUGIN_AUTH + protocol41 : true, + clientPluginAuth : true, + authPluginName : 'mysql_native_password' // This is the default MySQL auth plugin }, this._handshakeOptions); this._handshakeInitializationPacket = new Packets.HandshakeInitializationPacket(packetOpiotns); @@ -260,8 +264,7 @@ FakeConnection.prototype._handleQueryPacket = function _handleQueryPacket(packet FakeConnection.prototype._parsePacket = function(header) { var Packet = this._determinePacket(header); - var packet = new Packet({protocol41: true}); - + var packet = new Packet({protocol41: true, clientPluginAuth: this._handshakeInitializationPacket.clientPluginAuth}); packet.parse(this._parser); switch (Packet) { @@ -269,6 +272,10 @@ FakeConnection.prototype._parsePacket = function(header) { this._clientAuthenticationPacket = packet; if (this._handshakeOptions.oldPassword) { this._sendPacket(new Packets.UseOldPasswordPacket()); + } else if (this._handshakeOptions.forceAuthSwitch) { + this._sendPacket(new Packets.UseAuthSwitchPacket({ + authPluginName : this._handshakeOptions.authSwitchPlugin || this._handshakeInitializationPacket.authPluginName, + authPluginData : Buffer.from('3DA0ADA7C9E1BB3A110575DF53306F9D2DE7FD0900', 'hex') })); } else if (this._handshakeOptions.password === 'passwd') { var expected = Buffer.from('3DA0ADA7C9E1BB3A110575DF53306F9D2DE7FD09', 'hex'); this._sendAuthResponse(packet, expected); @@ -289,6 +296,22 @@ FakeConnection.prototype._parsePacket = function(header) { this._sendAuthResponse(packet, expected); break; + case Packets.AuthSwitchPacket: + this._authSwitchResponse = packet; + if (this._duringChangeUserPhase && this._handshakeOptions.password !== 'passwd') { + this._sendPacket(new Packets.OkPacket()); + } else { + var expected = Auth.tokenByPlugin( + this._handshakeOptions.authSwitchPlugin || this._handshakeInitializationPacket.authPluginName, + Buffer.from('3DA0ADA7C9E1BB3A110575DF53306F9D2DE7FD09', 'hex'), + this._handshakeOptions.password); + this._sendAuthResponse(packet, expected); + } + if (this._duringChangeUserPhase) { + this._duringChangeUserPhase = false; + this._parser.resetPacketNumber(); + } + break; case Packets.ComQueryPacket: if (!this.emit('query', packet)) { this._handleQueryPacket(packet); @@ -318,17 +341,28 @@ FakeConnection.prototype._parsePacket = function(header) { } this._clientAuthenticationPacket = new Packets.ClientAuthenticationPacket({ - clientFlags : this._clientAuthenticationPacket.clientFlags, - filler : this._clientAuthenticationPacket.filler, - maxPacketSize : this._clientAuthenticationPacket.maxPacketSize, - protocol41 : this._clientAuthenticationPacket.protocol41, - charsetNumber : packet.charsetNumber, - database : packet.database, - scrambleBuff : packet.scrambleBuff, - user : packet.user + clientFlags : this._clientAuthenticationPacket.clientFlags, + filler : this._clientAuthenticationPacket.filler, + maxPacketSize : this._clientAuthenticationPacket.maxPacketSize, + protocol41 : this._clientAuthenticationPacket.protocol41, + clientPluginAuth : this._clientAuthenticationPacket.clientPluginAuth, + authPluginName : this._clientAuthenticationPacket.authPluginName, + charsetNumber : packet.charsetNumber, + database : packet.database, + scrambleBuff : packet.scrambleBuff, + user : packet.user }); - this._sendPacket(new Packets.OkPacket()); - this._parser.resetPacketNumber(); + + if (this._clientAuthenticationPacket.clientPluginAuth) { + this._duringChangeUserPhase = true; + this._sendPacket(new Packets.UseAuthSwitchPacket({ + authPluginName : this._handshakeOptions.authSwitchPlugin || this._handshakeInitializationPacket.authPluginName, + authPluginData : Buffer.from('3DA0ADA7C9E1BB3A110575DF53306F9D2DE7FD0900', 'hex') })); + } else { + this._sendPacket(new Packets.OkPacket()); + this._parser.resetPacketNumber(); + } + break; case Packets.ComQuitPacket: if (!this.emit('quit', packet)) { @@ -355,6 +389,10 @@ FakeConnection.prototype._determinePacket = function(header) { return Packets.OldPasswordPacket; } + if ((this._handshakeOptions.forceAuthSwitch && !this._authSwitchResponse) || this._duringChangeUserPhase) { + return Packets.AuthSwitchPacket; + } + var firstByte = this._parser.peak(); switch (firstByte) { case 0x01: return Packets.ComQuitPacket; diff --git a/test/integration/connection/test-auth-switch-oldpassword.js b/test/integration/connection/test-auth-switch-oldpassword.js new file mode 100644 index 000000000..931c9b853 --- /dev/null +++ b/test/integration/connection/test-auth-switch-oldpassword.js @@ -0,0 +1,48 @@ +var assert = require('assert'); +var common = require('../../common'); + +common.getTestConnection(function (err, connection) { + assert.ifError(err); + + connection.query('SHOW PLUGINS', function(err, result) { + if (err) { + common.skipTest('No client auth plugins supported.'); + } + + mysql_old_password_found = false; + result.forEach(function(plugin) { + if (plugin.Name === 'mysql_old_password' && plugin.Status === 'ACTIVE') { + mysql_old_password_found = true; + } + }); + + if (!mysql_old_password_found) { + common.skipTest('Can not found mysql_old_password auth plugin'); + } + + connection.query('CREATE USER \'old_pass_auth\'@\'%\' IDENTIFIED WITH mysql_old_password', function (err) { + assert.ifError(err); + connection.query('SET OLD_PASSWORDS=1', function (err) { + assert.ifError(err); + connection.query('SET PASSWORD FOR \'old_pass_auth\'@\'%\' = PASSWORD(\'test\')', function (err) { + assert.ifError(err); + connection.query('SET GLOBAL SECURE_AUTH=0', function (err) { + assert.ifError(err); + common.getTestConnection({user: 'old_pass_auth', password: 'test', insecureAuth: true}, function (err, conn) { + assert.ifError(err); + conn.end(assert.ifError); + connection.query('SET GLOBAL SECURE_AUTH=1', function (err) { + assert.ifError(err); + connection.query('DROP USER \'old_pass_auth\'@\'%\'', function (err) { + assert.ifError(err); + connection.end(assert.ifError); + }); + }); + }); + }); + }); + }); + }); + }); +}); + diff --git a/test/unit/connection/test-change-user-no-server-auth-plugin.js b/test/unit/connection/test-change-user-no-server-auth-plugin.js new file mode 100644 index 000000000..2f0f8834d --- /dev/null +++ b/test/unit/connection/test-change-user-no-server-auth-plugin.js @@ -0,0 +1,40 @@ +var assert = require('assert'); +var common = require('../../common'); +var connection = common.createConnection({ + port : common.fakeServerPort, + user : 'user_1', + password : 'passwd' +}); + +var server = common.createFakeServer(); + +server.on('connection', function(incomingConnection) { + incomingConnection.handshake({ + user : connection.config.user, + password : connection.config.password, + serverCapabilities2 : 0, // No PLUGIN_AUTH + clientPluginAuth : false, + authPluginName : null + }); +}); + +server.listen(common.fakeServerPort, function(err) { + assert.ifError(err); + + connection.query('SELECT CURRENT_USER()', function (err, result) { + assert.ifError(err); + assert.strictEqual(result[0]['CURRENT_USER()'], 'user_1@localhost'); + + connection.changeUser({user: 'user_2', password: 'passwd'}, function (err) { + assert.ifError(err); + + connection.query('SELECT CURRENT_USER()', function (err, result) { + assert.ifError(err); + assert.strictEqual(result[0]['CURRENT_USER()'], 'user_2@localhost'); + + connection.destroy(); + server.destroy(); + }); + }); + }); +}); diff --git a/test/unit/connection/test-change-user-unknown-plugin.js b/test/unit/connection/test-change-user-unknown-plugin.js new file mode 100644 index 000000000..a997d1dcc --- /dev/null +++ b/test/unit/connection/test-change-user-unknown-plugin.js @@ -0,0 +1,32 @@ +var assert = require('assert'); +var common = require('../../common'); +var connection = common.createConnection({ + port : common.fakeServerPort, + user : 'user_1', + password : 'passwd' +}); + +var server = common.createFakeServer(); + +server.on('connection', function(incomingConnection) { + incomingConnection.handshake({ + user : connection.config.user, + password : connection.config.password, + authPluginName : 'mysql_unsupported_plugin' + }); +}); + +server.listen(common.fakeServerPort, function(err) { + assert.ifError(err); + + connection.query('SELECT CURRENT_USER()', function (err, result) { + assert.ifError(err); + assert.strictEqual(result[0]['CURRENT_USER()'], 'user_1@localhost'); + + connection.changeUser({user: 'user_2', password: 'passwd'}, function (err) { + assert.equal(err.code, 'UNSUPPORTED_AUTH_PLUGIN'); + connection.destroy(); + server.destroy(); + }); + }); +}); diff --git a/test/unit/connection/test-connection-auth-switch-error.js b/test/unit/connection/test-connection-auth-switch-error.js new file mode 100644 index 000000000..9c34303fd --- /dev/null +++ b/test/unit/connection/test-connection-auth-switch-error.js @@ -0,0 +1,27 @@ +var common = require('../../common'); +var connection = common.createConnection({ + port : common.fakeServerPort, + password : 'authswitch' +}); +var assert = require('assert'); + +var server = common.createFakeServer(); + +server.listen(common.fakeServerPort, function(err) { + if (err) throw err; + + connection.connect(function(err) { + assert.equal(err.code, 'UNSUPPORTED_AUTH_PLUGIN'); + connection.destroy(); + server.destroy(); + }); +}); + +server.on('connection', function(incomingConnection) { + incomingConnection.handshake({ + user : connection.config.user, + password : connection.config.password, + forceAuthSwitch : true, + authSwitchPlugin : 'mysql_unsupported_password' + }); +}); diff --git a/test/unit/connection/test-connection-auth-switch-oldpass-error.js b/test/unit/connection/test-connection-auth-switch-oldpass-error.js new file mode 100644 index 000000000..7df56a31d --- /dev/null +++ b/test/unit/connection/test-connection-auth-switch-oldpass-error.js @@ -0,0 +1,27 @@ +var common = require('../../common'); +var connection = common.createConnection({ + port : common.fakeServerPort, + password : 'authswitch' +}); +var assert = require('assert'); + +var server = common.createFakeServer(); + +server.listen(common.fakeServerPort, function(err) { + if (err) throw err; + + connection.connect(function(err) { + assert.equal(err.code, 'HANDSHAKE_INSECURE_AUTH'); + connection.destroy(); + server.destroy(); + }); +}); + +server.on('connection', function(incomingConnection) { + incomingConnection.handshake({ + user : connection.config.user, + password : connection.config.password, + forceAuthSwitch : true, + authSwitchPlugin : 'mysql_old_password' + }); +}); diff --git a/test/unit/connection/test-connection-auth-switch-oldpass.js b/test/unit/connection/test-connection-auth-switch-oldpass.js new file mode 100644 index 000000000..540a46415 --- /dev/null +++ b/test/unit/connection/test-connection-auth-switch-oldpass.js @@ -0,0 +1,36 @@ +var common = require('../../common'); +var connection = common.createConnection({ + port : common.fakeServerPort, + password : 'authswitch', + insecureAuth : true +}); +var assert = require('assert'); + +var server = common.createFakeServer(); + +var connected; +server.listen(common.fakeServerPort, function(err) { + if (err) throw err; + + connection.connect(function(err, result) { + if (err) throw err; + + connected = result; + + connection.destroy(); + server.destroy(); + }); +}); + +server.on('connection', function(incomingConnection) { + incomingConnection.handshake({ + user : connection.config.user, + password : connection.config.password, + forceAuthSwitch : true, + authSwitchPlugin : 'mysql_old_password' + }); +}); + +process.on('exit', function() { + assert.equal(connected.fieldCount, 0); +}); diff --git a/test/unit/connection/test-connection-auth-switch.js b/test/unit/connection/test-connection-auth-switch.js new file mode 100644 index 000000000..42751c0c6 --- /dev/null +++ b/test/unit/connection/test-connection-auth-switch.js @@ -0,0 +1,35 @@ +var common = require('../../common'); +var connection = common.createConnection({ + port : common.fakeServerPort, + password : 'authswitch' +}); +var assert = require('assert'); + +var server = common.createFakeServer(); + +var connected; +server.listen(common.fakeServerPort, function(err) { + if (err) throw err; + + connection.connect(function(err, result) { + if (err) throw err; + + connected = result; + + connection.destroy(); + server.destroy(); + }); +}); + +server.on('connection', function(incomingConnection) { + incomingConnection.handshake({ + user : connection.config.user, + password : connection.config.password, + forceAuthSwitch : true, + authSwitchPlugin : 'mysql_native_password' + }); +}); + +process.on('exit', function() { + assert.equal(connected.fieldCount, 0); +}); diff --git a/test/unit/connection/test-old-password-error.js b/test/unit/connection/test-old-password-error.js new file mode 100644 index 000000000..36afddc57 --- /dev/null +++ b/test/unit/connection/test-old-password-error.js @@ -0,0 +1,26 @@ +var common = require('../../common'); +var connection = common.createConnection({ + port : common.fakeServerPort, + password : 'oldpw' +}); +var assert = require('assert'); + +var server = common.createFakeServer(); + +server.listen(common.fakeServerPort, function(err) { + if (err) throw err; + + connection.connect(function(err) { + assert.equal(err.code, 'HANDSHAKE_INSECURE_AUTH'); + connection.destroy(); + server.destroy(); + }); +}); + +server.on('connection', function(incomingConnection) { + incomingConnection.handshake({ + user : connection.config.user, + password : connection.config.password, + oldPassword : true + }); +});