Skip to content

Commit

Permalink
Initial work on porting mysql < 4.1 authentication code
Browse files Browse the repository at this point in the history
  • Loading branch information
felixge committed Sep 2, 2010
1 parent 5887e9d commit aef575b
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 2 deletions.
53 changes: 53 additions & 0 deletions lib/mysql/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

26 changes: 26 additions & 0 deletions lib/mysql/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions lib/mysql/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++;
88 changes: 88 additions & 0 deletions test/fixture/libmysql_password.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include <stdio.h>
#include <string.h>
#include <math.h>

#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;
}

12 changes: 12 additions & 0 deletions test/simple/test-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
})();
78 changes: 76 additions & 2 deletions test/simple/test-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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);
});
14 changes: 14 additions & 0 deletions test/simple/test-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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]));
Expand Down

0 comments on commit aef575b

Please sign in to comment.