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