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.