| 1 | /** |
| 2 | * @description MeshCentral v1 legacy Swarm Server, used to update agents and get them on MeshCentral2 |
| 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 legacy Swarm Server server object |
| 17 | module.exports.CreateSwarmServer = function (parent, db, args, certificates) { |
| 18 | var obj = {}; |
| 19 | obj.parent = parent; |
| 20 | obj.db = db; |
| 21 | obj.args = args; |
| 22 | obj.certificates = certificates; |
| 23 | obj.legacyAgentConnections = {}; |
| 24 | obj.migrationAgents = {}; |
| 25 | obj.agentActionCount = {}; |
| 26 | obj.stats = { blockedConnect: 0, connectCount: 0, clientCertConnectCount: 0, noCertConnectCount: 0, bytesIn: 0, bytesOut: 0, httpGetRequest: 0, pushedAgents: {}, close: 0, onclose: 0, agentType: {} } |
| 27 | const tls = require('tls'); |
| 28 | const forge = require('node-forge'); |
| 29 | const common = require('./common.js'); |
| 30 | |
| 31 | const LegacyMeshProtocol = { |
| 32 | NODEPUSH: 1, // Used to send a node block to another peer. |
| 33 | NODEPULL: 2, // Used to send a pull block to another peer. |
| 34 | NODENOTIFY: 3, // Used to indicate the node ID to other peers. |
| 35 | NODECHALLENGE: 4, // Used to challenge a node identity. |
| 36 | NODECRESPONSE: 5, // Used to respond to a node challenge. |
| 37 | TARGETSTATUS: 6, // Used to send the peer connection status list. |
| 38 | LOCALEVENT: 7, // Used to send local events to subscribers. |
| 39 | AESCRYPTO: 8, // Used to send an encrypted block of data. |
| 40 | SESSIONKEY: 9, // Used to send a session key to a remote node. |
| 41 | SYNCSTART: 10, // Used to send kick off the SYNC request, send the start NodeID. |
| 42 | SYNCMETADATA: 11, // Used to send a sequence of NodeID & serial numbers. |
| 43 | SYNCREQUEST: 12, // Used to send a sequence of NodeID's to request. |
| 44 | NODEID: 13, // Used to send the NodeID in the clear. Used for multicast. |
| 45 | AGENTID: 14, // Used to send the AgentID & version to the other node. |
| 46 | PING: 15, // Used to query a target for the presence of the mesh agent (PB_NODEID response expected). |
| 47 | SETUPADMIN: 16, // Used to set the trusted mesh identifier, this code can only be used from local settings file. |
| 48 | POLICY: 17, // Used to send a policy block to another peer. |
| 49 | POLICYSECRET: 18, // Used to encode the PKCS12 private key of a policy block. |
| 50 | EVENTMASK: 19, // Used by the mesh service to change the event mask. |
| 51 | RECONNECT: 20, // Used by the mesh service to indicate disconnect & reconnection after n seconds. |
| 52 | GETSTATE: 21, // Used by the mesh service to obtain agent state. |
| 53 | CERTENCRYPTED: 22, // Used to send a certificate encrypted message to a node. |
| 54 | GETCOOKIE: 23, // Used to request a certificate encryption anti-replay cookie. |
| 55 | COOKIE: 24, // Used to carry an anti-replay cookie to a requestor. |
| 56 | SESSIONCKEY: 25, // Used to send a session key to a remote console. |
| 57 | INTERFACE: 26, // Used to send a local interface blob to a management console. |
| 58 | MULTICAST: 27, // Used by the mesh service to cause the agent to send a multicast. |
| 59 | SELFEXE: 28, // Used to transfer our own agent executable. |
| 60 | LEADERBADGE: 29, // User to send a leadership badge. |
| 61 | NODEINFO: 30, // Used to indicate a block information update to the web service. |
| 62 | TARGETEVENT: 31, // Used to send a single target update event. |
| 63 | DEBUG: 33, // Used to send debug information to web service. |
| 64 | TCPRELAY: 34, // Used to operate mesh leader TCP relay sockets |
| 65 | CERTSIGNED: 35, // Used to send a certificate signed message to a node. |
| 66 | ERRORCODE: 36, // Used to notify of an error. |
| 67 | MESSAGE: 37, // Used to route messages between nodes. |
| 68 | CMESSAGE: 38, // Used to embed a interface identifier along with a PB_MESSAGE. |
| 69 | EMESSAGE: 39, // Used to embed a target encryption certificate along with a MESSAGE or CMESSAGE. |
| 70 | SEARCH: 40, // Used to send a custom search to one or more remote nodes. |
| 71 | MESSAGERELAY: 41, // Used by no-certificate consoles to send hopping messages to nodes. |
| 72 | USERINPUT: 42, // Used to send user keyboard input to a target computer |
| 73 | APPID: 43, // Used to send a block of data to a specific application identifier. |
| 74 | APPSUBSCRIBE: 44, // Used to perform local app subscription to an agent. |
| 75 | APPDIRECT: 45, // Used to send message directly to remote applications. |
| 76 | APPREQACK: 46, // Used to request an ack message. |
| 77 | APPACK: 47, // Used to ack a received message. |
| 78 | SERVERECHO: 48, // Server will echo this message, used for testing. |
| 79 | KVMINFO: 49, // Used to send local KVM secondary process information to mesh agent. |
| 80 | REMOTEWAKE: 50, // Used to send remote wake information to server. |
| 81 | NEWCONNECTTOKEN: 51, // Used to send a new connection token to the Swarm Server. |
| 82 | WIFISCAN: 52, // Used to send visible WIFI AP's to the server. |
| 83 | AMTPROVISIONING: 53, // Used by the agent to send Intel AMT provisioning information to the server. |
| 84 | ANDROIDCOMMAND: 54, // Send a Android OS specific command (Android only). |
| 85 | NODEAPPDATA: 55, // Used to send application specific data block to the server for storage. |
| 86 | PROXY: 56, // Used to indicate the currently used proxy setting string. |
| 87 | FILEOPERATION: 57, // Used to perform short file operations. |
| 88 | APPSUBSCRIBERS: 58, // Used request and send to the mesh server the list of subscribed applications |
| 89 | CUSTOM: 100, // Message containing application specific data. |
| 90 | USERAUTH: 1000, // Authenticate a user to the swarm server. |
| 91 | USERMESH: 1001, // Request or return the mesh list for this console. |
| 92 | USERMESHS: 1002, // Send mesh overview information to the console. |
| 93 | USERNODES: 1003, // Send node overview information to the console. |
| 94 | JUSERMESHS: 1004, // Send mesh overview information to the console in JSON format. |
| 95 | JUSERNODES: 1005, // Send node overview information to the console in JSON format. |
| 96 | USERPOWERSTATE: 1006, // Used to send a power command from the console to the server. |
| 97 | JMESHPOWERTIMELINE: 1007, // Send the power timeline for all nodes in a mesh. |
| 98 | JMESHPOWERSUMMARY: 1008, // Send the power summary for sum of all nodes in a mesh. |
| 99 | USERCOMMAND: 1009, // Send a user admin text command to and from the server. |
| 100 | POWERBLOCK: 1010, // Request/Response of block of power state information. |
| 101 | MESHACCESSCHANGE: 1011, // Notify a console of a change in accessible meshes. |
| 102 | COOKIEAUTH: 1012, // Authenticate a user using a crypto cookie. |
| 103 | NODESTATECHANGE: 1013, // Indicates a node has changed power state. |
| 104 | JUSERNODE: 1014, // Send node overview information to the console in JSON format. |
| 105 | AMTWSMANEVENT: 1015, // Intel AMT WSMAN event sent to consoles. |
| 106 | ROUTINGCOOKIE: 1016, // Used by a console to request a routing cookie. |
| 107 | JCOLLABORATION: 1017, // Request/send back JSON collaboration state. |
| 108 | JRELATIONS: 1018, // Request/send back JSON relations state. |
| 109 | SETCOLLABSTATE: 1019, // Set the collaboration state for this session. |
| 110 | ADDRELATION: 1020, // Request that a new relation be added. |
| 111 | DELETERELATION: 1021, // Request a relation be deleted. |
| 112 | ACCEPTRELATION: 1022, // Request relation invitation be accepted. |
| 113 | RELATIONCHANGEEVENT: 1023, // Notify that a relation has changed. |
| 114 | COLLBCHANGEEVENT: 1024, // Notify that a collaboration state has change. |
| 115 | MULTICONSOLEMESSAGE: 1025, // Send a message to one or more console id's. |
| 116 | CONSOLEID: 1026, // Notify a console of it's console id. |
| 117 | CHANGERELATIONDATA: 1027, // Request that relation data be changed. |
| 118 | SETUSERDATA: 1028, // Set user data |
| 119 | GETUSERDATA: 1029, // Get user data |
| 120 | SERVERAUTH: 1030, // Used to verify the certificate of the server |
| 121 | USERAUTH2: 1031, // Authenticate a user to the swarm server (Uses SHA1 SALT) |
| 122 | GUESTREMOTEDESKTOP: 2001, // Guest usage: Remote Desktop |
| 123 | GUESTWEBRTCMESH: 2002 // Guest usage: WebRTC Mesh |
| 124 | }; |
| 125 | |
| 126 | obj.server = tls.createServer({ key: certificates.swarmserver.key, cert: certificates.swarmserver.cert, requestCert: true, rejectUnauthorized: false }, onConnection); |
| 127 | obj.server.listen(args.swarmport, function () { console.log('MeshCentral Legacy Swarm Server running on ' + certificates.CommonName + ':' + args.swarmport + '.'); obj.parent.updateServerState('swarm-port', args.swarmport); }).on('error', function (err) { console.error('ERROR: MeshCentral Swarm Server server port ' + args.swarmport + ' is not available.'); if (args.exactports) { process.exit(); } }); |
| 128 | loadMigrationAgents(); |
| 129 | |
| 130 | // Load all migration agents along with full executable in memory |
| 131 | function loadMigrationAgents() { |
| 132 | var migrationAgentsDir = null, migrationAgentsPath = obj.parent.path.join(obj.parent.datapath, 'migrationagents'); |
| 133 | try { migrationAgentsDir = obj.parent.fs.readdirSync(migrationAgentsPath); } catch (e) { } |
| 134 | if (migrationAgentsDir != null) { |
| 135 | for (var i in migrationAgentsDir) { |
| 136 | if (migrationAgentsDir[i].toLowerCase().startsWith('meshagent-')) { |
| 137 | var migrationAgentName = obj.parent.path.join(migrationAgentsPath, migrationAgentsDir[i]); |
| 138 | var agentInfo = migrationAgentsDir[i].substring(10).split('.'); |
| 139 | var agentVersion = parseInt(agentInfo[0]); |
| 140 | var agentArch = parseInt(agentInfo[1]); |
| 141 | if (obj.migrationAgents[agentArch] == null) { obj.migrationAgents[agentArch] = {}; } |
| 142 | if (obj.migrationAgents[agentArch][agentVersion] == null) { obj.migrationAgents[agentArch][agentVersion] = { arch: agentArch, ver: agentVersion, path: migrationAgentName }; } |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | function onData(data) { |
| 149 | if (this.relaySocket) { var ps = this; try { this.relaySocket.write(data, 'binary', function () { ps.resume(); }); } catch (ex) { } return; } |
| 150 | if (args.swarmdebug) { var buf = Buffer.from(data, "binary"); console.log('SWARM <-- (' + buf.length + '):' + buf.toString('hex')); } // Print out received bytes |
| 151 | obj.stats.bytesIn += data.length; |
| 152 | this.tag.accumulator += data; |
| 153 | |
| 154 | // 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. |
| 155 | if (this.tag.first == true) { |
| 156 | if (this.tag.accumulator.length < 3) return; |
| 157 | if ((this.tag.accumulator.substring(0, 3) == 'GET') || (this.tag.accumulator.substring(0, 3) == 'POS')) { |
| 158 | obj.stats.httpGetRequest++; |
| 159 | /*console.log("Swarm Connection, HTTP GET detected: " + socket.remoteAddress);*/ |
| 160 | //socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>MeshCentral2 legacy swarm server.<br />MeshCentral1 mesh agents should connect here for updates.</body></html>'); |
| 161 | //socket.end(); |
| 162 | |
| 163 | // Relay this connection to the main TLS port |
| 164 | this.pause(); |
| 165 | var relaySocket = tls.connect(obj.args.port, { rejectUnauthorized: false }, function () { this.write(this.parentSocket.tag.accumulator); this.parentSocket.resume(); }); |
| 166 | relaySocket.on('data', function (data) { try { var rs = this; this.pause(); this.parentSocket.write(data, 'binary', function () { rs.resume(); }); } catch (ex) { } }); |
| 167 | relaySocket.on('error', function (err) { try { this.parentSocket.end(); } catch (ex) { } }); |
| 168 | relaySocket.on('end', function () { try { this.parentSocket.end(); } catch (ex) { } }); |
| 169 | this.relaySocket = relaySocket; |
| 170 | relaySocket.parentSocket = this; |
| 171 | return; |
| 172 | } |
| 173 | this.tag.first = false; |
| 174 | } |
| 175 | |
| 176 | // A client certificate is required |
| 177 | if ((this.tag.clientCert == null) || (this.tag.clientCert.subject == null)) { |
| 178 | /*console.log("Swarm Connection, no client cert: " + socket.remoteAddress);*/ |
| 179 | this.write('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nMeshCentral2 legacy swarm server.\r\nNo client certificate given.'); |
| 180 | //this.end(); // If we don't close the connection, it may lead to less reconnection traffic. |
| 181 | return; |
| 182 | } |
| 183 | |
| 184 | try { |
| 185 | // Parse all of the agent binary command data we can |
| 186 | var l = 0; |
| 187 | do { l = ProcessCommand(this); if (l > 0) { this.tag.accumulator = this.tag.accumulator.substring(l); } } while (l > 0); |
| 188 | if (l < 0) { this.end(); } |
| 189 | } catch (e) { |
| 190 | console.log(e); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | // Process one AFP command |
| 195 | function ProcessCommand(socket) { |
| 196 | if (socket.tag.accumulator.length < 4) return 0; |
| 197 | var cmd = common.ReadShort(socket.tag.accumulator, 0); |
| 198 | var len = common.ReadShort(socket.tag.accumulator, 2); |
| 199 | if (len > socket.tag.accumulator.length) return 0; |
| 200 | var data = socket.tag.accumulator.substring(4, len); |
| 201 | //console.log('Swarm: Cmd=' + cmd + ', Len=' + len + '.'); |
| 202 | |
| 203 | switch (cmd) { |
| 204 | case LegacyMeshProtocol.NODEPUSH: { |
| 205 | parent.debug('swarmcmd', 'NODEPUSH'); |
| 206 | var nodeblock = obj.decodeNodeBlock(data); |
| 207 | if ((nodeblock != null) && (nodeblock.agenttype != null) && (nodeblock.agentversion != null)) { |
| 208 | if (socket.pingTimer == null) { socket.pingTimer = setInterval(function () { obj.SendCommand(socket, LegacyMeshProtocol.PING); }, 20000); } |
| 209 | parent.debug('swarmcmd', 'NODEPUSH:' + JSON.stringify(nodeblock)); |
| 210 | |
| 211 | // Log the agent type |
| 212 | if (obj.stats.agenttype[nodeblock.agenttype] == null) { obj.stats.agenttype[nodeblock.agenttype] = 1; } else { obj.stats.agenttype[nodeblock.agenttype]++; } |
| 213 | |
| 214 | // Check if this agent is asking of updates over and over again. |
| 215 | var actionCount = obj.agentActionCount[nodeblock.nodeidhex]; |
| 216 | if (actionCount == null) { actionCount = 0; } |
| 217 | if (actionCount > 2) { |
| 218 | // Already tried to update this agent two times, something is not right. |
| 219 | //console.log('SWARM: ' + actionCount + ' update actions on ' + nodeblock.nodeidhex + ', holding.'); |
| 220 | } else { |
| 221 | // Figure out what is the next agent version we need. |
| 222 | var nextAgentVersion = 0; |
| 223 | if (nodeblock.agentversion < 201) { nextAgentVersion = 201; } // If less then 201, move to transitional MC1 agent. |
| 224 | if (nodeblock.agentversion == 201) { nextAgentVersion = 202; } // If at 201, move to first MC2 agent. |
| 225 | |
| 226 | // See if we need to start the agent update |
| 227 | if ((nextAgentVersion > 0) && (obj.migrationAgents[nodeblock.agenttype] != null) && (obj.migrationAgents[nodeblock.agenttype][nextAgentVersion] != null)) { |
| 228 | // Start the update |
| 229 | socket.tag.update = obj.migrationAgents[nodeblock.agenttype][nextAgentVersion]; |
| 230 | socket.tag.updatePtr = 0; |
| 231 | //console.log('Performing legacy agent update from ' + nodeblock.agentversion + '.' + nodeblock.agenttype + ' to ' + socket.tag.update.ver + '.' + socket.tag.update.arch + ' on ' + nodeblock.agentname + '.'); |
| 232 | |
| 233 | // Update stats |
| 234 | if (obj.stats.pushedAgents[nodeblock.agenttype] == null) { obj.stats.pushedAgents[nodeblock.agenttype] = {}; } |
| 235 | if (obj.stats.pushedAgents[nodeblock.agenttype][nextAgentVersion] == null) { obj.stats.pushedAgents[nodeblock.agenttype][nextAgentVersion] = 1; } else { obj.stats.pushedAgents[nodeblock.agenttype][nextAgentVersion]++; } |
| 236 | |
| 237 | // Start the agent download using the task limiter so not to flood the server. Low priority task |
| 238 | obj.parent.taskLimiter.launch(function (socket, taskid, taskLimiterQueue) { |
| 239 | if (socket.xclosed == 1) { |
| 240 | // Socket is closed, do nothing |
| 241 | obj.parent.taskLimiter.completed(taskid); // Indicate this task complete |
| 242 | } else { |
| 243 | // Start the agent update |
| 244 | socket.tag.taskid = taskid; |
| 245 | obj.SendCommand(socket, LegacyMeshProtocol.GETSTATE, common.IntToStr(5) + common.IntToStr(0)); // agent.SendQuery(5, 0); // Start the agent download |
| 246 | } |
| 247 | }, socket, 2); |
| 248 | } else { |
| 249 | //console.log('No legacy agent update for ' + nodeblock.agentversion + '.' + nodeblock.agenttype + ' on ' + nodeblock.agentname + '.'); |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | // Mark this agent |
| 254 | obj.agentActionCount[nodeblock.nodeidhex] = ++actionCount; |
| 255 | } |
| 256 | break; |
| 257 | } |
| 258 | case LegacyMeshProtocol.AMTPROVISIONING: { |
| 259 | parent.debug('swarmcmd', 'AMTPROVISIONING'); |
| 260 | obj.SendCommand(socket, LegacyMeshProtocol.AMTPROVISIONING, common.ShortToStr(1)); |
| 261 | break; |
| 262 | } |
| 263 | case LegacyMeshProtocol.GETSTATE: { |
| 264 | parent.debug('swarmcmd', 'GETSTATE'); |
| 265 | if (len < 12) break; |
| 266 | var statecmd = common.ReadInt(data, 0); |
| 267 | //var statesync = common.ReadInt(data, 4); |
| 268 | switch (statecmd) { |
| 269 | case 6: { // Ask for agent block |
| 270 | if (socket.tag.update != null) { |
| 271 | // Send an agent block |
| 272 | if (socket.tag.update.binary == null) { socket.tag.update.binary = obj.parent.fs.readFileSync(socket.tag.update.path); } |
| 273 | var l = Math.min(socket.tag.update.binary.length - socket.tag.updatePtr, 16384); |
| 274 | obj.SendCommand(socket, LegacyMeshProtocol.GETSTATE, common.IntToStr(6) + common.IntToStr(socket.tag.updatePtr) + socket.tag.update.binary.toString('binary', socket.tag.updatePtr, socket.tag.updatePtr + l)); // agent.SendQuery(6, AgentFileLen + AgentBlock); |
| 275 | parent.debug('swarmcmd', 'Sending agent block, ptr = ' + socket.tag.updatePtr + ', len = ' + l); |
| 276 | |
| 277 | socket.tag.updatePtr += l; |
| 278 | if (socket.tag.updatePtr >= socket.tag.update.binary.length) { |
| 279 | // Send end-of-transfer |
| 280 | obj.SendCommand(socket, LegacyMeshProtocol.GETSTATE, common.IntToStr(7) + common.IntToStr(socket.tag.update.binary.length)); //agent.SendQuery(7, AgentFileLen); |
| 281 | parent.debug('swarmcmd', 'Sending end of agent, ptr = ' + socket.tag.updatePtr); |
| 282 | obj.parent.taskLimiter.completed(socket.tag.taskid); // Indicate this task complete |
| 283 | delete socket.tag.taskid; |
| 284 | delete socket.tag.update; |
| 285 | delete socket.tag.updatePtr; |
| 286 | } |
| 287 | } |
| 288 | break; |
| 289 | } |
| 290 | default: { |
| 291 | // All other state commands from the legacy agent must be ignored. |
| 292 | break; |
| 293 | } |
| 294 | } |
| 295 | break; |
| 296 | } |
| 297 | case LegacyMeshProtocol.APPSUBSCRIBERS: { |
| 298 | parent.debug('swarmcmd', 'APPSUBSCRIBERS'); |
| 299 | break; |
| 300 | } |
| 301 | default: { |
| 302 | parent.debug('swarmcmd', 'Unknown command: ' + cmd + ' of len ' + len + '.'); |
| 303 | } |
| 304 | } |
| 305 | return len; |
| 306 | } |
| 307 | |
| 308 | // Called when a legacy agent connects to this server |
| 309 | function onConnection(socket) { |
| 310 | // Check for blocked IP address |
| 311 | if (checkSwarmIpAddress(socket, obj.args.swarmallowedip) == false) { obj.stats.blockedConnect++; parent.debug('swarm', "New blocked agent connection"); return; } |
| 312 | obj.stats.connectCount++; |
| 313 | |
| 314 | socket.tag = { first: true, clientCert: socket.getPeerCertificate(true), accumulator: "" }; |
| 315 | parent.debug('swarm', 'New legacy agent connection'); |
| 316 | |
| 317 | if ((socket.tag.clientCert == null) || (socket.tag.clientCert.subject == null)) { obj.stats.noCertConnectCount++; } else { obj.stats.clientCertConnectCount++; } |
| 318 | |
| 319 | socket.addListener("data", onData); |
| 320 | socket.addListener("close", function () { |
| 321 | obj.stats.onclose++; |
| 322 | parent.debug('swarm', 'Connection closed'); |
| 323 | |
| 324 | // Perform aggressive cleanup |
| 325 | if (this.relaySocket) { try { this.relaySocket.end(); this.relaySocket.removeAllListeners(["data", "end", "error"]); delete this.relaySocket; } catch (ex) { } } |
| 326 | if (this.pingTimer != null) { clearInterval(this.pingTimer); delete this.pingTimer; } |
| 327 | if (this.tag && (typeof this.tag.taskid == 'number')) { |
| 328 | obj.parent.taskLimiter.completed(this.tag.taskid); // Indicate this task complete |
| 329 | delete this.tag.taskid; |
| 330 | } |
| 331 | if (this.tag) { |
| 332 | if (this.tag.accumulator) { delete this.tag.accumulator; } |
| 333 | if (this.tag.clientCert) { delete this.tag.clientCert; } |
| 334 | delete this.tag; |
| 335 | } |
| 336 | this.removeAllListeners([ "data", "close", "error" ]); |
| 337 | }); |
| 338 | |
| 339 | socket.addListener("error", function () { |
| 340 | //console.log("Swarm Error: " + socket.remoteAddress); |
| 341 | }); |
| 342 | } |
| 343 | |
| 344 | function getTagClass(data, tagClass, type) { |
| 345 | if ((data == null) || (data.value == null)) return; |
| 346 | for (var i in data.value) { |
| 347 | //console.log(JSON.stringify(data.value[i])); |
| 348 | if ((data.value[i].tagClass == tagClass) && (data.value[i].type == type)) { |
| 349 | return data.value[i]; |
| 350 | } |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | // Decode a node push block |
| 355 | obj.decodeNodeBlock = function (data) { |
| 356 | try { |
| 357 | // Traverse the DER to get the raw data (Not sure if this works all the time) |
| 358 | var info = {}, ptr = 68, der = forge.asn1.fromDer(forge.util.createBuffer(data, 'binary')); |
| 359 | der = getTagClass(der, 128, 0); |
| 360 | der = getTagClass(der, 0, 16); |
| 361 | der = getTagClass(der, 0, 16); |
| 362 | der = getTagClass(der, 128, 0); |
| 363 | der = getTagClass(der, 0, 4); |
| 364 | var binarydata = der.value; |
| 365 | |
| 366 | // Get the basic header values |
| 367 | info.certhashhex = common.rstr2hex(binarydata.substring(0, 32)); // Hash of the complete mesh agent certificate |
| 368 | info.nodeidhex = common.rstr2hex(binarydata.substring(32, 64)); // Old mesh agent nodeid |
| 369 | info.serialNumber = common.ReadIntX(binarydata, 64); // Block serial number |
| 370 | |
| 371 | // Got thru the sub-blocks |
| 372 | while (ptr < binarydata.length) { |
| 373 | var btyp = common.ReadShort(binarydata, ptr), blen = common.ReadShort(binarydata, ptr + 2), bdata = binarydata.substring(ptr + 4, ptr + 4 + blen); |
| 374 | switch (btyp) { |
| 375 | case 1: { // PBST_COMPUTERINFO |
| 376 | info.agenttype = common.ReadShortX(bdata, 0); |
| 377 | info.agentbuild = common.ReadShortX(bdata, 2); |
| 378 | info.agentversion = common.ReadIntX(bdata, 4); |
| 379 | info.agentname = bdata.substring(8, 64 + 8); |
| 380 | var xx = info.agentname.indexOf('\u0000'); |
| 381 | if (xx >= 0) { info.agentname = info.agentname.substring(0, xx); } |
| 382 | info.agentosdesc = bdata.substring(64 + 8, 64 + 64 + 8); |
| 383 | xx = info.agentosdesc.indexOf('\u0000'); |
| 384 | if (xx >= 0) { info.agentosdesc = info.agentosdesc.substring(0, xx); } |
| 385 | return info; |
| 386 | } |
| 387 | default: { |
| 388 | // All other commands from the legacy agent must be ignored. |
| 389 | break; |
| 390 | } |
| 391 | } |
| 392 | ptr += blen; |
| 393 | } |
| 394 | return info; |
| 395 | } catch (e) { } |
| 396 | return null; |
| 397 | }; |
| 398 | |
| 399 | // Disconnect legacy agent connection |
| 400 | obj.close = function (socket) { |
| 401 | obj.stats.close++; |
| 402 | try { socket.close(); } catch (e) { } |
| 403 | socket.xclosed = 1; |
| 404 | }; |
| 405 | |
| 406 | obj.SendCommand = function (socket, cmdid, data) { |
| 407 | if (data == null) { data = ''; } |
| 408 | Write(socket, common.ShortToStr(cmdid) + common.ShortToStr(data.length + 4) + data); |
| 409 | }; |
| 410 | |
| 411 | function Write(socket, data) { |
| 412 | obj.stats.bytesOut += data.length; |
| 413 | if (args.swarmdebug) { |
| 414 | // Print out sent bytes |
| 415 | var buf = Buffer.from(data, "binary"); |
| 416 | console.log('SWARM --> (' + buf.length + '):' + buf.toString('hex')); |
| 417 | socket.write(buf); |
| 418 | } else { |
| 419 | socket.write(Buffer.from(data, "binary")); |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | // Check if the source IP address is allowed for a given allowed list, return false if not |
| 424 | function checkSwarmIpAddress(socket, allowedIpList) { |
| 425 | if (allowedIpList == null) { return true; } |
| 426 | try { |
| 427 | var ip = socket.remoteAddress; |
| 428 | if (ip) { for (var i = 0; i < allowedIpList.length; i++) { if (require('ipcheck').match(ip, allowedIpList[i])) { return true; } } } |
| 429 | } catch (e) { console.log(e); } |
| 430 | return false; |
| 431 | } |
| 432 | |
| 433 | return obj; |
| 434 | }; |