From 648ecb6e69e73d7611e02ef80c5fc3640340549f Mon Sep 17 00:00:00 2001 From: David Perry Date: Thu, 28 May 2020 11:44:37 -0400 Subject: [PATCH] Initial command line/TCP server script This fairly simple script leverages the Node API provided by CyberChef to allow performing operations on local filesystem objects without needing to copy them into and out of a browser window. It requires a recipe to be specified at runtime as a JSON file. Also includes a simple TCP server for use by other local processes. This server is hard-coded to use localhost in order to discourage production use of the script. --- cli.js | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++ 2 files changed, 161 insertions(+) create mode 100755 cli.js diff --git a/cli.js b/cli.js new file mode 100755 index 00000000..0e3350c2 --- /dev/null +++ b/cli.js @@ -0,0 +1,157 @@ +#!/usr/bin/env node +/** + * @author Boolean263 [boolean263@protonmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ +'use strict'; + +const fs = require("fs"); +const path = require("path"); +const chef = require("cyberchef"); +const program = require("commander"); + +let slurpStream = (istream) => { // {{{1 + var ret = []; + var len = 0; + return new Promise(resolve => { + istream.on('readable', () => { + var chunk; + while ((chunk = istream.read()) !== null) { + ret.push(chunk); + len += chunk.length; + } + resolve(Buffer.concat(ret, len)); + }); + }); +}; // }}}1 + +let slurp = (fname) => { // {{{1 + let istream; + + if (fname === undefined || fname == '-') { + istream = process.stdin; + if (istream.isTTY) { + throw new Error("TTY input not supported"); + } + } + else { + istream = fs.createReadStream(fname, { flags: 'r' }); + } + return slurpStream(istream); +}; // }}}1 + +let getPort = (value, dummyPrevious) => { // {{{1 + // Get a valid port number from the command line + let ret = parseInt(value, 10); + if (ret < 1 || ret > 65535) { + throw new Error("invalid port number"); + } + return ret; +}; +// }}}1 + +///////// MAIN ///////// {{{1 + +program + .version(require('./package.json').version) + .usage('[options] [file [file ...]]') + .requiredOption('-r, --recipe-file ', + 'recipe JSON file') + .option('-l, --listen [port]', + 'listen on TCP port for data (default:random)', getPort, false) + .option('-o, --output ', + 'write result here (not for TCP; default:stdout)') + .parse(process.argv); + +// If we get no inputs and we aren't running a server, +// make stdin our single input +let inputs = program.args; +if (inputs.length == 0 && !program.listen) { + inputs = [ '-' ]; +} + +// Likewise stdout for our output +let ostream; +let outputIsDir = false; +if (program.output === undefined && !program.listen) { + ostream = process.stdout; +} +else if (inputs.length > 0) { + // See if our output is a directory + let st; + try { + st = fs.statSync(program.output); + outputIsDir = st.isDirectory(); + } + catch(err) { + if (err.code != 'ENOENT') throw err; + } + if (!outputIsDir) { + ostream = fs.createWriteStream(program.output); + } +} + +let recipe; +slurp(program.recipeFile).then((data) => { + recipe = JSON.parse(data); +}) +.catch((err) => { + console.error(`Error parsing recipe: ${err}`); + process.exit(1); +}) +.then(() => { + // First, deal with any files we want to read + for(let i of inputs) { + slurp(i).then((data) => { + let output = chef.bake(data, recipe); + if (outputIsDir) { + let outFileName = path.basename(i); + if (outFileName == '-') outFileName = 'from-stdin'; + ostream = fs.createWriteStream( + path.join(program.output, outFileName)); + } + ostream.write(output.presentAs("string", true)); + if (outputIsDir) ostream.end(); + }) + .catch((err) => { + console.error(err); + }); + } + + // Next, listen for TCP requests. + // This is intentionally hardcoded to localhost to discourage + // the use of this script as a production system. + if (program.listen) { + const net = require('net'); + const server = net.createServer((socket) => { + slurpStream(socket).then((data) => { + let output = chef.bake(data, recipe); + socket.write(output.presentAs("string", true)); + socket.end(); + }) + .catch((err) => { + console.error(err); + }); + }); + + // If no port given, let the OS choose one + if (program.listen === true) program.listen = 0; + server.listen(program.listen, '127.0.0.1') + .on('listening', () => { + console.log('Now listening on ' + + server.address().address + + ":" + server.address().port); + }); + + // Exit gracefully + process.on('SIGINT', () => { + console.log("Exiting"); + server.close(); + }); + } +}) +.catch((err) => { + console.error(err); + process.exit(2); +}) diff --git a/package.json b/package.json index a8f4198e..0e57d647 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,9 @@ "Firefox >= 38", "node >= 10" ], + "bin": { + "cyberchef": "./cli.js" + }, "devDependencies": { "@babel/core": "^7.8.7", "@babel/plugin-transform-runtime": "^7.8.3", @@ -98,6 +101,7 @@ "bson": "^4.0.3", "chi-squared": "^1.1.0", "codepage": "^1.14.0", + "commander": "^2.14.1", "core-js": "^3.6.4", "crypto-api": "^0.8.5", "crypto-js": "^4.0.0",