// Copyright 2018-2020 Campbell Crowley. All rights reserved. // Author: Campbell Crowley (dev@campbellcrowley.com) const childProcess = require('child_process'); require('./subModule.js').extend(Sandbox); // Extends the SubModule class. /** * @classdesc Creates a safe environment to run untrusted scripts. * This has been deprecated as it is too difficult to completely secure an * environment for our purposes. * @class * @augments SubModule * @listens Command#js */ function Sandbox() { const self = this; /** @inheritdoc */ this.myName = 'Sandbox'; /** @inheritdoc */ this.initialize = function() { self.command.on('js', commandJS); self.command.on(['py', 'python'], commandPython); self.command.on(['py3', 'python3'], commandPython3); }; /** @inheritdoc */ this.shutdown = function() { self.command.removeListener('js'); self.command.removeListener('py'); self.command.removeListener('py3'); }; /** * Arguments to pass into child_process.exec. * * @private * @default * @constant * @type {object} */ const execArgs = { timeout: 35000, // 5 second leniency for sandbox. maxBuffer: 2 * 1024, env: {}, }; /** * Command to execute to start a sandbox. * * @private * @default * @constant * @type {string} */ const sandboxCommand = 'firejail --profile=./src/lib/sandbox.profile -- '; /** * The command to run in the sandbox to run JavaScript. * * @private * @default * @constant * @type {string} */ const jsCommand = 'SBnode'; /** * The command to run in the sandbox to run Python2.7. * * @private * @default * @constant * @type {string} */ const pyCommand = 'SBpython'; /** * The command to run in the sandbox to run Python3. * * @private * @default * @constant * @type {string} */ const py3Command = 'SBpython3'; /** * Run JavaScript code in a sandbox, then show user outcome. * * @private * @type {commandHandler} * @param {Discord~Message} msg Message that triggered command. * @listens Command#js */ function commandJS(msg) { const cmd = `${sandboxCommand}${jsCommand}`; msg.channel.sendTyping(); const p = childProcess.exec(cmd, execArgs, (...args) => { scriptEnd(msg, ...args); }); p.stdin.write(msg.text); p.stdin.end(); } /** * Run Python2.7 code in a sandbox, then show user outcome. * * @private * @type {commandHandler} * @param {Discord~Message} msg Message that triggered command. * @listens Command#py */ function commandPython(msg) { const cmd = `${sandboxCommand}${pyCommand}`; msg.channel.sendTyping(); const p = childProcess.exec(cmd, execArgs, (...args) => { scriptEnd(msg, ...args); }); p.stdin.write(msg.text); p.stdin.end(); } /** * Run Python3 code in a sandbox, then show user outcome. * * @private * @type {commandHandler} * @param {Discord~Message} msg Message that triggered command. * @listens Command#py */ function commandPython3(msg) { const cmd = `${sandboxCommand}${py3Command}`; msg.channel.sendTyping(); const p = childProcess.exec(cmd, execArgs, (...args) => { scriptEnd(msg, ...args); }); p.stdin.write(msg.text); p.stdin.end(); } /** * Callback when script user's program has finished executing. * * @private * @param {Discord~Message} msg The Discord message that triggered the initial * execution. * @param {Error} err Error while running script. * @param {string|Buffer} stdout All data passed through stdout. * @param {string|Buffer} stderr All data paseed through stderr. */ function scriptEnd(msg, err, stdout, stderr) { if (err) { if (err.message === 'stderr maxBuffer exceeded' || err.message === 'stdout maxBuffer exceeded') { self.common.reply(msg, 'Code execution failed.', err.message); return; } else if (err.code === null) { self.common.reply( msg, 'Code execution failed.', 'Execution aborted. ' + 'Your code can run at most for 30 seconds.'); return; } self.common.reply( msg, 'Oops! Something didn\'t work right...', 'Something is broken internally.'); console.error(err); self.debug('STDERR: ' + stderr); self.debug('STDOUT: ' + stdout); return; } const embed = new self.Discord.EmbedBuilder(); embed.setColor([0, 255, 255]); if (stdout.length > 0) { if (stdout.indexOf('\\n') != stdout.lastIndexOf('\\n')) { stdout = stdout.replace(/\\n/g, '\n'); } embed.addFields([{name: 'STDOUT', value: stdout.substr(0, 1024)}]); } if (stderr.length > 0) { if (stderr.indexOf('\\n') != stderr.lastIndexOf('\\n')) { stderr = stderr.replace(/\\n/g, '\n'); } embed.addFields([{name: 'STDERR', value: stderr.substr(0, 1024)}]); } msg.channel.send({content: self.common.mention(msg), embeds: [embed]}); } } module.exports = new Sandbox();