/* jslint node: true */ 'use strict'; const MenuModule = require('./menu_module.js').MenuModule; const resetScreen = require('./ansi_term.js').resetScreen; const async = require('async'); const _ = require('lodash'); 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) { if(_.isString(self.config.sysCode) && _.isString(self.config.authCode) && _.isString(self.config.schemeCode) && _.isString(self.config.door)) { callback(null); } else { callback(new Error('Configuration is missing option(s)')); } }, 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 // var 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) { var status = body.trim(); if('complete' === status) { callback(null); } else { callback(new Error('Bad authentication status: ' + status)); } }); }, function createTelnetBridge(callback) { // // Authentication with BBSLink successful. Now, we need to create a telnet // bridge from us to them // var connectOpts = { port : self.config.port, host : self.config.host, }; var clientTerminated; self.client.term.write(resetScreen()); self.client.term.write(' Connecting to BBSLink.net, please wait...\n'); var bridgeConnection = net.createConnection(connectOpts, function connected() { self.client.log.info(connectOpts, 'BBSLink bridge connection established'); self.client.term.output.pipe(bridgeConnection); self.client.once('end', function clientEnd() { self.client.log.info('Connection ended. Terminating BBSLink connection'); clientTerminated = true; bridgeConnection.end(); }); }); var restorePipe = function() { self.client.term.output.unpipe(bridgeConnection); self.client.term.output.resume(); }; bridgeConnection.on('data', function incomingData(data) { // pass along // :TODO: just pipe this as well self.client.term.rawWrite(data); }); bridgeConnection.on('end', function connectionEnd() { restorePipe(); callback(clientTerminated ? new Error('Client connection terminated') : null); }); bridgeConnection.on('error', function error(err) { self.client.log.info('BBSLink bridge connection error: ' + err.message); restorePipe(); 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); }); } };