| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- /*
- Copyright 2018-2021 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-2021
- * @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 });
- if (obj.args.meiState != null) { SendJsonControl(obj.forwardClient.ws, { action: 'meiState', value: obj.args.meiState }); }
- }
- SendProtocolVersion(obj.forwardClient.ws, obj.args.clientuuid);
- SendServiceRequest(obj.forwardClient.ws, '[email protected]');
- }
- obj.updateMeiState = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'meiState', value: state }); }
- obj.sendMeiDeactivationState = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'deactivate', value: state }); }
- obj.sendStartTlsHostConfigResponse = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'startTlsHostConfig', value: state }); }
- obj.sendStopConfigurationResponse = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'stopConfiguration', value: state }); }
- 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 == '[email protected]') {
- obj.cirastate = CIRASTATE.AUTH_SERVICE_REQUEST_SENT;
- } else if (service == '[email protected]') {
- obj.cirastate = CIRASTATE.PFWD_SERVICE_REQUEST_SENT;
- }
- }
- function SendUserAuthRequest(socket, user, pass) {
- var service = "[email protected]";
- 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 == '[email protected]') {
- if (obj.cirastate >= CIRASTATE.AUTH_SERVICE_REQUEST_SENT) {
- SendUserAuthRequest(socket.ws, obj.args.mpsuser, obj.args.mpspass);
- }
- } else if (service == '[email protected]') {
- 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, '[email protected]');
- 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);
- SendChannelOpenFailure(socket.ws, p_res);
- });
- 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;
|