Merge branch 'master' of ssh://numinibsd/git/base/enigma-bbs

This commit is contained in:
Bryan Ashby 2016-10-03 20:49:38 -06:00
commit d1653d620d
13 changed files with 385 additions and 303 deletions

View File

@ -223,15 +223,14 @@ function initialize(cb) {
} }
function startListening(cb) { function startListening(cb) {
if(!conf.config.servers) { if(!conf.config.loginServers) {
// :TODO: Log error ... output to stderr as well. We can do it all with the logger // :TODO: Log error ... output to stderr as well. We can do it all with the logger
//logger.log.error('No servers configured'); return cb(new Error('No login servers configured'));
return cb(new Error('No servers configured'));
} }
const moduleUtil = require('./module_util.js'); // late load so we get Config const moduleUtil = require('./module_util.js'); // late load so we get Config
moduleUtil.loadModulesForCategory('servers', (err, module) => { moduleUtil.loadModulesForCategory('loginServers', (err, module) => {
if(err) { if(err) {
if('EENIGMODDISABLED' === err.code) { if('EENIGMODDISABLED' === err.code) {
logger.log.debug(err.message); logger.log.debug(err.message);

View File

@ -131,15 +131,16 @@ ClientTerminal.prototype.isANSI = function() {
// ansi-bbs: // ansi-bbs:
// * fTelnet // * fTelnet
// //
// pcansi:
// * ZOC
//
// screen: // screen:
// * ConnectBot (Android) // * ConnectBot (Android)
// //
// linux: // linux:
// * JuiceSSH (note: TERM=linux also) // * JuiceSSH (note: TERM=linux also)
// //
return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm' ].indexOf(this.termType) > -1;
// :TODO: Others??
return [ 'ansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm' ].indexOf(this.termType) > -1;
}; };
// :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it) // :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it)

View File

@ -26,7 +26,7 @@ function hasMessageConferenceAndArea(config) {
// :TODO: there is likely a better/cleaner way of doing this // :TODO: there is likely a better/cleaner way of doing this
var result = false; let result = false;
_.forEach(nonInternalConfs, confTag => { _.forEach(nonInternalConfs, confTag => {
if(_.has(config.messageConferences[confTag], 'areas') && if(_.has(config.messageConferences[confTag], 'areas') &&
Object.keys(config.messageConferences[confTag].areas) > 0) Object.keys(config.messageConferences[confTag].areas) > 0)
@ -78,15 +78,15 @@ function init(configPath, cb) {
if(hasMessageConferenceAndArea(mergedConfig)) { if(hasMessageConferenceAndArea(mergedConfig)) {
var msgAreasErr = new Error('Please create at least one message conference and area!'); var msgAreasErr = new Error('Please create at least one message conference and area!');
msgAreasErr.code = 'EBADCONFIG'; msgAreasErr.code = 'EBADCONFIG';
callback(msgAreasErr); return callback(msgAreasErr);
} else { } else {
callback(null, mergedConfig); return callback(null, mergedConfig);
} }
} }
], ],
function complete(err, mergedConfig) { function complete(err, mergedConfig) {
exports.config = mergedConfig; exports.config = mergedConfig;
cb(err); return cb(err);
} }
); );
} }
@ -171,7 +171,8 @@ function getDefaultConfig() {
paths : { paths : {
mods : paths.join(__dirname, './../mods/'), mods : paths.join(__dirname, './../mods/'),
servers : paths.join(__dirname, './servers/'), loginServers : paths.join(__dirname, './servers/login/'),
contentServers : paths.join(__dirname, './servers/content/'),
scannerTossers : paths.join(__dirname, './scanner_tossers/'), scannerTossers : paths.join(__dirname, './scanner_tossers/'),
mailers : paths.join(__dirname, './mailers/') , mailers : paths.join(__dirname, './mailers/') ,
@ -185,7 +186,7 @@ function getDefaultConfig() {
misc : paths.join(__dirname, './../misc/'), misc : paths.join(__dirname, './../misc/'),
}, },
servers : { loginServers : {
telnet : { telnet : {
port : 8888, port : 8888,
enabled : true, enabled : true,
@ -267,6 +268,23 @@ function getDefaultConfig() {
} }
}, },
fileBase: {
// areas with an explicit |storageDir| will be stored relative to |areaStoragePrefix|:
areaStoragePrefix : paths.join(__dirname, './../file_base/'),
fileNamePatterns: {
shortDesc : [ '^FILE_ID\.DIZ$', '^DESC\.SDI$' ],
longDesc : [ '^.*\.NFO$', '^README\.1ST$', '^README\.TXT$' ],
},
areas: {
message_attachment : {
name : 'Message attachments',
desc : 'File attachments to messages',
}
}
},
eventScheduler : { eventScheduler : {

View File

@ -47,57 +47,25 @@ function getModDatabasePath(moduleInfo, suffix) {
} }
function initializeDatabases(cb) { function initializeDatabases(cb) {
async.series( async.each( [ 'system', 'user', 'message', 'file' ], (dbName, next) => {
[ dbs[dbName] = new sqlite3.Database(getDatabasePath(dbName), err => {
function systemDb(callback) {
dbs.system = new sqlite3.Database(getDatabasePath('system'), err => {
if(err) { if(err) {
return callback(err); return cb(err);
} }
dbs.system.serialize( () => { dbs[dbName].serialize( () => {
createSystemTables(); DB_INIT_TABLE[dbName]();
});
return callback(null); return next(null);
}); });
},
function userDb(callback) {
dbs.user = new sqlite3.Database(getDatabasePath('user'), err => {
if(err) {
return callback(err);
}
dbs.user.serialize( () => {
createUserTables();
createInitialUserValues();
}); });
}, err => {
return callback(null); return cb(err);
}); });
},
function messageDb(callback) {
dbs.message = new sqlite3.Database(getDatabasePath('message'), err => {
if(err) {
return callback(err);
}
dbs.message.serialize(function serialized() {
createMessageBaseTables();
createInitialMessageValues();
});
return callback(null);
});
}
],
cb
);
} }
function createSystemTables() { const DB_INIT_TABLE = {
system : () => {
dbs.system.run('PRAGMA foreign_keys = ON;'); dbs.system.run('PRAGMA foreign_keys = ON;');
// Various stat/event logging - see stat_log.js // Various stat/event logging - see stat_log.js
@ -130,9 +98,9 @@ function createSystemTables() {
UNIQUE(timestamp, user_id, log_name) UNIQUE(timestamp, user_id, log_name)
);` );`
); );
} },
function createUserTables() { user : () => {
dbs.user.run('PRAGMA foreign_keys = ON;'); dbs.user.run('PRAGMA foreign_keys = ON;');
dbs.user.run( dbs.user.run(
@ -170,10 +138,9 @@ function createUserTables() {
timestamp DATETIME NOT NULL timestamp DATETIME NOT NULL
);` );`
); );
} },
function createMessageBaseTables() {
message : () => {
dbs.message.run('PRAGMA foreign_keys = ON;'); dbs.message.run('PRAGMA foreign_keys = ON;');
dbs.message.run( dbs.message.run(
@ -270,10 +237,83 @@ function createMessageBaseTables() {
UNIQUE(scan_toss, area_tag) UNIQUE(scan_toss, area_tag)
);` );`
); );
} },
function createInitialMessageValues() { file : () => {
} dbs.file.run('PRAGMA foreign_keys = ON;');
function createInitialUserValues() { dbs.file.run(
} // :TODO: should any of this be unique??
`CREATE TABLE IF NOT EXISTS file (
file_id INTEGER PRIMARY KEY,
area_tag VARCHAR NOT NULL,
file_sha1 VARCHAR NOT NULL,
file_name, /* FTS @ file_fts */
desc, /* FTS @ file_fts */
desc_long, /* FTS @ file_fts */
upload_by_username VARCHAR NOT NULL,
upload_timestamp DATETIME NOT NULL
);`
);
dbs.file.run(
`CREATE INDEX IF NOT EXISTS file_by_area_tag_index
ON file (area_tag);`
);
dbs.file.run(
`CREATE VIRTUAL TABLE IF NOT EXISTS file_fts USING fts4 (
content="file",
file_name,
desc,
desc_long
);`
);
dbs.file.run(
`CREATE TRIGGER IF NOT EXISTS file_before_update BEFORE UPDATE ON file BEGIN
DELETE FROM file_fts WHERE docid=old.rowid;
END;
CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
DELETE FROM file_fts WHERE docid=old.rowid;
END;
CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
INSERT INTO file_fts(docid, file_name, desc, long_desc) VALUES(new.rowid, new.file_name, new.desc, new.long_desc);
END;
CREATE TRIGGER IF NOT EXISTS file_after_insert AFTER INSERT ON file BEGIN
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.long_desc);
END;`
);
dbs.file.run(
`CREATE TABLE IF NOT EXISTS file_meta (
file_id INTEGER NOT NULL,
meta_name VARCHAR NOT NULL,
meta_value VARCHAR NOT NULL,
UNIQUE(file_id, meta_name, meta_value),
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
);`
);
dbs.file.run(
`CREATE TABLE IF NOT EXISTS hash_tag (
hash_tag_id INTEGER PRIMARY KEY,
hash_tag VARCHAR NOT NULL,
UNIQUE(hash_tag)
);`
);
dbs.file.run(
`CREATE TABLE IF NOT EXISTS file_hash_tag (
hash_tag_id INTEGER NOT NULL,
file_id INTEGER NOT NULL,
UNIQUE(hash_tag_id, file_id)
);`
);
}
};

View File

@ -2,11 +2,14 @@
'use strict'; 'use strict';
class EnigError extends Error { class EnigError extends Error {
constructor(message) { constructor(message, code, reason, reasonCode) {
super(message); super(message);
this.name = this.constructor.name; this.name = this.constructor.name;
this.message = message; this.message = message;
this.code = code;
this.reason = reason;
this.reasonCode = reasonCode;
if(typeof Error.captureStackTrace === 'function') { if(typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
@ -16,4 +19,12 @@ class EnigError extends Error {
} }
} }
class EnigMenuError extends EnigError { }
exports.EnigError = EnigError; exports.EnigError = EnigError;
exports.EnigMenuError = EnigMenuError;
exports.Errors = {
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode),
MenuStack : (reason, reasonCode) => new EnigMenuError('Menu stack error', -33001, reason, reasonCode),
};

View File

@ -8,6 +8,7 @@ const ansi = require('./ansi_term.js');
const theme = require('./theme.js'); const theme = require('./theme.js');
const Message = require('./message.js'); const Message = require('./message.js');
const updateMessageAreaLastReadId = require('./message_area.js').updateMessageAreaLastReadId; const updateMessageAreaLastReadId = require('./message_area.js').updateMessageAreaLastReadId;
const getMessageAreaByTag = require('./message_area.js').getMessageAreaByTag;
const getUserIdAndName = require('./user.js').getUserIdAndName; const getUserIdAndName = require('./user.js').getUserIdAndName;
const cleanControlCodes = require('./string_util.js').cleanControlCodes; const cleanControlCodes = require('./string_util.js').cleanControlCodes;
const StatLog = require('./stat_log.js'); const StatLog = require('./stat_log.js');
@ -563,7 +564,13 @@ function FullScreenEditorModule(options) {
break; break;
case 'edit' : case 'edit' :
self.viewControllers.header.getView(1).setText(self.client.user.username); // from const fromView = self.viewControllers.header.getView(1);
const area = getMessageAreaByTag(self.messageAreaTag);
if(area && area.realNames) {
fromView.setText(self.client.user.properties.real_name || self.client.user.username);
} else {
fromView.setText(self.client.user.username);
}
if(self.replyToMessage) { if(self.replyToMessage) {
self.initHeaderReplyEditMode(); self.initHeaderReplyEditMode();

View File

@ -3,6 +3,7 @@
// ENiGMA½ // ENiGMA½
const loadMenu = require('./menu_util.js').loadMenu; const loadMenu = require('./menu_util.js').loadMenu;
const Errors = require('./enig_error.js').Errors;
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
@ -57,16 +58,16 @@ module.exports = class MenuStack {
if(_.isArray(menuConfig.next)) { if(_.isArray(menuConfig.next)) {
nextMenu = this.client.acs.getConditionalValue(menuConfig.next, 'next'); nextMenu = this.client.acs.getConditionalValue(menuConfig.next, 'next');
if(!nextMenu) { if(!nextMenu) {
return cb(new Error('No matching condition for \'next\'!')); return cb(Errors.MenuStack('No matching condition for "next"', 'NOCONDMATCH'));
} }
} else if(_.isString(menuConfig.next)) { } else if(_.isString(menuConfig.next)) {
nextMenu = menuConfig.next; nextMenu = menuConfig.next;
} else { } else {
return cb(new Error('Invalid or missing \'next\' member in menu config!')); return cb(Errors.MenuStack('Invalid or missing "next" member in menu config', 'BADNEXT'));
} }
if(nextMenu === currentModuleInfo.name) { if(nextMenu === currentModuleInfo.name) {
return cb(new Error('Menu config \'next\' specifies current menu!')); return cb(Errors.MenuStack('Menu config "next" specifies current menu', 'ALREADYTHERE'));
} }
this.goto(nextMenu, { }, cb); this.goto(nextMenu, { }, cb);
@ -90,7 +91,7 @@ module.exports = class MenuStack {
return this.goto(previousModuleInfo.name, opts, cb); return this.goto(previousModuleInfo.name, opts, cb);
} }
return cb(new Error('No previous menu available!')); return cb(Errors.MenuStack('No previous menu available', 'NOPREV'));
} }
goto(name, options, cb) { goto(name, options, cb) {
@ -104,7 +105,7 @@ module.exports = class MenuStack {
if(currentModuleInfo && name === currentModuleInfo.name) { if(currentModuleInfo && name === currentModuleInfo.name) {
if(cb) { if(cb) {
cb(new Error('Already at supplied menu!')); cb(Errors.MenuStack('Already at supplied menu', 'ALREADYTHERE'));
} }
return; return;
} }

View File

@ -139,7 +139,7 @@ Message.createMessageUUID = function(areaTag, modTimestamp, subject, body) {
body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437'); body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437');
return uuid.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] ))); return uuid.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] )));
} };
Message.getMessageIdByUuid = function(uuid, cb) { Message.getMessageIdByUuid = function(uuid, cb) {
msgDb.get( msgDb.get(
@ -351,13 +351,13 @@ Message.prototype.persist = function(cb) {
return cb(new Error('Cannot persist invalid message!')); return cb(new Error('Cannot persist invalid message!'));
} }
let self = this; const self = this;
async.series( async.series(
[ [
function beginTransaction(callback) { function beginTransaction(callback) {
Message.startTransaction(err => { Message.startTransaction(err => {
callback(err); return callback(err);
}); });
}, },
function storeMessage(callback) { function storeMessage(callback) {
@ -375,19 +375,19 @@ Message.prototype.persist = function(cb) {
`INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp) `INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`, VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
[ self.areaTag, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, self.getMessageTimestampString(msgTimestamp) ], [ self.areaTag, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, self.getMessageTimestampString(msgTimestamp) ],
function inserted(err) { // use for this scope function inserted(err) { // use non-arrow function for 'this' scope
if(!err) { if(!err) {
self.messageId = this.lastID; self.messageId = this.lastID;
} }
callback(err); return callback(err);
} }
); );
}, },
function storeMeta(callback) { function storeMeta(callback) {
if(!self.meta) { if(!self.meta) {
callback(null); return callback(null);
} else { }
/* /*
Example of self.meta: Example of self.meta:
@ -412,16 +412,15 @@ Message.prototype.persist = function(cb) {
}, err => { }, err => {
callback(err); callback(err);
}); });
}
}, },
function storeHashTags(callback) { function storeHashTags(callback) {
// :TODO: hash tag support // :TODO: hash tag support
callback(null); return callback(null);
} }
], ],
err => { err => {
Message.endTransaction(err, transErr => { Message.endTransaction(err, transErr => {
cb(err ? err : transErr, self.messageId); return cb(err ? err : transErr, self.messageId);
}); });
} }
); );

View File

@ -560,10 +560,10 @@ function persistMessage(message, cb) {
async.series( async.series(
[ [
function persistMessageToDisc(callback) { function persistMessageToDisc(callback) {
message.persist(callback); return message.persist(callback);
}, },
function recordToMessageNetworks(callback) { function recordToMessageNetworks(callback) {
msgNetRecord(message, callback); return msgNetRecord(message, callback);
} }
], ],
cb cb

View File

@ -1218,6 +1218,7 @@ FTNMessageScanTossModule.prototype.startup = function(cb) {
{ {
schedule : this.moduleConfig.schedule.export, schedule : this.moduleConfig.schedule.export,
schedOK : -1 === exportSchedule.sched.error, schedOK : -1 === exportSchedule.sched.error,
next : moment(later.schedule(exportSchedule.sched).next(1)).format('ddd, MMM Do, YYYY @ h:m:ss a'),
immediate : exportSchedule.immediate ? true : false, immediate : exportSchedule.immediate ? true : false,
}, },
'Export schedule loaded' 'Export schedule loaded'
@ -1245,6 +1246,7 @@ FTNMessageScanTossModule.prototype.startup = function(cb) {
{ {
schedule : this.moduleConfig.schedule.import, schedule : this.moduleConfig.schedule.import,
schedOK : -1 === importSchedule.sched.error, schedOK : -1 === importSchedule.sched.error,
next : moment(later.schedule(importSchedule.sched).next(1)).format('ddd, MMM Do, YYYY @ h:m:ss a'),
watchFile : _.isString(importSchedule.watchFile) ? importSchedule.watchFile : 'None', watchFile : _.isString(importSchedule.watchFile) ? importSchedule.watchFile : 'None',
}, },
'Import schedule loaded' 'Import schedule loaded'

View File

@ -2,14 +2,14 @@
'use strict'; 'use strict';
// ENiGMA½ // ENiGMA½
const Config = require('../config.js').config; const Config = require('../../config.js').config;
const baseClient = require('../client.js'); const baseClient = require('../../client.js');
const Log = require('../logger.js').log; const Log = require('../../logger.js').log;
const ServerModule = require('../server_module.js').ServerModule; const ServerModule = require('../../server_module.js').ServerModule;
const userLogin = require('../user_login.js').userLogin; const userLogin = require('../../user_login.js').userLogin;
const enigVersion = require('../../package.json').version; const enigVersion = require('../../../package.json').version;
const theme = require('../theme.js'); const theme = require('../../theme.js');
const stringFormat = require('../string_format.js'); const stringFormat = require('../../string_format.js');
// deps // deps
const ssh2 = require('ssh2'); const ssh2 = require('ssh2');
@ -200,7 +200,7 @@ function SSHClient(clientConn) {
} }
// we're ready! // we're ready!
const firstMenu = self.isNewUser ? Config.servers.ssh.firstMenuNewUser : Config.servers.ssh.firstMenu; const firstMenu = self.isNewUser ? Config.loginServers.ssh.firstMenuNewUser : Config.loginServers.ssh.firstMenu;
self.emit('ready', { firstMenu : firstMenu } ); self.emit('ready', { firstMenu : firstMenu } );
}); });
@ -238,15 +238,15 @@ SSHServerModule.prototype.createServer = function() {
const serverConf = { const serverConf = {
hostKeys : [ hostKeys : [
{ {
key : fs.readFileSync(Config.servers.ssh.privateKeyPem), key : fs.readFileSync(Config.loginServers.ssh.privateKeyPem),
passphrase : Config.servers.ssh.privateKeyPass, passphrase : Config.loginServers.ssh.privateKeyPass,
} }
], ],
ident : 'enigma-bbs-' + enigVersion + '-srv', ident : 'enigma-bbs-' + enigVersion + '-srv',
// Note that sending 'banner' breaks at least EtherTerm! // Note that sending 'banner' breaks at least EtherTerm!
debug : (sshDebugLine) => { debug : (sshDebugLine) => {
if(true === Config.servers.ssh.traceConnections) { if(true === Config.loginServers.ssh.traceConnections) {
Log.trace(`SSH: ${sshDebugLine}`); Log.trace(`SSH: ${sshDebugLine}`);
} }
}, },

View File

@ -2,10 +2,10 @@
'use strict'; 'use strict';
// ENiGMA½ // ENiGMA½
const baseClient = require('../client.js'); const baseClient = require('../../client.js');
const Log = require('../logger.js').log; const Log = require('../../logger.js').log;
const ServerModule = require('../server_module.js').ServerModule; const ServerModule = require('../../server_module.js').ServerModule;
const Config = require('../config.js').config; const Config = require('../../config.js').config;
// deps // deps
const net = require('net'); const net = require('net');
@ -497,7 +497,7 @@ function TelnetClient(input, output) {
}); });
this.connectionDebug = (info, msg) => { this.connectionDebug = (info, msg) => {
if(Config.servers.telnet.traceConnections) { if(Config.loginServers.telnet.traceConnections) {
self.log.trace(info, 'Telnet: ' + msg); self.log.trace(info, 'Telnet: ' + msg);
} }
}; };
@ -584,7 +584,7 @@ TelnetClient.prototype.handleSbCommand = function(evt) {
if(!self.didReady) { if(!self.didReady) {
self.didReady = true; self.didReady = true;
self.emit('ready', { firstMenu : Config.servers.telnet.firstMenu } ); self.emit('ready', { firstMenu : Config.loginServers.telnet.firstMenu } );
} }
} else if('new environment' === evt.option) { } else if('new environment' === evt.option) {
// //

View File

@ -49,8 +49,12 @@ function ViewController(options) {
menuUtil.handleAction(self.client, formData, actionBlock, (err) => { menuUtil.handleAction(self.client, formData, actionBlock, (err) => {
if(err) { if(err) {
// :TODO: What can we really do here? // :TODO: What can we really do here?
if('ALREADYTHERE' === err.reasonCode) {
self.client.log.trace( err.reason );
} else {
self.client.log.warn( { err : err }, 'Error during handleAction()'); self.client.log.warn( { err : err }, 'Error during handleAction()');
} }
}
self.waitActionCompletion = false; self.waitActionCompletion = false;
}); });