mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-08-04 00:41:56 +02:00
ENiGMA 1/2 WILL USE SPACES FROM THIS POINT ON VS TABS
* Really just to make GitHub formatting happy. Arg.
This commit is contained in:
parent
5ddf04c882
commit
e9787cee3e
135 changed files with 27397 additions and 27397 deletions
|
@ -6,14 +6,14 @@ const Log = require('../../logger.js').log;
|
|||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const {
|
||||
splitTextAtTerms,
|
||||
isAnsi,
|
||||
cleanControlCodes
|
||||
splitTextAtTerms,
|
||||
isAnsi,
|
||||
cleanControlCodes
|
||||
} = require('../../string_util.js');
|
||||
const {
|
||||
getMessageConferenceByTag,
|
||||
getMessageAreaByTag,
|
||||
getMessageListForArea,
|
||||
getMessageConferenceByTag,
|
||||
getMessageAreaByTag,
|
||||
getMessageListForArea,
|
||||
} = require('../../message_area.js');
|
||||
const { sortAreasOrConfs } = require('../../conf_area_util.js');
|
||||
const AnsiPrep = require('../../ansi_prep.js');
|
||||
|
@ -26,221 +26,221 @@ const paths = require('path');
|
|||
const moment = require('moment');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Gopher',
|
||||
desc : 'Gopher Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.gopher.server',
|
||||
name : 'Gopher',
|
||||
desc : 'Gopher Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.gopher.server',
|
||||
};
|
||||
|
||||
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',
|
||||
// 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',
|
||||
|
||||
// Non-canonical
|
||||
HtmlFile : 'h',
|
||||
InfoMessage : 'i',
|
||||
SoundFile : 's',
|
||||
// Non-canonical
|
||||
HtmlFile : 'h',
|
||||
InfoMessage : 'i',
|
||||
SoundFile : 's',
|
||||
};
|
||||
|
||||
exports.getModule = class GopherModule extends ServerModule {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
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() {
|
||||
if(!this.enabled) {
|
||||
return;
|
||||
}
|
||||
createServer() {
|
||||
if(!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
this.publicHostname = config.contentServers.gopher.publicHostname;
|
||||
this.publicPort = config.contentServers.gopher.publicPort;
|
||||
const config = Config();
|
||||
this.publicHostname = config.contentServers.gopher.publicHostname;
|
||||
this.publicPort = config.contentServers.gopher.publicPort;
|
||||
|
||||
this.addRoute(/^\/?\r\n$/, this.defaultGenerator);
|
||||
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(/^\/?\r\n$/, this.defaultGenerator);
|
||||
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.server = net.createServer( socket => {
|
||||
socket.setEncoding('ascii');
|
||||
this.server = net.createServer( socket => {
|
||||
socket.setEncoding('ascii');
|
||||
|
||||
socket.on('data', data => {
|
||||
this.routeRequest(data, socket);
|
||||
});
|
||||
socket.on('data', data => {
|
||||
this.routeRequest(data, socket);
|
||||
});
|
||||
|
||||
socket.on('error', err => {
|
||||
if('ECONNRESET' !== err.code) { // normal
|
||||
this.log.trace( { error : err.message }, 'Socket error');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
socket.on('error', err => {
|
||||
if('ECONNRESET' !== err.code) { // normal
|
||||
this.log.trace( { error : err.message }, 'Socket error');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
listen() {
|
||||
if(!this.enabled) {
|
||||
return true; // nothing to do, but not an error
|
||||
}
|
||||
listen() {
|
||||
if(!this.enabled) {
|
||||
return true; // nothing to do, but not an error
|
||||
}
|
||||
|
||||
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 false;
|
||||
}
|
||||
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 false;
|
||||
}
|
||||
|
||||
return this.server.listen(port);
|
||||
}
|
||||
return this.server.listen(port);
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return _.get(Config(), 'contentServers.gopher.enabled', false) && this.isConfigured();
|
||||
}
|
||||
get enabled() {
|
||||
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')) &&
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
addRoute(selectorRegExp, generatorHandler) {
|
||||
if(_.isString(selectorRegExp)) {
|
||||
try {
|
||||
selectorRegExp = new RegExp(`${selectorRegExp}\r\n`);
|
||||
} catch(e) {
|
||||
this.log.warn( { pattern : selectorRegExp }, 'Invalid RegExp for selector' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.routes.set(selectorRegExp, generatorHandler.bind(this));
|
||||
}
|
||||
addRoute(selectorRegExp, generatorHandler) {
|
||||
if(_.isString(selectorRegExp)) {
|
||||
try {
|
||||
selectorRegExp = new RegExp(`${selectorRegExp}\r\n`);
|
||||
} catch(e) {
|
||||
this.log.warn( { pattern : selectorRegExp }, 'Invalid RegExp for selector' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.routes.set(selectorRegExp, generatorHandler.bind(this));
|
||||
}
|
||||
|
||||
routeRequest(selector, socket) {
|
||||
let match;
|
||||
for(let [regex, gen] of this.routes) {
|
||||
match = selector.match(regex);
|
||||
if(match) {
|
||||
return gen(match, res => {
|
||||
return socket.end(`${res}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
this.notFoundGenerator(selector, res => {
|
||||
return socket.end(`${res}`);
|
||||
});
|
||||
}
|
||||
routeRequest(selector, socket) {
|
||||
let match;
|
||||
for(let [regex, gen] of this.routes) {
|
||||
match = selector.match(regex);
|
||||
if(match) {
|
||||
return gen(match, res => {
|
||||
return socket.end(`${res}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
this.notFoundGenerator(selector, res => {
|
||||
return socket.end(`${res}`);
|
||||
});
|
||||
}
|
||||
|
||||
makeItem(itemType, text, selector, hostname, port) {
|
||||
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`;
|
||||
}
|
||||
makeItem(itemType, text, selector, hostname, port) {
|
||||
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`;
|
||||
}
|
||||
|
||||
defaultGenerator(selectorMatch, cb) {
|
||||
this.log.trace( { selector : selectorMatch[0] }, 'Serving default content');
|
||||
defaultGenerator(selectorMatch, cb) {
|
||||
this.log.trace( { selector : selectorMatch[0] }, 'Serving default content');
|
||||
|
||||
let bannerFile = _.get(Config(), 'contentServers.gopher.bannerFile', 'startup_banner.asc');
|
||||
bannerFile = paths.isAbsolute(bannerFile) ? bannerFile : paths.join(__dirname, '../../../misc', bannerFile);
|
||||
fs.readFile(bannerFile, 'utf8', (err, banner) => {
|
||||
if(err) {
|
||||
return cb('You have reached an ENiGMA½ Gopher server!');
|
||||
}
|
||||
let bannerFile = _.get(Config(), 'contentServers.gopher.bannerFile', 'startup_banner.asc');
|
||||
bannerFile = paths.isAbsolute(bannerFile) ? bannerFile : paths.join(__dirname, '../../../misc', bannerFile);
|
||||
fs.readFile(bannerFile, 'utf8', (err, banner) => {
|
||||
if(err) {
|
||||
return cb('You have reached an ENiGMA½ Gopher server!');
|
||||
}
|
||||
|
||||
banner = splitTextAtTerms(banner).map(l => this.makeItem(ItemTypes.InfoMessage, l)).join('');
|
||||
banner += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea');
|
||||
return cb(banner);
|
||||
});
|
||||
}
|
||||
banner = splitTextAtTerms(banner).map(l => this.makeItem(ItemTypes.InfoMessage, l)).join('');
|
||||
banner += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea');
|
||||
return cb(banner);
|
||||
});
|
||||
}
|
||||
|
||||
notFoundGenerator(selector, cb) {
|
||||
this.log.trace( { selector }, 'Serving not found content');
|
||||
return cb('Not found');
|
||||
}
|
||||
notFoundGenerator(selector, cb) {
|
||||
this.log.trace( { selector }, 'Serving not found content');
|
||||
return cb('Not found');
|
||||
}
|
||||
|
||||
isAreaAndConfExposed(confTag, areaTag) {
|
||||
const conf = _.get(Config(), [ 'contentServers', 'gopher', 'messageConferences', confTag ]);
|
||||
return Array.isArray(conf) && conf.includes(areaTag);
|
||||
}
|
||||
isAreaAndConfExposed(confTag, areaTag) {
|
||||
const conf = _.get(Config(), [ 'contentServers', 'gopher', 'messageConferences', confTag ]);
|
||||
return Array.isArray(conf) && conf.includes(areaTag);
|
||||
}
|
||||
|
||||
prepareMessageBody(body, cb) {
|
||||
if(isAnsi(body)) {
|
||||
AnsiPrep(
|
||||
body,
|
||||
{
|
||||
cols : 79, // Gopher std. wants 70, but we'll have to deal with it.
|
||||
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);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return cb(cleanControlCodes(body, { all : true } ));
|
||||
}
|
||||
}
|
||||
prepareMessageBody(body, cb) {
|
||||
if(isAnsi(body)) {
|
||||
AnsiPrep(
|
||||
body,
|
||||
{
|
||||
cols : 79, // Gopher std. wants 70, but we'll have to deal with it.
|
||||
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);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return cb(cleanControlCodes(body, { all : true } ));
|
||||
}
|
||||
}
|
||||
|
||||
shortenSubject(subject) {
|
||||
return _.truncate(subject, { length : 30 } );
|
||||
}
|
||||
shortenSubject(subject) {
|
||||
return _.truncate(subject, { length : 30 } );
|
||||
}
|
||||
|
||||
messageAreaGenerator(selectorMatch, cb) {
|
||||
this.log.trace( { selector : selectorMatch[0] }, 'Serving message area content');
|
||||
//
|
||||
// Selector should be:
|
||||
// /msgarea - list confs
|
||||
// /msgarea/conftag - list areas in conf
|
||||
// /msgarea/conftag/areatag - list messages in area
|
||||
// /msgarea/conftag/areatag/<UUID> - message as text
|
||||
// /msgarea/conftag/areatag/<UUID>_raw - full message as text + headers
|
||||
//
|
||||
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();
|
||||
messageAreaGenerator(selectorMatch, cb) {
|
||||
this.log.trace( { selector : selectorMatch[0] }, 'Serving message area content');
|
||||
//
|
||||
// Selector should be:
|
||||
// /msgarea - list confs
|
||||
// /msgarea/conftag - list areas in conf
|
||||
// /msgarea/conftag/areatag - list messages in area
|
||||
// /msgarea/conftag/areatag/<UUID> - message as text
|
||||
// /msgarea/conftag/areatag/<UUID>_raw - full message as text + headers
|
||||
//
|
||||
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();
|
||||
|
||||
return message.load( { uuid : msgUuid }, err => {
|
||||
if(err) {
|
||||
this.log.debug( { uuid : msgUuid }, 'Attempted access to non-existant message UUID!');
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
return message.load( { uuid : msgUuid }, err => {
|
||||
if(err) {
|
||||
this.log.debug( { uuid : msgUuid }, 'Attempted access to non-existant 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!');
|
||||
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!');
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to message in private area!');
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to message in private area!');
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
this.prepareMessageBody(message.message, msgBody => {
|
||||
const response = `${'-'.repeat(70)}
|
||||
this.prepareMessageBody(message.message, msgBody => {
|
||||
const response = `${'-'.repeat(70)}
|
||||
To : ${message.toUserName}
|
||||
From : ${message.fromUserName}
|
||||
When : ${moment(message.modTimestamp).format('dddd, MMMM Do YYYY, h:mm:ss a (UTCZ)')}
|
||||
|
@ -249,87 +249,87 @@ ID : ${message.messageUuid} (${message.messageId})
|
|||
${'-'.repeat(70)}
|
||||
${msgBody}
|
||||
`;
|
||||
return cb(response);
|
||||
});
|
||||
});
|
||||
} 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);
|
||||
return cb(response);
|
||||
});
|
||||
});
|
||||
} 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);
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to private area!');
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'Area is private'));
|
||||
}
|
||||
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!');
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return getMessageListForArea(null, areaTag, (err, msgList) => {
|
||||
const response = [
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
this.makeItem(ItemTypes.InfoMessage, `Messages in ${area.name}`),
|
||||
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}`
|
||||
))
|
||||
].join('');
|
||||
return getMessageListForArea(null, areaTag, (err, msgList) => {
|
||||
const response = [
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
this.makeItem(ItemTypes.InfoMessage, `Messages in ${area.name}`),
|
||||
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}`
|
||||
))
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
});
|
||||
} 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) {
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
return cb(response);
|
||||
});
|
||||
} 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) {
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
const areas = _.get(sysConfig, [ 'contentServers', 'gopher', 'messageConferences', confTag ], {})
|
||||
.map(areaTag => Object.assign( { areaTag }, getMessageAreaByTag(areaTag)))
|
||||
.filter(area => area && !Message.isPrivateAreaTag(area.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);
|
||||
sortAreasOrConfs(areas);
|
||||
|
||||
const response = [
|
||||
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, `/msgarea/${confTag}/${area.areaTag}`))
|
||||
].join('');
|
||||
const response = [
|
||||
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, `/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
|
||||
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
|
||||
|
||||
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);
|
||||
sortAreasOrConfs(confs);
|
||||
|
||||
const response = [
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
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, `/msgarea/${conf.confTag}`))
|
||||
].join('');
|
||||
const response = [
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
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, `/msgarea/${conf.confTag}`))
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
}
|
||||
}
|
||||
return cb(response);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -15,169 +15,169 @@ const paths = require('path');
|
|||
const mimeTypes = require('mime-types');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Web',
|
||||
desc : 'Web Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.web.server',
|
||||
name : 'Web',
|
||||
desc : 'Web Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.web.server',
|
||||
};
|
||||
|
||||
class Route {
|
||||
constructor(route) {
|
||||
Object.assign(this, route);
|
||||
|
||||
if(this.method) {
|
||||
this.method = this.method.toUpperCase();
|
||||
}
|
||||
constructor(route) {
|
||||
Object.assign(this, route);
|
||||
|
||||
try {
|
||||
this.pathRegExp = new RegExp(this.path);
|
||||
} catch(e) {
|
||||
Log.debug( { route : route }, 'Invalid regular expression for route path' );
|
||||
}
|
||||
}
|
||||
if(this.method) {
|
||||
this.method = this.method.toUpperCase();
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return (
|
||||
this.pathRegExp instanceof RegExp &&
|
||||
( -1 !== [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', ].indexOf(this.method) ) ||
|
||||
try {
|
||||
this.pathRegExp = new RegExp(this.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) ) ||
|
||||
!_.isFunction(this.handler)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
matchesRequest(req) {
|
||||
return req.method === this.method && this.pathRegExp.test(req.url);
|
||||
}
|
||||
matchesRequest(req) {
|
||||
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();
|
||||
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 = {};
|
||||
this.routes = {};
|
||||
|
||||
if(this.isEnabled() && config.contentServers.web.staticRoot) {
|
||||
this.addRoute({
|
||||
method : 'GET',
|
||||
path : '/static/.*$',
|
||||
handler : this.routeStaticFile.bind(this),
|
||||
});
|
||||
}
|
||||
}
|
||||
if(this.isEnabled() && config.contentServers.web.staticRoot) {
|
||||
this.addRoute({
|
||||
method : 'GET',
|
||||
path : '/static/.*$',
|
||||
handler : this.routeStaticFile.bind(this),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
buildUrl(pathAndQuery) {
|
||||
//
|
||||
// Create a URL such as
|
||||
// https://l33t.codes:44512/ + |pathAndQuery|
|
||||
//
|
||||
// Prefer HTTPS over HTTP. Be explicit about the port
|
||||
// only if non-standard. Allow users to override full prefix in config.
|
||||
//
|
||||
const config = Config();
|
||||
if(_.isString(config.contentServers.web.overrideUrlPrefix)) {
|
||||
return `${config.contentServers.web.overrideUrlPrefix}${pathAndQuery}`;
|
||||
}
|
||||
buildUrl(pathAndQuery) {
|
||||
//
|
||||
// Create a URL such as
|
||||
// https://l33t.codes:44512/ + |pathAndQuery|
|
||||
//
|
||||
// Prefer HTTPS over HTTP. Be explicit about the port
|
||||
// only if non-standard. Allow users to override full prefix in config.
|
||||
//
|
||||
const config = Config();
|
||||
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}`;
|
||||
} else {
|
||||
schema = 'http://';
|
||||
port = (80 === config.contentServers.web.http.port) ?
|
||||
'' :
|
||||
`:${config.contentServers.web.http.port}`;
|
||||
}
|
||||
|
||||
return `${schema}${config.contentServers.web.domain}${port}${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}`;
|
||||
} else {
|
||||
schema = 'http://';
|
||||
port = (80 === config.contentServers.web.http.port) ?
|
||||
'' :
|
||||
`:${config.contentServers.web.http.port}`;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.enableHttp || this.enableHttps;
|
||||
}
|
||||
return `${schema}${config.contentServers.web.domain}${port}${pathAndQuery}`;
|
||||
}
|
||||
|
||||
createServer() {
|
||||
if(this.enableHttp) {
|
||||
this.httpServer = http.createServer( (req, resp) => this.routeRequest(req, resp) );
|
||||
}
|
||||
isEnabled() {
|
||||
return this.enableHttp || this.enableHttps;
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
if(this.enableHttps) {
|
||||
const options = {
|
||||
cert : fs.readFileSync(config.contentServers.web.https.certPem),
|
||||
key : fs.readFileSync(config.contentServers.web.https.keyPem),
|
||||
};
|
||||
createServer() {
|
||||
if(this.enableHttp) {
|
||||
this.httpServer = http.createServer( (req, resp) => this.routeRequest(req, resp) );
|
||||
}
|
||||
|
||||
// additional options
|
||||
Object.assign(options, config.contentServers.web.https.options || {} );
|
||||
const config = Config();
|
||||
if(this.enableHttps) {
|
||||
const options = {
|
||||
cert : fs.readFileSync(config.contentServers.web.https.certPem),
|
||||
key : fs.readFileSync(config.contentServers.web.https.keyPem),
|
||||
};
|
||||
|
||||
this.httpsServer = https.createServer(options, (req, resp) => this.routeRequest(req, resp) );
|
||||
}
|
||||
}
|
||||
// additional options
|
||||
Object.assign(options, config.contentServers.web.https.options || {} );
|
||||
|
||||
listen() {
|
||||
let ok = true;
|
||||
this.httpsServer = https.createServer(options, (req, resp) => this.routeRequest(req, resp) );
|
||||
}
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
[ 'http', 'https' ].forEach(service => {
|
||||
const name = `${service}Server`;
|
||||
if(this[name]) {
|
||||
const port = parseInt(config.contentServers.web[service].port);
|
||||
if(isNaN(port)) {
|
||||
ok = false;
|
||||
return Log.warn( { port : config.contentServers.web[service].port, server : ModuleInfo.name }, `Invalid port (${service})` );
|
||||
}
|
||||
return this[name].listen(port);
|
||||
}
|
||||
});
|
||||
listen() {
|
||||
let ok = true;
|
||||
|
||||
return ok;
|
||||
}
|
||||
const config = Config();
|
||||
[ 'http', 'https' ].forEach(service => {
|
||||
const name = `${service}Server`;
|
||||
if(this[name]) {
|
||||
const port = parseInt(config.contentServers.web[service].port);
|
||||
if(isNaN(port)) {
|
||||
ok = false;
|
||||
return Log.warn( { port : config.contentServers.web[service].port, server : ModuleInfo.name }, `Invalid port (${service})` );
|
||||
}
|
||||
return this[name].listen(port);
|
||||
}
|
||||
});
|
||||
|
||||
addRoute(route) {
|
||||
route = new Route(route);
|
||||
return ok;
|
||||
}
|
||||
|
||||
if(!route.isValid()) {
|
||||
Log.warn( { route : route }, 'Cannot add route: missing or invalid required members' );
|
||||
return false;
|
||||
}
|
||||
addRoute(route) {
|
||||
route = new Route(route);
|
||||
|
||||
const routeKey = route.getRouteKey();
|
||||
if(routeKey in this.routes) {
|
||||
Log.warn( { route : route, routeKey : routeKey }, 'Cannot add route: duplicate method/path combination exists' );
|
||||
return false;
|
||||
}
|
||||
if(!route.isValid()) {
|
||||
Log.warn( { route : route }, 'Cannot add route: missing or invalid required members' );
|
||||
return false;
|
||||
}
|
||||
|
||||
this.routes[routeKey] = route;
|
||||
return true;
|
||||
}
|
||||
const routeKey = route.getRouteKey();
|
||||
if(routeKey in this.routes) {
|
||||
Log.warn( { route : route, routeKey : routeKey }, 'Cannot add route: duplicate method/path combination exists' );
|
||||
return false;
|
||||
}
|
||||
|
||||
routeRequest(req, resp) {
|
||||
const route = _.find(this.routes, r => r.matchesRequest(req) );
|
||||
this.routes[routeKey] = route;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!route && '/' === req.url) {
|
||||
return this.routeIndex(req, resp);
|
||||
}
|
||||
routeRequest(req, resp) {
|
||||
const route = _.find(this.routes, r => r.matchesRequest(req) );
|
||||
|
||||
return route ? route.handler(req, resp) : this.accessDenied(resp);
|
||||
}
|
||||
if(!route && '/' === req.url) {
|
||||
return this.routeIndex(req, resp);
|
||||
}
|
||||
|
||||
respondWithError(resp, code, bodyText, title) {
|
||||
const customErrorPage = paths.join(Config().contentServers.web.staticRoot, `${code}.html`);
|
||||
return route ? route.handler(req, resp) : this.accessDenied(resp);
|
||||
}
|
||||
|
||||
fs.readFile(customErrorPage, 'utf8', (err, data) => {
|
||||
resp.writeHead(code, { 'Content-Type' : 'text/html' } );
|
||||
respondWithError(resp, code, bodyText, title) {
|
||||
const customErrorPage = paths.join(Config().contentServers.web.staticRoot, `${code}.html`);
|
||||
|
||||
if(err) {
|
||||
return resp.end(`<!doctype html>
|
||||
fs.readFile(customErrorPage, 'utf8', (err, data) => {
|
||||
resp.writeHead(code, { 'Content-Type' : 'text/html' } );
|
||||
|
||||
if(err) {
|
||||
return resp.end(`<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -190,74 +190,74 @@ exports.getModule = class WebServerModule extends ServerModule {
|
|||
</article>
|
||||
</body>
|
||||
</html>`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return resp.end(data);
|
||||
});
|
||||
}
|
||||
return resp.end(data);
|
||||
});
|
||||
}
|
||||
|
||||
accessDenied(resp) {
|
||||
return this.respondWithError(resp, 401, 'Access denied.', 'Access Denied');
|
||||
}
|
||||
accessDenied(resp) {
|
||||
return this.respondWithError(resp, 401, 'Access denied.', 'Access Denied');
|
||||
}
|
||||
|
||||
fileNotFound(resp) {
|
||||
return this.respondWithError(resp, 404, 'File not found.', 'File Not Found');
|
||||
}
|
||||
fileNotFound(resp) {
|
||||
return this.respondWithError(resp, 404, 'File not found.', 'File Not Found');
|
||||
}
|
||||
|
||||
routeIndex(req, resp) {
|
||||
const filePath = paths.join(Config().contentServers.web.staticRoot, 'index.html');
|
||||
routeIndex(req, resp) {
|
||||
const filePath = paths.join(Config().contentServers.web.staticRoot, 'index.html');
|
||||
|
||||
return this.returnStaticPage(filePath, resp);
|
||||
}
|
||||
return this.returnStaticPage(filePath, resp);
|
||||
}
|
||||
|
||||
routeStaticFile(req, resp) {
|
||||
const fileName = req.url.substr(req.url.indexOf('/', 1));
|
||||
const filePath = paths.join(Config().contentServers.web.staticRoot, fileName);
|
||||
routeStaticFile(req, resp) {
|
||||
const fileName = req.url.substr(req.url.indexOf('/', 1));
|
||||
const filePath = paths.join(Config().contentServers.web.staticRoot, fileName);
|
||||
|
||||
return this.returnStaticPage(filePath, resp);
|
||||
}
|
||||
return this.returnStaticPage(filePath, resp);
|
||||
}
|
||||
|
||||
returnStaticPage(filePath, resp) {
|
||||
const self = this;
|
||||
returnStaticPage(filePath, resp) {
|
||||
const self = this;
|
||||
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if(err || !stats.isFile()) {
|
||||
return self.fileNotFound(resp);
|
||||
}
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if(err || !stats.isFile()) {
|
||||
return self.fileNotFound(resp);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Content-Type' : mimeTypes.contentType(paths.basename(filePath)) || mimeTypes.contentType('.bin'),
|
||||
'Content-Length' : stats.size,
|
||||
};
|
||||
const headers = {
|
||||
'Content-Type' : mimeTypes.contentType(paths.basename(filePath)) || mimeTypes.contentType('.bin'),
|
||||
'Content-Length' : stats.size,
|
||||
};
|
||||
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
resp.writeHead(200, headers);
|
||||
return readStream.pipe(resp);
|
||||
});
|
||||
}
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
resp.writeHead(200, headers);
|
||||
return readStream.pipe(resp);
|
||||
});
|
||||
}
|
||||
|
||||
routeTemplateFilePage(templatePath, preprocessCallback, resp) {
|
||||
const self = this;
|
||||
routeTemplateFilePage(templatePath, preprocessCallback, resp) {
|
||||
const self = this;
|
||||
|
||||
fs.readFile(templatePath, 'utf8', (err, templateData) => {
|
||||
if(err) {
|
||||
return self.fileNotFound(resp);
|
||||
}
|
||||
fs.readFile(templatePath, 'utf8', (err, templateData) => {
|
||||
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');
|
||||
}
|
||||
preprocessCallback(templateData, (err, finalPage, contentType) => {
|
||||
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,
|
||||
};
|
||||
const headers = {
|
||||
'Content-Type' : contentType || mimeTypes.contentType('.html'),
|
||||
'Content-Length' : finalPage.length,
|
||||
};
|
||||
|
||||
resp.writeHead(200, headers);
|
||||
return resp.end(finalPage);
|
||||
});
|
||||
});
|
||||
}
|
||||
resp.writeHead(200, headers);
|
||||
return resp.end(finalPage);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,215 +19,215 @@ 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',
|
||||
name : 'SSH',
|
||||
desc : 'SSH Server',
|
||||
author : 'NuSkooler',
|
||||
isSecure : true,
|
||||
packageName : 'codes.l33t.enigma.ssh.server',
|
||||
};
|
||||
|
||||
function SSHClient(clientConn) {
|
||||
baseClient.Client.apply(this, arguments);
|
||||
baseClient.Client.apply(this, arguments);
|
||||
|
||||
//
|
||||
// WARNING: Until we have emit 'ready', self.input, and self.output and
|
||||
// not yet defined!
|
||||
//
|
||||
//
|
||||
// WARNING: Until we have emit 'ready', self.input, and self.output and
|
||||
// not yet defined!
|
||||
//
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
let loginAttempts = 0;
|
||||
let loginAttempts = 0;
|
||||
|
||||
clientConn.on('authentication', function authAttempt(ctx) {
|
||||
const username = ctx.username || '';
|
||||
const password = ctx.password || '';
|
||||
clientConn.on('authentication', function authAttempt(ctx) {
|
||||
const username = ctx.username || '';
|
||||
const password = ctx.password || '';
|
||||
|
||||
const config = Config();
|
||||
self.isNewUser = (config.users.newUserNames || []).indexOf(username) > -1;
|
||||
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');
|
||||
|
||||
function terminateConnection() {
|
||||
ctx.reject();
|
||||
return clientConn.end();
|
||||
}
|
||||
function terminateConnection() {
|
||||
ctx.reject();
|
||||
return clientConn.end();
|
||||
}
|
||||
|
||||
function alreadyLoggedIn(username) {
|
||||
ctx.prompt(`${username} is already connected to the system. Terminating connection.\n(Press any key to continue)`);
|
||||
return terminateConnection();
|
||||
}
|
||||
function alreadyLoggedIn(username) {
|
||||
ctx.prompt(`${username} is already connected to the system. Terminating connection.\n(Press any key to continue)`);
|
||||
return terminateConnection();
|
||||
}
|
||||
|
||||
//
|
||||
// If the system is open and |isNewUser| is true, the login
|
||||
// sequence is hijacked in order to start the applicaiton process.
|
||||
//
|
||||
if(false === config.general.closedSystem && self.isNewUser) {
|
||||
return ctx.accept();
|
||||
}
|
||||
//
|
||||
// If the system is open and |isNewUser| is true, the login
|
||||
// sequence is hijacked in order to start the applicaiton process.
|
||||
//
|
||||
if(false === config.general.closedSystem && self.isNewUser) {
|
||||
return ctx.accept();
|
||||
}
|
||||
|
||||
if(username.length > 0 && password.length > 0) {
|
||||
loginAttempts += 1;
|
||||
if(username.length > 0 && password.length > 0) {
|
||||
loginAttempts += 1;
|
||||
|
||||
userLogin(self, ctx.username, ctx.password, function authResult(err) {
|
||||
if(err) {
|
||||
if(err.existingConn) {
|
||||
return alreadyLoggedIn(username);
|
||||
}
|
||||
userLogin(self, ctx.username, ctx.password, function authResult(err) {
|
||||
if(err) {
|
||||
if(err.existingConn) {
|
||||
return alreadyLoggedIn(username);
|
||||
}
|
||||
|
||||
return ctx.reject(SSHClient.ValidAuthMethods);
|
||||
}
|
||||
return ctx.reject(SSHClient.ValidAuthMethods);
|
||||
}
|
||||
|
||||
ctx.accept();
|
||||
});
|
||||
} else {
|
||||
if(-1 === SSHClient.ValidAuthMethods.indexOf(ctx.method)) {
|
||||
return ctx.reject(SSHClient.ValidAuthMethods);
|
||||
}
|
||||
ctx.accept();
|
||||
});
|
||||
} else {
|
||||
if(-1 === SSHClient.ValidAuthMethods.indexOf(ctx.method)) {
|
||||
return ctx.reject(SSHClient.ValidAuthMethods);
|
||||
}
|
||||
|
||||
if(0 === username.length) {
|
||||
// :TODO: can we display something here?
|
||||
return ctx.reject();
|
||||
}
|
||||
if(0 === username.length) {
|
||||
// :TODO: can we display something here?
|
||||
return ctx.reject();
|
||||
}
|
||||
|
||||
const interactivePrompt = { prompt : `${ctx.username}'s password: `, echo : false };
|
||||
const interactivePrompt = { prompt : `${ctx.username}'s password: `, echo : false };
|
||||
|
||||
ctx.prompt(interactivePrompt, function retryPrompt(answers) {
|
||||
loginAttempts += 1;
|
||||
ctx.prompt(interactivePrompt, function retryPrompt(answers) {
|
||||
loginAttempts += 1;
|
||||
|
||||
userLogin(self, username, (answers[0] || ''), err => {
|
||||
if(err) {
|
||||
if(err.existingConn) {
|
||||
return alreadyLoggedIn(username);
|
||||
}
|
||||
userLogin(self, username, (answers[0] || ''), err => {
|
||||
if(err) {
|
||||
if(err.existingConn) {
|
||||
return alreadyLoggedIn(username);
|
||||
}
|
||||
|
||||
if(loginAttempts >= config.general.loginAttempts) {
|
||||
return terminateConnection();
|
||||
}
|
||||
if(loginAttempts >= config.general.loginAttempts) {
|
||||
return terminateConnection();
|
||||
}
|
||||
|
||||
const artOpts = {
|
||||
client : self,
|
||||
name : 'SSHPMPT.ASC',
|
||||
readSauce : false,
|
||||
};
|
||||
const artOpts = {
|
||||
client : self,
|
||||
name : 'SSHPMPT.ASC',
|
||||
readSauce : false,
|
||||
};
|
||||
|
||||
theme.getThemeArt(artOpts, (err, artInfo) => {
|
||||
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!)';
|
||||
theme.getThemeArt(artOpts, (err, artInfo) => {
|
||||
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!)';
|
||||
|
||||
interactivePrompt.prompt = `Access denied\n${stringFormat(artInfo.data, { newUserNames : newUserNameList })}\n${ctx.username}'s password'`;
|
||||
}
|
||||
return ctx.prompt(interactivePrompt, retryPrompt);
|
||||
});
|
||||
} else {
|
||||
ctx.accept();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
interactivePrompt.prompt = `Access denied\n${stringFormat(artInfo.data, { newUserNames : newUserNameList })}\n${ctx.username}'s password'`;
|
||||
}
|
||||
return ctx.prompt(interactivePrompt, retryPrompt);
|
||||
});
|
||||
} else {
|
||||
ctx.accept();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.dataHandler = function(data) {
|
||||
self.emit('data', data);
|
||||
};
|
||||
this.dataHandler = function(data) {
|
||||
self.emit('data', data);
|
||||
};
|
||||
|
||||
this.updateTermInfo = function(info) {
|
||||
//
|
||||
// From ssh2 docs:
|
||||
// "rows and cols override width and height when rows and cols are non-zero."
|
||||
//
|
||||
let termHeight;
|
||||
let termWidth;
|
||||
this.updateTermInfo = function(info) {
|
||||
//
|
||||
// From ssh2 docs:
|
||||
// "rows and cols override width and height when rows and cols are non-zero."
|
||||
//
|
||||
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));
|
||||
assert(_.isObject(self.term));
|
||||
|
||||
//
|
||||
// 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) {
|
||||
self.term.termHeight = termHeight;
|
||||
self.term.termWidth = termWidth;
|
||||
//
|
||||
// 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) {
|
||||
self.term.termHeight = termHeight;
|
||||
self.term.termWidth = termWidth;
|
||||
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
}
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
}
|
||||
|
||||
if(_.isString(info.term) && info.term.length > 0 && 'unknown' === self.term.termType) {
|
||||
self.setTermType(info.term);
|
||||
}
|
||||
};
|
||||
if(_.isString(info.term) && info.term.length > 0 && 'unknown' === self.term.termType) {
|
||||
self.setTermType(info.term);
|
||||
}
|
||||
};
|
||||
|
||||
clientConn.once('ready', function clientReady() {
|
||||
self.log.info('SSH authentication success');
|
||||
clientConn.once('ready', function clientReady() {
|
||||
self.log.info('SSH authentication success');
|
||||
|
||||
clientConn.on('session', accept => {
|
||||
clientConn.on('session', accept => {
|
||||
|
||||
const session = accept();
|
||||
const session = accept();
|
||||
|
||||
session.on('pty', function pty(accept, reject, info) {
|
||||
self.log.debug(info, 'SSH pty event');
|
||||
session.on('pty', function pty(accept, reject, info) {
|
||||
self.log.debug(info, 'SSH pty event');
|
||||
|
||||
if(_.isFunction(accept)) {
|
||||
accept();
|
||||
}
|
||||
if(_.isFunction(accept)) {
|
||||
accept();
|
||||
}
|
||||
|
||||
if(self.input) { // do we have I/O?
|
||||
self.updateTermInfo(info);
|
||||
} else {
|
||||
self.cachedTermInfo = info;
|
||||
}
|
||||
});
|
||||
if(self.input) { // do we have I/O?
|
||||
self.updateTermInfo(info);
|
||||
} else {
|
||||
self.cachedTermInfo = info;
|
||||
}
|
||||
});
|
||||
|
||||
session.on('shell', accept => {
|
||||
self.log.debug('SSH shell event');
|
||||
session.on('shell', accept => {
|
||||
self.log.debug('SSH shell event');
|
||||
|
||||
const channel = accept();
|
||||
const channel = accept();
|
||||
|
||||
self.setInputOutput(channel.stdin, channel.stdout);
|
||||
self.setInputOutput(channel.stdin, channel.stdout);
|
||||
|
||||
channel.stdin.on('data', self.dataHandler);
|
||||
channel.stdin.on('data', self.dataHandler);
|
||||
|
||||
if(self.cachedTermInfo) {
|
||||
self.updateTermInfo(self.cachedTermInfo);
|
||||
delete 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 } );
|
||||
});
|
||||
// we're ready!
|
||||
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');
|
||||
session.on('window-change', (accept, reject, info) => {
|
||||
self.log.debug(info, 'SSH window-change event');
|
||||
|
||||
if(self.input) {
|
||||
self.updateTermInfo(info);
|
||||
} else {
|
||||
self.cachedTermInfo = info;
|
||||
}
|
||||
});
|
||||
if(self.input) {
|
||||
self.updateTermInfo(info);
|
||||
} else {
|
||||
self.cachedTermInfo = info;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
clientConn.on('end', () => {
|
||||
self.emit('end'); // remove client connection/tracking
|
||||
});
|
||||
clientConn.on('end', () => {
|
||||
self.emit('end'); // remove client connection/tracking
|
||||
});
|
||||
|
||||
clientConn.on('error', err => {
|
||||
self.log.warn( { error : err.message, code : err.code }, 'SSH connection error');
|
||||
});
|
||||
clientConn.on('error', err => {
|
||||
self.log.warn( { error : err.message, code : err.code }, 'SSH connection error');
|
||||
});
|
||||
}
|
||||
|
||||
util.inherits(SSHClient, baseClient.Client);
|
||||
|
@ -235,47 +235,47 @@ util.inherits(SSHClient, baseClient.Client);
|
|||
SSHClient.ValidAuthMethods = [ 'password', 'keyboard-interactive' ];
|
||||
|
||||
exports.getModule = class SSHServerModule extends LoginServerModule {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
createServer() {
|
||||
const config = Config();
|
||||
const serverConf = {
|
||||
hostKeys : [
|
||||
{
|
||||
key : fs.readFileSync(config.loginServers.ssh.privateKeyPem),
|
||||
passphrase : config.loginServers.ssh.privateKeyPass,
|
||||
}
|
||||
],
|
||||
ident : 'enigma-bbs-' + enigVersion + '-srv',
|
||||
createServer() {
|
||||
const config = Config();
|
||||
const serverConf = {
|
||||
hostKeys : [
|
||||
{
|
||||
key : fs.readFileSync(config.loginServers.ssh.privateKeyPem),
|
||||
passphrase : config.loginServers.ssh.privateKeyPass,
|
||||
}
|
||||
],
|
||||
ident : 'enigma-bbs-' + enigVersion + '-srv',
|
||||
|
||||
// Note that sending 'banner' breaks at least EtherTerm!
|
||||
debug : (sshDebugLine) => {
|
||||
if(true === config.loginServers.ssh.traceConnections) {
|
||||
Log.trace(`SSH: ${sshDebugLine}`);
|
||||
}
|
||||
},
|
||||
algorithms: { compress: ['none'] },
|
||||
};
|
||||
// Note that sending 'banner' breaks at least EtherTerm!
|
||||
debug : (sshDebugLine) => {
|
||||
if(true === config.loginServers.ssh.traceConnections) {
|
||||
Log.trace(`SSH: ${sshDebugLine}`);
|
||||
}
|
||||
},
|
||||
algorithms: { compress: ['none'] },
|
||||
};
|
||||
|
||||
this.server = ssh2.Server(serverConf);
|
||||
this.server.on('connection', (conn, info) => {
|
||||
Log.info(info, 'New SSH connection');
|
||||
this.handleNewClient(new SSHClient(conn), conn._sock, ModuleInfo);
|
||||
});
|
||||
}
|
||||
this.server = ssh2.Server(serverConf);
|
||||
this.server.on('connection', (conn, info) => {
|
||||
Log.info(info, 'New SSH connection');
|
||||
this.handleNewClient(new SSHClient(conn), conn._sock, ModuleInfo);
|
||||
});
|
||||
}
|
||||
|
||||
listen() {
|
||||
const config = Config();
|
||||
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)' );
|
||||
return false;
|
||||
}
|
||||
listen() {
|
||||
const config = Config();
|
||||
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)' );
|
||||
return false;
|
||||
}
|
||||
|
||||
this.server.listen(port);
|
||||
Log.info( { server : ModuleInfo.name, port : port }, 'Listening for connections' );
|
||||
return true;
|
||||
}
|
||||
this.server.listen(port);
|
||||
Log.info( { server : ModuleInfo.name, port : port }, 'Listening for connections' );
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -16,93 +16,93 @@ const fs = require('graceful-fs');
|
|||
const Writable = require('stream');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'WebSocket',
|
||||
desc : 'WebSocket Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.websocket.server',
|
||||
name : 'WebSocket',
|
||||
desc : 'WebSocket Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.websocket.server',
|
||||
};
|
||||
|
||||
function WebSocketClient(ws, req, serverType) {
|
||||
|
||||
Object.defineProperty(this, 'isSecure', {
|
||||
get : () => ('secure' === serverType || true === this.proxied) ? true : false,
|
||||
});
|
||||
Object.defineProperty(this, 'isSecure', {
|
||||
get : () => ('secure' === serverType || true === this.proxied) ? true : false,
|
||||
});
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
this.dataHandler = function(data) {
|
||||
self.socketBridge.emit('data', data);
|
||||
};
|
||||
this.dataHandler = function(data) {
|
||||
self.socketBridge.emit('data', data);
|
||||
};
|
||||
|
||||
//
|
||||
// This bridge makes accessible various calls that client sub classes
|
||||
// want to access on I/O socket
|
||||
//
|
||||
this.socketBridge = new class SocketBridge extends Writable {
|
||||
constructor(ws) {
|
||||
super();
|
||||
this.ws = ws;
|
||||
}
|
||||
//
|
||||
// This bridge makes accessible various calls that client sub classes
|
||||
// want to access on I/O socket
|
||||
//
|
||||
this.socketBridge = new class SocketBridge extends Writable {
|
||||
constructor(ws) {
|
||||
super();
|
||||
this.ws = ws;
|
||||
}
|
||||
|
||||
end() {
|
||||
return ws.close();
|
||||
}
|
||||
end() {
|
||||
return ws.close();
|
||||
}
|
||||
|
||||
write(data, cb) {
|
||||
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
|
||||
write(data, cb) {
|
||||
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
|
||||
|
||||
return this.ws.send(data, { binary : true }, cb);
|
||||
}
|
||||
return this.ws.send(data, { binary : true }, cb);
|
||||
}
|
||||
|
||||
// we need to fake some streaming work
|
||||
unpipe() {
|
||||
Log.trace('WebSocket SocketBridge unpipe()');
|
||||
}
|
||||
// we need to fake some streaming work
|
||||
unpipe() {
|
||||
Log.trace('WebSocket SocketBridge unpipe()');
|
||||
}
|
||||
|
||||
resume() {
|
||||
Log.trace('WebSocket SocketBridge resume()');
|
||||
}
|
||||
resume() {
|
||||
Log.trace('WebSocket SocketBridge resume()');
|
||||
}
|
||||
|
||||
get 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);
|
||||
get 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);
|
||||
|
||||
ws.on('message', this.dataHandler);
|
||||
ws.on('message', this.dataHandler);
|
||||
|
||||
ws.on('close', () => {
|
||||
// we'll remove client connection which will in turn end() via our SocketBridge above
|
||||
return this.emit('end');
|
||||
});
|
||||
ws.on('close', () => {
|
||||
// we'll remove client connection which will in turn end() via our SocketBridge above
|
||||
return this.emit('end');
|
||||
});
|
||||
|
||||
//
|
||||
// Montior connection status with ping/pong
|
||||
//
|
||||
ws.on('pong', () => {
|
||||
Log.trace(`Pong from ${this.socketBridge.remoteAddress}`);
|
||||
ws.isConnectionAlive = true;
|
||||
});
|
||||
//
|
||||
// Montior connection status with ping/pong
|
||||
//
|
||||
ws.on('pong', () => {
|
||||
Log.trace(`Pong from ${this.socketBridge.remoteAddress}`);
|
||||
ws.isConnectionAlive = true;
|
||||
});
|
||||
|
||||
TelnetClient.call(this, this.socketBridge, this.socketBridge);
|
||||
TelnetClient.call(this, this.socketBridge, this.socketBridge);
|
||||
|
||||
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') &&
|
||||
//
|
||||
// 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']}"`);
|
||||
this.proxied = true;
|
||||
} else {
|
||||
this.proxied = false;
|
||||
}
|
||||
{
|
||||
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
|
||||
this.banner();
|
||||
// start handshake process
|
||||
this.banner();
|
||||
}
|
||||
|
||||
require('util').inherits(WebSocketClient, TelnetClient);
|
||||
|
@ -110,101 +110,101 @@ require('util').inherits(WebSocketClient, TelnetClient);
|
|||
const WSS_SERVER_TYPES = [ 'insecure', 'secure' ];
|
||||
|
||||
exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
createServer() {
|
||||
//
|
||||
// We will actually create up to two servers:
|
||||
// * insecure websocket (ws://)
|
||||
// * secure (tls) websocket (wss://)
|
||||
//
|
||||
const config = _.get(Config(), 'loginServers.webSocket');
|
||||
if(!_.isObject(config)) {
|
||||
return;
|
||||
}
|
||||
createServer() {
|
||||
//
|
||||
// We will actually create up to two servers:
|
||||
// * insecure websocket (ws://)
|
||||
// * secure (tls) websocket (wss://)
|
||||
//
|
||||
const config = _.get(Config(), 'loginServers.webSocket');
|
||||
if(!_.isObject(config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) => {
|
||||
// dummy handler
|
||||
resp.writeHead(200);
|
||||
return resp.end('ENiGMA½ BBS WebSocket Server!');
|
||||
});
|
||||
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 } ),
|
||||
};
|
||||
}
|
||||
this.insecure = {
|
||||
httpServer : httpServer,
|
||||
wsServer : new WebSocketServer( { server : httpServer } ),
|
||||
};
|
||||
}
|
||||
|
||||
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),
|
||||
});
|
||||
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),
|
||||
});
|
||||
|
||||
this.secure = {
|
||||
httpServer : httpServer,
|
||||
wsServer : new WebSocketServer( { server : httpServer } ),
|
||||
};
|
||||
}
|
||||
}
|
||||
this.secure = {
|
||||
httpServer : httpServer,
|
||||
wsServer : new WebSocketServer( { server : httpServer } ),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
listen() {
|
||||
WSS_SERVER_TYPES.forEach(serverType => {
|
||||
const server = this[serverType];
|
||||
if(!server) {
|
||||
return;
|
||||
}
|
||||
listen() {
|
||||
WSS_SERVER_TYPES.forEach(serverType => {
|
||||
const server = this[serverType];
|
||||
if(!server) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverName = `${ModuleInfo.name} (${serverType})`;
|
||||
const port = parseInt(_.get(Config(), [ 'loginServers', 'webSocket', 'secure' === serverType ? 'wss' : 'ws', 'port' ] ));
|
||||
const serverName = `${ModuleInfo.name} (${serverType})`;
|
||||
const port = parseInt(_.get(Config(), [ 'loginServers', 'webSocket', 'secure' === serverType ? 'wss' : 'ws', 'port' ] ));
|
||||
|
||||
if(isNaN(port)) {
|
||||
Log.error( { server : serverName, port : port }, 'Cannot load server (invalid port)' );
|
||||
return;
|
||||
}
|
||||
if(isNaN(port)) {
|
||||
Log.error( { server : serverName, port : port }, 'Cannot load server (invalid port)' );
|
||||
return;
|
||||
}
|
||||
|
||||
server.httpServer.listen(port);
|
||||
server.httpServer.listen(port);
|
||||
|
||||
server.wsServer.on('connection', (ws, req) => {
|
||||
const webSocketClient = new WebSocketClient(ws, req, serverType);
|
||||
this.handleNewClient(webSocketClient, webSocketClient.socketBridge, ModuleInfo);
|
||||
});
|
||||
server.wsServer.on('connection', (ws, req) => {
|
||||
const webSocketClient = new WebSocketClient(ws, req, serverType);
|
||||
this.handleNewClient(webSocketClient, webSocketClient.socketBridge, ModuleInfo);
|
||||
});
|
||||
|
||||
Log.info( { server : serverName, port : port }, 'Listening for connections' );
|
||||
});
|
||||
Log.info( { server : serverName, port : port }, 'Listening for connections' );
|
||||
});
|
||||
|
||||
//
|
||||
// Send pings every 30s
|
||||
//
|
||||
setInterval( () => {
|
||||
WSS_SERVER_TYPES.forEach(serverType => {
|
||||
if(this[serverType]) {
|
||||
this[serverType].wsServer.clients.forEach(ws => {
|
||||
if(false === ws.isConnectionAlive) {
|
||||
Log.debug('WebSocket connection seems inactive. Terminating.');
|
||||
return ws.terminate();
|
||||
}
|
||||
//
|
||||
// Send pings every 30s
|
||||
//
|
||||
setInterval( () => {
|
||||
WSS_SERVER_TYPES.forEach(serverType => {
|
||||
if(this[serverType]) {
|
||||
this[serverType].wsServer.clients.forEach(ws => {
|
||||
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');
|
||||
return ws.ping('', false); // false=don't mask
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 30000);
|
||||
Log.trace('Ping to remote WebSocket client');
|
||||
return ws.ping('', false); // false=don't mask
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
webSocketConnection(conn) {
|
||||
const webSocketClient = new WebSocketClient(conn);
|
||||
this.handleNewClient(webSocketClient, webSocketClient.socketShim, ModuleInfo);
|
||||
}
|
||||
webSocketConnection(conn) {
|
||||
const webSocketClient = new WebSocketClient(conn);
|
||||
this.handleNewClient(webSocketClient, webSocketClient.socketShim, ModuleInfo);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue