Skip to content

Commit 0cb56d9

Browse files
committed
Initial work on porting mysql < 4.1 authentication code
1 parent 4d012d6 commit 0cb56d9

File tree

8 files changed

+284
-2
lines changed

8 files changed

+284
-2
lines changed

lib/mysql/auth.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,56 @@ exports.token = function(password, scramble) {
2626
var stage3 = sha1(scramble.toString('binary') + stage2);
2727
return xor(stage3, stage1);
2828
};
29+
30+
// This is a port of sql/password.c:hash_password which needs to be used for
31+
// pre-4.1 passwords.
32+
exports.hashPassword = function(password) {
33+
var nr = 1345345333,
34+
add = 7,
35+
nr2 = 0x12345671,
36+
result = new Buffer(8);
37+
38+
password = new Buffer(password);
39+
for (var i = 0; i < password.length; i++) {
40+
var c = password[i];
41+
if (c == 32 || c == 9) {
42+
// skip space in password
43+
continue;
44+
}
45+
46+
nr ^= this.multiply(((nr & 63) + add), c) + (nr << 8);
47+
nr2 += (nr2 << 8) ^ nr;
48+
add += c;
49+
}
50+
51+
this.int32Write(result, nr & ((1 << 31) - 1), 0);
52+
this.int32Write(result, nr2 & ((1 << 31) - 1), 4);
53+
54+
return result;
55+
}
56+
57+
// Provided by Herbert Vojčík, needed to deal with float point precision problems in JS
58+
exports.multiply = function(x, y) {
59+
var lowX = x & 0xffff,
60+
highX = (x >> 16) & 0xffff,
61+
lowY = y & 0xffff,
62+
highY = (y >> 16) & 0xffff
63+
result =
64+
0x10000 * ((highX * lowY + lowX * highY) & 0xffff) + lowY * lowX;
65+
66+
//result = result & 0xffffffff;
67+
result = result % 0x100000000;
68+
return result;
69+
}
70+
71+
exports.int32Write = function(buffer, number, offset) {
72+
var unsigned = (number < 0) ? (number + 0x100000000) : number;
73+
buffer[offset] = Math.floor(unsigned / 0xffffff);
74+
unsigned &= 0xffffff;
75+
buffer[offset + 1] = Math.floor(unsigned / 0xffff);
76+
unsigned &= 0xffff;
77+
buffer[offset + 2] = Math.floor(unsigned / 0xff);
78+
unsigned &= 0xff;
79+
buffer[offset + 3] = Math.floor(unsigned);
80+
};
81+

lib/mysql/client.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ Client.prototype._handlePacket = function(packet) {
184184
return;
185185
}
186186

187+
if (packet.type == Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET) {
188+
this._sendOldAuth(packet);
189+
return;
190+
}
191+
187192
var type = packet.type,
188193
task = this._queue[0],
189194
delegate = (task)
@@ -230,6 +235,28 @@ Client.prototype._sendAuth = function(greeting) {
230235
this.write(packet);
231236
};
232237

238+
Client.prototype._sendOldAuth = function(greeting) {
239+
//var token = auth.token(this.password, greeting.scrambleBuffer),
240+
//packetSize = (
241+
//4 + 4 + 1 + 23 +
242+
//this.user.length + 1 +
243+
//token.length + 1 +
244+
//this.database.length + 1
245+
//),
246+
//packet = new OutgoingPacket(packetSize, greeting.number+1);
247+
248+
//packet.writeNumber(4, this.flags);
249+
//packet.writeNumber(4, this.maxPacketSize);
250+
//packet.writeNumber(1, this.charsetNumber);
251+
//packet.writeFiller(23);
252+
//packet.writeNullTerminated(this.user);
253+
//packet.writeLengthCoded(token);
254+
//packet.writeNullTerminated(this.database);
255+
256+
//this.write(packet);
257+
};
258+
259+
233260
// Client Flags
234261
Client.LONG_PASSWORD = 1;
235262
Client.FOUND_ROWS = 2;

