UNPKG

chattervox

Version:

An AX.25 packet radio chat protocol with support for digital signatures and binary compression. Like IRC over radio waves 📡〰.

383 lines • 14.3 kB
#!/usr/bin/env node "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const argparse_1 = require("argparse"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const config = __importStar(require("./config")); const Keystore_1 = require("./Keystore"); const chat = __importStar(require("./subcommands/chat")); const addkey = __importStar(require("./subcommands/addkey")); const removekey = __importStar(require("./subcommands/removekey")); const showkey = __importStar(require("./subcommands/showkey")); const genkey = __importStar(require("./subcommands/genkey")); const send = __importStar(require("./subcommands/send")); const receive = __importStar(require("./subcommands/receive")); const exec = __importStar(require("./subcommands/exec")); const tty = __importStar(require("./subcommands/tty")); const init_1 = require("./ui/init"); const utils_1 = require("./utils"); function parseArgs() { const pkgBuff = fs.readFileSync(path.resolve(__dirname, '..', 'package.json')); const pkgJSON = JSON.parse(pkgBuff.toString('utf8')); const parser = new argparse_1.ArgumentParser({ prog: pkgJSON.name, version: pkgJSON.version, description: pkgJSON.description }); parser.addArgument(['--config', '-c'], { help: `Path to config file (default: ${config.defaultConfigPath})`, defaultValue: config.defaultConfigPath }); const subs = parser.addSubparsers({ title: 'subcommands', dest: 'subcommand' }); const chat = subs.addParser('chat', { addHelp: true, description: 'Enter the chat room.' }); chat; // intentionally unused const send = subs.addParser('send', { addHelp: true, description: 'Send chattervox packets.' }); send.addArgument(['--to', '-t'], { type: 'string', help: 'The recipient\'s callsign, callsign-ssid pair, or chatroom name (default: "CQ").', defaultValue: 'CQ', required: false }); send.addArgument(['--dont-sign', '-d'], { action: 'storeTrue', dest: 'dontSign', help: 'Don\'t sign messages.', required: false }); send.addArgument('message', { type: 'string', help: 'A UTF-8 message to be sent.', nargs: '?', required: false }); const receive = subs.addParser('receive', { addHelp: true, description: 'Write chattervox packets to stdout.' }); receive.addArgument(['--allow-unsigned', '-u'], { action: 'storeTrue', dest: 'allowUnsigned', help: 'Receive unsigned messages.', }); receive.addArgument(['--allow-untrusted', '-e'], { action: 'storeTrue', dest: 'allowUntrusted', help: 'Receive messages signed by senders not in keyring.', }); receive.addArgument(['--allow-invalid', '-i'], { action: 'storeTrue', dest: 'allowInvalid', help: 'Receive messages with invalid signatures.', }); receive.addArgument(['--all-recipients', '-g'], { action: 'storeTrue', dest: 'allRecipients', help: 'Receive messages to all callsigns and chat rooms.', }); receive.addArgument(['--allow-all', '-a'], { action: 'storeTrue', dest: 'allowAll', help: 'Receive all messages, independent of signatures and destinations.', }); receive.addArgument(['--raw', '-r'], { action: 'storeTrue', dest: 'raw', help: 'Print raw ax25 packets instead of parsed chattervox messages.', }); receive.addArgument('--to', { type: 'string', help: 'The recipient\'s callsign, callsign-ssid pair, or chatroom name (default: "CQ").', defaultValue: 'CQ', required: false }); receive.addArgument(['--verbose', '-v'], { action: 'storeTrue', help: 'Print verbose output from any chattervox packet received to stderr.', }); const showKey = subs.addParser('showkey', { addHelp: true, description: 'List keys.' }); showKey.addArgument('callsign', { type: 'string', nargs: '?' }); const addKey = subs.addParser('addkey', { addHelp: true, description: 'Add a new public key to the keystore associated with a callsign.' }); addKey.addArgument('callsign', { type: 'string' }); addKey.addArgument('publickey', { type: 'string' }); const removeKey = subs.addParser('removekey', { addHelp: true, description: 'Remove a public key from the keystore.' }); removeKey.addArgument('callsign', { type: 'string' }); removeKey.addArgument('publickey', { type: 'string' }); const genKey = subs.addParser('genkey', { addHelp: true, description: 'Generate a new keypair for your callsign.' }); genKey.addArgument('--make-signing', { action: 'storeTrue', dest: 'makeSigning', help: 'Make the generated key your default signing key.' }); const exec = subs.addParser('exec', { addHelp: true, description: 'Execute a command using chattervox as the stdin and stdout interface.' }); exec.addArgument(['--delay', '-s'], { type: 'int', help: 'Milliseconds after receiving stdin to wait before transmitting stdout (default: 3000).', defaultValue: 3000, required: false }); exec.addArgument(['--to', '-t'], { type: 'string', help: 'The recipient\'s callsign, callsign-ssid pair, or chatroom name (default: "CQ").', defaultValue: 'CQ', required: false }); exec.addArgument(['--dont-sign', '-d'], { action: 'storeTrue', dest: 'dontSign', help: 'Don\'t sign messages.', required: false }); exec.addArgument(['--stderr', '-f'], { action: 'storeTrue', help: 'Also transmit stderr.', defaultValue: false, required: false }); exec.addArgument(['--allow-unsigned', '-u'], { action: 'storeTrue', dest: 'allowUnsigned', help: 'Receive unsigned messages.', }); exec.addArgument(['--allow-untrusted', '-e'], { action: 'storeTrue', dest: 'allowUntrusted', help: 'Receive messages signed by senders not in keyring.', }); exec.addArgument(['--allow-invalid', '-i'], { action: 'storeTrue', dest: 'allowInvalid', help: 'Receive messages with invalid signatures.', }); exec.addArgument(['--all-recipients', '-g'], { action: 'storeTrue', dest: 'allRecipients', help: 'Receive messages to all callsigns and chat rooms.', }); exec.addArgument(['--allow-all', '-a'], { action: 'storeTrue', dest: 'allowAll', help: 'Receive all messages, independent of signatures and destinations.', }); exec.addArgument('command', { type: 'string', help: 'A command to be run' }); const tty = subs.addParser('tty', { addHelp: true, description: 'A dumb tty interface. Sends what\'s typed, prints what\'s received.' }); tty.addArgument(['--to', '-t'], { type: 'string', help: 'The recipient\'s callsign, callsign-ssid pair, or chatroom name (default: "CQ").', defaultValue: 'CQ', required: false }); tty.addArgument(['--dont-sign', '-d'], { action: 'storeTrue', dest: 'dontSign', help: 'Don\'t sign messages.', required: false }); tty.addArgument(['--allow-unsigned', '-u'], { action: 'storeTrue', dest: 'allowUnsigned', help: 'Receive unsigned messages.', }); tty.addArgument(['--allow-untrusted', '-e'], { action: 'storeTrue', dest: 'allowUntrusted', help: 'Receive messages signed by senders not in keyring.', }); tty.addArgument(['--allow-invalid', '-i'], { action: 'storeTrue', dest: 'allowInvalid', help: 'Receive messages with invalid signatures.', }); tty.addArgument(['--all-recipients', '-g'], { action: 'storeTrue', dest: 'allRecipients', help: 'Receive messages to all callsigns and chat rooms.', }); tty.addArgument(['--allow-all', '-a'], { action: 'storeTrue', dest: 'allowAll', help: 'Receive all messages, independent of signatures and destinations.', }); return parser.parseArgs(); } function validateArgs(args) { if (args.callsign != null && !utils_1.isCallsign(args.callsign)) { console.error(`${args.callsign} is not a valid callsign.`); if (utils_1.isCallsignSSID(args.callsign)) { console.error(`callsign should not include an SSID for key management subcommands.`); } process.exit(1); } if (args.to != null && args.to !== 'CQ' && !(utils_1.isCallsign(args.to) || utils_1.isCallsignSSID(args.to))) { console.error('--to must be a callsign, callsign-ssid pair, or chatroom name with less than 7 alphanumeric characters.'); process.exit(1); } } // not sure if we should add this here... // function validateKeystoreFile(conf: config.Config): void { // if (!fs.existsSync(conf.keystoreFile)) { // console.error(`No keystoreFile exists at location "${conf.keystoreFile}".`) // process.exit(1) // } else { // try { // JSON.parse(fs.readFileSync(conf.keystoreFile).toString('utf8')) // } catch (err) { // console.error(`Error loading keystoreFile file from "${conf.keystoreFile}".`) // console.error(err.message) // process.exit(1) // } // } // } function validateSigningKeyExists(conf, ks) { // if there is a signing in the config but it doesn't exist in the keystore if (conf.signingKey != null) { const signing = ks.getKeyPairs(conf.callsign).filter((key) => { return key.public === conf.signingKey; }); if (signing.length < 1) { console.error(`Default signing key has no matching private key found in the keystore.`); process.exit(1); } } } function cleanup() { return __awaiter(this, void 0, void 0, function* () { return yield exec.cleanup(); }); } function onSignal() { cleanup() .then(() => process.exit(0)) .catch(err => { console.error(err); process.exit(1); }); } function main() { return __awaiter(this, void 0, void 0, function* () { process.on('SIGINT', onSignal); // catch ctrl-c process.on('SIGTERM', onSignal); // catch kill const args = parseArgs(); validateArgs(args); // initialize a new config if (!config.exists(args.config)) { // if the default config doesn't exist, let's run the interactive init if (args.config === config.defaultConfigPath) { yield init_1.interactiveInit(); } else { console.error(`No config file exists at "${args.config}".`); process.exit(1); } } let conf = null; try { conf = config.load(args.config); } catch (err) { console.error(`Error loading config file from "${args.config}".`); console.error(err.message); process.exit(1); } // validate that keystore file exists // validateKeystoreFile(conf) const ks = new Keystore_1.Keystore(conf.keystoreFile); // if this subcommand is any of the commands that signs something if (['chat', 'send', 'receive'].includes(args.subcommand)) { validateSigningKeyExists(conf, ks); } let code = null; try { switch (args.subcommand) { case 'chat': code = yield chat.main(args, conf, ks); break; case 'send': code = yield send.main(args, conf, ks); break; case 'receive': code = yield receive.main(args, conf, ks); break; case 'showkey': code = yield showkey.main(args, conf, ks); break; case 'addkey': code = yield addkey.main(args, conf, ks); break; case 'removekey': code = yield removekey.main(args, conf, ks); break; case 'genkey': code = yield genkey.main(args, conf, ks); break; case 'exec': code = yield exec.main(args, conf, ks); break; case 'tty': code = yield tty.main(args, conf, ks); break; } } catch (err) { if (utils_1.isBrokenPipeError(err)) { console.error(`\nThe connection to KISS TNC ${conf.kissPort} has been closed with a broken pipe.`); code = 1; } else throw err; } process.exit(code); }); } main() .catch((err) => __awaiter(this, void 0, void 0, function* () { console.error(err); yield cleanup(); process.exit(1); })); //# sourceMappingURL=main.js.map