From aef575b5f3ddc4b99bd63668b1963bdfbb5084e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Sat, 21 Aug 2010 12:12:35 +0200 Subject: [PATCH] Initial work on porting mysql < 4.1 authentication code --- lib/mysql/auth.js | 53 +++++++++++++++++++ lib/mysql/client.js | 26 ++++++++++ lib/mysql/parser.js | 9 ++++ test/fixture/libmysql_password.c | 88 ++++++++++++++++++++++++++++++++ test/simple/test-auth.js | 12 +++++ test/simple/test-client.js | 78 +++++++++++++++++++++++++++- test/simple/test-parser.js | 14 +++++ 7 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 test/fixture/libmysql_password.c diff --git a/lib/mysql/auth.js b/lib/mysql/auth.js index 09e2d6c1c..0c4c062d9 100644 --- a/lib/mysql/auth.js +++ b/lib/mysql/auth.js @@ -30,3 +30,56 @@ exports.token = function(password, scramble) { var stage3 = sha1(scramble.toString('binary') + stage2); return xor(stage3, stage1); }; + +// This is a port of sql/password.c:hash_password which needs to be used for +// pre-4.1 passwords. +exports.hashPassword = function(password) { + var nr = 1345345333, + add = 7, + nr2 = 0x12345671, + result = new Buffer(8); + + password = new Buffer(password); + for (var i = 0; i < password.length; i++) { + var c = password[i]; + if (c == 32 || c == 9) { + // skip space in password + continue; + } + + nr ^= this.multiply(((nr & 63) + add), c) + (nr << 8); + nr2 += (nr2 << 8) ^ nr; + add += c; + } + + this.int32Write(result, nr & ((1 << 31) - 1), 0); + this.int32Write(result, nr2 & ((1 << 31) - 1), 4); + + return result; +} + +// Provided by Herbert Vojčík, needed to deal with float point precision problems in JS +exports.multiply = function(x, y) { + var lowX = x & 0xffff, + highX = (x >> 16) & 0xffff, + lowY = y & 0xffff, + highY = (y >> 16) & 0xffff + result = + 0x10000 * ((highX * lowY + lowX * highY) & 0xffff) + lowY * lowX; + + //result = result & 0xffffffff; + result = result % 0x100000000; + return result; +} + +exports.int32Write = function(buffer, number, offset) { + var unsigned = (number < 0) ? (number + 0x100000000) : number; + buffer[offset] = Math.floor(unsigned / 0xffffff); + unsigned &= 0xffffff; + buffer[offset + 1] = Math.floor(unsigned / 0xffff); + unsigned &= 0xffff; + buffer[offset + 2] = Math.floor(unsigned / 0xff); + unsigned &= 0xff; + buffer[offset + 3] = Math.floor(unsigned); +}; + diff --git a/lib/mysql/client.js b/lib/mysql/client.js index 9c9ac8bea..ab4bc2b34 100644 --- a/lib/mysql/client.js +++ b/lib/mysql/client.js @@ -194,6 +194,11 @@ Client.prototype._handlePacket = function(packet) { return; } + if (packet.type == Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET) { + this._sendOldAuth(packet); + return; + } + var type = packet.type, task = this._queue[0], delegate = (task) @@ -284,6 +289,27 @@ Client.prototype._debugPacket = function(packet) { console.log('<- %s: %j', packetName, packet); }; +Client.prototype._sendOldAuth = function(greeting) { + //var token = auth.token(this.password, greeting.scrambleBuffer), + //packetSize = ( + //4 + 4 + 1 + 23 + + //this.user.length + 1 + + //token.length + 1 + + //this.database.length + 1 + //), + //packet = new OutgoingPacket(packetSize, greeting.number+1); + + //packet.writeNumber(4, this.flags); + //packet.writeNumber(4, this.maxPacketSize); + //packet.writeNumber(1, this.charsetNumber); + //packet.writeFiller(23); + //packet.writeNullTerminated(this.user); + //packet.writeLengthCoded(token); + //packet.writeNullTerminated(this.database); + + //this.write(packet); +}; + // Client Flags Client.LONG_PASSWORD = 1; Client.FOUND_ROWS = 2; diff --git a/lib/mysql/parser.js b/lib/mysql/parser.js index 0ab523585..077a0e88f 100644 --- a/lib/mysql/parser.js +++ b/lib/mysql/parser.js @@ -12,6 +12,7 @@ function Parser() { this.state = Parser.PACKET_LENGTH; this.packet = null; this.greeted = false; + this.authenticated = false; this.receivingFieldPackets = false; this.receivingRowPackets = false; @@ -211,7 +212,14 @@ Parser.prototype.write = function(buffer) { break; } + if (c == 0xfe && !this.authenticated) { + packet.type = Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET; + break; + } + if (c === 0x00) { + // after the first OK PACKET, we are authenticated + this.authenticated = true; packet.type = Parser.OK_PACKET; advance(Parser.AFFECTED_ROWS) break; @@ -606,3 +614,4 @@ Parser.ROW_DATA_PACKET = p++; Parser.ROW_DATA_BINARY_PACKET = p++; Parser.OK_FOR_PREPARED_STATEMENT_PACKET = p++; Parser.PARAMETER_PACKET = p++; +Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET = p++; diff --git a/test/fixture/libmysql_password.c b/test/fixture/libmysql_password.c new file mode 100644 index 000000000..048d95f85 --- /dev/null +++ b/test/fixture/libmysql_password.c @@ -0,0 +1,88 @@ +#include +#include +#include + +#define SCRAMBLE_LENGTH_323 8 + +typedef unsigned long ulong; +typedef unsigned int uint; +typedef unsigned char uchar; + +struct rand_struct { + unsigned long seed1,seed2,max_value; + double max_value_dbl; +}; + +void hash_password(ulong *result, const char *password, uint password_len) +{ + register ulong nr=1345345333L, add=7, nr2=0x12345671L; + ulong tmp; + const char *password_end= password + password_len; + for (; password < password_end; password++) + { + if (*password == ' ' || *password == '\t') + continue; /* skip space in password */ + tmp= (ulong) (uchar) *password; + nr^= (((nr & 63)+add)*tmp)+ (nr << 8); + nr2+=(nr2 << 8) ^ nr; + add+=tmp; + } + result[0]=nr & (((ulong) 1L << 31) -1L); /* Don't use sign bit (str2int) */; + result[1]=nr2 & (((ulong) 1L << 31) -1L); +} + +void randominit(struct rand_struct *rand_st, ulong seed1, ulong seed2) +{ /* For mysql 3.21.# */ +#ifdef HAVE_purify + bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */ +#endif + rand_st->max_value= 0x3FFFFFFFL; + rand_st->max_value_dbl=(double) rand_st->max_value; + rand_st->seed1=seed1%rand_st->max_value ; + rand_st->seed2=seed2%rand_st->max_value; +} + +double my_rnd(struct rand_struct *rand_st) +{ + rand_st->seed1=(rand_st->seed1*3+rand_st->seed2) % rand_st->max_value; + rand_st->seed2=(rand_st->seed1+rand_st->seed2+33) % rand_st->max_value; + return (((double) rand_st->seed1)/rand_st->max_value_dbl); +} + +void scramble_323(char *to, const char *message, const char *password) +{ + struct rand_struct rand_st; + ulong hash_pass[2], hash_message[2]; + + if (password && password[0]) + { + char extra, *to_start=to; + const char *message_end= message + SCRAMBLE_LENGTH_323; + hash_password(hash_pass,password, (uint) strlen(password)); + hash_password(hash_message, message, SCRAMBLE_LENGTH_323); + randominit(&rand_st,hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + for (; message < message_end; message++) + *to++= (char) (floor(my_rnd(&rand_st)*31)+64); + extra=(char) (floor(my_rnd(&rand_st)*31)); + while (to_start != to) + *(to_start++)^=extra; + } + *to= 0; +} + +int main() { + const char password[] = "root"; + uint password_len = 4; + ulong result[2]; + + printf("hash_password(\"%s\"):\n", password); + + hash_password(result, password, password_len); + + printf("result 1: %ld\n", result[0]); + printf("result 2: %ld\n", result[1]); + + return 23; +} + diff --git a/test/simple/test-auth.js b/test/simple/test-auth.js index ac89dc686..3eb982355 100644 --- a/test/simple/test-auth.js +++ b/test/simple/test-auth.js @@ -45,3 +45,15 @@ test(function token() { assert.deepEqual(auth.token(null, SCRAMBLE), new Buffer(0)); })(); }); + +(function testHashPassword() { + var BUFFER; + gently.expect(auth, 'int32Write', 2, function (buffer, number, offset) { + assert.equal(number, [1732607522, 1780094397][offset / 4]); + assert.equal(buffer.length, 8); + BUFFER = buffer; + }); + + var result = auth.hashPassword('root'); + assert.strictEqual(result, BUFFER); +})(); diff --git a/test/simple/test-client.js b/test/simple/test-client.js index 7dd78c896..809e2380b 100644 --- a/test/simple/test-client.js +++ b/test/simple/test-client.js @@ -363,6 +363,16 @@ test(function _handlePacket() { client._handlePacket(PACKET); })(); + (function testUseOldPasswordProtocol() { + var PACKET = {type: Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET}; + + gently.expect(client, '_sendOldAuth', function (packet) { + assert.strictEqual(packet, PACKET); + }); + + client._handlePacket(PACKET); + })(); + (function testNormalOk() { var PACKET = {type: Parser.OK_PACKET}; @@ -437,9 +447,9 @@ test(function _handlePacket() { })(); }); -test(function _sendPacket() { +test(function _sendAuth() { var GREETING = {scrambleBuffer: new Buffer(20), number: 1}, - TOKEN = new Buffer(8), + TOKEN = new Buffer(20), PACKET; client.user = 'root'; @@ -541,3 +551,67 @@ test(function _packetToUserObject() { assert.equal(err.errorNumber, undefined); })(); }); + +test(function _sendOldAuth() { + var GREETING = {scrambleBuffer: new Buffer(20), number: 1}, + TOKEN = new Buffer(8), + PACKET; + + client.user = 'root'; + client.password = 'hello world'; + + //gently.expect(HIJACKED['./auth'], 'token', function(password, scramble) { + //assert.strictEqual(password, client.password); + //assert.strictEqual(scramble, GREETING.scrambleBuffer); + //return TOKEN; + //}); + + //gently.expect(OutgoingPacketStub, 'new', function(size, number) { + //assert.equal(size, ( + //4 + 4 + 1 + 23 + + //client.user.length + 1 + + //TOKEN.length + 1 + + //client.database.length + 1 + //)); + + //assert.equal(number, GREETING.number + 1); + //PACKET = this; + + //gently.expect(PACKET, 'writeNumber', function(bytes, number) { + //assert.strictEqual(bytes, 4); + //assert.strictEqual(client.flags, number); + //}); + + //gently.expect(PACKET, 'writeNumber', function(bytes, number) { + //assert.strictEqual(bytes, 4); + //assert.strictEqual(client.maxPacketSize, number); + //}); + + //gently.expect(PACKET, 'writeNumber', function(bytes, number) { + //assert.strictEqual(bytes, 1); + //assert.strictEqual(client.charsetNumber, number); + //}); + + //gently.expect(PACKET, 'writeFiller', function(bytes) { + //assert.strictEqual(bytes, 23); + //}); + + //gently.expect(PACKET, 'writeNullTerminated', function(user) { + //assert.strictEqual(user, client.user); + //}); + + //gently.expect(PACKET, 'writeLengthCoded', function(token) { + //assert.strictEqual(token, TOKEN); + //}); + + //gently.expect(PACKET, 'writeNullTerminated', function(database) { + //assert.strictEqual(database, client.database); + //}); + + //gently.expect(client, 'write', function(packet) { + //assert.strictEqual(packet, PACKET); + //}); + //}); + + client._sendOldAuth(GREETING); +}); diff --git a/test/simple/test-parser.js b/test/simple/test-parser.js index ccdc9d00e..9ea60161d 100644 --- a/test/simple/test-parser.js +++ b/test/simple/test-parser.js @@ -15,6 +15,7 @@ test(function constructor() { assert.strictEqual(parser.state, Parser.PACKET_LENGTH); assert.strictEqual(parser.packet, null); assert.strictEqual(parser.greeted, false); + assert.strictEqual(parser.authenticated, false); assert.strictEqual(parser.receivingFieldPackets, false); assert.strictEqual(parser.receivingRowPackets, false); assert.strictEqual(parser._lengthCodedLength, null); @@ -114,6 +115,18 @@ test(function write() { assert.strictEqual(parser.packet, null); })(); + (function testUseOldPasswordProtocolPacket() { + parser.write(new Buffer([1, 0, 0, 1])); + + gently.expect(parser, 'emit', function(event, val) { + assert.equal(event, 'packet'); + assert.equal(val.type, Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET); + }); + + parser.write(new Buffer([254])); + })(); + + (function testErrorPacket() { parser.write(new Buffer([12, 0, 0, 1])); assert.equal(parser.state, Parser.FIELD_COUNT); @@ -151,6 +164,7 @@ test(function write() { parser.write(new Buffer([0x00])); assert.equal(packet.type, Parser.OK_PACKET); + assert.equal(parser.authenticated, true); assert.equal(parser.state, Parser.AFFECTED_ROWS); parser.write(new Buffer([252, 17, 23]));