[go: nahoru, domu]

Skip to content

Commit

Permalink
updates aka procrastination
Browse files Browse the repository at this point in the history
  • Loading branch information
DrewML committed Dec 18, 2020
1 parent fed14c3 commit c4ccecb
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 141 deletions.
10 changes: 9 additions & 1 deletion bin/aimserver
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
#!/usr/bin/env node

require('../dist/cli').cli();
const authHost = process.env.AUTH_HOST;
const authPort = process.env.AUTH_PORT;

require('../dist/cli')
.cli({ authHost, authPort })
.catch(err => {
console.error(err);
process.exit(1);
});
47 changes: 29 additions & 18 deletions src/AIMAuthServer.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import net, { Socket, Server } from 'net';
import assert from 'assert';
import { Flap } from './types';
import { buildFlap } from './serverFlaps';
import { matchSnac, authKeyResponseSnac } from './serverSnacs';
import {
parseFlap,
parseSnac,
parseAuthRequest,
parseMD5LoginRequest,
} from './parsers';
import { buildFlap, parseFlap } from './flapUtils';
import { matchSnac, parseSnac } from './snacUtils';
import { authKeyResponseSnac } from './serverSentSnacs';
import { parseAuthRequest, parseMD5LoginRequest } from './clientSnacs';

interface AIMAuthServerOpts {
port?: number;
host?: string;
}

const defaults: AIMAuthServerOpts = {
port: 5190,
};

