First pass formatting with Prettier
* Added .prettierrc.json * Added .prettierignore * Formatted
This commit is contained in:
@@ -2,30 +2,29 @@
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const SysProps = require('../../system_property.js');
|
||||
const StatLog = require('../../stat_log.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const SysProps = require('../../system_property.js');
|
||||
const StatLog = require('../../stat_log.js');
|
||||
|
||||
// deps
|
||||
const net = require('net');
|
||||
const _ = require('lodash');
|
||||
const os = require('os');
|
||||
|
||||
const net = require('net');
|
||||
const _ = require('lodash');
|
||||
const os = require('os');
|
||||
|
||||
// MRC
|
||||
const protocolVersion = '1.2.9';
|
||||
const lineDelimiter = new RegExp('\r\n|\r|\n'); // eslint-disable-line no-control-regex
|
||||
const protocolVersion = '1.2.9';
|
||||
const lineDelimiter = new RegExp('\r\n|\r|\n'); // eslint-disable-line no-control-regex
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'MRC',
|
||||
desc : 'An MRC Chat Multiplexer',
|
||||
author : 'RiPuk',
|
||||
packageName : 'codes.l33t.enigma.mrc.server',
|
||||
notes : 'https://bbswiki.bottomlessabyss.net/index.php?title=MRC_Chat_platform',
|
||||
};
|
||||
const ModuleInfo = (exports.moduleInfo = {
|
||||
name: 'MRC',
|
||||
desc: 'An MRC Chat Multiplexer',
|
||||
author: 'RiPuk',
|
||||
packageName: 'codes.l33t.enigma.mrc.server',
|
||||
notes: 'https://bbswiki.bottomlessabyss.net/index.php?title=MRC_Chat_platform',
|
||||
});
|
||||
|
||||
const connectedSockets = new Set();
|
||||
|
||||
@@ -33,29 +32,30 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.log = Log.child( { server : 'MRC' } );
|
||||
this.log = Log.child({ server: 'MRC' });
|
||||
|
||||
const config = Config();
|
||||
this.boardName = config.general.prettyBoardName || config.general.boardName;
|
||||
const config = Config();
|
||||
this.boardName = config.general.prettyBoardName || config.general.boardName;
|
||||
this.mrcConnectOpts = {
|
||||
host : config.chatServers.mrc.serverHostname || 'mrc.bottomlessabyss.net',
|
||||
port : config.chatServers.mrc.serverPort || 5000,
|
||||
retryDelay : config.chatServers.mrc.retryDelay || 10000
|
||||
host: config.chatServers.mrc.serverHostname || 'mrc.bottomlessabyss.net',
|
||||
port: config.chatServers.mrc.serverPort || 5000,
|
||||
retryDelay: config.chatServers.mrc.retryDelay || 10000,
|
||||
};
|
||||
}
|
||||
|
||||
_connectionHandler() {
|
||||
const enigmaVersion = 'ENiGMA½-BBS_' + require('../../../package.json').version;
|
||||
|
||||
const handshake = `${this.boardName}~${enigmaVersion}/${os.platform()}.${os.arch()}/${protocolVersion}`;
|
||||
this.log.debug({ handshake : handshake }, 'Handshaking with MRC server');
|
||||
const handshake = `${
|
||||
this.boardName
|
||||
}~${enigmaVersion}/${os.platform()}.${os.arch()}/${protocolVersion}`;
|
||||
this.log.debug({ handshake: handshake }, 'Handshaking with MRC server');
|
||||
|
||||
this.sendRaw(handshake);
|
||||
this.log.info(this.mrcConnectOpts, 'Connected to MRC server');
|
||||
}
|
||||
|
||||
createServer(cb) {
|
||||
|
||||
if (!this.enabled) {
|
||||
return cb(null);
|
||||
}
|
||||
@@ -74,11 +74,19 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
const config = Config();
|
||||
|
||||
const port = parseInt(config.chatServers.mrc.multiplexerPort);
|
||||
if(isNaN(port)) {
|
||||
this.log.warn( { port : config.chatServers.mrc.multiplexerPort, server : ModuleInfo.name }, 'Invalid port' );
|
||||
return cb(Errors.Invalid(`Invalid port: ${config.chatServers.mrc.multiplexerPort}`));
|
||||
if (isNaN(port)) {
|
||||
this.log.warn(
|
||||
{ port: config.chatServers.mrc.multiplexerPort, server: ModuleInfo.name },
|
||||
'Invalid port'
|
||||
);
|
||||
return cb(
|
||||
Errors.Invalid(`Invalid port: ${config.chatServers.mrc.multiplexerPort}`)
|
||||
);
|
||||
}
|
||||
Log.info( { server : ModuleInfo.name, port : config.chatServers.mrc.multiplexerPort }, 'MRC multiplexer starting up');
|
||||
Log.info(
|
||||
{ server: ModuleInfo.name, port: config.chatServers.mrc.multiplexerPort },
|
||||
'MRC multiplexer starting up'
|
||||
);
|
||||
return this.server.listen(port, cb);
|
||||
}
|
||||
|
||||
@@ -89,7 +97,10 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
const self = this;
|
||||
|
||||
// create connection to MRC server
|
||||
this.mrcClient = net.createConnection(this.mrcConnectOpts, self._connectionHandler.bind(self));
|
||||
this.mrcClient = net.createConnection(
|
||||
this.mrcConnectOpts,
|
||||
self._connectionHandler.bind(self)
|
||||
);
|
||||
|
||||
this.mrcClient.requestedDisconnect = false;
|
||||
|
||||
@@ -97,7 +108,7 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
let buffer = new Buffer.from('');
|
||||
|
||||
function handleData(chunk) {
|
||||
if(_.isString(chunk)) {
|
||||
if (_.isString(chunk)) {
|
||||
buffer += chunk;
|
||||
} else {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
@@ -113,7 +124,7 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
buffer = new Buffer.from('');
|
||||
}
|
||||
|
||||
lines.forEach( line => {
|
||||
lines.forEach(line => {
|
||||
if (line.length) {
|
||||
let message = self.parseMessage(line);
|
||||
if (message) {
|
||||
@@ -123,7 +134,7 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
});
|
||||
}
|
||||
|
||||
this.mrcClient.on('data', (data) => {
|
||||
this.mrcClient.on('data', data => {
|
||||
handleData(data);
|
||||
});
|
||||
|
||||
@@ -132,52 +143,61 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
});
|
||||
|
||||
this.mrcClient.on('close', () => {
|
||||
if (this.mrcClient && this.mrcClient.requestedDisconnect) return;
|
||||
|
||||
if (this.mrcClient && this.mrcClient.requestedDisconnect)
|
||||
return;
|
||||
this.log.info(
|
||||
this.mrcConnectOpts,
|
||||
'Disconnected from MRC server, reconnecting'
|
||||
);
|
||||
this.log.debug(
|
||||
'Waiting ' + this.mrcConnectOpts.retryDelay + 'ms before retrying'
|
||||
);
|
||||
|
||||
this.log.info(this.mrcConnectOpts, 'Disconnected from MRC server, reconnecting');
|
||||
this.log.debug('Waiting ' + this.mrcConnectOpts.retryDelay + 'ms before retrying');
|
||||
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
self.connectToMrc();
|
||||
}, this.mrcConnectOpts.retryDelay);
|
||||
});
|
||||
|
||||
this.mrcClient.on('error', err => {
|
||||
this.log.info( { error : err.message }, 'MRC server error');
|
||||
this.log.info({ error: err.message }, 'MRC server error');
|
||||
});
|
||||
}
|
||||
|
||||
createLocalListener() {
|
||||
// start a local server for clients to connect to
|
||||
|
||||
this.server = net.createServer( socket => {
|
||||
this.server = net.createServer(socket => {
|
||||
socket.setEncoding('ascii');
|
||||
|
||||
socket.on('data', data => {
|
||||
// split on \n to deal with getting messages in batches
|
||||
data.toString().split(lineDelimiter).forEach( item => {
|
||||
if (item == '') return;
|
||||
data.toString()
|
||||
.split(lineDelimiter)
|
||||
.forEach(item => {
|
||||
if (item == '') return;
|
||||
|
||||
// save username with socket
|
||||
if(item.startsWith('--DUDE-ITS--')) {
|
||||
connectedSockets.add(socket);
|
||||
socket.username = item.split('|')[1];
|
||||
Log.debug( { server : 'MRC', user: socket.username } , 'User connected');
|
||||
} else {
|
||||
this.receiveFromClient(socket.username, item);
|
||||
}
|
||||
});
|
||||
// save username with socket
|
||||
if (item.startsWith('--DUDE-ITS--')) {
|
||||
connectedSockets.add(socket);
|
||||
socket.username = item.split('|')[1];
|
||||
Log.debug(
|
||||
{ server: 'MRC', user: socket.username },
|
||||
'User connected'
|
||||
);
|
||||
} else {
|
||||
this.receiveFromClient(socket.username, item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('end', function() {
|
||||
socket.on('end', function () {
|
||||
connectedSockets.delete(socket);
|
||||
});
|
||||
|
||||
socket.on('error', err => {
|
||||
if('ECONNRESET' !== err.code) { // normal
|
||||
this.log.error( { error: err.message }, 'MRC error' );
|
||||
if ('ECONNRESET' !== err.code) {
|
||||
// normal
|
||||
this.log.error({ error: err.message }, 'MRC error');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -196,8 +216,14 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
* Sends received messages to local clients
|
||||
*/
|
||||
sendToClient(message) {
|
||||
connectedSockets.forEach( (client) => {
|
||||
if (message.to_user == '' || message.to_user == client.username || message.to_user == 'CLIENT' || message.from_user == client.username || message.to_user == 'NOTME' ) {
|
||||
connectedSockets.forEach(client => {
|
||||
if (
|
||||
message.to_user == '' ||
|
||||
message.to_user == client.username ||
|
||||
message.to_user == 'CLIENT' ||
|
||||
message.from_user == client.username ||
|
||||
message.to_user == 'NOTME'
|
||||
) {
|
||||
// this.log.debug({ server : 'MRC', username : client.username, message : message }, 'Forwarding message to connected user');
|
||||
client.write(JSON.stringify(message) + '\n');
|
||||
}
|
||||
@@ -212,16 +238,59 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
|
||||
if (message.from_user == 'SERVER' && message.body == 'HELLO') {
|
||||
// reply with extra bbs info
|
||||
this.sendToMrcServer('CLIENT', '', 'SERVER', 'ALL', '', `INFOSYS:${StatLog.getSystemStat(SysProps.SysOpUsername)}`);
|
||||
this.sendToMrcServer('CLIENT', '', 'SERVER', 'ALL', '', `INFOWEB:${config.general.website}`);
|
||||
this.sendToMrcServer('CLIENT', '', 'SERVER', 'ALL', '', `INFOTEL:${config.general.telnetHostname}`);
|
||||
this.sendToMrcServer('CLIENT', '', 'SERVER', 'ALL', '', `INFOSSH:${config.general.sshHostname}`);
|
||||
this.sendToMrcServer('CLIENT', '', 'SERVER', 'ALL', '', `INFODSC:${config.general.description}`);
|
||||
|
||||
} else if (message.from_user == 'SERVER' && message.body.toUpperCase() == 'PING') {
|
||||
this.sendToMrcServer(
|
||||
'CLIENT',
|
||||
'',
|
||||
'SERVER',
|
||||
'ALL',
|
||||
'',
|
||||
`INFOSYS:${StatLog.getSystemStat(SysProps.SysOpUsername)}`
|
||||
);
|
||||
this.sendToMrcServer(
|
||||
'CLIENT',
|
||||
'',
|
||||
'SERVER',
|
||||
'ALL',
|
||||
'',
|
||||
`INFOWEB:${config.general.website}`
|
||||
);
|
||||
this.sendToMrcServer(
|
||||
'CLIENT',
|
||||
'',
|
||||
'SERVER',
|
||||
'ALL',
|
||||
'',
|
||||
`INFOTEL:${config.general.telnetHostname}`
|
||||
);
|
||||
this.sendToMrcServer(
|
||||
'CLIENT',
|
||||
'',
|
||||
'SERVER',
|
||||
'ALL',
|
||||
'',
|
||||
`INFOSSH:${config.general.sshHostname}`
|
||||
);
|
||||
this.sendToMrcServer(
|
||||
'CLIENT',
|
||||
'',
|
||||
'SERVER',
|
||||
'ALL',
|
||||
'',
|
||||
`INFODSC:${config.general.description}`
|
||||
);
|
||||
} else if (
|
||||
message.from_user == 'SERVER' &&
|
||||
message.body.toUpperCase() == 'PING'
|
||||
) {
|
||||
// reply to heartbeat
|
||||
this.sendToMrcServer('CLIENT', '', 'SERVER', 'ALL', '', `IMALIVE:${this.boardName}`);
|
||||
|
||||
this.sendToMrcServer(
|
||||
'CLIENT',
|
||||
'',
|
||||
'SERVER',
|
||||
'ALL',
|
||||
'',
|
||||
`IMALIVE:${this.boardName}`
|
||||
);
|
||||
} else {
|
||||
// if not a heartbeat, and we have clients then we need to send something to them
|
||||
this.sendToClient(message);
|
||||
@@ -232,8 +301,8 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
* Takes an MRC message and parses it into something usable
|
||||
*/
|
||||
parseMessage(line) {
|
||||
|
||||
const [from_user, from_site, from_room, to_user, to_site, to_room, body ] = line.split('~');
|
||||
const [from_user, from_site, from_room, to_user, to_site, to_room, body] =
|
||||
line.split('~');
|
||||
|
||||
// const msg = line.split('~');
|
||||
// if (msg.length < 7) {
|
||||
@@ -249,9 +318,19 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
receiveFromClient(username, message) {
|
||||
try {
|
||||
message = JSON.parse(message);
|
||||
this.sendToMrcServer(message.from_user, message.from_room, message.to_user, message.to_site, message.to_room, message.body);
|
||||
this.sendToMrcServer(
|
||||
message.from_user,
|
||||
message.from_room,
|
||||
message.to_user,
|
||||
message.to_site,
|
||||
message.to_room,
|
||||
message.body
|
||||
);
|
||||
} catch (e) {
|
||||
Log.debug({ server : 'MRC', user : username, message : message }, 'Dodgy message received from client');
|
||||
Log.debug(
|
||||
{ server: 'MRC', user: username, message: message },
|
||||
'Dodgy message received from client'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,16 +338,16 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
* Converts a message back into the MRC format and sends it to the central MRC server
|
||||
*/
|
||||
sendToMrcServer(fromUser, fromRoom, toUser, toSite, toRoom, messageBody) {
|
||||
|
||||
const line = [
|
||||
fromUser,
|
||||
this.boardName,
|
||||
sanitiseRoomName(fromRoom || ''),
|
||||
sanitiseName(toUser || ''),
|
||||
sanitiseName(toSite || ''),
|
||||
sanitiseRoomName(toRoom || ''),
|
||||
sanitiseMessage(messageBody || '')
|
||||
].join('~') + '~';
|
||||
const line =
|
||||
[
|
||||
fromUser,
|
||||
this.boardName,
|
||||
sanitiseRoomName(fromRoom || ''),
|
||||
sanitiseName(toUser || ''),
|
||||
sanitiseName(toSite || ''),
|
||||
sanitiseRoomName(toRoom || ''),
|
||||
sanitiseMessage(messageBody || ''),
|
||||
].join('~') + '~';
|
||||
|
||||
// Log.debug({ server : 'MRC', data : line }, 'Sending data');
|
||||
this.sendRaw(line);
|
||||
@@ -284,13 +363,13 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||
* User / site name must be ASCII 33-125, no MCI, 30 chars max, underscores
|
||||
*/
|
||||
function sanitiseName(str) {
|
||||
return str.replace(
|
||||
/\s/g, '_'
|
||||
).replace(
|
||||
/[^\x21-\x7D]|(\|\w\w)/g, '' // Non-printable & MCI
|
||||
).substr(
|
||||
0, 30
|
||||
);
|
||||
return str
|
||||
.replace(/\s/g, '_')
|
||||
.replace(
|
||||
/[^\x21-\x7D]|(\|\w\w)/g,
|
||||
'' // Non-printable & MCI
|
||||
)
|
||||
.substr(0, 30);
|
||||
}
|
||||
|
||||
function sanitiseRoomName(message) {
|
||||
@@ -300,4 +379,3 @@ function sanitiseRoomName(message) {
|
||||
function sanitiseMessage(message) {
|
||||
return message.replace(/[^\x20-\x7D]/g, '');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,89 +2,91 @@
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const {
|
||||
splitTextAtTerms,
|
||||
isAnsi,
|
||||
stripAnsiControlCodes
|
||||
} = require('../../string_util.js');
|
||||
stripAnsiControlCodes,
|
||||
} = require('../../string_util.js');
|
||||
const {
|
||||
getMessageConferenceByTag,
|
||||
getMessageAreaByTag,
|
||||
getMessageListForArea,
|
||||
} = require('../../message_area.js');
|
||||
const { sortAreasOrConfs } = require('../../conf_area_util.js');
|
||||
const AnsiPrep = require('../../ansi_prep.js');
|
||||
const { wordWrapText } = require('../../word_wrap.js');
|
||||
const { stripMciColorCodes } = require('../../color_codes.js');
|
||||
} = require('../../message_area.js');
|
||||
const { sortAreasOrConfs } = require('../../conf_area_util.js');
|
||||
const AnsiPrep = require('../../ansi_prep.js');
|
||||
const { wordWrapText } = require('../../word_wrap.js');
|
||||
const { stripMciColorCodes } = require('../../color_codes.js');
|
||||
|
||||
// deps
|
||||
const net = require('net');
|
||||
const _ = require('lodash');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const moment = require('moment');
|
||||
const net = require('net');
|
||||
const _ = require('lodash');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const moment = require('moment');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Gopher',
|
||||
desc : 'A RFC-1436-ish Gopher Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.gopher.server',
|
||||
notes : 'https://tools.ietf.org/html/rfc1436',
|
||||
};
|
||||
const ModuleInfo = (exports.moduleInfo = {
|
||||
name: 'Gopher',
|
||||
desc: 'A RFC-1436-ish Gopher Server',
|
||||
author: 'NuSkooler',
|
||||
packageName: 'codes.l33t.enigma.gopher.server',
|
||||
notes: 'https://tools.ietf.org/html/rfc1436',
|
||||
});
|
||||
|
||||
const Message = require('../../message.js');
|
||||
const Message = require('../../message.js');
|
||||
|
||||
const ItemTypes = {
|
||||
Invalid : '', // not really a type, of course!
|
||||
Invalid: '', // not really a type, of course!
|
||||
|
||||
// Canonical, RFC-1436
|
||||
TextFile : '0',
|
||||
SubMenu : '1',
|
||||
CCSONameserver : '2',
|
||||
Error : '3',
|
||||
BinHexFile : '4',
|
||||
DOSFile : '5',
|
||||
UuEncodedFile : '6',
|
||||
FullTextSearch : '7',
|
||||
Telnet : '8',
|
||||
BinaryFile : '9',
|
||||
AltServer : '+',
|
||||
GIFFile : 'g',
|
||||
ImageFile : 'I',
|
||||
Telnet3270 : 'T',
|
||||
TextFile: '0',
|
||||
SubMenu: '1',
|
||||
CCSONameserver: '2',
|
||||
Error: '3',
|
||||
BinHexFile: '4',
|
||||
DOSFile: '5',
|
||||
UuEncodedFile: '6',
|
||||
FullTextSearch: '7',
|
||||
Telnet: '8',
|
||||
BinaryFile: '9',
|
||||
AltServer: '+',
|
||||
GIFFile: 'g',
|
||||
ImageFile: 'I',
|
||||
Telnet3270: 'T',
|
||||
|
||||
// Non-canonical
|
||||
HtmlFile : 'h',
|
||||
InfoMessage : 'i',
|
||||
SoundFile : 's',
|
||||
HtmlFile: 'h',
|
||||
InfoMessage: 'i',
|
||||
SoundFile: 's',
|
||||
};
|
||||
|
||||
exports.getModule = class GopherModule extends ServerModule {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.routes = new Map(); // selector->generator => gopher item
|
||||
this.log = Log.child( { server : 'Gopher' } );
|
||||
this.routes = new Map(); // selector->generator => gopher item
|
||||
this.log = Log.child({ server: 'Gopher' });
|
||||
}
|
||||
|
||||
createServer(cb) {
|
||||
if(!this.enabled) {
|
||||
if (!this.enabled) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
this.publicHostname = config.contentServers.gopher.publicHostname;
|
||||
this.publicPort = config.contentServers.gopher.publicPort;
|
||||
this.publicPort = config.contentServers.gopher.publicPort;
|
||||
|
||||
this.addRoute(/^\/?msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator);
|
||||
this.addRoute(
|
||||
/^\/?msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/,
|
||||
this.messageAreaGenerator
|
||||
);
|
||||
this.addRoute(/^(\/?[^\t\r\n]*)\r\n$/, this.staticGenerator);
|
||||
|
||||
this.server = net.createServer( socket => {
|
||||
this.server = net.createServer(socket => {
|
||||
socket.setEncoding('ascii');
|
||||
|
||||
socket.on('data', data => {
|
||||
@@ -100,8 +102,9 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
});
|
||||
|
||||
socket.on('error', err => {
|
||||
if('ECONNRESET' !== err.code) { // normal
|
||||
this.log.trace( { error : err.message }, 'Socket error');
|
||||
if ('ECONNRESET' !== err.code) {
|
||||
// normal
|
||||
this.log.trace({ error: err.message }, 'Socket error');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -110,37 +113,46 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
}
|
||||
|
||||
listen(cb) {
|
||||
if(!this.enabled) {
|
||||
if (!this.enabled) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
const port = parseInt(config.contentServers.gopher.port);
|
||||
if(isNaN(port)) {
|
||||
this.log.warn( { port : config.contentServers.gopher.port, server : ModuleInfo.name }, 'Invalid port' );
|
||||
return cb(Errors.Invalid(`Invalid port: ${config.contentServers.gopher.port}`));
|
||||
if (isNaN(port)) {
|
||||
this.log.warn(
|
||||
{ port: config.contentServers.gopher.port, server: ModuleInfo.name },
|
||||
'Invalid port'
|
||||
);
|
||||
return cb(
|
||||
Errors.Invalid(`Invalid port: ${config.contentServers.gopher.port}`)
|
||||
);
|
||||
}
|
||||
|
||||
return this.server.listen(port, config.contentServers.gopher.address, cb);
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return _.get(Config(), 'contentServers.gopher.enabled', false) && this.isConfigured();
|
||||
return (
|
||||
_.get(Config(), 'contentServers.gopher.enabled', false) && this.isConfigured()
|
||||
);
|
||||
}
|
||||
|
||||
isConfigured() {
|
||||
// public hostname & port must be set; responses contain them!
|
||||
const config = Config();
|
||||
return _.isString(_.get(config, 'contentServers.gopher.publicHostname')) &&
|
||||
_.isNumber(_.get(config, 'contentServers.gopher.publicPort'));
|
||||
return (
|
||||
_.isString(_.get(config, 'contentServers.gopher.publicHostname')) &&
|
||||
_.isNumber(_.get(config, 'contentServers.gopher.publicPort'))
|
||||
);
|
||||
}
|
||||
|
||||
addRoute(selectorRegExp, generatorHandler) {
|
||||
if(_.isString(selectorRegExp)) {
|
||||
if (_.isString(selectorRegExp)) {
|
||||
try {
|
||||
selectorRegExp = new RegExp(`${selectorRegExp}\r\n`);
|
||||
} catch(e) {
|
||||
this.log.warn( { pattern : selectorRegExp }, 'Invalid RegExp for selector' );
|
||||
} catch (e) {
|
||||
this.log.warn({ pattern: selectorRegExp }, 'Invalid RegExp for selector');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -149,9 +161,9 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
|
||||
routeRequest(selector, socket) {
|
||||
let match;
|
||||
for(let [regex, gen] of this.routes) {
|
||||
for (let [regex, gen] of this.routes) {
|
||||
match = selector.match(regex);
|
||||
if(match) {
|
||||
if (match) {
|
||||
return gen(match, res => {
|
||||
return socket.end(`${res}`);
|
||||
});
|
||||
@@ -163,14 +175,17 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
}
|
||||
|
||||
makeItem(itemType, text, selector, hostname, port) {
|
||||
selector = selector || ''; // e.g. for info
|
||||
selector = selector || ''; // e.g. for info
|
||||
hostname = hostname || this.publicHostname;
|
||||
port = port || this.publicPort;
|
||||
return `${itemType}${text}\t${selector}\t${hostname}\t${port}\r\n`;
|
||||
}
|
||||
|
||||
staticGenerator(selectorMatch, cb) {
|
||||
this.log.debug( { selector : selectorMatch[1] || '(gophermap)' }, 'Serving static content');
|
||||
this.log.debug(
|
||||
{ selector: selectorMatch[1] || '(gophermap)' },
|
||||
'Serving static content'
|
||||
);
|
||||
|
||||
const requestedPath = selectorMatch[1];
|
||||
let path = this.resolveContentPath(requestedPath);
|
||||
@@ -192,7 +207,11 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
fs.readFile(path, isGopherMap ? 'utf8' : null, (err, content) => {
|
||||
if (err) {
|
||||
let content = 'You have reached an ENiGMA½ Gopher server!\r\n';
|
||||
content += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea');
|
||||
content += this.makeItem(
|
||||
ItemTypes.SubMenu,
|
||||
'Public Message Area',
|
||||
'/msgarea'
|
||||
);
|
||||
return cb(content);
|
||||
}
|
||||
|
||||
@@ -220,12 +239,17 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
}
|
||||
|
||||
notFoundGenerator(selector, cb) {
|
||||
this.log.debug( { selector }, 'Serving not found content');
|
||||
this.log.debug({ selector }, 'Serving not found content');
|
||||
return cb('Not found');
|
||||
}
|
||||
|
||||
isAreaAndConfExposed(confTag, areaTag) {
|
||||
const conf = _.get(Config(), [ 'contentServers', 'gopher', 'messageConferences', confTag ]);
|
||||
const conf = _.get(Config(), [
|
||||
'contentServers',
|
||||
'gopher',
|
||||
'messageConferences',
|
||||
confTag,
|
||||
]);
|
||||
return Array.isArray(conf) && conf.includes(areaTag);
|
||||
}
|
||||
|
||||
@@ -248,14 +272,14 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
// to follow the KISS principle: Wrap at 79.
|
||||
//
|
||||
const WordWrapColumn = 79;
|
||||
if(isAnsi(body)) {
|
||||
if (isAnsi(body)) {
|
||||
AnsiPrep(
|
||||
body,
|
||||
{
|
||||
cols : WordWrapColumn, // See notes above
|
||||
forceLineTerm : true, // Ensure each line is term'd
|
||||
asciiMode : true, // Export to ASCII
|
||||
fillLines : false, // Don't fill up to |cols|
|
||||
cols: WordWrapColumn, // See notes above
|
||||
forceLineTerm: true, // Ensure each line is term'd
|
||||
asciiMode: true, // Export to ASCII
|
||||
fillLines: false, // Don't fill up to |cols|
|
||||
},
|
||||
(err, prepped) => {
|
||||
return cb(prepped || body);
|
||||
@@ -263,23 +287,24 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
);
|
||||
} else {
|
||||
const cleaned = stripMciColorCodes(
|
||||
stripAnsiControlCodes(body, { all : true } )
|
||||
stripAnsiControlCodes(body, { all: true })
|
||||
);
|
||||
const prepped =
|
||||
splitTextAtTerms(cleaned)
|
||||
.map(l => (wordWrapText(l, { width : WordWrapColumn } ).wrapped || []).join('\n'))
|
||||
.join('\n');
|
||||
const prepped = splitTextAtTerms(cleaned)
|
||||
.map(l =>
|
||||
(wordWrapText(l, { width: WordWrapColumn }).wrapped || []).join('\n')
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
return cb(prepped);
|
||||
}
|
||||
}
|
||||
|
||||
shortenSubject(subject) {
|
||||
return _.truncate(subject, { length : 30 } );
|
||||
return _.truncate(subject, { length: 30 });
|
||||
}
|
||||
|
||||
messageAreaGenerator(selectorMatch, cb) {
|
||||
this.log.debug( { selector : selectorMatch[0] }, 'Serving message area content');
|
||||
this.log.debug({ selector: selectorMatch[0] }, 'Serving message area content');
|
||||
//
|
||||
// Selector should be:
|
||||
// /msgarea - list confs
|
||||
@@ -288,28 +313,40 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
// /msgarea/conftag/areatag/<UUID> - message as text
|
||||
// /msgarea/conftag/areatag/<UUID>_raw - full message as text + headers
|
||||
//
|
||||
if(selectorMatch[3] || selectorMatch[4]) {
|
||||
if (selectorMatch[3] || selectorMatch[4]) {
|
||||
// message
|
||||
//const raw = selectorMatch[4] ? true : false;
|
||||
// :TODO: support 'raw'
|
||||
const msgUuid = selectorMatch[3].replace(/\r\n|\//g, '');
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const message = new Message();
|
||||
const msgUuid = selectorMatch[3].replace(/\r\n|\//g, '');
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const message = new Message();
|
||||
|
||||
return message.load( { uuid : msgUuid }, err => {
|
||||
if(err) {
|
||||
this.log.debug( { uuid : msgUuid }, 'Attempted access to non-existent message UUID!');
|
||||
return message.load({ uuid: msgUuid }, err => {
|
||||
if (err) {
|
||||
this.log.debug(
|
||||
{ uuid: msgUuid },
|
||||
'Attempted access to non-existent message UUID!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
if(message.areaTag !== areaTag || !this.isAreaAndConfExposed(confTag, areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to non-exposed conference and/or area!');
|
||||
if (
|
||||
message.areaTag !== areaTag ||
|
||||
!this.isAreaAndConfExposed(confTag, areaTag)
|
||||
) {
|
||||
this.log.warn(
|
||||
{ areaTag },
|
||||
'Attempted access to non-exposed conference and/or area!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to message in private area!');
|
||||
if (Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn(
|
||||
{ areaTag },
|
||||
'Attempted access to message in private area!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
@@ -326,26 +363,29 @@ ${msgBody}
|
||||
return cb(response);
|
||||
});
|
||||
});
|
||||
} else if(selectorMatch[2]) {
|
||||
} else if (selectorMatch[2]) {
|
||||
// list messages in area
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const area = getMessageAreaByTag(areaTag);
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const area = getMessageAreaByTag(areaTag);
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to private area!');
|
||||
if (Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn({ areaTag }, 'Attempted access to private area!');
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'Area is private'));
|
||||
}
|
||||
|
||||
if(!area || !this.isAreaAndConfExposed(confTag, areaTag)) {
|
||||
this.log.warn( { confTag, areaTag }, 'Attempted access to non-exposed conference and/or area!');
|
||||
if (!area || !this.isAreaAndConfExposed(confTag, areaTag)) {
|
||||
this.log.warn(
|
||||
{ confTag, areaTag },
|
||||
'Attempted access to non-exposed conference and/or area!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
const filter = {
|
||||
resultType : 'messageList',
|
||||
sort : 'messageId',
|
||||
order : 'descending', // we want newest messages first for Gopher
|
||||
resultType: 'messageList',
|
||||
sort: 'messageId',
|
||||
order: 'descending', // we want newest messages first for Gopher
|
||||
};
|
||||
|
||||
return getMessageListForArea(null, areaTag, filter, (err, msgList) => {
|
||||
@@ -354,30 +394,48 @@ ${msgBody}
|
||||
this.makeItem(ItemTypes.InfoMessage, `Messages in ${area.name}`),
|
||||
this.makeItem(ItemTypes.InfoMessage, '(newest first)'),
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
...msgList.map(msg => this.makeItem(
|
||||
ItemTypes.TextFile,
|
||||
`${moment(msg.modTimestamp).format('YYYY-MM-DD hh:mma')}: ${this.shortenSubject(msg.subject)} (${msg.fromUserName} to ${msg.toUserName})`,
|
||||
`/msgarea/${confTag}/${areaTag}/${msg.messageUuid}`
|
||||
))
|
||||
...msgList.map(msg =>
|
||||
this.makeItem(
|
||||
ItemTypes.TextFile,
|
||||
`${moment(msg.modTimestamp).format(
|
||||
'YYYY-MM-DD hh:mma'
|
||||
)}: ${this.shortenSubject(msg.subject)} (${
|
||||
msg.fromUserName
|
||||
} to ${msg.toUserName})`,
|
||||
`/msgarea/${confTag}/${areaTag}/${msg.messageUuid}`
|
||||
)
|
||||
),
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
});
|
||||
} else if(selectorMatch[1]) {
|
||||
} else if (selectorMatch[1]) {
|
||||
// list areas in conf
|
||||
const sysConfig = Config();
|
||||
const confTag = selectorMatch[1].replace(/\r\n|\//g, '');
|
||||
const conf = _.get(sysConfig, [ 'contentServers', 'gopher', 'messageConferences', confTag ]) && getMessageConferenceByTag(confTag);
|
||||
if(!conf) {
|
||||
const confTag = selectorMatch[1].replace(/\r\n|\//g, '');
|
||||
const conf =
|
||||
_.get(sysConfig, [
|
||||
'contentServers',
|
||||
'gopher',
|
||||
'messageConferences',
|
||||
confTag,
|
||||
]) && getMessageConferenceByTag(confTag);
|
||||
if (!conf) {
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
const areas = _.get(sysConfig, [ 'contentServers', 'gopher', 'messageConferences', confTag ], {})
|
||||
.map(areaTag => Object.assign( { areaTag }, getMessageAreaByTag(areaTag)))
|
||||
const areas = _.get(
|
||||
sysConfig,
|
||||
['contentServers', 'gopher', 'messageConferences', confTag],
|
||||
{}
|
||||
)
|
||||
.map(areaTag => Object.assign({ areaTag }, getMessageAreaByTag(areaTag)))
|
||||
.filter(area => area && !Message.isPrivateAreaTag(area.areaTag));
|
||||
|
||||
if(0 === areas.length) {
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'No message areas available'));
|
||||
if (0 === areas.length) {
|
||||
return cb(
|
||||
this.makeItem(ItemTypes.InfoMessage, 'No message areas available')
|
||||
);
|
||||
}
|
||||
|
||||
sortAreasOrConfs(areas);
|
||||
@@ -386,18 +444,33 @@ ${msgBody}
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
this.makeItem(ItemTypes.InfoMessage, `Message areas in ${conf.name}`),
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
...areas.map(area => this.makeItem(ItemTypes.SubMenu, `${area.name} ${area.desc ? '- ' + area.desc : ''}`, `/msgarea/${confTag}/${area.areaTag}`))
|
||||
...areas.map(area =>
|
||||
this.makeItem(
|
||||
ItemTypes.SubMenu,
|
||||
`${area.name} ${area.desc ? '- ' + area.desc : ''}`,
|
||||
`/msgarea/${confTag}/${area.areaTag}`
|
||||
)
|
||||
),
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
} else {
|
||||
// message area base (list confs)
|
||||
const confs = Object.keys(_.get(Config(), 'contentServers.gopher.messageConferences', {}))
|
||||
.map(confTag => Object.assign( { confTag }, getMessageConferenceByTag(confTag)))
|
||||
.filter(conf => conf); // remove any baddies
|
||||
const confs = Object.keys(
|
||||
_.get(Config(), 'contentServers.gopher.messageConferences', {})
|
||||
)
|
||||
.map(confTag =>
|
||||
Object.assign({ confTag }, getMessageConferenceByTag(confTag))
|
||||
)
|
||||
.filter(conf => conf); // remove any baddies
|
||||
|
||||
if(0 === confs.length) {
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'No message conferences available'));
|
||||
if (0 === confs.length) {
|
||||
return cb(
|
||||
this.makeItem(
|
||||
ItemTypes.InfoMessage,
|
||||
'No message conferences available'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
sortAreasOrConfs(confs);
|
||||
@@ -407,10 +480,16 @@ ${msgBody}
|
||||
this.makeItem(ItemTypes.InfoMessage, 'Available Message Conferences'),
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
this.makeItem(ItemTypes.InfoMessage, ''),
|
||||
...confs.map(conf => this.makeItem(ItemTypes.SubMenu, `${conf.name} ${conf.desc ? '- ' + conf.desc : ''}`, `/msgarea/${conf.confTag}`))
|
||||
...confs.map(conf =>
|
||||
this.makeItem(
|
||||
ItemTypes.SubMenu,
|
||||
`${conf.name} ${conf.desc ? '- ' + conf.desc : ''}`,
|
||||
`/msgarea/${conf.confTag}`
|
||||
)
|
||||
),
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,46 +2,56 @@
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const ServerModule = require('../../server_module.js').ServerModule;
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const ServerModule = require('../../server_module.js').ServerModule;
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
|
||||
// deps
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const _ = require('lodash');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const mimeTypes = require('mime-types');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const _ = require('lodash');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const mimeTypes = require('mime-types');
|
||||
const forEachSeries = require('async/forEachSeries');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Web',
|
||||
desc : 'Web Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.web.server',
|
||||
};
|
||||
const ModuleInfo = (exports.moduleInfo = {
|
||||
name: 'Web',
|
||||
desc: 'Web Server',
|
||||
author: 'NuSkooler',
|
||||
packageName: 'codes.l33t.enigma.web.server',
|
||||
});
|
||||
|
||||
class Route {
|
||||
constructor(route) {
|
||||
Object.assign(this, route);
|
||||
|
||||
if(this.method) {
|
||||
if (this.method) {
|
||||
this.method = this.method.toUpperCase();
|
||||
}
|
||||
|
||||
try {
|
||||
this.pathRegExp = new RegExp(this.path);
|
||||
} catch(e) {
|
||||
Log.debug( { route : route }, 'Invalid regular expression for route path' );
|
||||
} catch (e) {
|
||||
Log.debug({ route: route }, 'Invalid regular expression for route path');
|
||||
}
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return (
|
||||
this.pathRegExp instanceof RegExp &&
|
||||
( -1 !== [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', ].indexOf(this.method) ) ||
|
||||
(this.pathRegExp instanceof RegExp &&
|
||||
-1 !==
|
||||
[
|
||||
'GET',
|
||||
'HEAD',
|
||||
'POST',
|
||||
'PUT',
|
||||
'DELETE',
|
||||
'CONNECT',
|
||||
'OPTIONS',
|
||||
'TRACE',
|
||||
].indexOf(this.method)) ||
|
||||
!_.isFunction(this.handler)
|
||||
);
|
||||
}
|
||||
@@ -50,24 +60,26 @@ class Route {
|
||||
return req.method === this.method && this.pathRegExp.test(req.url);
|
||||
}
|
||||
|
||||
getRouteKey() { return `${this.method}:${this.path}`; }
|
||||
getRouteKey() {
|
||||
return `${this.method}:${this.path}`;
|
||||
}
|
||||
}
|
||||
|
||||
exports.getModule = class WebServerModule extends ServerModule {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const config = Config();
|
||||
this.enableHttp = config.contentServers.web.http.enabled || false;
|
||||
this.enableHttps = config.contentServers.web.https.enabled || false;
|
||||
const config = Config();
|
||||
this.enableHttp = config.contentServers.web.http.enabled || false;
|
||||
this.enableHttps = config.contentServers.web.https.enabled || false;
|
||||
|
||||
this.routes = {};
|
||||
|
||||
if(this.isEnabled() && config.contentServers.web.staticRoot) {
|
||||
if (this.isEnabled() && config.contentServers.web.staticRoot) {
|
||||
this.addRoute({
|
||||
method : 'GET',
|
||||
path : '/static/.*$',
|
||||
handler : this.routeStaticFile.bind(this),
|
||||
method: 'GET',
|
||||
path: '/static/.*$',
|
||||
handler: this.routeStaticFile.bind(this),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -81,22 +93,24 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
// only if non-standard. Allow users to override full prefix in config.
|
||||
//
|
||||
const config = Config();
|
||||
if(_.isString(config.contentServers.web.overrideUrlPrefix)) {
|
||||
if (_.isString(config.contentServers.web.overrideUrlPrefix)) {
|
||||
return `${config.contentServers.web.overrideUrlPrefix}${pathAndQuery}`;
|
||||
}
|
||||
|
||||
let schema;
|
||||
let port;
|
||||
if(config.contentServers.web.https.enabled) {
|
||||
schema = 'https://';
|
||||
port = (443 === config.contentServers.web.https.port) ?
|
||||
'' :
|
||||
`:${config.contentServers.web.https.port}`;
|
||||
if (config.contentServers.web.https.enabled) {
|
||||
schema = 'https://';
|
||||
port =
|
||||
443 === config.contentServers.web.https.port
|
||||
? ''
|
||||
: `:${config.contentServers.web.https.port}`;
|
||||
} else {
|
||||
schema = 'http://';
|
||||
port = (80 === config.contentServers.web.http.port) ?
|
||||
'' :
|
||||
`:${config.contentServers.web.http.port}`;
|
||||
schema = 'http://';
|
||||
port =
|
||||
80 === config.contentServers.web.http.port
|
||||
? ''
|
||||
: `:${config.contentServers.web.http.port}`;
|
||||
}
|
||||
|
||||
return `${schema}${config.contentServers.web.domain}${port}${pathAndQuery}`;
|
||||
@@ -107,21 +121,25 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
}
|
||||
|
||||
createServer(cb) {
|
||||
if(this.enableHttp) {
|
||||
this.httpServer = http.createServer( (req, resp) => this.routeRequest(req, resp) );
|
||||
if (this.enableHttp) {
|
||||
this.httpServer = http.createServer((req, resp) =>
|
||||
this.routeRequest(req, resp)
|
||||
);
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
if(this.enableHttps) {
|
||||
if (this.enableHttps) {
|
||||
const options = {
|
||||
cert : fs.readFileSync(config.contentServers.web.https.certPem),
|
||||
key : fs.readFileSync(config.contentServers.web.https.keyPem),
|
||||
cert: fs.readFileSync(config.contentServers.web.https.certPem),
|
||||
key: fs.readFileSync(config.contentServers.web.https.keyPem),
|
||||
};
|
||||
|
||||
// additional options
|
||||
Object.assign(options, config.contentServers.web.https.options || {} );
|
||||
Object.assign(options, config.contentServers.web.https.options || {});
|
||||
|
||||
this.httpsServer = https.createServer(options, (req, resp) => this.routeRequest(req, resp) );
|
||||
this.httpsServer = https.createServer(options, (req, resp) =>
|
||||
this.routeRequest(req, resp)
|
||||
);
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
@@ -129,38 +147,61 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
|
||||
listen(cb) {
|
||||
const config = Config();
|
||||
forEachSeries([ 'http', 'https' ], (service, nextService) => {
|
||||
const name = `${service}Server`;
|
||||
if(this[name]) {
|
||||
const port = parseInt(config.contentServers.web[service].port);
|
||||
if(isNaN(port)) {
|
||||
Log.warn( { port : config.contentServers.web[service].port, server : ModuleInfo.name }, `Invalid port (${service})` );
|
||||
return nextService(Errors.Invalid(`Invalid port: ${config.contentServers.web[service].port}`));
|
||||
}
|
||||
forEachSeries(
|
||||
['http', 'https'],
|
||||
(service, nextService) => {
|
||||
const name = `${service}Server`;
|
||||
if (this[name]) {
|
||||
const port = parseInt(config.contentServers.web[service].port);
|
||||
if (isNaN(port)) {
|
||||
Log.warn(
|
||||
{
|
||||
port: config.contentServers.web[service].port,
|
||||
server: ModuleInfo.name,
|
||||
},
|
||||
`Invalid port (${service})`
|
||||
);
|
||||
return nextService(
|
||||
Errors.Invalid(
|
||||
`Invalid port: ${config.contentServers.web[service].port}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this[name].listen(port, config.contentServers.web[service].address, err => {
|
||||
return nextService(err);
|
||||
});
|
||||
} else {
|
||||
return nextService(null);
|
||||
this[name].listen(
|
||||
port,
|
||||
config.contentServers.web[service].address,
|
||||
err => {
|
||||
return nextService(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return nextService(null);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
addRoute(route) {
|
||||
route = new Route(route);
|
||||
|
||||
if(!route.isValid()) {
|
||||
Log.warn( { route : route }, 'Cannot add route: missing or invalid required members' );
|
||||
if (!route.isValid()) {
|
||||
Log.warn(
|
||||
{ route: route },
|
||||
'Cannot add route: missing or invalid required members'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const routeKey = route.getRouteKey();
|
||||
if(routeKey in this.routes) {
|
||||
Log.warn( { route : route, routeKey : routeKey }, 'Cannot add route: duplicate method/path combination exists' );
|
||||
if (routeKey in this.routes) {
|
||||
Log.warn(
|
||||
{ route: route, routeKey: routeKey },
|
||||
'Cannot add route: duplicate method/path combination exists'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -169,9 +210,9 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
}
|
||||
|
||||
routeRequest(req, resp) {
|
||||
const route = _.find(this.routes, r => r.matchesRequest(req) );
|
||||
const route = _.find(this.routes, r => r.matchesRequest(req));
|
||||
|
||||
if(!route && '/' === req.url) {
|
||||
if (!route && '/' === req.url) {
|
||||
return this.routeIndex(req, resp);
|
||||
}
|
||||
|
||||
@@ -179,12 +220,15 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
}
|
||||
|
||||
respondWithError(resp, code, bodyText, title) {
|
||||
const customErrorPage = paths.join(Config().contentServers.web.staticRoot, `${code}.html`);
|
||||
const customErrorPage = paths.join(
|
||||
Config().contentServers.web.staticRoot,
|
||||
`${code}.html`
|
||||
);
|
||||
|
||||
fs.readFile(customErrorPage, 'utf8', (err, data) => {
|
||||
resp.writeHead(code, { 'Content-Type' : 'text/html' } );
|
||||
resp.writeHead(code, { 'Content-Type': 'text/html' });
|
||||
|
||||
if(err) {
|
||||
if (err) {
|
||||
return resp.end(`<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -197,8 +241,7 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
<h2>${bodyText}</h2>
|
||||
</article>
|
||||
</body>
|
||||
</html>`
|
||||
);
|
||||
</html>`);
|
||||
}
|
||||
|
||||
return resp.end(data);
|
||||
@@ -232,13 +275,15 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
}
|
||||
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if(err || !stats.isFile()) {
|
||||
if (err || !stats.isFile()) {
|
||||
return self.fileNotFound(resp);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Content-Type' : mimeTypes.contentType(paths.basename(filePath)) || mimeTypes.contentType('.bin'),
|
||||
'Content-Length' : stats.size,
|
||||
'Content-Type':
|
||||
mimeTypes.contentType(paths.basename(filePath)) ||
|
||||
mimeTypes.contentType('.bin'),
|
||||
'Content-Length': stats.size,
|
||||
};
|
||||
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
@@ -259,18 +304,23 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||
const self = this;
|
||||
|
||||
fs.readFile(templatePath, 'utf8', (err, templateData) => {
|
||||
if(err) {
|
||||
if (err) {
|
||||
return self.fileNotFound(resp);
|
||||
}
|
||||
|
||||
preprocessCallback(templateData, (err, finalPage, contentType) => {
|
||||
if(err || !finalPage) {
|
||||
return self.respondWithError(resp, 500, 'Internal Server Error.', 'Internal Server Error');
|
||||
if (err || !finalPage) {
|
||||
return self.respondWithError(
|
||||
resp,
|
||||
500,
|
||||
'Internal Server Error.',
|
||||
'Internal Server Error'
|
||||
);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Content-Type' : contentType || mimeTypes.contentType('.html'),
|
||||
'Content-Length' : finalPage.length,
|
||||
'Content-Type': contentType || mimeTypes.contentType('.html'),
|
||||
'Content-Length': finalPage.length,
|
||||
};
|
||||
|
||||
resp.writeHead(200, headers);
|
||||
|
||||
@@ -2,35 +2,32 @@
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Config = require('../../config.js').get;
|
||||
const baseClient = require('../../client.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const Config = require('../../config.js').get;
|
||||
const baseClient = require('../../client.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const LoginServerModule = require('../../login_server_module.js');
|
||||
const userLogin = require('../../user_login.js').userLogin;
|
||||
const enigVersion = require('../../../package.json').version;
|
||||
const theme = require('../../theme.js');
|
||||
const stringFormat = require('../../string_format.js');
|
||||
const {
|
||||
Errors,
|
||||
ErrorReasons
|
||||
} = require('../../enig_error.js');
|
||||
const User = require('../../user.js');
|
||||
const UserProps = require('../../user_property.js');
|
||||
const userLogin = require('../../user_login.js').userLogin;
|
||||
const enigVersion = require('../../../package.json').version;
|
||||
const theme = require('../../theme.js');
|
||||
const stringFormat = require('../../string_format.js');
|
||||
const { Errors, ErrorReasons } = require('../../enig_error.js');
|
||||
const User = require('../../user.js');
|
||||
const UserProps = require('../../user_property.js');
|
||||
|
||||
// deps
|
||||
const ssh2 = require('ssh2');
|
||||
const fs = require('graceful-fs');
|
||||
const util = require('util');
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const ssh2 = require('ssh2');
|
||||
const fs = require('graceful-fs');
|
||||
const util = require('util');
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'SSH',
|
||||
desc : 'SSH Server',
|
||||
author : 'NuSkooler',
|
||||
isSecure : true,
|
||||
packageName : 'codes.l33t.enigma.ssh.server',
|
||||
};
|
||||
const ModuleInfo = (exports.moduleInfo = {
|
||||
name: 'SSH',
|
||||
desc: 'SSH Server',
|
||||
author: 'NuSkooler',
|
||||
isSecure: true,
|
||||
packageName: 'codes.l33t.enigma.ssh.server',
|
||||
});
|
||||
|
||||
function SSHClient(clientConn) {
|
||||
baseClient.Client.apply(this, arguments);
|
||||
@@ -43,16 +40,19 @@ function SSHClient(clientConn) {
|
||||
const self = this;
|
||||
|
||||
clientConn.on('authentication', function authAttempt(ctx) {
|
||||
const username = ctx.username || '';
|
||||
const config = Config();
|
||||
self.isNewUser = (config.users.newUserNames || []).indexOf(username) > -1;
|
||||
const username = ctx.username || '';
|
||||
const config = Config();
|
||||
self.isNewUser = (config.users.newUserNames || []).indexOf(username) > -1;
|
||||
|
||||
self.log.trace( { method : ctx.method, username : username, newUser : self.isNewUser }, 'SSH authentication attempt');
|
||||
self.log.trace(
|
||||
{ method: ctx.method, username: username, newUser: self.isNewUser },
|
||||
'SSH authentication attempt'
|
||||
);
|
||||
|
||||
const safeContextReject = (param) => {
|
||||
const safeContextReject = param => {
|
||||
try {
|
||||
return ctx.reject(param);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -64,58 +64,79 @@ function SSHClient(clientConn) {
|
||||
|
||||
// slow version to thwart brute force attacks
|
||||
const slowTerminateConnection = () => {
|
||||
setTimeout( () => {
|
||||
setTimeout(() => {
|
||||
return terminateConnection();
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const promptAndTerm = (msg, method = 'standard') => {
|
||||
if('keyboard-interactive' === ctx.method) {
|
||||
if ('keyboard-interactive' === ctx.method) {
|
||||
ctx.prompt(msg);
|
||||
}
|
||||
return 'slow' === method ? slowTerminateConnection() : terminateConnection();
|
||||
};
|
||||
|
||||
const accountAlreadyLoggedIn = (username) => {
|
||||
return promptAndTerm(`${username} is already connected to the system. Terminating connection.\n(Press any key to continue)`);
|
||||
const accountAlreadyLoggedIn = username => {
|
||||
return promptAndTerm(
|
||||
`${username} is already connected to the system. Terminating connection.\n(Press any key to continue)`
|
||||
);
|
||||
};
|
||||
|
||||
const accountDisabled = (username) => {
|
||||
const accountDisabled = username => {
|
||||
return promptAndTerm(`${username} is disabled.\n(Press any key to continue)`);
|
||||
};
|
||||
|
||||
const accountInactive = (username) => {
|
||||
return promptAndTerm(`${username} is waiting for +op activation.\n(Press any key to continue)`);
|
||||
const accountInactive = username => {
|
||||
return promptAndTerm(
|
||||
`${username} is waiting for +op activation.\n(Press any key to continue)`
|
||||
);
|
||||
};
|
||||
|
||||
const accountLocked = (username) => {
|
||||
return promptAndTerm(`${username} is locked.\n(Press any key to continue)`, 'slow');
|
||||
const accountLocked = username => {
|
||||
return promptAndTerm(
|
||||
`${username} is locked.\n(Press any key to continue)`,
|
||||
'slow'
|
||||
);
|
||||
};
|
||||
|
||||
const isSpecialHandleError = (err) => {
|
||||
return [ ErrorReasons.AlreadyLoggedIn, ErrorReasons.Disabled, ErrorReasons.Inactive, ErrorReasons.Locked ].includes(err.reasonCode);
|
||||
const isSpecialHandleError = err => {
|
||||
return [
|
||||
ErrorReasons.AlreadyLoggedIn,
|
||||
ErrorReasons.Disabled,
|
||||
ErrorReasons.Inactive,
|
||||
ErrorReasons.Locked,
|
||||
].includes(err.reasonCode);
|
||||
};
|
||||
|
||||
const handleSpecialError = (err, username) => {
|
||||
switch(err.reasonCode) {
|
||||
case ErrorReasons.AlreadyLoggedIn : return accountAlreadyLoggedIn(username);
|
||||
case ErrorReasons.Inactive : return accountInactive(username);
|
||||
case ErrorReasons.Disabled : return accountDisabled(username);
|
||||
case ErrorReasons.Locked : return accountLocked(username);
|
||||
default : return terminateConnection();
|
||||
switch (err.reasonCode) {
|
||||
case ErrorReasons.AlreadyLoggedIn:
|
||||
return accountAlreadyLoggedIn(username);
|
||||
case ErrorReasons.Inactive:
|
||||
return accountInactive(username);
|
||||
case ErrorReasons.Disabled:
|
||||
return accountDisabled(username);
|
||||
case ErrorReasons.Locked:
|
||||
return accountLocked(username);
|
||||
default:
|
||||
return terminateConnection();
|
||||
}
|
||||
};
|
||||
|
||||
const authWithPasswordOrPubKey = (authType) => {
|
||||
if(User.AuthFactor1Types.SSHPubKey !== authType || !self.user.isAuthenticated() || !ctx.signature) {
|
||||
const authWithPasswordOrPubKey = authType => {
|
||||
if (
|
||||
User.AuthFactor1Types.SSHPubKey !== authType ||
|
||||
!self.user.isAuthenticated() ||
|
||||
!ctx.signature
|
||||
) {
|
||||
// step 1: login/auth using PubKey
|
||||
userLogin(self, ctx.username, ctx.password, { authType, ctx }, (err) => {
|
||||
if(err) {
|
||||
if(isSpecialHandleError(err)) {
|
||||
userLogin(self, ctx.username, ctx.password, { authType, ctx }, err => {
|
||||
if (err) {
|
||||
if (isSpecialHandleError(err)) {
|
||||
return handleSpecialError(err, username);
|
||||
}
|
||||
|
||||
if(Errors.BadLogin().code === err.code) {
|
||||
if (Errors.BadLogin().code === err.code) {
|
||||
return slowTerminateConnection();
|
||||
}
|
||||
|
||||
@@ -126,8 +147,10 @@ function SSHClient(clientConn) {
|
||||
});
|
||||
} else {
|
||||
// step 2: verify signature
|
||||
const pubKeyActual = ssh2.utils.parseKey(self.user.getProperty(UserProps.AuthPubKey));
|
||||
if(!pubKeyActual || !pubKeyActual.verify(ctx.blob, ctx.signature)) {
|
||||
const pubKeyActual = ssh2.utils.parseKey(
|
||||
self.user.getProperty(UserProps.AuthPubKey)
|
||||
);
|
||||
if (!pubKeyActual || !pubKeyActual.verify(ctx.blob, ctx.signature)) {
|
||||
return slowTerminateConnection();
|
||||
}
|
||||
return ctx.accept();
|
||||
@@ -135,38 +158,48 @@ function SSHClient(clientConn) {
|
||||
};
|
||||
|
||||
const authKeyboardInteractive = () => {
|
||||
if(0 === username.length) {
|
||||
if (0 === username.length) {
|
||||
return safeContextReject();
|
||||
}
|
||||
|
||||
const interactivePrompt = { prompt : `${ctx.username}'s password: `, echo : false };
|
||||
const interactivePrompt = {
|
||||
prompt: `${ctx.username}'s password: `,
|
||||
echo: false,
|
||||
};
|
||||
|
||||
ctx.prompt(interactivePrompt, function retryPrompt(answers) {
|
||||
userLogin(self, username, (answers[0] || ''), err => {
|
||||
if(err) {
|
||||
if(isSpecialHandleError(err)) {
|
||||
userLogin(self, username, answers[0] || '', err => {
|
||||
if (err) {
|
||||
if (isSpecialHandleError(err)) {
|
||||
return handleSpecialError(err, username);
|
||||
}
|
||||
|
||||
if(Errors.BadLogin().code === err.code) {
|
||||
if (Errors.BadLogin().code === err.code) {
|
||||
return slowTerminateConnection();
|
||||
}
|
||||
|
||||
const artOpts = {
|
||||
client : self,
|
||||
name : 'SSHPMPT.ASC',
|
||||
readSauce : false,
|
||||
client: self,
|
||||
name: 'SSHPMPT.ASC',
|
||||
readSauce: false,
|
||||
};
|
||||
|
||||
theme.getThemeArt(artOpts, (err, artInfo) => {
|
||||
if(err) {
|
||||
if (err) {
|
||||
interactivePrompt.prompt = `Access denied\n${ctx.username}'s password: `;
|
||||
} else {
|
||||
const newUserNameList = _.has(config, 'users.newUserNames') && config.users.newUserNames.length > 0 ?
|
||||
config.users.newUserNames.map(newName => '"' + newName + '"').join(', ') :
|
||||
'(No new user names enabled!)';
|
||||
const newUserNameList =
|
||||
_.has(config, 'users.newUserNames') &&
|
||||
config.users.newUserNames.length > 0
|
||||
? config.users.newUserNames
|
||||
.map(newName => '"' + newName + '"')
|
||||
.join(', ')
|
||||
: '(No new user names enabled!)';
|
||||
|
||||
interactivePrompt.prompt = `Access denied\n${stringFormat(artInfo.data, { newUserNames : newUserNameList })}\n${ctx.username}'s password:`;
|
||||
interactivePrompt.prompt = `Access denied\n${stringFormat(
|
||||
artInfo.data,
|
||||
{ newUserNames: newUserNameList }
|
||||
)}\n${ctx.username}'s password:`;
|
||||
}
|
||||
return ctx.prompt(interactivePrompt, retryPrompt);
|
||||
});
|
||||
@@ -181,32 +214,32 @@ function SSHClient(clientConn) {
|
||||
// If the system is open and |isNewUser| is true, the login
|
||||
// sequence is hijacked in order to start the application process.
|
||||
//
|
||||
if(false === config.general.closedSystem && self.isNewUser) {
|
||||
if (false === config.general.closedSystem && self.isNewUser) {
|
||||
return ctx.accept();
|
||||
}
|
||||
|
||||
switch(ctx.method) {
|
||||
case 'password' :
|
||||
switch (ctx.method) {
|
||||
case 'password':
|
||||
return authWithPasswordOrPubKey(User.AuthFactor1Types.Password);
|
||||
//return authWithPassword();
|
||||
//return authWithPassword();
|
||||
|
||||
case 'publickey' :
|
||||
case 'publickey':
|
||||
return authWithPasswordOrPubKey(User.AuthFactor1Types.SSHPubKey);
|
||||
//return authWithPubKey();
|
||||
//return authWithPubKey();
|
||||
|
||||
case 'keyboard-interactive' :
|
||||
case 'keyboard-interactive':
|
||||
return authKeyboardInteractive();
|
||||
|
||||
default :
|
||||
default:
|
||||
return safeContextReject(SSHClient.ValidAuthMethods);
|
||||
}
|
||||
});
|
||||
|
||||
this.dataHandler = function(data) {
|
||||
this.dataHandler = function (data) {
|
||||
self.emit('data', data);
|
||||
};
|
||||
|
||||
this.updateTermInfo = function(info) {
|
||||
this.updateTermInfo = function (info) {
|
||||
//
|
||||
// From ssh2 docs:
|
||||
// "rows and cols override width and height when rows and cols are non-zero."
|
||||
@@ -214,12 +247,12 @@ function SSHClient(clientConn) {
|
||||
let termHeight;
|
||||
let termWidth;
|
||||
|
||||
if(info.rows > 0 && info.cols > 0) {
|
||||
termHeight = info.rows;
|
||||
termWidth = info.cols;
|
||||
} else if(info.width > 0 && info.height > 0) {
|
||||
termHeight = info.height;
|
||||
termWidth = info.width;
|
||||
if (info.rows > 0 && info.cols > 0) {
|
||||
termHeight = info.rows;
|
||||
termWidth = info.cols;
|
||||
} else if (info.width > 0 && info.height > 0) {
|
||||
termHeight = info.height;
|
||||
termWidth = info.width;
|
||||
}
|
||||
|
||||
assert(_.isObject(self.term));
|
||||
@@ -228,12 +261,16 @@ function SSHClient(clientConn) {
|
||||
// Note that if we fail here, connect.js attempts some non-standard
|
||||
// queries/etc., and ultimately will default to 80x24 if all else fails
|
||||
//
|
||||
if(termHeight > 0 && termWidth > 0) {
|
||||
if (termHeight > 0 && termWidth > 0) {
|
||||
self.term.termHeight = termHeight;
|
||||
self.term.termWidth = termWidth;
|
||||
}
|
||||
|
||||
if(_.isString(info.term) && info.term.length > 0 && 'unknown' === self.term.termType) {
|
||||
if (
|
||||
_.isString(info.term) &&
|
||||
info.term.length > 0 &&
|
||||
'unknown' === self.term.termType
|
||||
) {
|
||||
self.setTermType(info.term);
|
||||
}
|
||||
};
|
||||
@@ -242,17 +279,17 @@ function SSHClient(clientConn) {
|
||||
self.log.info('SSH authentication success');
|
||||
|
||||
clientConn.on('session', accept => {
|
||||
|
||||
const session = accept();
|
||||
|
||||
session.on('pty', function pty(accept, reject, info) {
|
||||
self.log.debug(info, 'SSH pty event');
|
||||
|
||||
if(_.isFunction(accept)) {
|
||||
if (_.isFunction(accept)) {
|
||||
accept();
|
||||
}
|
||||
|
||||
if(self.input) { // do we have I/O?
|
||||
if (self.input) {
|
||||
// do we have I/O?
|
||||
self.updateTermInfo(info);
|
||||
} else {
|
||||
self.cachedTermInfo = info;
|
||||
@@ -262,7 +299,7 @@ function SSHClient(clientConn) {
|
||||
session.on('env', (accept, reject, info) => {
|
||||
self.log.debug(info, 'SSH env event');
|
||||
|
||||
if(_.isFunction(accept)) {
|
||||
if (_.isFunction(accept)) {
|
||||
accept();
|
||||
}
|
||||
});
|
||||
@@ -276,49 +313,46 @@ function SSHClient(clientConn) {
|
||||
|
||||
channel.stdin.on('data', self.dataHandler);
|
||||
|
||||
if(self.cachedTermInfo) {
|
||||
if (self.cachedTermInfo) {
|
||||
self.updateTermInfo(self.cachedTermInfo);
|
||||
delete self.cachedTermInfo;
|
||||
}
|
||||
|
||||
// we're ready!
|
||||
const firstMenu = self.isNewUser ? Config().loginServers.ssh.firstMenuNewUser : Config().loginServers.ssh.firstMenu;
|
||||
self.emit('ready', { firstMenu : firstMenu } );
|
||||
const firstMenu = self.isNewUser
|
||||
? Config().loginServers.ssh.firstMenuNewUser
|
||||
: Config().loginServers.ssh.firstMenu;
|
||||
self.emit('ready', { firstMenu: firstMenu });
|
||||
});
|
||||
|
||||
session.on('window-change', (accept, reject, info) => {
|
||||
self.log.debug(info, 'SSH window-change event');
|
||||
|
||||
if(self.input) {
|
||||
if (self.input) {
|
||||
self.updateTermInfo(info);
|
||||
} else {
|
||||
self.cachedTermInfo = info;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
clientConn.once('end', () => {
|
||||
return self.emit('end'); // remove client connection/tracking
|
||||
return self.emit('end'); // remove client connection/tracking
|
||||
});
|
||||
|
||||
clientConn.on('error', err => {
|
||||
self.log.warn( { error : err.message, code : err.code }, 'SSH connection error');
|
||||
self.log.warn({ error: err.message, code: err.code }, 'SSH connection error');
|
||||
});
|
||||
|
||||
this.disconnect = function() {
|
||||
this.disconnect = function () {
|
||||
return clientConn.end();
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(SSHClient, baseClient.Client);
|
||||
|
||||
SSHClient.ValidAuthMethods = [
|
||||
'password',
|
||||
'keyboard-interactive',
|
||||
'publickey',
|
||||
];
|
||||
SSHClient.ValidAuthMethods = ['password', 'keyboard-interactive', 'publickey'];
|
||||
|
||||
exports.getModule = class SSHServerModule extends LoginServerModule {
|
||||
constructor() {
|
||||
@@ -327,27 +361,27 @@ exports.getModule = class SSHServerModule extends LoginServerModule {
|
||||
|
||||
createServer(cb) {
|
||||
const config = Config();
|
||||
if(true != config.loginServers.ssh.enabled) {
|
||||
if (true != config.loginServers.ssh.enabled) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const serverConf = {
|
||||
hostKeys : [
|
||||
hostKeys: [
|
||||
{
|
||||
key : fs.readFileSync(config.loginServers.ssh.privateKeyPem),
|
||||
passphrase : config.loginServers.ssh.privateKeyPass,
|
||||
}
|
||||
key: fs.readFileSync(config.loginServers.ssh.privateKeyPem),
|
||||
passphrase: config.loginServers.ssh.privateKeyPass,
|
||||
},
|
||||
],
|
||||
ident : 'enigma-bbs-' + enigVersion + '-srv',
|
||||
ident: 'enigma-bbs-' + enigVersion + '-srv',
|
||||
|
||||
// Note that sending 'banner' breaks at least EtherTerm!
|
||||
|
||||
debug : (sshDebugLine) => {
|
||||
if(true === config.loginServers.ssh.traceConnections) {
|
||||
debug: sshDebugLine => {
|
||||
if (true === config.loginServers.ssh.traceConnections) {
|
||||
Log.trace(`SSH: ${sshDebugLine}`);
|
||||
}
|
||||
},
|
||||
algorithms : config.loginServers.ssh.algorithms,
|
||||
algorithms: config.loginServers.ssh.algorithms,
|
||||
};
|
||||
|
||||
//
|
||||
@@ -370,19 +404,25 @@ exports.getModule = class SSHServerModule extends LoginServerModule {
|
||||
|
||||
listen(cb) {
|
||||
const config = Config();
|
||||
if(true != config.loginServers.ssh.enabled) {
|
||||
if (true != config.loginServers.ssh.enabled) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const port = parseInt(config.loginServers.ssh.port);
|
||||
if(isNaN(port)) {
|
||||
Log.error( { server : ModuleInfo.name, port : config.loginServers.ssh.port }, 'Cannot load server (invalid port)' );
|
||||
if (isNaN(port)) {
|
||||
Log.error(
|
||||
{ server: ModuleInfo.name, port: config.loginServers.ssh.port },
|
||||
'Cannot load server (invalid port)'
|
||||
);
|
||||
return cb(Errors.Invalid(`Invalid port: ${config.loginServers.ssh.port}`));
|
||||
}
|
||||
|
||||
this.server.listen(port, config.loginServers.ssh.address, err => {
|
||||
if(!err) {
|
||||
Log.info( { server : ModuleInfo.name, port : port }, 'Listening for connections' );
|
||||
if (!err) {
|
||||
Log.info(
|
||||
{ server: ModuleInfo.name, port: port },
|
||||
'Listening for connections'
|
||||
);
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
|
||||
@@ -9,17 +9,17 @@ const { Errors } = require('../../enig_error');
|
||||
const net = require('net');
|
||||
const {
|
||||
TelnetSocket,
|
||||
TelnetSpec: { Options, Commands }
|
||||
TelnetSpec: { Options, Commands },
|
||||
} = require('telnet-socket');
|
||||
const { inherits } = require('util');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Telnet',
|
||||
desc : 'Telnet Server v2',
|
||||
author : 'NuSkooler',
|
||||
isSecure : false,
|
||||
packageName : 'codes.l33t.enigma.telnet.server.v2',
|
||||
};
|
||||
const ModuleInfo = (exports.moduleInfo = {
|
||||
name: 'Telnet',
|
||||
desc: 'Telnet Server v2',
|
||||
author: 'NuSkooler',
|
||||
isSecure: false,
|
||||
packageName: 'codes.l33t.enigma.telnet.server.v2',
|
||||
});
|
||||
|
||||
class TelnetClient {
|
||||
constructor(socket) {
|
||||
@@ -36,14 +36,14 @@ class TelnetClient {
|
||||
this._clientReady();
|
||||
}, 3000);
|
||||
|
||||
this.dataHandler = function(data) {
|
||||
this.dataHandler = function (data) {
|
||||
this.emit('data', data);
|
||||
}.bind(this);
|
||||
|
||||
this.socket.on('data', this.dataHandler);
|
||||
|
||||
this.socket.on('error', err => {
|
||||
this._logDebug({ error : err.message }, 'Socket error');
|
||||
this._logDebug({ error: err.message }, 'Socket error');
|
||||
return this.emit('end');
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ class TelnetClient {
|
||||
});
|
||||
|
||||
this.socket.on('command error', (command, err) => {
|
||||
this._logDebug({ command, error : err.message }, 'Command error');
|
||||
this._logDebug({ command, error: err.message }, 'Command error');
|
||||
});
|
||||
|
||||
this.socket.on('DO', command => {
|
||||
@@ -61,12 +61,12 @@ class TelnetClient {
|
||||
// the banner - some terminals will ask over and over
|
||||
// if we respond to a DO with a WILL, so just don't
|
||||
// do anything...
|
||||
case Options.SGA :
|
||||
case Options.ECHO :
|
||||
case Options.TRANSMIT_BINARY :
|
||||
case Options.SGA:
|
||||
case Options.ECHO:
|
||||
case Options.TRANSMIT_BINARY:
|
||||
break;
|
||||
|
||||
default :
|
||||
default:
|
||||
return this.socket.command(Commands.WONT, command.option);
|
||||
}
|
||||
});
|
||||
@@ -77,15 +77,18 @@ class TelnetClient {
|
||||
|
||||
this.socket.on('WILL', command => {
|
||||
switch (command.option) {
|
||||
case Options.TTYPE :
|
||||
case Options.TTYPE:
|
||||
return this.socket.sb.send.ttype();
|
||||
|
||||
case Options.NEW_ENVIRON :
|
||||
return this.socket.sb.send.new_environ(
|
||||
[ 'ROWS', 'COLUMNS', 'TERM', 'TERM_PROGRAM' ]
|
||||
);
|
||||
case Options.NEW_ENVIRON:
|
||||
return this.socket.sb.send.new_environ([
|
||||
'ROWS',
|
||||
'COLUMNS',
|
||||
'TERM',
|
||||
'TERM_PROGRAM',
|
||||
]);
|
||||
|
||||
default :
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -96,23 +99,29 @@ class TelnetClient {
|
||||
|
||||
this.socket.on('SB', command => {
|
||||
switch (command.option) {
|
||||
case Options.TTYPE :
|
||||
case Options.TTYPE:
|
||||
this.setTermType(command.optionData.ttype);
|
||||
return this._clientReady();
|
||||
|
||||
case Options.NEW_ENVIRON :
|
||||
case Options.NEW_ENVIRON:
|
||||
{
|
||||
this._logDebug(
|
||||
{ vars : command.optionData.vars, uservars : command.optionData.uservars },
|
||||
{
|
||||
vars: command.optionData.vars,
|
||||
uservars: command.optionData.uservars,
|
||||
},
|
||||
'New environment received'
|
||||
);
|
||||
|
||||
// get a value from vars with fallback of user vars
|
||||
const getValue = (name) => {
|
||||
return command.optionData.vars &&
|
||||
const getValue = name => {
|
||||
return (
|
||||
command.optionData.vars &&
|
||||
(command.optionData.vars.find(nv => nv.name === name) ||
|
||||
command.optionData.uservars.find(nv => nv.name === name)
|
||||
);
|
||||
command.optionData.uservars.find(
|
||||
nv => nv.name === name
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
if ('unknown' === this.term.termType) {
|
||||
@@ -124,13 +133,15 @@ class TelnetClient {
|
||||
}
|
||||
|
||||
if (0 === this.term.termHeight || 0 === this.term.termWidth) {
|
||||
const updateTermSize = (what) => {
|
||||
const updateTermSize = what => {
|
||||
const value = parseInt(getValue(what));
|
||||
if (value) {
|
||||
this.term[what === 'ROWS' ? 'termHeight' : 'termWidth'] = value;
|
||||
this.term[
|
||||
what === 'ROWS' ? 'termHeight' : 'termWidth'
|
||||
] = value;
|
||||
|
||||
this._logDebug(
|
||||
{ [ what ] : value, source : 'NEW-ENVIRON' },
|
||||
{ [what]: value, source: 'NEW-ENVIRON' },
|
||||
'Window size updated'
|
||||
);
|
||||
}
|
||||
@@ -142,12 +153,12 @@ class TelnetClient {
|
||||
}
|
||||
break;
|
||||
|
||||
case Options.NAWS :
|
||||
case Options.NAWS:
|
||||
{
|
||||
const { width, height } = command.optionData;
|
||||
|
||||
this.term.termWidth = width;
|
||||
this.term.termHeight = height;
|
||||
this.term.termWidth = width;
|
||||
this.term.termHeight = height;
|
||||
|
||||
if (width) {
|
||||
this.term.env.COLUMNS = width;
|
||||
@@ -158,13 +169,13 @@ class TelnetClient {
|
||||
}
|
||||
|
||||
this._logDebug(
|
||||
{ width, height, source : 'NAWS' },
|
||||
{ width, height, source: 'NAWS' },
|
||||
'Windows size updated'
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default :
|
||||
default:
|
||||
return this._logTrace(command, 'SB');
|
||||
}
|
||||
});
|
||||
@@ -197,8 +208,8 @@ class TelnetClient {
|
||||
}
|
||||
|
||||
banner() {
|
||||
this.socket.dont.echo(); // don't echo characters
|
||||
this.socket.will.echo(); // ...we'll echo them back
|
||||
this.socket.dont.echo(); // don't echo characters
|
||||
this.socket.will.echo(); // ...we'll echo them back
|
||||
|
||||
this.socket.will.sga();
|
||||
this.socket.do.sga();
|
||||
@@ -229,7 +240,7 @@ class TelnetClient {
|
||||
}
|
||||
|
||||
this.clientReadyHandled = true;
|
||||
this.emit('ready', { firstMenu : Config().loginServers.telnet.firstMenu } );
|
||||
this.emit('ready', { firstMenu: Config().loginServers.telnet.firstMenu });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,14 +252,14 @@ exports.getModule = class TelnetServerModule extends LoginServerModule {
|
||||
}
|
||||
|
||||
createServer(cb) {
|
||||
this.server = net.createServer( socket => {
|
||||
this.server = net.createServer(socket => {
|
||||
const client = new TelnetClient(socket);
|
||||
client.banner(); // start negotiations
|
||||
client.banner(); // start negotiations
|
||||
this.handleNewClient(client, socket, ModuleInfo);
|
||||
});
|
||||
|
||||
this.server.on('error', err => {
|
||||
Log.info( { error : err.message }, 'Telnet server error');
|
||||
Log.info({ error: err.message }, 'Telnet server error');
|
||||
});
|
||||
|
||||
return cb(null);
|
||||
@@ -257,18 +268,24 @@ exports.getModule = class TelnetServerModule extends LoginServerModule {
|
||||
listen(cb) {
|
||||
const config = Config();
|
||||
const port = parseInt(config.loginServers.telnet.port);
|
||||
if(isNaN(port)) {
|
||||
Log.error( { server : ModuleInfo.name, port : config.loginServers.telnet.port }, 'Cannot load server (invalid port)' );
|
||||
if (isNaN(port)) {
|
||||
Log.error(
|
||||
{ server: ModuleInfo.name, port: config.loginServers.telnet.port },
|
||||
'Cannot load server (invalid port)'
|
||||
);
|
||||
return cb(Errors.Invalid(`Invalid port: ${config.loginServers.telnet.port}`));
|
||||
}
|
||||
|
||||
this.server.listen(port, config.loginServers.telnet.address, err => {
|
||||
if(!err) {
|
||||
Log.info( { server : ModuleInfo.name, port : port }, 'Listening for connections' );
|
||||
if (!err) {
|
||||
Log.info(
|
||||
{ server: ModuleInfo.name, port: port },
|
||||
'Listening for connections'
|
||||
);
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.TelnetClient = TelnetClient; // WebSockets is a wrapper on top of this
|
||||
exports.TelnetClient = TelnetClient; // WebSockets is a wrapper on top of this
|
||||
|
||||
@@ -2,32 +2,32 @@
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Config = require('../../config.js').get;
|
||||
const TelnetClient = require('./telnet.js').TelnetClient;
|
||||
const Log = require('../../logger.js').log;
|
||||
const Config = require('../../config.js').get;
|
||||
const TelnetClient = require('./telnet.js').TelnetClient;
|
||||
const Log = require('../../logger.js').log;
|
||||
const LoginServerModule = require('../../login_server_module.js');
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const WebSocketServer = require('ws').Server;
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const fs = require('graceful-fs');
|
||||
const _ = require('lodash');
|
||||
const WebSocketServer = require('ws').Server;
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const fs = require('graceful-fs');
|
||||
const { Duplex } = require('stream');
|
||||
const forEachSeries = require('async/forEachSeries');
|
||||
const forEachSeries = require('async/forEachSeries');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'WebSocket',
|
||||
desc : 'WebSocket Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.websocket.server',
|
||||
};
|
||||
const ModuleInfo = (exports.moduleInfo = {
|
||||
name: 'WebSocket',
|
||||
desc: 'WebSocket Server',
|
||||
author: 'NuSkooler',
|
||||
packageName: 'codes.l33t.enigma.websocket.server',
|
||||
});
|
||||
|
||||
class WebSocketClient extends TelnetClient {
|
||||
constructor(ws, req, serverType) {
|
||||
// allow WebSocket to act like a Duplex (socket)
|
||||
const wsDuplex = new class WebSocketDuplex extends Duplex {
|
||||
const wsDuplex = new (class WebSocketDuplex extends Duplex {
|
||||
constructor(ws) {
|
||||
super();
|
||||
this.ws = ws;
|
||||
@@ -42,17 +42,23 @@ class WebSocketClient extends TelnetClient {
|
||||
|
||||
// Support X-Forwarded-For and X-Real-IP headers for proxied connections
|
||||
this.resolvedRemoteAddress =
|
||||
(this.client.proxied && (httpRequest.headers['x-forwarded-for'] || httpRequest.headers['x-real-ip'])) ||
|
||||
(this.client.proxied &&
|
||||
(httpRequest.headers['x-forwarded-for'] ||
|
||||
httpRequest.headers['x-real-ip'])) ||
|
||||
httpRequest.connection.remoteAddress;
|
||||
}
|
||||
|
||||
get remoteAddress() {
|
||||
return this.resolvedRemoteAddress;
|
||||
return this.resolvedRemoteAddress;
|
||||
}
|
||||
|
||||
_write(data, encoding, cb) {
|
||||
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
|
||||
return this.ws.send(data, { binary : true }, cb);
|
||||
cb =
|
||||
cb ||
|
||||
(() => {
|
||||
/* eat it up */
|
||||
}); // handle data writes after close
|
||||
return this.ws.send(data, { binary: true }, cb);
|
||||
}
|
||||
|
||||
_read() {
|
||||
@@ -62,7 +68,7 @@ class WebSocketClient extends TelnetClient {
|
||||
_data(data) {
|
||||
this.push(data);
|
||||
}
|
||||
}(ws);
|
||||
})(ws);
|
||||
|
||||
super(wsDuplex);
|
||||
wsDuplex.setClient(this, req);
|
||||
@@ -85,16 +91,19 @@ class WebSocketClient extends TelnetClient {
|
||||
ws.isConnectionAlive = true;
|
||||
});
|
||||
|
||||
Log.trace( { headers : req.headers }, 'WebSocket connection headers' );
|
||||
Log.trace({ headers: req.headers }, 'WebSocket connection headers');
|
||||
|
||||
//
|
||||
// If the config allows it, look for 'x-forwarded-proto' as "https"
|
||||
// to override |isSecure|
|
||||
//
|
||||
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']}"`);
|
||||
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.proxied = true;
|
||||
} else {
|
||||
this.proxied = false;
|
||||
@@ -105,11 +114,11 @@ class WebSocketClient extends TelnetClient {
|
||||
}
|
||||
|
||||
get isSecure() {
|
||||
return ('secure' === this.serverType || true === this.proxied) ? true : false;
|
||||
return 'secure' === this.serverType || true === this.proxied ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
const WSS_SERVER_TYPES = [ 'insecure', 'secure' ];
|
||||
const WSS_SERVER_TYPES = ['insecure', 'secure'];
|
||||
|
||||
exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||
constructor() {
|
||||
@@ -123,35 +132,39 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||
// * secure (tls) websocket (wss://)
|
||||
//
|
||||
const config = _.get(Config(), 'loginServers.webSocket');
|
||||
if(!_.isObject(config)) {
|
||||
if (!_.isObject(config)) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const wsPort = _.get(config, 'ws.port');
|
||||
const wssPort = _.get(config, 'wss.port');
|
||||
const wsPort = _.get(config, 'ws.port');
|
||||
const wssPort = _.get(config, 'wss.port');
|
||||
|
||||
if(true === _.get(config, 'ws.enabled') && _.isNumber(wsPort)) {
|
||||
const httpServer = http.createServer( (req, resp) => {
|
||||
if (true === _.get(config, 'ws.enabled') && _.isNumber(wsPort)) {
|
||||
const httpServer = http.createServer((req, resp) => {
|
||||
// dummy handler
|
||||
resp.writeHead(200);
|
||||
return resp.end('ENiGMA½ BBS WebSocket Server!');
|
||||
});
|
||||
|
||||
this.insecure = {
|
||||
httpServer : httpServer,
|
||||
wsServer : new WebSocketServer( { server : httpServer } ),
|
||||
httpServer: httpServer,
|
||||
wsServer: new WebSocketServer({ server: httpServer }),
|
||||
};
|
||||
}
|
||||
|
||||
if(_.isObject(config, 'wss') && true === _.get(config, 'wss.enabled') && _.isNumber(wssPort)) {
|
||||
if (
|
||||
_.isObject(config, 'wss') &&
|
||||
true === _.get(config, 'wss.enabled') &&
|
||||
_.isNumber(wssPort)
|
||||
) {
|
||||
const httpServer = https.createServer({
|
||||
key : fs.readFileSync(config.wss.keyPem),
|
||||
cert : fs.readFileSync(config.wss.certPem),
|
||||
key: fs.readFileSync(config.wss.keyPem),
|
||||
cert: fs.readFileSync(config.wss.certPem),
|
||||
});
|
||||
|
||||
this.secure = {
|
||||
httpServer : httpServer,
|
||||
wsServer : new WebSocketServer( { server : httpServer } ),
|
||||
httpServer: httpServer,
|
||||
wsServer: new WebSocketServer({ server: httpServer }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,21 +175,24 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||
//
|
||||
// Send pings every 30s
|
||||
//
|
||||
setInterval( () => {
|
||||
setInterval(() => {
|
||||
WSS_SERVER_TYPES.forEach(serverType => {
|
||||
if(this[serverType]) {
|
||||
if (this[serverType]) {
|
||||
this[serverType].wsServer.clients.forEach(ws => {
|
||||
if(false === ws.isConnectionAlive) {
|
||||
Log.debug('WebSocket connection seems inactive. Terminating.');
|
||||
if (false === ws.isConnectionAlive) {
|
||||
Log.debug(
|
||||
'WebSocket connection seems inactive. Terminating.'
|
||||
);
|
||||
return ws.terminate();
|
||||
}
|
||||
|
||||
ws.isConnectionAlive = false; // pong will reset this
|
||||
ws.isConnectionAlive = false; // pong will reset this
|
||||
|
||||
Log.trace('Ping to remote WebSocket client');
|
||||
try {
|
||||
ws.ping('', false); // false=don't mask
|
||||
} catch(e) { // don't barf on closing state
|
||||
ws.ping('', false); // false=don't mask
|
||||
} catch (e) {
|
||||
// don't barf on closing state
|
||||
/* nothing */
|
||||
}
|
||||
});
|
||||
@@ -184,38 +200,55 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
forEachSeries(WSS_SERVER_TYPES, (serverType, nextServerType) => {
|
||||
const server = this[serverType];
|
||||
if(!server) {
|
||||
return nextServerType(null);
|
||||
}
|
||||
|
||||
const serverName = `${ModuleInfo.name} (${serverType})`;
|
||||
const conf = _.get(Config(), [ 'loginServers', 'webSocket', 'secure' === serverType ? 'wss' : 'ws' ] );
|
||||
const confPort = conf.port;
|
||||
const port = parseInt(confPort);
|
||||
|
||||
if(isNaN(port)) {
|
||||
Log.error( { server : serverName, port : confPort }, 'Cannot load server (invalid port)' );
|
||||
return nextServerType(Errors.Invalid(`Invalid port: ${confPort}`));
|
||||
}
|
||||
|
||||
server.httpServer.listen(port, conf.address, err => {
|
||||
if(err) {
|
||||
return nextServerType(err);
|
||||
forEachSeries(
|
||||
WSS_SERVER_TYPES,
|
||||
(serverType, nextServerType) => {
|
||||
const server = this[serverType];
|
||||
if (!server) {
|
||||
return nextServerType(null);
|
||||
}
|
||||
|
||||
server.wsServer.on('connection', (ws, req) => {
|
||||
const webSocketClient = new WebSocketClient(ws, req, serverType);
|
||||
this.handleNewClient(webSocketClient, webSocketClient.socket, ModuleInfo);
|
||||
});
|
||||
const serverName = `${ModuleInfo.name} (${serverType})`;
|
||||
const conf = _.get(Config(), [
|
||||
'loginServers',
|
||||
'webSocket',
|
||||
'secure' === serverType ? 'wss' : 'ws',
|
||||
]);
|
||||
const confPort = conf.port;
|
||||
const port = parseInt(confPort);
|
||||
|
||||
Log.info( { server : serverName, port : port }, 'Listening for connections' );
|
||||
return nextServerType(null);
|
||||
});
|
||||
},
|
||||
err => {
|
||||
cb(err);
|
||||
});
|
||||
if (isNaN(port)) {
|
||||
Log.error(
|
||||
{ server: serverName, port: confPort },
|
||||
'Cannot load server (invalid port)'
|
||||
);
|
||||
return nextServerType(Errors.Invalid(`Invalid port: ${confPort}`));
|
||||
}
|
||||
|
||||
server.httpServer.listen(port, conf.address, err => {
|
||||
if (err) {
|
||||
return nextServerType(err);
|
||||
}
|
||||
|
||||
server.wsServer.on('connection', (ws, req) => {
|
||||
const webSocketClient = new WebSocketClient(ws, req, serverType);
|
||||
this.handleNewClient(
|
||||
webSocketClient,
|
||||
webSocketClient.socket,
|
||||
ModuleInfo
|
||||
);
|
||||
});
|
||||
|
||||
Log.info(
|
||||
{ server: serverName, port: port },
|
||||
'Listening for connections'
|
||||
);
|
||||
return nextServerType(null);
|
||||
});
|
||||
},
|
||||
err => {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user