mpsserver.js 93 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  1. /**
  2. * @description MeshCentral Intel(R) AMT MPS server
  3. * @author Ylian Saint-Hilaire
  4. * @copyright Intel Corporation 2018-2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*jslint node: true */
  9. /*jshint node: true */
  10. /*jshint strict:false */
  11. /*jshint -W097 */
  12. /*jshint esversion: 6 */
  13. 'use strict';
  14. // Construct a Intel AMT MPS server object
  15. module.exports.CreateMpsServer = function (parent, db, args, certificates) {
  16. var obj = {};
  17. obj.fs = require('fs');
  18. obj.path = require('path');
  19. obj.parent = parent;
  20. obj.db = db;
  21. obj.args = args;
  22. obj.certificates = certificates;
  23. obj.ciraConnections = {}; // NodeID --> [ Socket ]
  24. var tlsSessionStore = {}; // Store TLS session information for quick resume.
  25. var tlsSessionStoreCount = 0; // Number of cached TLS session information in store.
  26. const constants = (require('crypto').constants ? require('crypto').constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
  27. const common = require('./common.js');
  28. const net = require('net');
  29. const tls = require('tls');
  30. const MAX_IDLE = 90000; // 90 seconds max idle time, higher than the typical KEEP-ALIVE periode of 60 seconds
  31. const KEEPALIVE_INTERVAL = 30; // 30 seconds is typical keepalive interval for AMT CIRA connection
  32. // This MPS server is also a tiny HTTPS server. HTTP responses are here.
  33. obj.httpResponses = {
  34. '/': '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"></head><body>MeshCentral MPS server.<br />Intel&reg; AMT computers should connect here.</body></html>'
  35. //'/text.ico': { file: 'c:\\temp\\test.iso', maxserve: 3, maxtime: Date.now() + 15000 }
  36. };
  37. // Set the MPS external port only if it's not set to zero and we are not in LAN mode.
  38. if ((args.lanonly != true) && (args.mpsport !== 0)) {
  39. if (obj.args.mpstlsoffload) {
  40. obj.server = net.createServer(onConnection);
  41. } else {
  42. if (obj.args.mpshighsecurity) {
  43. // Higher security TLS 1.2 and 1.3 only, some older Intel AMT CIRA connections will fail.
  44. obj.server = tls.createServer({ key: certificates.mps.key, cert: certificates.mps.cert, requestCert: true, rejectUnauthorized: false, ciphers: "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 }, onConnection)
  45. } else {
  46. // Lower security MPS in order to support older Intel AMT CIRA connections, we have to turn on TLSv1.
  47. obj.server = tls.createServer({ key: certificates.mps.key, cert: certificates.mps.cert, minVersion: 'TLSv1', requestCert: true, rejectUnauthorized: false, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA:@SECLEVEL=0", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION }, onConnection)
  48. }
  49. //obj.server.on('error', function () { console.log('MPS tls server error'); });
  50. obj.server.on('newSession', function (id, data, cb) { if (tlsSessionStoreCount > 1000) { tlsSessionStoreCount = 0; tlsSessionStore = {}; } tlsSessionStore[id.toString('hex')] = data; tlsSessionStoreCount++; cb(); });
  51. obj.server.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); });
  52. }
  53. obj.server.listen(args.mpsport, args.mpsportbind, function () {
  54. console.log("MeshCentral Intel(R) AMT server running on " + certificates.AmtMpsName + ":" + args.mpsport + ((args.mpsaliasport != null) ? (", alias port " + args.mpsaliasport) : "") + ".");
  55. obj.parent.authLog('mps', 'Server listening on ' + ((args.mpsportbind != null) ? args.mpsportbind : '0.0.0.0') + ' port ' + args.mpsport + '.');
  56. }).on("error", function (err) { console.error("ERROR: MeshCentral Intel(R) AMT server port " + args.mpsport + " is not available. Check if the MeshCentral is already running."); if (args.exactports) { process.exit(); } });
  57. obj.server.on('tlsClientError', function (err, tlssocket) { if (args.mpsdebug) { var remoteAddress = tlssocket.remoteAddress; if (tlssocket.remoteFamily == 'IPv6') { remoteAddress = '[' + remoteAddress + ']'; } console.log('MPS:Invalid TLS connection from ' + remoteAddress + ':' + tlssocket.remotePort + '.'); } });
  58. }
  59. obj.parent.updateServerState('mps-port', args.mpsport);
  60. obj.parent.updateServerState('mps-name', certificates.AmtMpsName);
  61. if (args.mpsaliasport != null) { obj.parent.updateServerState('mps-alias-port', args.mpsaliasport); }
  62. const APFProtocol = {
  63. UNKNOWN: 0,
  64. DISCONNECT: 1,
  65. SERVICE_REQUEST: 5,
  66. SERVICE_ACCEPT: 6,
  67. USERAUTH_REQUEST: 50,
  68. USERAUTH_FAILURE: 51,
  69. USERAUTH_SUCCESS: 52,
  70. GLOBAL_REQUEST: 80,
  71. REQUEST_SUCCESS: 81,
  72. REQUEST_FAILURE: 82,
  73. CHANNEL_OPEN: 90,
  74. CHANNEL_OPEN_CONFIRMATION: 91,
  75. CHANNEL_OPEN_FAILURE: 92,
  76. CHANNEL_WINDOW_ADJUST: 93,
  77. CHANNEL_DATA: 94,
  78. CHANNEL_CLOSE: 97,
  79. PROTOCOLVERSION: 192,
  80. KEEPALIVE_REQUEST: 208,
  81. KEEPALIVE_REPLY: 209,
  82. KEEPALIVE_OPTIONS_REQUEST: 210,
  83. KEEPALIVE_OPTIONS_REPLY: 211,
  84. JSON_CONTROL: 250 // This is a Mesh specific command that sends JSON to and from the MPS server.
  85. };
  86. /*
  87. const APFDisconnectCode = {
  88. HOST_NOT_ALLOWED_TO_CONNECT: 1,
  89. PROTOCOL_ERROR: 2,
  90. KEY_EXCHANGE_FAILED: 3,
  91. RESERVED: 4,
  92. MAC_ERROR: 5,
  93. COMPRESSION_ERROR: 6,
  94. SERVICE_NOT_AVAILABLE: 7,
  95. PROTOCOL_VERSION_NOT_SUPPORTED: 8,
  96. HOST_KEY_NOT_VERIFIABLE: 9,
  97. CONNECTION_LOST: 10,
  98. BY_APPLICATION: 11,
  99. TOO_MANY_CONNECTIONS: 12,
  100. AUTH_CANCELLED_BY_USER: 13,
  101. NO_MORE_AUTH_METHODS_AVAILABLE: 14,
  102. INVALID_CREDENTIALS: 15,
  103. CONNECTION_TIMED_OUT: 16,
  104. BY_POLICY: 17,
  105. TEMPORARILY_UNAVAILABLE: 18
  106. };
  107. const APFChannelOpenFailCodes = {
  108. ADMINISTRATIVELY_PROHIBITED: 1,
  109. CONNECT_FAILED: 2,
  110. UNKNOWN_CHANNEL_TYPE: 3,
  111. RESOURCE_SHORTAGE: 4,
  112. };
  113. */
  114. const APFChannelOpenFailureReasonCode = {
  115. AdministrativelyProhibited: 1,
  116. ConnectFailed: 2,
  117. UnknownChannelType: 3,
  118. ResourceShortage: 4,
  119. };
  120. // Stat counters
  121. var connectionCount = 0;
  122. var userAuthRequestCount = 0;
  123. var incorrectPasswordCount = 0;
  124. var meshNotFoundCount = 0;
  125. var unknownTlsNodeCount = 0;
  126. var unknownTlsMeshIdCount = 0;
  127. var addedTlsDeviceCount = 0;
  128. var unknownNodeCount = 0;
  129. var unknownMeshIdCount = 0;
  130. var addedDeviceCount = 0;
  131. var ciraTimeoutCount = 0;
  132. var protocolVersionCount = 0;
  133. var badUserNameLengthCount = 0;
  134. var channelOpenCount = 0;
  135. var channelOpenConfirmCount = 0;
  136. var channelOpenFailCount = 0;
  137. var channelCloseCount = 0;
  138. var disconnectCommandCount = 0;
  139. var socketClosedCount = 0;
  140. var socketErrorCount = 0;
  141. var maxDomainDevicesReached = 0;
  142. // Add a CIRA connection to the connection list
  143. function addCiraConnection(socket) {
  144. // Check if there is already a connection of the same type
  145. var sameType = false, connections = obj.ciraConnections[socket.tag.nodeid];
  146. if (connections != null) { for (var i in connections) { var conn = connections[i]; if (conn.tag.connType === socket.tag.connType) { sameType = true; } } }
  147. // Add this connection to the connections list
  148. if (connections == null) { obj.ciraConnections[socket.tag.nodeid] = [socket]; } else { obj.ciraConnections[socket.tag.nodeid].push(socket); }
  149. // Update connectivity state
  150. // Report the new state of a CIRA/Relay/LMS connection after a short delay. This is to wait for the connection to have the bounded ports setup before we advertise this new connection.
  151. socket.xxStartHold = 1;
  152. var f = function setConnFunc() {
  153. delete setConnFunc.socket.xxStartHold;
  154. const ciraArray = obj.ciraConnections[setConnFunc.socket.tag.nodeid];
  155. if ((ciraArray != null) && ((ciraArray.indexOf(setConnFunc.socket) >= 0))) { // Check if this connection is still present
  156. if (setConnFunc.socket.tag.connType == 0) {
  157. // Intel AMT CIRA connection. This connection indicates the remote device is present.
  158. obj.parent.SetConnectivityState(setConnFunc.socket.tag.meshid, setConnFunc.socket.tag.nodeid, setConnFunc.socket.tag.connectTime, 2, 7, null, { name: socket.tag.name }); // 7 = Present
  159. } else if (setConnFunc.socket.tag.connType == 1) {
  160. // Intel AMT Relay connection. This connection does not give any information about the remote device's power state.
  161. obj.parent.SetConnectivityState(setConnFunc.socket.tag.meshid, setConnFunc.socket.tag.nodeid, setConnFunc.socket.tag.connectTime, 8, 0, null, { name: socket.tag.name }); // 0 = Unknown
  162. }
  163. // Intel AMT LMS connection (connType == 2), we don't notify of these connections except telling the Intel AMT manager about them.
  164. // If the AMT manager is present, start management of this device
  165. if (obj.parent.amtManager != null) { obj.parent.amtManager.startAmtManagement(setConnFunc.socket.tag.nodeid, setConnFunc.socket.tag.connType, setConnFunc.socket); }
  166. }
  167. }
  168. f.socket = socket;
  169. setTimeout(f, 300);
  170. }
  171. // Remove a CIRA connection from the connection list
  172. function removeCiraConnection(socket) {
  173. // If the AMT manager is present, stop management of this device
  174. if (obj.parent.amtManager != null) { obj.parent.amtManager.stopAmtManagement(socket.tag.nodeid, socket.tag.connType, socket); }
  175. // Remove the connection from the list if present.
  176. const ciraArray = obj.ciraConnections[socket.tag.nodeid];
  177. if (ciraArray == null) return;
  178. var i = ciraArray.indexOf(socket);
  179. if (i == -1) return;
  180. ciraArray.splice(i, 1);
  181. if (ciraArray.length == 0) { delete obj.ciraConnections[socket.tag.nodeid]; } else { obj.ciraConnections[socket.tag.nodeid] = ciraArray; }
  182. // If we are removing a connection during the hold period, don't clear any state since it was never set.
  183. if (socket.xxStartHold == 1) return;
  184. // Check if there is already a connection of the same type
  185. var sameType = false, connections = obj.ciraConnections[socket.tag.nodeid];
  186. if (connections != null) { for (var i in connections) { var conn = connections[i]; if (conn.tag.connType === socket.tag.connType) { sameType = true; } } }
  187. if (sameType == true) return; // if there is a connection of the same type, don't change the connection state.
  188. // Update connectivity state
  189. if (socket.tag.connType == 0) {
  190. obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 2, null, { name: socket.tag.name }); // CIRA
  191. } else if (socket.tag.connType == 1) {
  192. obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 8, null, { name: socket.tag.name }); // Relay
  193. }
  194. }
  195. // Return statistics about this MPS server
  196. obj.getStats = function () {
  197. var ciraConnectionCount = 0;
  198. for (var i in obj.ciraConnections) { ciraConnectionCount += obj.ciraConnections[i].length; }
  199. return {
  200. ciraConnections: ciraConnectionCount,
  201. tlsSessionStore: Object.keys(tlsSessionStore).length,
  202. connectionCount: connectionCount,
  203. userAuthRequestCount: userAuthRequestCount,
  204. incorrectPasswordCount: incorrectPasswordCount,
  205. meshNotFoundCount: meshNotFoundCount,
  206. unknownTlsNodeCount: unknownTlsNodeCount,
  207. unknownTlsMeshIdCount: unknownTlsMeshIdCount,
  208. addedTlsDeviceCount: addedTlsDeviceCount,
  209. unknownNodeCount: unknownNodeCount,
  210. unknownMeshIdCount: unknownMeshIdCount,
  211. addedDeviceCount: addedDeviceCount,
  212. ciraTimeoutCount: ciraTimeoutCount,
  213. protocolVersionCount: protocolVersionCount,
  214. badUserNameLengthCount: badUserNameLengthCount,
  215. channelOpenCount: channelOpenCount,
  216. channelOpenConfirmCount: channelOpenConfirmCount,
  217. channelOpenFailCount: channelOpenFailCount,
  218. channelCloseCount: channelCloseCount,
  219. disconnectCommandCount: disconnectCommandCount,
  220. socketClosedCount: socketClosedCount,
  221. socketErrorCount: socketErrorCount,
  222. maxDomainDevicesReached: maxDomainDevicesReached
  223. };
  224. }
  225. // Required for TLS piping to MQTT broker
  226. function SerialTunnel(options) {
  227. var obj = new require('stream').Duplex(options);
  228. obj.forwardwrite = null;
  229. obj.updateBuffer = function (chunk) { this.push(chunk); };
  230. obj._write = function (chunk, encoding, callback) { if (obj.forwardwrite != null) { obj.forwardwrite(chunk); } else { console.err("Failed to fwd _write."); } if (callback) callback(); }; // Pass data written to forward
  231. obj._read = function (size) { }; // Push nothing, anything to read should be pushed from updateBuffer()
  232. return obj;
  233. }
  234. // Return's the length of an MQTT packet
  235. function getMQTTPacketLength(chunk) {
  236. var packet_len = 0;
  237. if (chunk.readUInt8(0) == 16) {
  238. if (chunk.readUInt8(1) < 128) {
  239. packet_len += chunk.readUInt8(1) + 2;
  240. } else {
  241. // continuation bit, get real value and do next
  242. packet_len += (chunk.readUInt8(1) & 0x7F) + 2;
  243. if (chunk.readUInt8(2) < 128) {
  244. packet_len += 1 + chunk.readUInt8(2) * 128;
  245. } else {
  246. packet_len += 1 + (chunk.readUInt8(2) & 0x7F) * 128;
  247. if (chunk.readUInt8(3) < 128) {
  248. packet_len += 1 + chunk.readUInt8(3) * 128 * 128;
  249. } else {
  250. packet_len += 1 + (chunk.readUInt8(3) & 0x7F) * 128 * 128;
  251. if (chunk.readUInt8(4) < 128) {
  252. packet_len += 1 + chunk.readUInt8(4) * 128 * 128 * 128;
  253. } else {
  254. packet_len += 1 + (chunk.readUInt8(4) & 0x7F) * 128 * 128 * 128;
  255. }
  256. }
  257. }
  258. }
  259. }
  260. return packet_len;
  261. }
  262. obj.onWebSocketConnection = function (socket, req) {
  263. connectionCount++;
  264. // connType: 0 = CIRA, 1 = Relay, 2 = LMS
  265. socket.tag = { first: true, connType: 0, clientCert: null, accumulator: '', activetunnels: 0, boundPorts: [], websocket: true, socket: socket, host: null, nextchannelid: 4, channels: {}, nextsourceport: 0, meiState: {} };
  266. socket.SetupChannel = function SetupChannel(targetport) { return SetupChannel.parent.SetupChannel(SetupChannel.conn, targetport); }
  267. socket.SetupChannel.parent = obj;
  268. socket.SetupChannel.conn = socket;
  269. socket.websocket = 1;
  270. socket.ControlMsg = function ControlMsg(message) { return ControlMsg.parent.SendJsonControl(ControlMsg.conn, message); }
  271. socket.ControlMsg.parent = obj;
  272. socket.ControlMsg.conn = socket;
  273. socket.remoteAddr = req.clientIp;
  274. socket.remotePort = socket._socket.remotePort;
  275. socket._socket.bytesReadEx = 0;
  276. socket._socket.bytesWrittenEx = 0;
  277. parent.debug('mps', "New CIRA websocket connection");
  278. socket.on('message', function (data) {
  279. if (args.mpsdebug) { var buf = Buffer.from(data, 'binary'); console.log("MPS <-- (" + buf.length + "):" + buf.toString('hex')); } // Print out received bytes
  280. // Traffic accounting
  281. parent.webserver.trafficStats.LMSIn += (this._socket.bytesRead - this._socket.bytesReadEx);
  282. parent.webserver.trafficStats.LMSOut += (this._socket.bytesWritten - this._socket.bytesWrittenEx);
  283. this._socket.bytesReadEx = this._socket.bytesRead;
  284. this._socket.bytesWrittenEx = this._socket.bytesWritten;
  285. this.tag.accumulator += data.toString('binary'); // Append as binary string
  286. try {
  287. // Parse all of the APF data we can
  288. var l = 0;
  289. do { l = ProcessCommand(this); if (l > 0) { this.tag.accumulator = this.tag.accumulator.substring(l); } } while (l > 0);
  290. if (l < 0) { this.terminate(); }
  291. } catch (e) {
  292. console.log(e);
  293. }
  294. });
  295. socket.addListener('close', function () {
  296. // Traffic accounting
  297. parent.webserver.trafficStats.LMSIn += (this._socket.bytesRead - this._socket.bytesReadEx);
  298. parent.webserver.trafficStats.LMSOut += (this._socket.bytesWritten - this._socket.bytesWrittenEx);
  299. this._socket.bytesReadEx = this._socket.bytesRead;
  300. this._socket.bytesWrittenEx = this._socket.bytesWritten;
  301. socketClosedCount++;
  302. parent.debug('mps', "CIRA websocket closed", this.tag.meshid, this.tag.nodeid);
  303. removeCiraConnection(socket);
  304. });
  305. socket.addListener('error', function (e) {
  306. socketErrorCount++;
  307. parent.debug('mps', "CIRA websocket connection error", e);
  308. });
  309. }
  310. // Called when a new TLS/TCP connection is accepted
  311. function onConnection(socket) {
  312. connectionCount++;
  313. // connType: 0 = CIRA, 1 = Relay, 2 = LMS
  314. if (obj.args.mpstlsoffload) {
  315. socket.tag = { first: true, connType: 0, clientCert: null, accumulator: '', activetunnels: 0, boundPorts: [], socket: socket, host: null, nextchannelid: 4, channels: {}, nextsourceport: 0, meiState: {} };
  316. } else {
  317. socket.tag = { first: true, connType: 0, clientCert: socket.getPeerCertificate(true), accumulator: '', activetunnels: 0, boundPorts: [], socket: socket, host: null, nextchannelid: 4, channels: {}, nextsourceport: 0, meiState: {} };
  318. }
  319. socket.SetupChannel = function SetupChannel(targetport) { return SetupChannel.parent.SetupChannel(SetupChannel.conn, targetport); }
  320. socket.SetupChannel.parent = obj;
  321. socket.SetupChannel.conn = socket;
  322. socket.ControlMsg = function ControlMsg(message) { return ControlMsg.parent.SendJsonControl(ControlMsg.conn, message); }
  323. socket.ControlMsg.parent = obj;
  324. socket.ControlMsg.conn = socket;
  325. socket.bytesReadEx = 0;
  326. socket.bytesWrittenEx = 0;
  327. socket.remoteAddr = cleanRemoteAddr(socket.remoteAddress);
  328. //socket.remotePort is already present, no need to set it.
  329. socket.setEncoding('binary');
  330. parent.debug('mps', "New CIRA connection");
  331. // Setup the CIRA keep alive timer
  332. socket.setTimeout(MAX_IDLE);
  333. socket.on('timeout', () => { ciraTimeoutCount++; parent.debug('mps', "CIRA timeout, disconnecting."); obj.close(socket); });
  334. socket.addListener('close', function () {
  335. // Traffic accounting
  336. parent.webserver.trafficStats.CIRAIn += (this.bytesRead - this.bytesReadEx);
  337. parent.webserver.trafficStats.CIRAOut += (this.bytesWritten - this.bytesWrittenEx);
  338. this.bytesReadEx = this.bytesRead;
  339. this.bytesWrittenEx = this.bytesWritten;
  340. socketClosedCount++;
  341. parent.debug('mps', 'CIRA connection closed');
  342. removeCiraConnection(socket);
  343. });
  344. socket.addListener('error', function (e) {
  345. socketErrorCount++;
  346. parent.debug('mps', 'CIRA connection error', e);
  347. //console.log("MPS Error: " + socket.remoteAddress);
  348. });
  349. socket.addListener('data', function (data) {
  350. if (args.mpsdebug) { var buf = Buffer.from(data, 'binary'); console.log("MPS <-- (" + buf.length + "):" + buf.toString('hex')); } // Print out received bytes
  351. // Traffic accounting
  352. parent.webserver.trafficStats.CIRAIn += (this.bytesRead - this.bytesReadEx);
  353. parent.webserver.trafficStats.CIRAOut += (this.bytesWritten - this.bytesWrittenEx);
  354. this.bytesReadEx = this.bytesRead;
  355. this.bytesWrittenEx = this.bytesWritten;
  356. socket.tag.accumulator += data;
  357. // Detect if this is an HTTPS request, if it is, return a simple answer and disconnect. This is useful for debugging access to the MPS port.
  358. if (socket.tag.first == true) {
  359. if (socket.tag.accumulator.length < 5) return;
  360. //if (!socket.tag.clientCert.subject) { console.log("MPS Connection, no client cert: " + socket.remoteAddress); socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nMeshCentral2 MPS server.\r\nNo client certificate given.'); obj.close(socket); return; }
  361. if ((socket.tag.accumulator.substring(0, 4) == 'GET ') || (socket.tag.accumulator.substring(0, 5) == 'HEAD ')) {
  362. if (args.mpsdebug) { console.log("MPS Connection, HTTP request detected: " + socket.remoteAddress); }
  363. socket.removeAllListeners('data');
  364. socket.removeAllListeners('close');
  365. socket.on('data', onHttpData);
  366. socket.on('close', onHttpClose);
  367. obj.httpSocket = socket;
  368. onHttpData.call(socket, data);
  369. return;
  370. }
  371. // If the MQTT broker is active, look for inbound MQTT connections
  372. if (parent.mqttbroker != null) {
  373. var chunk = Buffer.from(socket.tag.accumulator, 'binary');
  374. var packet_len = 0;
  375. if (chunk.readUInt8(0) == 16) { packet_len = getMQTTPacketLength(chunk); }
  376. if (chunk.readUInt8(0) == 16 && (socket.tag.accumulator.length < packet_len)) return; // Minimum MQTT detection
  377. // check if it is MQTT, need more initial packet to probe
  378. if (chunk.readUInt8(0) == 16 && ((chunk.slice(4, 8).toString() === 'MQTT') || (chunk.slice(5, 9).toString() === 'MQTT')
  379. || (chunk.slice(6, 10).toString() === 'MQTT') || (chunk.slice(7, 11).toString() === 'MQTT'))) {
  380. parent.debug('mps', "MQTT connection detected.");
  381. socket.removeAllListeners('data');
  382. socket.removeAllListeners('close');
  383. socket.setNoDelay(true);
  384. socket.serialtunnel = SerialTunnel();
  385. socket.serialtunnel.xtransport = 'mps';
  386. socket.serialtunnel.xip = socket.remoteAddress;
  387. socket.on('data', function (b) { socket.serialtunnel.updateBuffer(Buffer.from(b, 'binary')) });
  388. socket.serialtunnel.forwardwrite = function (b) { socket.write(b, 'binary') }
  389. socket.on('close', function () { socket.serialtunnel.emit('end'); });
  390. // Pass socket wrapper to the MQTT broker
  391. parent.mqttbroker.handle(socket.serialtunnel);
  392. socket.unshift(socket.tag.accumulator);
  393. return;
  394. }
  395. }
  396. socket.tag.first = false;
  397. // Setup this node with certificate authentication
  398. if (socket.tag.clientCert && socket.tag.clientCert.subject && socket.tag.clientCert.subject.O && socket.tag.clientCert.subject.O.length == 64) {
  399. // This is a node where the MeshID is indicated within the CIRA certificate
  400. var domainid = '', meshid;
  401. var xx = socket.tag.clientCert.subject.O.split('/');
  402. if (xx.length == 1) { meshid = xx[0]; } else { domainid = xx[0].toLowerCase(); meshid = xx[1]; }
  403. // Check the incoming domain
  404. var domain = obj.parent.config.domains[domainid];
  405. if (domain == null) { console.log('CIRA connection for invalid domain. meshid: ' + meshid); obj.close(socket); return; }
  406. socket.tag.domain = domain;
  407. socket.tag.domainid = domainid;
  408. socket.tag.meshid = 'mesh/' + domainid + '/' + meshid;
  409. socket.tag.nodeid = 'node/' + domainid + '/' + require('crypto').createHash('sha384').update(common.hex2rstr(socket.tag.clientCert.modulus, 'binary')).digest('base64').replace(/\+/g, '@').replace(/\//g, '$');
  410. socket.tag.name = socket.tag.clientCert.subject.CN;
  411. socket.tag.connectTime = Date.now();
  412. socket.tag.host = '';
  413. // Fetch the node
  414. obj.db.Get(socket.tag.nodeid, function (err, nodes) {
  415. if ((nodes == null) || (nodes.length !== 1)) {
  416. var mesh = obj.parent.webserver.meshes[socket.tag.meshid];
  417. if (mesh == null) {
  418. unknownTlsMeshIdCount++;
  419. console.log('ERROR: Intel AMT CIRA connected with unknown groupid: ' + socket.tag.meshid);
  420. obj.close(socket);
  421. return;
  422. } else if (mesh.mtype == 1) {
  423. // Check if we already have too many devices for this domain
  424. if (domain.limits && (typeof domain.limits.maxdevices == 'number')) {
  425. db.isMaxType(domain.limits.maxdevices, 'node', domain.id, function (ismax, count) {
  426. if (ismax == true) {
  427. // Too many devices in this domain.
  428. maxDomainDevicesReached++;
  429. console.log('Too many devices on this domain to accept the CIRA connection. meshid: ' + socket.tag.meshid);
  430. obj.close(socket);
  431. } else {
  432. // Attempts reverse DNS loopup on the device IP address
  433. require('dns').reverse(socket.remoteAddr, function (err, hostnames) {
  434. var hostname = socket.remoteAddr;
  435. if ((err == null) && (hostnames != null) && (hostnames.length > 0)) { hostname = hostnames[0]; }
  436. // We are under the limit, create the new device.
  437. // Node is not in the database, add it. Credentials will be empty until added by the user.
  438. var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, icon: (socket.tag.meiState.isBatteryPowered) ? 2 : 1, host: hostname, domain: domainid, intelamt: { user: (typeof socket.tag.meiState.amtuser == 'string') ? socket.tag.meiState.amtuser : '', pass: (typeof socket.tag.meiState.amtpass == 'string') ? socket.tag.meiState.amtpass : '', tls: 0, state: 2 } };
  439. if (socket.tag.meiState != null) {
  440. if ((typeof socket.tag.meiState.desc == 'string') && (socket.tag.meiState.desc.length > 0) && (socket.tag.meiState.desc.length < 1024)) { device.desc = socket.tag.meiState.desc; }
  441. if ((typeof socket.tag.meiState.Versions == 'object') && (typeof socket.tag.meiState.Versions.Sku == 'string')) { device.intelamt.sku = parseInt(socket.tag.meiState.Versions.Sku); }
  442. }
  443. obj.db.Set(device);
  444. // Event the new node
  445. addedTlsDeviceCount++;
  446. var change = 'CIRA added device ' + socket.tag.name + ' to mesh ' + mesh.name;
  447. obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: parent.webserver.CloneSafeNode(device), msg: change, domain: domainid });
  448. // Add the connection to the MPS connection list
  449. addCiraConnection(socket);
  450. });
  451. }
  452. });
  453. return;
  454. } else {
  455. // Attempts reverse DNS loopup on the device IP address
  456. require('dns').reverse(socket.remoteAddr, function (err, hostnames) {
  457. var hostname = socket.remoteAddr;
  458. if ((err == null) && (hostnames != null) && (hostnames.length > 0)) { hostname = hostnames[0]; }
  459. // Node is not in the database, add it. Credentials will be empty until added by the user.
  460. var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, icon: (socket.tag.meiState.isBatteryPowered) ? 2 : 1, host: hostname, domain: domainid, intelamt: { user: (typeof socket.tag.meiState.amtuser == 'string') ? socket.tag.meiState.amtuser : '', pass: (typeof socket.tag.meiState.amtpass == 'string') ? socket.tag.meiState.amtpass : '', tls: 0, state: 2 } };
  461. if (socket.tag.meiState != null) {
  462. if ((typeof socket.tag.meiState.desc == 'string') && (socket.tag.meiState.desc.length > 0) && (socket.tag.meiState.desc.length < 1024)) { device.desc = socket.tag.meiState.desc; }
  463. if ((typeof socket.tag.meiState.Versions == 'object') && (typeof socket.tag.meiState.Versions.Sku == 'string')) { device.intelamt.sku = parseInt(socket.tag.meiState.Versions.Sku); }
  464. }
  465. obj.db.Set(device);
  466. // Event the new node
  467. addedTlsDeviceCount++;
  468. var change = 'CIRA added device ' + socket.tag.name + ' to mesh ' + mesh.name;
  469. obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: parent.webserver.CloneSafeNode(device), msg: change, domain: domainid });
  470. });
  471. }
  472. } else {
  473. // New CIRA connection for unknown node, disconnect.
  474. unknownTlsNodeCount++;
  475. console.log('CIRA connection for unknown node with incorrect group type. meshid: ' + socket.tag.meshid);
  476. obj.close(socket);
  477. return;
  478. }
  479. } else {
  480. // Node is already present
  481. var node = nodes[0];
  482. socket.tag.meshid = node.meshid; // Correct the MeshID if the node has moved.
  483. socket.tag.name = node.name;
  484. if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
  485. }
  486. // Add the connection to the MPS connection list
  487. addCiraConnection(socket);
  488. });
  489. } else {
  490. // This node connected without certificate authentication, use password auth
  491. //console.log('Intel AMT CIRA connected without certificate authentication');
  492. }
  493. }
  494. try {
  495. // Parse all of the APF data we can
  496. var l = 0;
  497. do { l = ProcessCommand(socket); if (l > 0) { socket.tag.accumulator = socket.tag.accumulator.substring(l); } } while (l > 0);
  498. if (l < 0) { obj.close(socket); }
  499. } catch (e) {
  500. console.log(e);
  501. }
  502. });
  503. }
  504. // Process one APF command
  505. function ProcessCommand(socket) {
  506. var cmd = socket.tag.accumulator.charCodeAt(0);
  507. var len = socket.tag.accumulator.length;
  508. var data = socket.tag.accumulator;
  509. if (len == 0) { return 0; }
  510. switch (cmd) {
  511. case APFProtocol.KEEPALIVE_REQUEST: {
  512. if (len < 5) return 0;
  513. parent.debug('mpscmd', '--> KEEPALIVE_REQUEST');
  514. SendKeepAliveReply(socket, common.ReadInt(data, 1));
  515. return 5;
  516. }
  517. case APFProtocol.KEEPALIVE_REPLY: {
  518. if (len < 5) return 0;
  519. parent.debug('mpscmd', '--> KEEPALIVE_REPLY');
  520. return 5;
  521. }
  522. case APFProtocol.KEEPALIVE_OPTIONS_REPLY: {
  523. if (len < 9) return 0;
  524. const keepaliveInterval = common.ReadInt(data, 1);
  525. const timeout = common.ReadInt(data, 5);
  526. parent.debug('mpscmd', '--> KEEPALIVE_OPTIONS_REPLY', keepaliveInterval, timeout);
  527. return 9;
  528. }
  529. case APFProtocol.PROTOCOLVERSION: {
  530. if (len < 93) return 0;
  531. protocolVersionCount++;
  532. socket.tag.MajorVersion = common.ReadInt(data, 1);
  533. socket.tag.MinorVersion = common.ReadInt(data, 5);
  534. socket.tag.SystemId = guidToStr(common.rstr2hex(data.substring(13, 29))).toLowerCase();
  535. parent.debug('mpscmd', '--> PROTOCOLVERSION', socket.tag.MajorVersion, socket.tag.MinorVersion, socket.tag.SystemId);
  536. return 93;
  537. }
  538. case APFProtocol.USERAUTH_REQUEST: {
  539. if (len < 13) return 0;
  540. userAuthRequestCount++;
  541. var usernameLen = common.ReadInt(data, 1);
  542. if ((usernameLen > 2048) || (len < (5 + usernameLen))) return -1;
  543. var username = data.substring(5, 5 + usernameLen);
  544. var serviceNameLen = common.ReadInt(data, 5 + usernameLen);
  545. if ((serviceNameLen > 2048) || (len < (9 + usernameLen + serviceNameLen))) return -1;
  546. var serviceName = data.substring(9 + usernameLen, 9 + usernameLen + serviceNameLen);
  547. var methodNameLen = common.ReadInt(data, 9 + usernameLen + serviceNameLen);
  548. if ((methodNameLen > 2048) || (len < (13 + usernameLen + serviceNameLen + methodNameLen))) return -1;
  549. var methodName = data.substring(13 + usernameLen + serviceNameLen, 13 + usernameLen + serviceNameLen + methodNameLen);
  550. var passwordLen = 0, password = null;
  551. if (methodName == 'password') {
  552. passwordLen = common.ReadInt(data, 14 + usernameLen + serviceNameLen + methodNameLen);
  553. if ((passwordLen > 2048) || (len < (18 + usernameLen + serviceNameLen + methodNameLen + passwordLen))) return -1;
  554. password = data.substring(18 + usernameLen + serviceNameLen + methodNameLen, 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen);
  555. }
  556. //console.log('MPS:USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password);
  557. parent.debug('mpscmd', '--> USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password);
  558. // If the login uses a cookie, check this now
  559. if ((username == '**MeshAgentApfTunnel**') && (password != null)) {
  560. const cookie = parent.decodeCookie(password, parent.loginCookieEncryptionKey);
  561. if ((cookie == null) || (cookie.a !== 'apf')) {
  562. incorrectPasswordCount++;
  563. socket.ControlMsg({ action: 'console', msg: 'Invalid login username/password' });
  564. parent.debug('mps', 'Incorrect password', username, password);
  565. SendUserAuthFail(socket);
  566. return -1;
  567. }
  568. if (obj.parent.webserver.meshes[cookie.m] == null) {
  569. meshNotFoundCount++;
  570. socket.ControlMsg({ action: 'console', msg: 'Device group not found (1): ' + cookie.m });
  571. parent.debug('mps', 'Device group not found (1): ' + cookie.m, username, password);
  572. SendUserAuthFail(socket);
  573. return -1;
  574. }
  575. // Setup the connection
  576. socket.tag.nodeid = cookie.n;
  577. socket.tag.meshid = cookie.m;
  578. socket.tag.connectTime = Date.now();
  579. // Add the connection to the MPS connection list
  580. addCiraConnection(socket);
  581. SendUserAuthSuccess(socket); // Notify the auth success on the CIRA connection
  582. return 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen;
  583. } else {
  584. // Check the CIRA password
  585. if ((args.mpspass != null) && (password != args.mpspass)) {
  586. incorrectPasswordCount++;
  587. socket.ControlMsg({ action: 'console', msg: 'Invalid login username/password' });
  588. parent.debug('mps', 'Incorrect password', username, password);
  589. SendUserAuthFail(socket);
  590. return -1;
  591. }
  592. // Check the CIRA username, which should be the start of the MeshID.
  593. if (usernameLen != 16) {
  594. badUserNameLengthCount++;
  595. socket.ControlMsg({ action: 'console', msg: 'Username length not 16' });
  596. parent.debug('mps', 'Username length not 16', username, password);
  597. SendUserAuthFail(socket);
  598. return -1;
  599. }
  600. // Find the initial device group for this CIRA connection. Since Intel AMT does not allow @ or $ in the username, we escape these.
  601. // For possible for CIRA-LMS connections to still send @ or $, so we need to escape both sides.
  602. // The initial device group will tell us what device group type and domain this connection is for
  603. var initialMesh = null;
  604. const meshIdStart = ('/' + username).replace(/\@/g, 'X').replace(/\$/g, 'X');
  605. if (obj.parent.webserver.meshes) {
  606. for (var i in obj.parent.webserver.meshes) {
  607. if (obj.parent.webserver.meshes[i]._id.replace(/\@/g, 'X').replace(/\$/g, 'X').indexOf(meshIdStart) > 0) {
  608. initialMesh = obj.parent.webserver.meshes[i]; break;
  609. }
  610. }
  611. }
  612. if (initialMesh == null) {
  613. meshNotFoundCount++;
  614. socket.ControlMsg({ action: 'console', msg: 'Device group not found (2): ' + meshIdStart + ', u: ' + username + ', p: ' + password });
  615. parent.debug('mps', 'Device group not found (2)', meshIdStart, username, password);
  616. SendUserAuthFail(socket);
  617. return -1;
  618. }
  619. }
  620. // If this is a agent-less mesh, use the device guid 3 times as ID.
  621. if (initialMesh.mtype == 1) {
  622. // Intel AMT GUID (socket.tag.SystemId) will be used as NodeID
  623. const systemid = socket.tag.SystemId.split('-').join('');
  624. const nodeid = Buffer.from(systemid + systemid + systemid, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  625. const domain = obj.parent.config.domains[initialMesh.domain];
  626. if (domain == null) return;
  627. socket.tag.domain = domain;
  628. socket.tag.domainid = initialMesh.domain;
  629. if (socket.tag.name == null) { socket.tag.name = ''; }
  630. socket.tag.nodeid = 'node/' + initialMesh.domain + '/' + nodeid; // Turn 16bit systemid guid into 48bit nodeid that is base64 encoded
  631. socket.tag.connectTime = Date.now();
  632. obj.db.Get(socket.tag.nodeid, function (err, nodes) {
  633. if ((nodes == null) || (nodes.length !== 1)) {
  634. // Check if we already have too many devices for this domain
  635. if (domain.limits && (typeof domain.limits.maxdevices == 'number')) {
  636. db.isMaxType(domain.limits.maxdevices, 'node', initialMesh.domain, function (ismax, count) {
  637. if (ismax == true) {
  638. // Too many devices in this domain.
  639. maxDomainDevicesReached++;
  640. console.log('Too many devices on this domain to accept the CIRA connection. meshid: ' + socket.tag.meshid);
  641. obj.close(socket);
  642. } else {
  643. // Attempts reverse DNS loopup on the device IP address
  644. require('dns').reverse(socket.remoteAddr, function (err, hostnames) {
  645. var hostname = socket.remoteAddr;
  646. if ((err == null) && (hostnames != null) && (hostnames.length > 0)) { hostname = hostnames[0]; }
  647. // Set the device group
  648. socket.tag.meshid = initialMesh._id;
  649. // We are under the limit, create the new device.
  650. // Node is not in the database, add it. Credentials will be empty until added by the user.
  651. var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, icon: (socket.tag.meiState.isBatteryPowered) ? 2 : 1, host: hostname, domain: initialMesh.domain, intelamt: { user: (typeof socket.tag.meiState.amtuser == 'string') ? socket.tag.meiState.amtuser : '', pass: (typeof socket.tag.meiState.amtpass == 'string') ? socket.tag.meiState.amtpass : '', tls: 0, state: 2 } };
  652. if (socket.tag.meiState != null) {
  653. if ((typeof socket.tag.meiState.desc == 'string') && (socket.tag.meiState.desc.length > 0) && (socket.tag.meiState.desc.length < 1024)) { device.desc = socket.tag.meiState.desc; }
  654. if ((typeof socket.tag.meiState.Versions == 'object') && (typeof socket.tag.meiState.Versions.Sku == 'string')) { device.intelamt.sku = parseInt(socket.tag.meiState.Versions.Sku); }
  655. }
  656. obj.db.Set(device);
  657. // Event the new node
  658. addedDeviceCount++;
  659. var change = 'Added CIRA device ' + socket.tag.name + ' to group ' + initialMesh.name;
  660. obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: parent.webserver.CloneSafeNode(device), msg: change, domain: initialMesh.domain });
  661. // Add the connection to the MPS connection list
  662. addCiraConnection(socket);
  663. SendUserAuthSuccess(socket); // Notify the auth success on the CIRA connection
  664. });
  665. }
  666. });
  667. return;
  668. } else {
  669. // Attempts reverse DNS loopup on the device IP address
  670. const reverseDnsLookupHandler = function (err, hostnames) {
  671. var hostname = socket.remoteAddr;
  672. if ((err == null) && (hostnames != null) && (hostnames.length > 0)) { hostname = hostnames[0]; }
  673. // Set the device group
  674. socket.tag.meshid = initialMesh._id;
  675. // Node is not in the database, add it. Credentials will be empty until added by the user.
  676. var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, icon: (socket.tag.meiState && socket.tag.meiState.isBatteryPowered) ? 2 : 1, host: hostname, domain: initialMesh.domain, intelamt: { user: ((socket.tag.meiState) && (typeof socket.tag.meiState.amtuser == 'string')) ? socket.tag.meiState.amtuser : '', pass: ((socket.tag.meiState) && (typeof socket.tag.meiState.amtpass == 'string')) ? socket.tag.meiState.amtpass : '', tls: 0, state: 2 } };
  677. if (socket.tag.meiState != null) {
  678. if ((typeof socket.tag.meiState.desc == 'string') && (socket.tag.meiState.desc.length > 0) && (socket.tag.meiState.desc.length < 1024)) { device.desc = socket.tag.meiState.desc; }
  679. if ((typeof socket.tag.meiState.Versions == 'object') && (typeof socket.tag.meiState.Versions.Sku == 'string')) { device.intelamt.sku = parseInt(socket.tag.meiState.Versions.Sku); }
  680. }
  681. obj.db.Set(device);
  682. // Event the new node
  683. addedDeviceCount++;
  684. var change = 'Added CIRA device ' + socket.tag.name + ' to group ' + initialMesh.name;
  685. obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: parent.webserver.CloneSafeNode(device), msg: change, domain: initialMesh.domain });
  686. }
  687. try { require('dns').reverse(socket.remoteAddr, reverseDnsLookupHandler); } catch (ex) { reverseDnsLookupHandler(ex, null); }
  688. }
  689. } else {
  690. // Node is already present
  691. var node = nodes[0];
  692. socket.tag.meshid = node.meshid;
  693. socket.tag.name = node.name;
  694. if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
  695. }
  696. // Add the connection to the MPS connection list
  697. addCiraConnection(socket);
  698. SendUserAuthSuccess(socket); // Notify the auth success on the CIRA connection
  699. });
  700. } else if (initialMesh.mtype == 2) { // If this is a agent mesh, search the mesh for this device UUID
  701. // Intel AMT GUID (socket.tag.SystemId) will be used to search the node
  702. obj.db.getAmtUuidMeshNode(initialMesh.domain, initialMesh.mtype, socket.tag.SystemId, function (err, nodes) { // TODO: Need to optimize this request with indexes
  703. if ((nodes == null) || (nodes.length === 0) || (obj.parent.webserver.meshes == null)) {
  704. // New CIRA connection for unknown node, create a new device.
  705. unknownNodeCount++;
  706. console.log('CIRA connection for unknown node. groupid: ' + initialMesh._id + ', uuid: ' + socket.tag.SystemId);
  707. //obj.close(socket);
  708. //return;
  709. var domain = obj.parent.config.domains[initialMesh.domain];
  710. if (domain == null) return;
  711. // Check if we already have too many devices for this domain
  712. if (domain.limits && (typeof domain.limits.maxdevices == 'number')) {
  713. db.isMaxType(domain.limits.maxdevices, 'node', initialMesh.domain, function (ismax, count) {
  714. if (ismax == true) {
  715. // Too many devices in this domain.
  716. maxDomainDevicesReached++;
  717. console.log('Too many devices on this domain to accept the CIRA connection. meshid: ' + socket.tag.meshid);
  718. obj.close(socket);
  719. } else {
  720. // Attempts reverse DNS loopup on the device IP address
  721. require('dns').reverse(socket.remoteAddr, function (err, hostnames) {
  722. var hostname = socket.remoteAddr;
  723. if ((err == null) && (hostnames != null) && (hostnames.length > 0)) { hostname = hostnames[0]; }
  724. // Set the device group
  725. socket.tag.meshid = initialMesh._id;
  726. const systemid = socket.tag.SystemId.split('-').join('');
  727. const nodeid = Buffer.from(systemid + systemid + systemid, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  728. socket.tag.domain = domain;
  729. socket.tag.domainid = initialMesh.domain;
  730. socket.tag.name = hostname;
  731. socket.tag.nodeid = 'node/' + initialMesh.domain + '/' + nodeid; // Turn 16bit systemid guid into 48bit nodeid that is base64 encoded
  732. socket.tag.connectTime = Date.now();
  733. // Node is not in the database, add it. Credentials will be empty until added by the user.
  734. var device = { type: 'node', mtype: 2, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: hostname, icon: (socket.tag.meiState && socket.tag.meiState.isBatteryPowered) ? 2 : 1, host: hostname, domain: initialMesh.domain, intelamt: { user: ((socket.tag.meiState) && (typeof socket.tag.meiState.amtuser == 'string')) ? socket.tag.meiState.amtuser : '', pass: ((socket.tag.meiState) && (typeof socket.tag.meiState.amtpass == 'string')) ? socket.tag.meiState.amtpass : '', tls: 0, state: 2, agent: { id: 0, caps: 0 } } };
  735. if (socket.tag.meiState != null) {
  736. if ((typeof socket.tag.meiState.desc == 'string') && (socket.tag.meiState.desc.length > 0) && (socket.tag.meiState.desc.length < 1024)) { device.desc = socket.tag.meiState.desc; }
  737. if ((typeof socket.tag.meiState.Versions == 'object') && (typeof socket.tag.meiState.Versions.Sku == 'string')) { device.intelamt.sku = parseInt(socket.tag.meiState.Versions.Sku); }
  738. }
  739. obj.db.Set(device);
  740. // Event the new node
  741. addedDeviceCount++;
  742. var change = 'Added CIRA device ' + socket.tag.name + ' to group ' + initialMesh.name;
  743. obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: parent.webserver.CloneSafeNode(device), msg: change, domain: initialMesh.domain });
  744. // Add the connection to the MPS connection list
  745. addCiraConnection(socket);
  746. SendUserAuthSuccess(socket); // Notify the auth success on the CIRA connection
  747. });
  748. }
  749. });
  750. return;
  751. } else {
  752. // Attempts reverse DNS loopup on the device IP address
  753. require('dns').reverse(socket.remoteAddr, function (err, hostnames) {
  754. var hostname = socket.remoteAddr;
  755. if ((err == null) && (hostnames != null) && (hostnames.length > 0)) { hostname = hostnames[0]; }
  756. // Set the device group
  757. socket.tag.meshid = initialMesh._id;
  758. const systemid = socket.tag.SystemId.split('-').join('');
  759. const nodeid = Buffer.from(systemid + systemid + systemid, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  760. socket.tag.domain = domain;
  761. socket.tag.domainid = initialMesh.domain;
  762. socket.tag.name = hostname;
  763. socket.tag.nodeid = 'node/' + initialMesh.domain + '/' + nodeid; // Turn 16bit systemid guid into 48bit nodeid that is base64 encoded
  764. socket.tag.connectTime = Date.now();
  765. // Node is not in the database, add it. Credentials will be empty until added by the user.
  766. var device = { type: 'node', mtype: 2, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: hostname, icon: (socket.tag.meiState && socket.tag.meiState.isBatteryPowered) ? 2 : 1, host: hostname, domain: initialMesh.domain, agent: { ver: 0, id: 0, caps: 0 }, intelamt: { uuid: socket.tag.SystemId, user: ((socket.tag.meiState) && (typeof socket.tag.meiState.amtuser == 'string')) ? socket.tag.meiState.amtuser : '', pass: ((socket.tag.meiState) && (typeof socket.tag.meiState.amtpass == 'string')) ? socket.tag.meiState.amtpass : '', tls: 0, state: 2 } };
  767. if (socket.tag.meiState != null) {
  768. if ((typeof socket.tag.meiState.desc == 'string') && (socket.tag.meiState.desc.length > 0) && (socket.tag.meiState.desc.length < 1024)) { device.desc = socket.tag.meiState.desc; }
  769. if ((typeof socket.tag.meiState.Versions == 'object') && (typeof socket.tag.meiState.Versions.Sku == 'string')) { device.intelamt.sku = parseInt(socket.tag.meiState.Versions.Sku); }
  770. }
  771. obj.db.Set(device);
  772. // Event the new node
  773. addedDeviceCount++;
  774. var change = 'Added CIRA device ' + socket.tag.name + ' to group ' + initialMesh.name;
  775. obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: parent.webserver.CloneSafeNode(device), msg: change, domain: initialMesh.domain });
  776. // Add the connection to the MPS connection list
  777. addCiraConnection(socket);
  778. SendUserAuthSuccess(socket); // Notify the auth success on the CIRA connection
  779. });
  780. }
  781. return;
  782. }
  783. // Looking at nodes that match this UUID, select one in the same domain and mesh type.
  784. var node = null;
  785. for (var i in nodes) {
  786. if (initialMesh.domain == nodes[i].domain) {
  787. var nodemesh = obj.parent.webserver.meshes[nodes[i].meshid];
  788. if ((nodemesh != null) && (nodemesh.mtype == 2)) { node = nodes[i]; }
  789. }
  790. }
  791. if (node == null) {
  792. // New CIRA connection for unknown node, disconnect.
  793. unknownNodeCount++;
  794. console.log('CIRA connection for unknown node. candidate(s): ' + nodes.length + ', groupid: ' + initialMesh._id + ', uuid: ' + socket.tag.SystemId);
  795. obj.close(socket);
  796. return;
  797. }
  798. // Node is present
  799. if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
  800. socket.tag.nodeid = node._id;
  801. socket.tag.meshid = node.meshid;
  802. socket.tag.connectTime = Date.now();
  803. // Add the connection to the MPS connection list
  804. addCiraConnection(socket);
  805. SendUserAuthSuccess(socket); // Notify the auth success on the CIRA connection
  806. });
  807. } else { // Unknown mesh type
  808. // New CIRA connection for unknown node, disconnect.
  809. unknownMeshIdCount++;
  810. console.log('CIRA connection to a unknown group type. groupid: ' + socket.tag.meshid);
  811. obj.close(socket);
  812. return;
  813. }
  814. return 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen;
  815. }
  816. case APFProtocol.SERVICE_REQUEST: {
  817. if (len < 5) return 0;
  818. var xserviceNameLen = common.ReadInt(data, 1);
  819. if (xserviceNameLen > 2048) return -1;
  820. if (len < 5 + xserviceNameLen) return 0;
  821. var xserviceName = data.substring(5, 5 + xserviceNameLen);
  822. parent.debug('mpscmd', '--> SERVICE_REQUEST', xserviceName);
  823. if (xserviceName == "pfwd@amt.intel.com") { SendServiceAccept(socket, "pfwd@amt.intel.com"); }
  824. if (xserviceName == "auth@amt.intel.com") { SendServiceAccept(socket, "auth@amt.intel.com"); }
  825. return 5 + xserviceNameLen;
  826. }
  827. case APFProtocol.GLOBAL_REQUEST: {
  828. if (len < 14) return 0;
  829. var requestLen = common.ReadInt(data, 1);
  830. if (requestLen > 2048) return -1;
  831. if (len < 14 + requestLen) return 0;
  832. var request = data.substring(5, 5 + requestLen);
  833. //var wantResponse = data.charCodeAt(5 + requestLen);
  834. if (request == 'tcpip-forward') {
  835. var addrLen = common.ReadInt(data, 6 + requestLen);
  836. if (len < 14 + requestLen + addrLen) return 0;
  837. var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen);
  838. var port = common.ReadInt(data, 10 + requestLen + addrLen);
  839. parent.debug('mpscmd', '--> GLOBAL_REQUEST', request, addr + ':' + port);
  840. if (socket.tag.boundPorts.indexOf(port) == -1) { socket.tag.boundPorts.push(port); }
  841. SendTcpForwardSuccessReply(socket, port);
  842. //5900 port is the last TCP port on which connections for forwarding are to be cancelled. Ports order: 16993, 16992, 664, 623, 16995, 16994, 5900
  843. //Request keepalive interval time
  844. if (port === 5900) { SendKeepaliveOptionsRequest(socket, KEEPALIVE_INTERVAL, 0); }
  845. return 14 + requestLen + addrLen;
  846. }
  847. if (request == 'cancel-tcpip-forward') {
  848. var addrLen = common.ReadInt(data, 6 + requestLen);
  849. if (len < 14 + requestLen + addrLen) return 0;
  850. var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen);
  851. var port = common.ReadInt(data, 10 + requestLen + addrLen);
  852. parent.debug('mpscmd', '--> GLOBAL_REQUEST', request, addr + ':' + port);
  853. var portindex = socket.tag.boundPorts.indexOf(port);
  854. if (portindex >= 0) { socket.tag.boundPorts.splice(portindex, 1); }
  855. SendTcpForwardCancelReply(socket);
  856. return 14 + requestLen + addrLen;
  857. }
  858. if (request == 'udp-send-to@amt.intel.com') {
  859. var addrLen = common.ReadInt(data, 6 + requestLen);
  860. if (len < 26 + requestLen + addrLen) return 0;
  861. var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen);
  862. var port = common.ReadInt(data, 10 + requestLen + addrLen);
  863. var oaddrLen = common.ReadInt(data, 14 + requestLen + addrLen);
  864. if (len < 26 + requestLen + addrLen + oaddrLen) return 0;
  865. var oaddr = data.substring(18 + requestLen, 18 + requestLen + addrLen);
  866. var oport = common.ReadInt(data, 18 + requestLen + addrLen + oaddrLen);
  867. var datalen = common.ReadInt(data, 22 + requestLen + addrLen + oaddrLen);
  868. if (len < 26 + requestLen + addrLen + oaddrLen + datalen) return 0;
  869. parent.debug('mpscmd', '--> GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen);
  870. // TODO
  871. return 26 + requestLen + addrLen + oaddrLen + datalen;
  872. }
  873. return 6 + requestLen;
  874. }
  875. case APFProtocol.CHANNEL_OPEN: {
  876. if (len < 33) return 0;
  877. var ChannelTypeLength = common.ReadInt(data, 1);
  878. if (ChannelTypeLength > 2048) return -1;
  879. if (len < (33 + ChannelTypeLength)) return 0;
  880. // Decode channel identifiers and window size
  881. var ChannelType = data.substring(5, 5 + ChannelTypeLength);
  882. var SenderChannel = common.ReadInt(data, 5 + ChannelTypeLength);
  883. var WindowSize = common.ReadInt(data, 9 + ChannelTypeLength);
  884. // Decode the target
  885. var TargetLen = common.ReadInt(data, 17 + ChannelTypeLength);
  886. if (TargetLen > 2048) return -1;
  887. if (len < (33 + ChannelTypeLength + TargetLen)) return 0;
  888. var Target = data.substring(21 + ChannelTypeLength, 21 + ChannelTypeLength + TargetLen);
  889. var TargetPort = common.ReadInt(data, 21 + ChannelTypeLength + TargetLen);
  890. // Decode the source
  891. var SourceLen = common.ReadInt(data, 25 + ChannelTypeLength + TargetLen);
  892. if (SourceLen > 2048) return -1;
  893. if (len < (33 + ChannelTypeLength + TargetLen + SourceLen)) return 0;
  894. var Source = data.substring(29 + ChannelTypeLength + TargetLen, 29 + ChannelTypeLength + TargetLen + SourceLen);
  895. var SourcePort = common.ReadInt(data, 29 + ChannelTypeLength + TargetLen + SourceLen);
  896. channelOpenCount++;
  897. parent.debug('mpscmd', '--> CHANNEL_OPEN', ChannelType, SenderChannel, WindowSize, Target + ':' + TargetPort, Source + ':' + SourcePort);
  898. // Check if we understand this channel type
  899. //if (ChannelType.toLowerCase() == "direct-tcpip")
  900. {
  901. // We don't understand this channel type, send an error back
  902. SendChannelOpenFailure(socket, SenderChannel, APFChannelOpenFailureReasonCode.UnknownChannelType);
  903. return 33 + ChannelTypeLength + TargetLen + SourceLen;
  904. }
  905. /*
  906. // This is a correct connection. Lets get it setup
  907. var MeshAmtEventEndpoint = { ServerChannel: GetNextBindId(), AmtChannel: SenderChannel, MaxWindowSize: 2048, CurrentWindowSize:2048, SendWindow: WindowSize, InfoHeader: "Target: " + Target + ":" + TargetPort + ", Source: " + Source + ":" + SourcePort};
  908. // TODO: Connect this socket for a WSMAN event
  909. SendChannelOpenConfirmation(socket, SenderChannel, MeshAmtEventEndpoint.ServerChannel, MeshAmtEventEndpoint.MaxWindowSize);
  910. */
  911. return 33 + ChannelTypeLength + TargetLen + SourceLen;
  912. }
  913. case APFProtocol.CHANNEL_OPEN_CONFIRMATION:
  914. {
  915. if (len < 17) return 0;
  916. var RecipientChannel = common.ReadInt(data, 1);
  917. var SenderChannel = common.ReadInt(data, 5);
  918. var WindowSize = common.ReadInt(data, 9);
  919. socket.tag.activetunnels++;
  920. var cirachannel = socket.tag.channels[RecipientChannel];
  921. if (cirachannel == null) { /*console.log("MPS Error in CHANNEL_OPEN_CONFIRMATION: Unable to find channelid " + RecipientChannel);*/ return 17; }
  922. cirachannel.amtchannelid = SenderChannel;
  923. cirachannel.sendcredits = cirachannel.amtCiraWindow = WindowSize;
  924. channelOpenConfirmCount++;
  925. parent.debug('mpscmd', '--> CHANNEL_OPEN_CONFIRMATION', RecipientChannel, SenderChannel, WindowSize);
  926. if (cirachannel.closing == 1) {
  927. // Close this channel
  928. SendChannelClose(cirachannel.socket, cirachannel.amtchannelid);
  929. } else {
  930. cirachannel.state = 2;
  931. // Send any pending data
  932. if (cirachannel.sendBuffer != null) {
  933. if (cirachannel.sendBuffer.length <= cirachannel.sendcredits) {
  934. // Send the entire pending buffer
  935. SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer);
  936. cirachannel.sendcredits -= cirachannel.sendBuffer.length;
  937. delete cirachannel.sendBuffer;
  938. if (cirachannel.onSendOk) { cirachannel.onSendOk(cirachannel); }
  939. } else {
  940. // Send a part of the pending buffer
  941. SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer.slice(0, cirachannel.sendcredits));
  942. cirachannel.sendBuffer = cirachannel.sendBuffer.slice(cirachannel.sendcredits);
  943. cirachannel.sendcredits = 0;
  944. }
  945. }
  946. // Indicate the channel is open
  947. if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
  948. }
  949. return 17;
  950. }
  951. case APFProtocol.CHANNEL_OPEN_FAILURE:
  952. {
  953. if (len < 17) return 0;
  954. var RecipientChannel = common.ReadInt(data, 1);
  955. var ReasonCode = common.ReadInt(data, 5);
  956. channelOpenFailCount++;
  957. parent.debug('mpscmd', '--> CHANNEL_OPEN_FAILURE', RecipientChannel, ReasonCode);
  958. var cirachannel = socket.tag.channels[RecipientChannel];
  959. if (cirachannel == null) { console.log("MPS Error in CHANNEL_OPEN_FAILURE: Unable to find channelid " + RecipientChannel); return 17; }
  960. if (cirachannel.state > 0) {
  961. cirachannel.state = 0;
  962. if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
  963. delete socket.tag.channels[RecipientChannel];
  964. }
  965. return 17;
  966. }
  967. case APFProtocol.CHANNEL_CLOSE:
  968. {
  969. if (len < 5) return 0;
  970. var RecipientChannel = common.ReadInt(data, 1);
  971. channelCloseCount++;
  972. parent.debug('mpscmd', '--> CHANNEL_CLOSE', RecipientChannel);
  973. var cirachannel = socket.tag.channels[RecipientChannel];
  974. if (cirachannel == null) { console.log("MPS Error in CHANNEL_CLOSE: Unable to find channelid " + RecipientChannel); return 5; }
  975. socket.tag.activetunnels--;
  976. if (cirachannel.state > 0) {
  977. cirachannel.state = 0;
  978. if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
  979. SendChannelClose(cirachannel.socket, cirachannel.amtchannelid);
  980. delete socket.tag.channels[RecipientChannel];
  981. }
  982. return 5;
  983. }
  984. case APFProtocol.CHANNEL_WINDOW_ADJUST:
  985. {
  986. if (len < 9) return 0;
  987. var RecipientChannel = common.ReadInt(data, 1);
  988. var ByteToAdd = common.ReadInt(data, 5);
  989. var cirachannel = socket.tag.channels[RecipientChannel];
  990. if (cirachannel == null) { console.log("MPS Error in CHANNEL_WINDOW_ADJUST: Unable to find channelid " + RecipientChannel); return 9; }
  991. cirachannel.sendcredits += ByteToAdd;
  992. parent.debug('mpscmd', '--> CHANNEL_WINDOW_ADJUST', RecipientChannel, ByteToAdd, cirachannel.sendcredits);
  993. if (cirachannel.state == 2 && cirachannel.sendBuffer != null) {
  994. // Compute how much data we can send
  995. if (cirachannel.sendBuffer.length <= cirachannel.sendcredits) {
  996. // Send the entire pending buffer
  997. SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer);
  998. cirachannel.sendcredits -= cirachannel.sendBuffer.length;
  999. delete cirachannel.sendBuffer;
  1000. if (cirachannel.onSendOk) { cirachannel.onSendOk(cirachannel); }
  1001. } else {
  1002. // Send a part of the pending buffer
  1003. SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer.slice(0, cirachannel.sendcredits));
  1004. cirachannel.sendBuffer = cirachannel.sendBuffer.slice(cirachannel.sendcredits);
  1005. cirachannel.sendcredits = 0;
  1006. }
  1007. }
  1008. return 9;
  1009. }
  1010. case APFProtocol.CHANNEL_DATA:
  1011. {
  1012. if (len < 9) return 0;
  1013. var RecipientChannel = common.ReadInt(data, 1);
  1014. var LengthOfData = common.ReadInt(data, 5);
  1015. if (SourceLen > 1048576) return -1;
  1016. if (len < (9 + LengthOfData)) return 0;
  1017. parent.debug('mpscmddata', '--> CHANNEL_DATA', RecipientChannel, LengthOfData);
  1018. var cirachannel = socket.tag.channels[RecipientChannel];
  1019. if (cirachannel == null) { console.log("MPS Error in CHANNEL_DATA: Unable to find channelid " + RecipientChannel); return 9 + LengthOfData; }
  1020. if (cirachannel.state > 0) {
  1021. cirachannel.amtpendingcredits += LengthOfData;
  1022. if (cirachannel.onData) { cirachannel.onData(cirachannel, Buffer.from(data.substring(9, 9 + LengthOfData), 'binary')); }
  1023. if (cirachannel.amtpendingcredits > (cirachannel.ciraWindow / 2)) {
  1024. SendChannelWindowAdjust(cirachannel.socket, cirachannel.amtchannelid, cirachannel.amtpendingcredits); // Adjust the buffer window
  1025. cirachannel.amtpendingcredits = 0;
  1026. }
  1027. }
  1028. return 9 + LengthOfData;
  1029. }
  1030. case APFProtocol.DISCONNECT:
  1031. {
  1032. if (len < 7) return 0;
  1033. var ReasonCode = common.ReadInt(data, 1);
  1034. disconnectCommandCount++;
  1035. parent.debug('mpscmd', '--> DISCONNECT', ReasonCode);
  1036. removeCiraConnection(socket);
  1037. return 7;
  1038. }
  1039. case APFProtocol.JSON_CONTROL: // This is a Mesh specific command that sends JSON to and from the MPS server.
  1040. {
  1041. if (len < 5) return 0;
  1042. var jsondatalen = common.ReadInt(data, 1);
  1043. if (jsondatalen > 1048576) return -1;
  1044. if (len < (5 + jsondatalen)) return 0;
  1045. var jsondata = null, jsondatastr = data.substring(5, 5 + jsondatalen);
  1046. try { jsondata = JSON.parse(jsondatastr); } catch (ex) { }
  1047. if ((jsondata == null) || (typeof jsondata.action != 'string')) return;
  1048. parent.debug('mpscmd', '--> JSON_CONTROL', jsondata.action);
  1049. switch (jsondata.action) {
  1050. case 'connType':
  1051. if ((socket.tag.connType != 0) || (socket.tag.SystemId != null)) return; // Once set, the connection type can't be changed.
  1052. if (typeof jsondata.value != 'number') return;
  1053. socket.tag.connType = jsondata.value; // 0 = CIRA, 1 = Relay, 2 = LMS
  1054. //obj.SendJsonControl(socket, { action: 'mestate' }); // Request an MEI state refresh
  1055. break;
  1056. case 'meiState':
  1057. if (socket.tag.connType != 2) break; // Only accept MEI state on CIRA-LMS connection
  1058. socket.tag.meiState = jsondata.value;
  1059. if (((socket.tag.name == '') || (socket.tag.name == null)) && (typeof jsondata.value.OsHostname == 'string')) { socket.tag.name = jsondata.value.OsHostname; }
  1060. if (obj.parent.amtManager != null) { obj.parent.amtManager.mpsControlMessage(socket.tag.nodeid, socket, socket.tag.connType, jsondata); }
  1061. break;
  1062. case 'deactivate':
  1063. case 'startTlsHostConfig':
  1064. case 'stopConfiguration':
  1065. if (socket.tag.connType != 2) break; // Only accept MEI state on CIRA-LMS connection
  1066. if (obj.parent.amtManager != null) { obj.parent.amtManager.mpsControlMessage(socket.tag.nodeid, socket, socket.tag.connType, jsondata); }
  1067. break;
  1068. }
  1069. return 5 + jsondatalen;
  1070. }
  1071. default:
  1072. {
  1073. parent.debug('mpscmd', '--> Unknown CIRA command: ' + cmd);
  1074. return -1;
  1075. }
  1076. }
  1077. }
  1078. // Disconnect CIRA tunnel
  1079. obj.close = function (socket) {
  1080. try { socket.end(); } catch (e) { try { socket.close(); } catch (e) { } }
  1081. removeCiraConnection(socket);
  1082. };
  1083. // Disconnect all CIRA tunnel for a given NodeId
  1084. obj.closeAllForNode = function (nodeid) {
  1085. var connections = obj.ciraConnections[nodeid];
  1086. if (connections == null) return;
  1087. for (var i in connections) { obj.close(connections[i]); }
  1088. };
  1089. obj.SendJsonControl = function (socket, data) {
  1090. if (socket.tag.connType == 0) return; // This command is valid only for connections that are not really CIRA.
  1091. if (typeof data == 'object') { parent.debug('mpscmd', '<-- JSON_CONTROL', data.action); data = JSON.stringify(data); } else { parent.debug('mpscmd', '<-- JSON_CONTROL'); }
  1092. Write(socket, String.fromCharCode(APFProtocol.JSON_CONTROL) + common.IntToStr(data.length) + data);
  1093. }
  1094. function SendServiceAccept(socket, service) {
  1095. parent.debug('mpscmd', '<-- SERVICE_ACCEPT', service);
  1096. Write(socket, String.fromCharCode(APFProtocol.SERVICE_ACCEPT) + common.IntToStr(service.length) + service);
  1097. }
  1098. function SendTcpForwardSuccessReply(socket, port) {
  1099. parent.debug('mpscmd', '<-- REQUEST_SUCCESS', port);
  1100. Write(socket, String.fromCharCode(APFProtocol.REQUEST_SUCCESS) + common.IntToStr(port));
  1101. }
  1102. function SendTcpForwardCancelReply(socket) {
  1103. parent.debug('mpscmd', '<-- REQUEST_SUCCESS');
  1104. Write(socket, String.fromCharCode(APFProtocol.REQUEST_SUCCESS));
  1105. }
  1106. /*
  1107. function SendKeepAliveRequest(socket, cookie) {
  1108. parent.debug('mpscmd', '<-- KEEPALIVE_REQUEST', cookie);
  1109. Write(socket, String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + common.IntToStr(cookie));
  1110. }
  1111. */
  1112. function SendKeepAliveReply(socket, cookie) {
  1113. parent.debug('mpscmd', '<-- KEEPALIVE_REPLY', cookie);
  1114. Write(socket, String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + common.IntToStr(cookie));
  1115. }
  1116. function SendKeepaliveOptionsRequest(socket, keepaliveTime, timeout) {
  1117. parent.debug('mpscmd', '<-- KEEPALIVE_OPTIONS_REQUEST', keepaliveTime, timeout);
  1118. Write(socket, String.fromCharCode(APFProtocol.KEEPALIVE_OPTIONS_REQUEST) + common.IntToStr(keepaliveTime) + common.IntToStr(timeout));
  1119. }
  1120. function SendChannelOpenFailure(socket, senderChannel, reasonCode) {
  1121. parent.debug('mpscmd', '<-- CHANNEL_OPEN_FAILURE', senderChannel, reasonCode);
  1122. Write(socket, String.fromCharCode(APFProtocol.CHANNEL_OPEN_FAILURE) + common.IntToStr(senderChannel) + common.IntToStr(reasonCode) + common.IntToStr(0) + common.IntToStr(0));
  1123. }
  1124. /*
  1125. function SendChannelOpenConfirmation(socket, recipientChannelId, senderChannelId, initialWindowSize) {
  1126. parent.debug('mpscmd', '<-- CHANNEL_OPEN_CONFIRMATION', recipientChannelId, senderChannelId, initialWindowSize);
  1127. Write(socket, String.fromCharCode(APFProtocol.CHANNEL_OPEN_CONFIRMATION) + common.IntToStr(recipientChannelId) + common.IntToStr(senderChannelId) + common.IntToStr(initialWindowSize) + common.IntToStr(-1));
  1128. }
  1129. */
  1130. function SendChannelOpen(socket, direct, channelid, windowsize, target, targetport, source, sourceport) {
  1131. var connectionType = ((direct == true) ? 'direct-tcpip' : 'forwarded-tcpip');
  1132. if ((target == null) || (target == null)) target = ''; // TODO: Reports of target being undefined that causes target.length to fail. This is a hack.
  1133. parent.debug('mpscmd', '<-- CHANNEL_OPEN', connectionType, channelid, windowsize, target + ':' + targetport, source + ':' + sourceport);
  1134. Write(socket, String.fromCharCode(APFProtocol.CHANNEL_OPEN) + common.IntToStr(connectionType.length) + connectionType + common.IntToStr(channelid) + common.IntToStr(windowsize) + common.IntToStr(-1) + common.IntToStr(target.length) + target + common.IntToStr(targetport) + common.IntToStr(source.length) + source + common.IntToStr(sourceport));
  1135. }
  1136. function SendChannelClose(socket, channelid) {
  1137. parent.debug('mpscmd', '<-- CHANNEL_CLOSE', channelid);
  1138. Write(socket, String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + common.IntToStr(channelid));
  1139. }
  1140. // Send a buffer to a given channel
  1141. function SendChannelData(socket, channelid, data) {
  1142. parent.debug('mpscmddata', '<-- CHANNEL_DATA', channelid, data.length);
  1143. const buf = Buffer.alloc(9 + data.length);
  1144. buf[0] = APFProtocol.CHANNEL_DATA; // CHANNEL_DATA
  1145. buf.writeInt32BE(channelid, 1); // ChannelID
  1146. buf.writeInt32BE(data.length, 5); // Data Length
  1147. data.copy(buf, 9, 0);
  1148. WriteBuffer(socket, buf);
  1149. }
  1150. function SendChannelWindowAdjust(socket, channelid, bytestoadd) {
  1151. parent.debug('mpscmd', '<-- CHANNEL_WINDOW_ADJUST', channelid, bytestoadd);
  1152. Write(socket, String.fromCharCode(APFProtocol.CHANNEL_WINDOW_ADJUST) + common.IntToStr(channelid) + common.IntToStr(bytestoadd));
  1153. }
  1154. /*
  1155. function SendDisconnect(socket, reasonCode) {
  1156. parent.debug('mpscmd', '<-- DISCONNECT', reasonCode);
  1157. Write(socket, String.fromCharCode(APFProtocol.DISCONNECT) + common.IntToStr(reasonCode) + common.ShortToStr(0));
  1158. }
  1159. */
  1160. function SendUserAuthFail(socket) {
  1161. parent.debug('mpscmd', '<-- USERAUTH_FAILURE');
  1162. Write(socket, String.fromCharCode(APFProtocol.USERAUTH_FAILURE) + common.IntToStr(8) + 'password' + common.ShortToStr(0));
  1163. }
  1164. function SendUserAuthSuccess(socket) {
  1165. parent.debug('mpscmd', '<-- USERAUTH_SUCCESS');
  1166. Write(socket, String.fromCharCode(APFProtocol.USERAUTH_SUCCESS));
  1167. }
  1168. // Send a string or buffer
  1169. function Write(socket, data) {
  1170. try {
  1171. if (args.mpsdebug) {
  1172. // Print out sent bytes
  1173. var buf = Buffer.from(data, 'binary');
  1174. console.log('MPS --> (' + buf.length + '):' + buf.toString('hex'));
  1175. if (socket.websocket == 1) { socket.send(buf); } else { socket.write(buf); }
  1176. } else {
  1177. if (socket.websocket == 1) { socket.send(Buffer.from(data, 'binary')); } else { socket.write(Buffer.from(data, 'binary')); }
  1178. }
  1179. } catch (ex) { }
  1180. }
  1181. // Send a buffer
  1182. function WriteBuffer(socket, data) {
  1183. try {
  1184. if (args.mpsdebug) { console.log('MPS --> (' + buf.length + '):' + data.toString('hex')); } // Print out sent bytes
  1185. if (socket.websocket == 1) { socket.send(data); } else { socket.write(data); }
  1186. } catch (ex) { }
  1187. }
  1188. // Returns a CIRA/Relay/LMS connection to a nodeid, use the best possible connection, CIRA first, Relay second, LMS third.
  1189. // if oob is set to true, don't allow an LMS connection.
  1190. obj.GetConnectionToNode = function (nodeid, targetport, oob) {
  1191. var connectionArray = obj.ciraConnections[nodeid];
  1192. if (connectionArray == null) return null;
  1193. var selectConn = null;
  1194. // Select the best connection, which is the one with the lowest connType value.
  1195. for (var i in connectionArray) {
  1196. var conn = connectionArray[i];
  1197. if ((oob === true) && (conn.tag.connType == 2)) continue; // If an OOB connection is required, don't allow LMS connections.
  1198. if ((typeof oob === 'number') && (conn.tag.connType !== oob)) continue; // if OOB specifies an exact connection type, filter on this type.
  1199. if ((targetport != null) && (conn.tag.boundPorts.indexOf(targetport) == -1)) continue; // This connection does not route to the target port.
  1200. if ((selectConn == null) || (conn.tag.connType < selectConn.tag.connType)) { selectConn = conn; }
  1201. }
  1202. return selectConn;
  1203. }
  1204. // Setup a new channel to a nodeid, use the best possible connection, CIRA first, Relay second, LMS third.
  1205. // if oob is set to true, don't allow an LMS connection.
  1206. obj.SetupChannelToNode = function (nodeid, targetport, oob) {
  1207. var conn = obj.GetConnectionToNode(nodeid, targetport, oob);
  1208. if (conn == null) return null;
  1209. return obj.SetupChannel(conn, targetport);
  1210. }
  1211. // Setup a new channel
  1212. obj.SetupChannel = function (socket, targetport) {
  1213. var sourceport = (socket.tag.nextsourceport++ % 30000) + 1024;
  1214. var cirachannel = { targetport: targetport, channelid: socket.tag.nextchannelid++, socket: socket, state: 1, sendcredits: 0, amtpendingcredits: 0, amtCiraWindow: 0, ciraWindow: 32768 };
  1215. SendChannelOpen(socket, false, cirachannel.channelid, cirachannel.ciraWindow, socket.tag.host, targetport, '1.2.3.4', sourceport);
  1216. // This function writes data to this CIRA channel
  1217. cirachannel.write = function (data) {
  1218. if (cirachannel.state == 0) return false;
  1219. if (typeof data == 'string') { data = Buffer.from(data, 'binary'); } // Make sure we always handle buffers when sending data.
  1220. if (cirachannel.state == 1 || cirachannel.sendcredits == 0 || cirachannel.sendBuffer != null) {
  1221. // Channel is connected, but we are out of credits. Add the data to the outbound buffer.
  1222. if (cirachannel.sendBuffer == null) { cirachannel.sendBuffer = data; } else { cirachannel.sendBuffer = Buffer.concat([cirachannel.sendBuffer, data]); }
  1223. return true;
  1224. }
  1225. // Compute how much data we can send
  1226. if (data.length <= cirachannel.sendcredits) {
  1227. // Send the entire message
  1228. SendChannelData(cirachannel.socket, cirachannel.amtchannelid, data);
  1229. cirachannel.sendcredits -= data.length;
  1230. return true;
  1231. }
  1232. // Send a part of the message
  1233. cirachannel.sendBuffer = data.slice(cirachannel.sendcredits);
  1234. SendChannelData(cirachannel.socket, cirachannel.amtchannelid, data.slice(0, cirachannel.sendcredits));
  1235. cirachannel.sendcredits = 0;
  1236. return false;
  1237. };
  1238. // This function closes this CIRA channel
  1239. cirachannel.close = function () {
  1240. if (cirachannel.state == 0 || cirachannel.closing == 1) return;
  1241. if (cirachannel.state == 1) { cirachannel.closing = 1; cirachannel.state = 0; if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); } return; }
  1242. cirachannel.state = 0;
  1243. cirachannel.closing = 1;
  1244. SendChannelClose(cirachannel.socket, cirachannel.amtchannelid);
  1245. if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
  1246. };
  1247. socket.tag.channels[cirachannel.channelid] = cirachannel;
  1248. return cirachannel;
  1249. };
  1250. // Change a node to a new meshid, this is called when a node changes groups.
  1251. obj.changeDeviceMesh = function (nodeid, newMeshId) {
  1252. var connectionArray = obj.ciraConnections[nodeid];
  1253. if (connectionArray == null) return;
  1254. for (var i in connectionArray) {
  1255. var socket = connectionArray[i];
  1256. if ((socket != null) && (socket.tag != null)) { socket.tag.meshid = newMeshId; }
  1257. }
  1258. }
  1259. // Called when handling incoming HTTP data
  1260. function onHttpData(data) {
  1261. if (this.xdata == null) { this.xdata = data; } else { this.xdata += data; }
  1262. var headersize = this.xdata.indexOf('\r\n\r\n');
  1263. if (headersize < 0) { if (this.xdata.length > 4096) { this.end(); } return; }
  1264. var headers = this.xdata.substring(0, headersize).split('\r\n');
  1265. if (headers.length < 1) { this.end(); return; }
  1266. var headerObj = {};
  1267. for (var i = 1; i < headers.length; i++) { var j = headers[i].indexOf(': '); if (i > 0) { headerObj[headers[i].substring(0, j).toLowerCase()] = headers[i].substring(j + 2); } }
  1268. var hostHeader = (headerObj['host'] != null) ? ('Host: ' + headerObj['host'] + '\r\n') : '';
  1269. var directives = headers[0].split(' ');
  1270. if ((directives.length != 3) || ((directives[0] != 'GET') && (directives[0] != 'HEAD'))) { this.end(); return; }
  1271. //console.log('WebServer, request', directives[0], directives[1]);
  1272. var responseCode = 404, responseType = 'application/octet-stream', responseData = '', r = null;
  1273. // Check if this is a cookie request
  1274. if (directives[1].startsWith('/c/')) {
  1275. var cookie = obj.parent.decodeCookie(directives[1].substring(3).split('.')[0], obj.parent.loginCookieEncryptionKey, 30); // 30 minute timeout
  1276. if ((cookie != null) && (cookie.a == 'f') && (typeof cookie.f == 'string')) {
  1277. // Send the file header and pipe the rest of the file
  1278. var filestats = null;
  1279. try { filestats = obj.fs.statSync(cookie.f); } catch (ex) { }
  1280. if ((filestats == null) || (typeof filestats.size != 'number') || (filestats.size <= 0)) {
  1281. responseCode = 404; responseType = 'text/html'; responseData = 'File not found';
  1282. } else {
  1283. this.write('HTTP/1.1 200 OK\r\n' + hostHeader + 'Content-Type: ' + responseType + '\r\nConnection: keep-alive\r\nCache-Control: no-cache\r\nContent-Length: ' + filestats.size + '\r\n\r\n');
  1284. if (directives[0] == 'GET') { obj.fs.createReadStream(cookie.f, { flags: 'r' }).pipe(this); }
  1285. delete this.xdata;
  1286. return;
  1287. }
  1288. }
  1289. } else {
  1290. // Check if we have a preset response
  1291. if (obj.httpResponses != null) { r = obj.httpResponses[directives[1]]; }
  1292. if ((r != null) && (r.maxtime != null) && (r.maxtime < Date.now())) { r = null; delete obj.httpResponses[directives[1]]; } // Check if this entry is expired.
  1293. if (r != null) {
  1294. if (typeof r == 'string') {
  1295. responseCode = 200; responseType = 'text/html'; responseData = r;
  1296. } else if (typeof r == 'object') {
  1297. responseCode = 200;
  1298. if (r.type) { responseType = r.type; }
  1299. if (r.data) { responseData = r.data; }
  1300. if (r.shortfile) { try { responseData = obj.fs.readFileSync(r.shortfile); } catch (ex) { responseCode = 404; responseType = 'text/html'; responseData = 'File not found'; } }
  1301. if (r.file) {
  1302. // Send the file header and pipe the rest of the file
  1303. var filestats = null;
  1304. try { filestats = obj.fs.statSync(r.file); } catch (ex) { }
  1305. if ((filestats == null) || (typeof filestats.size != 'number') || (filestats.size <= 0)) {
  1306. responseCode = 404; responseType = 'text/html'; responseData = 'File not found';
  1307. } else {
  1308. this.write('HTTP/1.1 200 OK\r\n' + hostHeader + 'Content-Type: ' + responseType + '\r\nConnection: keep-alive\r\nCache-Control: no-cache\r\nContent-Length: ' + filestats.size + '\r\n\r\n');
  1309. if (directives[0] == 'GET') {
  1310. obj.fs.createReadStream(r.file, { flags: 'r' }).pipe(this);
  1311. if (typeof r.maxserve == 'number') { r.maxserve--; if (r.maxserve == 0) { delete obj.httpResponses[directives[1]]; } } // Check if this entry was server the maximum amount of times.
  1312. }
  1313. delete this.xdata;
  1314. return;
  1315. }
  1316. }
  1317. }
  1318. } else {
  1319. responseType = 'text/html';
  1320. responseData = 'Invalid request';
  1321. }
  1322. }
  1323. this.write('HTTP/1.1 ' + responseCode + ' OK\r\n' + hostHeader + 'Connection: keep-alive\r\nCache-Control: no-cache\r\nContent-Type: ' + responseType + '\r\nContent-Length: ' + responseData.length + '\r\n\r\n');
  1324. this.write(responseData);
  1325. delete this.xdata;
  1326. }
  1327. // Called when handling HTTP data and the socket closes
  1328. function onHttpClose() { }
  1329. // Add a HTTP file response
  1330. obj.addHttpFileResponse = function (path, file, maxserve, minutes) {
  1331. var r = { file: file };
  1332. if (typeof maxserve == 'number') { r.maxserve = maxserve; }
  1333. if (typeof minutes == 'number') { r.maxtime = Date.now() + (60000 * minutes); }
  1334. obj.httpResponses[path] = r;
  1335. // Clean up any expired files
  1336. const now = Date.now();
  1337. for (var i in obj.httpResponses) { if ((obj.httpResponses[i].maxtime != null) && (obj.httpResponses[i].maxtime < now)) { delete obj.httpResponses[i]; } }
  1338. }
  1339. // Drop all CIRA connections
  1340. obj.dropAllConnections = function () {
  1341. var dropCount = 0;
  1342. for (var nodeid in obj.ciraConnections) {
  1343. const connections = obj.ciraConnections[nodeid];
  1344. for (var i in connections) { if (connections[i].end) { connections[i].end(); dropCount++; } } // This will drop all TCP CIRA connections
  1345. }
  1346. return dropCount;
  1347. }
  1348. 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); }
  1349. // Clean a IPv6 address that encodes a IPv4 address
  1350. function cleanRemoteAddr(addr) { if (typeof addr != 'string') { return null; } if (addr.indexOf('::ffff:') == 0) { return addr.substring(7); } else { return addr; } }
  1351. // Example, this will add a file to stream, served 2 times max and 3 minutes max.
  1352. //obj.addHttpFileResponse('/a.png', 'c:\\temp\\MC2-LetsEncrypt.png', 2, 3);
  1353. return obj;
  1354. };