MeshCentral/agents/modules_meshcmd/amt-script.js
2018-02-02 12:46:09 -08:00

421 lines
22 KiB
JavaScript

/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @fileoverview Script Compiler / Decompiler / Runner
* @author Ylian Saint-Hilaire
* @version v0.1.0e
*/
// Core functions
script_functionTable1 = ['nop', 'jump', 'set', 'print', 'dialog', 'getitem', 'substr', 'indexof', 'split', 'join', 'length', 'jsonparse', 'jsonstr', 'add', 'substract', 'parseint', 'wsbatchenum', 'wsput', 'wscreate', 'wsdelete', 'wsexec', 'scriptspeed', 'wssubscribe', 'wsunsubscribe', 'readchar', 'signwithdummyca'];
// functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
script_functionTable2 = ['encodeuri', 'decodeuri', 'passwordcheck', 'atob', 'btoa', 'hex2str', 'str2hex', 'random', 'md5', 'maketoarray', 'readshort', 'readshortx', 'readint', 'readsint', 'readintx', 'shorttostr', 'shorttostrx', 'inttostr', 'inttostrx'];
// functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
script_functionTableX2 = [encodeURI, decodeURI, passwordcheck, atob, btoa, hex2rstr, rstr2hex, random, rstr_md5, MakeToArray, ReadShort, ReadShortX, ReadInt, ReadSInt, ReadIntX, ShortToStr, ShortToStrX, IntToStr, IntToStrX];
function MakeToArray(v) { if (!v || v == null || typeof v == 'object') return v; return [v]; }
function ReadShort(v, p) { return (v[p] << 8) + v[p + 1]; }
function ReadShortX(v, p) { return (v[p + 1] << 8) + v[p]; }
function ReadInt(v, p) { return (v[p] * 0x1000000) + (v[p + 1] << 16) + (v[p + 2] << 8) + v[p + 3]; } // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32.
function ReadSInt(v, p) { return (v[p] << 24) + (v[p + 1] << 16) + (v[p + 2] << 8) + v[p + 3]; }
function ReadIntX(v, p) { return (v[p + 3] * 0x1000000) + (v[p + 2] << 16) + (v[p + 1] << 8) + v[p]; }
function ShortToStr(v) { return ''; } // TODO
function ShortToStrX(v) { return ''; } // TODO
function IntToStr(v) { return ''; } // TODO
function IntToStrX(v) { return ''; } // TODO
function btoa(x) { return Buffer.from(x).toString('base64'); }
function atob(x) { var z = null; try { z = Buffer.from(x, 'base64').toString(); } catch (e) { console.log(e); } return z; }
function passwordcheck(p) { if (p.length < 8) return false; var upper = 0, lower = 0, number = 0, nonalpha = 0; for (var i in p) { var c = p.charCodeAt(i); if ((c > 64) && (c < 91)) { upper = 1; } else if ((c > 96) && (c < 123)) { lower = 1; } else if ((c > 47) && (c < 58)) { number = 1; } else { nonalpha = 1; } } return ((upper + lower + number + nonalpha) == 4); }
function hex2rstr(x) { Buffer.from(x, 'hex').toString(); }
function rstr2hex(x) { Buffer.from(x).toString('hex'); }
function random() { return 0; } // TODO
function rstr_md5(x) { return null; } // TODO
// Setup the script state
module.exports.setup = function(binary, startvars) {
var obj = { startvars: startvars };
if (binary.length < 6) { console.error('Invalid script length'); return null; } // Script must have at least 6 byte header
if (ReadInt(binary, 0) != 0x247D2945) { console.error('Invalid binary script'); return null; } // Check the script magic header
if (ReadShort(binary, 4) > 1) { console.error('Unsupported script version'); return null; } // Check the script version
obj.script = binary.slice(6);
// obj.onStep;
// obj.onConsole;
// Reset the script to the start
obj.reset = function (stepspeed) {
console.log('reset');
obj.stop();
obj.ip = 0;
obj.variables = startvars;
obj.state = 1;
}
// Start the script
obj.start = function (stepspeed) {
console.log('start');
obj.stop();
if (stepspeed == null) { obj.stepspeed = 100; } else { obj.stepspeed = stepspeed; }
if (obj.stepspeed > 0) { obj.timer = setInterval(function () { obj.step() }, obj.stepspeed); }
}
// Stop the script
obj.stop = function () {
console.log('stop');
if (obj.timer != null) { clearInterval(obj.timer); }
obj.timer = null;
obj.stepspeed = 0;
}
// function used to load and store variable values
obj.getVar = function (name) { if (name == undefined) return undefined; return obj.getVarEx(name.split('.'), obj.variables); }
obj.getVarEx = function (name, val) { try { if (name == undefined) return undefined; if (name.length == 0) return val; return obj.getVarEx(name.slice(1), val[name[0]]); } catch (e) { return null; } }
obj.setVar = function (name, val) { obj.setVarEx(name.split('.'), obj.variables, val); }
obj.setVarEx = function (name, vars, val) { if (name.length == 1) { vars[name[0]] = val; } else { obj.setVarEx(name.slice(1), vars[name[0]], val); } }
// Run the script one step forward
obj.step = function () {
console.log('step');
if (obj.state != 1) return;
if (obj.ip < obj.script.length) {
var cmdid = ReadShort(obj.script, obj.ip);
var cmdlen = ReadShort(obj.script, obj.ip + 2);
var argcount = ReadShort(obj.script, obj.ip + 4);
var argptr = obj.ip + 6;
var args = [];
// Clear all temp variables (This is optional)
for (var i in obj.variables) { if (i.startsWith('__')) { delete obj.variables[i]; } }
// Loop on each argument, moving forward by the argument length each time
for (var i = 0; i < argcount; i++) {
var arglen = ReadShort(obj.script, argptr);
var argval = obj.script.substring(argptr + 2, argptr + 2 + arglen); // <----------- Problem area
var argtyp = argval.charCodeAt(0);
argval = argval.substring(1);
if (argtyp < 2) {
// Get the value and replace all {var} with variable values
while (argval.split("{").length > 1) { var t = argval.split("{").pop().split("}").shift(); argval = argval.replace('{' + t + '}', obj.getVar(t)); }
if (argtyp == 1) { obj.variables['__' + i] = decodeURI(argval); argval = '__' + i; } // If argtyp is 1, this is a literal. Store in temp variable.
args.push(argval);
}
if (argtyp == 2 || argtyp == 3) {
obj.variables['__' + i] = ReadSInt(argval, 0);
args.push('__' + i);
}
argptr += (2 + arglen);
}
// Move instruction pointer forward by command size
obj.ip += cmdlen;
// Get all variable values
var argsval = [];
for (var i = 0; i < 10; i++) { argsval.push(obj.getVar(args[i])); }
var storeInArg0;
try {
if (cmdid < 10000) {
// Lets run the actual command
switch (cmdid) {
case 0: // nop
break;
case 1: // jump(label) or jump(label, a, compare, b)
if (argsval[2]) {
if (
(argsval[2] == '<' && argsval[1] < argsval[3]) ||
(argsval[2] == '<=' && argsval[1] <= argsval[3]) ||
(argsval[2] == '!=' && argsval[1] != argsval[3]) ||
(argsval[2] == '=' && argsval[1] == argsval[3]) ||
(argsval[2] == '>=' && argsval[1] >= argsval[3]) ||
(argsval[2] == '>' && argsval[1] > argsval[3])
) { obj.ip = argsval[0]; }
} else {
obj.ip = argsval[0]; // Set the instruction pointer to the new location in the script
}
break;
case 2: // set(variable, value)
if (args[1] == undefined) delete obj.variables[args[0]]; else obj.setVar(args[0], argsval[1]);
break;
case 3: // print(message)
if (obj.onConsole) { obj.onConsole(obj.toString(argsval[0]), obj); } else { console.log(obj.toString(argsval[0])); }
// Q(obj.consoleid).value += () + '\n'); Q(obj.console).scrollTop = Q(obj.console).scrollHeight;
break;
case 4: // dialog(title, content, buttons)
obj.state = 2;
obj.dialog = true;
setDialogMode(11, argsval[0], argsval[2], obj.xxStepDialogOk, argsval[1], obj);
break;
case 5: // getitem(a, b, c)
for (var i in argsval[1]) { if (argsval[1][i][argsval[2]] == argsval[3]) { storeInArg0 = i; } };
break;
case 6: // substr(variable_dest, variable_src, index, len)
storeInArg0 = argsval[1].substr(argsval[2], argsval[3]);
break;
case 7: // indexOf(variable_dest, variable_src, index, len)
storeInArg0 = argsval[1].indexOf(argsval[2]);
break;
case 8: // split(variable_dest, variable_src, separator)
storeInArg0 = argsval[1].split(argsval[2]);
break;
case 9: // join(variable_dest, variable_src, separator)
storeInArg0 = argsval[1].join(argsval[2]);
break;
case 10: // length(variable_dest, variable_src)
storeInArg0 = argsval[1].length;
break;
case 11: // jsonparse(variable_dest, json)
storeInArg0 = JSON.parse(argsval[1]);
break;
case 12: // jsonstr(variable_dest, variable_src)
storeInArg0 = JSON.stringify(argsval[1]);
break;
case 13: // add(variable_dest, variable_src, value)
storeInArg0 = (argsval[1] + argsval[2]);
break;
case 14: // substract(variable_dest, variable_src, value)
storeInArg0 = (argsval[1] - argsval[2]);
break;
case 15: // parseInt(variable_dest, variable_src)
storeInArg0 = parseInt(argsval[1]);
break;
case 16: // wsbatchenum(name, objectList)
obj.state = 2;
obj.amtstack.BatchEnum(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
break;
case 17: // wsput(name, args)
obj.state = 2;
obj.amtstack.Put(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
break;
case 18: // wscreate(name, args)
obj.state = 2;
obj.amtstack.Create(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
break;
case 19: // wsdelete(name, args)
obj.state = 2;
obj.amtstack.Delete(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
break;
case 20: // wsexec(name, method, args, selectors)
obj.state = 2;
obj.amtstack.Exec(argsval[0], argsval[1], argsval[2], obj.xxWsmanReturn, obj, 0, argsval[3]);
break;
case 21: // Script Speed
obj.stepspeed = argsval[0];
if (obj.timer != null) { clearInterval(obj.timer); obj.timer = setInterval(function () { obj.step() }, obj.stepspeed); }
break;
case 22: // wssubscribe(name, delivery, url, selectors, opaque, user, pass)
obj.state = 2;
obj.amtstack.Subscribe(argsval[0], argsval[1], argsval[2], obj.xxWsmanReturn, obj, 0, argsval[3], argsval[4], argsval[5], argsval[6]);
break;
case 23: // wsunsubscribe(name, selectors)
obj.state = 2;
obj.amtstack.UnSubscribe(argsval[0], obj.xxWsmanReturn, obj, 0, argsval[1]);
break;
case 24: // readchar(str, pos)
console.log(argsval[1], argsval[2], argsval[1].charCodeAt(argsval[2]));
storeInArg0 = argsval[1].charCodeAt(argsval[2]);
break;
case 25: // signWithDummyCa
// ###BEGIN###{Certificates}
obj.state = 2;
// DERKey, xxCaPrivateKey, certattributes, issuerattributes
amtcert_signWithCaKey(argsval[0], null, argsval[1], { 'CN': 'Untrusted Root Certificate' }, obj.xxSignWithDummyCaReturn);
// ###END###{Certificates}
break;
default: {
obj.state = 9;
console.error("Script Error, unknown command: " + cmdid);
}
}
} else {
if (cmdid < 20000) {
// functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
//storeInArg0 = script_functionTableX2[cmdid - 10000](argsval[1], argsval[2], argsval[3], argsval[4], argsval[5], argsval[6]);
} else {
// Optional functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
//if (script_functionTableX3 && script_functionTableX3[cmdid - 20000]) {
// storeInArg0 = script_functionTableX3[cmdid - 20000](obj, argsval[1], argsval[2], argsval[3], argsval[4], argsval[5], argsval[6]); // Note that optional calls start with "obj" as first argument.
//}
}
}
if (storeInArg0 != undefined) obj.setVar(args[0], storeInArg0);
} catch (e) {
if (typeof e == 'object') { e = e.message; }
obj.setVar('_exception', e);
}
}
if (obj.state == 1 && obj.ip >= obj.script.length) { obj.state = 0; obj.stop(); }
if (obj.onStep) obj.onStep(obj);
return obj;
}
obj.xxStepDialogOk = function (button) {
obj.variables['DialogSelect'] = button;
obj.state = 1;
obj.dialog = false;
if (obj.onStep) obj.onStep(obj);
}
// ###BEGIN###{**ClosureAdvancedMode}
obj.xxWsmanReturnFix = function (x) {
if (!x || x == null) return;
if (x.Header) { x['Header'] = x.Header; delete x.Header; }
if (x.Body) { x['Body'] = x.Body; delete x.Body; }
if (x.Responses) { x['Responses'] = x.Responses; delete x.Responses; }
if (x.Response) { x['Response'] = x.Response; delete x.Response; }
if (x.ReturnValueStr) { x['ReturnValueStr'] = x.ReturnValueStr; delete x.ReturnValueStr; }
}
// ###END###{**ClosureAdvancedMode}
obj.xxWsmanReturn = function (stack, name, responses, status) {
// ###BEGIN###{**ClosureAdvancedMode}
// This is required when Google Closure is used
if (responses) {
obj.xxWsmanReturnFix(responses);
for (var i in responses) {
obj.xxWsmanReturnFix(responses[i]);
for (var j in responses[i]) { obj.xxWsmanReturnFix(responses[i][j]); }
}
}
// ###END###{**ClosureAdvancedMode}
obj.setVar(name, responses);
obj.setVar('wsman_result', status);
obj.setVar('wsman_result_str', ((httpErrorTable[status]) ? (httpErrorTable[status]) : ('Error #' + status)));
obj.state = 1;
if (obj.onStep) obj.onStep(obj);
}
// ###BEGIN###{Certificates}
obj.xxSignWithDummyCaReturn = function (cert) {
obj.setVar('signed_cert', btoa(_arrayBufferToString(cert)));
obj.state = 1;
if (obj.onStep) obj.onStep(obj);
}
// ###END###{Certificates}
obj.toString = function (x) { if (typeof x == 'object') return JSON.stringify(x); return x; }
obj.reset();
return obj;
}
// Argument types: 0 = Variable, 1 = String, 2 = Integer, 3 = Label
module.exports.compile = function(script, onmsg) {
var r = '', scriptlines = script.split('\n'), labels = {}, labelswap = [], swaps = [];
// Go thru each script line and encode it
for (var i in scriptlines) {
var scriptline = scriptlines[i];
if (scriptline.startsWith('##SWAP ')) { var x = scriptline.split(' '); if (x.length == 3) { swaps[x[1]] = x[2]; } } // Add a swap instance
if (scriptline[0] == '#' || scriptline.length == 0) continue; // Skip comments & blank lines
for (var x in swaps) { scriptline = scriptline.split(x).join(swaps[x]); } // Apply all swaps
var keywords = scriptline.match(/"[^"]*"|[^\s"]+/g);
if (keywords.length == 0) continue; // Skip blank lines
if (scriptline[0] == ':') { labels[keywords[0].toUpperCase()] = r.length; continue; } // Mark a label position
var funcIndex = script_functionTable1.indexOf(keywords[0].toLowerCase());
if (funcIndex == -1) { funcIndex = script_functionTable2.indexOf(keywords[0].toLowerCase()); if (funcIndex >= 0) funcIndex += 10000; }
if (funcIndex == -1) { if (onmsg) { onmsg("Unabled to compile, unknown command: " + keywords[0]); } return ''; }
// Encode CommandId, CmdSize, ArgCount, Arg1Len, Arg1, Arg2Len, Arg2...
var cmd = ShortToStr(keywords.length - 1);
for (var j in keywords) {
if (j == 0) continue;
if (keywords[j][0] == ':') {
labelswap.push([keywords[j], r.length + cmd.length + 7]); // Add a label swap
cmd += ShortToStr(5) + String.fromCharCode(3) + IntToStr(0xFFFFFFFF); // Put an empty label
} else {
var argint = parseInt(keywords[j]);
if (argint == keywords[j]) {
cmd += ShortToStr(5) + String.fromCharCode(2) + IntToStr(argint);
} else {
if (keywords[j][0] == '"' && keywords[j][keywords[j].length - 1] == '"') {
cmd += ShortToStr(keywords[j].length - 1) + String.fromCharCode(1) + keywords[j].substring(1, keywords[j].length - 1);
} else {
cmd += ShortToStr(keywords[j].length + 1) + String.fromCharCode(0) + keywords[j];
}
}
}
}
cmd = ShortToStr(funcIndex) + ShortToStr(cmd.length + 4) + cmd;
r += cmd;
}
// Perform all the needed label swaps
for (i in labelswap) {
var label = labelswap[i][0].toUpperCase(), position = labelswap[i][1], target = labels[label];
if (target == undefined) { if (onmsg) { onmsg("Unabled to compile, unknown label: " + label); } return ''; }
r = r.substr(0, position) + IntToStr(target) + r.substr(position + 4);
}
return IntToStr(0x247D2945) + ShortToStr(1) + r;
}
// Decompile the script, intended for debugging only
module.exports.decompile = function(binary, onecmd) {
var r = '', ptr = 6, labelcount = 0, labels = {};
if (onecmd >= 0) {
ptr = onecmd; // If we are decompiling just one command, set the ptr to that command.
} else {
if (binary.length < 6) { return '# Invalid script length'; }
var magic = ReadInt(binary, 0);
var version = ReadShort(binary, 4);
if (magic != 0x247D2945) { return '# Invalid binary script: ' + magic; }
if (version != 1) { return '# Invalid script version'; }
}
// Loop on each command, moving forward by the command length each time.
while (ptr < binary.length) {
var cmdid = ReadShort(binary, ptr);
var cmdlen = ReadShort(binary, ptr + 2);
var argcount = ReadShort(binary, ptr + 4);
var argptr = ptr + 6;
var argstr = '';
if (!(onecmd >= 0)) r += ":label" + (ptr - 6) + "\n";
// Loop on each argument, moving forward by the argument length each time
for (var i = 0; i < argcount; i++) {
var arglen = ReadShort(binary, argptr);
var argval = binary.substring(argptr + 2, argptr + 2 + arglen);
var argtyp = argval.charCodeAt(0);
if (argtyp == 0) { argstr += ' ' + argval.substring(1); } // Variable
else if (argtyp == 1) { argstr += ' \"' + argval.substring(1) + '\"'; } // String
else if (argtyp == 2) { argstr += ' ' + ReadInt(argval, 1); } // Integer
else if (argtyp == 3) { // Label
var target = ReadInt(argval, 1);
var label = labels[target];
if (!label) { label = ":label" + target; labels[label] = target; }
argstr += ' ' + label;
}
argptr += (2 + arglen);
}
// Go in the script function table to decode the function
if (cmdid < 10000) {
r += script_functionTable1[cmdid] + argstr + "\n";
} else {
if ((cmdid >= 10000) && (cmdid < 10000)) { r += script_functionTable2[cmdid - 10000] + argstr + "\n"; }
}
ptr += cmdlen;
if (onecmd >= 0) return r; // If we are decompiling just one command, exit now
}
// Remove all unused labels
var scriptlines = r.split('\n');
r = '';
for (var i in scriptlines) {
var line = scriptlines[i];
if (line[0] != ':') { r += line + '\n'; } else { if (labels[line]) { r += line + '\n'; } }
}
return r;
}