mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-07-21 10:15:55 +02:00
* FTN BSO module: Export to <domain>.<zone> dirs where appropriate
* Code cleanup * Fix FTN packet header writing * Add CHRS support to FTN packet I/O * Change to FNV-1a hash of ms since 2016-1-1 ("enigma epoc") + message ID for MSGID serial number and <packet>.pkt BSO export * Only write some FTN kludges for EchoMail (vs NetMail) * If config specifies, call message network modoule(s) .record() method @ persist (WIP)
This commit is contained in:
parent
75698f62af
commit
a858a93ee1
9 changed files with 413 additions and 322 deletions
|
@ -4,13 +4,15 @@
|
|||
// ENiGMA½
|
||||
let MessageScanTossModule = require('../msg_scan_toss_module.js').MessageScanTossModule;
|
||||
let Config = require('../config.js').config;
|
||||
let ftnMailpacket = require('../ftn_mail_packet.js');
|
||||
let ftnMailPacket = require('../ftn_mail_packet.js');
|
||||
let ftnUtil = require('../ftn_util.js');
|
||||
let Address = require('../ftn_address.js');
|
||||
let Log = require('../logger.js').log;
|
||||
|
||||
let moment = require('moment');
|
||||
let _ = require('lodash');
|
||||
let paths = require('path');
|
||||
let mkdirp = require('mkdirp');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'FTN',
|
||||
|
@ -18,75 +20,191 @@ exports.moduleInfo = {
|
|||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
/*
|
||||
:TODO:
|
||||
* Add bundle timer (arcmail)
|
||||
* Queue until time elapses / fixed time interval
|
||||
* Pakcets append until >= max byte size
|
||||
* [if arch type is not empty): Packets -> bundle until max byte size -> repeat process
|
||||
* NetMail needs explicit isNetMail() check
|
||||
* NetMail filename / location / etc. is still unknown - need to post on groups & get real answers
|
||||
*/
|
||||
|
||||
exports.getModule = FTNMessageScanTossModule;
|
||||
|
||||
function FTNMessageScanTossModule() {
|
||||
MessageScanTossModule.call(this);
|
||||
|
||||
if(_.has(Config, 'scannerTossers.ftn_bso')) {
|
||||
this.config = Config.scannerTossers.ftn_bso;
|
||||
this.moduleConfig = Config.scannerTossers.ftn_bso;
|
||||
}
|
||||
|
||||
this.isDefaultDomainZone = function(networkName, address) {
|
||||
return(networkName === this.moduleConfig.defaultNetwork && address.zone === this.moduleConfig.defaultZone);
|
||||
}
|
||||
|
||||
this.getOutgoingPacketDir = function(networkName, remoteAddress) {
|
||||
let dir = this.moduleConfig.paths.outbound;
|
||||
if(!this.isDefaultDomainZone(networkName, remoteAddress)) {
|
||||
const hexZone = `000${remoteAddress.zone.toString(16)}`.substr(-3);
|
||||
dir = paths.join(dir, `${networkName.toLowerCase()}.${hexZone}`);
|
||||
}
|
||||
return dir;
|
||||
};
|
||||
|
||||
this.getOutgoingPacketFileName = function(basePath, message, isTemp) {
|
||||
//
|
||||
// Generating an outgoing packet file name comes with a few issues:
|
||||
// * We must use DOS 8.3 filenames due to legacy systems that receive
|
||||
// the packet not understanding LFNs
|
||||
// * We need uniqueness; This is especially important with packets that
|
||||
// end up in bundles and on the receiving/remote system where conflicts
|
||||
// with other systems could also occur
|
||||
//
|
||||
// There are a lot of systems in use here for the name:
|
||||
// * HEX CRC16/32 of data
|
||||
// * HEX UNIX timestamp
|
||||
// * Mystic at least at one point, used Hex8(day of month + seconds past midnight + hundredths of second)
|
||||
// See https://groups.google.com/forum/#!searchin/alt.bbs.mystic/netmail$20filename/alt.bbs.mystic/m1xLnY8i1pU/YnG2excdl6MJ
|
||||
// * SBBSEcho uses DDHHMMSS - see https://github.com/ftnapps/pkg-sbbs/blob/master/docs/fidonet.txt
|
||||
// * We already have a system for 8-character serial number gernation that is
|
||||
// used for e.g. in FTS-0009.001 MSGIDs... let's use that!
|
||||
//
|
||||
const name = ftnUtil.getMessageSerialNumber(message);
|
||||
const ext = (true === isTemp) ? 'pk_' : 'pkt';
|
||||
return paths.join(basePath, `${name}.${ext}`);
|
||||
};
|
||||
|
||||
this.createMessagePacket = function(message, config) {
|
||||
this.prepareMessage(message);
|
||||
this.createMessagePacket = function(message, options) {
|
||||
this.prepareMessage(message, options);
|
||||
|
||||
let packet = new ftnMailPacket.Packet();
|
||||
|
||||
let packetHeader = new ftnMailpacket.PacketHeader();
|
||||
packetHeader.init(
|
||||
config.network.localAddress,
|
||||
config.remoteAddress);
|
||||
let packetHeader = new ftnMailPacket.PacketHeader(
|
||||
options.network.localAddress,
|
||||
options.remoteAddress,
|
||||
options.nodeConfig.packetType);
|
||||
|
||||
packetHeader.setPassword(config.remoteNode.packetPassword || '');
|
||||
packetHeader.password = options.nodeConfig.packetPassword || '';
|
||||
|
||||
if(message.isPrivate()) {
|
||||
// :TODO: this should actually be checking for isNetMail()!!
|
||||
} else {
|
||||
const outgoingDir = this.getOutgoingPacketDir(options.networkName, options.remoteAddress);
|
||||
|
||||
mkdirp(outgoingDir, err => {
|
||||
if(err) {
|
||||
// :TODO: Handle me!!
|
||||
} else {
|
||||
packet.write(
|
||||
this.getOutgoingPacketFileName(outgoingDir, message),
|
||||
packetHeader,
|
||||
[ message ],
|
||||
{ encoding : options.encoding }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.prepareMessage = function(message, config) {
|
||||
this.prepareMessage = function(message, options) {
|
||||
//
|
||||
// Set various FTN kludges/etc.
|
||||
//
|
||||
message.meta.FtnProperty = message.meta.FtnProperty || {};
|
||||
message.meta.FtnProperty.ftn_orig_node = config.network.localAddress.node;
|
||||
message.meta.FtnProperty.ftn_dest_node = config.remoteAddress.node;
|
||||
message.meta.FtnProperty.ftn_orig_network = config.network.localAddress.net;
|
||||
message.meta.FtnProperty.ftn_dest_network = config.remoteAddress.net;
|
||||
message.meta.FtnKludge = message.meta.FtnKludge || {};
|
||||
|
||||
message.meta.FtnProperty.ftn_orig_node = options.network.localAddress.node;
|
||||
message.meta.FtnProperty.ftn_dest_node = options.remoteAddress.node;
|
||||
message.meta.FtnProperty.ftn_orig_network = options.network.localAddress.net;
|
||||
message.meta.FtnProperty.ftn_dest_network = options.remoteAddress.net;
|
||||
// :TODO: attr1 & 2
|
||||
message.meta.FtnProperty.ftn_cost = 0;
|
||||
|
||||
message.meta.FtnProperty.ftn_tear_line = ftnUtil.getTearLine();
|
||||
|
||||
message.meta.FtnProperty.ftn_tear_line = ftnUtil.getTearLine();
|
||||
message.meta.FtnProperty.ftn_origin = ftnUtil.getOrigin(config.network.localAddress);
|
||||
|
||||
// :TODO: Need an explicit isNetMail() check
|
||||
if(message.isPrivate()) {
|
||||
//
|
||||
// NetMail messages need a FRL-1005.001 "Via" line
|
||||
// http://ftsc.org/docs/frl-1005.001
|
||||
//
|
||||
if(_.isString(message.meta.FtnKludge['Via'])) {
|
||||
message.meta.FtnKludge['Via'] = [ message.meta.FtnKludge['Via'] ];
|
||||
if(_.isString(message.meta.FtnKludge.Via)) {
|
||||
message.meta.FtnKludge.Via = [ message.meta.FtnKludge.Via ];
|
||||
}
|
||||
message.meta.FtnKludge['Via'] = message.meta.FtnKludge['Via'] || [];
|
||||
message.meta.FtnKludge['Via'].push(ftnUtil.getVia(config.network.localAddress));
|
||||
message.meta.FtnKludge.Via = message.meta.FtnKludge.Via || [];
|
||||
message.meta.FtnKludge.Via.push(ftnUtil.getVia(options.network.localAddress));
|
||||
} else {
|
||||
message.meta.FtnProperty.ftn_area = message.areaTag;
|
||||
//
|
||||
// EchoMail requires some additional properties & kludges
|
||||
//
|
||||
message.meta.FtnProperty.ftn_origin = ftnUtil.getOrigin(options.network.localAddress);
|
||||
message.meta.FtnProperty.ftn_area = Config.messageNetworks.ftn.areas[message.areaTag].tag;
|
||||
|
||||
//
|
||||
// When exporting messages, we should create/update SEEN-BY
|
||||
// with remote address(s) we are exporting to.
|
||||
//
|
||||
message.meta.FtnProperty.ftn_seen_by =
|
||||
ftnUtil.getUpdatedSeenByEntries(
|
||||
message.meta.FtnProperty.ftn_seen_by,
|
||||
Config.messageNetworks.ftn.areas[message.areaTag].uplinks
|
||||
);
|
||||
|
||||
//
|
||||
// And create/update PATH for ourself
|
||||
//
|
||||
message.meta.FtnKludge.PATH =
|
||||
ftnUtil.getUpdatedPathEntries(message.meta.FtnKludge.PATH, options.network.localAddress);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// When exporting messages, we should create/update SEEN-BY
|
||||
// with remote address(s) we are exporting to.
|
||||
// Additional kludges
|
||||
//
|
||||
message.meta.FtnKludge.MSGID = ftnUtil.getMessageIdentifier(message, options.network.localAddress);
|
||||
message.meta.FtnKludge.TZUTC = ftnUtil.getUTCTimeZoneOffset();
|
||||
|
||||
if(!message.meta.FtnKludge.PID) {
|
||||
message.meta.FtnKludge.PID = ftnUtil.getProductIdentifier();
|
||||
}
|
||||
|
||||
if(!message.meta.FtnKludge.TID) {
|
||||
// :TODO: Create TID!!
|
||||
//message.meta.FtnKludge.TID =
|
||||
}
|
||||
|
||||
//
|
||||
message.meta.FtnProperty.ftn_seen_by =
|
||||
ftnUtil.getUpdatedSeenByEntries(
|
||||
message.meta.FtnProperty.ftn_seen_by,
|
||||
Config.messageNetworks.ftn.areas[message.areaTag].uplinks
|
||||
);
|
||||
|
||||
//
|
||||
// And create/update PATH for ourself
|
||||
//
|
||||
message.meta.FtnKludge['PATH'] =
|
||||
ftnUtil.getUpdatedPathEntries(
|
||||
message.meta.FtnKludge['PATH'],
|
||||
config.network.localAddress.node
|
||||
);
|
||||
// Determine CHRS and actual internal encoding name
|
||||
// Try to preserve anything already here
|
||||
let encoding = options.nodeConfig.encoding || 'utf8';
|
||||
if(message.meta.FtnKludge.CHRS) {
|
||||
const encFromChars = ftnUtil.getEncodingFromCharacterSetIdentifier(message.meta.FtnKludge.CHRS);
|
||||
if(encFromChars) {
|
||||
encoding = encFromChars;
|
||||
}
|
||||
}
|
||||
|
||||
options.encoding = encoding; // save for later
|
||||
message.meta.FtnKludge.CHRS = ftnUtil.getCharacterSetIdentifierByEncoding(encoding);
|
||||
// :TODO: FLAGS kludge?
|
||||
// :TODO: Add REPLY kludge if appropriate
|
||||
|
||||
};
|
||||
|
||||
|
||||
// :TODO: change to something like isAreaConfigValid
|
||||
// check paths, Addresses, etc.
|
||||
this.isAreaConfigComplete = function(areaConfig) {
|
||||
if(!_.isString(areaConfig.tag) || !_.isString(areaConfig.network)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(_.isString(areaConfig.uplinks)) {
|
||||
areaConfig.uplinks = areaConfig.uplinks.split(' ');
|
||||
}
|
||||
|
||||
return (_.isArray(areaConfig.uplinks));
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -95,23 +213,25 @@ require('util').inherits(FTNMessageScanTossModule, MessageScanTossModule);
|
|||
|
||||
FTNMessageScanTossModule.prototype.startup = function(cb) {
|
||||
Log.info('FidoNet Scanner/Tosser starting up');
|
||||
|
||||
cb(null);
|
||||
|
||||
FTNMessageScanTossModule.super_.prototype.startup.call(this, cb);
|
||||
};
|
||||
|
||||
FTNMessageScanTossModule.prototype.shutdown = function(cb) {
|
||||
Log.info('FidoNet Scanner/Tosser shutting down');
|
||||
|
||||
cb(null);
|
||||
FTNMessageScanTossModule.super_.prototype.shutdown.call(this, cb);
|
||||
};
|
||||
|
||||
FTNMessageScanTossModule.prototype.record = function(message, cb) {
|
||||
if(!_.has(Config, [ 'messageNetworks', 'ftn', 'areas', message.areaTag ])) {
|
||||
FTNMessageScanTossModule.prototype.record = function(message) {
|
||||
if(!_.has(this, 'moduleConfig.nodes') ||
|
||||
!_.has(Config, [ 'messageNetworks', 'ftn', 'areas', message.areaTag ]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const area = Config.messageNetworks.ftn.areas[message.areaTag];
|
||||
if(!_.isString(area.ftnArea) || !_.isArray(area.uplinks)) {
|
||||
const areaConfig = Config.messageNetworks.ftn.areas[message.areaTag];
|
||||
if(!this.isAreaConfigComplete(areaConfig)) {
|
||||
// :TODO: should probably log a warning here
|
||||
return;
|
||||
}
|
||||
|
@ -119,20 +239,31 @@ FTNMessageScanTossModule.prototype.record = function(message, cb) {
|
|||
//
|
||||
// For each uplink, find the best configuration match
|
||||
//
|
||||
area.uplinks.forEach(uplink => {
|
||||
areaConfig.uplinks.forEach(uplink => {
|
||||
// :TODO: sort by least # of '*' & take top?
|
||||
let matchNodes = _.filter(Object.keys(Config.scannerTossers.ftn_bso.nodes), addr => {
|
||||
const nodeKey = _.filter(Object.keys(this.moduleConfig.nodes), addr => {
|
||||
return Address.fromString(addr).isMatch(uplink);
|
||||
});
|
||||
|
||||
if(matchNodes.length > 0) {
|
||||
const nodeKey = matchNodes[0];
|
||||
})[0];
|
||||
|
||||
if(nodeKey) {
|
||||
const processOptions = {
|
||||
nodeConfig : this.moduleConfig.nodes[nodeKey],
|
||||
network : Config.messageNetworks.ftn.networks[areaConfig.network],
|
||||
remoteAddress : Address.fromString(uplink),
|
||||
networkName : areaConfig.network,
|
||||
};
|
||||
|
||||
if(_.isString(processOptions.network.localAddress)) {
|
||||
// :TODO: move/cache this - e.g. @ startup(). Think about due to Config cache
|
||||
processOptions.network.localAddress = Address.fromString(processOptions.network.localAddress);
|
||||
}
|
||||
|
||||
// :TODO: Validate the rest of the matching config -- or do that elsewhere, e.g. startup()
|
||||
|
||||
this.createMessagePacket(message, processOptions);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
cb(null);
|
||||
|
||||
// :TODO: should perhaps record in batches - e.g. start an event, record
|
||||
// to temp location until time is hit or N achieved such that if multiple
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue