diff --git a/.gitignore b/.gitignore index c860569..8546a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules built +dist diff --git a/package.json b/package.json index fea1d9a..6a60266 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,12 @@ "main": "src/index.js", "license": "MIT", "scripts": { - "dev": "nodemon ./src/index.js", - "start": "node ./src/index.js" + "dev": "nodemon --ignore ./dist/ --watch ./src -e ts --exec 'tsc && node ./dist/index.js'", + "start": "tsc && node ./dist/index.js" }, "devDependencies": { "@tsconfig/node14": "^1.0.1", + "@types/node": "^16.7.13", "nodemon": "^2.0.12", "typescript": "^4.4.2" } diff --git a/src/communicator.ts b/src/communicator.ts index 9d9504c..2525362 100644 --- a/src/communicator.ts +++ b/src/communicator.ts @@ -1,26 +1,29 @@ -const { FLAP, SNAC, TLV } = require('./structures'); -const { logDataStream } = require('./util'); -const { FLAGS_EMPTY } = require('./consts'); +import net from "net"; +import { FLAP, SNAC, TLV } from './structures'; +import { logDataStream } from './util'; +import { FLAGS_EMPTY } from './consts'; -const AuthorizationRegistrationService = require("./services/authorization-registration"); +import AuthorizationRegistrationService from "./services/authorization-registration"; +import BaseService from "./services/base"; -class Communicator { - constructor(socket) { +export default class Communicator { + + private _sequenceNumber = 0; + private services : {[key: number]: BaseService} = {}; + + constructor(public socket : net.Socket) { // Hold on to the socket this.socket = socket; - this.socket.on('data', (data) => { + this.socket.on('data', (data : Buffer) => { console.log('DATA-----------------------'); - const flap = FLAP.fromBuffer(Buffer.from(data, 'hex')); + const flap = FLAP.fromBuffer(data); console.log('RECV', flap.toString()); - console.log('RAW\n' + logDataStream(Buffer.from(data, 'hex'))); + console.log('RAW\n' + logDataStream(data)); this.handleMessage(flap); }); - this._sequenceNumber = 0; - this.registerServices(); - this.start(); } @@ -45,16 +48,21 @@ class Communicator { return ++this._sequenceNumber; } - send(message) { + send(message : FLAP) { console.log('SEND', message.toString()); console.log('RAW\n' + logDataStream(message.toBuffer())); console.log('-----------------------DATA'); this.socket.write(message.toBuffer()); } - handleMessage(message) { + handleMessage(message : FLAP) { switch (message.channel) { case 1: + // No SNACs on channel 1 + if (!(message.payload instanceof Buffer)) { + return; + } + const protocol = message.payload.readUInt32BE(); if (protocol !== 1) { @@ -73,7 +81,7 @@ class Communicator { switch (tlv.type) { case 0x06: // Requesting available services // this is just a dword list of service families - const servicesOffered = []; + const servicesOffered : Buffer[] = []; Object.values(this.services).forEach((service) => { servicesOffered.push(Buffer.from([0x00, service.family])); }); @@ -87,8 +95,8 @@ class Communicator { return; case 2: - if (!message.payload) { - console.error('No SNAC'); + if (!(message.payload instanceof SNAC)) { + console.error('Expected SNAC payload'); return; } @@ -105,5 +113,3 @@ class Communicator { } } } - -module.exports = Communicator; diff --git a/src/consts.ts b/src/consts.ts index 10797b4..ec8230c 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -1,7 +1,2 @@ -const AIM_MD5_STRING = "AOL Instant Messenger (SM)"; -const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]); - -module.exports = { - AIM_MD5_STRING, - FLAGS_EMPTY, -}; +export const AIM_MD5_STRING = "AOL Instant Messenger (SM)"; +export const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]); diff --git a/src/index.ts b/src/index.ts index e8829fb..f26048f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,5 @@ -const net = require('net'); -const Communicator = require('./communicator'); - -const communicators = {}; +import net from 'net'; +import Communicator from './communicator'; const server = net.createServer((socket) => { console.log('client connected...'); @@ -9,21 +7,19 @@ const server = net.createServer((socket) => { socket.on('error', (e) => { console.error('socket encountered an error:', e); - delete communicators[socket]; + socket.end(); }); socket.on('timeout', () => { console.log('socket timeout'); socket.end(); - delete communicators[socket]; }); socket.on('end', () => { console.log('client disconnected...'); }); - const c = new Communicator(socket); - communicators[socket] = c; + new Communicator(socket); }); server.on('error', (err) => { diff --git a/src/services/authorization-registration.ts b/src/services/authorization-registration.ts index ec98465..97558ab 100644 --- a/src/services/authorization-registration.ts +++ b/src/services/authorization-registration.ts @@ -1,27 +1,42 @@ -const crypto = require('crypto'); -const BaseService = require('./base'); -const { FLAP, SNAC, TLV } = require('../structures'); +import crypto from 'crypto'; +import BaseService from './base'; +import Communicator from '../communicator'; +import { FLAP, SNAC, TLV } from '../structures'; const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts'); -const users = { - toof: 'foo', +const users : {[key: string]: string} = { + 'toof': 'foo', }; -class AuthorizationRegistrationService extends BaseService { - constructor(communicator) { +export default class AuthorizationRegistrationService extends BaseService { + private cipher : string; + + constructor(communicator : Communicator) { super({ family: 0x17, version: 0x01 }, communicator); this.cipher = "HARDY"; } - handleMessage(message) { + override handleMessage(message : FLAP) { + if (message.payload instanceof Buffer) { + console.log('Wont handle Buffer payload'); + return; + } + switch (message.payload.service) { case 0x02: // Client login request (md5 login sequence) const tlvs = message.payload.tlvs; - const clientNameTLV = tlvs.find((tlv) => tlv.type === 0x03); + const clientNameTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x03); + if (!clientNameTLV || !(clientNameTLV instanceof TLV)) { + return; + } console.log("Attempting connection from", clientNameTLV.payload.toString('ascii')); - const userTLV = tlvs.find((tlv) => tlv.type === 0x01); + const userTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x01); + if (!userTLV || !(userTLV instanceof TLV)) { + return; + } + const username = userTLV.payload.toString('ascii'); if (!users[username]) { @@ -35,7 +50,10 @@ class AuthorizationRegistrationService extends BaseService { return; } - const passwordHashTLV = tlvs.find((tlv) => tlv.type === 0x25); + const passwordHashTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x25); + if (!passwordHashTLV || !(passwordHashTLV instanceof TLV)) { + return; + } const pwHash = crypto.createHash('md5'); pwHash.update(this.cipher); @@ -43,7 +61,7 @@ class AuthorizationRegistrationService extends BaseService { pwHash.update(AIM_MD5_STRING); const digest = pwHash.digest('hex'); - if (digest !== passwordHashTLV.payload.toString('hex')) { + if (digest !== (passwordHashTLV as TLV).payload.toString('hex')) { console.log('Invalid password for', username); const authResp = new FLAP(2, this._getNewSequenceNumber(), new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [ diff --git a/src/services/base.ts b/src/services/base.ts index 4951acd..51c4505 100644 --- a/src/services/base.ts +++ b/src/services/base.ts @@ -1,11 +1,22 @@ -class BaseService { - constructor({family, version}, communicator) { +import Communicator from "../communicator"; +import { FLAP } from "../structures"; + +interface ServiceFamilyVersion { + family : number, + version : number, +} + +export default class BaseService { + public family : number; + public version : number; + + constructor({family, version} : ServiceFamilyVersion, public communicator : Communicator) { this.family = family; this.version = version; this.communicator = communicator; } - send(message) { + send(message : FLAP) { this.communicator.send(message); } @@ -13,9 +24,7 @@ class BaseService { return this.communicator._getNewSequenceNumber(); } - handleMessage(message) { - return null; + handleMessage(message : FLAP) : void { + return; } } - -module.exports = BaseService; diff --git a/src/structures.ts b/src/structures.ts index db8eca0..41898ef 100644 --- a/src/structures.ts +++ b/src/structures.ts @@ -1,9 +1,9 @@ -const assert = require('assert'); +import assert from 'assert'; -const { logDataStream } = require('./util'); +import { logDataStream } from './util'; -class TLV { - static fromBuffer(buf) { +export class TLV { + static fromBuffer(buf : Buffer) { const type = buf.slice(0, 2).readInt16BE(0); const len = buf.slice(2, 4).readInt16BE(0) const payload = buf.slice(4, 4 + len); @@ -11,7 +11,9 @@ class TLV { return new TLV(type, payload); } - constructor(type, payload) { + public len : number; + + constructor(public type : number, public payload : Buffer) { this.type = type; this.len = payload.length; this.payload = payload; @@ -29,14 +31,14 @@ class TLV { } } -class SNAC { - static fromBuffer(buf, payloadLength = 0) { +export class SNAC { + static fromBuffer(buf : Buffer, payloadLength = 0) { assert(buf.length >= 10, 'Expected 10 bytes for SNAC header'); const family = buf.slice(0,2).readInt16BE(0); const service = buf.slice(2,4).readInt16BE(0); const flags = buf.slice(4, 6); const requestID = buf.slice(6, 10).readInt32BE(0); - const tlvs = []; // SNACs can have multiple TLVs + const tlvs : TLV[] = []; // SNACs can have multiple TLVs let tlvsIdx = 10; let cb = 0, cbLimit = 20; //circuit breaker @@ -54,7 +56,7 @@ class SNAC { return new SNAC(family, service, flags, requestID, tlvs); } - constructor(family, service, flags, requestID, tlvs = []) { + constructor(public family : number, public service : number, public flags : Buffer, public requestID : number , public tlvs : Array = []) { this.family = family; this.service = service; this.flags = flags; @@ -70,7 +72,7 @@ class SNAC { const SNACHeader = Buffer.alloc(10, 0, 'hex'); SNACHeader.writeUInt16BE(this.family); SNACHeader.writeUInt16BE(this.service, 2); - SNACHeader.writeUInt16BE(this.flags, 4); + SNACHeader.set(this.flags, 4); SNACHeader.writeUInt32BE(this.requestID, 6); const payload = this.tlvs.map((thing) => { @@ -84,14 +86,14 @@ class SNAC { } } -class FLAP { - static fromBuffer(buf) { +export class FLAP { + static fromBuffer(buf : Buffer) { assert.equal(buf[0], 0x2a, 'Expected 0x2a at start of FLAP header'); assert(buf.length >= 6, 'Expected at least 6 bytes for FLAP header'); const channel = buf.readInt8(1); const sequenceNumber = buf.slice(2,4).readInt16BE(0); const payloadLength = buf.slice(4, 6).readInt16BE(0); - let payload = buf.slice(6, 6 + payloadLength); + let payload : Buffer | SNAC = buf.slice(6, 6 + payloadLength); if (channel === 2) { payload = SNAC.fromBuffer(payload, payloadLength); @@ -100,21 +102,26 @@ class FLAP { return new FLAP(channel, sequenceNumber, payload) } - constructor(channel, sequenceNumber, payload) { + payloadLength: number; + + constructor(public channel: number, public sequenceNumber: number, public payload: Buffer | SNAC) { this.channel = channel; this.sequenceNumber = sequenceNumber; this.payload = payload; - this.payloadLength = this.payload.length; if (payload instanceof SNAC) { this.payloadLength = payload.toBuffer().length; + } else { + this.payloadLength = payload.length; } } toString() { - const hasSnac = this.payload instanceof SNAC; - const payload = hasSnac ? this.payload.toString() : logDataStream(this.payload).split('\n').join('\n '); + let payload = this.payload.toString(); + if (this.payload instanceof Buffer) { + payload = logDataStream(this.payload).split('\n').join('\n '); + } return `ch:${this.channel}, dn: ${this.sequenceNumber}, len: ${this.payloadLength}, payload:\n ${payload}` } diff --git a/src/util.ts b/src/util.ts index 477d0c7..b9396ad 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -function chunkString(str, len) { +function chunkString(str : string, len : number) { const size = Math.ceil(str.length/len) const r = Array(size) let offset = 0 @@ -11,11 +11,7 @@ function chunkString(str, len) { return r } -function logDataStream(data){ +export function logDataStream(data : Buffer){ const strs = chunkString(data.toString('hex'), 16); return strs.map((str) => chunkString(str, 2).join(' ')).join('\n'); } - -module.exports = { - logDataStream, -}; diff --git a/tests/data-structures.js b/tests/data-structures.js index 25b5f06..afd3547 100644 --- a/tests/data-structures.js +++ b/tests/data-structures.js @@ -1,4 +1,4 @@ -const assert = require('assert'); +import assert from 'assert'; const { FLAP, SNAC, TLV } = require('../src/structures'); diff --git a/tsconfig.json b/tsconfig.json index 3d56f92..28ee738 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "extends": "@tsconfig/node14/tsconfig.json", "compilerOptions": { "preserveConstEnums": true, - "outDir": "./built", + "outDir": "./dist", }, "include": ["src/**/*"], "exclude": ["node_modules", "**/*.spec.ts"] diff --git a/yarn.lock b/yarn.lock index bcaee44..1c3c4e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== +"@types/node@^16.7.13": + version "16.7.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.13.tgz#86fae356b03b5a12f2506c6cf6cd9287b205973f" + integrity sha512-pLUPDn+YG3FYEt/pHI74HmnJOWzeR+tOIQzUx93pi9M7D8OE7PSLr97HboXwk5F+JS+TLtWuzCOW97AHjmOXXA== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"