diff --git a/README.md b/README.md index 2c0efe7..4a486aa 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,13 @@ "manual_list" = True/False for creating your own JSON list of devices as device.json. See device.example.json. "manual_ip" = True/False for adding the IP to the device.json or to allow the script to find IPs. "use_ios_deploy" = True/False for using ios-deploy instead of cfgutil for querying devices (disables the profile command). +"allow_cmds" = True/False for allowing the execution of local commands that are defined in "cmds". +"cmds" = An array of objects that contain the "alias" (required), "command" (required), and "description" (optional). + "alias" is the name that you can send to the listener to execute a command without typing the full command. + "command" is a string used to hold the command and its parameters. Note that you should use nohup if the command's error output is larger than 200kb like Redux. + "description" is a string that you can use to keep notes about your command. + Note that passing parameters or using sudo are not supported as these open your system to attacks. + ``` ## Android support diff --git a/config.example.json b/config.example.json index b42644c..f481a0a 100644 --- a/config.example.json +++ b/config.example.json @@ -4,5 +4,18 @@ "domain": "http://YOUR-DOMAIN.TLD", "manual_list": false, "manual_ip": false, - "use_ios_deploy": false + "use_ios_deploy": false, + "allow_cmds": false, + "cmds": [ + { + "alias": "1", + "command": "pwd", + "description": "The pwd command prints the working directory" + }, + { + "alias": "redux_1", + "command": "cd ~/redux/ && nohup ./redux.js -f", + "description": "This command changes to the Redux directory and executes Redux with the full auto parameter. Output is sent to nohup.out so the listener can avoid buffer overflows" + } + ] } diff --git a/listener.js b/listener.js index cc868a0..1007125 100644 --- a/listener.js +++ b/listener.js @@ -8,6 +8,7 @@ const Request = require("request"); const express = require("express"); const { exec } = require("child_process"); const config = require("./config.json"); +const cmds = config.cmds; // DEFINE THE EXPRESS SERVER var server = express().use(express.json({ limit: "1mb" })); @@ -40,12 +41,13 @@ else if (config.use_ios_deploy) { server.post("/", (payload, res) => { console.log("[DCM] [listener.js] [" + getTime("log") + "] Received a Payload:", payload.body); let target = payload.body; - // GO THROUGH DEVICE ARRAY TO FIND A MATCH - devices.forEach(async (device, i) => { - switch (target.type) { + // CHECK IF THE PAYLOAD IS FOR DEVICES OR COMMANDS + // IF NOT A COMMAND, GO THROUGH DEVICE ARRAY TO FIND A MATCH + switch (target.type) { - // RESTART A DEVICE - case "restart": + // RESTART A DEVICE + case "restart": + devices.forEach(async (device, i) => { if (device.name == target.device) { if (device.reboot_cmd) { var command = device.reboot_cmd @@ -75,10 +77,12 @@ server.post("/", (payload, res) => { }); } } - break; + }); + break; - // REOPEN THE GAME - case "reopen": + // REOPEN THE GAME + case "reopen": + devices.forEach(async (device, i) => { if (device.name == target.device) { var ipaddr = ''; if (config.manual_ip) { @@ -127,14 +131,26 @@ server.post("/", (payload, res) => { }); } } - break; + }); + break; - // REAPPLY THE SAM PROFILE - case "profile": - if (!config.use_ios_deploy && device.name == target.device) { + // REAPPLY THE SAM PROFILE + case "profile": + if (config.use_ios_deploy) { + res.json({ + status: 'error', + node: config.name, + error: 'Listener set to only use iOS_Deploy.' + }); + break; + } + devices.forEach(async (device, i) => { + if (device.name == target.device) { // REMOVE THE SAM PROFILE + var errored = false; var profile = await cli_exec("cfgutil -e " + device.ecid + " -K org.der -C org.crt remove-profile com.apple.configurator.singleappmode", "device_command"); if (profile.hasError) { + errored = true; console.error("[DCM] [listener.js] [" + getTime("log") + "] Failed to remove the SAM1 profile from " + device.name + " : " + device.uuid + ".", profile.error); res.json({ status: 'error', @@ -146,6 +162,7 @@ server.post("/", (payload, res) => { // APPLY THE CLOCK PROFILE TO FORCE THE GAME CLOSED profile = await cli_exec("cfgutil -e " + device.ecid + " -K org.der -C org.crt install-profile sam_clock.mobileconfig", "device_command"); if (profile.hasError) { + errored = true; console.error("[DCM] [listener.js] [" + getTime("log") + "] Failed to add the SAM_CLOCK profile to " + device.name + " : " + device.uuid + ".", profile.error); res.json({ status: 'error', @@ -157,6 +174,7 @@ server.post("/", (payload, res) => { // REMOVE THE SAM PROFILE AGAIN profile = await cli_exec("cfgutil -e " + device.ecid + " -K org.der -C org.crt remove-profile com.apple.configurator.singleappmode", "device_command"); if (profile.hasError) { + errored = true; console.error("[DCM] [listener.js] [" + getTime("log") + "] Failed to remove the SAM2 profile from " + device.name + " : " + device.uuid + ".", profile.error); res.json({ status: 'error', @@ -168,26 +186,30 @@ server.post("/", (payload, res) => { // APPLY THE POGO PROFILE TO RELAUNCH THE GAME profile = await cli_exec("cfgutil -e " + device.ecid + " -K org.der -C org.crt install-profile sam_pogo.mobileconfig", "device_command"); if (profile.hasError) { + errored = true; console.error("[DCM] [listener.js] [" + getTime("log") + "] Failed to add the SAM_CALC profile to " + device.name + " : " + device.uuid + ".", profile.error); res.json({ status: 'error', node: config.name, error: 'Failed to add the SAM_POGO profile.' }); - break; } - // REAPPLICATION WAS SUCCESSFUL - console.log("[DCM] [listener.js] [" + getTime("log") + "] Reapplied the SAM profile to " + device.name + " : " + device.uuid + "."); - // SEND CONFIRMATION TO DCM - res.json({ - status: 'ok' - }); + if (!errored) { + // REAPPLICATION WAS SUCCESSFUL + console.log("[DCM] [listener.js] [" + getTime("log") + "] Reapplied the SAM profile to " + device.name + " : " + device.uuid + "."); + // SEND CONFIRMATION TO DCM + res.json({ + status: 'ok' + }); + } } - break; + }); + break; - // CHANGE DEVICE BRIGHTNESS - case "brightness": + // CHANGE DEVICE BRIGHTNESS + case "brightness": + devices.forEach(async (device, i) => { if (device.name == target.device) { let ipaddr = ''; if (config.manual_ip) { @@ -237,9 +259,51 @@ server.post("/", (payload, res) => { }); } } + }); + break; + + // INCOMING COMMAND + case "cmd": + if(!config.allow_cmds) { + res.json({ + status: 'error', + node: config.name, + error: 'Listener is not configured to execute local commands.' + }); break; - } - }); + } + // CHECK IF COMMAND IS IN THE CONFIG + cmds.forEach(async (cmd, i) => { + if(cmd.alias == target.cmdID) { + var cmd_string = cmd.command.toString(); + var cmd_results = await cli_exec(cmd_string, 'local_command'); + if(cmd_results.hasError) { + res.json({ + status: 'error', + node: config.name, + message: 'Failed to execute command: ' + cmd.alias + '\n' + cmd_results.error + }); + } + else { + res.json({ + status: 'ok', + node: config.name, + message: 'Listener ran command `'+cmd.alias+'` and output the following:\n'+cmd_results.result + }); + } + } + }); + break; + + // DEFAULT + default: + res.json({ + status: 'error', + node: config.name, + error: 'Listener received an unhandled request type: ' + target.type + }); + break; + } }); //------------------------------------------------------------------------------ @@ -343,6 +407,9 @@ function cli_exec(command, type) { } return resolve(ipaddr); + // BLOCK IN CASE WE NEED DIFFERENT INFO FOR LOCAL COMMANDS + case 'local_command': + // GENERAL IDEVICEDIAGNOSTICS COMMAND case 'device_command': response.hasError = false;