amt-lme.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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 MemoryStream = require('MemoryStream');
  14. var lme_id = 0; // Our next channel identifier
  15. var lme_port_offset = 0; // Debug: Set this to "-100" to bind to 16892 & 16893 and IN_ADDRANY. This is for LMS debugging.
  16. var lme_bindany = false; // If true, bind to all network interfaces, not just loopback.
  17. var xmlParser = null;
  18. try { xmlParser = require('amt-xml'); } catch (ex) { }
  19. // Documented in: https://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide/HTMLDocuments/MPSDocuments/Intel%20AMT%20Port%20Forwarding%20Protocol%20Reference%20Manual.pdf
  20. var APF_DISCONNECT = 1;
  21. var APF_SERVICE_REQUEST = 5;
  22. var APF_SERVICE_ACCEPT = 6;
  23. var APF_USERAUTH_REQUEST = 50;
  24. var APF_USERAUTH_FAILURE = 51;
  25. var APF_USERAUTH_SUCCESS = 52;
  26. var APF_GLOBAL_REQUEST = 80;
  27. var APF_REQUEST_SUCCESS = 81;
  28. var APF_REQUEST_FAILURE = 82;
  29. var APF_CHANNEL_OPEN = 90;
  30. var APF_CHANNEL_OPEN_CONFIRMATION = 91;
  31. var APF_CHANNEL_OPEN_FAILURE = 92;
  32. var APF_CHANNEL_WINDOW_ADJUST = 93;
  33. var APF_CHANNEL_DATA = 94;
  34. var APF_CHANNEL_CLOSE = 97;
  35. var APF_PROTOCOLVERSION = 192;
  36. function lme_object() {
  37. this.ourId = ++lme_id;
  38. this.amtId = -1;
  39. this.LME_CHANNEL_STATUS = 'LME_CS_FREE';
  40. this.txWindow = 0;
  41. this.rxWindow = 0;
  42. this.localPort = 0;
  43. this.errorCount = 0;
  44. }
  45. function stream_bufferedWrite() {
  46. var emitterUtils = require('events').inherits(this);
  47. this.buffer = [];
  48. this._readCheckImmediate = undefined;
  49. this._ObjectID = "bufferedWriteStream";
  50. // Writable Events
  51. emitterUtils.createEvent('close');
  52. emitterUtils.createEvent('drain');
  53. emitterUtils.createEvent('error');
  54. emitterUtils.createEvent('finish');
  55. emitterUtils.createEvent('pipe');
  56. emitterUtils.createEvent('unpipe');
  57. // Readable Events
  58. emitterUtils.createEvent('readable');
  59. this.isEmpty = function () {
  60. return (this.buffer.length == 0);
  61. };
  62. this.isWaiting = function () {
  63. return (this._readCheckImmediate == undefined);
  64. };
  65. this.write = function (chunk) {
  66. for (var args in arguments) { if (typeof (arguments[args]) == 'function') { this.once('drain', arguments[args]); break; } }
  67. var tmp = Buffer.alloc(chunk.length);
  68. chunk.copy(tmp);
  69. this.buffer.push({ offset: 0, data: tmp });
  70. this.emit('readable');
  71. return (this.buffer.length == 0 ? true : false);
  72. };
  73. this.read = function () {
  74. var size = arguments.length == 0 ? undefined : arguments[0];
  75. var bytesRead = 0;
  76. var list = [];
  77. while ((size == undefined || bytesRead < size) && this.buffer.length > 0) {
  78. var len = this.buffer[0].data.length - this.buffer[0].offset;
  79. var offset = this.buffer[0].offset;
  80. if (len > (size - bytesRead)) {
  81. // Only reading a subset
  82. list.push(this.buffer[0].data.slice(offset, offset + size - bytesRead));
  83. this.buffer[0].offset += (size - bytesRead);
  84. bytesRead += (size - bytesRead);
  85. } else {
  86. // Reading the entire thing
  87. list.push(this.buffer[0].data.slice(offset));
  88. bytesRead += len;
  89. this.buffer.shift();
  90. }
  91. }
  92. this._readCheckImmediate = setImmediate(function (buffered) {
  93. buffered._readCheckImmediate = undefined;
  94. if (buffered.buffer.length == 0) {
  95. buffered.emit('drain'); // Drained
  96. } else {
  97. buffered.emit('readable'); // Not drained
  98. }
  99. }, this);
  100. return (Buffer.concat(list));
  101. };
  102. }
  103. function lme_heci(options) {
  104. var emitterUtils = require('events').inherits(this);
  105. emitterUtils.createEvent('error');
  106. emitterUtils.createEvent('connect');
  107. emitterUtils.createEvent('notify');
  108. emitterUtils.createEvent('bind');
  109. this.on('newListener', function (name, func)
  110. {
  111. if (name == 'connect' && this._LME._connected == true) { func.call(this); }
  112. if (name == 'error' && this._LME._error !=null) { func.call(this, this._LME._error); }
  113. });
  114. if (options != null) {
  115. if (options.debug == true) { lme_port_offset = -100; } // LMS debug mode
  116. if (options.bindany == true) { lme_bindany = true; } // Bind to all ports
  117. }
  118. var heci = require('heci');
  119. this.INITIAL_RXWINDOW_SIZE = 4096;
  120. this._ObjectID = "lme";
  121. this._LME = heci.create();
  122. this._LME._connected = false;
  123. this._LME._error = null;
  124. this._LME.descriptorMetadata = "amt-lme";
  125. this._LME._binded = {};
  126. this._LME.LMS = this;
  127. this._LME.on('error', function (e) { this._error = e; this.LMS.emit('error', e); });
  128. this._LME.on('connect', function ()
  129. {
  130. this._connected = true;
  131. this._emitConnected = false;
  132. this.on('data', function (chunk) {
  133. // this = HECI
  134. var cmd = chunk.readUInt8(0);
  135. //console.log('LME Command ' + cmd + ', ' + chunk.length + ' byte(s).');
  136. switch (cmd) {
  137. default:
  138. console.log('Unhandled LME Command ' + cmd + ', ' + chunk.length + ' byte(s).');
  139. break;
  140. case APF_SERVICE_REQUEST:
  141. var nameLen = chunk.readUInt32BE(1);
  142. var name = chunk.slice(5, nameLen + 5);
  143. //console.log("Service Request for: " + name);
  144. if (name == '[email protected]' || name == '[email protected]') {
  145. var outBuffer = Buffer.alloc(5 + nameLen);
  146. outBuffer.writeUInt8(6, 0);
  147. outBuffer.writeUInt32BE(nameLen, 1);
  148. outBuffer.write(name.toString(), 5);
  149. this.write(outBuffer);
  150. //console.log('Answering APF_SERVICE_REQUEST');
  151. } else {
  152. //console.log('UNKNOWN APF_SERVICE_REQUEST');
  153. }
  154. break;
  155. case APF_GLOBAL_REQUEST:
  156. var nameLen = chunk.readUInt32BE(1);
  157. var name = chunk.slice(5, nameLen + 5).toString();
  158. switch (name) {
  159. case 'tcpip-forward':
  160. var len = chunk.readUInt32BE(nameLen + 6);
  161. var port = chunk.readUInt32BE(nameLen + 10 + len);
  162. //console.log("[" + chunk.length + "/" + len + "] APF_GLOBAL_REQUEST for: " + name + " on port " + port);
  163. if (this[name] == undefined) { this[name] = {}; }
  164. if (this[name][port] != null) { // Close the existing binding
  165. for (var i in this.sockets) {
  166. var channel = this.sockets[i];
  167. if (channel.localPort == port) { this.sockets[i].end(); delete this.sockets[i]; } // Close this socket
  168. }
  169. }
  170. if (this[name][port] == null)
  171. {
  172. try
  173. {
  174. // Bind a new server socket if not already present
  175. this[name][port] = require('net').createServer();
  176. this[name][port].descriptorMetadata = 'amt-lme (port: ' + port + ')';
  177. this[name][port].HECI = this;
  178. if (lme_port_offset == 0) {
  179. if (lme_bindany) {
  180. this[name][port].listen({ port: port }); // Bind all mode
  181. } else {
  182. this[name][port].listen({ port: port, host: '127.0.0.1' }); // Normal mode
  183. }
  184. } else {
  185. this[name][port].listen({ port: (port + lme_port_offset) }); // Debug mode
  186. }
  187. this[name][port].on('connection', function (socket) {
  188. //console.log('New [' + socket.remoteFamily + '] TCP Connection on: ' + socket.remoteAddress + ' :' + socket.localPort);
  189. this.HECI.LMS.bindDuplexStream(socket, socket.remoteFamily, socket.localPort - lme_port_offset);
  190. });
  191. this._binded[port] = true;
  192. if (!this._emitConnected)
  193. {
  194. this._emitConnected = true;
  195. this.LMS.emit('error', 'APF/BIND error');
  196. }
  197. this.LMS.emit('bind', this._binded);
  198. } catch (ex)
  199. {
  200. console.info1(ex, 'Port ' + port);
  201. if(!this._emitConnected)
  202. {
  203. this._emitConnected = true;
  204. this.LMS.emit('error', 'APF/BIND error');
  205. }
  206. }
  207. }
  208. var outBuffer = Buffer.alloc(5);
  209. outBuffer.writeUInt8(81, 0);
  210. outBuffer.writeUInt32BE(port, 1);
  211. this.write(outBuffer);
  212. break;
  213. case 'cancel-tcpip-forward':
  214. var outBuffer = Buffer.alloc(1);
  215. outBuffer.writeUInt8(APF_REQUEST_SUCCESS, 0);
  216. this.write(outBuffer);
  217. break;
  218. case '[email protected]':
  219. var outBuffer = Buffer.alloc(1);
  220. outBuffer.writeUInt8(APF_REQUEST_FAILURE, 0);
  221. this.write(outBuffer);
  222. break;
  223. default:
  224. //console.log("Unknown APF_GLOBAL_REQUEST for: " + name);
  225. break;
  226. }
  227. break;
  228. case APF_CHANNEL_OPEN_CONFIRMATION:
  229. var rChannel = chunk.readUInt32BE(1);
  230. var sChannel = chunk.readUInt32BE(5);
  231. var wSize = chunk.readUInt32BE(9);
  232. //console.log('rChannel/' + rChannel + ', sChannel/' + sChannel + ', wSize/' + wSize);
  233. if (this.sockets[rChannel] != undefined) {
  234. this.sockets[rChannel].lme.amtId = sChannel;
  235. this.sockets[rChannel].lme.rxWindow = wSize;
  236. this.sockets[rChannel].lme.txWindow = wSize;
  237. this.sockets[rChannel].lme.LME_CHANNEL_STATUS = 'LME_CS_CONNECTED';
  238. //console.log('LME_CS_CONNECTED');
  239. this.sockets[rChannel].bufferedStream = new stream_bufferedWrite();
  240. this.sockets[rChannel].bufferedStream.socket = this.sockets[rChannel];
  241. this.sockets[rChannel].bufferedStream.on('readable', function () {
  242. if (this.socket.lme.txWindow > 0) {
  243. var buffer = this.read(this.socket.lme.txWindow);
  244. var packet = Buffer.alloc(9 + buffer.length);
  245. packet.writeUInt8(APF_CHANNEL_DATA, 0);
  246. packet.writeUInt32BE(this.socket.lme.amtId, 1);
  247. packet.writeUInt32BE(buffer.length, 5);
  248. buffer.copy(packet, 9);
  249. this.socket.lme.txWindow -= buffer.length;
  250. this.socket.HECI.write(packet);
  251. }
  252. });
  253. this.sockets[rChannel].bufferedStream.on('drain', function () {
  254. this.socket.resume();
  255. });
  256. this.sockets[rChannel].on('data', function (chunk) {
  257. if (!this.bufferedStream.write(chunk)) { this.pause(); }
  258. });
  259. this.sockets[rChannel].on('end', function () {
  260. var outBuffer = Buffer.alloc(5);
  261. outBuffer.writeUInt8(APF_CHANNEL_CLOSE, 0);
  262. outBuffer.writeUInt32BE(this.lme.amtId, 1);
  263. this.HECI.write(outBuffer);
  264. });
  265. this.sockets[rChannel].resume();
  266. }
  267. break;
  268. case APF_PROTOCOLVERSION:
  269. var major = chunk.readUInt32BE(1);
  270. var minor = chunk.readUInt32BE(5);
  271. var reason = chunk.readUInt32BE(9);
  272. var outBuffer = Buffer.alloc(93);
  273. outBuffer.writeUInt8(192, 0);
  274. outBuffer.writeUInt32BE(1, 1);
  275. outBuffer.writeUInt32BE(0, 5);
  276. outBuffer.writeUInt32BE(reason, 9);
  277. //console.log('Answering PROTOCOL_VERSION');
  278. this.write(outBuffer);
  279. break;
  280. case APF_CHANNEL_WINDOW_ADJUST:
  281. var rChannelId = chunk.readUInt32BE(1);
  282. var bytesToAdd = chunk.readUInt32BE(5);
  283. if (this.sockets[rChannelId] != undefined) {
  284. this.sockets[rChannelId].lme.txWindow += bytesToAdd;
  285. if (!this.sockets[rChannelId].bufferedStream.isEmpty() && this.sockets[rChannelId].bufferedStream.isWaiting()) {
  286. this.sockets[rChannelId].bufferedStream.emit('readable');
  287. }
  288. } else {
  289. console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_WINDOW_ADJUST');
  290. }
  291. break;
  292. case APF_CHANNEL_DATA:
  293. var rChannelId = chunk.readUInt32BE(1);
  294. var dataLen = chunk.readUInt32BE(5);
  295. var data = chunk.slice(9, 9 + dataLen);
  296. if ((this.sockets != null) && (this.sockets[rChannelId] != undefined)) {
  297. this.sockets[rChannelId].pendingBytes.push(data.length);
  298. this.sockets[rChannelId].write(data, function () {
  299. var written = this.pendingBytes.shift();
  300. //console.log('adjust', this.lme.amtId, written);
  301. var outBuffer = Buffer.alloc(9);
  302. outBuffer.writeUInt8(APF_CHANNEL_WINDOW_ADJUST, 0);
  303. outBuffer.writeUInt32BE(this.lme.amtId, 1);
  304. outBuffer.writeUInt32BE(written, 5);
  305. this.HECI.write(outBuffer);
  306. });
  307. } else if ((this.insockets != null) && (this.insockets[rChannelId] != undefined)) {
  308. var channel = this.insockets[rChannelId];
  309. if (channel.data == null) { channel.data = data.toString(); } else { channel.data += data.toString(); }
  310. channel.rxWindow += dataLen;
  311. //console.log('IN DATA', channel.rxWindow, channel.data.length, dataLen, channel.amtId, data.toString());
  312. var httpData = parseHttp(channel.data);
  313. if ((httpData != null) || (channel.data.length >= 8000)) {
  314. // Parse the WSMAN
  315. var notify = null;
  316. if (xmlParser != null) { try { notify = xmlParser.ParseWsman(httpData); } catch (e) { } }
  317. // Event the http data
  318. if (notify != null) { this.LMS.emit('notify', notify, channel.options, _lmsNotifyToCode(notify)); }
  319. // Send channel close
  320. var buffer = Buffer.alloc(5);
  321. buffer.writeUInt8(APF_CHANNEL_CLOSE, 0);
  322. buffer.writeUInt32BE(amtId, 1);
  323. this.write(buffer);
  324. } else {
  325. if (channel.rxWindow > 6000) {
  326. // Send window adjust
  327. var buffer = Buffer.alloc(9);
  328. buffer.writeUInt8(APF_CHANNEL_WINDOW_ADJUST, 0);
  329. buffer.writeUInt32BE(channel.amtId, 1);
  330. buffer.writeUInt32BE(channel.rxWindow, 5);
  331. this.write(buffer);
  332. channel.rxWindow = 0;
  333. }
  334. }
  335. } else {
  336. console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_DATA');
  337. }
  338. break;
  339. case APF_CHANNEL_OPEN_FAILURE:
  340. var rChannelId = chunk.readUInt32BE(1);
  341. var reasonCode = chunk.readUInt32BE(5);
  342. if ((this.sockets != null) && (this.sockets[rChannelId] != undefined)) {
  343. this.sockets[rChannelId].end();
  344. delete this.sockets[rChannelId];
  345. } else if ((this.insockets != null) && (this.insockets[rChannelId] != undefined)) {
  346. delete this.insockets[rChannelId];
  347. } else {
  348. console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_OPEN_FAILURE');
  349. }
  350. break;
  351. case APF_CHANNEL_CLOSE:
  352. var rChannelId = chunk.readUInt32BE(1);
  353. if ((this.sockets != null) && (this.sockets[rChannelId] != undefined)) {
  354. this.sockets[rChannelId].end();
  355. var amtId = this.sockets[rChannelId].lme.amtId;
  356. var buffer = Buffer.alloc(5);
  357. delete this.sockets[rChannelId];
  358. buffer.writeUInt8(APF_CHANNEL_CLOSE, 0); // ????????????????????????????
  359. buffer.writeUInt32BE(amtId, 1);
  360. this.write(buffer);
  361. } else if ((this.insockets != null) && (this.insockets[rChannelId] != undefined)) {
  362. delete this.insockets[rChannelId];
  363. // Should I send a close back????
  364. } else {
  365. console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_CLOSE');
  366. }
  367. break;
  368. case APF_CHANNEL_OPEN:
  369. var nameLen = chunk.readUInt32BE(1);
  370. var name = chunk.slice(5, nameLen + 5).toString();
  371. var channelSender = chunk.readUInt32BE(nameLen + 5);
  372. var initialWindowSize = chunk.readUInt32BE(nameLen + 9);
  373. var hostToConnectLen = chunk.readUInt32BE(nameLen + 17);
  374. var hostToConnect = chunk.slice(nameLen + 21, nameLen + 21 + hostToConnectLen).toString();
  375. var portToConnect = chunk.readUInt32BE(nameLen + 21 + hostToConnectLen);
  376. var originatorIpLen = chunk.readUInt32BE(nameLen + 25 + hostToConnectLen);
  377. var originatorIp = chunk.slice(nameLen + 29 + hostToConnectLen, nameLen + 29 + hostToConnectLen + originatorIpLen).toString();
  378. var originatorPort = chunk.readUInt32BE(nameLen + 29 + hostToConnectLen + originatorIpLen);
  379. //console.log('APF_CHANNEL_OPEN', name, channelSender, initialWindowSize, 'From: ' + originatorIp + ':' + originatorPort, 'To: ' + hostToConnect + ':' + portToConnect);
  380. if (this.insockets == null) { this.insockets = {}; }
  381. var ourId = ++lme_id;
  382. var insocket = new lme_object();
  383. insocket.ourId = ourId;
  384. insocket.amtId = channelSender;
  385. insocket.txWindow = initialWindowSize;
  386. insocket.rxWindow = 0;
  387. insocket.options = { target: hostToConnect, targetPort: portToConnect, source: originatorIp, sourcePort: originatorPort };
  388. this.insockets[ourId] = insocket;
  389. var buffer = Buffer.alloc(17);
  390. buffer.writeUInt8(APF_CHANNEL_OPEN_CONFIRMATION, 0);
  391. buffer.writeUInt32BE(channelSender, 1); // Intel AMT sender channel
  392. buffer.writeUInt32BE(ourId, 5); // Our receiver channel id
  393. buffer.writeUInt32BE(4000, 9); // Initial Window Size
  394. buffer.writeUInt32BE(0xFFFFFFFF, 13); // Reserved
  395. this.write(buffer);
  396. /*
  397. var buffer = Buffer.alloc(17);
  398. buffer.writeUInt8(APF_CHANNEL_OPEN_FAILURE, 0);
  399. buffer.writeUInt32BE(channelSender, 1); // Intel AMT sender channel
  400. buffer.writeUInt32BE(2, 5); // Reason code
  401. buffer.writeUInt32BE(0, 9); // Reserved
  402. buffer.writeUInt32BE(0, 13); // Reserved
  403. this.write(buffer);
  404. console.log('Sent APF_CHANNEL_OPEN_FAILURE', channelSender);
  405. */
  406. break;
  407. }
  408. });
  409. //
  410. // Due to a change in behavior with AMT/11 (and possibly earlier), we are not going to emit 'connect' here, until
  411. // we can verify that the first APF/Channel can be bound. Older AMT, like AMT/7 only allowed a single LME connection, so we
  412. // used to emit connect here. However, newer AMT's will allow more than 1 LME connection, which will result in APF/Bind failure
  413. //
  414. //this.LMS.emit('connect');
  415. this.resume();
  416. });
  417. this.bindDuplexStream = function (duplexStream, remoteFamily, localPort) {
  418. var socket = duplexStream;
  419. //console.log('New [' + remoteFamily + '] Virtual Connection/' + socket.localPort);
  420. socket.pendingBytes = [];
  421. socket.HECI = this._LME;
  422. socket.LMS = this;
  423. socket.lme = new lme_object();
  424. socket.lme.Socket = socket;
  425. socket.localPort = localPort;
  426. var buffer = new MemoryStream();
  427. buffer.writeUInt8(0x5A);
  428. buffer.writeUInt32BE(15);
  429. buffer.write('forwarded-tcpip');
  430. buffer.writeUInt32BE(socket.lme.ourId);
  431. buffer.writeUInt32BE(this.INITIAL_RXWINDOW_SIZE);
  432. buffer.writeUInt32BE(0xFFFFFFFF);
  433. for (var i = 0; i < 2; ++i) {
  434. if (remoteFamily == 'IPv6') {
  435. buffer.writeUInt32BE(3);
  436. buffer.write('::1');
  437. } else {
  438. buffer.writeUInt32BE(9);
  439. buffer.write('127.0.0.1');
  440. }
  441. buffer.writeUInt32BE(localPort);
  442. }
  443. this._LME.write(buffer.buffer);
  444. if (this._LME.sockets == undefined) { this._LME.sockets = {}; }
  445. this._LME.sockets[socket.lme.ourId] = socket;
  446. socket.pause();
  447. };
  448. this._LME.connect(heci.GUIDS.LME, { noPipeline: 0 });
  449. }
  450. function parseHttp(httpData) {
  451. var i = httpData.indexOf('\r\n\r\n');
  452. if ((i == -1) || (httpData.length < (i + 2))) { return null; }
  453. var headers = require('http-headers')(httpData.substring(0, i), true);
  454. var contentLength = parseInt(headers['content-length']);
  455. if (httpData.length >= contentLength + i + 4) { return httpData.substring(i + 4, i + 4 + contentLength); }
  456. return null;
  457. }
  458. function _lmsNotifyToCode(notify) {
  459. if ((notify == null) || (notify.Body == null) || (notify.Body.MessageID == null)) return null;
  460. var msgid = notify.Body.MessageID;
  461. try { msgid += '-' + notify.Body.MessageArguments[0]; } catch (e) { }
  462. return msgid;
  463. }
  464. module.exports = lme_heci;