diff --git a/src/communicator.ts b/src/communicator.ts index 2525362..a568232 100644 --- a/src/communicator.ts +++ b/src/communicator.ts @@ -1,5 +1,5 @@ import net from "net"; -import { FLAP, SNAC, TLV } from './structures'; +import { FLAP, SNAC, TLV, TLVType } from './structures'; import { logDataStream } from './util'; import { FLAGS_EMPTY } from './consts'; @@ -29,6 +29,7 @@ export default class Communicator { start() { // Start negotiating a connection + console.log(FLAP, typeof FLAP); const hello = new FLAP(0x01, 0, Buffer.from([0x00, 0x00, 0x00, 0x01])); this.send(hello); } @@ -78,19 +79,18 @@ export default class Communicator { const tlv = TLV.fromBuffer(message.payload.slice(4)); console.log(tlv.toString()); - switch (tlv.type) { - case 0x06: // Requesting available services - // this is just a dword list of service families - const servicesOffered : Buffer[] = []; - Object.values(this.services).forEach((service) => { - servicesOffered.push(Buffer.from([0x00, service.family])); - }); - const resp = new FLAP(2, this._getNewSequenceNumber(), - new SNAC(0x01, 0x03, FLAGS_EMPTY, 0, [ - Buffer.concat(servicesOffered), - ])); - this.send(resp); - return; + if (tlv.type === TLVType.GetServices) { // Requesting available services + // this is just a dword list of service families + const servicesOffered : Buffer[] = []; + Object.values(this.services).forEach((service) => { + servicesOffered.push(Buffer.from([0x00, service.family])); + }); + const resp = new FLAP(2, this._getNewSequenceNumber(), + new SNAC(0x01, 0x03, FLAGS_EMPTY, 0, [ + Buffer.concat(servicesOffered), + ])); + this.send(resp); + return; } return; diff --git a/src/services/authorization-registration.ts b/src/services/authorization-registration.ts index 97558ab..99e7acf 100644 --- a/src/services/authorization-registration.ts +++ b/src/services/authorization-registration.ts @@ -1,7 +1,7 @@ import crypto from 'crypto'; import BaseService from './base'; import Communicator from '../communicator'; -import { FLAP, SNAC, TLV } from '../structures'; +import { FLAP, SNAC, TLV, ErrorCode, TLVType } from '../structures'; const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts'); @@ -26,13 +26,13 @@ export default class AuthorizationRegistrationService extends BaseService { switch (message.payload.service) { case 0x02: // Client login request (md5 login sequence) const tlvs = message.payload.tlvs; - const clientNameTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x03); + const clientNameTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.ClientName); if (!clientNameTLV || !(clientNameTLV instanceof TLV)) { return; } console.log("Attempting connection from", clientNameTLV.payload.toString('ascii')); - const userTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x01); + const userTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.User); if (!userTLV || !(userTLV instanceof TLV)) { return; } @@ -42,15 +42,15 @@ export default class AuthorizationRegistrationService extends BaseService { if (!users[username]) { const authResp = new FLAP(2, this._getNewSequenceNumber(), new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [ - new TLV(0x0001, Buffer.from(username)), // username - new TLV(0x0008, Buffer.from([0x00, 0x04])) // incorrect nick/password + TLV.forUsername(username), // username + TLV.forError(ErrorCode.IncorrectNick) // incorrect nick/password ])); this.send(authResp); return; } - const passwordHashTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x25); + const passwordHashTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.PasswordHash); if (!passwordHashTLV || !(passwordHashTLV instanceof TLV)) { return; } @@ -65,8 +65,8 @@ export default class AuthorizationRegistrationService extends BaseService { console.log('Invalid password for', username); const authResp = new FLAP(2, this._getNewSequenceNumber(), new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [ - new TLV(0x0001, Buffer.from(username)), // username - new TLV(0x0008, Buffer.from([0x00, 0x04])) // incorrect nick/password + TLV.forUsername(username), // username + TLV.forError(ErrorCode.IncorrectNick) // incorrect nick/password ])); this.send(authResp); return; @@ -74,9 +74,9 @@ export default class AuthorizationRegistrationService extends BaseService { const authResp = new FLAP(2, this._getNewSequenceNumber(), new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [ - new TLV(0x01, Buffer.from(username)), // username - new TLV(0x05, Buffer.from('10.0.1.29:5190')), // BOS address - new TLV(0x06, Buffer.from('im a cookie uwu')) // Authorization cookie + TLV.forUsername(username), // username + TLV.forBOSAddress('10.0.1.29:5190'), // BOS address + TLV.forCookie('im a cookie uwu') // Authorization cookie ])); this.send(authResp); diff --git a/src/structures.ts b/src/structures.ts deleted file mode 100644 index 41898ef..0000000 --- a/src/structures.ts +++ /dev/null @@ -1,148 +0,0 @@ -import assert from 'assert'; - -import { logDataStream } from './util'; - -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); - - return new TLV(type, payload); - } - - public len : number; - - constructor(public type : number, public payload : Buffer) { - this.type = type; - this.len = payload.length; - this.payload = payload; - } - - toString() { - return `TLV(0x${this.type.toString(16).padStart(2, '0')}, ${this.len}, ${this.payload.toString('ascii')})`; - } - - toBuffer() { - const TLVHeader = Buffer.alloc(4, 0, 'hex'); - TLVHeader.writeUInt16BE(this.type); - TLVHeader.writeUInt16BE(this.len, 2); - return Buffer.concat([TLVHeader, this.payload]); - } -} - -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 : TLV[] = []; // SNACs can have multiple TLVs - - let tlvsIdx = 10; - let cb = 0, cbLimit = 20; //circuit breaker - while (tlvsIdx < payloadLength && cb < cbLimit) { - const tlv = TLV.fromBuffer(buf.slice(tlvsIdx)); - tlvs.push(tlv); - tlvsIdx += tlv.len + 4; // 4 bytes for TLV type + tlvs length - cb++; - } - if (cb === cbLimit) { - console.error('Application error, cb limit reached'); - process.exit(1); - } - - return new SNAC(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; - this.requestID = requestID; - this.tlvs = tlvs; - } - - toString() { - return `SNAC(${this.family.toString(16)},${this.service.toString(16)}) #${this.requestID}\n ${this.tlvs}`; - } - - toBuffer() { - const SNACHeader = Buffer.alloc(10, 0, 'hex'); - SNACHeader.writeUInt16BE(this.family); - SNACHeader.writeUInt16BE(this.service, 2); - SNACHeader.set(this.flags, 4); - SNACHeader.writeUInt32BE(this.requestID, 6); - - const payload = this.tlvs.map((thing) => { - if (thing instanceof TLV) { - return thing.toBuffer(); - } - return thing; - }); - - return Buffer.concat([SNACHeader, ...payload]); - } -} - -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 : Buffer | SNAC = buf.slice(6, 6 + payloadLength); - - if (channel === 2) { - payload = SNAC.fromBuffer(payload, payloadLength); - } - - return new FLAP(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; - - if (payload instanceof SNAC) { - this.payloadLength = payload.toBuffer().length; - } else { - this.payloadLength = payload.length; - } - } - - toString() { - 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}` - } - - toBuffer() { - const FLAPHeader = Buffer.alloc(6, 0, 'hex'); - FLAPHeader.writeInt8(0x2a, 0); - FLAPHeader.writeInt8(this.channel, 1); - FLAPHeader.writeInt16BE(this.sequenceNumber, 2); - FLAPHeader.writeInt16BE(this.payloadLength, 4); - - let payload = this.payload; - if (payload instanceof SNAC) { - payload = payload.toBuffer(); - } - - return Buffer.concat([FLAPHeader, payload]); - } -} - -module.exports = { - TLV, - SNAC, - FLAP, -}; diff --git a/src/structures/ErrorCode.ts b/src/structures/ErrorCode.ts new file mode 100644 index 0000000..b82a195 --- /dev/null +++ b/src/structures/ErrorCode.ts @@ -0,0 +1,3 @@ +export const enum ErrorCode { + IncorrectNick = 0x04, +} diff --git a/src/structures/FLAP.ts b/src/structures/FLAP.ts new file mode 100644 index 0000000..215cce2 --- /dev/null +++ b/src/structures/FLAP.ts @@ -0,0 +1,59 @@ +import assert from "assert" + +import { SNAC } from "./SNAC"; +import { logDataStream } from '../util'; + +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 : Buffer | SNAC = buf.slice(6, 6 + payloadLength); + + if (channel === 2) { + payload = SNAC.fromBuffer(payload, payloadLength); + } + + return new FLAP(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; + + if (payload instanceof SNAC) { + this.payloadLength = payload.toBuffer().length; + } else { + this.payloadLength = payload.length; + } + } + + toString() { + 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}` + } + + toBuffer() { + const FLAPHeader = Buffer.alloc(6, 0, 'hex'); + FLAPHeader.writeInt8(0x2a, 0); + FLAPHeader.writeInt8(this.channel, 1); + FLAPHeader.writeInt16BE(this.sequenceNumber, 2); + FLAPHeader.writeInt16BE(this.payloadLength, 4); + + let payload = this.payload; + if (payload instanceof SNAC) { + payload = payload.toBuffer(); + } + + return Buffer.concat([FLAPHeader, payload]); + } +} diff --git a/src/structures/SNAC.ts b/src/structures/SNAC.ts new file mode 100644 index 0000000..4224b13 --- /dev/null +++ b/src/structures/SNAC.ts @@ -0,0 +1,57 @@ +import assert from "assert"; +import { TLV } from "./TLV"; + +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 : TLV[] = []; // SNACs can have multiple TLVs + + let tlvsIdx = 10; + let cb = 0, cbLimit = 20; //circuit breaker + while (tlvsIdx < payloadLength && cb < cbLimit) { + const tlv = TLV.fromBuffer(buf.slice(tlvsIdx)); + tlvs.push(tlv); + tlvsIdx += tlv.length + 4; // 4 bytes for TLV type + tlvs length + cb++; + } + if (cb === cbLimit) { + console.error('Application error, cb limit reached'); + process.exit(1); + } + + return new SNAC(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; + this.requestID = requestID; + this.tlvs = tlvs; + } + + toString() { + return `SNAC(${this.family.toString(16)},${this.service.toString(16)}) #${this.requestID}\n ${this.tlvs}`; + } + + toBuffer() { + const SNACHeader = Buffer.alloc(10, 0, 'hex'); + SNACHeader.writeUInt16BE(this.family); + SNACHeader.writeUInt16BE(this.service, 2); + SNACHeader.set(this.flags, 4); + SNACHeader.writeUInt32BE(this.requestID, 6); + + const payload = this.tlvs.map((thing) => { + if (thing instanceof TLV) { + return thing.toBuffer(); + } + return thing; + }); + + return Buffer.concat([SNACHeader, ...payload]); + } +} diff --git a/src/structures/TLV.ts b/src/structures/TLV.ts new file mode 100644 index 0000000..8c544c2 --- /dev/null +++ b/src/structures/TLV.ts @@ -0,0 +1,54 @@ +import {ErrorCode} from "./ErrorCode"; + +export const enum TLVType { + User = 0x01, + ClientName = 0x03, + GetServices = 0x06, + PasswordHash = 0x25, +} + +export class TLV { + get length() : number { + return this.payload.length; + } + + static fromBuffer(buf : Buffer) { + const type = buf.slice(0, 2).readInt16BE(0) as TLVType; + const len = buf.slice(2, 4).readInt16BE(0) + const payload = buf.slice(4, 4 + len); + + return new TLV(type, payload); + } + + static forUsername(username : string) : TLV { + return new TLV(0x01, Buffer.from(username)); + } + + static forBOSAddress(address : string ) : TLV { + return new TLV(0x05, Buffer.from(address)); + } + + static forCookie(cookie : string) : TLV { + return new TLV(0x06, Buffer.from(cookie)); + } + + static forError(errorCode : ErrorCode) : TLV { + return new TLV(0x08, Buffer.from([0x00, errorCode])); + } + + constructor(public type : TLVType, public payload : Buffer) { + this.type = type; + this.payload = payload; + } + + toString() { + return `TLV(0x${this.type.toString(16).padStart(2, '0')}, ${this.length}, ${this.payload.toString('ascii')})`; + } + + toBuffer() { + const TLVHeader = Buffer.alloc(4, 0, 'hex'); + TLVHeader.writeUInt16BE(this.type); + TLVHeader.writeUInt16BE(this.length, 2); + return Buffer.concat([TLVHeader, this.payload]); + } +} diff --git a/src/structures/index.ts b/src/structures/index.ts new file mode 100644 index 0000000..5724018 --- /dev/null +++ b/src/structures/index.ts @@ -0,0 +1,4 @@ +export * from "./ErrorCode"; +export * from "./TLV"; +export * from "./SNAC"; +export * from "./FLAP"; diff --git a/tsconfig.json b/tsconfig.json index 28ee738..6bacef8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@tsconfig/node14/tsconfig.json", "compilerOptions": { "preserveConstEnums": true, + "esModuleInterop": true, "outDir": "./dist", }, "include": ["src/**/*"],