Merge branch '0.0.11-beta' into master (#276 initial)

* This is the start of #276: Master will be caught up and some additional changes soon
This commit is contained in:
Bryan Ashby 2020-06-03 20:41:27 -06:00
commit 9a1ede3055
No known key found for this signature in database
GPG Key ID: B49EB437951D2542
72 changed files with 3952 additions and 1882 deletions

View File

@ -63,7 +63,7 @@ ENiGMA has been tested with many terminals. However, the following are suggested
## Installation
On *nix type systems:
```
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/master/misc/install.sh | bash
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/0.0.11-beta/misc/install.sh | bash
```
Please see [Installation Methods](https://nuskooler.github.io/enigma-bbs/installation/installation-methods.html) for Windows, Docker, and so on...

View File

@ -40,6 +40,9 @@ npm install
Report your issue on Xibalba BBS, hop in #enigma-bbs on FreeNode and chat, or
[file a issue on GitHub](https://github.com/NuSkooler/enigma-bbs/issues).
# 0.0.10-alpha to 0.0.11-beta
* Node.js 12.x LTS is now in use. Follow standard Node.js upgrade procedures (e.g.: `nvm install 12 && nvm use 12`).
# 0.0.9-alpha to 0.0.10-alpha
* Security related files such as private keys and certs are now looked for in `config/security` by default.
* Default archive handler for zip files has switched to InfoZip due to a bug in the latest p7Zip packages causing "volume not found" errors. Ensure you have the InfoZip `zip` and `unzip` commands in ENiGMA's path. You can switch back to 7Zip by overriding `archiveHandler` for `application/zip` in your `config.hjson` under `fileTypes` to `7Zip`.

View File

@ -1,6 +1,13 @@
# Whats New
This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub.
## 0.0.11-beta
* Upgraded from `alpha` to `beta` -- The software is far along and mature enough at this point!
* Development is now against Node.js 12.x LTS. Other versions may work but are not currently supported!
* [QWK support](/docs/messageareas/qwk.md)
* `oputil fb scan *areaTagWildcard*` scans all areas in which wildcard is matched.
* The archiver configuration `escapeTelnet` has been renamed `escapeIACs`. Support for the old value will be removed in the future.
## 0.0.10-alpha
+ `oputil.js user rename USERNAME NEWNAME`
+ `my_messages.js` module (defaulted to "m" at the message menu) to list public messages addressed to the currently logged in user. Takes into account their username and `real_name` property.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -288,6 +288,17 @@
}
}
qwkExportPacketCurrentConfig: {
mci: {
TL1: {
width: 70
}
TL2: {
width: 70
}
}
}
mailMenuCreateMessage: {
0: {
mci: {

View File

@ -87,6 +87,7 @@ function ANSIEscapeParser(options) {
let pos = 0;
let start = 0;
let charCode;
let lastCharCode;
while(pos < len) {
charCode = text.charCodeAt(pos) & 0xff; // 8bit clean
@ -102,6 +103,12 @@ function ANSIEscapeParser(options) {
break;
case LF :
// Handle ANSI saved with UNIX-style LF's only
// vs the CRLF pairs
if (lastCharCode !== CR) {
self.column = 1;
}
self.emit('literal', text.slice(start, pos));
start = pos;
@ -126,6 +133,7 @@ function ANSIEscapeParser(options) {
}
++pos;
lastCharCode = charCode;
}
//

View File

@ -204,23 +204,37 @@ module.exports = class ArchiveUtil {
});
}
compressTo(archType, archivePath, files, cb) {
compressTo(archType, archivePath, files, workDir, cb) {
const archiver = this.getArchiver(archType, paths.extname(archivePath));
if(!archiver) {
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
}
if (!cb && _.isFunction(workDir)) {
cb = workDir;
workDir = null;
}
const fmtObj = {
archivePath : archivePath,
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
};
const args = archiver.compress.args.map( arg => stringFormat(arg, fmtObj) );
// :TODO: DRY with extractTo()
const args = archiver.compress.args.map( arg => {
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
});
const fileListPos = args.indexOf('{fileList}');
if(fileListPos > -1) {
// replace {fileList} with 0:n sep file list arguments
args.splice.apply(args, [fileListPos, 1].concat(files));
}
let proc;
try {
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts());
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts(workDir));
} catch(e) {
return cb(Errors.ExternalProcess(
`Error spawning archiver process "${archiver.compress.cmd}" with args "${args.join(' ')}": ${e.message}`)
@ -332,15 +346,15 @@ module.exports = class ArchiveUtil {
});
}
getPtyOpts(extractPath) {
getPtyOpts(cwd) {
const opts = {
name : 'enigma-archiver',
cols : 80,
rows : 24,
env : process.env,
};
if(extractPath) {
opts.cwd = extractPath;
if(cwd) {
opts.cwd = cwd;
}
// :TODO: set cwd to supplied temp path if not sepcific extract
return opts;

View File

@ -49,7 +49,7 @@ function getFontNameFromSAUCE(sauce) {
function sliceAtEOF(data, eofMarker) {
let eof = data.length;
const stopPos = Math.max(data.length - (256), 0); // 256 = 2 * sizeof(SAUCE)
const stopPos = Math.max(data.length - 256, 0); // 256 = 2 * sizeof(SAUCE)
for(let i = eof - 1; i > stopPos; i--) {
if(eofMarker === data[i]) {
@ -57,9 +57,16 @@ function sliceAtEOF(data, eofMarker) {
break;
}
}
if(eof === data.length || eof < 128) {
if (eof === data.length) {
return data; // nothing to do
}
// try to prevent goofs
if (eof < 128 && 'SAUCE00' !== data.slice(eof + 1, eof + 8).toString()) {
return data;
}
return data.slice(0, eof);
}

View File

@ -189,8 +189,12 @@ function initialize(cb) {
function basicInit(callback) {
logger.init();
logger.log.info(
{ version : require('../package.json').version },
'**** ENiGMA½ Bulletin Board System Starting Up! ****');
{
version : require('../package.json').version,
nodeVersion : process.version,
},
'**** ENiGMA½ Bulletin Board System Starting Up! ****'
);
process.on('SIGINT', shutdownSystem);

View File

@ -84,7 +84,7 @@ function Client(/*input, output*/) {
this.user = new User();
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
this.lastKeyPressMs = Date.now();
this.lastActivityTime = Date.now();
this.menuStack = new MenuStack(this);
this.acs = new ACS( { client : this, user : this.user } );
this.mciCache = {};
@ -96,7 +96,7 @@ function Client(/*input, output*/) {
Object.defineProperty(this, 'node', {
get : function() {
return self.session.id + 1;
return self.session.id;
}
});
@ -107,11 +107,13 @@ function Client(/*input, output*/) {
});
this.setTemporaryDirectDataHandler = function(handler) {
this.dataPassthrough = true; // let implementations do with what they will here
this.input.removeAllListeners('data');
this.input.on('data', handler);
};
this.restoreDataHandler = function() {
this.dataPassthrough = false;
this.input.removeAllListeners('data');
this.input.on('data', this.dataHandler);
};
@ -406,7 +408,7 @@ function Client(/*input, output*/) {
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input'); // jshint ignore:line
}
self.lastKeyPressMs = Date.now();
self.lastActivityTime = Date.now();
if(!self.ignoreInput) {
self.emit('key press', ch, key);
@ -438,7 +440,7 @@ Client.prototype.startIdleMonitor = function() {
this.stopIdleMonitor();
}
this.lastKeyPressMs = Date.now();
this.lastActivityTime = Date.now();
//
// Every 1m, check for idle.
@ -476,7 +478,7 @@ Client.prototype.startIdleMonitor = function() {
// use override value if set
idleLogoutSeconds = this.idleLogoutSecondsOverride || idleLogoutSeconds;
if(idleLogoutSeconds > 0 && (nowMs - this.lastKeyPressMs >= (idleLogoutSeconds * 1000))) {
if(idleLogoutSeconds > 0 && (nowMs - this.lastActivityTime >= (idleLogoutSeconds * 1000))) {
this.emit('idle timeout');
}
}, 1000 * 60);
@ -489,6 +491,10 @@ Client.prototype.stopIdleMonitor = function() {
}
};
Client.prototype.explicitActivityTimeUpdate = function() {
this.lastActivityTime = Date.now();
}
Client.prototype.overrideIdleLogoutSeconds = function(seconds) {
this.idleLogoutSecondsOverride = seconds;
};

View File

@ -61,27 +61,27 @@ function getActiveConnectionList(authUsersOnly) {
function addNewClient(client, clientSock) {
//
// Assign ID/client ID to next lowest & available #
// Find a node ID "slot"
//
let id = 0;
for(let i = 0; i < clientConnections.length; ++i) {
if(clientConnections[i].id > id) {
break;
let nodeId;
for (nodeId = 1; nodeId < Number.MAX_SAFE_INTEGER; ++nodeId) {
const existing = clientConnections.find(client => nodeId === client.node);
if (!existing) {
break; // available slot
}
id++;
}
client.session.id = id;
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
client.session.id = nodeId;
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
// create a unique identifier one-time ID for this session
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ id, moment().valueOf() ]);
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ nodeId, moment().valueOf() ]);
clientConnections.push(client);
clientConnections.sort( (c1, c2) => c1.session.id - c2.session.id);
// Create a client specific logger
// Note that this will be updated @ login with additional information
client.log = logger.log.child( { clientId : id, sessionId : client.session.uniqueId } );
client.log = logger.log.child( { nodeId, sessionId : client.session.uniqueId } );
const connInfo = {
remoteAddress : remoteAddress,
@ -101,7 +101,7 @@ function addNewClient(client, clientSock) {
{ client : client, connectionCount : clientConnections.length }
);
return id;
return nodeId;
}
function removeClient(client) {
@ -114,7 +114,7 @@ function removeClient(client) {
logger.log.info(
{
connectionCount : clientConnections.length,
clientId : client.session.id
nodeId : client.node,
},
'Client disconnected'
);

View File

@ -678,7 +678,7 @@ function getDefaultConfig() {
},
decompress : {
cmd : 'unzip',
args : [ '{archivePath}', '-d', '{extractPath}' ],
args : [ '-n', '{archivePath}', '-d', '{extractPath}' ],
},
list : {
cmd : 'unzip',
@ -688,7 +688,7 @@ function getDefaultConfig() {
},
extract : {
cmd : 'unzip',
args : [ '{archivePath}', '{fileList}', '-d', '{extractPath}' ],
args : [ '-n', '{archivePath}', '{fileList}', '-d', '{extractPath}' ],
}
},
@ -865,8 +865,7 @@ function getDefaultConfig() {
recvArgs : [
'--zmodem', '--binary', '--restricted', '--keep-uppercase', // dumps to CWD which is set to {uploadDir}
],
// :TODO: can we not just use --escape ?
escapeTelnet : true, // set to true to escape Telnet codes such as IAC
processIACs : true, // escape/de-escape IACs (0xff)
}
}
},

55
core/cp437util.js Normal file
View File

@ -0,0 +1,55 @@
const CP437UnicodeTable = [
'\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006',
'\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D',
'\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014',
'\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B',
'\u001C', '\u001D', '\u001E', '\u001F', '\u0020', '\u0021', '\u0022',
'\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029',
'\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', '\u0030',
'\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037',
'\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E',
'\u003F', '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045',
'\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C',
'\u004D', '\u004E', '\u004F', '\u0050', '\u0051', '\u0052', '\u0053',
'\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A',
'\u005B', '\u005C', '\u005D', '\u005E', '\u005F', '\u0060', '\u0061',
'\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068',
'\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F',
'\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076',
'\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D',
'\u007E', '\u007F', '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4',
'\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB', '\u00E8', '\u00EF',
'\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6',
'\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', '\u00FF', '\u00D6',
'\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', '\u00E1',
'\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA',
'\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB',
'\u00BB', '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561',
'\u2562', '\u2556', '\u2555', '\u2563', '\u2551', '\u2557', '\u255D',
'\u255C', '\u255B', '\u2510', '\u2514', '\u2534', '\u252C', '\u251C',
'\u2500', '\u253C', '\u255E', '\u255F', '\u255A', '\u2554', '\u2569',
'\u2566', '\u2560', '\u2550', '\u256C', '\u2567', '\u2568', '\u2564',
'\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', '\u256A',
'\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580',
'\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5',
'\u03C4', '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6',
'\u03B5', '\u2229', '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320',
'\u2321', '\u00F7', '\u2248', '\u00B0', '\u2219', '\u00B7', '\u221A',
'\u207F', '\u00B2', '\u25A0', '\u00A0'
];
const NonCP437EncodableRegExp = /[^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004A\u004B\u004C\u004D\u004E\u004F\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005A\u005B\u005C\u005D\u005E\u005F\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006A\u006B\u006C\u006D\u006E\u006F\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007A\u007B\u007C\u007D\u007E\u007F\u00C7\u00FC\u00E9\u00E2\u00E4\u00E0\u00E5\u00E7\u00EA\u00EB\u00E8\u00EF\u00EE\u00EC\u00C4\u00C5\u00C9\u00E6\u00C6\u00F4\u00F6\u00F2\u00FB\u00F9\u00FF\u00D6\u00DC\u00A2\u00A3\u00A5\u20A7\u0192\u00E1\u00ED\u00F3\u00FA\u00F1\u00D1\u00AA\u00BA\u00BF\u2310\u00AC\u00BD\u00BC\u00A1\u00AB\u00BB\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255D\u255C\u255B\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u255E\u255F\u255A\u2554\u2569\u2566\u2560\u2550\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256B\u256A\u2518\u250C\u2588\u2584\u258C\u2590\u2580\u03B1\u00DF\u0393\u03C0\u03A3\u03C3\u00B5\u03C4\u03A6\u0398\u03A9\u03B4\u221E\u03C6\u03B5\u2229\u2261\u00B1\u2265\u2264\u2320\u2321\u00F7\u2248\u00B0\u2219\u00B7\u221A\u207F\u00B2\u25A0\u00A0]/;
const isCP437Encodable = (s) => {
if (!s.length) {
return true;
}
return !NonCP437EncodableRegExp.test(s);
}
module.exports = {
CP437UnicodeTable,
isCP437Encodable,
}

View File

@ -16,6 +16,10 @@ function trackDoorRunBegin(client, doorTag) {
}
function trackDoorRunEnd(trackInfo) {
if (!trackInfo) {
return;
}
const { startTime, client, doorTag } = trackInfo;
const diff = moment.duration(moment().diff(startTime));

View File

@ -1,11 +1,12 @@
/* jslint node: true */
'use strict';
const FileEntry = require('./file_entry.js');
const UserProps = require('./user_property.js');
const FileEntry = require('./file_entry');
const UserProps = require('./user_property');
const Events = require('./events');
// deps
const { partition } = require('lodash');
const _ = require('lodash');
module.exports = class DownloadQueue {
constructor(client) {
@ -20,6 +21,10 @@ module.exports = class DownloadQueue {
}
}
static get(client) {
return new DownloadQueue(client);
}
get items() {
return this.client.user.downloadQueue;
}
@ -52,7 +57,7 @@ module.exports = class DownloadQueue {
fileIds = [ fileIds ];
}
const [ remain, removed ] = partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
const [ remain, removed ] = _.partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
this.client.user.downloadQueue = remain;
return removed;
}
@ -76,4 +81,23 @@ module.exports = class DownloadQueue {
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property');
}
}
addTemporaryDownload(entry) {
this.add(entry, true); // true=systemFile
// clean up after ourselves when the session ends
const thisUniqueId = this.client.session.uniqueId;
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
if(thisUniqueId === _.get(evt, 'client.session.uniqueId')) {
FileEntry.removeEntry(entry, { removePhysFile : true }, err => {
const Log = require('./logger').log;
if(err) {
Log.warn( { fileId : entry.fileId, path : entry.filePath }, 'Failed removing temporary session download' );
} else {
Log.debug( { fileId : entry.fileId, path : entry.filePath }, 'Removed temporary session download item' );
}
});
}
});
}
};

View File

@ -37,6 +37,8 @@ exports.Errors = {
MissingParam : (reason, reasonCode) => new EnigError('Missing paramter(s)', -32008, reason, reasonCode),
MissingMci : (reason, reasonCode) => new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode),
BadLogin : (reason, reasonCode) => new EnigError('Bad login attempt', -32010, reason, reasonCode),
UserInterrupt : (reason, reasonCode) => new EnigError('User interrupted', -32011, reason, reasonCode),
NothingToDo : (reason, reasonCode) => new EnigError('Nothing to do', -32012, reason, reasonCode),
};
exports.ErrorReasons = {

View File

@ -17,6 +17,7 @@ const StatLog = require('./stat_log.js');
const UserProps = require('./user_property.js');
const SysProps = require('./system_property.js');
const SAUCE = require('./sauce.js');
const { wildcardMatch } = require('./string_util');
// deps
const _ = require('lodash');
@ -40,6 +41,7 @@ exports.getAreaDefaultStorageDirectory = getAreaDefaultStorageDirectory;
exports.getAreaStorageLocations = getAreaStorageLocations;
exports.getDefaultFileAreaTag = getDefaultFileAreaTag;
exports.getFileAreaByTag = getFileAreaByTag;
exports.getFileAreasByTagWildcardRule = getFileAreasByTagWildcardRule;
exports.getFileEntryPath = getFileEntryPath;
exports.changeFileAreaWithOptions = changeFileAreaWithOptions;
exports.scanFile = scanFile;
@ -143,6 +145,15 @@ function getFileAreaByTag(areaTag) {
}
}
function getFileAreasByTagWildcardRule(rule) {
const areaTags = Object.keys(Config().fileBase.areas)
.filter(areaTag => {
return !isInternalArea(areaTag) && wildcardMatch(areaTag, rule);
});
return areaTags.map(areaTag => getFileAreaByTag(areaTag));
}
function changeFileAreaWithOptions(client, areaTag, options, cb) {
async.waterfall(
[

View File

@ -5,7 +5,7 @@ const UserProps = require('./user_property.js');
// deps
const _ = require('lodash');
const uuidV4 = require('uuid/v4');
const { v4 : UUIDv4 } = require('uuid');
module.exports = class FileBaseFilters {
constructor(client) {
@ -41,7 +41,7 @@ module.exports = class FileBaseFilters {
}
add(filterInfo) {
const filterUuid = uuidV4();
const filterUuid = UUIDv4();
filterInfo.tags = this.cleanTags(filterInfo.tags);

View File

@ -7,8 +7,6 @@ const FileEntry = require('./file_entry.js');
const FileArea = require('./file_base_area.js');
const { renderSubstr } = require('./string_util.js');
const { Errors } = require('./enig_error.js');
const Events = require('./events.js');
const Log = require('./logger.js').log;
const DownloadQueue = require('./download_queue.js');
const { exportFileList } = require('./file_base_list_export.js');
@ -19,7 +17,7 @@ const fs = require('graceful-fs');
const fse = require('fs-extra');
const paths = require('path');
const moment = require('moment');
const uuidv4 = require('uuid/v4');
const { v4 : UUIDv4 } = require('uuid');
const yazl = require('yazl');
/*
@ -28,7 +26,7 @@ const yazl = require('yazl');
tsFormat - timestamp format (theme 'short')
descWidth - max desc width (45)
progBarChar - progress bar character ()
compressThreshold - threshold to kick in comrpession for lists (1.44 MiB)
compressThreshold - threshold to kick in compression for lists (1.44 MiB)
templates - object containing:
header - filename of header template (misc/file_list_header.asc)
entry - filename of entry template (misc/file_list_entry.asc)
@ -190,7 +188,7 @@ exports.getModule = class FileBaseListExport extends MenuModule {
const outputFileName = paths.join(
sysTempDownloadDir,
`file_list_${uuidv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
`file_list_${UUIDv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
);
fs.writeFile(outputFileName, listBody, 'utf8', err => {
@ -222,28 +220,14 @@ exports.getModule = class FileBaseListExport extends MenuModule {
newEntry.persist(err => {
if(!err) {
// queue it!
const dlQueue = new DownloadQueue(self.client);
dlQueue.add(newEntry, true); // true=systemFile
// clean up after ourselves when the session ends
const thisClientId = self.client.session.id;
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
if(thisClientId === _.get(evt, 'client.session.id')) {
FileEntry.removeEntry(newEntry, { removePhysFile : true }, err => {
if(err) {
Log.warn( { fileId : newEntry.fileId, path : outputFileName }, 'Failed removing temporary session download' );
} else {
Log.debug( { fileId : newEntry.fileId, path : outputFileName }, 'Removed temporary session download item' );
}
});
}
});
DownloadQueue.get(self.client).addTemporaryDownload(newEntry);
}
return callback(err);
});
},
function done(callback) {
// re-enable idle monitor
// :TODO: this should probably be moved down below at the end of the full waterfall
self.client.startIdleMonitor();
updateStatus('Exported list has been added to your download queue');

View File

@ -364,6 +364,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
const external = this.protocolConfig.external;
const cmd = external[`${this.direction}Cmd`];
// support for handlers that need IACs taken care of over Telnet/etc.
const processIACs =
external.processIACs ||
external.escapeTelnet; // deprecated name
// :TODO: we should only do this when over Telnet (or derived, such as WebSockets)?
const IAC = Buffer.from([255]);
const EscapedIAC = Buffer.from([255, 255]);
this.client.log.debug(
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction },
'Executing external protocol'
@ -378,21 +388,68 @@ exports.getModule = class TransferFileModule extends MenuModule {
const externalProc = pty.spawn(cmd, args, spawnOpts);
let dataHits = 0;
const updateActivity = () => {
if (0 === (dataHits++ % 4)) {
this.client.explicitActivityTimeUpdate();
}
};
this.client.setTemporaryDirectDataHandler(data => {
updateActivity();
// needed for things like sz/rz
if(external.escapeTelnet) {
const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
externalProc.write(Buffer.from(tmp, 'binary'));
if(processIACs) {
let iacPos = data.indexOf(EscapedIAC);
if (-1 === iacPos) {
return externalProc.write(data);
}
// at least one double (escaped) IAC
let lastPos = 0;
while (iacPos > -1) {
let rem = iacPos - lastPos;
if (rem >= 0) {
externalProc.write(data.slice(lastPos, iacPos + 1));
}
lastPos = iacPos + 2;
iacPos = data.indexOf(EscapedIAC, lastPos);
}
if (lastPos < data.length) {
externalProc.write(data.slice(lastPos));
}
// const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
// externalProc.write(Buffer.from(tmp, 'binary'));
} else {
externalProc.write(data);
}
});
externalProc.on('data', data => {
updateActivity();
// needed for things like sz/rz
if(external.escapeTelnet) {
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff'); // escape
this.client.term.rawWrite(Buffer.from(tmp, 'binary'));
if(processIACs) {
let iacPos = data.indexOf(IAC);
if (-1 === iacPos) {
return this.client.term.rawWrite(data);
}
// Has at least a single IAC
let lastPos = 0;
while (iacPos !== -1) {
if (iacPos - lastPos > 0) {
this.client.term.rawWrite(data.slice(lastPos, iacPos));
}
this.client.term.rawWrite(EscapedIAC);
lastPos = iacPos + 1;
iacPos = data.indexOf(IAC, lastPos);
}
if (lastPos < data.length) {
this.client.term.rawWrite(data.slice(lastPos));
}
} else {
this.client.term.rawWrite(data);
}

View File

@ -165,6 +165,74 @@ exports.PacketHeader = PacketHeader;
// * Writeup on differences between type 2, 2.2, and 2+:
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
//
const PacketHeaderParser = new Parser()
.uint16le('origNode')
.uint16le('destNode')
.uint16le('year')
.uint16le('month')
.uint16le('day')
.uint16le('hour')
.uint16le('minute')
.uint16le('second')
.uint16le('baud')
.uint16le('packetType')
.uint16le('origNet')
.uint16le('destNet')
.int8('prodCodeLo')
.int8('prodRevLo') // aka serialNo
.buffer('password', { length : 8 }) // can't use string; need CP437 - see https://github.com/keichi/binary-parser/issues/33
.uint16le('origZone')
.uint16le('destZone')
//
// The following is "filler" in FTS-0001, specifics in
// FSC-0045 and FSC-0048
//
.uint16le('auxNet')
.uint16le('capWordValidate')
.int8('prodCodeHi')
.int8('prodRevHi')
.uint16le('capWord')
.uint16le('origZone2')
.uint16le('destZone2')
.uint16le('origPoint')
.uint16le('destPoint')
.uint32le('prodData');
const MessageHeaderParser = new Parser()
.uint16le('messageType')
.uint16le('ftn_msg_orig_node')
.uint16le('ftn_msg_dest_node')
.uint16le('ftn_msg_orig_net')
.uint16le('ftn_msg_dest_net')
.uint16le('ftn_attr_flags')
.uint16le('ftn_cost')
//
// It would be nice to just string() these, but we want CP437 which requires
// iconv. Another option would be to use a formatter, but until issue 33
// (https://github.com/keichi/binary-parser/issues/33) is fixed, this is cumbersome.
//
.array('modDateTime', {
type : 'uint8',
length : 20, // FTS-0001.016: 20 bytes
})
.array('toUserName', {
type : 'uint8',
// :TODO: array needs some soft of 'limit' field
readUntil : b => 0x00 === b,
})
.array('fromUserName', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('subject', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('message', {
type : 'uint8',
readUntil : b => 0x00 === b,
});
function Packet(options) {
var self = this;
@ -175,39 +243,7 @@ function Packet(options) {
let packetHeader;
try {
packetHeader = new Parser()
.uint16le('origNode')
.uint16le('destNode')
.uint16le('year')
.uint16le('month')
.uint16le('day')
.uint16le('hour')
.uint16le('minute')
.uint16le('second')
.uint16le('baud')
.uint16le('packetType')
.uint16le('origNet')
.uint16le('destNet')
.int8('prodCodeLo')
.int8('prodRevLo') // aka serialNo
.buffer('password', { length : 8 }) // can't use string; need CP437 - see https://github.com/keichi/binary-parser/issues/33
.uint16le('origZone')
.uint16le('destZone')
//
// The following is "filler" in FTS-0001, specifics in
// FSC-0045 and FSC-0048
//
.uint16le('auxNet')
.uint16le('capWordValidate')
.int8('prodCodeHi')
.int8('prodRevHi')
.uint16le('capWord')
.uint16le('origZone2')
.uint16le('destZone2')
.uint16le('origPoint')
.uint16le('destPoint')
.uint32le('prodData')
.parse(packetBuffer);
packetHeader = PacketHeaderParser.parse(packetBuffer);
} catch(e) {
return Errors.Invalid(`Unable to parse FTN packet header: ${e.message}`);
}
@ -544,41 +580,7 @@ function Packet(options) {
let msgData;
try {
msgData = new Parser()
.uint16le('messageType')
.uint16le('ftn_msg_orig_node')
.uint16le('ftn_msg_dest_node')
.uint16le('ftn_msg_orig_net')
.uint16le('ftn_msg_dest_net')
.uint16le('ftn_attr_flags')
.uint16le('ftn_cost')
//
// It would be nice to just string() these, but we want CP437 which requires
// iconv. Another option would be to use a formatter, but until issue 33
// (https://github.com/keichi/binary-parser/issues/33) is fixed, this is cumbersome.
//
.array('modDateTime', {
type : 'uint8',
length : 20, // FTS-0001.016: 20 bytes
})
.array('toUserName', {
type : 'uint8',
// :TODO: array needs some soft of 'limit' field
readUntil : b => 0x00 === b,
})
.array('fromUserName', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('subject', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('message', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.parse(packetBuffer);
msgData = MessageHeaderParser.parse(packetBuffer);
} catch(e) {
return cb(Errors.Invalid(`Failed to parse FTN message header: ${e.message}`));
}

View File

@ -375,26 +375,28 @@ function getCharacterSetIdentifierByEncoding(encodingName) {
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
}
// http://ftsc.org/docs/fts-5003.001
// http://www.unicode.org/L2/L1999/99325-N.htm
function getEncodingFromCharacterSetIdentifier(chrs) {
const ident = chrs.split(' ')[0].toUpperCase();
// :TODO: fill in the rest!!!
return {
// level 1
'ASCII' : 'iso-646-1',
'DUTCH' : 'iso-646',
'FINNISH' : 'iso-646-10',
'FRENCH' : 'iso-646',
'CANADIAN' : 'iso-646',
'GERMAN' : 'iso-646',
'ITALIAN' : 'iso-646',
'NORWEIG' : 'iso-646',
'PORTU' : 'iso-646',
'ASCII' : 'ascii', // ISO-646-1
'DUTCH' : 'ascii', // ISO-646
'FINNISH' : 'ascii', // ISO-646-10
'FRENCH' : 'ascii', // ISO-646
'CANADIAN' : 'ascii', // ISO-646
'GERMAN' : 'ascii', // ISO-646
'ITALIAN' : 'ascii', // ISO-646
'NORWEIG' : 'ascii', // ISO-646
'PORTU' : 'ascii', // ISO-646
'SPANISH' : 'iso-656',
'SWEDISH' : 'iso-646-10',
'SWISS' : 'iso-646',
'UK' : 'iso-646',
'ISO-10' : 'iso-646-10',
'SWEDISH' : 'ascii', // ISO-646-10
'SWISS' : 'ascii', // ISO-646
'UK' : 'ascii', // ISO-646
'ISO-10' : 'ascii', // ISO-646-10
// level 2
'CP437' : 'cp437',

View File

@ -72,12 +72,12 @@ module.exports = class LoginServerModule extends ServerModule {
});
client.on('error', err => {
logger.log.info({ clientId : client.session.id, error : err.message }, 'Connection error');
logger.log.info({ nodeId : client.node, error : err.message }, 'Connection error');
});
client.on('close', err => {
const logFunc = err ? logger.log.info : logger.log.debug;
logFunc( { clientId : client.session.id }, 'Connection closed');
logFunc( { nodeId : client.node }, 'Connection closed');
clientConns.removeClient(client);
});

59
core/mbf.js Normal file
View File

@ -0,0 +1,59 @@
const { Errors } = require('./enig_error');
//
// Utils for dealing with Microsoft Binary Format (MBF) used
// in various BASIC languages, etc.
//
// - https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// - https://stackoverflow.com/questions/2268191/how-to-convert-from-ieee-python-float-to-microsoft-basic-float
//
// Number to 32bit MBF
numToMbf32 = (v) => {
const mbf = Buffer.alloc(4);
if (0 === v) {
return mbf;
}
const ieee = Buffer.alloc(4);
ieee.writeFloatLE(v, 0);
const sign = ieee[3] & 0x80;
let exp = (ieee[3] << 1) | (ieee[2] >> 7);
if (exp === 0xfe) {
throw Errors.Invalid(`${v} cannot be converted to mbf`);
}
exp += 2;
mbf[3] = exp;
mbf[2] = sign | (ieee[2] & 0x7f);
mbf[1] = ieee[1];
mbf[0] = ieee[0];
return mbf;
}
mbf32ToNum = (mbf) => {
if (0 === mbf[3]) {
return 0.0;
}
const ieee = Buffer.alloc(4);
const sign = mbf[2] & 0x80;
const exp = mbf[3] - 2;
ieee[3] = sign | (exp >> 1);
ieee[2] = (exp << 7) | (mbf[2] & 0x7f);
ieee[1] = mbf[1];
ieee[0] = mbf[0];
return ieee.readFloatLE(0);
}
module.exports = {
numToMbf32,
mbf32ToNum,
}

View File

@ -11,6 +11,9 @@ const {
sanitizeString,
getISOTimestampString } = require('./database.js');
const { isCP437Encodable } = require('./cp437util');
const { containsNonLatinCodepoints } = require('./string_util');
const {
isAnsi, isFormattedLine,
splitTextAtTerms,
@ -49,7 +52,8 @@ const SYSTEM_META_NAMES = {
const ADDRESS_FLAVOR = {
Local : 'local', // local / non-remote addressing
FTN : 'ftn', // FTN style
Email : 'email',
Email : 'email', // From email
QWK : 'qwk', // QWK packet
};
const STATE_FLAGS0 = {
@ -87,6 +91,13 @@ const FTN_PROPERTY_NAMES = {
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
};
const QWKPropertyNames = {
MessageNumber : 'qwk_msg_num',
MessageStatus : 'qwk_msg_status', // See http://wiki.synchro.net/ref:qwk for a decent list
ConferenceNumber : 'qwk_conf_num',
InReplyToNum : 'qwk_in_reply_to_num', // note that we prefer the 'InReplyToMsgId' kludge if available
};
// :TODO: this is a ugly hack due to bad variable names - clean it up & just _.camelCase(k)!
const MESSAGE_ROW_MAP = {
reply_to_message_id : 'replyToMsgId',
@ -96,15 +107,23 @@ const MESSAGE_ROW_MAP = {
module.exports = class Message {
constructor(
{
messageId = 0, areaTag = Message.WellKnownAreaTags.Invalid, uuid, replyToMsgId = 0,
toUserName = '', fromUserName = '', subject = '', message = '', modTimestamp = moment(),
meta, hashTags = [],
messageId = 0,
areaTag = Message.WellKnownAreaTags.Invalid,
uuid,
replyToMsgId = 0,
toUserName = '',
fromUserName = '',
subject = '',
message = '',
modTimestamp = moment(),
meta,
hashTags = [],
} = { }
)
{
this.messageId = messageId;
this.areaTag = areaTag;
this.uuid = uuid;
this.messageUuid = uuid;
this.replyToMsgId = replyToMsgId;
this.toUserName = toUserName;
this.fromUserName = fromUserName;
@ -123,6 +142,10 @@ module.exports = class Message {
this.hashTags = hashTags;
}
get uuid() { // deprecated, will be removed in the near future
return this.messageUuid;
}
isValid() { return true; } // :TODO: obviously useless; look into this or remove it
static isPrivateAreaTag(areaTag) {
@ -137,6 +160,20 @@ module.exports = class Message {
return null !== _.get(this, 'meta.System.remote_from_user', null);
}
isCP437Encodable() {
return isCP437Encodable(this.toUserName) &&
isCP437Encodable(this.fromUserName) &&
isCP437Encodable(this.subject) &&
isCP437Encodable(this.message);
}
containsNonLatinCodepoints() {
return containsNonLatinCodepoints(this.toUserName) ||
containsNonLatinCodepoints(this.fromUserName) ||
containsNonLatinCodepoints(this.subject) ||
containsNonLatinCodepoints(this.message);
}
/*
:TODO: finish me
static checkUserHasDeleteRights(user, messageIdOrUuid, cb) {
@ -183,6 +220,10 @@ module.exports = class Message {
return FTN_PROPERTY_NAMES;
}
static get QWKPropertyNames() {
return QWKPropertyNames;
}
setLocalToUserId(userId) {
this.meta.System = this.meta.System || {};
this.meta.System[Message.SystemMetaNames.LocalToUserID] = userId;
@ -259,7 +300,7 @@ module.exports = class Message {
filter.extraFields = []
filter.privateTagUserId = <userId> - if set, only private messages belonging to <userId> are processed
- any other areaTag or confTag filters will be ignored
- areaTags filter ignored
- if NOT present, private areas are skipped
*=NYI
@ -335,20 +376,23 @@ module.exports = class Message {
)`);
} else {
if(filter.areaTag && filter.areaTag.length > 0) {
if(Array.isArray(filter.areaTag)) {
const areaList = filter.areaTag
.filter(t => t != Message.WellKnownAreaTags.Private)
.map(t => `"${t}"`).join(', ');
if(areaList.length > 0) {
appendWhereClause(`m.area_tag IN(${areaList})`);
}
} else if(_.isString(filter.areaTag) && Message.WellKnownAreaTags.Private !== filter.areaTag) {
appendWhereClause(`m.area_tag = "${filter.areaTag}"`);
if (!Array.isArray(filter.areaTag)) {
filter.areaTag = [ filter.areaTag ];
}
}
// explicit exclude of Private
appendWhereClause(`m.area_tag != "${Message.WellKnownAreaTags.Private}"`, 'AND');
const areaList = filter.areaTag
.filter(t => t !== Message.WellKnownAreaTags.Private)
.map(t => `"${t}"`).join(', ');
if(areaList.length > 0) {
appendWhereClause(`m.area_tag IN(${areaList})`);
} else {
// nothing to do; no areas remain
return cb(null, []);
}
} else {
// explicit exclude of Private
appendWhereClause(`m.area_tag != "${Message.WellKnownAreaTags.Private}"`, 'AND');
}
}
if(_.isNumber(filter.replyToMessageId)) {
@ -652,8 +696,8 @@ module.exports = class Message {
function storeMessage(trans, callback) {
// generate a UUID for this message if required (general case)
const msgTimestamp = moment();
if(!self.uuid) {
self.uuid = Message.createMessageUUID(
if(!self.messageUuid) {
self.messageUuid = Message.createMessageUUID(
self.areaTag,
msgTimestamp,
self.subject,
@ -664,7 +708,10 @@ module.exports = class Message {
trans.run(
`INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
[ self.areaTag, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, getISOTimestampString(msgTimestamp) ],
[
self.areaTag, self.messageUuid, self.replyToMsgId, self.toUserName,
self.fromUserName, self.subject, self.message, getISOTimestampString(msgTimestamp)
],
function inserted(err) { // use non-arrow function for 'this' scope
if(!err) {
self.messageId = this.lastID;

View File

@ -24,11 +24,13 @@ exports.getAvailableMessageConferences = getAvailableMessageConferences;
exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences;
exports.getAvailableMessageAreasByConfTag = getAvailableMessageAreasByConfTag;
exports.getSortedAvailMessageAreasByConfTag = getSortedAvailMessageAreasByConfTag;
exports.getAllAvailableMessageAreaTags = getAllAvailableMessageAreaTags;
exports.getDefaultMessageConferenceTag = getDefaultMessageConferenceTag;
exports.getDefaultMessageAreaTagByConfTag = getDefaultMessageAreaTagByConfTag;
exports.getSuitableMessageConfAndAreaTags = getSuitableMessageConfAndAreaTags;
exports.getMessageConferenceByTag = getMessageConferenceByTag;
exports.getMessageAreaByTag = getMessageAreaByTag;
exports.getMessageConfTagByAreaTag = getMessageConfTagByAreaTag;
exports.changeMessageConference = changeMessageConference;
exports.changeMessageArea = changeMessageArea;
exports.hasMessageConfAndAreaRead = hasMessageConfAndAreaRead;
@ -139,6 +141,20 @@ function getSortedAvailMessageAreasByConfTag(confTag, options) {
return areas;
}
function getAllAvailableMessageAreaTags(client, options) {
const areaTags = [];
// mask over older messy APIs for now
const confOpts = Object.assign({}, options, { noClient : client ? false : true });
const areaOpts = Object.assign({}, options, { client });
Object.keys(getAvailableMessageConferences(client, confOpts)).forEach(confTag => {
areaTags.push(...Object.keys(getAvailableMessageAreasByConfTag(confTag, areaOpts)));
});
return areaTags;
}
function getDefaultMessageConferenceTag(client, disableAcsCheck) {
//
// Find the first conference marked 'default'. If found,

View File

@ -0,0 +1,428 @@
// ENiGMA½
const { MenuModule } = require('./menu_module');
const Message = require('./message');
const { Errors } = require('./enig_error');
const {
getMessageAreaByTag,
getMessageConferenceByTag,
hasMessageConfAndAreaRead,
getAllAvailableMessageAreaTags,
} = require('./message_area');
const FileArea = require('./file_base_area');
const { QWKPacketWriter } = require('./qwk_mail_packet');
const { renderSubstr } = require('./string_util');
const Config = require('./config').get;
const FileEntry = require('./file_entry');
const DownloadQueue = require('./download_queue');
const { getISOTimestampString } = require('./database');
// deps
const async = require('async');
const _ = require('lodash');
const fse = require('fs-extra');
const temptmp = require('temptmp');
const paths = require('path');
const { v4 : UUIDv4 } = require('uuid');
const moment = require('moment');
const FormIds = {
main : 0,
};
const MciViewIds = {
main : {
status : 1,
progressBar : 2,
customRangeStart : 10,
}
};
const UserProperties = {
ExportOptions : 'qwk_export_options',
ExportAreas : 'qwk_export_msg_areas',
};
exports.moduleInfo = {
name : 'QWK Export',
desc : 'Exports a QWK Packet for download',
author : 'NuSkooler',
};
exports.getModule = class MessageBaseQWKExport extends MenuModule {
constructor(options) {
super(options);
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1);
this.config.bbsID = this.config.bbsID || _.get(Config(), 'messageNetworks.qwk.bbsID', 'ENIGMA');
this.tempName = `${UUIDv4().substr(-8).toUpperCase()}.QWK`;
this.sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if (err) {
return cb(err);
}
async.waterfall(
[
(callback) => {
this.prepViewController('main', FormIds.main, mciData.menu, err => {
return callback(err);
});
},
(callback) => {
this.temptmp = temptmp.createTrackedSession('qwkuserexp');
this.temptmp.mkdir({ prefix : 'enigqwkwriter-'}, (err, tempDir) => {
if (err) {
return callback(err);
}
this.tempPacketDir = tempDir;
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(this.sysTempDownloadArea);
// ensure dir exists
fse.mkdirs(sysTempDownloadDir, err => {
return callback(err, sysTempDownloadDir);
});
});
},
(sysTempDownloadDir, callback) => {
this._performExport(sysTempDownloadDir, err => {
return callback(err);
});
},
],
err => {
this.temptmp.cleanup();
if (err) {
// :TODO: doesn't do anything currently:
if ('NORESULTS' === err.reasonCode) {
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'qwkExportNoResults');
}
return this.prevMenu();
}
return cb(err);
}
);
});
}
finishedLoading() {
this.prevMenu();
}
_getUserQWKExportOptions() {
let qwkOptions = this.client.user.getProperty(UserProperties.ExportOptions);
try {
qwkOptions = JSON.parse(qwkOptions);
} catch(e) {
qwkOptions = {
enableQWKE : true,
enableHeadersExtension : true,
enableAtKludges : true,
archiveFormat : 'application/zip',
};
}
return qwkOptions;
}
_getUserQWKExportAreas() {
let qwkExportAreas = this.client.user.getProperty(UserProperties.ExportAreas);
try {
qwkExportAreas = JSON.parse(qwkExportAreas).map(exportArea => {
if (exportArea.newerThanTimestamp) {
exportArea.newerThanTimestamp = moment(exportArea.newerThanTimestamp);
}
return exportArea;
});
} catch(e) {
// default to all public and private without 'since'
qwkExportAreas = getAllAvailableMessageAreaTags(this.client).map(areaTag => {
return { areaTag };
});
// Include user's private area
qwkExportAreas.push({
areaTag : Message.WellKnownAreaTags.Private,
});
}
return qwkExportAreas;
}
_performExport(sysTempDownloadDir, cb) {
const statusView = this.viewControllers.main.getView(MciViewIds.main.status);
const updateStatus = (status) => {
if (statusView) {
statusView.setText(status);
}
};
const progBarView = this.viewControllers.main.getView(MciViewIds.main.progressBar);
const updateProgressBar = (curr, total) => {
if (progBarView) {
const prog = Math.floor( (curr / total) * progBarView.dimens.width );
progBarView.setText(this.config.progBarChar.repeat(prog));
}
};
let cancel = false;
let lastProgUpdate = 0;
const progressHandler = (state, next) => {
// we can produce a TON of updates; only update progress at most every 3/4s
if (Date.now() - lastProgUpdate > 750) {
switch (state.step) {
case 'next_area' :
updateStatus(state.status);
updateProgressBar(0, 0);
this.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state);
break;
case 'message' :
updateStatus(state.status);
updateProgressBar(state.current, state.total);
this.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state);
break;
default :
break;
}
lastProgUpdate = Date.now();
}
return next(cancel ? Errors.UserInterrupt('User canceled') : null);
};
const keyPressHandler = (ch, key) => {
if('escape' === key.name) {
cancel = true;
this.client.removeListener('key press', keyPressHandler);
}
};
let totalExported = 0;
const processMessagesWithFilter = (filter, cb) => {
Message.findMessages(filter, (err, messageIds) => {
if (err) {
return cb(err);
}
let current = 1;
async.eachSeries(messageIds, (messageId, nextMessageId) => {
const message = new Message();
message.load({ messageId }, err => {
if (err) {
return nextMessageId(err);
}
const progress = {
message,
step : 'message',
total : ++totalExported,
areaCurrent : current,
areaCount : messageIds.length,
status : `${_.truncate(message.subject, { length : 25 })} (${current} / ${messageIds.length})`,
};
progressHandler(progress, err => {
if (err) {
return nextMessageId(err);
}
packetWriter.appendMessage(message);
current += 1;
return nextMessageId(null);
});
});
},
err => {
return cb(err);
});
});
};
const packetWriter = new QWKPacketWriter(
Object.assign(this._getUserQWKExportOptions(), {
user : this.client.user,
bbsID : this.config.bbsID,
})
);
packetWriter.on('warning', warning => {
this.client.log.warn( { warning }, 'QWK packet writer warning');
});
async.waterfall(
[
(callback) => {
// don't count idle monitor while processing
this.client.stopIdleMonitor();
// let user cancel
this.client.on('key press', keyPressHandler);
packetWriter.once('ready', () => {
return callback(null);
});
packetWriter.once('error', err => {
this.client.log.error( { error : err.message }, 'QWK packet writer error');
cancel = true;
});
packetWriter.init();
},
(callback) => {
// For each public area -> for each message
const userExportAreas = this._getUserQWKExportAreas();
const publicExportAreas = userExportAreas
.filter(exportArea => {
return exportArea.areaTag !== Message.WellKnownAreaTags.Private;
});
async.eachSeries(publicExportAreas, (exportArea, nextExportArea) => {
const area = getMessageAreaByTag(exportArea.areaTag);
const conf = getMessageConferenceByTag(area.confTag);
if (!area || !conf) {
// :TODO: remove from user properties - this area does not exist
this.client.log.warn({ areaTag : exportArea.areaTag }, 'Cannot QWK export area as it does not exist');
return nextExportArea(null);
}
if (!hasMessageConfAndAreaRead(this.client, area)) {
this.client.log.warn({ areaTag : area.areaTag }, 'Cannot QWK export area due to ACS');
return nextExportArea(null);
}
const progress = {
conf,
area,
step : 'next_area',
status : `Gathering in ${conf.name} - ${area.name}...`,
};
progressHandler(progress, err => {
if (err) {
return nextExportArea(err);
}
const filter = {
resultType : 'id',
areaTag : exportArea.areaTag,
newerThanTimestamp : exportArea.newerThanTimestamp
};
processMessagesWithFilter(filter, err => {
return nextExportArea(err);
});
});
},
err => {
return callback(err, userExportAreas);
});
},
(userExportAreas, callback) => {
// Private messages to current user if the user has
// elected to export private messages
const privateExportArea = userExportAreas.find(exportArea => exportArea.areaTag === Message.WellKnownAreaTags.Private);
if (!privateExportArea) {
return callback(null);
}
const filter = {
resultType : 'id',
privateTagUserId : this.client.user.userId,
newerThanTimestamp : privateExportArea.newerThanTimestamp,
};
return processMessagesWithFilter(filter, callback);
},
(callback) => {
let packetInfo;
packetWriter.once('packet', info => {
packetInfo = info;
});
packetWriter.once('finished', () => {
return callback(null, packetInfo);
});
packetWriter.finish(this.tempPacketDir);
},
(packetInfo, callback) => {
if (0 === totalExported) {
return callback(Errors.NothingToDo('No messages exported'));
}
const sysDownloadPath = paths.join(sysTempDownloadDir, this.tempName);
fse.move(packetInfo.path, sysDownloadPath, err => {
return callback(null, sysDownloadPath, packetInfo);
});
},
(sysDownloadPath, packetInfo, callback) => {
const newEntry = new FileEntry({
areaTag : this.sysTempDownloadArea.areaTag,
fileName : paths.basename(sysDownloadPath),
storageTag : this.sysTempDownloadArea.storageTags[0],
meta : {
upload_by_username : this.client.user.username,
upload_by_user_id : this.client.user.userId,
byte_size : packetInfo.stats.size,
session_temp_dl : 1, // download is valid until session is over
// :TODO: something like this: allow to override the displayed/downloaded as filename
// separate from the actual on disk filename. E.g. we could always download as "ENIGMA.QWK"
//visible_filename : paths.basename(packetInfo.path),
}
});
newEntry.desc = 'QWK Export';
newEntry.persist(err => {
if(!err) {
// queue it!
DownloadQueue.get(this.client).addTemporaryDownload(newEntry);
}
return callback(err);
});
},
(callback) => {
// update user's export area dates; they can always change/reset them again
const updatedUserExportAreas = this._getUserQWKExportAreas().map(exportArea => {
return Object.assign(exportArea, {
newerThanTimestamp : getISOTimestampString(),
});
});
return this.client.user.persistProperty(
UserProperties.ExportAreas,
JSON.stringify(updatedUserExportAreas),
callback
);
},
],
err => {
this.client.startIdleMonitor(); // re-enable
this.client.removeListener('key press', keyPressHandler);
if (!err) {
updateStatus('A QWK packet has been placed in your download queue');
} else if (err.code === Errors.NothingToDo().code) {
updateStatus('No messages to export with current criteria');
err = null;
}
return cb(err);
}
);
}
};

View File

@ -35,7 +35,7 @@ function startup(cb) {
function resolveMimeType(query) {
if(mimeTypes.extensions[query]) {
return query; // alreaed a mime-type
return query; // already a mime-type
}
return mimeTypes.lookup(query) || undefined; // lookup() returns false; we want undefined

View File

@ -50,7 +50,7 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
} else {
// note: not logging 'from' here as it's part of client.log.xxxx()
self.client.log.info(
{ to : msg.toUserName, subject : msg.subject, uuid : msg.uuid },
{ to : msg.toUserName, subject : msg.subject, uuid : msg.messageUuid },
'Message persisted'
);
}

View File

@ -521,7 +521,24 @@ function scanFileAreas() {
});
},
function scanAreas(callback) {
fileArea = require('../../core/file_base_area.js');
fileArea = require('../../core/file_base_area');
// Further expand any wildcards
let areaAndStorageInfoExpanded = [];
options.areaAndStorageInfo.forEach(info => {
if (info.areaTag.indexOf('*') > -1) {
const areas = fileArea.getFileAreasByTagWildcardRule(info.areaTag);
areas.forEach(area => {
areaAndStorageInfoExpanded.push(Object.assign({}, info, {
areaTag : area.areaTag,
}));
});
} else {
areaAndStorageInfoExpanded.push(info);
}
});
options.areaAndStorageInfo = areaAndStorageInfoExpanded;
async.eachSeries(options.areaAndStorageInfo, (areaAndStorage, nextAreaTag) => {
const areaInfo = fileArea.getFileAreaByTag(areaAndStorage.areaTag);

View File

@ -59,6 +59,15 @@ Actions:
group USERNAME [+|-]GROUP Adds (+) or removes (-) user from a group
list [FILTER] List users with optional FILTER.
Valid filters:
all : All users (default).
disabled : Disabled users.
inactive : Inactive users.
active : Active (regular) users.
locked : Locked users.
info arguments:
--security Include security information in output
@ -92,8 +101,12 @@ cat arguments:
Actions:
scan AREA_TAG[@STORAGE_TAG] Scan specified area
May contain optional GLOB as last parameter.
Example: ./oputil.js fb scan d0pew4r3z *.zip
Tips:
- May contain optional GLOB as last parameter.
Example: ./oputil.js fb scan d0pew4r3z *.zip
- AREA_TAG may contain simple wildcards.
Example: ./oputil.js fb scan *warez*
info CRITERIA Display information about areas and/or files
@ -170,6 +183,11 @@ Actions:
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
qwk-dump PATH Dumps a QWK packet to stdout.
qwk-export [AREA_TAGS] PATH Exports one or more configured message area to a QWK
packet in the directory specified by PATH. The QWK
BBS ID will be obtained by the final component of PATH.
import-areas arguments:
--conf CONF_TAG Conference tag in which to import areas
--network NETWORK Network name/key to associate FTN areas
@ -177,6 +195,13 @@ import-areas arguments:
--type TYPE Area import type
Valid types are "bbs" and "na".
qwk-export arguments:
--user USER User in which to export for. Defaults to the SysOp.
--after TIMESTAMP Export only messages with a timestamp later than
TIMESTAMP.
--no-qwke Disable QWKE extensions.
--no-synchronet Disable Synchronet style extensions.
`
};

View File

@ -10,17 +10,19 @@ const {
initConfigAndDatabases,
getAnswers,
writeConfig,
} = require('./oputil_common.js');
const getHelpFor = require('./oputil_help.js').getHelpFor;
const Address = require('../ftn_address.js');
const Errors = require('../enig_error.js').Errors;
} = require('./oputil_common.js');
const getHelpFor = require('./oputil_help.js').getHelpFor;
const Address = require('../ftn_address.js');
const Errors = require('../enig_error.js').Errors;
// deps
const async = require('async');
const paths = require('path');
const fs = require('fs');
const hjson = require('hjson');
const _ = require('lodash');
const async = require('async');
const paths = require('path');
const fs = require('fs');
const hjson = require('hjson');
const _ = require('lodash');
const moment = require('moment');
exports.handleMessageBaseCommand = handleMessageBaseCommand;
@ -434,6 +436,200 @@ function getImportEntries(importType, importData) {
return importEntries;
}
function dumpQWKPacket() {
const packetPath = argv._[argv._.length - 1];
if(argv._.length < 3 || !packetPath || 0 === packetPath.length) {
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
}
async.waterfall(
[
(callback) => {
return initConfigAndDatabases(callback);
},
(callback) => {
const { QWKPacketReader } = require('../qwk_mail_packet');
const reader = new QWKPacketReader(packetPath);
reader.on('error', err => {
console.error(`ERROR: ${err.message}`);
return callback(err);
});
reader.on('done', () => {
return callback(null);
});
reader.on('archive type', archiveType => {
console.info(`-> Archive type: ${archiveType}`);
});
reader.on('creator', creator => {
console.info(`-> Creator: ${creator}`);
});
reader.on('message', message => {
console.info('--- message ---');
console.info(`To: ${message.toUserName}`);
console.info(`From: ${message.fromUserName}`);
console.info(`Subject: ${message.subject}`);
console.info(`Message:\r\n${message.message}`);
});
reader.read();
}
],
err => {
}
)
}
function exportQWKPacket() {
let packetPath = argv._[argv._.length - 1];
if(argv._.length < 3 || !packetPath || 0 === packetPath.length) {
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
}
// oputil mb qwk-export TAGS PATH [--user USER] [--after TIMESTAMP]
// [areaTag1,areaTag2,...] PATH --user USER --after TIMESTAMP
let bbsID = 'ENIGMA';
const filename = paths.basename(packetPath);
if (filename) {
const ext = paths.extname(filename);
bbsID = paths.basename(filename, ext);
}
packetPath = paths.dirname(packetPath);
const posArgLen = argv._.length;
let areaTags;
if (4 === posArgLen) {
areaTags = argv._[posArgLen - 2].split(',');
} else {
areaTags = [];
}
let newerThanTimestamp = null;
if (argv.after) {
const ts = moment(argv.after);
if (ts.isValid()) {
newerThanTimestamp = ts.format();
}
}
const userName = argv.user || '-';
const writerOptions = {
enableQWKE : !(false === argv.qwke),
enableHeadersExtension : !(false === argv.synchronet),
enableAtKludges : !(false === argv.synchronet),
archiveFormat : argv.format || 'application/zip'
};
let totalExported = 0;
async.waterfall(
[
(callback) => {
return initConfigAndDatabases(callback);
},
(callback) => {
const User = require('../../core/user.js');
User.getUserIdAndName(userName, (err, userId) => {
if (err) {
if ('-' === userName) {
userId = 1;
} else {
return callback(err);
}
}
return User.getUser(userId, callback);
});
},
(user, callback) => {
// populate area tags with all available to user
// if they were not explicitly supplied
if (!areaTags.length) {
const {
getAllAvailableMessageAreaTags
} = require('../../core/message_area');
areaTags = getAllAvailableMessageAreaTags();
}
return callback(null, user);
},
(user, callback) => {
const Message = require('../message');
const filter = {
resultType : 'id',
areaTag : areaTags,
newerThanTimestamp,
};
// public
Message.findMessages(filter, (err, publicMessageIds) => {
if (err) {
return callback(err);
}
delete filter.areaTag;
filter.privateTagUserId = user.userId;
Message.findMessages(filter, (err, privateMessageIds) => {
return callback(err, user, Message, privateMessageIds.concat(publicMessageIds));
});
});
},
(user, Message, messageIds, callback) => {
const { QWKPacketWriter } = require('../qwk_mail_packet');
const writer = new QWKPacketWriter(Object.assign(writerOptions, {
bbsID,
user,
}));
writer.on('ready', () => {
async.eachSeries(messageIds, (messageId, nextMessageId) => {
const message = new Message();
message.load( { messageId }, err => {
if (!err) {
writer.appendMessage(message);
++totalExported;
}
return nextMessageId(err);
});
},
(err) => {
writer.finish(packetPath);
if (err) {
console.error(`Failed to write one or more messages: ${err.message}`);
}
});
});
writer.on('warning', err => {
console.warn(`!!! ${err.reason ? err.reason : err.message}`);
});
writer.on('finished', () => {
return callback(null);
});
writer.init();
}
],
err => {
if(err) {
return console.error(err.reason ? err.reason : err.message);
}
console.info(`-> Exported ${totalExported} messages`);
}
);
}
function handleMessageBaseCommand() {
function errUsage() {
@ -452,5 +648,7 @@ function handleMessageBaseCommand() {
return({
areafix : areaFix,
'import-areas' : importAreas,
'qwk-dump' : dumpQWKPacket,
'qwk-export' : exportQWKPacket,
}[action] || errUsage)();
}

View File

@ -460,6 +460,66 @@ function twoFactorAuthOTP(user) {
);
}
function listUsers() {
// oputil user list [disabled|inactive|active|locked|all]
// :TODO: --created-since SPEC and --last-called SPEC
// --created-since SPEC
// SPEC can be TIMESTAMP or e.g. "-1hour" or "-90days"
// :TODO: --sort name|id
let listWhat;
if (argv._.length > 2) {
listWhat = argv._[argv._.length - 1];
} else {
listWhat = 'all';
}
const User = require('../../core/user');
if (![ 'all' ].concat(Object.keys(User.AccountStatus)).includes(listWhat)) {
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
}
async.waterfall(
[
(callback) => {
const UserProps = require('../../core/user_property');
const userListOpts = {
properties : [
UserProps.AccountStatus,
],
};
User.getUserList(userListOpts, (err, userList) => {
if (err) {
return callback(err);
}
if ('all' === listWhat) {
return callback(null, userList);
}
const accountStatusFilter = User.AccountStatus[listWhat].toString();
return callback(null, userList.filter(user => {
return user[UserProps.AccountStatus] === accountStatusFilter;
}));
});
},
(userList, callback) => {
userList.forEach(user => {
console.info(`${user.userId}: ${user.userName}`);
});
},
],
err => {
if(err) {
return console.error(err.reason ? err.reason : err.message);
}
}
);
}
function handleUserCommand() {
function errUsage() {
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
@ -470,20 +530,25 @@ function handleUserCommand() {
}
const action = argv._[1];
const usernameIdx = [
'pw', 'pass', 'passwd', 'password',
'group',
'mv', 'rename',
'2fa-otp', 'otp'
].includes(action) ? argv._.length - 2 : argv._.length - 1;
const userName = argv._[usernameIdx];
const userRequired = ![ 'list' ].includes(action);
if(!userName) {
let userName;
if (userRequired) {
const usernameIdx = [
'pw', 'pass', 'passwd', 'password',
'group',
'mv', 'rename',
'2fa-otp', 'otp'
].includes(action) ? argv._.length - 2 : argv._.length - 1;
userName = argv._[usernameIdx];
}
if(!userName && userRequired) {
return errUsage();
}
initAndGetUser(userName, (err, user) => {
if(err) {
if(userName && err) {
process.exitCode = ExitCodes.ERROR;
return console.error(err.message);
}
@ -512,6 +577,7 @@ function handleUserCommand() {
'2fa-otp' : twoFactorAuthOTP,
otp : twoFactorAuthOTP,
list : listUsers,
}[action] || errUsage)(user, action);
});
}

1538
core/qwk_mail_packet.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,25 @@ exports.SAUCE_SIZE = SAUCE_SIZE;
//
const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ];
const SAUCEParser = new Parser()
.buffer('id', { length : 5 } )
.buffer('version', { length : 2 } )
.buffer('title', { length: 35 } )
.buffer('author', { length : 20 } )
.buffer('group', { length: 20 } )
.buffer('date', { length: 8 } )
.uint32le('fileSize')
.int8('dataType')
.int8('fileType')
.uint16le('tinfo1')
.uint16le('tinfo2')
.uint16le('tinfo3')
.uint16le('tinfo4')
.int8('numComments')
.int8('flags')
// :TODO: does this need to be optional?
.buffer('tinfos', { length: 22 } ); // SAUCE 00.5
function readSAUCE(data, cb) {
if(data.length < SAUCE_SIZE) {
return cb(Errors.DoesNotExist('No SAUCE record present'));
@ -33,30 +52,11 @@ function readSAUCE(data, cb) {
let sauceRec;
try {
sauceRec = new Parser()
.buffer('id', { length : 5 } )
.buffer('version', { length : 2 } )
.buffer('title', { length: 35 } )
.buffer('author', { length : 20 } )
.buffer('group', { length: 20 } )
.buffer('date', { length: 8 } )
.uint32le('fileSize')
.int8('dataType')
.int8('fileType')
.uint16le('tinfo1')
.uint16le('tinfo2')
.uint16le('tinfo3')
.uint16le('tinfo4')
.int8('numComments')
.int8('flags')
// :TODO: does this need to be optional?
.buffer('tinfos', { length: 22 } ) // SAUCE 00.5
.parse(data.slice(data.length - SAUCE_SIZE));
sauceRec = SAUCEParser.parse(data.slice(data.length - SAUCE_SIZE));
} catch(e) {
return cb(Errors.Invalid('Invalid SAUCE record'));
}
if(!SAUCE_ID.equals(sauceRec.id)) {
return cb(Errors.DoesNotExist('No SAUCE record present'));
}

View File

@ -36,7 +36,7 @@ const assert = require('assert');
const sane = require('sane');
const fse = require('fs-extra');
const iconv = require('iconv-lite');
const uuidV4 = require('uuid/v4');
const { v4 : UUIDv4 } = require('uuid');
exports.moduleInfo = {
name : 'FTN BSO',
@ -517,8 +517,20 @@ function FTNMessageScanTossModule() {
};
this.hasValidConfiguration = function() {
if(!_.has(this, 'moduleConfig.nodes') || !_.has(Config(), 'messageNetworks.ftn.areas')) {
this.hasValidConfiguration = function({shouldLog = false} = {}) {
const hasNodes = _.has(this, 'moduleConfig.nodes');
const hasAreas = _.has(Config(), 'messageNetworks.ftn.areas');
if(!hasNodes && !hasAreas) {
if (shouldLog) {
Log.warn(
{
'scannerTossers.ftn_bso.nodes' : hasNodes,
'messageNetworks.ftn.areas' : hasAreas,
},
'Missing one or more required configuration blocks'
);
}
return false;
}
@ -1203,7 +1215,7 @@ function FTNMessageScanTossModule() {
//
if(true === _.get(Config(), [ 'messageNetworks', 'ftn', 'areas', config.localAreaTag, 'allowDupes' ], false)) {
// just generate a UUID & therefor always allow for dupes
message.uuid = uuidV4();
message.messageUuid = UUIDv4();
}
return callback(null);
@ -1366,7 +1378,7 @@ function FTNMessageScanTossModule() {
}
}
message.uuid = Message.createMessageUUID(
message.messageUuid = Message.createMessageUUID(
localAreaTag,
message.modTimestamp,
message.subject,
@ -1386,7 +1398,7 @@ function FTNMessageScanTossModule() {
if('SQLITE_CONSTRAINT' === err.code || 'DUPE_MSGID' === err.code) {
const msgId = _.has(message.meta, 'FtnKludge.MSGID') ? message.meta.FtnKludge.MSGID : 'N/A';
Log.info(
{ area : localAreaTag, subject : message.subject, uuid : message.uuid, MSGID : msgId },
{ area : localAreaTag, subject : message.subject, uuid : message.messageUuid, MSGID : msgId },
'Not importing non-unique message');
return next(null);
@ -2151,6 +2163,8 @@ FTNMessageScanTossModule.prototype.processTicFilesInDirectory = function(importD
FTNMessageScanTossModule.prototype.startup = function(cb) {
Log.info(`${exports.moduleInfo.name} Scanner/Tosser starting up`);
this.hasValidConfiguration({ shouldLog : true }); // just check and log
let importing = false;
let self = this;
@ -2287,13 +2301,18 @@ FTNMessageScanTossModule.prototype.shutdown = function(cb) {
FTNMessageScanTossModule.prototype.performImport = function(cb) {
if(!this.hasValidConfiguration()) {
return cb(new Error('Missing or invalid configuration'));
return cb(Errors.MissingConfig('Invalid or missing configuration'));
}
const self = this;
async.each( [ 'inbound', 'secInbound' ], (inboundType, nextDir) => {
self.importFromDirectory(inboundType, self.moduleConfig.paths[inboundType], () => {
const importDir = self.moduleConfig.paths[inboundType];
self.importFromDirectory(inboundType, importDir, err => {
if (err) {
Log.trace({ importDir, error : err.message }, 'Cannot perform FTN import for directory');
}
return nextDir(null);
});
}, cb);
@ -2305,7 +2324,7 @@ FTNMessageScanTossModule.prototype.performExport = function(cb) {
// and let's find out what messages need exported.
//
if(!this.hasValidConfiguration()) {
return cb(new Error('Missing or invalid configuration'));
return cb(Errors.MissingConfig('Invalid or missing configuration'));
}
const self = this;
@ -2313,7 +2332,7 @@ FTNMessageScanTossModule.prototype.performExport = function(cb) {
async.eachSeries( [ 'EchoMail', 'NetMail' ], (type, nextType) => {
self[`perform${type}Export`]( err => {
if(err) {
Log.warn( { error : err.message, type : type }, 'Error(s) during export' );
Log.warn( { type, error : err.message }, 'Error(s) during export' );
}
return nextType(null); // try next, always
});
@ -2330,7 +2349,7 @@ FTNMessageScanTossModule.prototype.record = function(message) {
return;
}
const info = { uuid : message.uuid, subject : message.subject };
const info = { uuid : message.messageUuid, subject : message.subject };
function exportLog(err) {
if(err) {
@ -2344,7 +2363,7 @@ FTNMessageScanTossModule.prototype.record = function(message) {
Object.assign(info, { type : 'NetMail' } );
if(this.exportingStart()) {
this.exportNetMailMessagesToUplinks( [ message.uuid ], err => {
this.exportNetMailMessagesToUplinks( [ message.messageUuid ], err => {
this.exportingEnd( () => exportLog(err) );
});
}
@ -2357,7 +2376,7 @@ FTNMessageScanTossModule.prototype.record = function(message) {
}
if(this.exportingStart()) {
this.exportEchoMailMessagesToUplinks( [ message.uuid ], areaConfig, err => {
this.exportEchoMailMessagesToUplinks( [ message.messageUuid ], areaConfig, err => {
this.exportingEnd( () => exportLog(err) );
});
}

View File

@ -249,11 +249,10 @@ exports.getModule = class MrcModule extends ServerModule {
receiveFromClient(username, message) {
try {
message = JSON.parse(message);
this.sendToMrcServer(message.from_user, message.from_room, message.to_user, message.to_site, message.to_room, message.body);
} catch (e) {
Log.debug({ server : 'MRC', user : username, message : message }, 'Dodgy message received from client');
}
this.sendToMrcServer(message.from_user, message.from_room, message.to_user, message.to_site, message.to_room, message.body);
}
/**
@ -264,11 +263,11 @@ exports.getModule = class MrcModule extends ServerModule {
const line = [
fromUser,
this.boardName,
sanitiseRoomName(fromRoom),
sanitiseRoomName(fromRoom || ''),
sanitiseName(toUser || ''),
sanitiseName(toSite || ''),
sanitiseRoomName(toRoom || ''),
sanitiseMessage(messageBody)
sanitiseMessage(messageBody || '')
].join('~') + '~';
// Log.debug({ server : 'MRC', data : line }, 'Sending data');

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ const http = require('http');
const https = require('https');
const fs = require('graceful-fs');
const Writable = require('stream');
const { Duplex } = require('stream');
const forEachSeries = require('async/forEachSeries');
const ModuleInfo = exports.moduleInfo = {
@ -24,100 +25,91 @@ const ModuleInfo = exports.moduleInfo = {
packageName : 'codes.l33t.enigma.websocket.server',
};
function WebSocketClient(ws, req, serverType) {
class WebSocketClient extends TelnetClient {
constructor(ws, req, serverType) {
// allow WebSocket to act like a Duplex (socket)
const wsDuplex = new class WebSocketDuplex extends Duplex {
constructor(ws) {
super();
this.ws = ws;
Object.defineProperty(this, 'isSecure', {
get : () => ('secure' === serverType || true === this.proxied) ? true : false,
});
this.ws.on('close', err => this.emit('close', err));
this.ws.on('error', err => this.emit('error', err));
this.ws.on('message', data => this._data(data));
}
const self = this;
setClient(client, httpRequest) {
this.client = client;
this.dataHandler = function(data) {
if(self.pipedDest) {
self.pipedDest.write(data);
// Support X-Forwarded-For and X-Real-IP headers for proxied connections
this.resolvedRemoteAddress =
(this.client.proxied && (httpRequest.headers['x-forwarded-for'] || httpRequest.headers['x-real-ip'])) ||
httpRequest.connection.remoteAddress;
}
get remoteAddress() {
return this.resolvedRemoteAddress;
}
_write(data, encoding, cb) {
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
return this.ws.send(data, { binary : true }, cb);
}
_read() {
// dummy
}
_data(data) {
this.push(data);
}
}(ws);
super(wsDuplex);
wsDuplex.setClient(this, req);
// fudge remoteAddress on socket, which is now TelnetSocket
this.socket.remoteAddress = wsDuplex.remoteAddress;
wsDuplex.on('close', () => {
// we'll remove client connection which will in turn end() via our SocketBridge above
return this.emit('end');
});
this.serverType = serverType;
//
// Monitor connection status with ping/pong
//
ws.on('pong', () => {
Log.trace(`Pong from ${wsDuplex.remoteAddress}`);
ws.isConnectionAlive = true;
});
Log.trace( { headers : req.headers }, 'WebSocket connection headers' );
//
// If the config allows it, look for 'x-forwarded-proto' as "https"
// to override |isSecure|
//
if(true === _.get(Config(), 'loginServers.webSocket.proxied') &&
'https' === req.headers['x-forwarded-proto'])
{
Log.debug(`Assuming secure connection due to X-Forwarded-Proto of "${req.headers['x-forwarded-proto']}"`);
this.proxied = true;
} else {
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.proxied = false;
}
end() {
return ws.close();
}
write(data, cb) {
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
return this.ws.send(data, { binary : true }, cb);
}
pipe(dest) {
Log.trace('WebSocket SocketBridge pipe()');
self.pipedDest = dest;
}
unpipe() {
Log.trace('WebSocket SocketBridge unpipe()');
self.pipedDest = null;
}
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);
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');
});
//
// Monitor 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);
Log.trace( { headers : req.headers }, 'WebSocket connection headers' );
//
// If the config allows it, look for 'x-forwarded-proto' as "https"
// to override |isSecure|
//
if(true === _.get(Config(), 'loginServers.webSocket.proxied') &&
'https' === req.headers['x-forwarded-proto'])
{
Log.debug(`Assuming secure connection due to X-Forwarded-Proto of "${req.headers['x-forwarded-proto']}"`);
this.proxied = true;
} else {
this.proxied = false;
// start handshake process
this.banner();
}
// start handshake process
this.banner();
get isSecure() {
return ('secure' === this.serverType || true === this.proxied) ? true : false;
}
}
require('util').inherits(WebSocketClient, TelnetClient);
const WSS_SERVER_TYPES = [ 'insecure', 'secure' ];
exports.getModule = class WebSocketLoginServer extends LoginServerModule {
@ -216,7 +208,7 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
server.wsServer.on('connection', (ws, req) => {
const webSocketClient = new WebSocketClient(ws, req, serverType);
this.handleNewClient(webSocketClient, webSocketClient.socketBridge, ModuleInfo);
this.handleNewClient(webSocketClient, webSocketClient.socket, ModuleInfo);
});
Log.info( { server : serverName, port : port }, 'Listening for connections' );
@ -227,9 +219,4 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
cb(err);
});
}
webSocketConnection(conn) {
const webSocketClient = new WebSocketClient(conn);
this.handleNewClient(webSocketClient, webSocketClient.socketShim, ModuleInfo);
}
};

View File

@ -13,6 +13,7 @@ exports.pad = pad;
exports.insert = insert;
exports.replaceAt = replaceAt;
exports.isPrintable = isPrintable;
exports.containsNonLatinCodepoints = containsNonLatinCodepoints;
exports.stripAllLineFeeds = stripAllLineFeeds;
exports.debugEscapedString = debugEscapedString;
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
@ -28,6 +29,7 @@ exports.isAnsi = isAnsi;
exports.isAnsiLine = isAnsiLine;
exports.isFormattedLine = isFormattedLine;
exports.splitTextAtTerms = splitTextAtTerms;
exports.wildcardMatch = wildcardMatch;
// :TODO: create Unicode version of this
const VOWELS = [
@ -196,6 +198,20 @@ function isPrintable(s) {
return !RE_NON_PRINTABLE.test(s);
}
const NonLatinCodePointsRegExp = /[^\u0000-\u00ff]/;
function containsNonLatinCodepoints(s) {
if (!s.length) {
return false;
}
if (s.charCodeAt(0) > 255) {
return true;
}
return NonLatinCodepointsRegEx.test(s);
}
function stripAllLineFeeds(s) {
return s.replace(/\r?\n|[\r\u2028\u2029]/g, '');
}
@ -459,3 +475,8 @@ function isAnsi(input) {
function splitTextAtTerms(s) {
return s.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
}
function wildcardMatch(input, rule) {
const escapeRegex = (s) => s.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
return new RegExp("^" + rule.split("*").map(escapeRegex).join(".*") + "$").test(input);
}

View File

@ -11,7 +11,16 @@ const async = require('async');
const _ = require('lodash');
const net = require('net');
const EventEmitter = require('events');
const buffers = require('buffers');
const {
TelnetSocket,
TelnetSpec :
{
Commands,
Options,
SubNegotiationCommands,
},
} = require('telnet-socket');
/*
Expected configuration block:
@ -33,7 +42,10 @@ exports.moduleInfo = {
author : 'Andrew Pamment',
};
const IAC_DO_TERM_TYPE = Buffer.from( [ 255, 253, 24 ] );
const IAC_DO_TERM_TYPE = TelnetSocket.commandBuffer(
Commands.DO,
Options.TTYPE,
);
class TelnetClientConnection extends EventEmitter {
constructor(client) {
@ -46,6 +58,7 @@ class TelnetClientConnection extends EventEmitter {
restorePipe() {
if(!this.pipeRestored) {
this.pipeRestored = true;
this.client.dataPassthrough = false;
// client may have bailed
if(null !== _.get(this, 'client.term.output', null)) {
@ -62,6 +75,7 @@ class TelnetClientConnection extends EventEmitter {
this.emit('connected');
this.pipeRestored = false;
this.client.dataPassthrough = true;
this.client.term.output.pipe(this.bridgeConnection);
});
@ -69,7 +83,7 @@ class TelnetClientConnection extends EventEmitter {
this.client.term.rawWrite(data);
//
// Wait for a terminal type request, and send it eactly once.
// Wait for a terminal type request, and send it exactly once.
// This is enough (in additional to other negotiations handled in telnet.js)
// to get us in on most systems
//
@ -110,25 +124,18 @@ class TelnetClientConnection extends EventEmitter {
// Create a TERMINAL-TYPE sub negotiation buffer using the
// actual/current terminal type.
//
let bufs = buffers();
bufs.push(Buffer.from(
const sendTermType = TelnetSocket.commandBuffer(
Commands.SB,
Options.TTYPE,
[
255, // IAC
250, // SB
24, // TERMINAL-TYPE
0, // IS
SubNegotiationCommands.IS,
...Buffer.from(this.client.term.termType), // e.g. "ansi"
Commands.IAC,
Commands.SE,
]
));
bufs.push(
Buffer.from(this.client.term.termType), // e.g. "ansi"
Buffer.from( [ 255, 240 ] ) // IAC, SE
);
return bufs.toBuffer();
return sendTermType;
}
}
exports.getModule = class TelnetBridgeModule extends MenuModule {

View File

@ -81,9 +81,9 @@ function userLogin(client, username, password, options, cb) {
if(existingClientConnection) {
client.log.info(
{
existingClientId : existingClientConnection.session.id,
username : user.username,
userId : user.userId
existingNodeId : existingClientConnection.node,
username : user.username,
userId : user.userId
},
'Already logged in'
);
@ -97,11 +97,12 @@ function userLogin(client, username, password, options, cb) {
// update client logger with addition of username
client.log = logger.log.child(
{
clientId : client.log.fields.clientId,
nodeId : client.log.fields.nodeId,
sessionId : client.log.fields.sessionId,
username : user.username,
}
);
client.log.info('Successful login');
// User's unique session identifier is the same as the connection itself

View File

@ -29,7 +29,7 @@ module.exports = {
UserComment : 'user_comment', // NYI
AutoSignature : 'auto_signature',
DownloadQueue : 'dl_queue', // download_queue.js
DownloadQueue : 'dl_queue', // see download_queue.js
FailedLoginAttempts : 'failed_login_attempts',
AccountLockedTs : 'account_locked_timestamp',
@ -64,5 +64,6 @@ module.exports = {
AuthFactor2OTP : 'auth_factor2_otp', // If present, OTP type for 2FA. See OTPTypes
AuthFactor2OTPSecret : 'auth_factor2_otp_secret', // Secret used in conjunction with OTP 2FA
AuthFactor2OTPBackupCodes : 'auth_factor2_otp_backup', // JSON array of backup codes
};

View File

@ -41,6 +41,8 @@
- [Message networks]({{ site.baseurl }}{% link messageareas/message-networks.md %})
- [BSO Import & Export]({{ site.baseurl }}{% link messageareas/bso-import-export.md %})
- [Netmail]({{ site.baseurl }}{% link messageareas/netmail.md %})
- [QWK]({{ site.baseurl }}{% link messageareas/qwk.md %})
- [FTN]({{ site.baseurl }}{% link messageareas/ftn.md %})
- Art
- [General]({{ site.baseurl }}{% link art/general.md %})
@ -65,6 +67,7 @@
- BBSLink
- Combatnet
- Exodus
- [Telnet Bridge]({{ site.baseurl }}{% link modding/telnet-bridge.md %})
- [Existing Mods]({{ site.baseurl }}{% link modding/existing-mods.md %})
- [File Area List]({{ site.baseurl }}{% link modding/file-area-list.md %})
- [Last Callers]({{ site.baseurl }}{% link modding/last-callers.md %})
@ -86,6 +89,7 @@
- [Auto Signature Editor]({{ site.baseurl }}{% link modding/autosig-edit.md %})
- Administration
- [Administration]({{ site.baseurl }}{% link admin/administration.md %})
- [oputil]({{ site.baseurl }}{% link admin/oputil.md %})
- [Updating]({{ site.baseurl }}{% link admin/updating.md %})

View File

@ -0,0 +1,43 @@
---
layout: page
title: Administration
---
# Administration
## Keeping Up to Date
See [Updating](updating.md).
## Viewing Logs
See [Monitoring Logs](/docs/troubleshooting/monitoring-logs.md).
## Managing Users
User management is currently handled via the [oputil CLI](oputil.md).
## Backing Up Your System
It is *highly* recommended to perform **regular backups** of your system. Nothing is worse than spending a lot of time setting up a system only to have to go away unexpectedly!
In general, simply creating a copy/archive of your system is enough for the default configuration. If you have changed default paths to point outside of your main ENiGMA½ installation take special care to ensure these are preserved as well. Database files may be in a state of flux when simply copying files. See **Database Backups** below for details on consistent backups.
### Database Backups
[SQLite's CLI backup command](https://sqlite.org/cli.html#special_commands_to_sqlite3_dot_commands_) can be used for creating database backup files. This can be performed as an additional step to a full backup to ensure the database is backed up in a consistent state (whereas simply copying the files does not make any guarantees).
As an example, consider the following Bash script that creates foo.sqlite3.backup files:
```bash
for dbfile in /path/to/enigma-bbs/db/*.sqlite3; do
sqlite3 $dbfile ".backup '/path/to/db_backup/$(basename $dbfile).backup'"
done
```
### Backup Tools
There are many backup solutions available across all platforms. Configuration of such tools is outside the scope of this documentation. With that said, the author has had great success with [Borg](https://www.borgbackup.org/).
## General Maintenance Tasks
### Vacuuming Database Files
SQLite database files become less performant over time and waste space. It is recommended to periodically vacuum your databases. Before proceeding, you should make a backup!
Example:
```bash
sqlite3 ./db/message.sqlite3 "vacuum;"
```

View File

@ -291,17 +291,32 @@ Actions:
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
qwk-dump PATH Dumps a QWK packet to stdout.
qwk-export [AREA_TAGS] PATH Exports one or more configured message area to a QWK
packet in the directory specified by PATH. The QWK
BBS ID will be obtained by the final component of PATH.
import-areas arguments:
--conf CONF_TAG Conference tag in which to import areas
--network NETWORK Network name/key to associate FTN areas
--uplinks UL1,UL2,... One or more uplinks (comma separated)
--type TYPE Area import type
Valid types are "bbs" and "na"
Valid types are "bbs" and "na".
qwk-export arguments:
--user USER User in which to export for. Defaults to the SysOp.
--after TIMESTAMP Export only messages with a timestamp later than
TIMESTAMP.
--no-qwke Disable QWKE extensions.
--no-synchronet Disable Synchronet style extensions.
```
| Action | Description | Examples |
|-----------|-------------------|---------------------------------------|
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` |
| `areafix` | Utility for sending AreaFix mails without logging into the system | |
| `qwk-dump` | Dump a QWK packet to stdout | `./oputil.js mb qwk-dump /path/to/XIBALBA.QWK` |
| `qwk-export` | Export messages to a QWK packet | `./oputil.js mb qwk-export /path/to/XIBALBA.QWK` |
When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".

View File

@ -2,19 +2,30 @@
layout: page
title: Updating
---
## Updating your Installation
Updating ENiGMA½ can be a bit of a learning curve compared to other systems. Especially when running off of a development branch (such as `0.0.9-alpha` being the recommended branch as of this writing), you'll want frequent updates.
# Updating
Keeping your system up to date ensures you have the latest fixes, features, and general improvements. Updating ENiGMA½ can be a bit of a learning curve compared to traditional binary-release systems you may be used to, especially when running from Git cloned source.
## Steps
In general the steps are as follows:
1. `cd /path/to/enigma-bbs`
2. `git pull`
3. `npm update` or `yarn` to refresh any new or updated modules.
4. Merge updates to `config/menu_template.hjson` to your `config/yourbbsname-menu.hjson` file.
## Updating From Source
If you have installed using Git source (if you used the `install.sh` script) follow these general steps to update your system:
1. **Back up your system**!
2. Pull down the latest source:
```bash
cd /path/to/enigma-bbs
git pull
```
3. :bulb: Review `WHATSNEW.md` and `UPDATE.md` for any specific instructions or changes to be aware of.
4. Update your dependencies:
```bash
npm install # or 'yarn'
```
4. Merge updates from `config/menu_template.hjson` to your `config/yourbbsname-menu.hjson` file (or simply use the template as a reference to spot any newly added default menus that you may wish to have on your system as well!).
5. If there are updates to the `art/themes/luciano_blocktronics/theme.hjson` file and you have a custom theme, you may want to look at them as well.
6. Finally, restart your running ENiGMA½ instance.
Visual diff tools such as [DiffMerge](https://www.sourcegear.com/diffmerge/downloads.php) (free, works on all major platforms) can be very helpful here.
:information_source: Visual diff tools such as [DiffMerge](https://www.sourcegear.com/diffmerge/downloads.php) (free, works on all major platforms) can be very helpful for the tasks outlined above!
:information_source: It is recommended to tail the logs and poke around a bit after an update.
Remember to also keep an eye on [WHATSNEW](/WHATSNEW.md) and [UPGRADE](/UPGRADE.md)!

View File

@ -7,13 +7,36 @@ One of the most basic elements of BBS customization is through it's artwork. ENi
As a general rule, art files live in one of two places:
1. The `art/general` directory. This is where you place command non-themed art files.
2. Within a theme such as `art/themes/super_fancy_theme`.
1. The `art/general` directory. This is where you place common/non-themed art files.
2. Within a _theme_ such as `art/themes/super_fancy_theme`.
### Menu Entries
While art can be displayed programmatically such as from a custom module, the most basic and common form is via `menu.hjson` entries. This usually falls into one of two forms: a "standard" entry where a single `art` spec is utilized or a entry for a custom module where multiple pieces are declared and used. The second style usually takes the form of a `config.art` block with two or more entries.
### Art in Menus
While art can be displayed programmatically such as from a custom module, the most basic and common form is via `menu.hjson` entries. This usually falls into one of two forms.
A menu entry has a few elements that control how art is choosen and displayed. First, the `art` *spec* tells teh system how to look for the art asset. Second, the `config` block can further control aspecs of lookup and display:
**Form 1**: A "standard" entry where a single `art` spec is utilized:
```hjson
{
mainMenu: {
art: main_menu.ans
}
}
```
**Form 2**: An entry for a custom module where multiple pieces are declared and used. The second style usually takes the form of a `config.art` block with two or more entries:
```hjson
{
nodeMessage: {
config: {
art: {
header: node_msg_header
footer: node_msg_footer
}
}
}
}
```
A menu entry has a few elements that control how art is selected and displayed. First, the `art` *spec* tells the system how to look for the art asset. Second, the `config` block can further control aspects of lookup and display. The following table describes such entries:
| Item | Description|
|------|------------|
@ -23,13 +46,13 @@ A menu entry has a few elements that control how art is choosen and displayed. F
| `cls` | Clear the screen before display if set to `true`. |
| `random` | Set to `false` to explicitly disable random lookup. |
| `types` | An optional array of types (aka file extensions) to consider for lookup. For example : `[ '.ans', '.asc' ]` |
| `readSauce` | May be set to `false` if you need to explictly disable SAUCE support. |
| `readSauce` | May be set to `false` if you need to explicitly disable SAUCE support. |
#### Art Spec
It was mentioned that the `art` member is a *spec*. The value of a `art` member controls how the system looks for an asset. The following forms are supported:
In the section above it is mentioned that the `art` member is a *spec*. The value of a `art` spec controls how the system looks for an asset. The following forms are supported:
* `FOO`: The system will look for `FOO.ANS`, `FOO.ASC`, `FOO.TXT`, etc. using the default search path. Unless otherwise specified if `FOO1.ANS`, `FOO2.ANS`, and so on exist, a random selection will be made.
* `FOO.ANS`: By specifying an extension, only that type will be searched for.
* `FOO.ANS`: By specifying an extension, only the exact match will be searched for.
* `rel/path/to/BAR.ANS`: Only match a path (relative to the system's `art` directory).
* `/path/to/BAZ.ANS`: Exact path only.
@ -40,8 +63,27 @@ ENiGMA½ uses a fallback system for art selection. When a menu entry calls for a
3. In the system default theme directory.
4. In the `art/general` directory.
#### ACS-Driven Conditionals
The [ACS](/docs/configuration/acs.md) system can be used to make conditional art selection choices. To do this, provide an array of possible values in your art spec. As an example:
```hjson
{
fancyMenu: {
art: [
{
acs: GM[l33t]
art: leet_art.ans
}
{
// default
art: newb.asc
}
]
}
}
```
#### SyncTERM Style Fonts
ENiGMA½ can set a [SyncTERM](http://syncterm.bbsdev.net/) style font for art display. This is supported by many popular BBS terminals besides just SyncTERM and is common for displaying Amiga style fonts for example. The system will use the `font` specifier or look for a font declared in an artworks SAUCE record (unless `readSauce` is `false`).
ENiGMA½ can set a [SyncTERM](http://syncterm.bbsdev.net/) style font for art display. This is supported by many other popular BBS terminals as well. A common usage is for displaying Amiga style fonts for example. The system will use the `font` specifier or look for a font declared in an artworks SAUCE record (unless `readSauce` is `false`).
The most common fonts are probably as follows:
@ -96,7 +138,7 @@ See [this specification](https://github.com/protomouse/synchronet/blob/master/sr
#### SyncTERM Style Baud Rates
The `baudRate` member can set a [SyncTERM](http://syncterm.bbsdev.net/) style emulated baud rate. May be `300`, `600`, `1200`, `2400`, `4800`, `9600`, `19200`, `38400`, `57600`, `76800`, or `115200`. A value of `ulimited`, `off`, or `0` resets (disables) the rate. See [this specification](https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt) for more information.
## Common Example
### Common Example
```hjson
fullLogoffSequenceRandomBoardAd: {
art: OTHRBBS
@ -109,3 +151,6 @@ fullLogoffSequenceRandomBoardAd: {
}
}
```
### See Also
See also the [Show Art Module](/docs/modding/show-art.md) for more advanced art display!

View File

@ -16,8 +16,8 @@ for a full listing. Many codes attempt to pay homage to Oblivion/2, iNiQUiTY, et
| Code | Description |
|------|--------------|
| `BN` | Board Name |
| `VL` | Version *label*, e.g. "ENiGMA½ v0.0.10-alpha" |
| `VN` | Version *number*, eg.. "0.0.10-alpha" |
| `VL` | Version *label*, e.g. "ENiGMA½ v0.0.11-beta" |
| `VN` | Version *number*, eg.. "0.0.11-beta" |
| `SN` | SysOp username |
| `SR` | SysOp real name |
| `SL` | SysOp location |

View File

@ -8,10 +8,10 @@ ENiGMA½ offers a powerful and flexible file base. Configuration of file the fil
## ENiGMA½ File Base Key Concepts
First, there are some core concepts you should understand:
* Storage Tags
* Areas (and Area Tags)
* Area Tags
### Storage Tags
*Storage Tags* define paths to physical (file) storage locations that are referenced in a file *Area* entry. Each entry may be either a fully qualified path or a relative path. Relative paths are relative to the value set by the `areaStoragePrefix` key (defaults to `/path/to/enigma-bbs/file_base`).
*Storage Tags* define paths to physical (filesystem) storage locations that are referenced in a file *Area* entry. Each entry may be either a fully qualified path or a relative path. Relative paths are relative to the value set by the `fileBase.areaStoragePrefix` key (defaults to `/path/to/enigma-bbs/file_base`).
Below is an example defining some storage tags using the relative and fully qualified forms:
@ -28,7 +28,7 @@ storageTags: {
:information_source: Remember that paths are case sensitive on most non-Windows systems!
### Areas
File base *Areas* are configured using the `fileBase::areas` configuration block in `config.hjson`. Valid members for an area are as follows:
File base *Areas* are configured using the `fileBase.areas` configuration block in `config.hjson`. Each entry's block starts with an *area tag*. Valid members for an area are as follows:
| Item | Required | Description |
|--------|---------------|------------------|
@ -41,7 +41,7 @@ Example areas section:
```hjson
areas: {
retro_pc: {
retro_pc: { // an area tag!
name: Retro PC
desc: Oldschool PC/DOS
storageTags: [ "retro_pc_dos", "retro_pc_bbs" ]
@ -55,6 +55,7 @@ This combines the two concepts described above. When viewing the file areas from
```hjson
fileBase: {
// override the default relative location
areaStoragePrefix: /enigma-bbs/file_base
storageTags: {

View File

@ -20,3 +20,9 @@ ENiGMA½ has strayed away from the old familiar setup here and instead takes a m
* Duplicates are checked for by cryptographically secure [SHA-256](https://en.wikipedia.org/wiki/SHA-2) hashes.
* Support for many archive and file formats. External utilities can easily be added to the configuration to extend for additional formats.
* Much, much more!
### Modding
The default ENiGMA½ approach for file areas may not be for everyone. Remember that you can mod everything your setup! Some inspirational examples:
* A more traditional set of areas and scrolling file listings.
* An S/X style integration of message areas and file areas.
* Something completely different! Some tweaks are possible without any code while others may require creating new JavaScript modules to use instead of the defaults.

View File

@ -2,7 +2,8 @@
layout: page
title: TIC Support
---
ENiGMA½ supports TIC files. This is handled by mapping TIC areas to local file areas.
## TIC Support
ENiGMA½ supports FidoNet-Style TIC file attachments by mapping TIC areas to local file areas.
Under a given node defined in the `ftn_bso` config section in `config.hjson` (see
[BSO Import/Export](../messageareas/bso-import-export)), TIC configuration may be supplied:
@ -28,11 +29,9 @@ Under a given node defined in the `ftn_bso` config section in `config.hjson` (se
}
```
You then need to configure the mapping between TIC areas you want to carry, and the file
base area and storage tag for them to be tossed to. Optionally you can also add hashtags to the tossed
files to assist users in searching for files:
You then need to configure the mapping between TIC areas you want to carry, and the file base area and storage tag for them to be tossed to. Optionally you can also add hashtags to the tossed files to assist users in searching for files:
````hjson
```hjson
ticAreas: {
agn_node: {
areaTag: msgNetworks
@ -41,14 +40,13 @@ ticAreas: {
}
}
````
```
Multiple TIC areas can be mapped to a single file base area.
## Example Configuration
### Example Configuration
An example configuration linking file base areas, FTN BSO node configuration and TIC area configuration.
An example configuration linking filebase areas, FTN BSO node configuration and TIC area configuration.
````hjson
```hjson
fileBase: {
areaStoragePrefix: /home/bbs/file_areas/
@ -97,4 +95,7 @@ ticAreas: {
hashTags: agoranet,infopack
}
}
````
```
## See Also
[Message Networks](/docs/messageareas/message-networks.md)

View File

@ -3,9 +3,9 @@ layout: page
title: Uploads
---
## Uploads
The default ACS for file areas areas in ENiGMA½ is to allow read (viewing of the area), and downloads for users while only permitting SysOps to write (upload). See [File Base ACS](acs.md) for more information.
The default ACS for file areas in ENiGMA½ is to allow regular users 'read' and sysops 'read/write'. Read ACS includes listing and downloading while write allows for uploading. See [File Base ACS](acs.md) for more information.
To allow uploads to a particular area, change the ACS level for `write`. For example:
Let's allow regular users (in the "users" group) to upload to an area:
```hjson
uploads: {
name: Uploads

View File

@ -6,10 +6,10 @@ title: Install Script
Under most Linux/UNIX like environments (Linux, BSD, OS X, ...) new users can simply execute the `install.sh` script to get everything up and running. Cut + paste the following into your terminal:
```
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/master/misc/install.sh | bash
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/0.0.11-beta/misc/install.sh | bash
```
You may review the [installation script](https://raw.githubusercontent.com/NuSkooler/enigma-bbs/master/misc/install.sh)
You may review the [installation script](https://raw.githubusercontent.com/NuSkooler/enigma-bbs/0.0.11-beta/misc/install.sh)
on GitHub before running it.
The script will install nvm, Node.js 6 and grab the latest ENiGMA BBS from GitHub. It will also guide you through creating a basic configuration file, and recommend some packages to install.

View File

@ -28,6 +28,8 @@ System started!
```
Grab your favourite telnet client, connect to localhost:8888 and test out your installation.
To shut down the server, press Ctrl-C.
## Points of Interest
* The default port for Telnet is 8888 and 8889 for SSH.

View File

@ -5,57 +5,22 @@ title: BSO Import / Export
## BSO Import / Export
The scanner/tosser module `ftn_bso` provides **B**inkley **S**tyle **O**utbound (BSO) import/toss and scan/export of messages EchoMail and NetMail messages. Configuration is supplied in `config.hjson` under `scannerTossers.ftn_bso`.
:information_source: ENiGMA½'s `ftn_bso` module is not a mailer and **makes no attempts** to perfrom packet transport! An external [mailer](http://www.filegate.net/bbsmailers.htm) such as [Binkd](https://github.com/pgul/binkd) is required for this!
:information_source: ENiGMA½'s `ftn_bso` module is not a mailer and **makes no attempts to perform packet transport**! An external [mailer](http://www.filegate.net/bbsmailers.htm) such as [Binkd](https://github.com/pgul/binkd) is required for this task.
### Configuration
Let's look at some of the basic configuration:
| Config Item | Required | Description |
|-------------|----------|----------------------------------------------------------|
| `schedule` | :+1: | Sets `import` and `export` schedules. [Later style text parsing](https://bunkat.github.io/later/parsers.html#text) supported. `import` also can utilize a `@watch:<path/to/file>` syntax while `export` additionally supports `@immediate`. |
| `schedule` | :+1: | Sets `import` and `export` schedules. [Later style text parsing](https://bunkat.github.io/later/parsers.html#text) supported. `import` also can utilize a `@watch:<path/to/file>` syntax while `export` additionally supports `@immediate`. |
| `packetMsgEncoding` | :-1: | Override default `utf8` encoding.
| `defaultNetwork` | :-1: | Explicitly set default network (by tag in `messageNetworks.ftn.networks`). If not set, the first found is used. |
| `nodes` | :+1: | Per-node settings. Entries (keys) here support wildcards for a portion of the FTN-style address (e.g.: `21:1/*`). `archiveType` may be set to a FTN supported archive extention that the system supports (TODO); if unset, only .PKT files are produced. `encoding` may be set to override `packetMsgEncoding` on a per-node basis. If the node requires a packet password, set `packetPassword` |
| `paths` | :-1: | An optional configuration block that can set a additional paths or override defaults. See "Paths" below. |
| `defaultNetwork` | :-1: | Explicitly set default network (by tag found within `messageNetworks.ftn.networks`). If not set, the first found is used. |
| `nodes` | :+1: | Per-node settings. Entries (keys) here support wildcards for a portion of the FTN-style address (e.g.: `21:1/*`). See **Nodes** below.
| `paths` | :-1: | An optional configuration block that can set a additional paths or override defaults. See **Paths** below. |
| `packetTargetByteSize` | :-1: | Overrides the system *target* packet (.pkt) size of 512000 bytes (512k) |
| `bundleTargetByteSize` | :-1: | Overrides the system *target* ArcMail bundle size of 2048000 bytes (2M) |
### Paths
Paths for packet files work out of the box and are relative to your install directory. If you want to configure `reject` or `retain` to keep rejected/imported packet files respectively, set those values. You may override defaults as well.
| Key | Description | Default |
|-----|-------------|---------|
| `outbound` | *Base* path to write outbound (exported) packet files and bundles. | `enigma-bbs/mail/ftn_out/` |
| `inbound` | *Base* path to write inbound (ie: those written by an external mailer) packet files an bundles. | `enigma-bbs/mail/ftn_in/` |
| `secInbound` | *Base* path to write **secure** inbound packet files and bundles. | `enigma-bbs/mail/ftn_secin/` |
| `reject` | Path in which to write rejected packet files. | No default |
| `retain` | Path in which to write imported packet files. Useful for debugging or if you wish to archive the raw .pkt files. | No default |
## Scheduling
Schedules can be defined for importing and exporting via `import` and `export` under `schedule`. Each entry is allowed a "free form" text and/or special indicators for immediate export or watch file triggers.
* `@immediate`: A message will be immediately exported if this trigger is defined in a schedule. Only used for `export`.
* `@watch:/path/to/file`: This trigger watches the path specified for changes and will trigger an import or export when such events occur. Only used for `import`.
* Free form [Later style](https://bunkat.github.io/later/parsers.html#text) text — can be things like `at 5:00 pm` or `every 2 hours`.
See [Later text parsing documentation](http://bunkat.github.io/later/parsers.html#text) for more information.
### Example Schedule Configuration
```hjson
{
scannerTossers: {
ftn_bso: {
schedule: {
import: every 1 hours or @watch:/path/to/watchfile.ext
export: every 1 hours or @immediate
}
}
}
}
```
## Nodes
#### Nodes
The `nodes` section defines how to export messages for one or more uplinks.
A node entry starts with a [FTN address](http://ftsc.org/docs/old/fsp-1028.001) (up to 5D) **as a key** in `config.hjson`. This key may contain wildcard(s) for net/zone/node/point/domain.
@ -65,7 +30,7 @@ A node entry starts with a [FTN address](http://ftsc.org/docs/old/fsp-1028.001)
| `packetType` | :-1: | `2`, `2.2`, or `2+`. Defaults to `2+` for modern mailer compatiability. |
| `packetPassword` | :-1: | Optional password for the packet |
| `encoding` | :-1: | Encoding to use for message bodies; Defaults to `utf-8`. |
| `archiveType` | :-1: | Specifies the archive type (by extension) for ArcMail bundles. This should be `zip` for most setups. Other valid examples include `arc`, `arj`, `lhz`, `pak`, `sqz`, or `zoo`. See [Archivers](docs/configuration/archivers.md) for more information. |
| `archiveType` | :-1: | Specifies the archive type (by extension or MIME type) for ArcMail bundles. This should be `zip` (or `application/zip`) for most setups. Other valid examples include `arc`, `arj`, `lhz`, `pak`, `sqz`, or `zoo`. See [Archivers](docs/configuration/archivers.md) for more information. |
**Example**:
```hjson
@ -85,7 +50,42 @@ A node entry starts with a [FTN address](http://ftsc.org/docs/old/fsp-1028.001)
}
```
## A More Complete Example
#### Paths
Paths for packet files work out of the box and are relative to your install directory. If you want to configure `reject` or `retain` to keep rejected/imported packet files respectively, set those values. You may override defaults as well.
| Key | Description | Default |
|-----|-------------|---------|
| `outbound` | *Base* path to write outbound (exported) packet files and bundles. | `enigma-bbs/mail/ftn_out/` |
| `inbound` | *Base* path to write inbound (ie: those written by an external mailer) packet files an bundles. | `enigma-bbs/mail/ftn_in/` |
| `secInbound` | *Base* path to write **secure** inbound packet files and bundles. | `enigma-bbs/mail/ftn_secin/` |
| `reject` | Path in which to write rejected packet files. | No default |
| `retain` | Path in which to write imported packet files. Useful for debugging or if you wish to archive the raw .pkt files. | No default |
### Scheduling
Schedules can be defined for importing and exporting via `import` and `export` under `schedule`. Each entry is allowed a "free form" text and/or special indicators for immediate export or watch file triggers.
* `@immediate`: A message will be immediately exported if this trigger is defined in a schedule. Only used for `export`.
* `@watch:/path/to/file`: This trigger watches the path specified for changes and will trigger an import or export when such events occur. Only used for `import`.
* Free form [Later style](https://bunkat.github.io/later/parsers.html#text) text — can be things like `at 5:00 pm` or `every 2 hours`.
See [Later text parsing documentation](http://bunkat.github.io/later/parsers.html#text) for more information.
#### Example Schedule Configuration
```hjson
{
scannerTossers: {
ftn_bso: {
schedule: {
import: every 1 hours or @watch:/path/to/watchfile.ext
export: every 1 hours or @immediate
}
}
}
}
```
### A More Complete Example
Below is a more complete example showing the sections described above.
```hjson
@ -149,7 +149,7 @@ do
done
```
Now, create an Event Scheuler entry in your `config.hjson`. As an example:
Now, create an Event Scheduler entry in your `config.hjson`. As an example:
```hjson
eventScheduler: {
events: {
@ -163,4 +163,4 @@ eventScheduler: {
```
## Additional Resources
* [Blog entry on setting up ENiGMA + Binkd on CentOS7](https://l33t.codes/enigma-12-binkd-on-centos-7/). Note that this references an **older version**, so be wary of the `config.hjson` refernces!
[Blog entry on setting up ENiGMA + Binkd on CentOS7](https://l33t.codes/enigma-12-binkd-on-centos-7/). Note that this references an **older version**, so be wary of the `config.hjson` references!

105
docs/messageareas/ftn.md Normal file
View File

@ -0,0 +1,105 @@
---
layout: page
title: FidoNet-Style Networks (FTN)
---
## FidoNet-Style Networks (FTN)
[FidoNet](https://en.wikipedia.org/wiki/FidoNet) proper and other FidoNet-Style networks are supported by ENiGMA½. A bit of configuration and you'll be up and running in no time!
### Configuration
Getting a fully running FTN enabled system requires a few configuration points:
1. `messageNetworks.ftn.networks`: Declares available networks. That is, networks you wish to sync up with.
2. `messageNetworks.ftn.areas`: Establishes local area mappings (ENiGMA½ to/from FTN area tags) and per-area specific configurations.
3. `scannerTossers.ftn_bso`: General configuration for the scanner/tosser (import/export) process. This is also where we configure per-node (uplink) settings.
:information_source: ENiGMA½'s `ftn_bso` module is **not a mailer** and makes **no attempts** to perform packet transport! An external utility such as Binkd is required for this task.
#### Networks
The `networks` block is a per-network configuration where each entry's ID (or "key") may be referenced elsewhere in `config.hjson`. For example, consider two networks: ArakNet (`araknet`) and fsxNet (`fsxnet`):
```hjson
{
messageNetworks: {
ftn: {
networks: {
// it is recommended to use lowercase network tags
fsxnet: {
defaultZone: 21
localAddress: "21:1/121"
}
araknet: {
defaultZone: 10
localAddress: "10:101/9"
}
}
}
}
}
```
#### Areas
The `areas` section describes a mapping of local **area tags** configured in your `messageConferences` (see [Configuring a Message Area](configuring-a-message-area.md)) to a message network (described above), a FTN specific area tag, and remote uplink address(s). This section can be thought of similar to the *AREAS.BBS* file used by other BBS packages.
When ENiGMA½ imports messages, they will be placed in the local area that matches key under `areas` while exported messages will be sent to the relevant `network`.
| Config Item | Required | Description |
|-------------|----------|----------------------------------------------------------|
| `network` | :+1: | Associated network from the `networks` section above |
| `tag` | :+1: | FTN area tag (ie: `FSX_GEN`) |
| `uplinks` | :+1: | An array of FTN address uplink(s) for this network |
Example:
```hjson
{
messageNetworks: {
ftn: {
areas: {
// it is recommended to use lowercase area tags
fsx_general: // *local* tag found within messageConferences
network: fsxnet // that we are mapping to this network
tag: FSX_GEN // ...and this remote FTN-specific tag
uplinks: [ "21:1/100" ] // a single string also allowed here
}
}
}
}
}
```
:information_source: You can import `AREAS.BBS` or FTN style `.NA` files using [oputil](/docs/admin/oputil.md)!
#### A More Complete Example
Below is a more complete *example* illustrating some of the concepts above:
```hjson
{
messageNetworks: {
ftn: {
networks: {
fsxnet: {
defaultZone: 21
localAddress: "21:1/121"
}
}
areas: {
fsx_general: {
network: fsxnet
// ie as found in your info packs .NA file
tag: FSX_GEN
uplinks: [ "21:1/100" ]
}
}
}
}
}
```
:information_source: Remember for a complete FTN experience, you'll probably also want to configure [FTN/BSO scanner/tosser](bso-import-export.md) settings.
#### FTN/BSO Scanner Tosser
Please see the [FTN/BSO Scanner/Tosser](bso-import-export.md) documentation for information on this area.

View File

@ -3,103 +3,22 @@ layout: page
title: Message Networks
---
## Message Networks
ENiGMA½ considers all non-ENiGMA½, non-local messages (and their networks, such as FTN "external". That is, messages are only imported and exported from/to such a networks. Configuring such external message networks in ENiGMA½ requires three sections in your `config.hjson`.
ENiGMA½ supports external networks such as FidoNet-Style (FTN) and QWK by the way of importing and exporting to/from it's own internal format. This allows for a very flexible system that can easily be extended by creating new network modules.
1. `messageNetworks.<networkType>.networks`: declares available networks.
2. `messageNetworks.<networkType>.areas`: establishes local area mappings and per-area specifics.
3. `scannerTossers.<name>`: general configuration for the scanner/tosser (import/export). This is also where we configure per-node settings.
All message network configuration occurs under the `messageNetworks.<name>` block in `config.hjson` (where name is something such as `ftn` or `qwk`). The most basic of external message network configurations generally comprises of two sections:
### FTN Networks
1. `messageNetworks.<name>.networks`: Global/general configuration for a particular network where `<name>` is for example `ftn` or `qwk`.
2. `messageNetworks.<name>.areas`: Provides mapping of ENiGMA½ **area tags** to their external counterparts.
:information_source: A related section under `scannerTossers.<name>` may provide configuration for scanning (importing) and tossing (exporting) messages for a particular network type. As an example, FidoNet-Style networks often work with BinkleyTerm Style Outbound (BSO) and thus the [FTN/BSO scanner/tosser](bso-import-export.md) (`ftn_bso`) module.
### Currently Supported Networks
The following networks are supported out of the box. Remember that you can create modules to add others if desired!
#### FidoNet-Style (FTN)
FidoNet and FidoNet style (FTN) networks as well as a [FTN/BSO scanner/tosser](bso-import-export.md) (`ftn_bso` module) are configured via the `messageNetworks.ftn` and `scannerTossers.ftn_bso` blocks in `config.hjson`.
:information_source: ENiGMA½'s `ftn_bso` module is **not a mailer** and makes **no attempts** to perform packet transport! An external utility such as Binkd is required for this!
See [FidoNet-Style Networks](ftn.md) for more information.
#### Networks
The `networks` block a per-network configuration where each entry's key may be referenced elsewhere in `config.hjson`.
Example: the following example declares two networks: `araknet` and `fsxnet`:
```hjson
{
messageNetworks: {
ftn: {
networks: {
// it is recommended to use lowercase network tags
fsxnet: {
defaultZone: 21
localAddress: "21:1/121"
}
araknet: {
defaultZone: 10
localAddress: "10:101/9"
}
}
}
}
}
```
#### Areas
The `areas` section describes a mapping of local **area tags** configured in your `messageConferences` (see [Configuring a Message Area](configuring-a-message-area.md)) to a message network (described above), a FTN specific area tag, and remote uplink address(s). This section can be thought of similar to the *AREAS.BBS* file used by other BBS packages.
When ENiGMA½ imports messages, they will be placed in the local area that matches key under `areas` while exported messages will be sent to the relevant `network`.
| Config Item | Required | Description |
|-------------|----------|----------------------------------------------------------|
| `network` | :+1: | Associated network from the `networks` section above |
| `tag` | :+1: | FTN area tag (ie: `FSX_GEN`) |
| `uplinks` | :+1: | An array of FTN address uplink(s) for this network |
Example:
```hjson
{
messageNetworks: {
ftn: {
areas: {
// it is recommended to use lowercase area tags
fsx_general: // *local* tag found within messageConferences
network: fsxnet // that we are mapping to this network
tag: FSX_GEN // ...and this remote FTN-specific tag
uplinks: [ "21:1/100" ] // a single string also allowed here
}
}
}
}
}
```
:information_source: You can import `AREAS.BBS` or FTN style `.NA` files using [oputil](/docs/admin/oputil.md)!
### A More Complete Example
Below is a more complete *example* illustrating some of the concepts above:
```hjson
{
messageNetworks: {
ftn: {
networks: {
fsxnet: {
defaultZone: 21
localAddress: "21:1/121"
}
}
areas: {
fsx_general: {
network: fsxnet
// ie as found in your info packs .NA file
tag: FSX_GEN
uplinks: [ "21:1/100" ]
}
}
}
}
}
```
:information_source: Remember for a complete FTN experience, you'll probably also want to configure [FTN/BSO scanner/tosser](bso-import-export.md) settings.
### FTN/BSO Scanner Tosser
Please see the [FTN/BSO Scanner/Tosser](bso-import-export.md) documentation for information on this area.
#### QWK
See [QWK and QWK-Net Style Networks](qwk.md) for more information.

47
docs/messageareas/qwk.md Normal file
View File

@ -0,0 +1,47 @@
---
layout: page
title: QWK Support
---
## QWK and QWK-Net Style Networks
As like all other networks such as FidoNet-Style (FTN) networks, ENiGMA½ considers QWK external to the system but can import and export the format.
### Supported Standards
QWK must be considered a semi-standard as there are many implementations. What follows is a short & incomplete list of such standards ENiGMA½ supports:
* The basic [QWK packet format](http://fileformats.archiveteam.org/wiki/QWK).
* [QWKE extensions](https://github.com/wwivbbs/wwiv/blob/master/specs/qwk/qwke.txt).
* [Synchronet BBS style extensions](http://wiki.synchro.net/ref:qwk) such as `HEADERS.DAT`, `@` kludges, and UTF-8 handling.
### Configuration
QWK configuration occurs in the `messageNetworks.qwk` config block of `config.hjson`. As QWK wants to deal with conference numbers and ENiGMA½ uses area tags (conferences and conference tags are only used for logical grouping), a mapping can be made.
:information_source: During a regular, non QWK-Net exports, conference numbers can be auto-generated. Note that for QWK-Net style networks, you will need to create mappings however.
Example:
```hjson
{
messageNetworks: {
qwk: {
areas: {
general: { // local ENiGMA½ area tag
conference: 1 // conference number to map to
}
}
}
}
}
```
### oputil
The `oputil.js` utility can export packet files, dump the messages of a packet to stdout, etc. See [the oputil documentation](/docs/admin/oputil.md) for more information.
### Offline Readers
A few of the offline readers that have been tested with QWK packet files produced by ENiGMA½:
| Software | Status | Notes |
|----------|--------|-------|
| MultiMail/Win v0.52 | Supported | Private mail seems to break even with bundles from other systems |
| SkyReader/W32 v1.00 | Supported | Works well. No QWKE or HEADERS.DAT support. Gets confused with low conference numbers. |
There are also [many other readers](https://www.softwolves.pp.se/old/2000/faq/bwprod) for various systems.

View File

@ -3,7 +3,7 @@ layout: page
title: Local Doors
---
## Local Doors
ENiGMA½ has many ways to add doors to your system. In addition to the many built in door server modules, local doors are of course also supported using the ! The `abracadabra` module!
ENiGMA½ has many ways to add doors to your system. In addition to the [many built in door server modules](door-servers.md), local doors are of course also supported using the ! The `abracadabra` module!
## The abracadabra Module
The `abracadabra` module provides a generic and flexible solution for many door types. Through this module you can execute native processes & scripts directly, and perform I/O through standard I/O (stdio) or a temporary TCP server.
@ -149,7 +149,7 @@ Please see the [bivrost!](https://github.com/NuSkooler/bivrost) documentation fo
Pre-built binaries of bivrost! have been released under [Phenom Productions](https://www.phenomprod.com/) and can be found on various boards.
#### Alternative Workarounds
Alternative workarounds include Telnet Bridge (`telnet_bridge` module) to hook up Telnet-accessible (including local) door servers -- It may also be possible bridge via [NET2BBS](http://pcmicro.com/netfoss/guide/net2bbs.html).
Alternative workarounds include [Telnet Bridge module](telnet-bridge.md) to hook up Telnet-accessible (including local) door servers -- It may also be possible bridge via [NET2BBS](http://pcmicro.com/netfoss/guide/net2bbs.html).
### QEMU with abracadabra
[QEMU](http://wiki.qemu.org/Main_Page) provides a robust, cross platform solution for launching doors under many platforms (likely anywhere Node.js is supported and ENiGMA½ can run). Note however that there is an important and major caveat: **Multiple instances of a particular door/OS image should not be run at once!** Being more flexible means being a bit more complex. Let's look at an example for running L.O.R.D. under a UNIX like system such as Linux or FreeBSD.
@ -223,8 +223,13 @@ doorLORD: {
}
```
## See Also
* [Telnet Bridge](telnet-bridge.md)
* [Door Servers](door-servers.md)
## Additional Resources
### DOSBox
### DOS Emulation
* [DOSEMU](http://www.dosemu.org/)
* [DOSBox-X](https://github.com/joncampbell123/dosbox-x)
### Door Downloads & Support Sites

View File

@ -29,8 +29,8 @@ showWithExtraArgs: {
If the `showWithExtraArgs` menu was entered and passed `extraArgs` as the following:
```json
{
fizzBang : true,
fooBaz : "LOLART"
"fizzBang" : true,
"fooBaz" : "LOLART"
}
```

View File

@ -0,0 +1,96 @@
---
layout: page
title: Telnet Bridge
---
## Telnet Bridge
The `telnet_bridge` module allows "bridged" Telnet connections from your board to other Telnet services (such as other BBSes!).
## Configuration
### Config Block
Available `config` entries:
* `host`: Hostname or IP address to connect to.
* `port`: Port to connect to. Defaults to the standard Telnet port of `23`.
* `font`: A SyncTERM style font. Useful for example if you would like to connect form a "DOS" style BBS to an Amiga. See [the general art documentation on SyncTERM Style Fonts](/docs/art/general.md).
### Example
Below is an example `menu.hjson` entry that would connect to [Xibalba](https://xibalba.l33t.codes):
```hjson
{
telnetBridgeXibalba: {
desc: Xibalba BBS
module: telnet_bridge
config: {
host: xibalba.l33t.codes
port: 45510
}
}
}
```
### Using Extra Args
The `telnet_bridge` module can also accept standard `extraArgs` of the same configuration arguments described above. This can be illustrated with an example:
```hjson
telnetBridgeMenu: {
desc: Telnet Bridge
art: telnet_bridge
config: {
font: cp437
}
form: {
0: {
mci: {
VM1: {
argName: selection
items: [
{
board: BLACK Flag
soft: Mystic
data: bf
}
{
board: Xibalba
soft: ENiGMA½
data: xib
}
]
// sort by 'board' fields above
sort: board
submit: true
}
}
submit: {
*: [
{
value: { "selection" : "bf" }
action: @menu:telnetBridgeFromExtraFlags
extraArgs: {
host: blackflag.acid.org
}
}
{
value: { "selection" : "xib" }
action: @menu:telnetBridgeFromExtraFlags
extraArgs: {
host: xibalba.l33t.codes
port: 44510
}
}
]
}
}
}
}
telnetBridgeFromExtraFlags: {
desc: Telnet Bridge
module: telnet_bridge
}
```
Here we've created a lightbar menu with custom items in which we'd use `itemFormat`'s with in a theme. When the user selects an item, the `telnetBridgeFromExtraFlags` menu is instantiated using the supplied `extraArgs`.

View File

@ -16,6 +16,7 @@ Entries available under `config.loginServers.ssh`:
| `firstMenuNewUser` | :-1: | Menu presented to user when logging in with one of the usernames found within `users.newUserNames` in your `config.hjson`. Examples include `new` and `apply`. |
| `enabled` | :+1: | Set to `true` to enable the SSH server. |
| `port` | :-1: | Override the default port of `8443`. |
| `address` | :-1: | Sets an explicit bind address. |
| `algorithms` | :-1: | Configuration block for SSH algorithms. Includes keys of `kex`, `cipher`, `hmac`, and `compress`. See the algorithms section in the [ssh2-streams](https://github.com/mscdex/ssh2-streams#ssh2stream-methods) documentation for details. For defaults set by ENiGMA½, see `core/config.js`.
| `traceConnections` | :-1: | Set to `true` to enable full trace-level information on SSH connections.

View File

@ -8,10 +8,11 @@ The Telnet *login server* provides a standard **non-secure** Telnet login experi
## Configuration
The following configuration can be made in `config.hjson` under the `loginServers.telnet` block:
| Item | Required | Description |
| Key | Required | Description |
|------|----------|-------------|
| `enabled` | :-1: Defaults to `true`. Set to `false` to disable Telnet |
| `port` | :-1: | Override the default port of `8888`. |
| `address` | :-1: | Sets an explicit bind address. |
| `firstMenu` | :-1: | First menu a telnet connected user is presented with. Defaults to `telnetConnected`. |
### Example Configuration

View File

@ -2,13 +2,10 @@
layout: page
title: Web Server
---
ENiGMA½ comes with a built in *content server* for supporting both HTTP and HTTPS. Currently the
[File Bases](file_base.md) registers routes for file downloads, and static files can also be served
for your BBS. Other features will likely come in the future or you can easily write your own!
ENiGMA½ comes with a built in *content server* for supporting both HTTP and HTTPS. Currently the [File Bases](file_base.md) registers routes for file downloads, password reset email links are handled via the server, and static files can also be served for your BBS. Other features will likely come in the future or you can easily write your own!
## Configuration
By default the web server is not enabled. To enable it, you will need to at a minimum configure two keys in
the `contentServers::web` section of `config.hjson`:
# Configuration
By default the web server is not enabled. To enable it, you will need to at a minimum configure two keys in the `contentServers.web` section of `config.hjson`:
```hjson
contentServers: {
@ -17,39 +14,44 @@ contentServers: {
http: {
enabled: true
port: 8080
}
}
}
```
This will configure HTTP for port 8080 (override with `port`). To additionally enable HTTPS, you will need a
PEM encoded SSL certificate and private key. [LetsEncrypt](https://letsencrypt.org/) supply free trusted
certificates that work perfectly with ENiGMA½.
The following is a table of all configuration keys available under `contentServers.web`:
| Key | Required | Description |
|------|----------|-------------|
| `domain` | :+1: | Sets the domain, e.g. `bbs.yourdomain.com`. |
| `http` | :-1: | Sub configuration for HTTP (non-secure) connections. See **HTTP Configuration** below. |
| `overrideUrlPrefix` | :-1: | Instructs the system to be explicit when handing out URLs. Useful if your server is behind a transparent proxy. |
Once obtained, simply enable the HTTPS server:
### HTTP Configuration
Entries available under `contentServers.web.http`:
```hjson
contentServers: {
web: {
domain: bbs.yourdomain.com
// set 'overrideUrlPrefix' if for example, you use a transparent proxy in front of ENiGMA and need to be explicit about URLs the system hands out
overrideUrlPrefix: https://bbs.yourdomain.com
https: {
enabled: true
port: 8443
certPem: /path/to/your/cert.pem
keyPem: /path/to/your/cert_private_key.pem
}
}
}
```
| Key | Required | Description |
|------|----------|-------------|
| `enable` | :+1: | Set to `true` to enable this server.
| `port` | :-1: | Override the default port of `8080`. |
| `address` | :-1: | Sets an explicit bind address. |
If no certificate paths are supplied, ENiGMA½ will assume the defaults of `/config/https_cert.pem` and
`/config/https_cert_key.pem` accordingly.
### HTTPS Configuration
Entries available under `contentServers.web.https`:
### Static Routes
Static files live relative to the `contentServers::web::staticRoot` path which defaults to `enigma-bbs/www`.
| Key | Required | Description |
|------|----------|-------------|
| `enable` | :+1: | Set to `true` to enable this server.
| `port` | :-1: | Override the default port of `8080`. |
| `address` | :-1: | Sets an explicit bind address. |
| `certPem` | :+1: | Overrides the default certificate path of `/config/https_cert.pem`. Certificate must be in PEM format. See **Certificates** below. |
| `keyPem` | :+1: | Overrides the default certificate key path of `/config/https_cert_key.pem`. Key must be in PEM format. See **Certificates** below. |
### Custom Error Pages
Customized error pages can be created for [HTTP error codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error)
by providing a `<error_code>.html` file in the *static routes* area. For example: `404.html`.
#### Certificates
If you don't have a TLS certificate for your domain, a good source for a certificate can be [LetsEncrypt](https://letsencrypt.org/) who supplies free and trusted TLS certificates.
## Static Routes
Static files live relative to the `contentServers.web.staticRoot` path which defaults to `enigma-bbs/www`.
## Custom Error Pages
Customized error pages can be created for [HTTP error codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error) by providing a `<error_code>.html` file in the *static routes* area. For example: `404.html`.

View File

@ -3,7 +3,7 @@ layout: page
title: Monitoring Logs
---
## Monitoring Logs
ENiGMA½ does not produce much to stdout. Logs are produced by Bunyan which outputs each entry as a JSON object.
ENiGMA½ does not produce much to stdout. Logs are produced by [Bunyan](https://github.com/trentm/node-bunyan) which outputs each entry as a JSON object.
Start by installing bunyan and making it available on your path:
@ -11,7 +11,7 @@ Start by installing bunyan and making it available on your path:
npm install bunyan -g
```
or with Yarn:
or via Yarn:
```bash
yarn global add bunyan
```

View File

@ -2,7 +2,7 @@
{ # this ensures the entire script is downloaded before execution
ENIGMA_NODE_VERSION=${ENIGMA_NODE_VERSION:=10}
ENIGMA_NODE_VERSION=${ENIGMA_NODE_VERSION:=12}
ENIGMA_BRANCH=${ENIGMA_BRANCH:=master}
ENIGMA_INSTALL_DIR=${ENIGMA_INSTALL_DIR:=$HOME/enigma-bbs}
ENIGMA_SOURCE=${ENIGMA_SOURCE:=https://github.com/NuSkooler/enigma-bbs.git}

View File

@ -1,6 +1,6 @@
{
"name": "enigma-bbs",
"version": "0.0.10-alpha",
"version": "0.0.11-beta",
"description": "ENiGMA½ Bulletin Board System",
"author": "Bryan Ashby <bryan@l33t.codes>",
"license": "BSD-2-Clause",
@ -22,44 +22,46 @@
"retro"
],
"dependencies": {
"async": "3.1.0",
"binary-parser": "^1.5.0",
"async": "3.2.0",
"binary-parser": "^1.6.2",
"buffers": "github:NuSkooler/node-buffers",
"bunyan": "^1.8.12",
"exiftool": "^0.0.3",
"fs-extra": "8.1.0",
"fs-extra": "9.0.0",
"glob": "7.1.6",
"graceful-fs": "^4.2.3",
"hashids": "2.1.0",
"graceful-fs": "^4.2.4",
"hashids": "2.2.1",
"hjson": "^3.2.1",
"iconv-lite": "0.5.0",
"inquirer": "^7.0.0",
"iconv-lite": "0.5.1",
"ini-config-parser": "^1.0.4",
"inquirer": "^7.1.0",
"later": "1.2.0",
"lodash": "^4.17.15",
"lru-cache": "^5.1.1",
"mime-types": "2.1.25",
"minimist": "1.2.0",
"moment": "^2.24.0",
"mime-types": "2.1.27",
"minimist": "1.2.5",
"moment": "^2.25.3",
"nntp-server": "^1.0.3",
"node-pty": "^0.9.0",
"nodemailer": "^6.3.1",
"nodemailer": "^6.4.6",
"otplib": "11.0.1",
"qrcode-generator": "^1.4.4",
"rlogin": "^1.0.0",
"sane": "4.1.0",
"sanitize-filename": "^1.6.3",
"sqlite3": "^4.1.0",
"sqlite3-trans": "^1.2.1",
"ssh2": "0.8.6",
"sqlite3": "^4.2.0",
"sqlite3-trans": "^1.2.2",
"ssh2": "0.8.9",
"temptmp": "^1.1.0",
"uuid": "^3.3.3",
"uuid": "^8.0.0",
"uuid-parse": "1.1.0",
"ws": "^7.2.0",
"ws": "^7.3.0",
"xxhash": "^0.3.0",
"yazl": "^2.5.1"
"yazl": "^2.5.1",
"telnet-socket" : "^0.2.3"
},
"devDependencies": {},
"engines": {
"node": ">=8"
"node": ">=12"
}
}

627
yarn.lock
View File

@ -10,21 +10,16 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
ajv@^5.3.0:
version "5.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
ansi-escapes@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
@ -47,12 +42,18 @@ ansi-regex@^4.1.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
dependencies:
color-convert "^1.9.0"
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
anymatch@^2.0.0:
version "2.0.0"
@ -107,53 +108,33 @@ array-unique@^0.3.2:
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
asn1@~0.2.0, asn1@~0.2.3:
asn1@~0.2.0:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
dependencies:
safer-buffer "~2.1.0"
assert-plus@1.0.0, assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
async-limiter@^1.0.0:
async@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
at-least-node@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
async@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.1.0.tgz#42b3b12ae1b74927b5217d8c0016baaf62463772"
integrity sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@ -172,17 +153,17 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
dependencies:
tweetnacl "^0.14.3"
binary-parser@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/binary-parser/-/binary-parser-1.5.0.tgz#3e50de3a5076badbacd760e833e7d94892b9e9fa"
integrity sha512-z+hqNSnO7trFDPLihjUGTwlSTbcIzLYSCwnbiasFkRvCIY9F3ZTex7Mlm9UAP3w5mfHD3KxejnWFPJjtsVVMuw==
binary-parser@1.6.2, binary-parser@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/binary-parser/-/binary-parser-1.6.2.tgz#8410a82ffd9403271ec182bd91e63a09cee88cbe"
integrity sha512-cYAhKB51A9T/uylDvMK7uAYaPLWLwlferNOpnQ0E0fuO73yPi7kWaWiOm22BvuKxCbggmkiFN0VkuLg6gc+KQQ==
brace-expansion@^1.1.7:
version "1.1.11"
@ -256,19 +237,13 @@ capture-exit@^2.0.0:
dependencies:
rsvp "^4.8.4"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chardet@^0.7.0:
version "0.7.0"
@ -302,16 +277,16 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
coffee-script@^1.12.4:
version "1.12.7"
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@ -320,31 +295,17 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "1.1.3"
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
combined-stream@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
integrity sha1-cj599ugBrFYTETp+RFqbactjKBg=
dependencies:
delayed-stream "~1.0.0"
combined-stream@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
dependencies:
delayed-stream "~1.0.0"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
component-emitter@^1.2.1:
version "1.2.1"
@ -366,7 +327,7 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
core-util-is@1.0.2, core-util-is@~1.0.0:
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
@ -382,13 +343,6 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
dependencies:
assert-plus "^1.0.0"
debug@^2.1.2, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -408,6 +362,11 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
deep-extend@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
@ -447,11 +406,6 @@ del@^3.0.0:
pify "^3.0.0"
rimraf "^2.2.8"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@ -479,14 +433,6 @@ dtrace-provider@~0.8:
dependencies:
nan "^2.10.0"
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@ -555,11 +501,6 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
external-editor@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27"
@ -583,26 +524,6 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
fb-watchman@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
@ -632,20 +553,6 @@ for-in@^1.0.2:
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=
dependencies:
asynckit "^0.4.0"
combined-stream "1.0.6"
mime-types "^2.1.12"
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@ -661,14 +568,15 @@ from2@^2.3.0:
inherits "^2.0.1"
readable-stream "^2.0.0"
fs-extra@8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
fs-extra@9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3"
integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==
dependencies:
at-least-node "^1.0.0"
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
jsonfile "^6.0.1"
universalify "^1.0.0"
fs-minipass@^1.2.5:
version "1.2.5"
@ -708,13 +616,6 @@ get-value@^2.0.3, get-value@^2.0.6:
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
dependencies:
assert-plus "^1.0.0"
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@ -771,28 +672,15 @@ graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
graceful-fs@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
graceful-fs@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29"
integrity sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==
dependencies:
ajv "^5.3.0"
har-schema "^2.0.0"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-unicode@^2.0.0:
version "2.0.1"
@ -830,29 +718,20 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
hashids@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/hashids/-/hashids-2.1.0.tgz#96eec6de3081a76dcd839650a6a26e6081e729d3"
integrity sha512-N53K2p7TrwKLNHKHcEDH+qpiAgO9JfyPEg8Tfy4fB9AcVhwxlTanJ55HVV9BQJQ6ajM1Wfmtl2wgKuEbcucolw==
hashids@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/hashids/-/hashids-2.2.1.tgz#ad0c600f0083aa0df7451dfd184e53db34f71289"
integrity sha512-+hQeKWwpSDiWFeu/3jKUvwboE4Z035gR6FnpscbHPOEEjCbgv2px9/Mlb3O0nOTRyZOw4MMFRYfVL3zctOV6OQ==
hjson@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/hjson/-/hjson-3.2.1.tgz#20de41dc87fc9a10d1557d0230b0e02afb1b09ac"
integrity sha512-OhhrFMeC7dVuA1xvxuXGTv/yTdhTvbe8hz+3LgVNsfi9+vgz0sF/RrkuX8eegpKaMc9cwYwydImBH6iePoJtdQ==
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
dependencies:
assert-plus "^1.0.0"
jsprim "^1.2.2"
sshpk "^1.7.0"
iconv-lite@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.0.tgz#59cdde0a2a297cc2aeb0c6445a195ee89f127550"
integrity sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==
iconv-lite@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.1.tgz#b2425d3c7b18f7219f2ca663d103bddb91718d64"
integrity sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==
dependencies:
safer-buffer ">= 2.1.2 < 3"
@ -883,28 +762,37 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini-config-parser@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/ini-config-parser/-/ini-config-parser-1.0.4.tgz#0abc75cb68c506204712d2b4861400b6adbfda78"
integrity sha512-5hLh5Cqai67pTrLQ9q/K/3EtSP2Tzu41AZzwPLSegkkMkc42dGweLgkbiocCBiBBEg2fPhs6pKmdFhwj5Ul3Bg==
dependencies:
coffee-script "^1.12.4"
deep-extend "^0.5.1"
rimraf "^2.6.1"
ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
inquirer@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a"
integrity sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==
inquirer@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29"
integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
dependencies:
ansi-escapes "^4.2.1"
chalk "^2.4.2"
chalk "^3.0.0"
cli-cursor "^3.1.0"
cli-width "^2.0.0"
external-editor "^3.0.3"
figures "^3.0.0"
lodash "^4.17.15"
mute-stream "0.0.8"
run-async "^2.2.0"
rxjs "^6.4.0"
run-async "^2.4.0"
rxjs "^6.5.3"
string-width "^4.1.0"
strip-ansi "^5.1.0"
strip-ansi "^6.0.0"
through "^2.3.6"
is-accessor-descriptor@^0.1.6:
@ -1020,21 +908,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@ -1062,48 +940,15 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
json-schema-traverse@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
jsonfile@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
dependencies:
universalify "^1.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
dependencies:
assert-plus "1.0.0"
extsprintf "1.3.0"
json-schema "0.2.3"
verror "1.10.0"
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@ -1138,11 +983,6 @@ lodash@^4.17.15:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
lodash@^4.17.4:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@ -1188,29 +1028,17 @@ micromatch@^3.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.2"
mime-db@1.42.0:
version "1.42.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
mime-db@1.44.0:
version "1.44.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
mime-db@~1.36.0:
version "1.36.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397"
integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==
mime-types@2.1.25:
version "2.1.25"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437"
integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==
mime-types@2.1.27:
version "2.1.27"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
dependencies:
mime-db "1.42.0"
mime-types@^2.1.12, mime-types@~2.1.19:
version "2.1.20"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
integrity sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==
dependencies:
mime-db "~1.36.0"
mime-db "1.44.0"
mimic-fn@^2.1.0:
version "2.1.0"
@ -1229,7 +1057,12 @@ minimist@0.0.8:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0:
minimist@1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minimist@^1.1.1, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
@ -1269,10 +1102,10 @@ moment@^2.10.6:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
moment@^2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
moment@^2.25.3:
version "2.25.3"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.25.3.tgz#252ff41319cf41e47761a1a88cab30edfe9808c0"
integrity sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==
ms@2.0.0:
version "2.0.0"
@ -1388,10 +1221,10 @@ node-pty@^0.9.0:
dependencies:
nan "^2.14.0"
nodemailer@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346"
integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ==
nodemailer@^6.4.6:
version "6.4.6"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.6.tgz#d37f504f6560b36616f646a606894fe18819107f"
integrity sha512-/kJ+FYVEm2HuUlw87hjSqTss+GU35D4giOpdSfGp7DO+5h6RlJj7R94YaYHOkoxu1CSaM0d3WRBtCzwXrY6MKA==
nopt@^4.0.1:
version "4.0.1"
@ -1443,11 +1276,6 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -1545,11 +1373,6 @@ path-key@^2.0.0, path-key@^2.0.1:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@ -1582,11 +1405,6 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
psl@^1.1.24:
version "1.1.29"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
@ -1595,21 +1413,11 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
qrcode-generator@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7"
integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@ -1665,32 +1473,6 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
request@^2.87.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
caseless "~0.12.0"
combined-stream "~1.0.6"
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.0"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
oauth-sign "~0.9.0"
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.4.3"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@ -1733,21 +1515,19 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
dependencies:
is-promise "^2.1.0"
run-async@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
rxjs@^6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504"
integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==
rxjs@^6.5.3:
version "6.5.5"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
dependencies:
tslib "^1.9.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@ -1764,7 +1544,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
"safer-buffer@>= 2.1.2 < 3", safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -1918,53 +1698,36 @@ split2@^3.0.0:
dependencies:
readable-stream "^3.0.0"
sqlite3-trans@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/sqlite3-trans/-/sqlite3-trans-1.2.1.tgz#642dff9f6da53d533ccd264b49e68c8818542255"
integrity sha512-KLtR+PBZN/moxDTKWTwWypkunDCJ0oi5vknjht8omjUXswwUEf+MX2DKtgQB1V5Tsjgc4mL4mHjv9zp7+FHs5g==
sqlite3-trans@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/sqlite3-trans/-/sqlite3-trans-1.2.2.tgz#faf268cc8d04dfd1a4854d64a70a229bdb50609f"
integrity sha512-+c2je0JMgPeNYHM7vMwEv/nHqOMYa5NNgQDcUyFkVMJ5QHATOQ+GywJptlVbkRCjgSTctmighfWLwUHPlkXbSQ==
dependencies:
lodash "^4.17.4"
lodash "^4.17.15"
sqlite3@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.1.0.tgz#e051fb9c133be15726322a69e2e37ec560368380"
integrity sha512-RvqoKxq+8pDHsJo7aXxsFR18i+dU2Wp5o12qAJOV5LNcDt+fgJsc2QKKg3sIRfXrN9ZjzY1T7SNe/DFVqAXjaw==
sqlite3@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901"
integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==
dependencies:
nan "^2.12.1"
node-pre-gyp "^0.11.0"
request "^2.87.0"
ssh2-streams@~0.4.7:
version "0.4.7"
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.7.tgz#093b89069de9cf5f06feff0601a5301471b01611"
integrity sha512-JhF8BNfeguOqVHOLhXjzLlRKlUP8roAEhiT/y+NcBQCqpRUupLNrRf2M+549OPNVGx21KgKktug4P3MY/IvTig==
ssh2-streams@~0.4.10:
version "0.4.10"
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
dependencies:
asn1 "~0.2.0"
bcrypt-pbkdf "^1.0.2"
streamsearch "~0.1.2"
ssh2@0.8.6:
version "0.8.6"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.6.tgz#dcc62e1d3b9e58a21f711f5186f043e4e792e6da"
integrity sha512-T0cPmEtmtC8WxSupicFDjx3vVUdNXO8xu2a/D5bjt8ixOUCe387AgvxU3mJgEHpu7+Sq1ZYx4d3P2pl/yxMH+w==
ssh2@0.8.9:
version "0.8.9"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
dependencies:
ssh2-streams "~0.4.7"
sshpk@^1.7.0:
version "1.14.2"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
integrity sha1-xvxhZIo9nE52T9P8306hBeSSupg=
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
dashdash "^1.12.0"
getpass "^0.1.1"
safer-buffer "^2.0.2"
optionalDependencies:
bcrypt-pbkdf "^1.0.0"
ecc-jsbn "~0.1.1"
jsbn "~0.1.0"
tweetnacl "~0.14.0"
ssh2-streams "~0.4.10"
static-extend@^0.1.1:
version "0.1.2"
@ -2033,13 +1796,20 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^5.1.0, strip-ansi@^5.2.0:
strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
dependencies:
ansi-regex "^5.0.0"
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@ -2050,12 +1820,12 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
dependencies:
has-flag "^3.0.0"
has-flag "^4.0.0"
tar@^4:
version "4.4.6"
@ -2070,6 +1840,14 @@ tar@^4:
safe-buffer "^5.1.2"
yallist "^3.0.2"
telnet-socket@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/telnet-socket/-/telnet-socket-0.2.3.tgz#0ffdc64ea957cb64f8ac5287d45a857f1c05a16e"
integrity sha512-PbZycTkGq6VcVUa35FYFySx4pCzmJo4xoMX6cimls1/kv/lrgMfddKfgjBKt6HQuokkkDfieDhGLq/L/P2Unaw==
dependencies:
binary-parser "1.6.2"
buffers "github:NuSkooler/node-buffers"
temptmp@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/temptmp/-/temptmp-1.1.0.tgz#bfbbff858d7f7d59c563fbf069758a7775ecd431"
@ -2132,14 +1910,6 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
truncate-utf8-bytes@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
@ -2152,14 +1922,7 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
dependencies:
safe-buffer "^5.0.1"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
tweetnacl@^0.14.3:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
@ -2179,10 +1942,10 @@ union-value@^1.0.0:
is-extendable "^0.1.1"
set-value "^0.4.3"
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
unset-value@^1.0.0:
version "1.0.0"
@ -2217,24 +1980,10 @@ uuid-parse@1.1.0:
resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.1.0.tgz#7061c5a1384ae0e1f943c538094597e1b5f3a65b"
integrity sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==
uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
uuid@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
dependencies:
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"
uuid@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c"
integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
walker@~1.0.5:
version "1.0.7"
@ -2262,12 +2011,10 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.0.tgz#422eda8c02a4b5dba7744ba66eebbd84bcef0ec7"
integrity sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==
dependencies:
async-limiter "^1.0.0"
ws@^7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
xtend@~4.0.1:
version "4.0.1"