New command run dialog box can now run scripts that are stored on the server.

Ylian Saint-Hilaire committed Aug 19, 2022 at 17:30 UTC d171d2af8241cd5f2d9a71154f007ce6ee9f2277
3 files changed +122 -87
meshuser.js
+89 -70
@@ -2784,92 +2784,111 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2784 {
2785 if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
2786 if (typeof command.type != 'number') break; // Check command type
2787 - if (typeof command.cmds != 'string') break; // Check commands
2787 if (typeof command.runAsUser != 'number') { command.runAsUser = 0; } // Check runAsUser
2788
2790 - for (i in command.nodeids) {
2791 - var nodeid = command.nodeids[i], err = null;
2789 + const processRunCommand = function (command) {
2790 + for (i in command.nodeids) {
2791 + var nodeid = command.nodeids[i], err = null;
2792
2793 - // Argument validation
2794 - if (common.validateString(nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check nodeid
2795 - else {
2796 - if (nodeid.indexOf('/') == -1) { nodeid = 'node/' + domain.id + '/' + nodeid; }
2797 - if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
2798 - }
2799 - if (err != null) {
2800 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: err })); } catch (ex) { } }
2801 - continue;
2802 - }
2803 -
2804 - // Get the node and the rights for this node
2805 - parent.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
2806 - // Check if this node was found
2807 - if (node == null) {
2808 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Invalid nodeid' })); } catch (ex) { } }
2809 - return;
2793 + // Argument validation
2794 + if (common.validateString(nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check nodeid
2795 + else {
2796 + if (nodeid.indexOf('/') == -1) { nodeid = 'node/' + domain.id + '/' + nodeid; }
2797 + if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
2798 + }
2799 + if (err != null) {
2800 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: err })); } catch (ex) { } }
2801 + continue;
2802 }
2803
2812 - if (command.type == 4) {
2813 - // This is an agent console command
2814 -
2815 - // Check we have the rights to run commands on this device, MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
2816 - if ((rights & 24) != 24) {
2817 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
2804 + // Get the node and the rights for this node
2805 + parent.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
2806 + // Check if this node was found
2807 + if (node == null) {
2808 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Invalid nodeid' })); } catch (ex) { } }
2809 return;
2810 }
2811
2821 - // Send the commands to the agent
2822 - var agent = parent.wsagents[node._id];
2823 - if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) {
2824 - try { agent.send(JSON.stringify({ action: 'msg', type: 'console', value: command.cmds, rights: rights, sessionid: ws.sessionId })); } catch (ex) { }
2825 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
2826 - } else {
2827 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } }
2828 - }
2829 - } else {
2830 - // This is a standard (bash/shell/powershell) command.
2812 + if (command.type == 4) {
2813 + // This is an agent console command
2814
2832 - // Check we have the rights to run commands on this device
2833 - if ((rights & MESHRIGHT_REMOTECOMMAND) == 0) {
2834 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
2835 - return;
2836 - }
2815 + // Check we have the rights to run commands on this device, MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
2816 + if ((rights & 24) != 24) {
2817 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
2818 + return;
2819 + }
2820
2838 - // Get the agent and run the commands
2839 - var agent = parent.wsagents[node._id];
2840 - if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) {
2841 - // Check if this agent is correct for this command type
2842 - // command.type 1 = Windows Command, 2 = Windows PowerShell, 3 = Linux/BSD/macOS
2843 - var commandsOk = false;
2844 - if ((agent.agentInfo.agentId > 0) && (agent.agentInfo.agentId < 5)) {
2845 - // Windows Agent
2846 - if ((command.type == 1) || (command.type == 2)) { commandsOk = true; }
2847 - else if (command.type === 0) { command.type = 1; commandsOk = true; } // Set the default type of this agent
2821 + // Send the commands to the agent
2822 + var agent = parent.wsagents[node._id];
2823 + if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) {
2824 + try { agent.send(JSON.stringify({ action: 'msg', type: 'console', value: command.cmds, rights: rights, sessionid: ws.sessionId })); } catch (ex) { }
2825 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
2826 } else {
2849 - // Non-Windows Agent
2850 - if (command.type == 3) { commandsOk = true; }
2851 - else if (command.type === 0) { command.type = 3; commandsOk = true; } // Set the default type of this agent
2827 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } }
2828 }
2853 - if (commandsOk == true) {
2854 - // Send the commands to the agent
2855 - try { agent.send(JSON.stringify({ action: 'runcommands', type: command.type, cmds: command.cmds, runAsUser: command.runAsUser })); } catch (ex) { }
2856 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
2829 + } else {
2830 + // This is a standard (bash/shell/powershell) command.
2831
2858 - // Send out an event that these commands where run on this device
2859 - var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
2860 - var msgid = 24; // "Running commands"
2861 - if (command.type == 1) { msgid = 99; } // "Running commands as user"
2862 - if (command.type == 2) { msgid = 100; } // "Running commands as user if possible"
2863 - var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'runcommands', msg: 'Running commands', msgid: msgid, cmds: command.cmds, cmdType: command.type, runAsUser: command.runAsUser, domain: domain.id };
2864 - parent.parent.DispatchEvent(targets, obj, event);
2832 + // Check we have the rights to run commands on this device
2833 + if ((rights & MESHRIGHT_REMOTECOMMAND) == 0) {
2834 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
2835 + return;
2836 + }
2837 +
2838 + // Get the agent and run the commands
2839 + var agent = parent.wsagents[node._id];
2840 + if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) {
2841 + // Check if this agent is correct for this command type
2842 + // command.type 1 = Windows Command, 2 = Windows PowerShell, 3 = Linux/BSD/macOS
2843 + var commandsOk = false;
2844 + if ((agent.agentInfo.agentId > 0) && (agent.agentInfo.agentId < 5)) {
2845 + // Windows Agent
2846 + if ((command.type == 1) || (command.type == 2)) { commandsOk = true; }
2847 + else if (command.type === 0) { command.type = 1; commandsOk = true; } // Set the default type of this agent
2848 + } else {
2849 + // Non-Windows Agent
2850 + if (command.type == 3) { commandsOk = true; }
2851 + else if (command.type === 0) { command.type = 3; commandsOk = true; } // Set the default type of this agent
2852 + }
2853 + if (commandsOk == true) {
2854 + // Send the commands to the agent
2855 + try { agent.send(JSON.stringify({ action: 'runcommands', type: command.type, cmds: command.cmds, runAsUser: command.runAsUser })); } catch (ex) { }
2856 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
2857 +
2858 + // Send out an event that these commands where run on this device
2859 + var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
2860 + var msgid = 24; // "Running commands"
2861 + if (command.type == 1) { msgid = 99; } // "Running commands as user"
2862 + if (command.type == 2) { msgid = 100; } // "Running commands as user if possible"
2863 + var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'runcommands', msg: 'Running commands', msgid: msgid, cmds: command.cmds, cmdType: command.type, runAsUser: command.runAsUser, domain: domain.id };
2864 + parent.parent.DispatchEvent(targets, obj, event);
2865 + } else {
2866 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Invalid command type' })); } catch (ex) { } }
2867 + }
2868 } else {
2866 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Invalid command type' })); } catch (ex) { } }
2869 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } }
2870 }
2868 - } else {
2869 - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } }
2871 }
2871 - }
2872 - });
2872 + });
2873 + }
2874 + }
2875 +
2876 + if (typeof command.cmdpath == 'string') {
2877 + // If a server command path is used, load the script from the path
2878 + var file = parent.getServerFilePath(user, domain, command.cmdpath);
2879 + if (file != null) {
2880 + fs.readFile(file.fullpath, function (err, data) {
2881 + // If loaded correctly, run loaded commands
2882 + if ((err != null) || (data == null) || (data.length == 0) || (data.length > 65535)) return;
2883 + command.cmds = data.toString();
2884 + delete command.cmdpath;
2885 + processRunCommand(command);
2886 + });
2887 + }
2888 + } else if (typeof command.cmds == 'string') {
2889 + // Run provided commands
2890 + if (command.cmds.length > 65535) return;
2891 + processRunCommand(command);
2892 }
2893 break;
2894 }
public/styles/style.css
+2 -2
@@ -591,14 +591,14 @@ body {
591 width: 260px;
592 }
593
594 -#d3serveraction {
594 +#d3serveraction, #d2serveraction {
595 width: 100%;
596 background-color: #d3d9d6;
597 text-align: left;
598 padding: 3px;
599 }
600
601 -#d3serverfiles {
601 +#d3serverfiles, #d2serverfiles {
602 width: 100%;
603 height: 150px;
604 background-color: white;
views/default.handlebars
+31 -15
@@ -1281,7 +1281,7 @@
1281 </div>
1282 <div id=d3servermode>
1283 <div id=d3serveraction valign=bottom>
1284 - <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;
1284 + <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p3CurrentFolder></span>
1285 </div>
1286 <div id=d3serverfiles></div>
1287 </div>
@@ -5731,15 +5731,17 @@
5731 x += '</select>';
5732 x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0' + ((runopt.runAs == 0)?' selected':'') + '>' + "Run as agent" + '</option><option value=1' + ((runopt.runAs == 1)?' selected':'') + '>' + "Run as user, agent if no user" + '</option><option value=2' + ((runopt.runAs == 2)?' selected':'') + '>' + "Must run as user" + '</option></select>';
5733 x += '<select id=d2cmdsource onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px><option value=0' + ((runopt.source == 0)?' selected':'') + '>' + "Commands from text box" + '</option><option value=1' + ((runopt.source == 1)?' selected':'') + '>' + "Commands from file" + '</option>';
5734 - //if (userinfo.siteadmin & 8) { x += '<option value=2' + ((runopt.source == 2)?' selected':'') + '>' + "Commands from file on server" + '</option>'; }
5734 + if (userinfo.siteadmin & 8) { x += '<option value=2' + ((runopt.source == 2)?' selected':'') + '>' + "Commands from file on server" + '</option>'; }
5735 x += '</select><textarea id=d2runcmd onkeyup=d2runCommandValidate() style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll>' + (runopt.cmd ? EscapeHtml(decodeURIComponent(runopt.cmd)) : '') + '</textarea>';
5736 x += '<div id=d2runfile style=display:none><input id=d2runfileex type=file onchange=d2runCommandValidate() id=d2localFile name=files onchange=d2runCommandValidate() /></div>';
5737 - x += '<div id=d2runsfile style=display:none>bb</div>';
5737 + if (userinfo.siteadmin & 8) { x += '<div id=d2runsfile style=display:none><div id=d2serveraction valign=bottom><input type=button id=p2FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p2CurrentFolder></span></div><div id=d2serverfiles></div></div>'; }
5738 setDialogMode(2, "Run Commands", 3, d2groupActionFunctionRunCommands, x, options);
5739 Q('d2runcmd').focus();
5740 d2runCommandValidate();
5741 + if (userinfo.siteadmin & 8) { d3fileoptions = { dialog: 2, files: 'd2serverfiles', folderup: 'p2FolderUp', currentFolder: 'p2CurrentFolder', func: null }; d3updatefiles(); } // Update the server files
5742 }
5743 }
5744 +
5745 function d2runCommandValidate() {
5746 QV('d2cmduser', Q('d2cmdtype').value < 4);
5747 QV('d2runcmd', Q('d2cmdsource').value == 0);
@@ -5751,6 +5753,7 @@
5753 if (Q('d2cmdsource').value == 2) { ok = false; } // From server file
5754 QE('idx_dlgOkButton', ok);
5755 }
5756 +
5757 function d2groupActionFunctionRunCommands(b, options) {
5758 var type = 3;
5759 try { type = parseInt(Q('d2cmdtype').value); } catch (ex) { }
@@ -5770,7 +5773,11 @@
5773 }
5774 if (Q('d2cmdsource').value == 2) {
5775 // From server file
5773 - cmd.cmds = '';
5776 + var files = d3getFileSel();
5777 + if (files.length != 1) return;
5778 + cmd.cmdpath = d3filetreelocation.join('/') + '/' + files[0];
5779 + meshserver.send(cmd);
5780 + if (options.func) { options.func(); }
5781 }
5782 }
5783
@@ -16115,6 +16122,7 @@
16122 //
16123
16124 function d3init() {
16125 + d3fileoptions = { dialog: 1, filter: 'd3filter', files: 'd3serverfiles', folderup: 'p3FolderUp', currentFolder: 'p3CurrentFolder', func: d3setActions };
16126 Q('d3localFile').value = '';
16127 Q('d3localFile').accept = Q('d3filter').value;
16128 d3modechange();
@@ -16129,10 +16137,11 @@
16137
16138 var d3filetreelinkpath;
16139 var d3filetreelocation = [];
16132 -
16140 + var d3fileoptions = null;
16141 function d3updatefiles() {
16134 - if (Q('d3uploadMode').value == 1) return;
16135 - var html1 = '', html2 = '', filetreex = filetree, folderdepth = 1, publicPath = null;
16142 + if (d3fileoptions == null) return;
16143 + if ((d3fileoptions.filter == 'd3filter') && (Q('d3uploadMode').value == 1)) return;
16144 + var html1 = '', html2 = '', filetreex = filetree, folderdepth = 1, publicPath = null, lastFolderName = '';
16145
16146 // Navigate to path location, build the paths at the same time
16147 var d3filetreelocation2 = [], oldlinkpath = d3filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
@@ -16150,6 +16159,7 @@
16159 if (d3filetreelinkpath != '') { d3filetreelinkpath += '/' + d3filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + d3filetreelocation[i]; } }
16160 }
16161 filetreex = filetreex.f[d3filetreelocation[i]];
16162 + lastFolderName = filetreex.n;
16163 folderdepth++;
16164 } else {
16165 break;
@@ -16161,7 +16171,8 @@
16171 var filetreexx = p5sort_files(filetreex.f);
16172
16173 // File filter
16164 - var fileFilter = Q('d3filter').value
16174 + var fileFilter = '';
16175 + if (d3fileoptions.filter) { fileFilter = Q(d3fileoptions.filter).value };
16176
16177 // Display all files and folders at this location
16178 for (var i in filetreexx) {
@@ -16189,19 +16200,24 @@
16200 if (f.t < 3) { html1 += h; } else { html2 += h; }
16201 }
16202
16192 - QH('d3serverfiles', html1 + html2);
16193 - QE('p3FolderUp', d3filetreelocation.length > 0);
16194 - d3setActions();
16203 + if (d3fileoptions.currentFolder) { QH(d3fileoptions.currentFolder, lastFolderName); }
16204 + QH(d3fileoptions.files, html1 + html2);
16205 + QE(d3fileoptions.folderup, d3filetreelocation.length > 0);
16206 + if (d3fileoptions.func) { d3fileoptions.func(); }
16207 }
16208
16209 function d3folderset(x) { d3filetreelocation.push(decodeURIComponent(x)); d3updatefiles(); return false; }
16210 function d3folderup(x) { if (x == null) { d3filetreelocation.pop(); } else { while (d3filetreelocation.length > x) { d3filetreelocation.pop(); } } d3updatefiles(); }
16211 function d3getFileSel() { var cc = []; var checkboxes = document.getElementsByName('fcx'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { cc.push(checkboxes[i].value) } } return cc; }
16212 function d3setActions() {
16201 - var mode = Q('d3uploadMode').value;
16202 - if (mode == 1) {
16203 - QE('idx_dlgOkButton', Q('d3localFile').value.length > 0);
16204 - } else {
16213 + if (d3fileoptions.dialog == 1) {
16214 + var mode = Q('d3uploadMode').value;
16215 + if (mode == 1) {
16216 + QE('idx_dlgOkButton', Q('d3localFile').value.length > 0);
16217 + } else {
16218 + QE('idx_dlgOkButton', d3getFileSel().length == 1);
16219 + }
16220 + } else if (d3fileoptions.dialog == 2) {
16221 QE('idx_dlgOkButton', d3getFileSel().length == 1);
16222 }
16223 }