/**
* @summary The first server an Oscar Protocol client
* connects to when signing on. Confirms client
* credentials, and returns a cookie and address
* to contact the next service (the BOSS server)
*/
export class AIMAuthServer {
private server: Server;
private host: string;
private port: number;

constructor(private opts: AIMAuthServerOpts = defaults) {
constructor(opts: AIMAuthServerOpts = {}) {
this.host = opts.host ?? '0.0.0.0';
this.port = opts.port ?? 5190;
this.server = net.createServer(this.onNewConnection.bind(this));
this.server.on('error', this.onServerError.bind(this));
}
Expand All @@ -48,7 +50,9 @@ export class AIMAuthServer {
socket.write(
buildFlap({
channel: 1,
sequence: 1, // TODO: Sequence should be generated in an abstraction around socket.write
// TODO: Sequence numbers should be generated in an abstraction around socket.write,
// to prevent out-of-order sequences, which is a fatal error for an OSCAR client
sequence: 1,
data: Buffer.from([0x1]), // Flap version 1
}),
);
Expand All @@ -64,6 +68,7 @@ export class AIMAuthServer {
}

private onChannel2(flap: Flap, socket: Socket) {
console.log('channel 2 flap', flap);
const snac = parseSnac(flap.data);

if (matchSnac(snac, 'AUTH', 'MD5_AUTH_REQUEST')) {
Expand Down Expand Up @@ -125,10 +130,16 @@ export class AIMAuthServer {
handler.call(this, flap, socket);
}

listen() {
const { port, host } = this.opts;
this.server.listen(port, host, () => {
console.log(`AIMAuthServer: Listening on ${port}`);
listen(): Promise<{ host: string; port: number }> {
return new Promise((res) => {
this.server.listen(this.port, this.host, () => {
const address = this.server.address();
assert(
address && typeof address !== 'string',
'Unexpected net.AddressInfo in AIMAuthServer.listen',
);
res({ host: address.address, port: address.port });
});
});
}
}
Expand Down
15 changes: 12 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { AIMAuthServer } from './AIMAuthServer';

export function cli() {
const server = new AIMAuthServer();
server.listen();
interface CLIOpts {
authHost?: string;
authPort?: string;
}

export async function cli(opts: CLIOpts) {
const authServer = new AIMAuthServer({
host: opts.authHost,
port: (opts.authPort && Number(opts.authPort)) || undefined,
});
const { host, port } = await authServer.listen();
console.log(`Auth Service listening on ${host}:${port}`);
}
26 changes: 26 additions & 0 deletions src/clientSnacs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { parseTLVs } from './parseTLVs';
import { TLVS } from './constants';

/**
* @see http://iserverd1.khstu.ru/oscar/snac_17_06.html
*/
export function parseAuthRequest(data: Buffer) {
const tlvs = parseTLVs(data);
const screennameTLV = tlvs.first(TLVS.SCREENNAME);

return {
screenname: screennameTLV.value.toString('ascii'),
};
}

/**
* @see http://iserverd1.khstu.ru/oscar/snac_17_02.html
*/
export function parseMD5LoginRequest(data: Buffer) {
const tlvs = parseTLVs(data);
const screenname = tlvs.first(TLVS.SCREENNAME);
const passwordHash = tlvs.first(TLVS.PASSWORD_HASH);
const clientID = tlvs.first(TLVS.CLIENT_ID_STRING);

return { screenname, passwordHash, clientID };
}
8 changes: 0 additions & 8 deletions src/customInspects.ts

This file was deleted.

18 changes: 18 additions & 0 deletions src/serverFlaps.ts → src/flapUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assert } from 'console';
import { Flap } from './types';

interface BuildFlapOpts {
channel: number;
Expand All @@ -17,3 +18,20 @@ export function buildFlap({ channel, sequence, data }: BuildFlapOpts) {

return Buffer.concat([buf, data]);
}

/**
* @see https://en.wikipedia.org/wiki/OSCAR_protocol#FLAP_header
*/
export function parseFlap(rawFlap: Buffer): Flap {
const id = rawFlap.readUInt8(0);
assert(id === 0x2a, 'Unexpected Flap ID');

const flap: Flap = {
channel: rawFlap.readUInt8(1),
sequence: rawFlap.readUInt16BE(2),
byteLength: rawFlap.readUInt16BE(4),
data: rawFlap.subarray(6),
};

return flap;
}
34 changes: 34 additions & 0 deletions src/parseTLVs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import assert from 'assert';
import { TLV } from './types';
import { MultiMap } from './MultiMap';

/**
* @see http://iserverd1.khstu.ru/oscar/basic.html#b0003
*/
export function parseTLVs(data: Buffer) {
const tlvs = new MultiMap<number, TLV>();

for (let cursor = 0; cursor < data.byteLength /* */; ) {
const type = data.readUInt16BE(cursor);

const lengthStart = cursor + 2;
const length = data.readUInt16BE(lengthStart);

const valueStart = lengthStart + 2;
const valueEnd = valueStart + length;
// A TLV's value can be 0 bytes. Odd that they're
// not just excluded from the request, but ¯\_(ツ)_/¯
const value = length
? data.subarray(valueStart, valueEnd)
: // Empty buffer so we don't have to explicitly handle
// a null/undefined anywhere a TLV type propagates
Buffer.alloc(0);

tlvs.set(type, { type, length, value });

cursor = valueEnd;
assert(cursor <= data.byteLength, 'Overflow that should never happen');
}

return tlvs;
}
94 changes: 0 additions & 94 deletions src/parsers.ts

This file was deleted.

22 changes: 22 additions & 0 deletions src/serverSentSnacs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import assert from 'assert';
import { buildSnac } from './snacUtils';

/**
* @see http://iserverd1.khstu.ru/oscar/snac_17_07.html
* @param authKey Key for client to use when encrypting password. Size should not exceed u32 int
* @param reqID SNAC Request ID from client auth request
*/
export function authKeyResponseSnac(authKey: string, reqID: number) {
assert(authKey.length < 0xffff, 'authKey size exceeds u32 int');

const authKeyBuf = Buffer.from(authKey, 'ascii');
const authKeyLen = Buffer.alloc(32);
authKeyLen.writeUInt32BE(authKeyBuf.byteLength, 0);

return buildSnac({
family: 0x17,
subtype: 0x7,
reqID,
data: Buffer.concat([authKeyLen, authKeyBuf]),
});
}
26 changes: 9 additions & 17 deletions src/serverSnacs.ts → src/snacUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import assert from 'assert';
import { Snac } from './types';
import { SNACS } from './constants';

Expand Down Expand Up @@ -44,21 +43,14 @@ export function buildSnac({
}

/**
* @see http://iserverd1.khstu.ru/oscar/snac_17_07.html
* @param authKey Key for client to use when encrypting password. Size should not exceed u32 int
* @param reqID SNAC Request ID from client auth request
* @see http://web.archive.org/web/20080308233204/http://dev.aol.com/aim/oscar/#SNAC
*/
export function authKeyResponseSnac(authKey: string, reqID: number) {
assert(authKey.length < 0xffff, 'authKey size exceeds u32 int');

const authKeyBuf = Buffer.from(authKey, 'ascii');
const authKeyLen = Buffer.alloc(32);
authKeyLen.writeUInt32BE(authKeyBuf.byteLength, 0);

return buildSnac({
family: 0x17,
subtype: 0x7,
reqID,
data: Buffer.concat([authKeyLen, authKeyBuf]),
});
export function parseSnac(rawSnac: Buffer): Snac {
return {
family: rawSnac.readUInt16BE(0),
subtype: rawSnac.readUInt16BE(2),
flags: rawSnac.readUInt16BE(4),
requestID: rawSnac.readUInt32BE(6),
data: rawSnac.subarray(10),
};
}
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export interface Snac {
data: Buffer;
}

/**
* @see http://iserverd1.khstu.ru/oscar/basic.html#b0003
*/
export interface TLV {
type: number;
length: number;
Expand Down

0 comments on commit c4ccecb

Please sign in to comment.