lib/mysql/parser.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ function Parser() {
1212
this.state = Parser.PACKET_LENGTH;
1313
this.packet = null;
1414
this.greeted = false;
15+
this.authenticated = false;
1516
this.receivingFieldPackets = false;
1617
this.receivingRowPackets = false;
1718

@@ -211,7 +212,14 @@ Parser.prototype.write = function(buffer) {
211212
break;
212213
}
213214

215+
if (c == 0xfe && !this.authenticated) {
216+
packet.type = Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET;
217+
break;
218+
}
219+
214220
if (c === 0x00) {
221+
// after the first OK PACKET, we are authenticated
222+
this.authenticated = true;
215223
packet.type = Parser.OK_PACKET;
216224
advance(Parser.AFFECTED_ROWS)
217225
break;
@@ -582,3 +590,4 @@ Parser.ROW_DATA_PACKET = p++;
582590
Parser.ROW_DATA_BINARY_PACKET = p++;
583591
Parser.OK_FOR_PREPARED_STATEMENT_PACKET = p++;
584592
Parser.PARAMETER_PACKET = p++;
593+
Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET = p++;

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{ "name" : "mysql"
2+
, "version": "0.1.0"
3+
, "directories" : { "lib" : "./lib/mysql" }
4+
, "main" : "./lib/mysql/index"
5+
}

test/fixture/libmysql_password.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
#include <math.h>
4+
5+
#define SCRAMBLE_LENGTH_323 8
6+
7+
typedef unsigned long ulong;
8+
typedef unsigned int uint;
9+
typedef unsigned char uchar;
10+
11+
struct rand_struct {
12+
unsigned long seed1,seed2,max_value;
13+
double max_value_dbl;
14+
};
15+
16+
void hash_password(ulong *result, const char *password, uint password_len)
17+
{
18+
register ulong nr=1345345333L, add=7, nr2=0x12345671L;
19+
ulong tmp;
20+
const char *password_end= password + password_len;
21+
for (; password < password_end; password++)
22+
{
23+
if (*password == ' ' || *password == '\t')
24+
continue; /* skip space in password */
25+
tmp= (ulong) (uchar) *password;
26+
nr^= (((nr & 63)+add)*tmp)+ (nr << 8);
27+
nr2+=(nr2 << 8) ^ nr;
28+
add+=tmp;
29+
}
30+
result[0]=nr & (((ulong) 1L << 31) -1L); /* Don't use sign bit (str2int) */;
31+
result[1]=nr2 & (((ulong) 1L << 31) -1L);
32+
}
33+
34+
void randominit(struct rand_struct *rand_st, ulong seed1, ulong seed2)
35+
{ /* For mysql 3.21.# */
36+
#ifdef HAVE_purify
37+
bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */
38+
#endif
39+
rand_st->max_value= 0x3FFFFFFFL;
40+
rand_st->max_value_dbl=(double) rand_st->max_value;
41+
rand_st->seed1=seed1%rand_st->max_value ;
42+
rand_st->seed2=seed2%rand_st->max_value;
43+
}
44+
45+
double my_rnd(struct rand_struct *rand_st)
46+
{
47+
rand_st->seed1=(rand_st->seed1*3+rand_st->seed2) % rand_st->max_value;
48+
rand_st->seed2=(rand_st->seed1+rand_st->seed2+33) % rand_st->max_value;
49+
return (((double) rand_st->seed1)/rand_st->max_value_dbl);
50+
}
51+
52+
void scramble_323(char *to, const char *message, const char *password)
53+
{
54+
struct rand_struct rand_st;
55+
ulong hash_pass[2], hash_message[2];
56+
57+
if (password && password[0])
58+
{
59+
char extra, *to_start=to;
60+
const char *message_end= message + SCRAMBLE_LENGTH_323;
61+
hash_password(hash_pass,password, (uint) strlen(password));
62+
hash_password(hash_message, message, SCRAMBLE_LENGTH_323);
63+
randominit(&rand_st,hash_pass[0] ^ hash_message[0],
64+
hash_pass[1] ^ hash_message[1]);
65+
for (; message < message_end; message++)
66+
*to++= (char) (floor(my_rnd(&rand_st)*31)+64);
67+
extra=(char) (floor(my_rnd(&rand_st)*31));
68+
while (to_start != to)
69+
*(to_start++)^=extra;
70+
}
71+
*to= 0;
72+
}
73+
74+
int main() {
75+
const char password[] = "root";
76+
uint password_len = 4;
77+
ulong result[2];
78+
79+
printf("hash_password(\"%s\"):\n", password);
80+
81+
hash_password(result, password, password_len);
82+
83+
printf("result 1: %ld\n", result[0]);
84+
printf("result 2: %ld\n", result[1]);
85+
86+
return 23;
87+
}
88+

test/simple/test-auth.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,15 @@ test(function token() {
3838

3939
assert.deepEqual(auth.token('root', SCRAMBLE), TOKEN);
4040
});
41+
42+
(function testHashPassword() {
43+
var BUFFER;
44+
gently.expect(auth, 'int32Write', 2, function (buffer, number, offset) {
45+
assert.equal(number, [1732607522, 1780094397][offset / 4]);
46+
assert.equal(buffer.length, 8);
47+
BUFFER = buffer;
48+
});
49+
50+
var result = auth.hashPassword('root');
51+
assert.strictEqual(result, BUFFER);
52+
})();

test/simple/test-client.js

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,16 @@ test(function _handlePacket() {
354354
client._handlePacket(PACKET);
355355
})();
356356

357+
(function testUseOldPasswordProtocol() {
358+
var PACKET = {type: Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET};
359+
360+
gently.expect(client, '_sendOldAuth', function (packet) {
361+
assert.strictEqual(packet, PACKET);
362+
});
363+
364+
client._handlePacket(PACKET);
365+
})();
366+
357367
(function testNormalOk() {
358368
var PACKET = {type: Parser.OK_PACKET},
359369
TASK = {delegate: gently.expect(function okCb(err, packet) {
@@ -411,9 +421,9 @@ test(function _handlePacket() {
411421
})();
412422
});
413423

414-
test(function _sendPacket() {
424+
test(function _sendAuth() {
415425
var GREETING = {scrambleBuffer: new Buffer(20), number: 1},
416-
TOKEN = new Buffer(8),
426+
TOKEN = new Buffer(20),
417427
PACKET;
418428

419429
client.user = 'root';
@@ -475,3 +485,67 @@ test(function _sendPacket() {
475485

476486
client._sendAuth(GREETING);
477487
});
488+
489+
test(function _sendOldAuth() {
490+
var GREETING = {scrambleBuffer: new Buffer(20), number: 1},
491+
TOKEN = new Buffer(8),
492+
PACKET;
493+
494+
client.user = 'root';
495+
client.password = 'hello world';
496+
497+
//gently.expect(HIJACKED['./auth'], 'token', function(password, scramble) {
498+
//assert.strictEqual(password, client.password);
499+
//assert.strictEqual(scramble, GREETING.scrambleBuffer);
500+
//return TOKEN;
501+
//});
502+
503+
//gently.expect(OutgoingPacketStub, 'new', function(size, number) {
504+
//assert.equal(size, (
505+
//4 + 4 + 1 + 23 +
506+
//client.user.length + 1 +
507+
//TOKEN.length + 1 +
508+
//client.database.length + 1
509+
//));
510+
511+
//assert.equal(number, GREETING.number + 1);
512+
//PACKET = this;
513+
514+
//gently.expect(PACKET, 'writeNumber', function(bytes, number) {
515+
//assert.strictEqual(bytes, 4);
516+
//assert.strictEqual(client.flags, number);
517+
//});
518+
519+
//gently.expect(PACKET, 'writeNumber', function(bytes, number) {
520+
//assert.strictEqual(bytes, 4);
521+
//assert.strictEqual(client.maxPacketSize, number);
522+
//});
523+
524+
//gently.expect(PACKET, 'writeNumber', function(bytes, number) {
525+
//assert.strictEqual(bytes, 1);
526+
//assert.strictEqual(client.charsetNumber, number);
527+
//});
528+
529+
//gently.expect(PACKET, 'writeFiller', function(bytes) {
530+
//assert.strictEqual(bytes, 23);
531+
//});
532+
533+
//gently.expect(PACKET, 'writeNullTerminated', function(user) {
534+
//assert.strictEqual(user, client.user);
535+
//});
536+
537+
//gently.expect(PACKET, 'writeLengthCoded', function(token) {
538+
//assert.strictEqual(token, TOKEN);
539+
//});
540+
541+
//gently.expect(PACKET, 'writeNullTerminated', function(database) {
542+
//assert.strictEqual(database, client.database);
543+
//});
544+
545+
//gently.expect(client, 'write', function(packet) {
546+
//assert.strictEqual(packet, PACKET);
547+
//});
548+
//});
549+
550+
client._sendOldAuth(GREETING);
551+
});

test/simple/test-parser.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ test(function constructor() {
1515
assert.strictEqual(parser.state, Parser.PACKET_LENGTH);
1616
assert.strictEqual(parser.packet, null);
1717
assert.strictEqual(parser.greeted, false);
18+
assert.strictEqual(parser.authenticated, false);
1819
assert.strictEqual(parser.receivingFieldPackets, false);
1920
assert.strictEqual(parser.receivingRowPackets, false);
2021
assert.strictEqual(parser._lengthCodedLength, null);
@@ -114,6 +115,18 @@ test(function write() {
114115
assert.strictEqual(parser.packet, null);
115116
})();
116117

118+
(function testUseOldPasswordProtocolPacket() {
119+
parser.write(new Buffer([1, 0, 0, 1]));
120+
121+
gently.expect(parser, 'emit', function(event, val) {
122+
assert.equal(event, 'packet');
123+
assert.equal(val.type, Parser.USE_OLD_PASSWORD_PROTOCOL_PACKET);
124+
});
125+
126+
parser.write(new Buffer([254]));
127+
})();
128+
129+
117130
(function testErrorPacket() {
118131
parser.write(new Buffer([12, 0, 0, 1]));
119132
assert.equal(parser.state, Parser.FIELD_COUNT);
@@ -151,6 +164,7 @@ test(function write() {
151164

152165
parser.write(new Buffer([0x00]));
153166
assert.equal(packet.type, Parser.OK_PACKET);
167+
assert.equal(parser.authenticated, true);
154168
assert.equal(parser.state, Parser.AFFECTED_ROWS);
155169

156170
parser.write(new Buffer([252, 17, 23]));

0 commit comments

Comments
 (0)