diff --git a/core/bbs.js b/core/bbs.js index ea351480..39b69767 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -11,6 +11,7 @@ const logger = require('./logger.js'); const database = require('./database.js'); const clientConns = require('./client_connections.js'); const resolvePath = require('./misc_util.js').resolvePath; +const events = require('./events.js'); // deps const async = require('async'); @@ -27,7 +28,7 @@ exports.main = main; const initServices = {}; const ENIGMA_COPYRIGHT = 'ENiGMA½ Copyright (c) 2014-2017 Bryan Ashby'; -const HELP = +const HELP = `${ENIGMA_COPYRIGHT} usage: main.js @@ -60,7 +61,7 @@ function main() { conf.init(resolvePath(configPath), function configInit(err) { // - // If the user supplied a path and we can't read/parse it + // If the user supplied a path and we can't read/parse it // then it's a fatal error // if(err) { @@ -84,15 +85,17 @@ function main() { } return callback(err); }); - }, + } ], function complete(err) { + events.registerModules(); + // note this is escaped: fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => { console.info(ENIGMA_COPYRIGHT); - if(!err) { + if(!err) { console.info(banner); - } + } console.info('System started!'); }); @@ -140,7 +143,7 @@ function shutdownSystem() { }, function stopMsgNetwork(callback) { require('./msg_network.js').shutdown(callback); - } + } ], () => { console.info('Goodbye!'); @@ -173,9 +176,9 @@ function initialize(cb) { process.on('SIGINT', shutdownSystem); require('later').date.localTime(); // use local times for later.js/scheduling - + return callback(null); - }, + }, function initDatabases(callback) { return database.initializeDatabases(callback); }, @@ -195,7 +198,7 @@ function initialize(cb) { // * Makes this accessible for MCI codes, easy non-blocking access, etc. // * We do this every time as the op is free to change this information just // like any other user - // + // const User = require('./user.js'); async.waterfall( @@ -223,7 +226,7 @@ function initialize(cb) { opProps.username = opUserName; _.each(opProps, (v, k) => { - StatLog.setNonPeristentSystemStat(`sysop_${k}`, v); + StatLog.setNonPeristentSystemStat(`sysop_${k}`, v); }); } @@ -235,7 +238,7 @@ function initialize(cb) { return require('./predefined_mci.js').init(callback); }, function readyMessageNetworkSupport(callback) { - return require('./msg_network.js').startup(callback); + return require('./msg_network.js').startup(callback); }, function listenConnections(callback) { return require('./listening_server.js').startup(callback); diff --git a/core/connect.js b/core/connect.js index ffd28a0c..21ab421a 100644 --- a/core/connect.js +++ b/core/connect.js @@ -3,6 +3,7 @@ // ENiGMA½ const ansi = require('./ansi_term.js'); +const events = require('./events.js'); // deps const async = require('async'); @@ -82,7 +83,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) { // if(h < 10 || w < 10) { client.log.warn( - { height : h, width : w }, + { height : h, width : w }, 'Ignoring ANSI CPR screen size query response due to very small values'); return done(new Error('Term size <= 10 considered invalid')); } @@ -91,11 +92,11 @@ function ansiQueryTermSizeIfNeeded(client, cb) { client.term.termWidth = w; client.log.debug( - { - termWidth : client.term.termWidth, - termHeight : client.term.termHeight, - source : 'ANSI CPR' - }, + { + termWidth : client.term.termWidth, + termHeight : client.term.termHeight, + source : 'ANSI CPR' + }, 'Window size updated' ); @@ -109,7 +110,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) { return done(new Error('No term size established by CPR within timeout')); }, 2000); - // Start the process: Query for CPR + // Start the process: Query for CPR client.term.rawWrite(ansi.queryScreenSize()); } @@ -123,7 +124,7 @@ function displayBanner(term) { // note: intentional formatting: term.pipeWrite(` |06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN -|06Copyright (c) 2014-2017 Bryan Ashby |14- |12http://l33t.codes/ +|06Copyright (c) 2014-2017 Bryan Ashby |14- |12http://l33t.codes/ |06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/ |00` ); @@ -153,11 +154,11 @@ function connectEntry(client, nextMenu) { if(0 === term.termHeight || 0 === term.termWidth) { // // We still don't have something good for term height/width. - // Default to DOS size 80x25. + // Default to DOS size 80x25. // - // :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing??? + // :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing??? client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!'); - + term.termHeight = 25; term.termWidth = 80; } @@ -165,8 +166,8 @@ function connectEntry(client, nextMenu) { return callback(null); }); - }, - ], + }, + ], () => { prepareTerminal(term); @@ -175,6 +176,9 @@ function connectEntry(client, nextMenu) { // displayBanner(term); + // fire event + events.emit('codes.l33t.enigma.system.connect', {'client': client}); + setTimeout( () => { return client.menuStack.goto(nextMenu); }, 500); diff --git a/core/events.js b/core/events.js new file mode 100644 index 00000000..f558ed21 --- /dev/null +++ b/core/events.js @@ -0,0 +1,47 @@ +/* jslint node: true */ +'use strict'; + +const Config = require('./config.js'); +const fs = require('fs'); +const path = require('path'); +const events = require('events'); +const logger = require('./logger.js'); + +var eventEmitter = new events.EventEmitter(); + +var self = module.exports = { + emit: function(eventName, args) { + logger.log.debug("Emit "+eventName); + eventEmitter.emit(eventName, args); + }, + on: function(eventName, listener) { + logger.log.debug("Register listener for "+eventName); + eventEmitter.on(eventName, listener); + }, + remove: function(eventName, listener) { + logger.log.debug("Remove listener for "+eventName); + eventEmitter.removeListener(eventName, listener); + }, + registerModules: function() { + var mods = fs.readdirSync(Config.config.paths.mods); + + mods.forEach(function(item) { + var modPath = Config.config.paths.mods+item; + if (item.substr(item.length-3) != '.js') { + modPath += path.sep+item+'.js'; + } + if (fs.existsSync(modPath)) { + var module = require(modPath); + + if (module.registerEvents !== undefined) { + logger.log.debug(modPath+" calling registerEvents function"); + module.registerEvents(); + } else { + logger.log.debug(modPath+" has no registerEvents function"); + } + } else { + logger.log.debug(modPath+" - file not found"); + } + }); + } +} diff --git a/core/plugin_module.js b/core/plugin_module.js index aeac4950..31ba6f01 100644 --- a/core/plugin_module.js +++ b/core/plugin_module.js @@ -4,4 +4,4 @@ exports.PluginModule = PluginModule; function PluginModule(options) { -} \ No newline at end of file +} diff --git a/core/servers/login/websocket.js b/core/servers/login/websocket.js index 41a3e838..6f826cc9 100644 --- a/core/servers/login/websocket.js +++ b/core/servers/login/websocket.js @@ -25,9 +25,11 @@ const ModuleInfo = exports.moduleInfo = { function WebSocketClient(ws, req, serverType) { Object.defineProperty(this, 'isSecure', { - get : () => ('secure' === serverType || true === this.secureProxyConnection) ? true : false, + get : () => ('secure' === serverType || true === this.proxied) ? true : false, }); + const self = this; + // // This bridge makes accessible various calls that client sub classes // want to access on I/O socket @@ -39,7 +41,7 @@ function WebSocketClient(ws, req, serverType) { } end() { - return ws.terminate(); + return ws.terminate(); } write(data, cb) { @@ -47,7 +49,8 @@ function WebSocketClient(ws, req, serverType) { } get remoteAddress() { - return req.connection.remoteAddress; + // Support X-Forwarded-For and X-Real-IP headers for proxied connections + return (self.proxied && (req.headers['x-forwarded-for'] || req.headers['x-real-ip'])) || req.connection.remoteAddress; } }(ws); @@ -56,7 +59,8 @@ function WebSocketClient(ws, req, serverType) { }); ws.on('close', () => { - this.end(); + // we'll remove client connection which will in turn end() via our SocketBridge above + return this.emit('end'); }); // @@ -75,11 +79,13 @@ function WebSocketClient(ws, req, serverType) { // If the config allows it, look for 'x-forwarded-proto' as "https" // to override |isSecure| // - if(true === _.get(Config, 'loginServers.webSocket.secureProxy') && + if(true === _.get(Config, 'loginServers.webSocket.proxied') && 'https' === req.headers['x-forwarded-proto']) { - Log.debug(`Assuming secure connection due to X-Forwarded-Proto of ${req.headers['x-forwarded-proto']}`); - this.secureProxyConnection = true; + Log.debug(`Assuming secure connection due to X-Forwarded-Proto of "${req.headers['x-forwarded-proto']}"`); + this.proxied = true; + } else { + this.proxied = false; } // start handshake process