multiserver.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /**
  2. * @description MeshCentral Multi-Server Support
  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 Mesh Multi-Server object. This is used for MeshCentral-to-MeshCentral communication.
  15. module.exports.CreateMultiServer = function (parent, args) {
  16. var obj = {};
  17. const WebSocket = require('ws');
  18. obj.parent = parent;
  19. obj.crypto = require('crypto');
  20. obj.peerConfig = parent.config.peers;
  21. obj.forge = require('node-forge');
  22. obj.outPeerServers = {}; // Outgoing peer servers
  23. obj.peerServers = {}; // All connected servers (in & out). Only present in this list if the connection is setup
  24. obj.serverid = null;
  25. // Create a mesh server module that will connect to other servers
  26. obj.CreatePeerOutServer = function (parent, serverid, url) {
  27. var obj = {};
  28. obj.parent = parent;
  29. obj.serverid = serverid;
  30. obj.url = url;
  31. obj.ws = null;
  32. obj.certificates = parent.parent.certificates;
  33. obj.common = require('./common.js');
  34. obj.forge = require('node-forge');
  35. obj.crypto = require('crypto');
  36. obj.connectionState = 0;
  37. obj.retryTimer = null;
  38. obj.retryBackoff = 0;
  39. obj.connectHandler = null;
  40. obj.webCertificateHash = obj.parent.parent.webserver.webCertificateHash;
  41. obj.agentCertificateHashBase64 = obj.parent.parent.webserver.agentCertificateHashBase64;
  42. obj.agentCertificateAsn1 = obj.parent.parent.webserver.agentCertificateAsn1;
  43. obj.peerServerId = null;
  44. obj.authenticated = 0;
  45. obj.serverCertHash = null;
  46. obj.pendingData = [];
  47. // Disconnect from the server and/or stop trying
  48. obj.stop = function () {
  49. obj.connectionState = 0;
  50. disconnect();
  51. };
  52. // Make one attempt at connecting to the server
  53. function connect() {
  54. obj.retryTimer = null;
  55. obj.connectionState = 1;
  56. // Get the web socket setup
  57. obj.ws = new WebSocket(obj.url + 'meshserver.ashx', { rejectUnauthorized: false, servername: obj.certificates.CommonName, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key });
  58. obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Connecting to: ' + url + 'meshserver.ashx');
  59. // Register the connection failed event
  60. obj.ws.on('error', function (error) { obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Error: ' + error); disconnect(); });
  61. obj.ws.on('close', function () { obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Disconnected'); disconnect(); });
  62. // Register the connection event
  63. obj.ws.on('open', function () {
  64. obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Connected');
  65. obj.connectionState |= 2;
  66. obj.nonce = obj.crypto.randomBytes(48).toString('binary');
  67. // Get the peer server's certificate and compute the server public key hash
  68. if (obj.ws._socket == null) return;
  69. if (obj.url.toLowerCase().startsWith('wss://')) {
  70. // We are using TLS, use the certificate hash
  71. var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(obj.ws._socket.getPeerCertificate().raw.toString('binary')));
  72. obj.serverCertHash = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
  73. } else {
  74. // We are not using TLS, blank out the TLS certificate hash
  75. obj.serverCertHash = Buffer.alloc(48).toString('binary');
  76. }
  77. // Start authenticate the peer server by sending a auth nonce & server TLS cert hash.
  78. // Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
  79. obj.ws.send(Buffer.from(obj.common.ShortToStr(1) + obj.serverCertHash + obj.nonce, 'binary')); // Command 1, hash + nonce
  80. });
  81. // If a message is received
  82. obj.ws.on('message', function (msg) {
  83. if (typeof msg != 'string') { msg = msg.toString('binary'); }
  84. if (msg.length < 2) return;
  85. if (msg.charCodeAt(0) == 123) {
  86. if ((obj.connectionState & 4) != 0) { processServerData(msg); } else { obj.pendingData.push(msg); }
  87. } else {
  88. var cmd = obj.common.ReadShort(msg, 0);
  89. switch (cmd) {
  90. case 1: {
  91. // Server authentication request
  92. if (msg.length != 98) { obj.parent.parent.debug('peer', 'OutPeer: Bad server authentication message, length = ' + msg.length + ', should be 98. HEX: ' + Buffer.from(msg.substring(0, 4096), 'binary').toString('hex')); return; }
  93. // Check that the server hash matches the TLS server certificate public key hash
  94. if (obj.url.toLowerCase().startsWith('wss://') && (obj.serverCertHash != msg.substring(2, 50))) { obj.parent.parent.debug('peer', 'OutPeer: Server hash mismatch.'); disconnect(); return; }
  95. obj.servernonce = msg.substring(50);
  96. // Perform the hash signature using the server agent certificate
  97. obj.parent.parent.certificateOperations.acceleratorPerformSignature(0, msg.substring(2) + obj.nonce, null, function (tag, signature) {
  98. // Send back our certificate + signature
  99. if (obj.ws != null) { obj.ws.send(Buffer.from(obj.common.ShortToStr(2) + obj.common.ShortToStr(obj.agentCertificateAsn1.length) + obj.agentCertificateAsn1 + signature, 'binary')); } // Command 2, certificate + signature
  100. });
  101. break;
  102. }
  103. case 2: {
  104. // Server certificate
  105. var certlen = obj.common.ReadShort(msg, 2), serverCert = null;
  106. var serverCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----';
  107. try { serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { }
  108. if (serverCert == null) { obj.parent.parent.debug('peer', 'OutPeer: Invalid server certificate.'); disconnect(); return; }
  109. var serverid = Buffer.from(obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  110. if (serverid !== obj.agentCertificateHashBase64) { obj.parent.parent.debug('peer', 'OutPeer: Server hash mismatch.'); disconnect(); return; }
  111. // Server signature, verify it. This is the fast way, without using forge. (TODO: Use accelerator for this?)
  112. const verify = obj.parent.crypto.createVerify('SHA384');
  113. verify.end(Buffer.from(obj.serverCertHash + obj.nonce + obj.servernonce, 'binary'));
  114. if (verify.verify(serverCertPem, Buffer.from(msg.substring(4 + certlen), 'binary')) !== true) { obj.parent.parent.debug('peer', 'OutPeer: Server sign check failed.'); disconnect(); return; }
  115. // Connection is a success, clean up
  116. delete obj.nonce;
  117. delete obj.servernonce;
  118. obj.serverCertHash = Buffer.from(obj.serverCertHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); // Change this value to base64
  119. obj.connectionState |= 4;
  120. obj.retryBackoff = 0; // Set backoff connection timer back to fast.
  121. obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
  122. // Send information about our server to the peer
  123. if (obj.connectionState == 15) {
  124. obj.send({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 });
  125. for (var i in obj.pendingData) { processServerData(obj.pendingData[i]); } // Process any pending data
  126. obj.pendingData = [];
  127. }
  128. //if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
  129. break;
  130. }
  131. case 4: {
  132. // Peer server confirmed authentication, we are allowed to send commands to the server
  133. obj.connectionState |= 8;
  134. if (obj.connectionState == 15) {
  135. obj.send({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 });
  136. for (var i in obj.pendingData) { processServerData(obj.pendingData[i]); } // Process any pending data
  137. obj.pendingData = [];
  138. }
  139. //if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
  140. break;
  141. }
  142. default: {
  143. obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Un-handled command: ' + cmd);
  144. break;
  145. }
  146. }
  147. }
  148. });
  149. }
  150. // Disconnect from the server, if we need to, try again with a delay.
  151. function disconnect() {
  152. if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; }
  153. if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(0); }
  154. if (obj.ws != null) { obj.ws.close(); obj.ws = null; }
  155. if (obj.retryTimer != null) { clearTimeout(obj.retryTimer); obj.retryTimer = null; }
  156. // Re-try connection
  157. if (obj.connectionState >= 1) { obj.connectionState = 1; if (obj.retryTimer == null) { obj.retryTimer = setTimeout(connect, getConnectRetryTime()); } }
  158. }
  159. // Get the next retry time in milliseconds
  160. function getConnectRetryTime() {
  161. // The (random & 0x1FFF) creates a random number between 0 and 4096.
  162. if (obj.retryBackoff < 30000) { obj.retryBackoff += ((require('crypto').randomBytes(4).readUInt32BE(0) & 0x1FFF) + 1000); }
  163. return obj.retryBackoff;
  164. }
  165. // Send a JSON message to the peer server
  166. obj.send = function (msg) {
  167. try {
  168. if (obj.ws == null || obj.connectionState != 15) { return; }
  169. if (typeof msg == 'string') { obj.ws.send(msg); return; }
  170. if (typeof msg == 'object') { obj.ws.send(JSON.stringify(msg)); return; }
  171. } catch (ex) { }
  172. };
  173. // Process incoming peer server JSON data
  174. function processServerData(msg) {
  175. var str = msg.toString('utf8'), command = null;
  176. if (str[0] == '{') {
  177. try { command = JSON.parse(str); } catch (e) { obj.parent.parent.debug('peer', 'Unable to parse server JSON (' + obj.remoteaddr + ').'); return; } // If the command can't be parsed, ignore it.
  178. if (command.action == 'info') {
  179. if (obj.authenticated != 3) {
  180. // We get the peer's serverid and database identifier.
  181. if ((command.serverid != null) && (command.dbid != null)) {
  182. if (command.serverid == obj.parent.serverid) { console.log('ERROR: Same server ID, trying to peer with self. (' + obj.url + ', ' + command.serverid + ').'); return; }
  183. if (command.dbid != obj.parent.parent.db.identifier) { console.log('ERROR: Database ID mismatch. Trying to peer to a server with the wrong database. (' + obj.url + ', ' + command.serverid + ').'); return; }
  184. if (obj.url.toLowerCase().startsWith('wss://') && (obj.serverCertHash != command.serverCertHash)) { console.log('ERROR: Outer certificate hash mismatch (2). (' + obj.url + ', ' + command.serverid + ').'); return; }
  185. obj.peerServerId = command.serverid;
  186. obj.peerServerKey = Buffer.from(command.key, 'hex');
  187. obj.authenticated = 3;
  188. obj.parent.SetupPeerServer(obj, obj.peerServerId);
  189. }
  190. }
  191. } else if (obj.authenticated == 3) {
  192. // Pass the message to the parent object for processing.
  193. obj.parent.ProcessPeerServerMessage(obj, obj.peerServerId, command);
  194. }
  195. }
  196. }
  197. connect();
  198. return obj;
  199. };
  200. // Create a mesh server module that received a connection to another server
  201. obj.CreatePeerInServer = function (parent, ws, req, tls) {
  202. var obj = {};
  203. obj.ws = ws;
  204. obj.tls = tls;
  205. obj.parent = parent;
  206. obj.common = require('./common.js');
  207. obj.forge = require('node-forge');
  208. obj.crypto = require('crypto');
  209. obj.authenticated = 0;
  210. obj.remoteaddr = obj.ws._socket.remoteAddress;
  211. obj.receivedCommands = 0;
  212. obj.webCertificateHash = obj.parent.parent.webserver.webCertificateHash;
  213. obj.agentCertificateHashBase64 = obj.parent.parent.webserver.agentCertificateHashBase64;
  214. obj.agentCertificateAsn1 = obj.parent.parent.webserver.agentCertificateAsn1;
  215. obj.infoSent = 0;
  216. obj.peerServerId = null;
  217. obj.serverCertHash = null;
  218. obj.pendingData = [];
  219. if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
  220. obj.parent.parent.debug('peer', 'InPeer: Connected (' + obj.remoteaddr + ')');
  221. // Send a message to the peer server
  222. obj.send = function (msg) {
  223. try {
  224. if (typeof msg == 'string') { obj.ws.send(msg); return; }
  225. if (typeof msg == 'object') { obj.ws.send(JSON.stringify(msg)); return; }
  226. } catch (ex) { }
  227. };
  228. // Disconnect this server
  229. obj.close = function (arg) {
  230. if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug('peer', 'InPeer: Soft disconnect ' + obj.peerServerId + ' (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket
  231. if (arg == 2) { try { obj.ws._socket._parent.end(); obj.parent.parent.debug('peer', 'InPeer: Hard disconnect ' + obj.peerServerId + ' (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
  232. if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; }
  233. };
  234. // When data is received from the peer server web socket
  235. ws.on('message', function (msg) {
  236. if (typeof msg != 'string') { msg = msg.toString('binary'); }
  237. if (msg.length < 2) return;
  238. if (msg.charCodeAt(0) == 123) {
  239. if (msg.length < 2) return;
  240. if (obj.authenticated >= 2) { processServerData(msg); } else { obj.pendingData.push(msg); }
  241. } else if (obj.authenticated < 2) { // We are not authenticated
  242. var cmd = obj.common.ReadShort(msg, 0);
  243. if (cmd == 1) {
  244. // Peer server authentication request
  245. if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
  246. obj.receivedCommands += 1; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
  247. // Check that the server hash matches out own web certificate hash
  248. if ((obj.tls == true) && (obj.webCertificateHash != msg.substring(2, 50))) { obj.close(); return; }
  249. obj.peernonce = msg.substring(50);
  250. // Perform the hash signature using the server agent certificate
  251. obj.parent.parent.certificateOperations.acceleratorPerformSignature(0, msg.substring(2) + obj.nonce, null, function (tag, signature) {
  252. // Send back our certificate + signature
  253. obj.ws.send(Buffer.from(obj.common.ShortToStr(2) + obj.common.ShortToStr(obj.agentCertificateAsn1.length) + obj.agentCertificateAsn1 + signature, 'binary')); // Command 2, certificate + signature
  254. });
  255. // Check the peer server signature if we can
  256. if (obj.unauthsign != null) {
  257. if (processPeerSignature(obj.unauthsign) == false) { obj.close(); return; } else { completePeerServerConnection(); }
  258. }
  259. }
  260. else if (cmd == 2) {
  261. // Peer server certificate
  262. if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) { obj.parent.parent.debug('peer', 'InPeer: Invalid command 2.'); return; }
  263. obj.receivedCommands += 2; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
  264. // Decode the certificate
  265. var certlen = obj.common.ReadShort(msg, 2);
  266. obj.unauth = {};
  267. try { obj.unauth.nodeid = Buffer.from(obj.forge.pki.getPublicKeyFingerprint(obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))).publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } catch (e) { console.log(e); return; }
  268. obj.unauth.nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----';
  269. // Check the peer server signature if we can
  270. if (obj.peernonce == null) {
  271. obj.unauthsign = msg.substring(4 + certlen);
  272. } else {
  273. if (processPeerSignature(msg.substring(4 + certlen)) == false) { obj.parent.parent.debug('peer', 'InPeer: Invalid signature.'); obj.close(); return; }
  274. }
  275. completePeerServerConnection();
  276. }
  277. else if (cmd == 3) {
  278. if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) { obj.parent.parent.debug('peer', 'InPeer: Invalid command 3.'); return; }
  279. obj.receivedCommands += 4; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
  280. completePeerServerConnection();
  281. }
  282. }
  283. });
  284. // If error, do nothing
  285. ws.on('error', function (err) { obj.parent.parent.debug('peer', 'InPeer: Connection Error: ' + err); });
  286. // If the peer server web socket is closed, clean up.
  287. ws.on('close', function (req) { obj.parent.parent.debug('peer', 'InPeer disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); obj.close(0); });
  288. // obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug('peer', 'Peer server TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); });
  289. // Start authenticate the peer server by sending a auth nonce & server TLS cert hash.
  290. // Send 384 bits SHA382 hash of TLS cert public key + 384 bits nonce
  291. obj.nonce = obj.crypto.randomBytes(48).toString('binary');
  292. obj.ws.send(Buffer.from(obj.common.ShortToStr(1) + obj.webCertificateHash + obj.nonce, 'binary')); // Command 1, hash + nonce
  293. // Once we get all the information about an peer server, run this to hook everything up to the server
  294. function completePeerServerConnection() {
  295. if (obj.authenticated != 1) return;
  296. obj.ws.send(Buffer.from(obj.common.ShortToStr(4), 'binary'));
  297. obj.send({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 });
  298. obj.authenticated = 2;
  299. // Process any pending data that was received before peer authentication
  300. for (var i in obj.pendingData) { processServerData(obj.pendingData[i]); }
  301. obj.pendingData = null;
  302. }
  303. // Verify the peer server signature
  304. function processPeerSignature(msg) {
  305. // Verify the signature. This is the fast way, without using forge.
  306. const verify = obj.parent.crypto.createVerify('SHA384');
  307. verify.end(Buffer.from(obj.parent.parent.webserver.webCertificateHash + obj.nonce + obj.peernonce, 'binary'));
  308. if (verify.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) { console.log('Peer sign fail 1'); return false; }
  309. if (obj.unauth.nodeid !== obj.agentCertificateHashBase64) { console.log('Peer sign fail 2'); return false; }
  310. // Connection is a success, clean up
  311. obj.nodeid = obj.unauth.nodeid;
  312. delete obj.nonce;
  313. delete obj.peernonce;
  314. delete obj.unauth;
  315. if (obj.unauthsign) delete obj.unauthsign;
  316. obj.authenticated = 1;
  317. return true;
  318. }
  319. // Process incoming peer server JSON data
  320. function processServerData(msg) {
  321. var str = msg.toString('utf8'), command = null;
  322. if (str[0] == '{') {
  323. try { command = JSON.parse(str); } catch (e) { obj.parent.parent.debug('peer', 'Unable to parse server JSON (' + obj.remoteaddr + ').'); return; } // If the command can't be parsed, ignore it.
  324. if (command.action == 'info') {
  325. if (obj.authenticated != 3) {
  326. // We get the peer's serverid and database identifier.
  327. if ((command.serverid != null) && (command.dbid != null)) {
  328. if (command.serverid == obj.parent.serverid) { console.log('ERROR: Same server ID, trying to peer with self. (' + obj.remoteaddr + ', ' + command.serverid + ').'); return; }
  329. if (command.dbid != obj.parent.parent.db.identifier) { console.log('ERROR: Database ID mismatch. Trying to peer to a server with the wrong database. (' + obj.remoteaddr + ', ' + command.serverid + ').'); return; }
  330. if (obj.parent.peerConfig.servers[command.serverid] == null) { console.log('ERROR: Unknown peer serverid: ' + command.serverid + ' (' + obj.remoteaddr + ').'); return; }
  331. obj.peerServerId = command.serverid;
  332. obj.peerServerKey = Buffer.from(command.key, 'hex');
  333. obj.serverCertHash = command.serverCertHash;
  334. obj.authenticated = 3;
  335. obj.parent.SetupPeerServer(obj, obj.peerServerId);
  336. }
  337. }
  338. } else if (obj.authenticated == 3) {
  339. // Pass the message to the parent object for processing.
  340. obj.parent.ProcessPeerServerMessage(obj, obj.peerServerId, command);
  341. }
  342. }
  343. }
  344. return obj;
  345. };
  346. // If we have no peering configuration, don't setup this object
  347. if (obj.peerConfig == null) { return null; }
  348. obj.serverid = obj.parent.config.peers.serverid;
  349. if (obj.serverid == null) { obj.serverid = require("os").hostname().toLowerCase(); } else { obj.serverid = obj.serverid.toLowerCase(); }
  350. if (args.serverid != null) { obj.serverid = args.serverid.toLowerCase(); }
  351. if (obj.parent.config.peers.servers[obj.serverid] == null) { console.log("Error: Unable to peer with other servers, \"" + obj.serverid + "\" not present in peer servers list."); return null; }
  352. //console.log('Server peering ID: ' + obj.serverid);
  353. // Return the private key of a peer server
  354. obj.getServerCookieKey = function (serverid) {
  355. var server = obj.peerServers[serverid];
  356. if (server && server.peerServerKey) return server.peerServerKey;
  357. return null;
  358. };
  359. // Dispatch an event to all other MeshCentral2 peer servers
  360. obj.DispatchEvent = function (ids, source, event) {
  361. for (var serverid in obj.peerServers) { obj.peerServers[serverid].send({ action: 'bus', ids: ids, event: event }); }
  362. };
  363. // Dispatch a message to other MeshCentral2 peer servers
  364. obj.DispatchMessage = function (msg) {
  365. for (var serverid in obj.peerServers) { obj.peerServers[serverid].send(msg); }
  366. };
  367. // Dispatch a message to other MeshCentral2 peer servers
  368. obj.DispatchMessageSingleServer = function (msg, serverid) {
  369. var server = obj.peerServers[serverid];
  370. if (server != null) { server.send(msg); }
  371. };
  372. // Attempt to connect to all peers
  373. obj.ConnectToPeers = function () {
  374. for (var serverId in obj.peerConfig.servers) {
  375. // We will only connect to names that are larger then ours. This way, eveyone has one connection to everyone else (no cross-connections).
  376. if ((serverId > obj.serverid) && (obj.peerConfig.servers[serverId].url != null) && (obj.outPeerServers[serverId] == null)) {
  377. obj.outPeerServers[serverId] = obj.CreatePeerOutServer(obj, serverId, obj.peerConfig.servers[serverId].url);
  378. }
  379. }
  380. };
  381. // We connected to a peer server, setup everything
  382. obj.SetupPeerServer = function (server, peerServerId) {
  383. obj.parent.debug('peer', 'Connected to peer server ' + peerServerId + '.');
  384. //console.log('Connected to peer server ' + peerServerId + '.');
  385. obj.peerServers[peerServerId] = server;
  386. // Send the list of connections to the peer
  387. server.send({ action: 'connectivityTable', connectivityTable: obj.parent.peerConnectivityByNode[obj.parent.serverId] });
  388. // Send a list of user sessions to the peer
  389. server.send({ action: 'sessionsTable', sessionsTable: Object.keys(obj.parent.webserver.wssessions2) });
  390. };
  391. // We disconnected to a peer server, clean up everything
  392. obj.ClearPeerServer = function (server, peerServerId) {
  393. obj.parent.debug('peer', 'Disconnected from peer server ' + peerServerId + '.');
  394. //console.log('Disconnected from peer server ' + peerServerId + '.');
  395. // Clean up the connectivity state
  396. delete obj.peerServers[peerServerId];
  397. var oldList = obj.parent.peerConnectivityByNode[peerServerId];
  398. obj.parent.peerConnectivityByNode[peerServerId] = {};
  399. obj.parent.UpdateConnectivityState(oldList);
  400. // Clean up the sessions list
  401. for (var i in obj.parent.webserver.wsPeerSessions[peerServerId]) { delete obj.parent.webserver.wsPeerSessions2[obj.parent.webserver.wsPeerSessions[peerServerId][i]]; }
  402. delete obj.parent.webserver.wsPeerSessions[peerServerId];
  403. delete obj.parent.webserver.wsPeerSessions3[peerServerId];
  404. obj.parent.webserver.recountSessions(); // Recount all sessions
  405. };
  406. // Process a message coming from a peer server
  407. obj.ProcessPeerServerMessage = function (server, peerServerId, msg) {
  408. var userid, i;
  409. //console.log('ProcessPeerServerMessage', peerServerId, msg.action);
  410. switch (msg.action) {
  411. case 'mqtt': {
  412. if ((obj.parent.mqttbroker != null) && (msg.nodeid != null)) { obj.parent.mqttbroker.publishNoPeers(msg.nodeid, msg.topic, msg.message); } // Dispatch in the MQTT broker
  413. break;
  414. }
  415. case 'bus': {
  416. obj.parent.DispatchEvent(msg.ids, null, msg.event, true); // Dispatch the peer event
  417. break;
  418. }
  419. case 'connectivityTable': {
  420. obj.parent.peerConnectivityByNode[peerServerId] = msg.connectivityTable;
  421. obj.parent.UpdateConnectivityState(msg.connectivityTable);
  422. break;
  423. }
  424. case 'sessionsTable': {
  425. obj.parent.webserver.wsPeerSessions[peerServerId] = msg.sessionsTable;
  426. var userToSession = {};
  427. for (i in msg.sessionsTable) {
  428. var sessionid = msg.sessionsTable[i];
  429. obj.parent.webserver.wsPeerSessions2[sessionid] = peerServerId;
  430. userid = sessionid.split('/').slice(0, 3).join('/'); // Take the sessionid and keep only the userid partion
  431. if (userToSession[userid] == null) { userToSession[userid] = [sessionid]; } else { userToSession[userid].push(sessionid); } // UserId -> [ SessionId ]
  432. }
  433. obj.parent.webserver.wsPeerSessions3[peerServerId] = userToSession; // ServerId --> UserId --> SessionId
  434. obj.parent.webserver.recountSessions(); // Recount all sessions
  435. break;
  436. }
  437. case 'sessionStart': {
  438. obj.parent.webserver.wsPeerSessions[peerServerId].push(msg.sessionid);
  439. obj.parent.webserver.wsPeerSessions2[msg.sessionid] = peerServerId;
  440. userid = msg.sessionid.split('/').slice(0, 3).join('/');
  441. if (obj.parent.webserver.wsPeerSessions3[peerServerId] == null) { obj.parent.webserver.wsPeerSessions3[peerServerId] = {}; }
  442. if (obj.parent.webserver.wsPeerSessions3[peerServerId][userid] == null) { obj.parent.webserver.wsPeerSessions3[peerServerId][userid] = [msg.sessionid]; } else { obj.parent.webserver.wsPeerSessions3[peerServerId][userid].push(msg.sessionid); }
  443. obj.parent.webserver.recountSessions(msg.sessionid); // Recount a specific user
  444. break;
  445. }
  446. case 'sessionEnd': {
  447. i = obj.parent.webserver.wsPeerSessions[peerServerId].indexOf(msg.sessionid);
  448. if (i >= 0) { obj.parent.webserver.wsPeerSessions[peerServerId].splice(i, 1); }
  449. delete obj.parent.webserver.wsPeerSessions2[msg.sessionid];
  450. userid = msg.sessionid.split('/').slice(0, 3).join('/');
  451. if (obj.parent.webserver.wsPeerSessions3[peerServerId][userid] != null) {
  452. i = obj.parent.webserver.wsPeerSessions3[peerServerId][userid].indexOf(msg.sessionid);
  453. if (i >= 0) {
  454. obj.parent.webserver.wsPeerSessions3[peerServerId][userid].splice(i, 1);
  455. if (obj.parent.webserver.wsPeerSessions3[peerServerId][userid].length == 0) { delete obj.parent.webserver.wsPeerSessions3[peerServerId][userid]; }
  456. }
  457. }
  458. obj.parent.webserver.recountSessions(msg.sessionid); // Recount a specific user
  459. break;
  460. }
  461. case 'SetConnectivityState': {
  462. obj.parent.SetConnectivityState(msg.meshid, msg.nodeid, msg.connectTime, msg.connectType, msg.powerState, peerServerId, msg.extraInfo);
  463. break;
  464. }
  465. case 'ClearConnectivityState': {
  466. obj.parent.ClearConnectivityState(msg.meshid, msg.nodeid, msg.connectType, peerServerId, msg.extraInfo);
  467. break;
  468. }
  469. case 'relay': {
  470. // Check if there is a waiting session
  471. var rsession = obj.parent.webserver.wsrelays[msg.id];
  472. if (rsession != null) {
  473. // Yes, there is a waiting session, see if we must initiate.
  474. if (peerServerId > obj.parent.serverId) {
  475. // We must initiate the connection to the peer
  476. userid = null;
  477. if (rsession.peer1.user != null) { userid = rsession.peer1.user._id; }
  478. obj.createPeerRelay(rsession.peer1.ws, rsession.peer1.req, peerServerId, userid);
  479. delete obj.parent.webserver.wsrelays[msg.id];
  480. }
  481. } else {
  482. // Add this relay session to the peer relay list
  483. obj.parent.webserver.wsPeerRelays[msg.id] = { serverId: peerServerId, time: Date.now() };
  484. // Clear all relay sessions that are more than 1 minute
  485. var oneMinuteAgo = Date.now() - 60000;
  486. for (i in obj.parent.webserver.wsPeerRelays) { if (obj.parent.webserver.wsPeerRelays[i].time < oneMinuteAgo) { delete obj.parent.webserver.wsPeerRelays[i]; } }
  487. }
  488. break;
  489. }
  490. case 'msg': {
  491. if (msg.sessionid != null) {
  492. // Route this message to a connected user session
  493. if (msg.fromNodeid != null) { msg.nodeid = msg.fromNodeid; delete msg.fromNodeid; }
  494. var ws = obj.parent.webserver.wssessions2[msg.sessionid];
  495. if (ws != null) { ws.send(JSON.stringify(msg)); }
  496. } else if (msg.nodeid != null) {
  497. // Route this message to a connected agent
  498. if (msg.fromSessionid != null) { msg.sessionid = msg.fromSessionid; delete msg.fromSessionid; }
  499. var agent = obj.parent.webserver.wsagents[msg.nodeid];
  500. if (agent != null) { delete msg.nodeid; agent.send(JSON.stringify(msg)); } // Remove the nodeid since it's implyed and send the message to the agent
  501. } else if (msg.meshid != null) {
  502. // Route this message to all users of this mesh
  503. if (msg.fromNodeid != null) { msg.nodeid = msg.fromNodeid; delete msg.fromNodeid; }
  504. var cmdstr = JSON.stringify(msg);
  505. for (userid in obj.parent.webserver.wssessions) { // Find all connected users for this mesh and send the message
  506. if (parent.webserver.GetMeshRights(userid, msg.meshid) != 0) { // TODO: Look at what rights are needed for message routing
  507. var sessions = obj.parent.webserver.wssessions[userid];
  508. // Send the message to all users on this server
  509. for (i in sessions) { sessions[i].send(cmdstr); }
  510. }
  511. }
  512. }
  513. break;
  514. }
  515. case 'newIntelAmtPolicy': {
  516. // See if any agents for the affected device group is connected, if so, update the Intel AMT policy
  517. for (var nodeid in obj.parent.webserver.wsagents) {
  518. const agent = obj.parent.webserver.wsagents[nodeid];
  519. if (agent.dbMeshKey == msg.meshid) { agent.sendUpdatedIntelAmtPolicy(msg.amtpolicy); }
  520. }
  521. break;
  522. }
  523. case 'agentMsgByMeshId': {
  524. // See if any agents for the target device group is connected, if so, send the message
  525. const jsonCmd = JSON.stringify(msg.command);
  526. for (var nodeid in obj.parent.webserver.wsagents) {
  527. var agent = obj.parent.webserver.wsagents[nodeid];
  528. if (agent.dbMeshKey == msg.meshid) { try { agent.send(jsonCmd); } catch (ex) { } }
  529. }
  530. break;
  531. }
  532. case 'agentCommand': {
  533. if (msg.nodeid != null) {
  534. // Route this message to a connected agent
  535. var agent = obj.parent.webserver.wsagents[msg.nodeid];
  536. if (agent != null) { agent.send(JSON.stringify(msg.command)); }
  537. } else if (msg.meshid != null) {
  538. // Route this message to all connected agents of this mesh
  539. for (var nodeid in obj.parent.webserver.wsagents) {
  540. var agent = obj.parent.webserver.wsagents[nodeid];
  541. if (agent.dbMeshKey == msg.meshid) { try { agent.send(JSON.stringify(msg.command)); } catch (ex) { } }
  542. }
  543. }
  544. break;
  545. }
  546. default: {
  547. // Unknown peer server command
  548. console.log('Unknown action from peer server ' + peerServerId + ': ' + msg.action + '.');
  549. break;
  550. }
  551. }
  552. };
  553. // Create a tunnel connection to a peer server
  554. obj.createPeerRelay = function (ws, req, serverid, user) {
  555. var server = obj.peerServers[serverid];
  556. if ((server == null) || (server.peerServerKey == null)) { return null; }
  557. var cookieKey = server.peerServerKey;
  558. // Parse the user if needed
  559. if (typeof user == 'string') { user = { _id: user, domain: user.split('/')[1] }; }
  560. // Build the connection URL
  561. var path = req.path;
  562. if (path[0] == '/') path = path.substring(1);
  563. if (path.substring(path.length - 11) == '/.websocket') { path = path.substring(0, path.length - 11); }
  564. var queryStr = '';
  565. for (var i in req.query) { if (i.toLowerCase() != 'auth') { queryStr += ((queryStr == '') ? '?' : '&') + i + '=' + req.query[i]; } }
  566. if (user != null) { queryStr += ((queryStr == '') ? '?' : '&') + 'auth=' + obj.parent.encodeCookie({ userid: user._id, domainid: user.domain, ps: 1 }, cookieKey); }
  567. var url = obj.peerConfig.servers[serverid].url + path + queryStr;
  568. // Setup an connect the web socket
  569. var tunnel = obj.createPeerRelayEx(ws, url, serverid);
  570. tunnel.connect();
  571. };
  572. // Create a tunnel connection to a peer server
  573. // We assume that "ws" is paused already.
  574. obj.createPeerRelayEx = function (ws, url, serverid) {
  575. var peerTunnel = { parent: obj, ws1: ws, ws2: null, url: url, serverid: serverid };
  576. peerTunnel.connect = function () {
  577. // Get the web socket setup
  578. peerTunnel.parent.parent.debug('peer', 'FTunnel ' + peerTunnel.serverid + ': Start connect to ' + peerTunnel.url);
  579. peerTunnel.ws2 = new WebSocket(peerTunnel.url, { rejectUnauthorized: false, servername: this.parent.parent.certificates.CommonName, cert: this.parent.parent.certificates.agent.cert, key: this.parent.parent.certificates.agent.key });
  580. // Register the connection failed event
  581. peerTunnel.ws2.on('error', function (error) { peerTunnel.parent.parent.debug('peer', 'FTunnel ' + obj.serverid + ': Connection error'); peerTunnel.close(); });
  582. // If the peer server web socket is closed, clean up.
  583. peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug('peer', 'FTunnel disconnect ' + peerTunnel.serverid); peerTunnel.close(); });
  584. // If a message is received from the peer, Peer ---> Browser (TODO: Pipe this?)
  585. peerTunnel.ws2.on('message', function (msg, isBinary) { try { peerTunnel.ws2._socket.pause(); peerTunnel.ws1.send((isBinary ? msg : msg.toString('binary')), function () { peerTunnel.ws2._socket.resume(); }); } catch (e) { } });
  586. // Register the connection event
  587. peerTunnel.ws2.on('open', function () {
  588. peerTunnel.parent.parent.debug('peer', 'FTunnel ' + peerTunnel.serverid + ': Connected');
  589. if (peerTunnel.ws2._socket.getPeerCertificate != null) {
  590. // Get the peer server's certificate and compute the server public key hash
  591. var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(peerTunnel.ws2._socket.getPeerCertificate().raw.toString('binary')));
  592. var serverCertHashHex = Buffer.from(obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  593. // Check if the peer certificate is the expected one for this serverid
  594. if ((obj.peerServers[serverid] == null) || (obj.peerServers[serverid].serverCertHash != serverCertHashHex)) { console.log('ERROR: Outer certificate hash mismatch (1). (' + peerTunnel.url + ', ' + peerTunnel.serverid + ').'); peerTunnel.close(); return; }
  595. }
  596. // Connection accepted, resume the web socket to start the data flow
  597. peerTunnel.ws1._socket.resume();
  598. });
  599. // If a message is received from the browser, Browser ---> Peer
  600. peerTunnel.ws1.on('message', function (msg) { try { peerTunnel.ws1._socket.pause(); peerTunnel.ws2.send(msg, function () { peerTunnel.ws1._socket.resume(); }); } catch (e) { } });
  601. // If error, do nothing
  602. peerTunnel.ws1.on('error', function (err) { peerTunnel.close(); });
  603. // If the web socket is closed, close the associated TCP connection.
  604. peerTunnel.ws1.on('close', function (req) { peerTunnel.parent.parent.debug('peer', 'FTunnel disconnect ' + peerTunnel.serverid); peerTunnel.close(); });
  605. };
  606. // Disconnect both sides of the tunnel
  607. peerTunnel.close = function (arg) {
  608. if (arg == 2) {
  609. // Hard close, close the TCP socket
  610. if (peerTunnel.ws1 != null) { try { peerTunnel.ws1._socket._parent.end(); peerTunnel.parent.parent.debug('peer', 'FTunnel1: Hard disconnect'); } catch (e) { console.log(e); } delete peerTunnel.ws1; }
  611. if (peerTunnel.ws2 != null) { try { peerTunnel.ws2._socket._parent.end(); peerTunnel.parent.parent.debug('peer', 'FTunnel2: Hard disconnect'); } catch (e) { console.log(e); } delete peerTunnel.ws2; }
  612. } else {
  613. // Soft close, close the websocket
  614. if (peerTunnel.ws1 != null) { try { peerTunnel.ws1.close(); peerTunnel.parent.parent.debug('peer', 'FTunnel1: Soft disconnect '); } catch (e) { console.log(e); } delete peerTunnel.ws1; }
  615. if (peerTunnel.ws2 != null) { try { peerTunnel.ws2.close(); peerTunnel.parent.parent.debug('peer', 'FTunnel2: Soft disconnect '); } catch (e) { console.log(e); } delete peerTunnel.ws2; }
  616. }
  617. };
  618. return peerTunnel;
  619. };
  620. setTimeout(function () { obj.ConnectToPeers(); }, 1000); // Delay this a little to make sure we are ready on our side.
  621. return obj;
  622. };