amt-mei.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /*
  2. Copyright 2018-2021 Intel Corporation
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. var Q = require('queue');
  14. var g_internal = null;
  15. function retry_pthi_later()
  16. {
  17. if (++g_internal.errorCount < 20)
  18. {
  19. g_internal.timeout = setTimeout(function (p)
  20. {
  21. p.connect(require('heci').GUIDS.AMT, { noPipeline: 1 });
  22. }, 250, this);
  23. }
  24. else
  25. {
  26. this.Parent.emit('error', 'PTHI Connection could not be established');
  27. }
  28. }
  29. function amt_heci()
  30. {
  31. var emitterUtils = require('events').inherits(this);
  32. emitterUtils.createEvent('error');
  33. var heci = require('heci');
  34. var sendConsole = function (msg) { try { require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": msg }); } catch (ex) { } }
  35. this._ObjectID = "pthi";
  36. var that = this;
  37. if (g_internal == null)
  38. {
  39. g_internal = { _rq: new Q(), _amt: null, errorCount: 0 };
  40. g_internal._setupPTHI = function _g_setupPTHI()
  41. {
  42. console.info1('setupPTHI()');
  43. this._amt = heci.create();
  44. this._amt.descriptorMetadata = "amt-pthi";
  45. this._amt.BiosVersionLen = 65;
  46. this._amt.UnicodeStringLen = 20;
  47. this._amt.Parent = that;
  48. this._amt.on('error', function _amtOnError(e)
  49. {
  50. console.info1('PTHIError: ' + e);
  51. if (g_internal._rq.isEmpty())
  52. {
  53. console.info1(' Queue is empty');
  54. this.Parent.emit('error', e); // No pending requests, so propagate the error up
  55. }
  56. else
  57. {
  58. console.info1(' Queue is NOT empty');
  59. // Try again
  60. retry_pthi_later.call(this);
  61. }
  62. });
  63. this._amt.on('connect', function _amtOnConnect()
  64. {
  65. g_internal.errorCount = 0;
  66. this.on('data', function _amtOnData(chunk)
  67. {
  68. //console.log("Received: " + chunk.length + " bytes");
  69. var header = this.Parent.getCommand(chunk);
  70. console.info1("CMD = " + header.Command + " (Status: " + header.Status + ") Response = " + header.IsResponse);
  71. var user = g_internal._rq.deQueue();
  72. var params = user.optional;
  73. var callback = user.func;
  74. params.unshift(header);
  75. callback.apply(this.Parent, params);
  76. if (g_internal._rq.isEmpty())
  77. {
  78. console.info1('No more requests, disconnecting');
  79. // No More Requests, we can close PTHI
  80. g_internal._amt.disconnect();
  81. g_internal._amt = null;
  82. }
  83. else
  84. {
  85. // Send the next request
  86. console.info1('Sending Next Request');
  87. this.write(g_internal._rq.peekQueue().send);
  88. }
  89. });
  90. // Start sending requests
  91. this.write(g_internal._rq.peekQueue().send);
  92. });
  93. };
  94. }
  95. function trim(x) { var y = x.indexOf('\0'); if (y >= 0) { return x.substring(0, y); } else { return x; } }
  96. this.getCommand = function getCommand(chunk) {
  97. var command = chunk.length == 0 ? (g_internal._rq.peekQueue().cmd | 0x800000) : chunk.readUInt32LE(4);
  98. var ret = { IsResponse: (command & 0x800000) == 0x800000 ? true : false, Command: (command & 0x7FFFFF), Status: chunk.length != 0 ? chunk.readUInt32LE(12) : -1, Data: chunk.length != 0 ? chunk.slice(16) : null };
  99. return (ret);
  100. };
  101. this.sendCommand = function sendCommand()
  102. {
  103. if (arguments.length < 3 || typeof (arguments[0]) != 'number' || typeof (arguments[1]) != 'object' || typeof (arguments[2]) != 'function') { throw ('invalid parameters'); }
  104. var args = [];
  105. for (var i = 3; i < arguments.length; ++i) { args.push(arguments[i]); }
  106. console.info1('sendCommand(' + arguments[0] + ')', this._hashCode());
  107. var header = Buffer.from('010100000000000000000000', 'hex');
  108. header.writeUInt32LE(arguments[0] | 0x04000000, 4);
  109. header.writeUInt32LE(arguments[1] == null ? 0 : arguments[1].length, 8);
  110. g_internal._rq.enQueue({ cmd: arguments[0], func: arguments[2], optional: args, send: (arguments[1] == null ? header : Buffer.concat([header, arguments[1]])) });
  111. if (!g_internal._amt)
  112. {
  113. g_internal._setupPTHI();
  114. g_internal._amt.connect(heci.GUIDS.AMT, { noPipeline: 1 });
  115. }
  116. }
  117. this.getVersion = function getVersion(callback) {
  118. var optional = [];
  119. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  120. this.sendCommand(26, null, function (header, fn, opt) {
  121. if (header.Status == 0) {
  122. var i, CodeVersion = header.Data, val = { BiosVersion: CodeVersion.slice(0, g_internal._amt.BiosVersionLen).toString(), Versions: [] }, v = CodeVersion.slice(g_internal._amt.BiosVersionLen + 4);
  123. for (i = 0; i < CodeVersion.readUInt32LE(g_internal._amt.BiosVersionLen) ; ++i)
  124. {
  125. val.Versions[i] = { Description: v.slice(2, v.readUInt16LE(0) + 2).toString(), Version: v.slice(4 + g_internal._amt.UnicodeStringLen, 4 + g_internal._amt.UnicodeStringLen + v.readUInt16LE(2 + g_internal._amt.UnicodeStringLen)).toString() };
  126. v = v.slice(4 + (2 * g_internal._amt.UnicodeStringLen));
  127. }
  128. if (val.BiosVersion.indexOf('\0') > 0) { val.BiosVersion = val.BiosVersion.substring(0, val.BiosVersion.indexOf('\0')); }
  129. opt.unshift(val);
  130. } else {
  131. opt.unshift(null);
  132. }
  133. fn.apply(this, opt);
  134. }, callback, optional);
  135. };
  136. // Fill the left with zeros until the string is of a given length
  137. function zeroLeftPad(str, len) {
  138. if ((len == null) && (typeof (len) != 'number')) { return null; }
  139. if (str == null) str = ''; // If null, this is to generate zero leftpad string
  140. var zlp = '';
  141. for (var i = 0; i < len - str.length; i++) { zlp += '0'; }
  142. return zlp + str;
  143. }
  144. this.getUuid = function getUuid(callback) {
  145. var optional = [];
  146. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  147. this.sendCommand(0x5c, null, function (header, fn, opt) {
  148. if (header.Status == 0) {
  149. var result = {};
  150. result.uuid = [zeroLeftPad(header.Data.readUInt32LE(0).toString(16), 8),
  151. zeroLeftPad(header.Data.readUInt16LE(4).toString(16), 4),
  152. zeroLeftPad(header.Data.readUInt16LE(6).toString(16), 4),
  153. zeroLeftPad(header.Data.readUInt16BE(8).toString(16), 4),
  154. zeroLeftPad(header.Data.slice(10).toString('hex').toLowerCase(), 12)].join('-');
  155. opt.unshift(result);
  156. } else {
  157. opt.unshift(null);
  158. }
  159. fn.apply(this, opt);
  160. }, callback, optional);
  161. };
  162. this.getProvisioningState = function getProvisioningState(callback) {
  163. var optional = [];
  164. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  165. this.sendCommand(17, null, function (header, fn, opt) {
  166. if (header.Status == 0) {
  167. var result = {};
  168. result.state = header.Data.readUInt32LE(0);
  169. if (result.state < 3) { result.stateStr = ["PRE", "IN", "POST"][result.state]; }
  170. opt.unshift(result);
  171. } else {
  172. opt.unshift(null);
  173. }
  174. fn.apply(this, opt);
  175. }, callback, optional);
  176. };
  177. this.getProvisioningMode = function getProvisioningMode(callback) {
  178. var optional = [];
  179. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  180. this.sendCommand(8, null, function (header, fn, opt) {
  181. if (header.Status == 0) {
  182. var result = {};
  183. result.mode = header.Data.readUInt32LE(0);
  184. if (result.mode < 4) { result.modeStr = ["NONE", "ENTERPRISE", "SMALL_BUSINESS", "REMOTE_ASSISTANCE"][result.mode]; }
  185. result.legacy = header.Data.readUInt32LE(4) == 0 ? false : true;
  186. opt.unshift(result);
  187. } else {
  188. opt.unshift(null);
  189. }
  190. fn.apply(this, opt);
  191. }, callback, optional);
  192. };
  193. this.getEHBCState = function getEHBCState(callback) {
  194. var optional = [];
  195. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  196. this.sendCommand(132, null, function (header, fn, opt) {
  197. if (header.Status == 0) {
  198. opt.unshift({ EHBC: header.Data.readUInt32LE(0) != 0 });
  199. } else {
  200. opt.unshift(null);
  201. }
  202. fn.apply(this, opt);
  203. }, callback, optional);
  204. };
  205. this.getControlMode = function getControlMode(callback) {
  206. var optional = [];
  207. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  208. this.sendCommand(107, null, function (header, fn, opt) {
  209. if (header.Status == 0) {
  210. var result = {};
  211. result.controlMode = header.Data.readUInt32LE(0);
  212. if (result.controlMode < 3) { result.controlModeStr = ["NONE_RPAT", "CLIENT", "ADMIN", "REMOTE_ASSISTANCE"][result.controlMode]; }
  213. opt.unshift(result);
  214. } else {
  215. opt.unshift(null);
  216. }
  217. fn.apply(this, opt);
  218. }, callback, optional);
  219. };
  220. this.getMACAddresses = function getMACAddresses(callback) {
  221. var optional = [];
  222. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  223. this.sendCommand(37, null, function (header, fn, opt) {
  224. if (header.Status == 0) {
  225. opt.unshift({ DedicatedMAC: header.Data.slice(0, 6).toString('hex:'), HostMAC: header.Data.slice(6, 12).toString('hex:') });
  226. } else { opt.unshift({ DedicatedMAC: null, HostMAC: null }); }
  227. fn.apply(this, opt);
  228. }, callback, optional);
  229. };
  230. this.getDnsSuffix = function getDnsSuffix(callback) {
  231. var optional = [];
  232. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  233. this.sendCommand(54, null, function (header, fn, opt) {
  234. if (header.Status == 0) {
  235. var resultLen = header.Data.readUInt16LE(0);
  236. if (resultLen > 0) { opt.unshift(header.Data.slice(2, 2 + resultLen).toString()); } else { opt.unshift(null); }
  237. } else {
  238. opt.unshift(null);
  239. }
  240. fn.apply(this, opt);
  241. }, callback, optional);
  242. };
  243. this.getHashHandles = function getHashHandles(callback) {
  244. var optional = [];
  245. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  246. this.sendCommand(0x2C, null, function (header, fn, opt) {
  247. var result = [];
  248. if (header.Status == 0) {
  249. var resultLen = header.Data.readUInt32LE(0);
  250. for (var i = 0; i < resultLen; ++i) {
  251. result.push(header.Data.readUInt32LE(4 + (4 * i)));
  252. }
  253. }
  254. opt.unshift(result);
  255. fn.apply(this, opt);
  256. }, callback, optional);
  257. };
  258. this.getCertHashEntry = function getCertHashEntry(handle, callback) {
  259. var optional = [];
  260. for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
  261. var data = Buffer.alloc(4);
  262. data.writeUInt32LE(handle, 0);
  263. this.sendCommand(0x2D, data, function (header, fn, opt) {
  264. if (header.Status == 0) {
  265. var result = {};
  266. result.isDefault = header.Data.readUInt32LE(0);
  267. result.isActive = header.Data.readUInt32LE(4);
  268. result.hashAlgorithm = header.Data.readUInt8(72);
  269. if (result.hashAlgorithm < 4) {
  270. result.hashAlgorithmStr = ["MD5", "SHA1", "SHA256", "SHA512"][result.hashAlgorithm];
  271. result.hashAlgorithmSize = [16, 20, 32, 64][result.hashAlgorithm];
  272. result.certificateHash = header.Data.slice(8, 8 + result.hashAlgorithmSize).toString('hex');
  273. }
  274. result.name = header.Data.slice(73 + 2, 73 + 2 + header.Data.readUInt16LE(73)).toString();
  275. opt.unshift(result);
  276. } else {
  277. opt.unshift(null);
  278. }
  279. fn.apply(this, opt);
  280. }, callback, optional);
  281. };
  282. this.getCertHashEntries = function getCertHashEntries(callback) {
  283. var optional = [];
  284. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  285. this.getHashHandles(function (handles, fn, opt) {
  286. var entries = [];
  287. this.getCertHashEntry(handles.shift(), this._getHashEntrySink, fn, opt, entries, handles);
  288. }, callback, optional);
  289. };
  290. this._getHashEntrySink = function _getHashEntrySink(result, fn, opt, entries, handles) {
  291. entries.push(result);
  292. if (handles.length > 0) {
  293. this.getCertHashEntry(handles.shift(), this._getHashEntrySink, fn, opt, entries, handles);
  294. } else {
  295. opt.unshift(entries);
  296. fn.apply(this, opt);
  297. }
  298. }
  299. this.getLocalSystemAccount = function getLocalSystemAccount(callback) {
  300. var optional = [];
  301. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  302. this.sendCommand(103, Buffer.alloc(40), function (header, fn, opt) {
  303. if (header.Status == 0 && header.Data.length == 68) {
  304. opt.unshift({ user: trim(header.Data.slice(0, 33).toString()), pass: trim(header.Data.slice(33, 67).toString()), raw: header.Data });
  305. }
  306. else {
  307. opt.unshift(null);
  308. }
  309. fn.apply(this, opt);
  310. }, callback, optional);
  311. }
  312. this.getLanInterfaceSettings = function getLanInterfaceSettings(index, callback) {
  313. var optional = [];
  314. for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
  315. var ifx = Buffer.alloc(4);
  316. ifx.writeUInt32LE(index);
  317. this.sendCommand(0x48, ifx, function onGetLanInterfaceSettings(header, fn, opt) {
  318. if (header.Status == 0) {
  319. var info = {};
  320. info.enabled = header.Data.readUInt32LE(0);
  321. info.dhcpEnabled = header.Data.readUInt32LE(8);
  322. switch (header.Data[12]) {
  323. case 1:
  324. info.dhcpMode = 'ACTIVE'
  325. break;
  326. case 2:
  327. info.dhcpMode = 'PASSIVE'
  328. break;
  329. default:
  330. info.dhcpMode = 'UNKNOWN';
  331. break;
  332. }
  333. info.mac = header.Data.slice(14).toString('hex:');
  334. var addr = header.Data.readUInt32LE(4);
  335. info.address = ((addr >> 24) & 255) + '.' + ((addr >> 16) & 255) + '.' + ((addr >> 8) & 255) + '.' + (addr & 255);
  336. opt.unshift(info);
  337. fn.apply(this, opt);
  338. }
  339. else {
  340. opt.unshift(null);
  341. fn.apply(this, opt);
  342. }
  343. }, callback, optional);
  344. };
  345. this.unprovision = function unprovision(mode, callback) {
  346. var optional = [];
  347. for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
  348. var data = Buffer.alloc(4);
  349. data.writeUInt32LE(mode, 0);
  350. this.sendCommand(16, data, function (header, fn, opt) {
  351. opt.unshift(header.Status);
  352. fn.apply(this, opt);
  353. }, callback, optional);
  354. }
  355. this.startConfiguration = function startConfiguration(callback) {
  356. var optional = [];
  357. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  358. this.sendCommand(0x29, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
  359. }
  360. this.stopConfiguration = function stopConfiguration(callback) {
  361. var optional = [];
  362. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  363. this.sendCommand(0x5E, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
  364. }
  365. this.openUserInitiatedConnection = function openUserInitiatedConnection(callback) {
  366. var optional = [];
  367. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  368. this.sendCommand(0x44, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
  369. }
  370. this.closeUserInitiatedConnection = function closeUnserInitiatedConnected(callback) {
  371. var optional = [];
  372. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  373. this.sendCommand(0x45, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
  374. }
  375. this.getRemoteAccessConnectionStatus = function getRemoteAccessConnectionStatus(callback) {
  376. var optional = [];
  377. for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
  378. this.sendCommand(0x46, null, function (header, fn, opt) {
  379. if (header.Status == 0) {
  380. var hostname = header.Data.slice(14, header.Data.readUInt16LE(12) + 14).toString()
  381. opt.unshift({ status: header.Status, networkStatus: header.Data.readUInt32LE(0), remoteAccessStatus: header.Data.readUInt32LE(4), remoteAccessTrigger: header.Data.readUInt32LE(8), mpsHostname: hostname, raw: header.Data });
  382. } else {
  383. opt.unshift({ status: header.Status });
  384. }
  385. fn.apply(this, opt);
  386. }, callback, optional);
  387. }
  388. this.getProtocolVersion = function getProtocolVersion(callback) {
  389. var optional = [];
  390. for (var i = 1; i < arguments.length; ++i) { opt.push(arguments[i]); }
  391. if (!this._tmpSession) { this._tmpSession = heci.create(); this._tmpSession.parent = this; }
  392. this._tmpSession.doIoctl(heci.IOCTL.HECI_VERSION, Buffer.alloc(5), Buffer.alloc(5), function (status, buffer, self, fn, opt) {
  393. if (status == 0) {
  394. var result = buffer.readUInt8(0).toString() + '.' + buffer.readUInt8(1).toString() + '.' + buffer.readUInt8(2).toString() + '.' + buffer.readUInt16BE(3).toString();
  395. opt.unshift(result);
  396. fn.apply(self, opt);
  397. }
  398. else {
  399. opt.unshift(null);
  400. fn.apply(self, opt);
  401. }
  402. }, this, callback, optional);
  403. }
  404. this.startConfigurationHBased = function startConfigurationHBased(certHash, hostVpn, dnsSuffixList, func) {
  405. if ((certHash == null) || ((certHash.length != 32) && (certHash.length != 48))) { func({ status: -101 }); }
  406. this.stopConfiguration(function (status) {
  407. if (status == 0) {
  408. // We stopped the configuration, wait 20 seconds before starting up again.
  409. var f = function tf() { delete tf.parent.xtimeout; tf.parent.startConfigurationHBasedEx(certHash, hostVpn, dnsSuffixList, func); }
  410. f.parent = this;
  411. this.xtimeout = setTimeout(f, 20000);
  412. } else {
  413. // We are not in the connect mode, this is good, start configuration right away.
  414. this.startConfigurationHBasedEx(certHash, hostVpn, dnsSuffixList, func);
  415. }
  416. })
  417. }
  418. this.startConfigurationHBasedEx = function startConfigurationHBased(certHash, hostVpn, dnsSuffixList, func) {
  419. var optional = [];
  420. for (var i = 4; i < arguments.length; ++i) { optional.push(arguments[i]); }
  421. // Format the command
  422. var data = Buffer.alloc(1 + 64 + 4 + 4 + ((dnsSuffixList != null) ? 320 : 0));
  423. data[0] = (certHash.length == 48) ? 3 : 2 // Write certificate hash type: SHA256 = 2, SHA384 = 3
  424. certHash.copy(data, 1); // Write the hash
  425. data.writeUInt32LE(hostVpn ? 1 : 0, 65); // Write is HostVPN is enabled
  426. if (dnsSuffixList != null) {
  427. data.writeUInt32LE(dnsSuffixList.length, 69); // Write the number of DNS Suffix, from 0 to 4
  428. var ptr = 73;
  429. for (var i = 0; i < dnsSuffixList.length; i++) { ptr += data.write(dnsSuffixList[i], ptr) + 1; } // Write up to 4 DNS Suffix with null seperation.
  430. }
  431. // Send the command
  432. this.sendCommand(139, data, function (header, fn, opt) {
  433. if (header.Status == 0) {
  434. var amtHash = null;
  435. if (header.Data[0] == 2) { amtHash = header.Data.slice(1, 33); } // SHA256
  436. if (header.Data[0] == 3) { amtHash = header.Data.slice(1, 49); } // SHA384
  437. opt.unshift({ status: header.Status, hash: amtHash.toString('hex') });
  438. } else {
  439. opt.unshift({ status: header.Status });
  440. }
  441. fn.apply(this, opt);
  442. }, func, optional);
  443. }
  444. }
  445. module.exports = amt_heci;
  446. /*
  447. AMT_STATUS_SUCCESS = 0,
  448. AMT_STATUS_INTERNAL_ERROR = 1,
  449. AMT_STATUS_INVALID_AMT_MODE = 3,
  450. AMT_STATUS_INVALID_MESSAGE_LENGTH = 4,
  451. AMT_STATUS_MAX_LIMIT_REACHED = 23,
  452. AMT_STATUS_INVALID_PARAMETER = 36,
  453. AMT_STATUS_RNG_GENERATION_IN_PROGRESS = 47,
  454. AMT_STATUS_RNG_NOT_READY = 48,
  455. AMT_STATUS_CERTIFICATE_NOT_READY = 49,
  456. AMT_STATUS_INVALID_HANDLE = 2053
  457. AMT_STATUS_NOT_FOUND = 2068,
  458. */