From c0eab31b025d2259c664905a00e6de178ea214b3 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Sat, 17 Oct 2020 19:23:24 -0700 Subject: [PATCH] Added Intel AMT tunneling configuration support. --- agents/meshcmd.js | 98 +++++- agents/meshcore.js | 33 +- agents/modules_meshcmd/apfclient.js | 450 +++++++++++++++++++++++++++ agents/modules_meshcore/apfclient.js | 119 ++++--- amt/amt-wsman-comm.js | 6 +- amtmanager.js | 98 ++++-- mpsserver.js | 10 + webserver.js | 4 +- 8 files changed, 709 insertions(+), 109 deletions(-) create mode 100644 agents/modules_meshcmd/apfclient.js diff --git a/agents/meshcmd.js b/agents/meshcmd.js index ebd3d761..fee1f627 100644 --- a/agents/meshcmd.js +++ b/agents/meshcmd.js @@ -113,7 +113,7 @@ function run(argv) { //console.log('addedModules = ' + JSON.stringify(addedModules)); var actionpath = 'meshaction.txt'; if (args.actionfile != null) { actionpath = args.actionfile; } - var actions = ['HELP', 'ROUTE', 'MICROLMS', 'AMTSCAN', 'AMTPOWER', 'AMTFEATURES', 'AMTNETWORK', 'AMTLOADWEBAPP', 'AMTLOADSMALLWEBAPP', 'AMTLOADLARGEWEBAPP', 'AMTCLEARWEBAPP', 'AMTSTORAGESTATE', 'AMTINFO', 'AMTINFODEBUG', 'AMTVERSIONS', 'AMTHASHES', 'AMTSAVESTATE', 'AMTSCRIPT', 'AMTUUID', 'AMTCCM', 'AMTACM', 'AMTDEACTIVATE', 'AMTACMDEACTIVATE', 'SMBIOS', 'RAWSMBIOS', 'MESHCOMMANDER', 'AMTAUDITLOG', 'AMTEVENTLOG', 'AMTPRESENCE', 'AMTWIFI', 'AMTWAKE']; + var actions = ['HELP', 'ROUTE', 'MICROLMS', 'AMTCONFIG', 'AMTSCAN', 'AMTPOWER', 'AMTFEATURES', 'AMTNETWORK', 'AMTLOADWEBAPP', 'AMTLOADSMALLWEBAPP', 'AMTLOADLARGEWEBAPP', 'AMTCLEARWEBAPP', 'AMTSTORAGESTATE', 'AMTINFO', 'AMTINFODEBUG', 'AMTVERSIONS', 'AMTHASHES', 'AMTSAVESTATE', 'AMTSCRIPT', 'AMTUUID', 'AMTCCM', 'AMTACM', 'AMTDEACTIVATE', 'AMTACMDEACTIVATE', 'SMBIOS', 'RAWSMBIOS', 'MESHCOMMANDER', 'AMTAUDITLOG', 'AMTEVENTLOG', 'AMTPRESENCE', 'AMTWIFI', 'AMTWAKE']; // Load the action file var actionfile = null; @@ -129,6 +129,7 @@ function run(argv) { if ((typeof args.localport) == 'string') { settings.localport = parseInt(args.localport); } if ((typeof args.remotenodeid) == 'string') { settings.remotenodeid = args.remotenodeid; } if ((typeof args.name) == 'string') { settings.name = args.name; } + if ((typeof args.id) == 'string') { settings.id = args.id; } if ((typeof args.username) == 'string') { settings.username = args.username; } if ((typeof args.password) == 'string') { settings.password = args.password; } if ((typeof args.url) == 'string') { settings.url = args.url; } @@ -174,6 +175,7 @@ function run(argv) { console.log(' meshcmd [action] [arguments...]\r\n'); console.log('Valid MeshCentral actions:'); console.log(' Route - Map a local TCP port to a remote computer.'); + console.log(' AmtConfig - Setup Intel AMT on this computer.'); console.log('\r\nValid local actions:'); console.log(' SMBios - Display System Management BIOS tables for this computer.'); console.log(' RawSMBios - Display RAW System Management BIOS tables for this computer.'); @@ -245,6 +247,12 @@ function run(argv) { console.log(' --tag [string] Optional string sent to the server during activation.'); console.log(' --serverhttpshash [hash] Optional TLS server certificate hash.'); console.log(' --profile [string] Optional profile used for server activation.'); + } else if (action == 'amtconfig') { + console.log('AmtConfig will attempt to activate and configure Intel AMT on this computer. The command must be run on a computer with Intel AMT, must run as administrator and the Intel management driver must be installed. Example usage:\r\n\r\n meshcmd amtconfig --url [url]'); + console.log('\r\nPossible arguments:\r\n'); + console.log(' --url [wss://server] The address of the MeshCentral server.'); + console.log(' --id [groupid] The device group identifier.'); + console.log(' --serverhttpshash [hash] Optional TLS server certificate hash.'); } else if (action == 'amtacm') { console.log('AmtACM will attempt to activate Intel AMT on this computer into admin control mode (ACM). The command must be run on a computer with Intel AMT, must run as administrator and the Intel management driver must be installed. Intel AMT must be in "pre-provisioning" state for this command to work. Example usage:\r\n\r\n meshcmd amtacm --url [url]'); console.log('\r\nPossible arguments:\r\n'); @@ -643,6 +651,12 @@ function run(argv) { settings.localport = 16992; debug(1, "Settings: " + JSON.stringify(settings)); getAmtUuid(); + } else if (settings.action == 'amtconfig') { + // Start Intel AMT configuration + if ((settings.url == null) || (typeof settings.url != 'string') || (settings.url == '')) { console.log('No MeshCentral server URL specified, use --url [url].'); exit(1); return; } + if ((settings.id == null) || (typeof settings.id != 'string') || (settings.id == '')) { console.log('No device group identifier specified, use --id [identifier].'); exit(1); return; } + debug(1, "Settings: " + JSON.stringify(settings)); + configureAmt(); } else if (settings.action == 'amtccm') { // Start activation to CCM if (((settings.password == null) || (typeof settings.password != 'string') || (settings.password == '')) && ((settings.url == null) || (typeof settings.url != 'string') || (settings.url == ''))) { console.log('No or invalid parameters specified, use --password [password] or --url [url].'); exit(1); return; } @@ -1127,6 +1141,88 @@ function startMeshCommander() { } +// +// Configure Intel AMT +// + +function configureAmt() { + console.log('Starting Intel AMT configuration...'); + settings.noconsole = true; + + // Display Intel AMT version and activation state + mestate = {}; + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { console.log(ex); exit(1); return; } + amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; }); + amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } }); + amtMei.getVersion(function (val) { mestate.vers = {}; if (val != null) { for (var version in val.Versions) { mestate.vers[val.Versions[version].Description] = val.Versions[version].Version; } } }); + amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } }); + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { mestate.uuid = result.uuid; } }); + amtMei.getControlMode(function (result) { if (result != null) { mestate.controlMode = result.controlMode; } }); // controlMode: 0 = NoActivated, 1 = CCM, 2 = ACM + amtMei.getDnsSuffix(function (result) { + if ((mestate.vers == null) || (mestate.vers['AMT'] == null)) { console.log("Unable to get Intel AMT version."); exit(100); return; } + if (mestate.ProvisioningState == null) { console.log("Unable to read Intel AMT activation state."); exit(100); return; } + //if ((settings.action != 'amtdiscover') && (mestate.controlMode == 2)) { console.log("Intel AMT already activation in admin control mode."); exit(100); return; } + if (mestate.uuid == null) { console.log("Unable to get Intel AMT UUID."); exit(100); return; } + var fqdn = null; + //if ((mestate.net0 == null) && (meinfo.net0.enabled != 0)) { console.log("No Intel AMT wired interface, can't perform ACM activation."); exit(100); return; } + if (result) { fqdn = result; } // If Intel AMT has a trusted DNS suffix set, use that one. + else { + // Look for the DNS suffix for the Intel AMT Ethernet interface + var interfaces = require('os').networkInterfaces(); + for (var i in interfaces) { + for (var j in interfaces[i]) { + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { fqdn = interfaces[i][j].fqdn; } + } + } + } + if (fqdn != null) { settings.fqdn = fqdn; settings.uuid = mestate.uuid; } + getTrustedHashes(amtMei, function () { startLms(configureAmt2, amtMei); }); + }); + +} + +function configureAmt2() { + // Connect to MPS and start APF relay + var apfarg = { + mpsurl: settings.url, + mpsuser: settings.id.substring(0, 16), + mpspass: settings.id.substring(0, 16), + mpskeepalive: 60000, + clientname: require('os').hostname(), + clientaddress: '127.0.0.1', + clientuuid: mestate.uuid, + conntype: 2 // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay. + }; + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) { + console.log("Unable to get Intel AMT UUID: " + apfarg.clientuuid); + exit(1); return; + } else { + settings.apftunnel = require('apfclient')({ debug: (settings.debuglevel > 0) }, apfarg); + settings.apftunnel.onJsonControl = configureJsonControl; + settings.apftunnel.onChannelClosed = function () { exit(0); } + try { + settings.apftunnel.connect(); + console.log("Started APF tunnel..."); + } catch (e) { + console.log(JSON.stringify(e)); + exit(1); return; + } + } +} + +function configureJsonControl(data) { + switch (data.action) { + case 'console': + console.log(data.msg); + break; + case 'close': + exit(0); + break; + } +} + + // // Deactivate Intel AMT CCM // diff --git a/agents/meshcore.js b/agents/meshcore.js index 8efe9230..174ac102 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -2563,7 +2563,7 @@ function createMeshCore(agent) { var response = null; switch (cmd) { case 'help': { // Displays available commands - var fin = '', f = '', availcommands = 'coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper,agentmsg'; + var fin = '', f = '', availcommands = 'amtconfig,coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper,agentmsg'; if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; } if (process.platform != 'freebsd') { availcommands += ',vm';} if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; } @@ -3540,6 +3540,29 @@ function createMeshCore(agent) { if (diag) { diag.close(); diag = null; } break; } + case 'amtconfig': { + if (meshCoreObj.intelamt == null) { response = "No Intel AMT support delected"; break; } + if (apftunnel != null) { response = "Intel AMT server tunnel already active"; break; } + var apfarg = { + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpskeepalive: 60000, + clientname: require('os').hostname(), + clientaddress: '127.0.0.1', + clientuuid: meshCoreObj.intelamt.uuid, + conntype: 2 // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + }; + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) { response = "Unable to get Intel AMT UUID"; break; } + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } + } + apftunnel.onChannelClosed = function () { apftunnel = null; } + try { apftunnel.connect(); response = "Started Intel AMT configuration"; } catch (ex) { response = JSON.stringify(ex); } + break; + } case 'apf': { if (meshCoreObj.intelamt !== null) { if (args['_'].length == 1) { @@ -3562,8 +3585,12 @@ function createMeshCore(agent) { if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) { response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid; } else { - var tobj = { debug: false }; - apftunnel = require('apfclient')(tobj, apfarg); + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } + } + apftunnel.onChannelClosed = function () { apftunnel = null; } try { apftunnel.connect(); response = "Started APF tunnel"; diff --git a/agents/modules_meshcmd/apfclient.js b/agents/modules_meshcmd/apfclient.js new file mode 100644 index 00000000..155a3b56 --- /dev/null +++ b/agents/modules_meshcmd/apfclient.js @@ -0,0 +1,450 @@ +/* +Copyright 2018-2020 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. +*/ + +/** +* @description APF/CIRA Client for Duktape +* @author Joko Sastriawan & Ylian Saint-Hilaire +* @copyright Intel Corporation 2020 +* @license Apache-2.0 +* @version v0.0.2 +*/ + +function CreateAPFClient(parent, args) { + if ((args.clientuuid == null) || (args.clientuuid.length != 36)) return null; // Require a UUID if this exact length + + var obj = {}; + obj.parent = parent; + obj.args = args; + obj.http = require('http'); + obj.net = require('net'); + obj.forwardClient = null; + obj.downlinks = {}; + obj.pfwd_idx = 0; + obj.timer = null; // Keep alive timer + + // obj.onChannelClosed + // obj.onJsonControl + + // Function copied from common.js + function ReadInt(v, p) { return (v.charCodeAt(p) * 0x1000000) + (v.charCodeAt(p + 1) << 16) + (v.charCodeAt(p + 2) << 8) + v.charCodeAt(p + 3); }; // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32. + function IntToStr(v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); }; + function hex2rstr(d) { var r = '', m = ('' + d).match(/../g), t; while (t = m.shift()) { r += String.fromCharCode('0x' + t); } return r; }; + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }; // Convert decimal to hex + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }; // Convert a raw string to a hex string + function d2h(d) { return (d / 256 + 1 / 512).toString(16).substring(2, 4); } + function buf2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += d2h(input[i]); } return r; }; + function Debug(str) { if (obj.parent.debug) { console.log(str); } } + function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + "-" + g.substring(10, 12) + g.substring(8, 10) + "-" + g.substring(14, 16) + g.substring(12, 14) + "-" + g.substring(16, 20) + "-" + g.substring(20); } + function strToGuid(s) { s = s.replace(/-/g, ''); var ret = s.substring(6, 8) + s.substring(4, 6) + s.substring(2, 4) + s.substring(0, 2) + s.substring(10, 12) + s.substring(8, 10) + s.substring(14, 16) + s.substring(12, 14) + s.substring(16, 20) + s.substring(20); return ret; } + function binzerostring(len) { var res = ''; for (var l = 0; l < len; l++) { res += String.fromCharCode(0 & 0xFF); } return res; } + + // CIRA state + var CIRASTATE = { + INITIAL: 0, + PROTOCOL_VERSION_SENT: 1, + AUTH_SERVICE_REQUEST_SENT: 2, + AUTH_REQUEST_SENT: 3, + PFWD_SERVICE_REQUEST_SENT: 4, + GLOBAL_REQUEST_SENT: 5, + FAILED: -1 + } + obj.cirastate = CIRASTATE.INITIAL; + + // REDIR state + var REDIR_TYPE = { + REDIR_UNKNOWN: 0, + REDIR_SOL: 1, + REDIR_KVM: 2, + REDIR_IDER: 3 + } + + // redirection start command + obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20); + obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52); + obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52); + + // Intel AMT forwarded port list for non-TLS mode + //var pfwd_ports = [16992, 623, 16994, 5900]; + var pfwd_ports = [ 16992, 16993 ]; + + // protocol definitions + var APFProtocol = { + UNKNOWN: 0, + DISCONNECT: 1, + SERVICE_REQUEST: 5, + SERVICE_ACCEPT: 6, + USERAUTH_REQUEST: 50, + USERAUTH_FAILURE: 51, + USERAUTH_SUCCESS: 52, + GLOBAL_REQUEST: 80, + REQUEST_SUCCESS: 81, + REQUEST_FAILURE: 82, + CHANNEL_OPEN: 90, + CHANNEL_OPEN_CONFIRMATION: 91, + CHANNEL_OPEN_FAILURE: 92, + CHANNEL_WINDOW_ADJUST: 93, + CHANNEL_DATA: 94, + CHANNEL_CLOSE: 97, + PROTOCOLVERSION: 192, + KEEPALIVE_REQUEST: 208, + KEEPALIVE_REPLY: 209, + KEEPALIVE_OPTIONS_REQUEST: 210, + KEEPALIVE_OPTIONS_REPLY: 211, + JSON_CONTROL: 250 // This is a Mesh specific command that sends JSON to and from the MPS server. + } + + var APFDisconnectCode = { + HOST_NOT_ALLOWED_TO_CONNECT: 1, + PROTOCOL_ERROR: 2, + KEY_EXCHANGE_FAILED: 3, + RESERVED: 4, + MAC_ERROR: 5, + COMPRESSION_ERROR: 6, + SERVICE_NOT_AVAILABLE: 7, + PROTOCOL_VERSION_NOT_SUPPORTED: 8, + HOST_KEY_NOT_VERIFIABLE: 9, + CONNECTION_LOST: 10, + BY_APPLICATION: 11, + TOO_MANY_CONNECTIONS: 12, + AUTH_CANCELLED_BY_USER: 13, + NO_MORE_AUTH_METHODS_AVAILABLE: 14, + INVALID_CREDENTIALS: 15, + CONNECTION_TIMED_OUT: 16, + BY_POLICY: 17, + TEMPORARILY_UNAVAILABLE: 18 + } + + var APFChannelOpenFailCodes = { + ADMINISTRATIVELY_PROHIBITED: 1, + CONNECT_FAILED: 2, + UNKNOWN_CHANNEL_TYPE: 3, + RESOURCE_SHORTAGE: 4, + } + + var APFChannelOpenFailureReasonCode = { + AdministrativelyProhibited: 1, + ConnectFailed: 2, + UnknownChannelType: 3, + ResourceShortage: 4, + } + + obj.onSecureConnect = function onSecureConnect(resp, ws, head) { + Debug("APF Secure WebSocket connected."); + //console.log(JSON.stringify(resp)); + obj.forwardClient.tag = { accumulator: [] }; + obj.forwardClient.ws = ws; + obj.forwardClient.ws.on('end', function () { + Debug("APF: Connection is closing."); + if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; } + if (obj.onChannelClosed) { obj.onChannelClosed(obj); } + }); + + obj.forwardClient.ws.on('data', function (data) { + obj.forwardClient.tag.accumulator += hex2rstr(buf2hex(data)); + try { + var len = 0; + do { + len = ProcessData(obj.forwardClient); + if (len > 0) { obj.forwardClient.tag.accumulator = obj.forwardClient.tag.accumulator.slice(len); } + if (obj.cirastate == CIRASTATE.FAILED) { + Debug("APF: in a failed state, destroying socket."); + obj.forwardClient.ws.end(); + } + } while (len > 0); + } catch (ex) { Debug(ex); } + }); + + obj.forwardClient.ws.on('error', function (e) { + Debug("APF: Connection error, ending connecting."); + if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; } + }); + + obj.state = CIRASTATE.INITIAL; + if ((typeof obj.args.conntype == 'number') && (obj.args.conntype != 0)) { SendJsonControl(obj.forwardClient.ws, { action: 'connType', value: obj.args.conntype } ); } + SendProtocolVersion(obj.forwardClient.ws, obj.args.clientuuid); + SendServiceRequest(obj.forwardClient.ws, 'auth@amt.intel.com'); + } + + function SendJsonControl(socket, o) { + var data = JSON.stringify(o) + socket.write(String.fromCharCode(APFProtocol.JSON_CONTROL) + IntToStr(data.length) + data); + Debug("APF: Send JSON control: " + data); + } + + function SendProtocolVersion(socket, uuid) { + var data = String.fromCharCode(APFProtocol.PROTOCOLVERSION) + IntToStr(1) + IntToStr(0) + IntToStr(0) + hex2rstr(strToGuid(uuid)) + binzerostring(64); + socket.write(data); + Debug("APF: Send protocol version 1 0 " + uuid); + obj.cirastate = CIRASTATE.PROTOCOL_VERSION_SENT; + } + + function SendServiceRequest(socket, service) { + var data = String.fromCharCode(APFProtocol.SERVICE_REQUEST) + IntToStr(service.length) + service; + socket.write(data); + Debug("APF: Send service request " + service); + if (service == 'auth@amt.intel.com') { + obj.cirastate = CIRASTATE.AUTH_SERVICE_REQUEST_SENT; + } else if (service == 'pfwd@amt.intel.com') { + obj.cirastate = CIRASTATE.PFWD_SERVICE_REQUEST_SENT; + } + } + + function SendUserAuthRequest(socket, user, pass) { + var service = "pfwd@amt.intel.com"; + var data = String.fromCharCode(APFProtocol.USERAUTH_REQUEST) + IntToStr(user.length) + user + IntToStr(service.length) + service; + //password auth + data += IntToStr(8) + 'password'; + data += binzerostring(1) + IntToStr(pass.length) + pass; + socket.write(data); + Debug("APF: Send username password authentication to MPS"); + obj.cirastate = CIRASTATE.AUTH_REQUEST_SENT; + } + + function SendGlobalRequestPfwd(socket, amthostname, amtport) { + var tcpipfwd = 'tcpip-forward'; + var data = String.fromCharCode(APFProtocol.GLOBAL_REQUEST) + IntToStr(tcpipfwd.length) + tcpipfwd + binzerostring(1, 1); + data += IntToStr(amthostname.length) + amthostname + IntToStr(amtport); + socket.write(data); + Debug("APF: Send tcpip-forward " + amthostname + ":" + amtport); + obj.cirastate = CIRASTATE.GLOBAL_REQUEST_SENT; + } + + function SendKeepAliveRequest(socket) { + socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + IntToStr(255)); + Debug("APF: Send keepalive request"); + } + + function SendKeepAliveReply(socket, cookie) { + socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + IntToStr(cookie)); + Debug("APF: Send keepalive reply"); + } + + function ProcessData(socket) { + var cmd = socket.tag.accumulator.charCodeAt(0); + var len = socket.tag.accumulator.length; + var data = socket.tag.accumulator; + if (len == 0) { return 0; } + + // Respond to MPS according to obj.cirastate + switch (cmd) { + case APFProtocol.SERVICE_ACCEPT: { + var slen = ReadInt(data, 1), service = data.substring(5, 6 + slen); + Debug("APF: Service request to " + service + " accepted."); + if (service == 'auth@amt.intel.com') { + if (obj.cirastate >= CIRASTATE.AUTH_SERVICE_REQUEST_SENT) { + SendUserAuthRequest(socket.ws, obj.args.mpsuser, obj.args.mpspass); + } + } else if (service == 'pfwd@amt.intel.com') { + if (obj.cirastate >= CIRASTATE.PFWD_SERVICE_REQUEST_SENT) { + SendGlobalRequestPfwd(socket.ws, obj.args.clientname, pfwd_ports[obj.pfwd_idx++]); + } + } + return 5 + slen; + } + case APFProtocol.REQUEST_SUCCESS: { + if (len >= 5) { + var port = ReadInt(data, 1); + Debug("APF: Request to port forward " + port + " successful."); + // iterate to pending port forward request + if (obj.pfwd_idx < pfwd_ports.length) { + SendGlobalRequestPfwd(socket.ws, obj.args.clientname, pfwd_ports[obj.pfwd_idx++]); + } else { + // no more port forward, now setup timer to send keep alive + Debug("APF: Start keep alive for every " + obj.args.mpskeepalive + " ms."); + obj.timer = setInterval(function () { + SendKeepAliveRequest(obj.forwardClient.ws); + }, obj.args.mpskeepalive);// + } + return 5; + } + Debug("APF: Request successful."); + return 1; + } + case APFProtocol.USERAUTH_SUCCESS: { + Debug("APF: User Authentication successful"); + // Send Pfwd service request + SendServiceRequest(socket.ws, 'pfwd@amt.intel.com'); + return 1; + } + case APFProtocol.USERAUTH_FAILURE: { + Debug("APF: User Authentication failed"); + obj.cirastate = CIRASTATE.FAILED; + return 14; + } + case APFProtocol.KEEPALIVE_REQUEST: { + Debug("APF: Keep Alive Request with cookie: " + ReadInt(data, 1)); + SendKeepAliveReply(socket.ws, ReadInt(data, 1)); + return 5; + } + case APFProtocol.KEEPALIVE_REPLY: { + Debug("APF: Keep Alive Reply with cookie: " + ReadInt(data, 1)); + return 5; + } + // Channel management + case APFProtocol.CHANNEL_OPEN: { + // Parse CHANNEL OPEN request + var p_res = parseChannelOpen(data); + Debug("APF: CHANNEL_OPEN request: " + JSON.stringify(p_res)); + // Check if target port is in pfwd_ports + if (pfwd_ports.indexOf(p_res.target_port) >= 0) { + // Connect socket to that port + var chan = obj.net.createConnection({ host: obj.args.clientaddress, port: p_res.target_port }, function () { + //require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "CHANNEL_OPEN-open" }); + // obj.downlinks[p_res.sender_chan].setEncoding('binary');//assume everything is binary, not interpreting + SendChannelOpenConfirm(socket.ws, p_res); + }); + + // Setup flow control + chan.maxInWindow = p_res.window_size; // Oddly, we are using the same window size as the other side. + chan.curInWindow = 0; + + chan.on('data', function (ddata) { + // Relay data to fordwardclient + // TODO: Implement flow control + SendChannelData(socket.ws, p_res.sender_chan, ddata); + }); + + chan.on('error', function (e) { + Debug("Downlink connection error: " + e); + }); + + chan.on('end', function () { + var chan = obj.downlinks[p_res.sender_chan]; + if (chan != null) { + Debug("Socket ends."); + try { SendChannelClose(socket.ws, p_res.sender_chan); } catch (ex) { } + delete obj.downlinks[p_res.sender_chan]; + } + }); + + obj.downlinks[p_res.sender_chan] = chan; + } else { + // Not a supported port, fail the connection + SendChannelOpenFailure(socket.ws, p_res); + } + return p_res.len; + } + case APFProtocol.CHANNEL_OPEN_CONFIRMATION: { + Debug("APF: CHANNEL_OPEN_CONFIRMATION"); + return 17; + } + case APFProtocol.CHANNEL_CLOSE: { + var rcpt_chan = ReadInt(data, 1); + Debug("APF: CHANNEL_CLOSE: " + rcpt_chan); + try { obj.downlinks[rcpt_chan].end(); } catch (ex) { } + return 5; + } + case APFProtocol.CHANNEL_DATA: { + Debug("APF: CHANNEL_DATA: " + JSON.stringify(rstr2hex(data))); + var rcpt_chan = ReadInt(data, 1); + var chan_data_len = ReadInt(data, 5); + var chan_data = data.substring(9, 9 + chan_data_len); + var chan = obj.downlinks[rcpt_chan]; + if (chan != null) { + chan.curInWindow += chan_data_len; + try { + chan.write(Buffer.from(chan_data, 'binary'), function () { + Debug("Write completed."); + // If the incoming window is over half used, send an adjust. + if (this.curInWindow > (this.maxInWindow / 2)) { SendChannelWindowAdjust(socket.ws, rcpt_chan, this.curInWindow); this.curInWindow = 0; } + }); + } catch (ex) { Debug("Cannot forward data to downlink socket."); } + } + return 9 + chan_data_len; + } + case APFProtocol.CHANNEL_WINDOW_ADJUST: { + Debug("APF: CHANNEL_WINDOW_ADJUST"); + return 9; + } + case APFProtocol.JSON_CONTROL: { + Debug("APF: JSON_CONTROL"); + var len = ReadInt(data, 1); + if (obj.onJsonControl) { var o = null; try { o = JSON.parse(data.substring(5, 5 + len)); } catch (ex) { } if (o != null) { obj.onJsonControl(o); } } + return 5 + len; + } + default: { + Debug("CMD: " + cmd + " is not implemented."); + obj.cirastate = CIRASTATE.FAILED; + return 0; + } + } + } + + function parseChannelOpen(data) { + var result = { cmd: APFProtocol.CHANNEL_OPEN }; + var chan_type_slen = ReadInt(data, 1); + result.chan_type = data.substring(5, 5 + chan_type_slen); + result.sender_chan = ReadInt(data, 5 + chan_type_slen); + result.window_size = ReadInt(data, 9 + chan_type_slen); + var c_len = ReadInt(data, 17 + chan_type_slen); + result.target_address = data.substring(21 + chan_type_slen, 21 + chan_type_slen + c_len); + result.target_port = ReadInt(data, 21 + chan_type_slen + c_len); + var o_len = ReadInt(data, 25 + chan_type_slen + c_len); + result.origin_address = data.substring(29 + chan_type_slen + c_len, 29 + chan_type_slen + c_len + o_len); + result.origin_port = ReadInt(data, 29 + chan_type_slen + c_len + o_len); + result.len = 33 + chan_type_slen + c_len + o_len; + return result; + } + + function SendChannelOpenFailure(socket, chan_data) { + socket.write(String.fromCharCode(APFProtocol.CHANNEL_OPEN_FAILURE) + IntToStr(chan_data.sender_chan) + IntToStr(2) + IntToStr(0) + IntToStr(0)); + Debug("APF: Send ChannelOpenFailure"); + } + + function SendChannelOpenConfirm(socket, chan_data) { + socket.write(String.fromCharCode(APFProtocol.CHANNEL_OPEN_CONFIRMATION) + IntToStr(chan_data.sender_chan) + IntToStr(chan_data.sender_chan) + IntToStr(chan_data.window_size) + IntToStr(0xFFFFFFFF)); + Debug("APF: Send ChannelOpenConfirmation"); + } + + function SendChannelWindowAdjust(socket, chan, size) { + socket.write(String.fromCharCode(APFProtocol.CHANNEL_WINDOW_ADJUST) + IntToStr(chan) + IntToStr(size)); + Debug("APF: Send ChannelWindowAdjust, channel: " + chan + ", size: " + size); + } + + function SendChannelData(socket, chan, data) { + socket.write(Buffer.concat([Buffer.from(String.fromCharCode(APFProtocol.CHANNEL_DATA) + IntToStr(chan) + IntToStr(data.length), 'binary'), data])); + Debug("APF: Send ChannelData: " + data.toString('hex')); + } + + function SendChannelClose(socket, chan) { + socket.write(String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + IntToStr(chan)); + Debug("APF: Send ChannelClose "); + } + + obj.connect = function () { + if (obj.forwardClient != null) { + try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } + //obj.forwardClient = null; + } + obj.cirastate = CIRASTATE.INITIAL; + obj.pfwd_idx = 0; + + //obj.forwardClient = new obj.ws(obj.args.mpsurl, obj.tlsoptions); + //obj.forwardClient.on("open", obj.onSecureConnect); + + var wsoptions = obj.http.parseUri(obj.args.mpsurl); + wsoptions.rejectUnauthorized = 0; + obj.forwardClient = obj.http.request(wsoptions); + obj.forwardClient.upgrade = obj.onSecureConnect; + obj.forwardClient.end(); // end request, trigger completion of HTTP request + } + + obj.disconnect = function () { try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } } + + return obj; +} + +module.exports = CreateAPFClient; \ No newline at end of file diff --git a/agents/modules_meshcore/apfclient.js b/agents/modules_meshcore/apfclient.js index e72dac0d..393169f1 100644 --- a/agents/modules_meshcore/apfclient.js +++ b/agents/modules_meshcore/apfclient.js @@ -35,6 +35,9 @@ function CreateAPFClient(parent, args) { obj.pfwd_idx = 0; obj.timer = null; // Keep alive timer + // obj.onChannelClosed + // obj.onJsonControl + // Function copied from common.js function ReadInt(v, p) { return (v.charCodeAt(p) * 0x1000000) + (v.charCodeAt(p + 1) << 16) + (v.charCodeAt(p + 2) << 8) + v.charCodeAt(p + 3); }; // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32. function IntToStr(v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); }; @@ -139,16 +142,14 @@ function CreateAPFClient(parent, args) { } obj.onSecureConnect = function onSecureConnect(resp, ws, head) { - //Debug("APF Secure WebSocket connected."); + Debug("APF Secure WebSocket connected."); //console.log(JSON.stringify(resp)); obj.forwardClient.tag = { accumulator: [] }; obj.forwardClient.ws = ws; obj.forwardClient.ws.on('end', function () { - //Debug("APF: Connection is closing."); - if (obj.timer != null) { - clearInterval(obj.timer); - obj.timer = null; - } + Debug("APF: Connection is closing."); + if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; } + if (obj.onChannelClosed) { obj.onChannelClosed(obj); } }); obj.forwardClient.ws.on('data', function (data) { @@ -159,21 +160,16 @@ function CreateAPFClient(parent, args) { len = ProcessData(obj.forwardClient); if (len > 0) { obj.forwardClient.tag.accumulator = obj.forwardClient.tag.accumulator.slice(len); } if (obj.cirastate == CIRASTATE.FAILED) { - //Debug("APF: in a failed state, destroying socket."); + Debug("APF: in a failed state, destroying socket."); obj.forwardClient.ws.end(); } } while (len > 0); - } catch (e) { - Debug(e); - } + } catch (ex) { Debug(ex); } }); obj.forwardClient.ws.on('error', function (e) { - //Debug("APF: Connection error, ending connecting."); - if (obj.timer != null) { - clearInterval(obj.timer); - obj.timer = null; - } + Debug("APF: Connection error, ending connecting."); + if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; } }); obj.state = CIRASTATE.INITIAL; @@ -185,20 +181,20 @@ function CreateAPFClient(parent, args) { function SendJsonControl(socket, o) { var data = JSON.stringify(o) socket.write(String.fromCharCode(APFProtocol.JSON_CONTROL) + IntToStr(data.length) + data); - //Debug("APF: Send JSON control: " + data); + Debug("APF: Send JSON control: " + data); } function SendProtocolVersion(socket, uuid) { var data = String.fromCharCode(APFProtocol.PROTOCOLVERSION) + IntToStr(1) + IntToStr(0) + IntToStr(0) + hex2rstr(strToGuid(uuid)) + binzerostring(64); socket.write(data); - //Debug("APF: Send protocol version 1 0 " + uuid); + Debug("APF: Send protocol version 1 0 " + uuid); obj.cirastate = CIRASTATE.PROTOCOL_VERSION_SENT; } function SendServiceRequest(socket, service) { var data = String.fromCharCode(APFProtocol.SERVICE_REQUEST) + IntToStr(service.length) + service; socket.write(data); - //Debug("APF: Send service request " + service); + Debug("APF: Send service request " + service); if (service == 'auth@amt.intel.com') { obj.cirastate = CIRASTATE.AUTH_SERVICE_REQUEST_SENT; } else if (service == 'pfwd@amt.intel.com') { @@ -213,7 +209,7 @@ function CreateAPFClient(parent, args) { data += IntToStr(8) + 'password'; data += binzerostring(1) + IntToStr(pass.length) + pass; socket.write(data); - //Debug("APF: Send username password authentication to MPS"); + Debug("APF: Send username password authentication to MPS"); obj.cirastate = CIRASTATE.AUTH_REQUEST_SENT; } @@ -222,18 +218,18 @@ function CreateAPFClient(parent, args) { var data = String.fromCharCode(APFProtocol.GLOBAL_REQUEST) + IntToStr(tcpipfwd.length) + tcpipfwd + binzerostring(1, 1); data += IntToStr(amthostname.length) + amthostname + IntToStr(amtport); socket.write(data); - //Debug("APF: Send tcpip-forward " + amthostname + ":" + amtport); + Debug("APF: Send tcpip-forward " + amthostname + ":" + amtport); obj.cirastate = CIRASTATE.GLOBAL_REQUEST_SENT; } function SendKeepAliveRequest(socket) { socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + IntToStr(255)); - //Debug("APF: Send keepalive request"); + Debug("APF: Send keepalive request"); } function SendKeepAliveReply(socket, cookie) { socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + IntToStr(cookie)); - //Debug("APF: Send keepalive reply"); + Debug("APF: Send keepalive reply"); } function ProcessData(socket) { @@ -246,7 +242,7 @@ function CreateAPFClient(parent, args) { switch (cmd) { case APFProtocol.SERVICE_ACCEPT: { var slen = ReadInt(data, 1), service = data.substring(5, 6 + slen); - //Debug("APF: Service request to " + service + " accepted."); + Debug("APF: Service request to " + service + " accepted."); if (service == 'auth@amt.intel.com') { if (obj.cirastate >= CIRASTATE.AUTH_SERVICE_REQUEST_SENT) { SendUserAuthRequest(socket.ws, obj.args.mpsuser, obj.args.mpspass); @@ -261,47 +257,47 @@ function CreateAPFClient(parent, args) { case APFProtocol.REQUEST_SUCCESS: { if (len >= 5) { var port = ReadInt(data, 1); - //Debug("APF: Request to port forward " + port + " successful."); + Debug("APF: Request to port forward " + port + " successful."); // iterate to pending port forward request if (obj.pfwd_idx < pfwd_ports.length) { SendGlobalRequestPfwd(socket.ws, obj.args.clientname, pfwd_ports[obj.pfwd_idx++]); } else { // no more port forward, now setup timer to send keep alive - //Debug("APF: Start keep alive for every " + obj.args.mpskeepalive + " ms."); + Debug("APF: Start keep alive for every " + obj.args.mpskeepalive + " ms."); obj.timer = setInterval(function () { SendKeepAliveRequest(obj.forwardClient.ws); }, obj.args.mpskeepalive);// } return 5; } - //Debug("APF: Request successful."); + Debug("APF: Request successful."); return 1; } case APFProtocol.USERAUTH_SUCCESS: { - //Debug("APF: User Authentication successful"); + Debug("APF: User Authentication successful"); // Send Pfwd service request SendServiceRequest(socket.ws, 'pfwd@amt.intel.com'); return 1; } case APFProtocol.USERAUTH_FAILURE: { - //Debug("APF: User Authentication failed"); + Debug("APF: User Authentication failed"); obj.cirastate = CIRASTATE.FAILED; return 14; } case APFProtocol.KEEPALIVE_REQUEST: { - //Debug("APF: Keep Alive Request with cookie: " + ReadInt(data, 1)); + Debug("APF: Keep Alive Request with cookie: " + ReadInt(data, 1)); SendKeepAliveReply(socket.ws, ReadInt(data, 1)); return 5; } case APFProtocol.KEEPALIVE_REPLY: { - //Debug("APF: Keep Alive Reply with cookie: " + ReadInt(data, 1)); + Debug("APF: Keep Alive Reply with cookie: " + ReadInt(data, 1)); return 5; } // Channel management case APFProtocol.CHANNEL_OPEN: { // Parse CHANNEL OPEN request var p_res = parseChannelOpen(data); - //Debug("APF: CHANNEL_OPEN request: " + JSON.stringify(p_res)); + Debug("APF: CHANNEL_OPEN request: " + JSON.stringify(p_res)); // Check if target port is in pfwd_ports if (pfwd_ports.indexOf(p_res.target_port) >= 0) { // Connect socket to that port @@ -322,21 +318,15 @@ function CreateAPFClient(parent, args) { }); chan.on('error', function (e) { - //Debug("Downlink connection error: " + e); + Debug("Downlink connection error: " + e); }); chan.on('end', function () { var chan = obj.downlinks[p_res.sender_chan]; if (chan != null) { - try { - //Debug("Socket ends."); - SendChannelClose(socket.ws, p_res.sender_chan); - chan.xclosed = 1; - // Add some delay before removing... otherwise race condition - setTimeout(function () { delete obj.downlinks[p_res.sender_chan]; }, 100); - } catch (e) { - //Debug("Downlink connection exception: " + e); - } + Debug("Socket ends."); + try { SendChannelClose(socket.ws, p_res.sender_chan); } catch (ex) { } + delete obj.downlinks[p_res.sender_chan]; } }); @@ -348,22 +338,17 @@ function CreateAPFClient(parent, args) { return p_res.len; } case APFProtocol.CHANNEL_OPEN_CONFIRMATION: { - //Debug("APF: CHANNEL_OPEN_CONFIRMATION"); + Debug("APF: CHANNEL_OPEN_CONFIRMATION"); return 17; } case APFProtocol.CHANNEL_CLOSE: { var rcpt_chan = ReadInt(data, 1); - //Debug("APF: CHANNEL_CLOSE: " + rcpt_chan); - var chan = obj.downlinks[rcpt_chan]; - if ((chan != null) && (chan.xclosed !== 1)) { - SendChannelClose(socket.ws, rcpt_chan); - try { obj.downlinks[rcpt_chan].end(); } catch (e) { } - delete obj.downlinks[rcpt_chan]; - } + Debug("APF: CHANNEL_CLOSE: " + rcpt_chan); + try { obj.downlinks[rcpt_chan].end(); } catch (ex) { } return 5; } case APFProtocol.CHANNEL_DATA: { - //Debug("APF: CHANNEL_DATA: " + JSON.stringify(rstr2hex(data))); + Debug("APF: CHANNEL_DATA: " + JSON.stringify(rstr2hex(data))); var rcpt_chan = ReadInt(data, 1); var chan_data_len = ReadInt(data, 5); var chan_data = data.substring(9, 9 + chan_data_len); @@ -372,22 +357,26 @@ function CreateAPFClient(parent, args) { chan.curInWindow += chan_data_len; try { chan.write(Buffer.from(chan_data, 'binary'), function () { - //Debug("Write completed."); + Debug("Write completed."); // If the incoming window is over half used, send an adjust. if (this.curInWindow > (this.maxInWindow / 2)) { SendChannelWindowAdjust(socket.ws, rcpt_chan, this.curInWindow); this.curInWindow = 0; } }); - } catch (e) { - //Debug("Cannot forward data to downlink socket."); - } + } catch (ex) { Debug("Cannot forward data to downlink socket."); } } return 9 + chan_data_len; } case APFProtocol.CHANNEL_WINDOW_ADJUST: { - //Debug("APF: CHANNEL_WINDOW_ADJUST "); + Debug("APF: CHANNEL_WINDOW_ADJUST"); return 9; } + case APFProtocol.JSON_CONTROL: { + Debug("APF: JSON_CONTROL"); + var len = ReadInt(data, 1); + if (obj.onJsonControl) { var o = null; try { o = JSON.parse(data.substring(5, 5 + len)); } catch (ex) { } if (o != null) { obj.onJsonControl(o); } } + return 5 + len; + } default: { - //Debug("CMD: " + cmd + " is not implemented."); + Debug("CMD: " + cmd + " is not implemented."); obj.cirastate = CIRASTATE.FAILED; return 0; } @@ -412,36 +401,32 @@ function CreateAPFClient(parent, args) { function SendChannelOpenFailure(socket, chan_data) { socket.write(String.fromCharCode(APFProtocol.CHANNEL_OPEN_FAILURE) + IntToStr(chan_data.sender_chan) + IntToStr(2) + IntToStr(0) + IntToStr(0)); - //Debug("APF: Send ChannelOpenFailure"); + Debug("APF: Send ChannelOpenFailure"); } function SendChannelOpenConfirm(socket, chan_data) { socket.write(String.fromCharCode(APFProtocol.CHANNEL_OPEN_CONFIRMATION) + IntToStr(chan_data.sender_chan) + IntToStr(chan_data.sender_chan) + IntToStr(chan_data.window_size) + IntToStr(0xFFFFFFFF)); - //Debug("APF: Send ChannelOpenConfirmation"); + Debug("APF: Send ChannelOpenConfirmation"); } function SendChannelWindowAdjust(socket, chan, size) { socket.write(String.fromCharCode(APFProtocol.CHANNEL_WINDOW_ADJUST) + IntToStr(chan) + IntToStr(size)); - //Debug("APF: Send ChannelWindowAdjust: " + rstr2hex(data)); + Debug("APF: Send ChannelWindowAdjust, channel: " + chan + ", size: " + size); } function SendChannelData(socket, chan, data) { socket.write(Buffer.concat([Buffer.from(String.fromCharCode(APFProtocol.CHANNEL_DATA) + IntToStr(chan) + IntToStr(data.length), 'binary'), data])); - //Debug("APF: Send ChannelData: " + rstr2hex(buf)); + Debug("APF: Send ChannelData: " + data.toString('hex')); } function SendChannelClose(socket, chan) { socket.write(String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + IntToStr(chan)); - //Debug("APF: Send ChannelClose "); + Debug("APF: Send ChannelClose "); } obj.connect = function () { if (obj.forwardClient != null) { - try { - obj.forwardClient.ws.end(); - } catch (e) { - Debug(e); - } + try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } //obj.forwardClient = null; } obj.cirastate = CIRASTATE.INITIAL; @@ -457,7 +442,7 @@ function CreateAPFClient(parent, args) { obj.forwardClient.end(); // end request, trigger completion of HTTP request } - obj.disconnect = function () { try { obj.forwardClient.ws.end(); } catch (e) { Debug(e); } } + obj.disconnect = function () { try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } } return obj; } diff --git a/amt/amt-wsman-comm.js b/amt/amt-wsman-comm.js index a6c0fb14..79c540a2 100644 --- a/amt/amt-wsman-comm.js +++ b/amt/amt-wsman-comm.js @@ -240,19 +240,17 @@ var CreateWsmanComm = function (host, port, user, pass, tls, tlsoptions, mpsConn if (state == 2) { // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF var options = { socket: ser, ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false }; - if (obj.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; } + if (obj.xtlsMethod == 1) { options.secureProtocol = 'TLSv1_method'; } if (obj.xtlsoptions) { if (obj.xtlsoptions.ca) options.ca = obj.xtlsoptions.ca; if (obj.xtlsoptions.cert) options.cert = obj.xtlsoptions.cert; if (obj.xtlsoptions.key) options.key = obj.xtlsoptions.key; } - //obj.socket = new TLSSocket(ser, options); obj.socket = obj.tls.connect(obj.port, obj.host, options, obj.xxOnSocketConnected); obj.socket.setEncoding('binary'); obj.socket.setTimeout(6000); // Set socket idle timeout - obj.socket.on('error', function (err) { console.log("CIRA TLS Connection Error ", err); obj.xxOnSocketClosed(); }); - //obj.socket.on('error', function (e) { if (e.message && e.message.indexOf('sslv3 alert bad record mac') >= 0) { obj.xtlsMethod = 1 - obj.xtlsMethod; } }); + obj.socket.on('error', function (ex) { obj.xtlsMethod = 1 - obj.xtlsMethod; }); obj.socket.on('close', obj.xxOnSocketClosed); obj.socket.on('timeout', obj.xxOnSocketTimeout); diff --git a/amtmanager.js b/amtmanager.js index 67398abf..bb3669ae 100644 --- a/amtmanager.js +++ b/amtmanager.js @@ -76,6 +76,9 @@ module.exports.CreateAmtManager = function(parent) { // Remove the device from the list devices.splice(i, 1); if (devices.length == 0) { delete obj.amtDevices[dev.nodeid]; } else { obj.amtDevices[dev.nodeid] = devices; } + + // Notify connection closure if this is a LMS connection + if (dev.connType == 2) { dev.controlMsg({ action: "close" }); } return true; } @@ -103,9 +106,13 @@ module.exports.CreateAmtManager = function(parent) { var devices = obj.amtDevices[nodeid], dev = null; if (devices != null) { for (var i in devices) { if ((devices[i].mpsConnection == connection) || (devices[i].host == connection)) { dev = devices[i]; } } } if (dev != null) return false; // We are already managing this device on this connection - dev = { nodeid: nodeid, connType: connType }; + dev = { nodeid: nodeid, connType: connType, domainid: nodeid.split('/')[1] }; if (typeof connection == 'string') { dev.host = connection; } if (typeof connection == 'object') { dev.mpsConnection = connection; } + dev.consoleMsg = function deviceConsoleMsg(msg) { if (typeof deviceConsoleMsg.conn == 'object') { deviceConsoleMsg.conn.ControlMsg({ action: 'console', msg: msg }); } } + dev.consoleMsg.conn = connection; + dev.controlMsg = function deviceControlMsg(msg) { if (typeof deviceControlMsg.conn == 'object') { deviceControlMsg.conn.ControlMsg(msg); } } + dev.controlMsg.conn = connection; parent.debug('amt', "Start Management", nodeid, connType); addAmtDevice(dev); fetchIntelAmtInformation(dev); @@ -192,6 +199,7 @@ module.exports.CreateAmtManager = function(parent) { //if (node.host) { dev.host = node.host.toLowerCase(); } dev.meshid = node.meshid; dev.intelamt = node.intelamt; + dev.consoleMsg("Attempting Intel AMT connection..."); attemptInitialContact(dev); }); } @@ -201,7 +209,7 @@ module.exports.CreateAmtManager = function(parent) { parent.debug('amt', "Attempt Initial Contact", dev.name, dev.connType); if ((dev.acctry == null) && ((typeof dev.intelamt.user != 'string') || (typeof dev.intelamt.pass != 'string'))) { - if ((obj.amtAdminAccounts[dev.domainid] != null) && (obj.amtAdminAccounts[dev.domainid].length > 0)) { dev.acctry = 0; } else { return; } + if ((obj.amtAdminAccounts[dev.domainid] != null) && (obj.amtAdminAccounts[dev.domainid].length > 0)) { dev.acctry = 0; } else { removeAmtDevice(dev); return; } } switch (dev.connType) { @@ -315,6 +323,7 @@ module.exports.CreateAmtManager = function(parent) { // Check the response if ((status == 200) && (responses['AMT_GeneralSettings'] != null) && (responses['IPS_HostBasedSetupService'] != null) && (responses['IPS_HostBasedSetupService'].response != null) && (responses['IPS_HostBasedSetupService'].response != null) && (stack.wsman.comm.digestRealm == responses['AMT_GeneralSettings'].response.DigestRealm)) { // Everything looks good + dev.consoleMsg(stack.wsman.comm.xtls ? "Intel AMT connected with TLS." : "Intel AMT connected."); dev.state = 1; if (dev.aquired == null) { dev.aquired = {}; } dev.aquired.controlMode = responses['IPS_HostBasedSetupService'].response.CurrentControlMode; // 1 = CCM, 2 = ACM @@ -330,15 +339,20 @@ module.exports.CreateAmtManager = function(parent) { // Perform Intel AMT clock sync attemptSyncClock(dev, function () { - attemptFetchHardwareInventory(dev); // See if we need to get hardware inventory - - if (dev.connType != 2) { - // Start power polling if not connected to LMS - var ppfunc = function powerPoleFunction() { fetchPowerState(powerPoleFunction.dev); } - ppfunc.dev = dev; - dev.polltimer = new setTimeout(ppfunc, 290000); // Poll for power state every 4 minutes 50 seconds. - fetchPowerState(dev); - } + // See if we need to get hardware inventory + attemptFetchHardwareInventory(dev, function () { + dev.consoleMsg('Done.'); + if (dev.connType != 2) { + // Start power polling if not connected to LMS + var ppfunc = function powerPoleFunction() { fetchPowerState(powerPoleFunction.dev); } + ppfunc.dev = dev; + dev.polltimer = new setTimeout(ppfunc, 290000); // Poll for power state every 4 minutes 50 seconds. + fetchPowerState(dev); + } else { + // For LMS connections, close now. + dev.controlMsg({ action: "close" }); + } + }); }); } else { // We got a bad response @@ -509,7 +523,8 @@ module.exports.CreateAmtManager = function(parent) { // Care should be take not to have many pending WSMAN called when performing clock sync. function attemptSyncClock(dev, func) { if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. - dev.clockSyncCompleted = func; + dev.taskCount = 1; + dev.taskCompleted = func; dev.amtstack.AMT_TimeSynchronizationService_GetLowAccuracyTimeSynch(attemptSyncClockEx); } @@ -517,17 +532,19 @@ module.exports.CreateAmtManager = function(parent) { function attemptSyncClockEx(stack, name, response, status) { const dev = stack.dev; if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. - if (status != 200) { removeDevice(dev.nodeid); } + if (status != 200) { removeDevice(dev.nodeid); return; } // Compute how much drift between Intel AMT and our clock. var t = new Date(), now = new Date(); t.setTime(response.Body['Ta0'] * 1000); if (Math.abs(t - now) > 10000) { // If the Intel AMT clock is more than 10 seconds off, set it. + dev.consoleMsg("Performing clock sync."); var Tm1 = Math.round(now.getTime() / 1000); dev.amtstack.AMT_TimeSynchronizationService_SetHighAccuracyTimeSynch(response.Body['Ta0'], Tm1, Tm1, attemptSyncClockSet); } else { // Clock is fine, we are done. - if (dev.clockSyncCompleted != null) { var f = dev.clockSyncCompleted; delete dev.clockSyncCompleted; f(); } + dev.consoleMsg("Clock ok."); + devTaskCompleted(dev) } } @@ -536,28 +553,37 @@ module.exports.CreateAmtManager = function(parent) { const dev = stack.dev; if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. if (status != 200) { removeDevice(dev.nodeid); } - if (dev.clockSyncCompleted != null) { var f = dev.clockSyncCompleted; delete dev.clockSyncCompleted; f(); } + devTaskCompleted(dev) } - function attemptFetchHardwareInventory(dev) { - if (obj.amtDevices[dev.nodeid] == null) return false; // Device no longer exists, ignore this request. + function attemptFetchHardwareInventory(dev, func) { + if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. const mesh = parent.webserver.meshes[dev.meshid]; - if (mesh == null) { removeDevice(dev.nodeid); return false; } + if (mesh == null) { removeDevice(dev.nodeid); return; } if (mesh.mtype == 1) { // If this is a Intel AMT only device group, pull the hardware inventory and network information for this device + dev.consoleMsg("Fetching hardware inventory."); + dev.taskCount = 2; + dev.taskCompleted = func; dev.amtstack.BatchEnum('', ['*CIM_ComputerSystemPackage', 'CIM_SystemPackaging', '*CIM_Chassis', 'CIM_Chip', '*CIM_Card', '*CIM_BIOSElement', 'CIM_Processor', 'CIM_PhysicalMemory', 'CIM_MediaAccessDevice', 'CIM_PhysicalPackage'], attemptFetchHardwareInventoryResponse); dev.amtstack.BatchEnum('', ['AMT_EthernetPortSettings'], attemptFetchNetworkResponse); - return true; + } else { + if (func) { func(); } } - return false; + } + + // + function devTaskCompleted(dev) { + dev.taskCount--; + if (dev.taskCount == 0) { var f = dev.taskCompleted; delete dev.taskCount; delete dev.taskCompleted; if (f != null) { f(); } } } function attemptFetchNetworkResponse(stack, name, responses, status) { const dev = stack.dev; if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. - if (status != 200) return; + if (status != 200) { devTaskCompleted(dev); return; } //console.log(JSON.stringify(responses, null, 2)); - if ((responses['AMT_EthernetPortSettings'] == null) || (responses['AMT_EthernetPortSettings'].responses == null)) return; + if ((responses['AMT_EthernetPortSettings'] == null) || (responses['AMT_EthernetPortSettings'].responses == null)) { devTaskCompleted(dev); return; } // Find the wired and wireless interfaces var wired = null, wireless = null; @@ -567,7 +593,7 @@ module.exports.CreateAmtManager = function(parent) { if (netif.WLANLinkProtectionLevel != null) { wireless = netif; } else { wired = netif; } } } - if ((wired == null) && (wireless == null)) return; + if ((wired == null) && (wireless == null)) { devTaskCompleted(dev); return; } // Sent by the agent to update agent network interface information var net = { netif2: {} }; @@ -601,6 +627,8 @@ module.exports.CreateAmtManager = function(parent) { // Event the node interface information change parent.DispatchEvent(parent.webserver.CreateMeshDispatchTargets(dev.meshid, [dev.nodeid]), obj, { action: 'ifchange', nodeid: dev.nodeid, domain: dev.nodeid.split('/')[1], nolog: 1 }); + + devTaskCompleted(dev); } @@ -625,7 +653,7 @@ module.exports.CreateAmtManager = function(parent) { function attemptFetchHardwareInventoryResponse(stack, name, responses, status) { const dev = stack.dev; if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. - if (status != 200) return; + if (status != 200) { devTaskCompleted(dev); return; } // Extract basic data var hw = {} @@ -648,9 +676,11 @@ module.exports.CreateAmtManager = function(parent) { var m2 = {}, m = hw.PhysicalMemory[i]; m2.BankLabel = m.BankLabel; m2.Capacity = m.Capacity; - m2.PartNumber = m.PartNumber.trim(); - m2.SerialNumber = m.SerialNumber.trim(); - m2.Manufacturer = m.Manufacturer.trim(); + if (m.PartNumber) { m2.PartNumber = m.PartNumber.trim(); } + if (typeof m.SerialNumber == 'string') { m2.SerialNumber = m.SerialNumber.trim(); } + if (typeof m.SerialNumber == 'number') { m2.SerialNumber = m.SerialNumber; } + if (typeof m.SerialNumber == 'string') { m2.Manufacturer = m.Manufacturer.trim(); } + if (typeof m.Manufacturer == 'number') { m2.Manufacturer = m.Manufacturer; } memory.push(m2); } hw2.hardware.windows.memory = memory; @@ -660,21 +690,21 @@ module.exports.CreateAmtManager = function(parent) { for (var i in hw.MediaAccessDevice) { var m2 = {}, m = hw.MediaAccessDevice[i]; m2.Caption = m.DeviceID; - m2.Size = (m.MaxMediaSize * 1000); + if (m.MaxMediaSize) { m2.Size = (m.MaxMediaSize * 1000); } drives.push(m2); } hw2.hardware.identifiers.storage_devices = drives; } if (hw.Bios != null) { - hw2.hardware.identifiers.bios_vendor = hw.Bios.Manufacturer.trim(); + if (hw.Bios.Manufacturer) { hw2.hardware.identifiers.bios_vendor = hw.Bios.Manufacturer.trim(); } hw2.hardware.identifiers.bios_version = hw.Bios.Version; if (hw.Bios.ReleaseDate && hw.Bios.ReleaseDate.Datetime) { hw2.hardware.identifiers.bios_date = hw.Bios.ReleaseDate.Datetime; } } if (hw.PhysicalPackage != null) { - hw2.hardware.identifiers.board_name = hw.Card.Model.trim(); - hw2.hardware.identifiers.board_vendor = hw.Card.Manufacturer.trim(); - hw2.hardware.identifiers.board_version = hw.Card.Version.trim(); - hw2.hardware.identifiers.board_serial = hw.Card.SerialNumber.trim(); + if (hw.Card.Model) { hw2.hardware.identifiers.board_name = hw.Card.Model.trim(); } + if (hw.Card.Manufacturer) { hw2.hardware.identifiers.board_vendor = hw.Card.Manufacturer.trim(); } + if (hw.Card.Version) { hw2.hardware.identifiers.board_version = hw.Card.Version.trim(); } + if (hw.Card.SerialNumber) { hw2.hardware.identifiers.board_serial = hw.Card.SerialNumber.trim(); } } if ((hw.Chips != null) && (hw.Chips.length > 0)) { for (var i in hw.Chips) { @@ -704,6 +734,8 @@ module.exports.CreateAmtManager = function(parent) { parent.DispatchEvent(parent.webserver.CreateMeshDispatchTargets(dev.meshid, [dev.nodeid]), obj, event); } }); + + devTaskCompleted(dev); } function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + '-' + g.substring(10, 12) + g.substring(8, 10) + '-' + g.substring(14, 16) + g.substring(12, 14) + '-' + g.substring(16, 20) + '-' + g.substring(20); } diff --git a/mpsserver.js b/mpsserver.js index daaaaa5e..aa1a0f9f 100644 --- a/mpsserver.js +++ b/mpsserver.js @@ -285,6 +285,9 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { socket.SetupChannel.parent = obj; socket.SetupChannel.conn = socket; socket.websocket = 1; + socket.ControlMsg = function ControlMsg(message) { return ControlMsg.parent.SendJsonControl(ControlMsg.conn, message); } + socket.ControlMsg.parent = obj; + socket.ControlMsg.conn = socket; parent.debug('mps', "New CIRA websocket connection"); socket.on('message', function (data) { @@ -913,6 +916,13 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { for (var i in connections) { obj.close(connections[i]); } }; + obj.SendJsonControl = function(socket, data) { + if (socket.tag.connType == 0) return; // This command is valid only for connections that are not really CIRA. + parent.debug('mpscmd', '<-- JSON_CONTROL'); + if (typeof data == 'object') { data = JSON.stringify(data); } + Write(socket, String.fromCharCode(APFProtocol.JSON_CONTROL) + common.IntToStr(data.length) + data); + } + function SendServiceAccept(socket, service) { parent.debug('mpscmd', '<-- SERVICE_ACCEPT', service); Write(socket, String.fromCharCode(APFProtocol.SERVICE_ACCEPT) + common.IntToStr(service.length) + service); diff --git a/webserver.js b/webserver.js index 034738ac..7d5eaa48 100644 --- a/webserver.js +++ b/webserver.js @@ -3520,6 +3520,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); } }); + // Note that here, req.query.p: 1 = WSMAN with server auth, 2 = REDIR with server auth, 3 = WSMAN without server auth, 4 = REDIR with server auth + // Fetch Intel AMT credentials & Setup interceptor if (req.query.p == 1) { parent.debug('webrelaydata', 'INTERCEPTOR1', { host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); @@ -3587,7 +3589,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Compute target port var port = 16992; if (node.intelamt.tls > 0) port = 16993; // This is a direct connection, use TLS when possible - if (req.query.p == 2) port += 2; + if ((req.query.p == 2) || (req.query.p == 4)) port += 2; if (node.intelamt.tls == 0) { // If this is TCP (without TLS) set a normal TCP socket