Inital commit for APF over secure websocket

jsastriawan committed Aug 29, 2019 at 14:38 UTC 6883d792dcae31c2ad2abfd3543f631a3ba557bd
3 files changed +733
apfserver.js new
+729
@@ -0,0 +1,729 @@
1 +/**
2 +* @description MeshCentral Intel(R) AMT APF over websocket server
3 +* @author Ylian Saint-Hilaire/Joko Sastriawan
4 +* @copyright Intel Corporation 2018-2019
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 APF server object
17 +module.exports.CreateApfServer = function (parent, db, args) {
18 + var obj = {};
19 + obj.parent = parent;
20 + obj.db = db;
21 + obj.args = args;
22 + obj.apfConnections = {};
23 + const constants = (require('crypto').constants ? require('crypto').constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
24 + const common = require("./common.js");
25 + const net = require("net");
26 + const MAX_IDLE = 90000; // 90 seconds max idle time, higher than the typical KEEP-ALIVE periode of 60 seconds
27 +
28 +
29 + const APFProtocol = {
30 + UNKNOWN: 0,
31 + DISCONNECT: 1,
32 + SERVICE_REQUEST: 5,
33 + SERVICE_ACCEPT: 6,
34 + USERAUTH_REQUEST: 50,
35 + USERAUTH_FAILURE: 51,
36 + USERAUTH_SUCCESS: 52,
37 + GLOBAL_REQUEST: 80,
38 + REQUEST_SUCCESS: 81,
39 + REQUEST_FAILURE: 82,
40 + CHANNEL_OPEN: 90,
41 + CHANNEL_OPEN_CONFIRMATION: 91,
42 + CHANNEL_OPEN_FAILURE: 92,
43 + CHANNEL_WINDOW_ADJUST: 93,
44 + CHANNEL_DATA: 94,
45 + CHANNEL_CLOSE: 97,
46 + PROTOCOLVERSION: 192,
47 + KEEPALIVE_REQUEST: 208,
48 + KEEPALIVE_REPLY: 209,
49 + KEEPALIVE_OPTIONS_REQUEST: 210,
50 + KEEPALIVE_OPTIONS_REPLY: 211
51 + };
52 +
53 + /*
54 + const APFDisconnectCode = {
55 + HOST_NOT_ALLOWED_TO_CONNECT: 1,
56 + PROTOCOL_ERROR: 2,
57 + KEY_EXCHANGE_FAILED: 3,
58 + RESERVED: 4,
59 + MAC_ERROR: 5,
60 + COMPRESSION_ERROR: 6,
61 + SERVICE_NOT_AVAILABLE: 7,
62 + PROTOCOL_VERSION_NOT_SUPPORTED: 8,
63 + HOST_KEY_NOT_VERIFIABLE: 9,
64 + CONNECTION_LOST: 10,
65 + BY_APPLICATION: 11,
66 + TOO_MANY_CONNECTIONS: 12,
67 + AUTH_CANCELLED_BY_USER: 13,
68 + NO_MORE_AUTH_METHODS_AVAILABLE: 14,
69 + INVALID_CREDENTIALS: 15,
70 + CONNECTION_TIMED_OUT: 16,
71 + BY_POLICY: 17,
72 + TEMPORARILY_UNAVAILABLE: 18
73 + };
74 +
75 + const APFChannelOpenFailCodes = {
76 + ADMINISTRATIVELY_PROHIBITED: 1,
77 + CONNECT_FAILED: 2,
78 + UNKNOWN_CHANNEL_TYPE: 3,
79 + RESOURCE_SHORTAGE: 4,
80 + };
81 + */
82 +
83 + const APFChannelOpenFailureReasonCode = {
84 + AdministrativelyProhibited: 1,
85 + ConnectFailed: 2,
86 + UnknownChannelType: 3,
87 + ResourceShortage: 4,
88 + };
89 +
90 + // Stat counters
91 + var connectionCount = 0;
92 + var userAuthRequestCount = 0;
93 + var incorrectPasswordCount = 0;
94 + var meshNotFoundCount = 0;
95 + var unknownNodeCount = 0;
96 + var unknownMeshIdCount = 0;
97 + var addedDeviceCount = 0;
98 + var ciraTimeoutCount = 0;
99 + var protocolVersionCount = 0;
100 + var badUserNameLengthCount = 0;
101 + var channelOpenCount = 0;
102 + var channelOpenConfirmCount = 0;
103 + var channelOpenFailCount = 0;
104 + var channelCloseCount = 0;
105 + var disconnectCommandCount = 0;
106 + var socketClosedCount = 0;
107 + var socketErrorCount = 0;
108 + var maxDomainDevicesReached = 0;
109 +
110 + // Return statistics about this APF server
111 + obj.getStats = function () {
112 + return {
113 + apfConnections: Object.keys(obj.apfConnections).length,
114 + connectionCount: connectionCount,
115 + userAuthRequestCount: userAuthRequestCount,
116 + incorrectPasswordCount: incorrectPasswordCount,
117 + meshNotFoundCount: meshNotFoundCount,
118 + unknownNodeCount: unknownNodeCount,
119 + unknownMeshIdCount: unknownMeshIdCount,
120 + addedDeviceCount: addedDeviceCount,
121 + apfTimeoutCount: ciraTimeoutCount,
122 + protocolVersionCount: protocolVersionCount,
123 + badUserNameLengthCount: badUserNameLengthCount,
124 + channelOpenCount: channelOpenCount,
125 + channelOpenConfirmCount: channelOpenConfirmCount,
126 + channelOpenFailCount: channelOpenFailCount,
127 + channelCloseCount: channelCloseCount,
128 + disconnectCommandCount: disconnectCommandCount,
129 + socketClosedCount: socketClosedCount,
130 + socketErrorCount: socketErrorCount,
131 + maxDomainDevicesReached : maxDomainDevicesReached
132 + };
133 + }
134 +
135 + obj.onConnection = function(socket) {
136 + console.log("Here");
137 + connectionCount++;
138 + // treat APS over WS like tlsoffload APF
139 + socket.tag = { first: true, clientCert: null, accumulator: "", activetunnels: 0, boundPorts: [], socket: socket, host: null, nextchannelid: 4, channels: {}, nextsourceport: 0 };
140 + parent.debug('apf', "New APF connection");
141 +
142 + // Setup the APF keep alive timer
143 + // Websocket does not have timout
144 + // socket.setTimeout(MAX_IDLE);
145 + //socket.on("timeout", () => { ciraTimeoutCount++; parent.debug('mps', "APF timeout, disconnecting."); try { socket.terminate(); } catch (e) { } });
146 + //use on message instead because of websocket
147 + socket.on("message", function (data) {
148 + // use the same debug flag like APF
149 + if (obj.args.debug) { var buf = Buffer.from(data, "binary"); console.log("APF <-- (" + buf.length + "):" + buf.toString('hex')); } // Print out received bytes
150 + socket.tag.accumulator += data.toString("binary"); // append as binary string
151 +
152 +
153 + try {
154 + // Parse all of the APF data we can
155 + var l = 0;
156 + do { l = ProcessCommand(socket); if (l > 0) { socket.tag.accumulator = socket.tag.accumulator.substring(l); } } while (l > 0);
157 + if (l < 0) { socket.terminate(); }
158 + } catch (e) {
159 + console.log(e);
160 + }
161 + });
162 +
163 + // Process one AFP command
164 + function ProcessCommand(socket) {
165 + var cmd = socket.tag.accumulator.charCodeAt(0);
166 + var len = socket.tag.accumulator.length;
167 + var data = socket.tag.accumulator;
168 + if (len == 0) { return 0; }
169 +
170 + switch (cmd) {
171 + case APFProtocol.KEEPALIVE_REQUEST: {
172 + if (len < 5) return 0;
173 + parent.debug('apfcmd', 'KEEPALIVE_REQUEST');
174 + SendKeepAliveReply(socket, common.ReadInt(data, 1));
175 + return 5;
176 + }
177 + case APFProtocol.KEEPALIVE_REPLY: {
178 + if (len < 5) return 0;
179 + parent.debug('apfcmd', 'KEEPALIVE_REPLY');
180 + return 5;
181 + }
182 + case APFProtocol.PROTOCOLVERSION: {
183 + if (len < 93) return 0;
184 + protocolVersionCount++;
185 + socket.tag.MajorVersion = common.ReadInt(data, 1);
186 + socket.tag.MinorVersion = common.ReadInt(data, 5);
187 + socket.tag.SystemId = guidToStr(common.rstr2hex(data.substring(13, 29))).toLowerCase();
188 + parent.debug('apfcmd', 'PROTOCOLVERSION', socket.tag.MajorVersion, socket.tag.MinorVersion, socket.tag.SystemId);
189 + return 93;
190 + }
191 + case APFProtocol.USERAUTH_REQUEST: {
192 + if (len < 13) return 0;
193 + userAuthRequestCount++;
194 + var usernameLen = common.ReadInt(data, 1);
195 + var username = data.substring(5, 5 + usernameLen);
196 + var serviceNameLen = common.ReadInt(data, 5 + usernameLen);
197 + var serviceName = data.substring(9 + usernameLen, 9 + usernameLen + serviceNameLen);
198 + var methodNameLen = common.ReadInt(data, 9 + usernameLen + serviceNameLen);
199 + var methodName = data.substring(13 + usernameLen + serviceNameLen, 13 + usernameLen + serviceNameLen + methodNameLen);
200 + var passwordLen = 0, password = null;
201 + if (methodName == 'password') {
202 + passwordLen = common.ReadInt(data, 14 + usernameLen + serviceNameLen + methodNameLen);
203 + password = data.substring(18 + usernameLen + serviceNameLen + methodNameLen, 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen);
204 + }
205 + //console.log('APF:USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password);
206 + parent.debug('apfcmd', 'USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password);
207 +
208 + // Check the APF password
209 + if ((args.mpspass != null) && (password != args.mpspass)) { incorrectPasswordCount++; parent.debug('mps', 'Incorrect password', username, password); SendUserAuthFail(socket); return -1; }
210 +
211 + // Check the APF username, which should be the start of the MeshID.
212 + if (usernameLen != 16) { badUserNameLengthCount++; parent.debug('mps', 'Username length not 16', username, password); SendUserAuthFail(socket); return -1; }
213 + var meshIdStart = '/' + username, mesh = null;
214 + if (obj.parent.webserver.meshes) { for (var i in obj.parent.webserver.meshes) { if (obj.parent.webserver.meshes[i]._id.replace(/\@/g, 'X').replace(/\$/g, 'X').indexOf(meshIdStart) > 0) { mesh = obj.parent.webserver.meshes[i]; break; } } }
215 + if (mesh == null) { meshNotFoundCount++; parent.debug('mps', 'Mesh not found', username, password); SendUserAuthFail(socket); return -1; }
216 +
217 + // If this is a agent-less mesh, use the device guid 3 times as ID.
218 + if (mesh.mtype == 1) {
219 + // Intel AMT GUID (socket.tag.SystemId) will be used as NodeID
220 + var systemid = socket.tag.SystemId.split('-').join('');
221 + var nodeid = Buffer.from(systemid + systemid + systemid, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
222 + var domain = obj.parent.config.domains[mesh.domain];
223 + socket.tag.domain = domain;
224 + socket.tag.domainid = mesh.domain;
225 + socket.tag.name = '';
226 + socket.tag.nodeid = 'node/' + mesh.domain + '/' + nodeid; // Turn 16bit systemid guid into 48bit nodeid that is base64 encoded
227 + socket.tag.meshid = mesh._id;
228 + socket.tag.connectTime = Date.now();
229 +
230 + obj.db.Get(socket.tag.nodeid, function (err, nodes) {
231 + if ((nodes == null) || (nodes.length !== 1)) {
232 + // Check if we already have too many devices for this domain
233 + if (domain.limits && (typeof domain.limits.maxdevices == 'number')) {
234 + db.isMaxType(domain.limits.maxdevices, 'node', mesh.domain, function (ismax, count) {
235 + if (ismax == true) {
236 + // Too many devices in this domain.
237 + maxDomainDevicesReached++;
238 + console.log('Too many devices on this domain to accept the APF connection. meshid: ' + socket.tag.meshid);
239 + socket.terminate();
240 + } else {
241 + // We are under the limit, create the new device.
242 + // Node is not in the database, add it. Credentials will be empty until added by the user.
243 + var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: mesh.domain, intelamt: { user: '', pass: '', tls: 0, state: 2 } };
244 + obj.db.Set(device);
245 +
246 + // Event the new node
247 + addedDeviceCount++;
248 + var device2 = common.Clone(device);
249 + if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
250 + var change = 'APF added device ' + socket.tag.name + ' to group ' + mesh.name;
251 + obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: mesh.domain });
252 +
253 + // Add the connection to the APF connection list
254 + obj.apfConnections[socket.tag.nodeid] = socket;
255 + // send connectivuty update type 8 for APF
256 + obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 8, 7); // TODO: Right now report power state as "present" (7) until we can poll.
257 + SendUserAuthSuccess(socket); // Notify the auth success on the APF connection
258 + }
259 + });
260 + return;
261 + } else {
262 + // Node is not in the database, add it. Credentials will be empty until added by the user.
263 + var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: mesh.domain, intelamt: { user: '', pass: '', tls: 0, state: 2 } };
264 + obj.db.Set(device);
265 +
266 + // Event the new node
267 + addedDeviceCount++;
268 + var device2 = common.Clone(device);
269 + if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
270 + var change = 'APF added device ' + socket.tag.name + ' to group ' + mesh.name;
271 + obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: mesh.domain });
272 + }
273 + } else {
274 + // Node is already present
275 + var node = nodes[0];
276 + if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
277 + }
278 +
279 + // Add the connection to the APF connection list
280 + obj.apfConnections[socket.tag.nodeid] = socket;
281 + obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 8, 7); // TODO: Right now report power state as "present" (7) until we can poll.
282 + SendUserAuthSuccess(socket); // Notify the auth success on the APF connection
283 + });
284 + } else if (mesh.mtype == 2) { // If this is a agent mesh, search the mesh for this device UUID
285 + // Intel AMT GUID (socket.tag.SystemId) will be used to search the node
286 + obj.db.getAmtUuidNode(mesh._id, socket.tag.SystemId, function (err, nodes) { // TODO: May need to optimize this request with indexes
287 + if ((nodes == null) || (nodes.length !== 1)) {
288 + // New APF connection for unknown node, disconnect.
289 + unknownNodeCount++;
290 + console.log('APF connection for unknown node. groupid: ' + mesh._id + ', uuid: ' + socket.tag.SystemId);
291 + socket.terminate();
292 + return;
293 + }
294 +
295 + // Node is present
296 + var node = nodes[0];
297 + if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
298 + socket.tag.nodeid = node._id;
299 + socket.tag.meshid = mesh._id;
300 + socket.tag.connectTime = Date.now();
301 +
302 + // Add the connection to the APF connection list
303 + obj.apfConnections[socket.tag.nodeid] = socket;
304 + obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 8, 7); // TODO: Right now report power state as "present" (7) until we can poll.
305 + SendUserAuthSuccess(socket); // Notify the auth success on the APF connection
306 + });
307 + } else { // Unknown mesh type
308 + // New APF connection for unknown node, disconnect.
309 + unknownMeshIdCount++;
310 + console.log('APF connection to a unknown group type. groupid: ' + socket.tag.meshid);
311 + socket.terminate();
312 + return;
313 + }
314 + return 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen;
315 + }
316 + case APFProtocol.SERVICE_REQUEST: {
317 + if (len < 5) return 0;
318 + var xserviceNameLen = common.ReadInt(data, 1);
319 + if (len < 5 + xserviceNameLen) return 0;
320 + var xserviceName = data.substring(5, 5 + xserviceNameLen);
321 + parent.debug('apfcmd', 'SERVICE_REQUEST', xserviceName);
322 + if (xserviceName == "pfwd@amt.intel.com") { SendServiceAccept(socket, "pfwd@amt.intel.com"); }
323 + if (xserviceName == "auth@amt.intel.com") { SendServiceAccept(socket, "auth@amt.intel.com"); }
324 + return 5 + xserviceNameLen;
325 + }
326 + case APFProtocol.GLOBAL_REQUEST: {
327 + if (len < 14) return 0;
328 + var requestLen = common.ReadInt(data, 1);
329 + if (len < 14 + requestLen) return 0;
330 + var request = data.substring(5, 5 + requestLen);
331 + //var wantResponse = data.charCodeAt(5 + requestLen);
332 +
333 + if (request == "tcpip-forward") {
334 + var addrLen = common.ReadInt(data, 6 + requestLen);
335 + if (len < 14 + requestLen + addrLen) return 0;
336 + var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen);
337 + var port = common.ReadInt(data, 10 + requestLen + addrLen);
338 + parent.debug('apfcmd', 'GLOBAL_REQUEST', request, addr + ':' + port);
339 + ChangeHostname(socket, addr, socket.tag.SystemId);
340 + if (socket.tag.boundPorts.indexOf(port) == -1) { socket.tag.boundPorts.push(port); }
341 + SendTcpForwardSuccessReply(socket, port);
342 + return 14 + requestLen + addrLen;
343 + }
344 +
345 + if (request == "cancel-tcpip-forward") {
346 + var addrLen = common.ReadInt(data, 6 + requestLen);
347 + if (len < 14 + requestLen + addrLen) return 0;
348 + var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen);
349 + var port = common.ReadInt(data, 10 + requestLen + addrLen);
350 + parent.debug('apfcmd', 'GLOBAL_REQUEST', request, addr + ':' + port);
351 + var portindex = socket.tag.boundPorts.indexOf(port);
352 + if (portindex >= 0) { socket.tag.boundPorts.splice(portindex, 1); }
353 + SendTcpForwardCancelReply(socket);
354 + return 14 + requestLen + addrLen;
355 + }
356 +
357 + if (request == "udp-send-to@amt.intel.com") {
358 + var addrLen = common.ReadInt(data, 6 + requestLen);
359 + if (len < 26 + requestLen + addrLen) return 0;
360 + var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen);
361 + var port = common.ReadInt(data, 10 + requestLen + addrLen);
362 + var oaddrLen = common.ReadInt(data, 14 + requestLen + addrLen);
363 + if (len < 26 + requestLen + addrLen + oaddrLen) return 0;
364 + var oaddr = data.substring(18 + requestLen, 18 + requestLen + addrLen);
365 + var oport = common.ReadInt(data, 18 + requestLen + addrLen + oaddrLen);
366 + var datalen = common.ReadInt(data, 22 + requestLen + addrLen + oaddrLen);
367 + if (len < 26 + requestLen + addrLen + oaddrLen + datalen) return 0;
368 + parent.debug('apfcmd', 'GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen);
369 + // TODO
370 + return 26 + requestLen + addrLen + oaddrLen + datalen;
371 + }
372 +
373 + return 6 + requestLen;
374 + }
375 + case APFProtocol.CHANNEL_OPEN: {
376 + if (len < 33) return 0;
377 + var ChannelTypeLength = common.ReadInt(data, 1);
378 + if (len < (33 + ChannelTypeLength)) return 0;
379 +
380 + // Decode channel identifiers and window size
381 + var ChannelType = data.substring(5, 5 + ChannelTypeLength);
382 + var SenderChannel = common.ReadInt(data, 5 + ChannelTypeLength);
383 + var WindowSize = common.ReadInt(data, 9 + ChannelTypeLength);
384 +
385 + // Decode the target
386 + var TargetLen = common.ReadInt(data, 17 + ChannelTypeLength);
387 + if (len < (33 + ChannelTypeLength + TargetLen)) return 0;
388 + var Target = data.substring(21 + ChannelTypeLength, 21 + ChannelTypeLength + TargetLen);
389 + var TargetPort = common.ReadInt(data, 21 + ChannelTypeLength + TargetLen);
390 +
391 + // Decode the source
392 + var SourceLen = common.ReadInt(data, 25 + ChannelTypeLength + TargetLen);
393 + if (len < (33 + ChannelTypeLength + TargetLen + SourceLen)) return 0;
394 + var Source = data.substring(29 + ChannelTypeLength + TargetLen, 29 + ChannelTypeLength + TargetLen + SourceLen);
395 + var SourcePort = common.ReadInt(data, 29 + ChannelTypeLength + TargetLen + SourceLen);
396 +
397 + channelOpenCount++;
398 + parent.debug('apfcmd', 'CHANNEL_OPEN', ChannelType, SenderChannel, WindowSize, Target + ':' + TargetPort, Source + ':' + SourcePort);
399 +
400 + // Check if we understand this channel type
401 + //if (ChannelType.toLowerCase() == "direct-tcpip")
402 + {
403 + // We don't understand this channel type, send an error back
404 + SendChannelOpenFailure(socket, SenderChannel, APFChannelOpenFailureReasonCode.UnknownChannelType);
405 + return 33 + ChannelTypeLength + TargetLen + SourceLen;
406 + }
407 +
408 + /*
409 + // This is a correct connection. Lets get it setup
410 + var MeshAmtEventEndpoint = { ServerChannel: GetNextBindId(), AmtChannel: SenderChannel, MaxWindowSize: 2048, CurrentWindowSize:2048, SendWindow: WindowSize, InfoHeader: "Target: " + Target + ":" + TargetPort + ", Source: " + Source + ":" + SourcePort};
411 + // TODO: Connect this socket for a WSMAN event
412 + SendChannelOpenConfirmation(socket, SenderChannel, MeshAmtEventEndpoint.ServerChannel, MeshAmtEventEndpoint.MaxWindowSize);
413 + */
414 +
415 + return 33 + ChannelTypeLength + TargetLen + SourceLen;
416 + }
417 + case APFProtocol.CHANNEL_OPEN_CONFIRMATION:
418 + {
419 + if (len < 17) return 0;
420 + var RecipientChannel = common.ReadInt(data, 1);
421 + var SenderChannel = common.ReadInt(data, 5);
422 + var WindowSize = common.ReadInt(data, 9);
423 + socket.tag.activetunnels++;
424 + var cirachannel = socket.tag.channels[RecipientChannel];
425 + if (cirachannel == null) { /*console.log("APF Error in CHANNEL_OPEN_CONFIRMATION: Unable to find channelid " + RecipientChannel);*/ return 17; }
426 + cirachannel.amtchannelid = SenderChannel;
427 + cirachannel.sendcredits = cirachannel.amtCiraWindow = WindowSize;
428 + channelOpenConfirmCount++;
429 + parent.debug('apfcmd', 'CHANNEL_OPEN_CONFIRMATION', RecipientChannel, SenderChannel, WindowSize);
430 + if (cirachannel.closing == 1) {
431 + // Close this channel
432 + SendChannelClose(cirachannel.socket, cirachannel.amtchannelid);
433 + } else {
434 + cirachannel.state = 2;
435 + // Send any pending data
436 + if (cirachannel.sendBuffer != null) {
437 + if (cirachannel.sendBuffer.length <= cirachannel.sendcredits) {
438 + // Send the entire pending buffer
439 + SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer);
440 + cirachannel.sendcredits -= cirachannel.sendBuffer.length;
441 + delete cirachannel.sendBuffer;
442 + if (cirachannel.onSendOk) { cirachannel.onSendOk(cirachannel); }
443 + } else {
444 + // Send a part of the pending buffer
445 + SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer.substring(0, cirachannel.sendcredits));
446 + cirachannel.sendBuffer = cirachannel.sendBuffer.substring(cirachannel.sendcredits);
447 + cirachannel.sendcredits = 0;
448 + }
449 + }
450 + // Indicate the channel is open
451 + if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
452 + }
453 + return 17;
454 + }
455 + case APFProtocol.CHANNEL_OPEN_FAILURE:
456 + {
457 + if (len < 17) return 0;
458 + var RecipientChannel = common.ReadInt(data, 1);
459 + var ReasonCode = common.ReadInt(data, 5);
460 + channelOpenFailCount++;
461 + parent.debug('apfcmd', 'CHANNEL_OPEN_FAILURE', RecipientChannel, ReasonCode);
462 + var cirachannel = socket.tag.channels[RecipientChannel];
463 + if (cirachannel == null) { console.log("APF Error in CHANNEL_OPEN_FAILURE: Unable to find channelid " + RecipientChannel); return 17; }
464 + if (cirachannel.state > 0) {
465 + cirachannel.state = 0;
466 + if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
467 + delete socket.tag.channels[RecipientChannel];
468 + }
469 + return 17;
470 + }
471 + case APFProtocol.CHANNEL_CLOSE:
472 + {
473 + if (len < 5) return 0;
474 + var RecipientChannel = common.ReadInt(data, 1);
475 + channelCloseCount++;
476 + parent.debug('apfcmd', 'CHANNEL_CLOSE', RecipientChannel);
477 + var cirachannel = socket.tag.channels[RecipientChannel];
478 + if (cirachannel == null) { console.log("APF Error in CHANNEL_CLOSE: Unable to find channelid " + RecipientChannel); return 5; }
479 + socket.tag.activetunnels--;
480 + if (cirachannel.state > 0) {
481 + cirachannel.state = 0;
482 + if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
483 + delete socket.tag.channels[RecipientChannel];
484 + }
485 + return 5;
486 + }
487 + case APFProtocol.CHANNEL_WINDOW_ADJUST:
488 + {
489 + if (len < 9) return 0;
490 + var RecipientChannel = common.ReadInt(data, 1);
491 + var ByteToAdd = common.ReadInt(data, 5);
492 + var cirachannel = socket.tag.channels[RecipientChannel];
493 + if (cirachannel == null) { console.log("APF Error in CHANNEL_WINDOW_ADJUST: Unable to find channelid " + RecipientChannel); return 9; }
494 + cirachannel.sendcredits += ByteToAdd;
495 + parent.debug('apfcmd', 'CHANNEL_WINDOW_ADJUST', RecipientChannel, ByteToAdd, cirachannel.sendcredits);
496 + if (cirachannel.state == 2 && cirachannel.sendBuffer != null) {
497 + // Compute how much data we can send
498 + if (cirachannel.sendBuffer.length <= cirachannel.sendcredits) {
499 + // Send the entire pending buffer
500 + SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer);
501 + cirachannel.sendcredits -= cirachannel.sendBuffer.length;
502 + delete cirachannel.sendBuffer;
503 + if (cirachannel.onSendOk) { cirachannel.onSendOk(cirachannel); }
504 + } else {
505 + // Send a part of the pending buffer
506 + SendChannelData(cirachannel.socket, cirachannel.amtchannelid, cirachannel.sendBuffer.substring(0, cirachannel.sendcredits));
507 + cirachannel.sendBuffer = cirachannel.sendBuffer.substring(cirachannel.sendcredits);
508 + cirachannel.sendcredits = 0;
509 + }
510 + }
511 + return 9;
512 + }
513 + case APFProtocol.CHANNEL_DATA:
514 + {
515 + if (len < 9) return 0;
516 + var RecipientChannel = common.ReadInt(data, 1);
517 + var LengthOfData = common.ReadInt(data, 5);
518 + if (len < (9 + LengthOfData)) return 0;
519 + parent.debug('apfcmddata', 'CHANNEL_DATA', RecipientChannel, LengthOfData);
520 + var cirachannel = socket.tag.channels[RecipientChannel];
521 + if (cirachannel == null) { console.log("APF Error in CHANNEL_DATA: Unable to find channelid " + RecipientChannel); return 9 + LengthOfData; }
522 + cirachannel.amtpendingcredits += LengthOfData;
523 + if (cirachannel.onData) cirachannel.onData(cirachannel, data.substring(9, 9 + LengthOfData));
524 + if (cirachannel.amtpendingcredits > (cirachannel.ciraWindow / 2)) {
525 + SendChannelWindowAdjust(cirachannel.socket, cirachannel.amtchannelid, cirachannel.amtpendingcredits); // Adjust the buffer window
526 + cirachannel.amtpendingcredits = 0;
527 + }
528 + return 9 + LengthOfData;
529 + }
530 + case APFProtocol.DISCONNECT:
531 + {
532 + if (len < 7) return 0;
533 + var ReasonCode = common.ReadInt(data, 1);
534 + disconnectCommandCount++;
535 + parent.debug('apfcmd', 'DISCONNECT', ReasonCode);
536 + try { delete obj.apfConnections[socket.tag.nodeid]; } catch (e) { }
537 + obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 8);
538 + return 7;
539 + }
540 + default:
541 + {
542 + parent.debug('apfcmd', 'Unknown APF command: ' + cmd);
543 + return -1;
544 + }
545 + }
546 + }
547 +
548 + socket.addListener("close", function () {
549 + socketClosedCount++;
550 + parent.debug('mps', 'APF connection closed');
551 + try { delete obj.apfConnections[socket.tag.nodeid]; } catch (e) { }
552 + obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 8);
553 + });
554 +
555 + socket.addListener("error", function () {
556 + socketErrorCount++;
557 + //console.log("APF Error: " + socket.remoteAddress);
558 + });
559 +
560 + }
561 +
562 + // Disconnect APF tunnel
563 + obj.close = function (socket) {
564 + try { socket.terminate(); } catch (e) { }
565 + try { delete obj.apfConnections[socket.tag.nodeid]; } catch (e) { }
566 + obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 8);
567 + };
568 +
569 + function SendServiceAccept(socket, service) {
570 + Write(socket, String.fromCharCode(APFProtocol.SERVICE_ACCEPT) + common.IntToStr(service.length) + service);
571 + }
572 +
573 + function SendTcpForwardSuccessReply(socket, port) {
574 + Write(socket, String.fromCharCode(APFProtocol.REQUEST_SUCCESS) + common.IntToStr(port));
575 + }
576 +
577 + function SendTcpForwardCancelReply(socket) {
578 + Write(socket, String.fromCharCode(APFProtocol.REQUEST_SUCCESS));
579 + }
580 +
581 + /*
582 + function SendKeepAliveRequest(socket, cookie) {
583 + Write(socket, String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + common.IntToStr(cookie));
584 + }
585 + */
586 +
587 + function SendKeepAliveReply(socket, cookie) {
588 + Write(socket, String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + common.IntToStr(cookie));
589 + }
590 +
591 + function SendChannelOpenFailure(socket, senderChannel, reasonCode) {
592 + Write(socket, String.fromCharCode(APFProtocol.CHANNEL_OPEN_FAILURE) + common.IntToStr(senderChannel) + common.IntToStr(reasonCode) + common.IntToStr(0) + common.IntToStr(0));
593 + }
594 +
595 + /*
596 + function SendChannelOpenConfirmation(socket, recipientChannelId, senderChannelId, initialWindowSize) {
597 + Write(socket, String.fromCharCode(APFProtocol.CHANNEL_OPEN_CONFIRMATION) + common.IntToStr(recipientChannelId) + common.IntToStr(senderChannelId) + common.IntToStr(initialWindowSize) + common.IntToStr(-1));
598 + }
599 + */
600 +
601 + function SendChannelOpen(socket, direct, channelid, windowsize, target, targetport, source, sourceport) {
602 + var connectionType = ((direct == true) ? "direct-tcpip" : "forwarded-tcpip");
603 + if ((target == null) || (target == null)) target = ''; // TODO: Reports of target being undefined that causes target.length to fail. This is a hack.
604 + 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));
605 + }
606 +
607 + function SendChannelClose(socket, channelid) {
608 + Write(socket, String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + common.IntToStr(channelid));
609 + }
610 +
611 + function SendChannelData(socket, channelid, data) {
612 + Write(socket, String.fromCharCode(APFProtocol.CHANNEL_DATA) + common.IntToStr(channelid) + common.IntToStr(data.length) + data);
613 + }
614 +
615 + function SendChannelWindowAdjust(socket, channelid, bytestoadd) {
616 + parent.debug('apfcmd', 'SendChannelWindowAdjust', channelid, bytestoadd);
617 + Write(socket, String.fromCharCode(APFProtocol.CHANNEL_WINDOW_ADJUST) + common.IntToStr(channelid) + common.IntToStr(bytestoadd));
618 + }
619 +
620 + /*
621 + function SendDisconnect(socket, reasonCode) {
622 + Write(socket, String.fromCharCode(APFProtocol.DISCONNECT) + common.IntToStr(reasonCode) + common.ShortToStr(0));
623 + }
624 + */
625 +
626 + function SendUserAuthFail(socket) {
627 + Write(socket, String.fromCharCode(APFProtocol.USERAUTH_FAILURE) + common.IntToStr(8) + 'password' + common.ShortToStr(0));
628 + }
629 +
630 + function SendUserAuthSuccess(socket) {
631 + Write(socket, String.fromCharCode(APFProtocol.USERAUTH_SUCCESS));
632 + }
633 +
634 + function Write(socket, data) {
635 + if (obj.args.debug) {
636 + // Print out sent bytes
637 + var buf = Buffer.from(data, "binary");
638 + console.log('APF --> (' + buf.length + '):' + buf.toString('hex'));
639 + socket.send(buf);
640 + } else {
641 + socket.send(Buffer.from(data, "binary"));
642 + }
643 + }
644 +
645 + obj.SetupCiraChannel = function (socket, targetport) {
646 + var sourceport = (socket.tag.nextsourceport++ % 30000) + 1024;
647 + var cirachannel = { targetport: targetport, channelid: socket.tag.nextchannelid++, socket: socket, state: 1, sendcredits: 0, amtpendingcredits: 0, amtCiraWindow: 0, ciraWindow: 32768 };
648 + SendChannelOpen(socket, false, cirachannel.channelid, cirachannel.ciraWindow, socket.tag.host, targetport, "1.2.3.4", sourceport);
649 +
650 + // This function writes data to this APF channel
651 + cirachannel.write = function (data) {
652 + if (cirachannel.state == 0) return false;
653 + if (cirachannel.state == 1 || cirachannel.sendcredits == 0 || cirachannel.sendBuffer != null) {
654 + // Channel is connected, but we are out of credits. Add the data to the outbound buffer.
655 + if (cirachannel.sendBuffer == null) { cirachannel.sendBuffer = data; } else { cirachannel.sendBuffer += data; }
656 + return true;
657 + }
658 + // Compute how much data we can send
659 + if (data.length <= cirachannel.sendcredits) {
660 + // Send the entire message
661 + SendChannelData(cirachannel.socket, cirachannel.amtchannelid, data);
662 + cirachannel.sendcredits -= data.length;
663 + return true;
664 + }
665 + // Send a part of the message
666 + cirachannel.sendBuffer = data.substring(cirachannel.sendcredits);
667 + SendChannelData(cirachannel.socket, cirachannel.amtchannelid, data.substring(0, cirachannel.sendcredits));
668 + cirachannel.sendcredits = 0;
669 + return false;
670 + };
671 +
672 + // This function closes this APF channel
673 + cirachannel.close = function () {
674 + if (cirachannel.state == 0 || cirachannel.closing == 1) return;
675 + if (cirachannel.state == 1) { cirachannel.closing = 1; cirachannel.state = 0; if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); } return; }
676 + cirachannel.state = 0;
677 + cirachannel.closing = 1;
678 + SendChannelClose(cirachannel.socket, cirachannel.amtchannelid);
679 + if (cirachannel.onStateChange) { cirachannel.onStateChange(cirachannel, cirachannel.state); }
680 + };
681 +
682 + socket.tag.channels[cirachannel.channelid] = cirachannel;
683 + return cirachannel;
684 + };
685 +
686 + function ChangeHostname(socket, host, systemid) {
687 + if (socket.tag.host === host) return; // Nothing to change
688 + socket.tag.host = host;
689 +
690 + // Change the device
691 + obj.db.Get(socket.tag.nodeid, function (err, nodes) {
692 + if ((nodes == null) || (nodes.length !== 1)) return;
693 + var node = nodes[0];
694 +
695 + // See if any changes need to be made
696 + if ((node.intelamt != null) && (node.intelamt.host == host) && (node.name != null) && (node.name != '') && (node.intelamt.state == 2)) return;
697 +
698 + // Get the mesh for this device
699 + obj.db.Get(node.meshid, function (err, meshes) {
700 + if ((meshes == null) || (meshes.length !== 1)) return;
701 + var mesh = meshes[0];
702 +
703 + // Ready the node change event
704 + var changes = ['host'], event = { etype: 'node', action: 'changenode', nodeid: node._id };
705 + event.msg = +": ";
706 +
707 + // Make the change & save
708 + if (node.intelamt == null) node.intelamt = {};
709 + node.intelamt.host = host;
710 + node.intelamt.state = 2; // TODO: this is not real AMT state
711 + if (((node.name == null) || (node.name == '')) && (host != null) && (host != '')) { node.name = host.split('.')[0]; } // If this system has no name, set it to the start of the domain name.
712 + if (((node.name == null) || (node.name == '')) && (systemid != null)) { node.name = systemid; } // If this system still has no name, set it to the system GUID.
713 + obj.db.Set(node);
714 +
715 + // Event the node change
716 + event.msg = 'APF changed device ' + node.name + ' from group ' + mesh.name + ': ' + changes.join(', ');
717 + var node2 = common.Clone(node);
718 + if (node2.intelamt && node2.intelamt.pass) delete node2.intelamt.pass; // Remove the Intel AMT password before eventing this.
719 + event.node = node2;
720 + if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
721 + obj.parent.DispatchEvent(['*', node.meshid], obj, event);
722 + });
723 + });
724 + }
725 +
726 + 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); }
727 +
728 + return obj;
729 +};
meshcentral.js
+3
@@ -26,6 +26,7 @@ function CreateMeshCentralServer(config, args) {
26 obj.webserver = null;
27 obj.redirserver = null;
28 obj.mpsserver = null;
29 + obj.apfserver = null;
30 obj.swarmserver = null;
31 obj.mailserver = null;
32 obj.amtEventHandler = null;
@@ -819,6 +820,8 @@ function CreateMeshCentralServer(config, args) {
820 if ((obj.args.sessiontime != null) && ((typeof obj.args.sessiontime != 'number') || (obj.args.sessiontime < 1))) { delete obj.args.sessiontime; }
821 if (!obj.args.sessionkey) { obj.args.sessionkey = buf.toString('hex').toUpperCase(); }
822
823 + // Create APF server to hook into webserver
824 + obj.apfserver = require('./apfserver.js').CreateApfServer(obj, obj.db, obj.args);
825 // Start the web server and if needed, the redirection web server.
826 obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.certificates);
827 if (obj.redirserver != null) { obj.redirserver.hookMainWebServer(obj.certificates); }
webserver.js
+1
@@ -3198,6 +3198,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3198 obj.app.post(url + 'uploadmeshcorefile.ashx', handleUploadMeshCoreFile);
3199 obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
3200 obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
3201 + obj.app.ws(url+'apf.ashx', function (ws, req) { obj.parent.apfserver.onConnection(ws);})
3202 obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); }); });
3203 obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
3204 obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking.