mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-08-01 15:34:30 +02:00
Pardon the noise. More tab to space conversion!
This commit is contained in:
parent
c3635bb26b
commit
1d8be6b014
128 changed files with 8017 additions and 8017 deletions
|
@ -1,62 +1,62 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const {
|
||||
splitTextAtTerms,
|
||||
isAnsi,
|
||||
cleanControlCodes
|
||||
} = require('../../string_util.js');
|
||||
} = 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');
|
||||
} = require('../../message_area.js');
|
||||
const { sortAreasOrConfs } = require('../../conf_area_util.js');
|
||||
const AnsiPrep = require('../../ansi_prep.js');
|
||||
|
||||
// deps
|
||||
const net = require('net');
|
||||
const _ = require('lodash');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const moment = require('moment');
|
||||
// deps
|
||||
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 : '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 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 {
|
||||
|
@ -64,7 +64,7 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.routes = new Map(); // selector->generator => gopher item
|
||||
this.routes = new Map(); // selector->generator => gopher item
|
||||
this.log = Log.child( { server : 'Gopher' } );
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
|
||||
const config = Config();
|
||||
this.publicHostname = config.contentServers.gopher.publicHostname;
|
||||
this.publicPort = config.contentServers.gopher.publicPort;
|
||||
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);
|
||||
|
@ -88,7 +88,7 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
});
|
||||
|
||||
socket.on('error', err => {
|
||||
if('ECONNRESET' !== err.code) { // normal
|
||||
if('ECONNRESET' !== err.code) { // normal
|
||||
this.log.trace( { error : err.message }, 'Socket error');
|
||||
}
|
||||
});
|
||||
|
@ -97,7 +97,7 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
|
||||
listen() {
|
||||
if(!this.enabled) {
|
||||
return true; // nothing to do, but not an error
|
||||
return true; // nothing to do, but not an error
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
|
@ -115,10 +115,10 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
}
|
||||
|
||||
isConfigured() {
|
||||
// public hostname & port must be set; responses contain them!
|
||||
// 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'));
|
||||
_.isNumber(_.get(config, 'contentServers.gopher.publicPort'));
|
||||
}
|
||||
|
||||
addRoute(selectorRegExp, generatorHandler) {
|
||||
|
@ -149,7 +149,7 @@ 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`;
|
||||
|
@ -186,10 +186,10 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
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|
|
||||
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);
|
||||
|
@ -207,21 +207,21 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
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
|
||||
// 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
|
||||
// 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();
|
||||
// :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) {
|
||||
|
@ -248,15 +248,15 @@ Subject: ${message.subject}
|
|||
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);
|
||||
// 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!');
|
||||
|
@ -283,10 +283,10 @@ ${msgBody}
|
|||
return cb(response);
|
||||
});
|
||||
} else if(selectorMatch[1]) {
|
||||
// list areas in conf
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
|
@ -310,10 +310,10 @@ ${msgBody}
|
|||
|
||||
return cb(response);
|
||||
} else {
|
||||
// message area base (list confs)
|
||||
// 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
|
||||
.filter(conf => conf); // remove any baddies
|
||||
|
||||
if(0 === confs.length) {
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'No message conferences available'));
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const ServerModule = require('../../server_module.js').ServerModule;
|
||||
const Config = require('../../config.js').get;
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const ServerModule = require('../../server_module.js').ServerModule;
|
||||
const Config = require('../../config.js').get;
|
||||
|
||||
// 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');
|
||||
// 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 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 {
|
||||
|
@ -39,8 +39,8 @@ class Route {
|
|||
isValid() {
|
||||
return (
|
||||
this.pathRegExp instanceof RegExp &&
|
||||
( -1 !== [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', ].indexOf(this.method) ) ||
|
||||
!_.isFunction(this.handler)
|
||||
( -1 !== [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', ].indexOf(this.method) ) ||
|
||||
!_.isFunction(this.handler)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -55,28 +55,28 @@ 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) {
|
||||
this.addRoute({
|
||||
method : 'GET',
|
||||
path : '/static/.*$',
|
||||
handler : this.routeStaticFile.bind(this),
|
||||
method : 'GET',
|
||||
path : '/static/.*$',
|
||||
handler : this.routeStaticFile.bind(this),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
buildUrl(pathAndQuery) {
|
||||
//
|
||||
// Create a URL such as
|
||||
// https://l33t.codes:44512/ + |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.
|
||||
// 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)) {
|
||||
|
@ -86,13 +86,13 @@ exports.getModule = class WebServerModule extends ServerModule {
|
|||
let schema;
|
||||
let port;
|
||||
if(config.contentServers.web.https.enabled) {
|
||||
schema = 'https://';
|
||||
port = (443 === config.contentServers.web.https.port) ?
|
||||
schema = 'https://';
|
||||
port = (443 === config.contentServers.web.https.port) ?
|
||||
'' :
|
||||
`:${config.contentServers.web.https.port}`;
|
||||
} else {
|
||||
schema = 'http://';
|
||||
port = (80 === config.contentServers.web.http.port) ?
|
||||
schema = 'http://';
|
||||
port = (80 === config.contentServers.web.http.port) ?
|
||||
'' :
|
||||
`:${config.contentServers.web.http.port}`;
|
||||
}
|
||||
|
@ -112,11 +112,11 @@ exports.getModule = class WebServerModule extends ServerModule {
|
|||
const config = Config();
|
||||
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
|
||||
// additional options
|
||||
Object.assign(options, config.contentServers.web.https.options || {} );
|
||||
|
||||
this.httpsServer = https.createServer(options, (req, resp) => this.routeRequest(req, resp) );
|
||||
|
@ -178,18 +178,18 @@ exports.getModule = class WebServerModule extends ServerModule {
|
|||
|
||||
if(err) {
|
||||
return resp.end(`<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>${title}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h2>${bodyText}</h2>
|
||||
</article>
|
||||
</body>
|
||||
</html>`
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>${title}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h2>${bodyText}</h2>
|
||||
</article>
|
||||
</body>
|
||||
</html>`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -227,8 +227,8 @@ exports.getModule = class WebServerModule extends ServerModule {
|
|||
}
|
||||
|
||||
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);
|
||||
|
@ -251,8 +251,8 @@ exports.getModule = class WebServerModule extends ServerModule {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
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');
|
||||
// ENiGMA½
|
||||
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');
|
||||
|
||||
// deps
|
||||
const ssh2 = require('ssh2');
|
||||
const fs = require('graceful-fs');
|
||||
const util = require('util');
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
// deps
|
||||
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',
|
||||
name : 'SSH',
|
||||
desc : 'SSH Server',
|
||||
author : 'NuSkooler',
|
||||
isSecure : true,
|
||||
packageName : 'codes.l33t.enigma.ssh.server',
|
||||
};
|
||||
|
||||
function SSHClient(clientConn) {
|
||||
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;
|
||||
|
@ -39,11 +39,11 @@ function SSHClient(clientConn) {
|
|||
let loginAttempts = 0;
|
||||
|
||||
clientConn.on('authentication', function authAttempt(ctx) {
|
||||
const username = ctx.username || '';
|
||||
const password = ctx.password || '';
|
||||
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');
|
||||
|
||||
|
@ -58,8 +58,8 @@ function SSHClient(clientConn) {
|
|||
}
|
||||
|
||||
//
|
||||
// If the system is open and |isNewUser| is true, the login
|
||||
// sequence is hijacked in order to start the applicaiton process.
|
||||
// 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();
|
||||
|
@ -85,7 +85,7 @@ function SSHClient(clientConn) {
|
|||
}
|
||||
|
||||
if(0 === username.length) {
|
||||
// :TODO: can we display something here?
|
||||
// :TODO: can we display something here?
|
||||
return ctx.reject();
|
||||
}
|
||||
|
||||
|
@ -105,9 +105,9 @@ function SSHClient(clientConn) {
|
|||
}
|
||||
|
||||
const artOpts = {
|
||||
client : self,
|
||||
name : 'SSHPMPT.ASC',
|
||||
readSauce : false,
|
||||
client : self,
|
||||
name : 'SSHPMPT.ASC',
|
||||
readSauce : false,
|
||||
};
|
||||
|
||||
theme.getThemeArt(artOpts, (err, artInfo) => {
|
||||
|
@ -136,31 +136,31 @@ function SSHClient(clientConn) {
|
|||
|
||||
this.updateTermInfo = function(info) {
|
||||
//
|
||||
// From ssh2 docs:
|
||||
// "rows and cols override width and height when rows and cols are non-zero."
|
||||
// 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;
|
||||
termHeight = info.rows;
|
||||
termWidth = info.cols;
|
||||
} else if(info.width > 0 && info.height > 0) {
|
||||
termHeight = info.height;
|
||||
termWidth = info.width;
|
||||
termHeight = info.height;
|
||||
termWidth = info.width;
|
||||
}
|
||||
|
||||
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
|
||||
// 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.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) {
|
||||
|
@ -182,7 +182,7 @@ function SSHClient(clientConn) {
|
|||
accept();
|
||||
}
|
||||
|
||||
if(self.input) { // do we have I/O?
|
||||
if(self.input) { // do we have I/O?
|
||||
self.updateTermInfo(info);
|
||||
} else {
|
||||
self.cachedTermInfo = info;
|
||||
|
@ -203,7 +203,7 @@ function SSHClient(clientConn) {
|
|||
delete self.cachedTermInfo;
|
||||
}
|
||||
|
||||
// we're ready!
|
||||
// we're ready!
|
||||
const firstMenu = self.isNewUser ? Config().loginServers.ssh.firstMenuNewUser : Config().loginServers.ssh.firstMenu;
|
||||
self.emit('ready', { firstMenu : firstMenu } );
|
||||
});
|
||||
|
@ -222,7 +222,7 @@ function SSHClient(clientConn) {
|
|||
});
|
||||
|
||||
clientConn.on('end', () => {
|
||||
self.emit('end'); // remove client connection/tracking
|
||||
self.emit('end'); // remove client connection/tracking
|
||||
});
|
||||
|
||||
clientConn.on('error', err => {
|
||||
|
@ -244,13 +244,13 @@ exports.getModule = class SSHServerModule extends LoginServerModule {
|
|||
const serverConf = {
|
||||
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',
|
||||
|
||||
// Note that sending 'banner' breaks at least EtherTerm!
|
||||
// Note that sending 'banner' breaks at least EtherTerm!
|
||||
debug : (sshDebugLine) => {
|
||||
if(true === config.loginServers.ssh.traceConnections) {
|
||||
Log.trace(`SSH: ${sshDebugLine}`);
|
||||
|
|
|
@ -1,166 +1,166 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const baseClient = require('../../client.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const LoginServerModule = require('../../login_server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const EnigAssert = require('../../enigma_assert.js');
|
||||
const { stringFromNullTermBuffer } = require('../../string_util.js');
|
||||
// ENiGMA½
|
||||
const baseClient = require('../../client.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const LoginServerModule = require('../../login_server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const EnigAssert = require('../../enigma_assert.js');
|
||||
const { stringFromNullTermBuffer } = require('../../string_util.js');
|
||||
|
||||
// deps
|
||||
const net = require('net');
|
||||
const buffers = require('buffers');
|
||||
const { Parser } = require('binary-parser');
|
||||
const util = require('util');
|
||||
// deps
|
||||
const net = require('net');
|
||||
const buffers = require('buffers');
|
||||
const { Parser } = require('binary-parser');
|
||||
const util = require('util');
|
||||
|
||||
//var debug = require('debug')('telnet');
|
||||
//var debug = require('debug')('telnet');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Telnet',
|
||||
desc : 'Telnet Server',
|
||||
author : 'NuSkooler',
|
||||
isSecure : false,
|
||||
packageName : 'codes.l33t.enigma.telnet.server',
|
||||
name : 'Telnet',
|
||||
desc : 'Telnet Server',
|
||||
author : 'NuSkooler',
|
||||
isSecure : false,
|
||||
packageName : 'codes.l33t.enigma.telnet.server',
|
||||
};
|
||||
|
||||
exports.TelnetClient = TelnetClient;
|
||||
exports.TelnetClient = TelnetClient;
|
||||
|
||||
//
|
||||
// Telnet Protocol Resources
|
||||
// * http://pcmicro.com/netfoss/telnet.html
|
||||
// * http://mud-dev.wikidot.com/telnet:negotiation
|
||||
// Telnet Protocol Resources
|
||||
// * http://pcmicro.com/netfoss/telnet.html
|
||||
// * http://mud-dev.wikidot.com/telnet:negotiation
|
||||
//
|
||||
|
||||
/*
|
||||
TODO:
|
||||
* Document COMMANDS -- add any missing
|
||||
* Document OPTIONS -- add any missing
|
||||
* Internally handle OPTIONS:
|
||||
* Some should be emitted generically
|
||||
* Some should be handled internally -- denied, handled, etc.
|
||||
*
|
||||
TODO:
|
||||
* Document COMMANDS -- add any missing
|
||||
* Document OPTIONS -- add any missing
|
||||
* Internally handle OPTIONS:
|
||||
* Some should be emitted generically
|
||||
* Some should be handled internally -- denied, handled, etc.
|
||||
*
|
||||
|
||||
* Allow term (ttype) to be set by environ sub negotiation
|
||||
* Allow term (ttype) to be set by environ sub negotiation
|
||||
|
||||
* Process terms in loop.... research needed
|
||||
* Process terms in loop.... research needed
|
||||
|
||||
* Handle will/won't
|
||||
* Handle do's, ..
|
||||
* Some won't should close connection
|
||||
* Handle will/won't
|
||||
* Handle do's, ..
|
||||
* Some won't should close connection
|
||||
|
||||
* Options/Commands we don't understand shouldn't crash the server!!
|
||||
* Options/Commands we don't understand shouldn't crash the server!!
|
||||
|
||||
|
||||
*/
|
||||
|
||||
const COMMANDS = {
|
||||
SE : 240, // End of Sub-Negotation Parameters
|
||||
NOP : 241, // No Operation
|
||||
DM : 242, // Data Mark
|
||||
BRK : 243, // Break
|
||||
IP : 244, // Interrupt Process
|
||||
AO : 245, // Abort Output
|
||||
AYT : 246, // Are You There?
|
||||
EC : 247, // Erase Character
|
||||
EL : 248, // Erase Line
|
||||
GA : 249, // Go Ahead
|
||||
SB : 250, // Start Sub-Negotiation Parameters
|
||||
WILL : 251, //
|
||||
WONT : 252,
|
||||
DO : 253,
|
||||
DONT : 254,
|
||||
IAC : 255, // (Data Byte)
|
||||
SE : 240, // End of Sub-Negotation Parameters
|
||||
NOP : 241, // No Operation
|
||||
DM : 242, // Data Mark
|
||||
BRK : 243, // Break
|
||||
IP : 244, // Interrupt Process
|
||||
AO : 245, // Abort Output
|
||||
AYT : 246, // Are You There?
|
||||
EC : 247, // Erase Character
|
||||
EL : 248, // Erase Line
|
||||
GA : 249, // Go Ahead
|
||||
SB : 250, // Start Sub-Negotiation Parameters
|
||||
WILL : 251, //
|
||||
WONT : 252,
|
||||
DO : 253,
|
||||
DONT : 254,
|
||||
IAC : 255, // (Data Byte)
|
||||
};
|
||||
|
||||
//
|
||||
// Resources:
|
||||
// * http://www.faqs.org/rfcs/rfc1572.html
|
||||
// Resources:
|
||||
// * http://www.faqs.org/rfcs/rfc1572.html
|
||||
//
|
||||
const SB_COMMANDS = {
|
||||
IS : 0,
|
||||
SEND : 1,
|
||||
INFO : 2,
|
||||
IS : 0,
|
||||
SEND : 1,
|
||||
INFO : 2,
|
||||
};
|
||||
|
||||
//
|
||||
// Telnet Options
|
||||
// Telnet Options
|
||||
//
|
||||
// Resources
|
||||
// * http://mars.netanya.ac.il/~unesco/cdrom/booklet/HTML/NETWORKING/node300.html
|
||||
// * http://www.networksorcery.com/enp/protocol/telnet.htm
|
||||
// Resources
|
||||
// * http://mars.netanya.ac.il/~unesco/cdrom/booklet/HTML/NETWORKING/node300.html
|
||||
// * http://www.networksorcery.com/enp/protocol/telnet.htm
|
||||
//
|
||||
const OPTIONS = {
|
||||
TRANSMIT_BINARY : 0, // http://tools.ietf.org/html/rfc856
|
||||
ECHO : 1, // http://tools.ietf.org/html/rfc857
|
||||
// RECONNECTION : 2
|
||||
SUPPRESS_GO_AHEAD : 3, // aka 'SGA': RFC 858 @ http://tools.ietf.org/html/rfc858
|
||||
//APPROX_MESSAGE_SIZE : 4
|
||||
STATUS : 5, // http://tools.ietf.org/html/rfc859
|
||||
TIMING_MARK : 6, // http://tools.ietf.org/html/rfc860
|
||||
//RC_TRANS_AND_ECHO : 7, // aka 'RCTE' @ http://www.rfc-base.org/txt/rfc-726.txt
|
||||
//OUPUT_LINE_WIDTH : 8,
|
||||
//OUTPUT_PAGE_SIZE : 9, //
|
||||
//OUTPUT_CARRIAGE_RETURN_DISP : 10, // RFC 652
|
||||
//OUTPUT_HORIZ_TABSTOPS : 11, // RFC 653
|
||||
//OUTPUT_HORIZ_TAB_DISP : 12, // RFC 654
|
||||
//OUTPUT_FORMFEED_DISP : 13, // RFC 655
|
||||
//OUTPUT_VERT_TABSTOPS : 14, // RFC 656
|
||||
//OUTPUT_VERT_TAB_DISP : 15, // RFC 657
|
||||
//OUTPUT_LF_DISP : 16, // RFC 658
|
||||
//EXTENDED_ASCII : 17, // RFC 659
|
||||
//LOGOUT : 18, // RFC 727
|
||||
//BYTE_MACRO : 19, // RFC 753
|
||||
//DATA_ENTRY_TERMINAL : 20, // RFC 1043
|
||||
//SUPDUP : 21, // RFC 736
|
||||
//SUPDUP_OUTPUT : 22, // RFC 749
|
||||
SEND_LOCATION : 23, // RFC 779
|
||||
TERMINAL_TYPE : 24, // aka 'TTYPE': RFC 1091 @ http://tools.ietf.org/html/rfc1091
|
||||
//END_OF_RECORD : 25, // RFC 885
|
||||
//TACACS_USER_ID : 26, // RFC 927
|
||||
//OUTPUT_MARKING : 27, // RFC 933
|
||||
//TERMINCAL_LOCATION_NUMBER : 28, // RFC 946
|
||||
//TELNET_3270_REGIME : 29, // RFC 1041
|
||||
WINDOW_SIZE : 31, // aka 'NAWS': RFC 1073 @ http://tools.ietf.org/html/rfc1073
|
||||
TERMINAL_SPEED : 32, // RFC 1079 @ http://tools.ietf.org/html/rfc1079
|
||||
REMOTE_FLOW_CONTROL : 33, // RFC 1072 @ http://tools.ietf.org/html/rfc1372
|
||||
LINEMODE : 34, // RFC 1184 @ http://tools.ietf.org/html/rfc1184
|
||||
X_DISPLAY_LOCATION : 35, // aka 'XDISPLOC': RFC 1096 @ http://tools.ietf.org/html/rfc1096
|
||||
NEW_ENVIRONMENT_DEP : 36, // aka 'NEW-ENVIRON': RFC 1408 @ http://tools.ietf.org/html/rfc1408 (note: RFC 1572 is an update to this)
|
||||
AUTHENTICATION : 37, // RFC 2941 @ http://tools.ietf.org/html/rfc2941
|
||||
ENCRYPT : 38, // RFC 2946 @ http://tools.ietf.org/html/rfc2946
|
||||
NEW_ENVIRONMENT : 39, // aka 'NEW-ENVIRON': RFC 1572 @ http://tools.ietf.org/html/rfc1572 (note: update to RFC 1408)
|
||||
//TN3270E : 40, // RFC 2355
|
||||
//XAUTH : 41,
|
||||
//CHARSET : 42, // RFC 2066
|
||||
//REMOTE_SERIAL_PORT : 43,
|
||||
//COM_PORT_CONTROL : 44, // RFC 2217
|
||||
//SUPRESS_LOCAL_ECHO : 45,
|
||||
//START_TLS : 46,
|
||||
//KERMIT : 47, // RFC 2840
|
||||
//SEND_URL : 48,
|
||||
//FORWARD_X : 49,
|
||||
TRANSMIT_BINARY : 0, // http://tools.ietf.org/html/rfc856
|
||||
ECHO : 1, // http://tools.ietf.org/html/rfc857
|
||||
// RECONNECTION : 2
|
||||
SUPPRESS_GO_AHEAD : 3, // aka 'SGA': RFC 858 @ http://tools.ietf.org/html/rfc858
|
||||
//APPROX_MESSAGE_SIZE : 4
|
||||
STATUS : 5, // http://tools.ietf.org/html/rfc859
|
||||
TIMING_MARK : 6, // http://tools.ietf.org/html/rfc860
|
||||
//RC_TRANS_AND_ECHO : 7, // aka 'RCTE' @ http://www.rfc-base.org/txt/rfc-726.txt
|
||||
//OUPUT_LINE_WIDTH : 8,
|
||||
//OUTPUT_PAGE_SIZE : 9, //
|
||||
//OUTPUT_CARRIAGE_RETURN_DISP : 10, // RFC 652
|
||||
//OUTPUT_HORIZ_TABSTOPS : 11, // RFC 653
|
||||
//OUTPUT_HORIZ_TAB_DISP : 12, // RFC 654
|
||||
//OUTPUT_FORMFEED_DISP : 13, // RFC 655
|
||||
//OUTPUT_VERT_TABSTOPS : 14, // RFC 656
|
||||
//OUTPUT_VERT_TAB_DISP : 15, // RFC 657
|
||||
//OUTPUT_LF_DISP : 16, // RFC 658
|
||||
//EXTENDED_ASCII : 17, // RFC 659
|
||||
//LOGOUT : 18, // RFC 727
|
||||
//BYTE_MACRO : 19, // RFC 753
|
||||
//DATA_ENTRY_TERMINAL : 20, // RFC 1043
|
||||
//SUPDUP : 21, // RFC 736
|
||||
//SUPDUP_OUTPUT : 22, // RFC 749
|
||||
SEND_LOCATION : 23, // RFC 779
|
||||
TERMINAL_TYPE : 24, // aka 'TTYPE': RFC 1091 @ http://tools.ietf.org/html/rfc1091
|
||||
//END_OF_RECORD : 25, // RFC 885
|
||||
//TACACS_USER_ID : 26, // RFC 927
|
||||
//OUTPUT_MARKING : 27, // RFC 933
|
||||
//TERMINCAL_LOCATION_NUMBER : 28, // RFC 946
|
||||
//TELNET_3270_REGIME : 29, // RFC 1041
|
||||
WINDOW_SIZE : 31, // aka 'NAWS': RFC 1073 @ http://tools.ietf.org/html/rfc1073
|
||||
TERMINAL_SPEED : 32, // RFC 1079 @ http://tools.ietf.org/html/rfc1079
|
||||
REMOTE_FLOW_CONTROL : 33, // RFC 1072 @ http://tools.ietf.org/html/rfc1372
|
||||
LINEMODE : 34, // RFC 1184 @ http://tools.ietf.org/html/rfc1184
|
||||
X_DISPLAY_LOCATION : 35, // aka 'XDISPLOC': RFC 1096 @ http://tools.ietf.org/html/rfc1096
|
||||
NEW_ENVIRONMENT_DEP : 36, // aka 'NEW-ENVIRON': RFC 1408 @ http://tools.ietf.org/html/rfc1408 (note: RFC 1572 is an update to this)
|
||||
AUTHENTICATION : 37, // RFC 2941 @ http://tools.ietf.org/html/rfc2941
|
||||
ENCRYPT : 38, // RFC 2946 @ http://tools.ietf.org/html/rfc2946
|
||||
NEW_ENVIRONMENT : 39, // aka 'NEW-ENVIRON': RFC 1572 @ http://tools.ietf.org/html/rfc1572 (note: update to RFC 1408)
|
||||
//TN3270E : 40, // RFC 2355
|
||||
//XAUTH : 41,
|
||||
//CHARSET : 42, // RFC 2066
|
||||
//REMOTE_SERIAL_PORT : 43,
|
||||
//COM_PORT_CONTROL : 44, // RFC 2217
|
||||
//SUPRESS_LOCAL_ECHO : 45,
|
||||
//START_TLS : 46,
|
||||
//KERMIT : 47, // RFC 2840
|
||||
//SEND_URL : 48,
|
||||
//FORWARD_X : 49,
|
||||
|
||||
//PRAGMA_LOGON : 138,
|
||||
//SSPI_LOGON : 139,
|
||||
//PRAGMA_HEARTBEAT : 140
|
||||
//PRAGMA_LOGON : 138,
|
||||
//SSPI_LOGON : 139,
|
||||
//PRAGMA_HEARTBEAT : 140
|
||||
|
||||
ARE_YOU_THERE : 246, // aka 'AYT' RFC 854 @ https://tools.ietf.org/html/rfc854
|
||||
ARE_YOU_THERE : 246, // aka 'AYT' RFC 854 @ https://tools.ietf.org/html/rfc854
|
||||
|
||||
EXTENDED_OPTIONS_LIST : 255, // RFC 861 (STD 32)
|
||||
EXTENDED_OPTIONS_LIST : 255, // RFC 861 (STD 32)
|
||||
};
|
||||
|
||||
// Commands used within NEW_ENVIRONMENT[_DEP]
|
||||
// Commands used within NEW_ENVIRONMENT[_DEP]
|
||||
const NEW_ENVIRONMENT_COMMANDS = {
|
||||
VAR : 0,
|
||||
VALUE : 1,
|
||||
ESC : 2,
|
||||
USERVAR : 3,
|
||||
VAR : 0,
|
||||
VALUE : 1,
|
||||
ESC : 2,
|
||||
USERVAR : 3,
|
||||
};
|
||||
|
||||
const IAC_BUF = Buffer.from([ COMMANDS.IAC ]);
|
||||
const IAC_SE_BUF = Buffer.from([ COMMANDS.IAC, COMMANDS.SE ]);
|
||||
const IAC_BUF = Buffer.from([ COMMANDS.IAC ]);
|
||||
const IAC_SE_BUF = Buffer.from([ COMMANDS.IAC, COMMANDS.SE ]);
|
||||
|
||||
const COMMAND_NAMES = Object.keys(COMMANDS).reduce(function(names, name) {
|
||||
names[COMMANDS[name]] = name.toLowerCase();
|
||||
|
@ -178,9 +178,9 @@ const COMMAND_IMPLS = {};
|
|||
};
|
||||
});
|
||||
|
||||
// :TODO: See TooTallNate's telnet.js: Handle COMMAND_IMPL for IAC in binary mode
|
||||
// :TODO: See TooTallNate's telnet.js: Handle COMMAND_IMPL for IAC in binary mode
|
||||
|
||||
// Create option names such as 'transmit binary' -> OPTIONS.TRANSMIT_BINARY
|
||||
// Create option names such as 'transmit binary' -> OPTIONS.TRANSMIT_BINARY
|
||||
const OPTION_NAMES = Object.keys(OPTIONS).reduce(function(names, name) {
|
||||
names[OPTIONS[name]] = name.toLowerCase().replace(/_/g, ' ');
|
||||
return names;
|
||||
|
@ -193,19 +193,19 @@ function unknownOption(bufs, i, event) {
|
|||
}
|
||||
|
||||
const OPTION_IMPLS = {};
|
||||
// :TODO: fill in the rest...
|
||||
OPTION_IMPLS.NO_ARGS =
|
||||
OPTION_IMPLS[OPTIONS.ECHO] =
|
||||
OPTION_IMPLS[OPTIONS.STATUS] =
|
||||
OPTION_IMPLS[OPTIONS.LINEMODE] =
|
||||
OPTION_IMPLS[OPTIONS.TRANSMIT_BINARY] =
|
||||
OPTION_IMPLS[OPTIONS.AUTHENTICATION] =
|
||||
OPTION_IMPLS[OPTIONS.TERMINAL_SPEED] =
|
||||
OPTION_IMPLS[OPTIONS.REMOTE_FLOW_CONTROL] =
|
||||
OPTION_IMPLS[OPTIONS.X_DISPLAY_LOCATION] =
|
||||
OPTION_IMPLS[OPTIONS.SEND_LOCATION] =
|
||||
OPTION_IMPLS[OPTIONS.ARE_YOU_THERE] =
|
||||
OPTION_IMPLS[OPTIONS.SUPPRESS_GO_AHEAD] = function(bufs, i, event) {
|
||||
// :TODO: fill in the rest...
|
||||
OPTION_IMPLS.NO_ARGS =
|
||||
OPTION_IMPLS[OPTIONS.ECHO] =
|
||||
OPTION_IMPLS[OPTIONS.STATUS] =
|
||||
OPTION_IMPLS[OPTIONS.LINEMODE] =
|
||||
OPTION_IMPLS[OPTIONS.TRANSMIT_BINARY] =
|
||||
OPTION_IMPLS[OPTIONS.AUTHENTICATION] =
|
||||
OPTION_IMPLS[OPTIONS.TERMINAL_SPEED] =
|
||||
OPTION_IMPLS[OPTIONS.REMOTE_FLOW_CONTROL] =
|
||||
OPTION_IMPLS[OPTIONS.X_DISPLAY_LOCATION] =
|
||||
OPTION_IMPLS[OPTIONS.SEND_LOCATION] =
|
||||
OPTION_IMPLS[OPTIONS.ARE_YOU_THERE] =
|
||||
OPTION_IMPLS[OPTIONS.SUPPRESS_GO_AHEAD] = function(bufs, i, event) {
|
||||
event.buf = bufs.splice(0, i).toBuffer();
|
||||
return event;
|
||||
};
|
||||
|
@ -214,12 +214,12 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) {
|
|||
if(event.commandCode !== COMMANDS.SB) {
|
||||
OPTION_IMPLS.NO_ARGS(bufs, i, event);
|
||||
} else {
|
||||
// We need 4 bytes header + data + IAC SE
|
||||
// We need 4 bytes header + data + IAC SE
|
||||
if(bufs.length < 7) {
|
||||
return MORE_DATA_REQUIRED;
|
||||
}
|
||||
|
||||
const end = bufs.indexOf(IAC_SE_BUF, 5); // look past header bytes
|
||||
const end = bufs.indexOf(IAC_SE_BUF, 5); // look past header bytes
|
||||
if(-1 === end) {
|
||||
return MORE_DATA_REQUIRED;
|
||||
}
|
||||
|
@ -232,10 +232,10 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) {
|
|||
.uint8('opt')
|
||||
.uint8('is')
|
||||
.array('ttype', {
|
||||
type : 'uint8',
|
||||
readUntil : b => 255 === b, // 255=COMMANDS.IAC
|
||||
type : 'uint8',
|
||||
readUntil : b => 255 === b, // 255=COMMANDS.IAC
|
||||
})
|
||||
// note we read iac2 above
|
||||
// note we read iac2 above
|
||||
.uint8('se')
|
||||
.parse(bufs.toBuffer());
|
||||
} catch(e) {
|
||||
|
@ -248,10 +248,10 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) {
|
|||
EnigAssert(OPTIONS.TERMINAL_TYPE === ttypeCmd.opt);
|
||||
EnigAssert(SB_COMMANDS.IS === ttypeCmd.is);
|
||||
EnigAssert(ttypeCmd.ttype.length > 0);
|
||||
// note we found IAC_SE above
|
||||
// note we found IAC_SE above
|
||||
|
||||
// some terminals such as NetRunner provide a NULL-terminated buffer
|
||||
// slice to remove IAC
|
||||
// some terminals such as NetRunner provide a NULL-terminated buffer
|
||||
// slice to remove IAC
|
||||
event.ttype = stringFromNullTermBuffer(ttypeCmd.ttype.slice(0, -1), 'ascii');
|
||||
|
||||
bufs.splice(0, end);
|
||||
|
@ -264,7 +264,7 @@ OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) {
|
|||
if(event.commandCode !== COMMANDS.SB) {
|
||||
OPTION_IMPLS.NO_ARGS(bufs, i, event);
|
||||
} else {
|
||||
// we need 9 bytes
|
||||
// we need 9 bytes
|
||||
if(bufs.length < 9) {
|
||||
return MORE_DATA_REQUIRED;
|
||||
}
|
||||
|
@ -291,39 +291,39 @@ OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) {
|
|||
EnigAssert(COMMANDS.IAC === nawsCmd.iac2);
|
||||
EnigAssert(COMMANDS.SE === nawsCmd.se);
|
||||
|
||||
event.cols = event.columns = event.width = nawsCmd.width;
|
||||
event.rows = event.height = nawsCmd.height;
|
||||
event.cols = event.columns = event.width = nawsCmd.width;
|
||||
event.rows = event.height = nawsCmd.height;
|
||||
}
|
||||
return event;
|
||||
};
|
||||
|
||||
// Build an array of delimiters for parsing NEW_ENVIRONMENT[_DEP]
|
||||
// Build an array of delimiters for parsing NEW_ENVIRONMENT[_DEP]
|
||||
const NEW_ENVIRONMENT_DELIMITERS = [];
|
||||
Object.keys(NEW_ENVIRONMENT_COMMANDS).forEach(function onKey(k) {
|
||||
NEW_ENVIRONMENT_DELIMITERS.push(NEW_ENVIRONMENT_COMMANDS[k]);
|
||||
});
|
||||
|
||||
// Handle the deprecated RFC 1408 & the updated RFC 1572:
|
||||
OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT_DEP] =
|
||||
OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
|
||||
// Handle the deprecated RFC 1408 & the updated RFC 1572:
|
||||
OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT_DEP] =
|
||||
OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
|
||||
if(event.commandCode !== COMMANDS.SB) {
|
||||
OPTION_IMPLS.NO_ARGS(bufs, i, event);
|
||||
} else {
|
||||
//
|
||||
// We need 4 bytes header + <optional payload> + IAC SE
|
||||
// Many terminals send a empty list:
|
||||
// IAC SB NEW-ENVIRON IS IAC SE
|
||||
// We need 4 bytes header + <optional payload> + IAC SE
|
||||
// Many terminals send a empty list:
|
||||
// IAC SB NEW-ENVIRON IS IAC SE
|
||||
//
|
||||
if(bufs.length < 6) {
|
||||
return MORE_DATA_REQUIRED;
|
||||
}
|
||||
|
||||
let end = bufs.indexOf(IAC_SE_BUF, 4); // look past header bytes
|
||||
let end = bufs.indexOf(IAC_SE_BUF, 4); // look past header bytes
|
||||
if(-1 === end) {
|
||||
return MORE_DATA_REQUIRED;
|
||||
}
|
||||
|
||||
// :TODO: It's likely that we could do all the env name/value parsing directly in Parser.
|
||||
// :TODO: It's likely that we could do all the env name/value parsing directly in Parser.
|
||||
|
||||
let envCmd;
|
||||
try {
|
||||
|
@ -331,12 +331,12 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
|
|||
.uint8('iac1')
|
||||
.uint8('sb')
|
||||
.uint8('opt')
|
||||
.uint8('isOrInfo') // IS=initial, INFO=updates
|
||||
.uint8('isOrInfo') // IS=initial, INFO=updates
|
||||
.array('envBlock', {
|
||||
type : 'uint8',
|
||||
readUntil : b => 255 === b, // 255=COMMANDS.IAC
|
||||
readUntil : b => 255 === b, // 255=COMMANDS.IAC
|
||||
})
|
||||
// note we consume IAC above
|
||||
// note we consume IAC above
|
||||
.uint8('se')
|
||||
.parse(bufs.splice(0, bufs.length).toBuffer());
|
||||
} catch(e) {
|
||||
|
@ -350,34 +350,34 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
|
|||
EnigAssert(SB_COMMANDS.IS === envCmd.isOrInfo || SB_COMMANDS.INFO === envCmd.isOrInfo);
|
||||
|
||||
if(OPTIONS.NEW_ENVIRONMENT_DEP === envCmd.opt) {
|
||||
// :TODO: we should probably support this for legacy clients?
|
||||
// :TODO: we should probably support this for legacy clients?
|
||||
Log.warn('Handling deprecated RFC 1408 NEW-ENVIRON');
|
||||
}
|
||||
|
||||
const envBuf = envCmd.envBlock.slice(0, -1); // remove IAC
|
||||
const envBuf = envCmd.envBlock.slice(0, -1); // remove IAC
|
||||
|
||||
if(envBuf.length < 4) { // TYPE + single char name + sep + single char value
|
||||
// empty env block
|
||||
if(envBuf.length < 4) { // TYPE + single char name + sep + single char value
|
||||
// empty env block
|
||||
return event;
|
||||
}
|
||||
|
||||
const States = {
|
||||
Name : 1,
|
||||
Value : 2,
|
||||
Name : 1,
|
||||
Value : 2,
|
||||
};
|
||||
|
||||
let state = States.Name;
|
||||
const setVars = {};
|
||||
const delVars = [];
|
||||
let varName;
|
||||
// :TODO: handle ESC type!!!
|
||||
// :TODO: handle ESC type!!!
|
||||
while(envBuf.length) {
|
||||
switch(state) {
|
||||
case States.Name :
|
||||
{
|
||||
const type = parseInt(envBuf.splice(0, 1));
|
||||
if(![ NEW_ENVIRONMENT_COMMANDS.VAR, NEW_ENVIRONMENT_COMMANDS.USERVAR, NEW_ENVIRONMENT_COMMANDS.ESC ].includes(type)) {
|
||||
return event; // fail :(
|
||||
return event; // fail :(
|
||||
}
|
||||
|
||||
let nameEnd = envBuf.indexOf(NEW_ENVIRONMENT_COMMANDS.VALUE);
|
||||
|
@ -387,7 +387,7 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
|
|||
|
||||
varName = envBuf.splice(0, nameEnd);
|
||||
if(!varName) {
|
||||
return event; // something is wrong.
|
||||
return event; // something is wrong.
|
||||
}
|
||||
|
||||
varName = Buffer.from(varName).toString('ascii');
|
||||
|
@ -397,7 +397,7 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
|
|||
state = States.Value;
|
||||
} else {
|
||||
state = States.Name;
|
||||
delVars.push(varName); // no value; del this var
|
||||
delVars.push(varName); // no value; del this var
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -423,15 +423,15 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
|
|||
}
|
||||
}
|
||||
|
||||
// :TODO: Handle deleting previously set vars via delVars
|
||||
event.type = envCmd.isOrInfo;
|
||||
event.envVars = setVars;
|
||||
// :TODO: Handle deleting previously set vars via delVars
|
||||
event.type = envCmd.isOrInfo;
|
||||
event.envVars = setVars;
|
||||
}
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
const MORE_DATA_REQUIRED = 0xfeedface;
|
||||
const MORE_DATA_REQUIRED = 0xfeedface;
|
||||
|
||||
function parseBufs(bufs) {
|
||||
EnigAssert(bufs.length >= 2);
|
||||
|
@ -440,16 +440,16 @@ function parseBufs(bufs) {
|
|||
}
|
||||
|
||||
function parseCommand(bufs, i, event) {
|
||||
const command = bufs.get(i); // :TODO: fix deprecation... [i] is not the same
|
||||
event.commandCode = command;
|
||||
event.command = COMMAND_NAMES[command];
|
||||
const command = bufs.get(i); // :TODO: fix deprecation... [i] is not the same
|
||||
event.commandCode = command;
|
||||
event.command = COMMAND_NAMES[command];
|
||||
|
||||
const handler = COMMAND_IMPLS[command];
|
||||
if(handler) {
|
||||
return handler(bufs, i + 1, event);
|
||||
} else {
|
||||
if(2 !== bufs.length) {
|
||||
Log.warn( { bufsLength : bufs.length }, 'Expected bufs length of 2'); // expected: IAC + COMMAND
|
||||
Log.warn( { bufsLength : bufs.length }, 'Expected bufs length of 2'); // expected: IAC + COMMAND
|
||||
}
|
||||
|
||||
event.buf = bufs.splice(0, 2).toBuffer();
|
||||
|
@ -458,9 +458,9 @@ function parseCommand(bufs, i, event) {
|
|||
}
|
||||
|
||||
function parseOption(bufs, i, event) {
|
||||
const option = bufs.get(i); // :TODO: fix deprecation... [i] is not the same
|
||||
event.optionCode = option;
|
||||
event.option = OPTION_NAMES[option];
|
||||
const option = bufs.get(i); // :TODO: fix deprecation... [i] is not the same
|
||||
event.optionCode = option;
|
||||
event.option = OPTION_NAMES[option];
|
||||
|
||||
const handler = OPTION_IMPLS[option];
|
||||
return handler ? handler(bufs, i + 1, event) : unknownOption(bufs, i + 1, event);
|
||||
|
@ -470,20 +470,20 @@ function parseOption(bufs, i, event) {
|
|||
function TelnetClient(input, output) {
|
||||
baseClient.Client.apply(this, arguments);
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
let bufs = buffers();
|
||||
this.bufs = bufs;
|
||||
let bufs = buffers();
|
||||
this.bufs = bufs;
|
||||
|
||||
this.sentDont = {}; // DON'T's we've already sent
|
||||
this.sentDont = {}; // DON'T's we've already sent
|
||||
|
||||
this.setInputOutput(input, output);
|
||||
|
||||
this.negotiationsComplete = false; // are we in the 'negotiation' phase?
|
||||
this.didReady = false; // have we emit the 'ready' event?
|
||||
this.negotiationsComplete = false; // are we in the 'negotiation' phase?
|
||||
this.didReady = false; // have we emit the 'ready' event?
|
||||
|
||||
this.subNegotiationState = {
|
||||
newEnvironRequested : false,
|
||||
newEnvironRequested : false,
|
||||
};
|
||||
|
||||
this.dataHandler = function(b) {
|
||||
|
@ -498,7 +498,7 @@ function TelnetClient(input, output) {
|
|||
while((i = bufs.indexOf(IAC_BUF)) >= 0) {
|
||||
|
||||
//
|
||||
// Some clients will send even IAC separate from data
|
||||
// Some clients will send even IAC separate from data
|
||||
//
|
||||
if(bufs.length <= (i + 1)) {
|
||||
i = MORE_DATA_REQUIRED;
|
||||
|
@ -517,7 +517,7 @@ function TelnetClient(input, output) {
|
|||
break;
|
||||
} else if(i) {
|
||||
if(i.option) {
|
||||
self.emit(i.option, i); // "transmit binary", "echo", ...
|
||||
self.emit(i.option, i); // "transmit binary", "echo", ...
|
||||
}
|
||||
|
||||
self.handleTelnetEvent(i);
|
||||
|
@ -530,8 +530,8 @@ function TelnetClient(input, output) {
|
|||
|
||||
if(MORE_DATA_REQUIRED !== i && bufs.length > 0) {
|
||||
//
|
||||
// Standard data payload. This can still be "non-user" data
|
||||
// such as ANSI control, but we don't handle that here.
|
||||
// Standard data payload. This can still be "non-user" data
|
||||
// such as ANSI control, but we don't handle that here.
|
||||
//
|
||||
self.emit('data', bufs.splice(0).toBuffer());
|
||||
}
|
||||
|
@ -576,7 +576,7 @@ function TelnetClient(input, output) {
|
|||
util.inherits(TelnetClient, baseClient.Client);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Telnet Command/Option handling
|
||||
// Telnet Command/Option handling
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
TelnetClient.prototype.handleTelnetEvent = function(evt) {
|
||||
|
||||
|
@ -584,14 +584,14 @@ TelnetClient.prototype.handleTelnetEvent = function(evt) {
|
|||
return this.connectionWarn( { evt : evt }, 'No command for event');
|
||||
}
|
||||
|
||||
// handler name e.g. 'handleWontCommand'
|
||||
// handler name e.g. 'handleWontCommand'
|
||||
const handlerName = `handle${evt.command.charAt(0).toUpperCase()}${evt.command.substr(1)}Command`;
|
||||
|
||||
if(this[handlerName]) {
|
||||
// specialized
|
||||
// specialized
|
||||
this[handlerName](evt);
|
||||
} else {
|
||||
// generic-ish
|
||||
// generic-ish
|
||||
this.handleMiscCommand(evt);
|
||||
}
|
||||
};
|
||||
|
@ -599,16 +599,16 @@ TelnetClient.prototype.handleTelnetEvent = function(evt) {
|
|||
TelnetClient.prototype.handleWillCommand = function(evt) {
|
||||
if('terminal type' === evt.option) {
|
||||
//
|
||||
// See RFC 1091 @ http://www.faqs.org/rfcs/rfc1091.html
|
||||
// See RFC 1091 @ http://www.faqs.org/rfcs/rfc1091.html
|
||||
//
|
||||
this.requestTerminalType();
|
||||
} else if('new environment' === evt.option) {
|
||||
//
|
||||
// See RFC 1572 @ http://www.faqs.org/rfcs/rfc1572.html
|
||||
// See RFC 1572 @ http://www.faqs.org/rfcs/rfc1572.html
|
||||
//
|
||||
this.requestNewEnvironment();
|
||||
} else {
|
||||
// :TODO: temporary:
|
||||
// :TODO: temporary:
|
||||
this.connectionTrace(evt, 'WILL');
|
||||
}
|
||||
};
|
||||
|
@ -628,20 +628,20 @@ TelnetClient.prototype.handleWontCommand = function(evt) {
|
|||
};
|
||||
|
||||
TelnetClient.prototype.handleDoCommand = function(evt) {
|
||||
// :TODO: handle the rest, e.g. echo nd the like
|
||||
// :TODO: handle the rest, e.g. echo nd the like
|
||||
|
||||
if('linemode' === evt.option) {
|
||||
//
|
||||
// Client wants to enable linemode editing. Denied.
|
||||
// Client wants to enable linemode editing. Denied.
|
||||
//
|
||||
this.wont.linemode();
|
||||
} else if('encrypt' === evt.option) {
|
||||
//
|
||||
// Client wants to enable encryption. Denied.
|
||||
// Client wants to enable encryption. Denied.
|
||||
//
|
||||
this.wont.encrypt();
|
||||
} else {
|
||||
// :TODO: temporary:
|
||||
// :TODO: temporary:
|
||||
this.connectionTrace(evt, 'DO');
|
||||
}
|
||||
};
|
||||
|
@ -655,33 +655,33 @@ TelnetClient.prototype.handleSbCommand = function(evt) {
|
|||
|
||||
if('terminal type' === evt.option) {
|
||||
//
|
||||
// See RFC 1091 @ http://www.faqs.org/rfcs/rfc1091.html
|
||||
// See RFC 1091 @ http://www.faqs.org/rfcs/rfc1091.html
|
||||
//
|
||||
// :TODO: According to RFC 1091 @ http://www.faqs.org/rfcs/rfc1091.html
|
||||
// We should keep asking until we see a repeat. From there, determine the best type/etc.
|
||||
// :TODO: According to RFC 1091 @ http://www.faqs.org/rfcs/rfc1091.html
|
||||
// We should keep asking until we see a repeat. From there, determine the best type/etc.
|
||||
self.setTermType(evt.ttype);
|
||||
|
||||
self.negotiationsComplete = true; // :TODO: throw in a array of what we've taken care. Complete = array satisified or timeout
|
||||
self.negotiationsComplete = true; // :TODO: throw in a array of what we've taken care. Complete = array satisified or timeout
|
||||
|
||||
self.readyNow();
|
||||
} else if('new environment' === evt.option) {
|
||||
//
|
||||
// Handling is as follows:
|
||||
// * Map 'TERM' -> 'termType' and only update if ours is 'unknown'
|
||||
// * Map COLUMNS -> 'termWidth' and only update if ours is 0
|
||||
// * Map ROWS -> 'termHeight' and only update if ours is 0
|
||||
// * Add any new variables, ignore any existing
|
||||
// Handling is as follows:
|
||||
// * Map 'TERM' -> 'termType' and only update if ours is 'unknown'
|
||||
// * Map COLUMNS -> 'termWidth' and only update if ours is 0
|
||||
// * Map ROWS -> 'termHeight' and only update if ours is 0
|
||||
// * Add any new variables, ignore any existing
|
||||
//
|
||||
Object.keys(evt.envVars || {} ).forEach(function onEnv(name) {
|
||||
if('TERM' === name && 'unknown' === self.term.termType) {
|
||||
self.setTermType(evt.envVars[name]);
|
||||
} else if('COLUMNS' === name && 0 === self.term.termWidth) {
|
||||
self.term.termWidth = parseInt(evt.envVars[name]);
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
self.connectionDebug({ termWidth : self.term.termWidth, source : 'NEW-ENVIRON'}, 'Window width updated');
|
||||
} else if('ROWS' === name && 0 === self.term.termHeight) {
|
||||
self.term.termHeight = parseInt(evt.envVars[name]);
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
self.connectionDebug({ termHeight : self.term.termHeight, source : 'NEW-ENVIRON'}, 'Window height updated');
|
||||
} else {
|
||||
if(name in self.term.env) {
|
||||
|
@ -704,11 +704,11 @@ TelnetClient.prototype.handleSbCommand = function(evt) {
|
|||
|
||||
} else if('window size' === evt.option) {
|
||||
//
|
||||
// Update termWidth & termHeight.
|
||||
// Set LINES and COLUMNS environment variables as well.
|
||||
// Update termWidth & termHeight.
|
||||
// Set LINES and COLUMNS environment variables as well.
|
||||
//
|
||||
self.term.termWidth = evt.width;
|
||||
self.term.termHeight = evt.height;
|
||||
self.term.termWidth = evt.width;
|
||||
self.term.termHeight = evt.height;
|
||||
|
||||
if(evt.width > 0) {
|
||||
self.term.env.COLUMNS = evt.height;
|
||||
|
@ -718,7 +718,7 @@ TelnetClient.prototype.handleSbCommand = function(evt) {
|
|||
self.term.env.ROWS = evt.height;
|
||||
}
|
||||
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
self.clearMciCache(); // term size changes = invalidate cache
|
||||
|
||||
self.connectionDebug({ termWidth : evt.width , termHeight : evt.height, source : 'NAWS' }, 'Window size updated');
|
||||
} else {
|
||||
|
@ -736,11 +736,11 @@ TelnetClient.prototype.handleMiscCommand = function(evt) {
|
|||
EnigAssert(evt.command !== 'undefined' && evt.command.length > 0);
|
||||
|
||||
//
|
||||
// See:
|
||||
// * RFC 854 @ http://tools.ietf.org/html/rfc854
|
||||
// See:
|
||||
// * RFC 854 @ http://tools.ietf.org/html/rfc854
|
||||
//
|
||||
if('ip' === evt.command) {
|
||||
// Interrupt Process (IP)
|
||||
// Interrupt Process (IP)
|
||||
this.log.debug('Interrupt Process (IP) - Ending');
|
||||
|
||||
this.input.end();
|
||||
|
@ -817,33 +817,33 @@ TelnetClient.prototype.banner = function() {
|
|||
};
|
||||
|
||||
function Command(command, client) {
|
||||
this.command = COMMANDS[command.toUpperCase()];
|
||||
this.client = client;
|
||||
this.command = COMMANDS[command.toUpperCase()];
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
// Create Command objects with echo, transmit_binary, ...
|
||||
// Create Command objects with echo, transmit_binary, ...
|
||||
Object.keys(OPTIONS).forEach(function(name) {
|
||||
const code = OPTIONS[name];
|
||||
|
||||
Command.prototype[name.toLowerCase()] = function() {
|
||||
const buf = Buffer.alloc(3);
|
||||
buf[0] = COMMANDS.IAC;
|
||||
buf[1] = this.command;
|
||||
buf[2] = code;
|
||||
buf[0] = COMMANDS.IAC;
|
||||
buf[1] = this.command;
|
||||
buf[2] = code;
|
||||
return this.client.output.write(buf);
|
||||
};
|
||||
});
|
||||
|
||||
// Create do, dont, etc. methods on Client
|
||||
// Create do, dont, etc. methods on Client
|
||||
['do', 'dont', 'will', 'wont'].forEach(function(command) {
|
||||
const get = function() {
|
||||
return new Command(command, this);
|
||||
};
|
||||
|
||||
Object.defineProperty(TelnetClient.prototype, command, {
|
||||
get : get,
|
||||
enumerable : true,
|
||||
configurable : true
|
||||
get : get,
|
||||
enumerable : true,
|
||||
configurable : true
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -861,9 +861,9 @@ exports.getModule = class TelnetServerModule extends LoginServerModule {
|
|||
this.handleNewClient(client, sock, ModuleInfo);
|
||||
|
||||
//
|
||||
// Set a timeout and attempt to proceed even if we don't know
|
||||
// the term type yet, which is the preferred trigger
|
||||
// for moving along
|
||||
// Set a timeout and attempt to proceed even if we don't know
|
||||
// the term type yet, which is the preferred trigger
|
||||
// for moving along
|
||||
//
|
||||
setTimeout( () => {
|
||||
if(!client.didReady) {
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
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');
|
||||
// ENiGMA½
|
||||
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');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const WebSocketServer = require('ws').Server;
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const fs = require('graceful-fs');
|
||||
const Writable = require('stream');
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const WebSocketServer = require('ws').Server;
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
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) {
|
||||
|
@ -35,8 +35,8 @@ function WebSocketClient(ws, req, serverType) {
|
|||
};
|
||||
|
||||
//
|
||||
// This bridge makes accessible various calls that client sub classes
|
||||
// want to access on I/O socket
|
||||
// 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) {
|
||||
|
@ -49,12 +49,12 @@ function WebSocketClient(ws, req, serverType) {
|
|||
}
|
||||
|
||||
write(data, cb) {
|
||||
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
|
||||
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
|
||||
|
||||
return this.ws.send(data, { binary : true }, cb);
|
||||
}
|
||||
|
||||
// we need to fake some streaming work
|
||||
// we need to fake some streaming work
|
||||
unpipe() {
|
||||
Log.trace('WebSocket SocketBridge unpipe()');
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ function WebSocketClient(ws, req, serverType) {
|
|||
}
|
||||
|
||||
get remoteAddress() {
|
||||
// Support X-Forwarded-For and X-Real-IP headers for proxied connections
|
||||
// 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);
|
||||
|
@ -72,12 +72,12 @@ function WebSocketClient(ws, req, serverType) {
|
|||
ws.on('message', this.dataHandler);
|
||||
|
||||
ws.on('close', () => {
|
||||
// we'll remove client connection which will in turn end() via our SocketBridge above
|
||||
// we'll remove client connection which will in turn end() via our SocketBridge above
|
||||
return this.emit('end');
|
||||
});
|
||||
|
||||
//
|
||||
// Montior connection status with ping/pong
|
||||
// Montior connection status with ping/pong
|
||||
//
|
||||
ws.on('pong', () => {
|
||||
Log.trace(`Pong from ${this.socketBridge.remoteAddress}`);
|
||||
|
@ -89,11 +89,11 @@ function WebSocketClient(ws, req, serverType) {
|
|||
Log.trace( { headers : req.headers }, 'WebSocket connection headers' );
|
||||
|
||||
//
|
||||
// If the config allows it, look for 'x-forwarded-proto' as "https"
|
||||
// to override |isSecure|
|
||||
// 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'])
|
||||
'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;
|
||||
|
@ -101,7 +101,7 @@ function WebSocketClient(ws, req, serverType) {
|
|||
this.proxied = false;
|
||||
}
|
||||
|
||||
// start handshake process
|
||||
// start handshake process
|
||||
this.banner();
|
||||
}
|
||||
|
||||
|
@ -116,40 +116,40 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
|||
|
||||
createServer() {
|
||||
//
|
||||
// We will actually create up to two servers:
|
||||
// * insecure websocket (ws://)
|
||||
// * secure (tls) websocket (wss://)
|
||||
// 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
|
||||
// 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)) {
|
||||
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 } ),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -161,8 +161,8 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
|||
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)' );
|
||||
|
@ -180,7 +180,7 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
|||
});
|
||||
|
||||
//
|
||||
// Send pings every 30s
|
||||
// Send pings every 30s
|
||||
//
|
||||
setInterval( () => {
|
||||
WSS_SERVER_TYPES.forEach(serverType => {
|
||||
|
@ -191,10 +191,10 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
|||
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
|
||||
return ws.ping('', false); // false=don't mask
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue