264 lines
9.0 KiB
JavaScript
264 lines
9.0 KiB
JavaScript
/* jslint node: true */
|
|
'use strict';
|
|
|
|
const { MenuModule } = require('./menu_module.js');
|
|
const { resetScreen } = require('./ansi_term.js');
|
|
const { Errors } = require('./enig_error.js');
|
|
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
|
|
|
|
// deps
|
|
const async = require('async');
|
|
const http = require('http');
|
|
const net = require('net');
|
|
const crypto = require('crypto');
|
|
|
|
const packageJson = require('../package.json');
|
|
|
|
/*
|
|
Expected configuration block:
|
|
|
|
{
|
|
module: bbs_link
|
|
...
|
|
config: {
|
|
sysCode: XXXXX
|
|
authCode: XXXXX
|
|
schemeCode: XXXX
|
|
door: lord
|
|
|
|
// default hoss: games.bbslink.net
|
|
host: games.bbslink.net
|
|
|
|
// defualt port: 23
|
|
port: 23
|
|
}
|
|
}
|
|
*/
|
|
|
|
// :TODO: BUG: When a client disconnects, it's not handled very well -- the log is spammed with tons of errors
|
|
// :TODO: ENH: Support nodeMax and tooManyArt
|
|
|
|
exports.moduleInfo = {
|
|
name: 'BBSLink',
|
|
desc: 'BBSLink Access Module',
|
|
author: 'NuSkooler',
|
|
};
|
|
|
|
exports.getModule = class BBSLinkModule extends MenuModule {
|
|
constructor(options) {
|
|
super(options);
|
|
|
|
this.config = options.menuConfig.config;
|
|
this.config.host = this.config.host || 'games.bbslink.net';
|
|
this.config.port = this.config.port || 23;
|
|
}
|
|
|
|
initSequence() {
|
|
let token;
|
|
let randomKey;
|
|
let clientTerminated;
|
|
const self = this;
|
|
|
|
async.series(
|
|
[
|
|
function validateConfig(callback) {
|
|
return self.validateConfigFields(
|
|
{
|
|
host: 'string',
|
|
sysCode: 'string',
|
|
authCode: 'string',
|
|
schemeCode: 'string',
|
|
door: 'string',
|
|
port: 'number',
|
|
},
|
|
callback
|
|
);
|
|
},
|
|
function acquireToken(callback) {
|
|
//
|
|
// Acquire an authentication token
|
|
//
|
|
crypto.randomBytes(16, function rand(ex, buf) {
|
|
if (ex) {
|
|
callback(ex);
|
|
} else {
|
|
randomKey = buf.toString('base64').substr(0, 6);
|
|
self.simpleHttpRequest(
|
|
'/token.php?key=' + randomKey,
|
|
null,
|
|
function resp(err, body) {
|
|
if (err) {
|
|
callback(err);
|
|
} else {
|
|
token = body.trim();
|
|
self.client.log.trace(
|
|
{ token: token },
|
|
'BBSLink token'
|
|
);
|
|
callback(null);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
});
|
|
},
|
|
function authenticateToken(callback) {
|
|
//
|
|
// Authenticate the token we acquired previously
|
|
//
|
|
const headers = {
|
|
'X-User': self.client.user.userId.toString(),
|
|
'X-System': self.config.sysCode,
|
|
'X-Auth': crypto
|
|
.createHash('md5')
|
|
.update(self.config.authCode + token)
|
|
.digest('hex'),
|
|
'X-Code': crypto
|
|
.createHash('md5')
|
|
.update(self.config.schemeCode + token)
|
|
.digest('hex'),
|
|
'X-Rows': self.client.term.termHeight.toString(),
|
|
'X-Key': randomKey,
|
|
'X-Door': self.config.door,
|
|
'X-Token': token,
|
|
'X-Type': 'enigma-bbs',
|
|
'X-Version': packageJson.version,
|
|
};
|
|
|
|
self.simpleHttpRequest(
|
|
'/auth.php?key=' + randomKey,
|
|
headers,
|
|
function resp(err, body) {
|
|
const status = body.trim();
|
|
|
|
if ('complete' === status) {
|
|
return callback(null);
|
|
}
|
|
return callback(
|
|
Errors.AccessDenied(
|
|
`Bad authentication status: ${status}`
|
|
)
|
|
);
|
|
}
|
|
);
|
|
},
|
|
function createTelnetBridge(callback) {
|
|
//
|
|
// Authentication with BBSLink successful. Now, we need to create a telnet
|
|
// bridge from us to them
|
|
//
|
|
const connectOpts = {
|
|
port: self.config.port,
|
|
host: self.config.host,
|
|
};
|
|
|
|
let dataOut;
|
|
|
|
self.client.term.write(resetScreen());
|
|
self.client.term.write(
|
|
` Connecting to ${self.config.host}, please wait...\n`
|
|
);
|
|
|
|
const doorTracking = trackDoorRunBegin(
|
|
self.client,
|
|
`bbslink_${self.config.door}`
|
|
);
|
|
|
|
const bridgeConnection = net.createConnection(
|
|
connectOpts,
|
|
function connected() {
|
|
self.client.log.info(
|
|
connectOpts,
|
|
'BBSLink bridge connection established'
|
|
);
|
|
|
|
dataOut = data => {
|
|
return bridgeConnection.write(data);
|
|
};
|
|
|
|
self.client.term.output.on('data', dataOut);
|
|
|
|
self.client.once('end', function clientEnd() {
|
|
self.client.log.info(
|
|
'Connection ended. Terminating BBSLink connection'
|
|
);
|
|
clientTerminated = true;
|
|
bridgeConnection.end();
|
|
});
|
|
}
|
|
);
|
|
|
|
const restore = () => {
|
|
if (dataOut && self.client.term.output) {
|
|
self.client.term.output.removeListener('data', dataOut);
|
|
dataOut = null;
|
|
}
|
|
|
|
trackDoorRunEnd(doorTracking);
|
|
};
|
|
|
|
bridgeConnection.on('data', function incomingData(data) {
|
|
// pass along
|
|
// :TODO: just pipe this as well
|
|
self.client.term.rawWrite(data);
|
|
});
|
|
|
|
bridgeConnection.on('end', function connectionEnd() {
|
|
restore();
|
|
return callback(
|
|
clientTerminated
|
|
? Errors.General('Client connection terminated')
|
|
: null
|
|
);
|
|
});
|
|
|
|
bridgeConnection.on('error', function error(err) {
|
|
self.client.log.info(
|
|
'BBSLink bridge connection error: ' + err.message
|
|
);
|
|
restore();
|
|
return callback(err);
|
|
});
|
|
},
|
|
],
|
|
function complete(err) {
|
|
if (err) {
|
|
self.client.log.warn(
|
|
{ error: err.toString() },
|
|
'BBSLink connection error'
|
|
);
|
|
}
|
|
|
|
if (!clientTerminated) {
|
|
self.prevMenu();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
simpleHttpRequest(path, headers, cb) {
|
|
const getOpts = {
|
|
host: this.config.host,
|
|
path: path,
|
|
headers: headers,
|
|
};
|
|
|
|
const req = http.get(getOpts, function response(resp) {
|
|
let data = '';
|
|
|
|
resp.on('data', function chunk(c) {
|
|
data += c;
|
|
});
|
|
|
|
resp.on('end', function respEnd() {
|
|
cb(null, data);
|
|
req.end();
|
|
});
|
|
});
|
|
|
|
req.on('error', function reqErr(err) {
|
|
cb(err);
|
|
});
|
|
}
|
|
};
|