forked from Ylianst/MeshCentral
-
Notifications
You must be signed in to change notification settings - Fork 0
/
webserver.js
2272 lines (2055 loc) · 142 KB
/
webserver.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @description Meshcentral web server
* @author Ylian Saint-Hilaire
* @version v0.0.1
*/
"use strict";
/*
class SerialTunnel extends require('stream').Duplex {
constructor(options) { super(options); this.forwardwrite = null; }
updateBuffer(chunk) { this.push(chunk); }
_write(chunk, encoding, callback) { if (this.forwardwrite != null) { this.forwardwrite(chunk); } else { console.err("Failed to fwd _write."); } if (callback) callback(); } // Pass data written to forward
_read(size) { } // Push nothing, anything to read should be pushed from updateBuffer()
}
*/
// Older NodeJS does not support the keyword "class", so we do without using this syntax
// TODO: Validate that it's the same as above and that it works.
function SerialTunnel(options) {
var obj = new require('stream').Duplex(options);
obj.forwardwrite = null;
obj.updateBuffer = function (chunk) { this.push(chunk); }
obj._write = function (chunk, encoding, callback) { if (obj.forwardwrite != null) { obj.forwardwrite(chunk); } else { console.err("Failed to fwd _write."); } if (callback) callback(); } // Pass data written to forward
obj._read = function(size) { } // Push nothing, anything to read should be pushed from updateBuffer()
return obj;
}
// ExpressJS login sample
// https://github.com/expressjs/express/blob/master/examples/auth/index.js
// Polyfill startsWith/endsWith for older NodeJS
if (!String.prototype.startsWith) { String.prototype.startsWith = function (searchString, position) { position = position || 0; return this.substr(position, searchString.length) === searchString; }; }
if (!String.prototype.endsWith) { String.prototype.endsWith = function (searchString, position) { var subjectString = this.toString(); if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } position -= searchString.length; var lastIndex = subjectString.lastIndexOf(searchString, position); return lastIndex !== -1 && lastIndex === position; }; }
// Construct a HTTP web server object
module.exports.CreateWebServer = function (parent, db, args, secret, certificates) {
var obj = {};
// Modules
obj.fs = require('fs');
obj.net = require('net');
obj.tls = require('tls');
obj.path = require('path');
obj.hash = require('./pass').hash;
obj.bodyParser = require('body-parser');
obj.session = require('express-session');
obj.exphbs = require('express-handlebars');
obj.crypto = require('crypto');
obj.common = require('./common.js');
obj.express = require('express');
obj.meshAgentHandler = require('./meshagent.js');
obj.meshRelayHandler = require('./meshrelay.js')
obj.interceptor = require('./interceptor');
// Variables
obj.parent = parent;
obj.filespath = parent.filespath;
obj.db = db;
obj.app = obj.express();
obj.app.use(require('compression')());
obj.tlsServer = null;
obj.tcpServer;
obj.certificates = certificates;
obj.args = args;
obj.users = {};
obj.meshes = {};
obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
// Perform hash on web certificate and agent certificate
obj.webCertificatHash = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'binary' });
obj.webCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
obj.agentCertificatAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
// Main lists
obj.wsagents = {};
obj.wssessions = {}; // UserId --> Array Of Sessions
obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
obj.wsPeerSessions = {}; // ServerId --> Array Of "UserId + SessionRnd"
obj.wsPeerSessions2 = {}; // "UserId + SessionRnd" --> ServerId
obj.wsPeerSessions3 = {}; // ServerId --> UserId --> [ SessionId ]
obj.sessionsCount = {}; // Merged session counters, used when doing server peering. UserId --> SessionCount
obj.wsrelays = {}; // Id -> Relay
obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
// Setup randoms
obj.crypto.randomBytes(32, function (err, buf) { obj.httpAuthRandom = buf; });
obj.crypto.randomBytes(16, function (err, buf) { obj.httpAuthRealm = buf.toString('hex'); });
obj.crypto.randomBytes(32, function (err, buf) { obj.relayRandom = buf; });
function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
if (obj.args.notls || obj.args.tlsoffload) {
// Setup the HTTP server without TLS
obj.expressWs = require('express-ws')(obj.app);
} else {
// Setup the HTTP server with TLS
//var certOperations = require('./certoperations.js').CertificateOperations();
//var webServerCert = certOperations.GetWebServerCertificate('./data', 'SampleServer.org', 'US', 'SampleOrg');
obj.tlsServer = require('https').createServer({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.calist, rejectUnauthorized: true }, obj.app);
obj.expressWs = require('express-ws')(obj.app, obj.tlsServer);
}
// Setup middleware
obj.app.engine('handlebars', obj.exphbs({})); // defaultLayout: 'main'
obj.app.set('view engine', 'handlebars');
obj.app.use(obj.bodyParser.urlencoded({ extended: false }));
obj.app.use(obj.session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: secret // If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances
}));
// Session-persisted message middleware
obj.app.use(function (req, res, next) {
if (req.session != null) {
var err = req.session.error;
var msg = req.session.success;
var passhint = req.session.passhint;
delete req.session.error;
delete req.session.success;
delete req.session.passhint;
}
res.locals.message = '';
if (err) res.locals.message = '<p class="msg error">' + err + '</p>';
if (msg) res.locals.message = '<p class="msg success">' + msg + '</p>';
if (passhint) res.locals.passhint = EscapeHtml(passhint);
next();
});
// Fetch all users from the database, keep this in memory
obj.db.GetAllType('user', function (err, docs) {
var domainUserCount = {};
for (var i in parent.config.domains) { domainUserCount[i] = 0; }
for (var i in docs) { var u = obj.users[docs[i]._id] = docs[i]; domainUserCount[u.domain]++; }
for (var i in parent.config.domains) {
if (domainUserCount[i] == 0) {
if (parent.config.domains[i].newAccounts == 0) { parent.config.domains[i].newAccounts = 2; }
console.log('Server ' + ((i == '') ? '' : (i + ' ')) + 'has no users, next new account will be site administrator.');
}
}
});
// Fetch all meshes from the database, keep this in memory
obj.db.GetAllType('mesh', function (err, docs) { for (var i in docs) { obj.meshes[docs[i]._id] = docs[i]; } });
// Authenticate the user
obj.authenticate = function (name, pass, domain, fn) {
if (!module.parent) console.log('authenticating %s:%s:%s', domain.id, name, pass);
var user = obj.users['user/' + domain.id + '/' + name.toLowerCase()];
// Query the db for the given username
if (!user) return fn(new Error('cannot find user'));
// Apply the same algorithm to the POSTed password, applying the hash against the pass / salt, if there is a match we found the user
if (user.salt == null) {
fn(new Error('invalid password'));
} else {
obj.hash(pass, user.salt, function (err, hash) {
if (err) return fn(err);
if (hash == user.hash) return fn(null, user._id);
fn(new Error('invalid password'), null, user.passhint);
});
}
}
/*
obj.restrict = function (req, res, next) {
console.log('restrict', req.url);
var domain = getDomain(req);
if (req.session.userid) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect(domain.url + 'login');
}
}
*/
// Check if the source IP address is allowed, return false if not
function checkUserIpAddress(req, res) {
if (obj.userAllowedIp == null) { return true; }
try {
if (typeof obj.userAllowedIp == 'string') { if (obj.userAllowedIp == "") { obj.userAllowedIp = null; return true; } else { obj.userAllowedIp = obj.userAllowedIp.split(','); } }
var ip = null, type = 0;
if (req.connection) { ip = req.connection.remoteAddress; type = 1; } // HTTP(S) request
else if (req._socket) { ip = req._socket.remoteAddress; type = 2; } // WebSocket request
if (ip.startsWith('::ffff:')) { ip = ip.substring(7); } // Fix IPv4 IP's encoded in IPv6 form
if ((ip != null) && (obj.userAllowedIp.indexOf(ip) >= 0)) { return true; }
if (type == 1) { res.sendStatus(401); }
else if (type == 2) { try { req.close(); } catch (e) { } }
} catch (e) { console.log(e); }
return false;
}
// Return the current domain of the request
function getDomain(req) {
var x = req.url.split('/');
if (x.length < 2) return parent.config.domains[''];
if (parent.config.domains[x[1].toLowerCase()]) return parent.config.domains[x[1].toLowerCase()];
return parent.config.domains[''];
}
function handleLogoutRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
// Destroy the user's session to log them out will be re-created next request
if (req.session.userid) {
var user = obj.users[req.session.userid]
obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, action: 'logout', msg: 'Account logout', domain: domain.id })
}
req.session.destroy(function () {
res.redirect(domain.url);
});
}
function handleLoginRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
obj.authenticate(req.body.username, req.body.password, domain, function (err, userid, passhint) {
if (userid) {
var user = obj.users[userid];
// Save login time
user.login = Date.now();
obj.db.SetUser(user);
// Regenerate session when signing in to prevent fixation
req.session.regenerate(function () {
// Store the user's primary key in the session store to be retrieved, or in this case the entire user object
// req.session.success = 'Authenticated as ' + user.name + 'click to <a href="/logout">logout</a>. You may now access <a href="/restricted">/restricted</a>.';
delete req.session.loginmode;
req.session.userid = userid;
req.session.domainid = domain.id;
req.session.currentNode = '';
if (req.session.passhint) { delete req.session.passhint; }
if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
if (req.body.host) {
obj.db.GetAllType('node', function (err, docs) {
for (var i = 0; i < docs.length; i++) {
if (docs[i].name == req.body.host) {
req.session.currentNode = docs[i]._id;
break;
}
}
console.log("CurrentNode: " + req.session.currentNode);
// This redirect happens after finding node is completed
res.redirect(domain.url);
});
} else {
res.redirect(domain.url);
}
});
obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, action: 'login', msg: 'Account login', domain: domain.id })
} else {
delete req.session.loginmode;
req.session.error = '<b style=color:#8C001A>Login failed, check username and password.</b>';
if ((passhint != null) && (passhint.length > 0)) {
req.session.passhint = passhint;
} else {
if (req.session.passhint) { delete req.session.passhint; }
}
res.redirect(domain.url);
}
});
}
function handleCreateAccountRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
if (domain.newAccounts == 0) { res.sendStatus(401); return; }
if (!req.body.username || !req.body.email || !req.body.password1 || !req.body.password2 || (req.body.password1 != req.body.password2) || req.body.username == '~') {
req.session.loginmode = 2;
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>';;
res.redirect(domain.url);
} else {
// Check if there is domain.newAccountToken, check if supplied token is valid
if ((domain.newAccountsPass != null) && (domain.newAccountsPass != '') && (req.body.anewaccountpass != domain.newAccountsPass)) {
req.session.loginmode = 2;
req.session.error = '<b style=color:#8C001A>Invalid account creation token.</b>';
res.redirect(domain.url);
return;
}
// Check if user exists
if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
req.session.loginmode = 2;
req.session.error = '<b style=color:#8C001A>Username already exists.</b>';
} else {
var hint = req.body.apasswordhint;
if (hint.length > 250) hint = hint.substring(0, 250);
var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Date.now(), login: Date.now(), domain: domain.id, passhint: hint };
var usercount = 0;
for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newAccounts == 2) { domain.newAccounts = 0; } } // If this is the first user, give the account site admin.
obj.users[user._id] = user;
req.session.userid = user._id;
req.session.domainid = domain.id;
// Create a user, generate a salt and hash the password
obj.hash(req.body.password1, function (err, salt, hash) {
if (err) throw err;
user.salt = salt;
user.hash = hash;
obj.db.SetUser(user);
});
obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: user, action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id })
}
res.redirect(domain.url);
}
}
function handleDeleteAccountRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
// Check if the user is logged and we have all required parameters
if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url); return; }
var user = obj.users[req.session.userid];
if (!user) return;
// Check if the password is correct
obj.authenticate(user.name, req.body.apassword1, domain, function (err, userid) {
var user = obj.users[userid];
if (user) {
obj.db.Remove(user._id);
delete obj.users[user._id];
req.session.destroy(function () { res.redirect(domain.url); });
obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, action: 'accountremove', msg: 'Account removed', domain: domain.id })
} else {
res.redirect(domain.url);
}
});
}
// Handle password changes
function handlePasswordChangeRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
// Check if the user is logged and we have all required parameters
if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url); return; }
// Update the password
obj.hash(req.body.apassword1, function (err, salt, hash) {
if (err) throw err;
var hint = req.body.apasswordhint;
if (hint.length > 250) hint = hint.substring(0, 250);
var user = obj.users[req.session.userid];
user.salt = salt;
user.hash = hash;
user.passchange = Date.now();
user.passhint = req.body.apasswordhint;
obj.db.SetUser(user);
req.session.viewmode = 2;
res.redirect(domain.url);
obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, action: 'passchange', msg: 'Account password changed: ' + user.name, domain: domain.id })
});
}
// Indicates that any request to "/" should render "default" or "login" depending on login state
function handleRootRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
if (!obj.args) { res.sendStatus(500); return; }
var domain = getDomain(req);
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
// Check if we have an incomplete domain name in the path
if (domain.id != '' && req.url.split('/').length == 2) { res.redirect(domain.url); return; }
if (obj.args.nousers == true) {
// If in single user mode, setup things here.
if (req.session && req.session.loginmode) { delete req.session.loginmode; }
req.session.userid = 'user/' + domain.id + '/~';
req.session.domainid = domain.id;
req.session.currentNode = '';
if (obj.users[req.session.userid] == null) {
// Create the dummy user ~ with impossible password
obj.users[req.session.userid] = { type: 'user', _id: req.session.userid, name: '~', email: '~', domain: domain.id, siteadmin: 0xFFFFFFFF };
obj.db.SetUser(obj.users[req.session.userid]);
}
} else if (obj.args.user && (!req.session || !req.session.userid) && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
// If a default user is active, setup the session here.
if (req.session && req.session.loginmode) { delete req.session.loginmode; }
req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
req.session.domainid = domain.id;
req.session.currentNode = '';
}
// If a user is logged in, serve the default app, otherwise server the login app.
if (req.session && req.session.userid) {
if (req.session.domainid != domain.id) { req.session.destroy(function () { res.redirect(domain.url); }); return; } // Check is the session is for the correct domain
var viewmode = 1;
if (req.session.viewmode) {
viewmode = req.session.viewmode;
delete req.session.viewmode;
}
var currentNode = '';
if (req.session.currentNode) {
currentNode = req.session.currentNode;
delete req.session.currentNode;
}
var user;
var logoutcontrol;
if (!obj.args.nousers) {
user = obj.users[req.session.userid]
logoutcontrol = 'Welcome ' + user.name + '.';
}
var features = 0;
if (obj.args.wanonly == true) { features += 1; } // WAN-only mode
if (obj.args.lanonly == true) { features += 2; } // LAN-only mode
if (obj.args.nousers == true) { features += 4; } // Single user mode
if (domain.userQuota == -1) { features += 8; } // No server files mode
if (obj.args.tlsoffload == true) { features += 16; } // No mutual-auth CIRA
if ((!obj.args.user) && (!obj.args.nousers)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.certificates.CommonName, serverPublicPort: args.port, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass });
} else {
// Send back the login application
res.render(obj.path.join(__dirname, 'views/login'), { loginmode: req.session.loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newAccounts, newAccountPass: (((domain.newAccountsPass == null) || (domain.newAccountsPass == ''))?0:1), serverDnsName: obj.certificates.CommonName, serverPublicPort: obj.args.port });
}
}
// Get the link to the root certificate if needed
function getRootCertLink() {
// TODO: This is not quite right, we need to check if the HTTPS certificate is issued from MeshCentralRoot, if so, add this download link.
if (obj.args.notls == null && obj.certificates.RootName.substring(0, 16) == 'MeshCentralRoot-') { return '<a href=/MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>'; }
return '';
}
// Renter the terms of service.
function handleTermsRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
if (req.session && req.session.userid) {
if (req.session.domainid != domain.id) { req.session.destroy(function () { res.redirect(domain.url); }); return; } // Check is the session is for the correct domain
var user = obj.users[req.session.userid];
res.render(obj.path.join(__dirname, 'views/terms'), { logoutControl: 'Welcome ' + user.name + '. <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>' });
} else {
res.render(obj.path.join(__dirname, 'views/terms'), { title: domain.title, title2: domain.title2 });
}
}
// Returns the server root certificate encoded in base64
function getRootCertBase64() {
var rootcert = obj.certificates.root.cert;
var i = rootcert.indexOf("-----BEGIN CERTIFICATE-----\r\n");
if (i >= 0) { rootcert = rootcert.substring(i + 29); }
i = rootcert.indexOf("-----END CERTIFICATE-----");
if (i >= 0) { rootcert = rootcert.substring(i, 0); }
return new Buffer(rootcert, 'base64').toString('base64');
}
// Returns the mesh server root certificate
function handleRootCertRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=' + certificates.RootName + '.cer' });
res.send(new Buffer(getRootCertBase64(), 'base64'));
}
// Returns an mescript for Intel AMT configuration
function handleMeScriptRequest(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
if (req.query.type == 1) {
var filename = 'cira_setup.mescript';
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=' + filename });
var serverNameSplit = obj.certificates.CommonName.split('.');
if ((serverNameSplit.length == 4) && (parseInt(serverNameSplit[0]) == serverNameSplit[0]) && (parseInt(serverNameSplit[1]) == serverNameSplit[1]) && (parseInt(serverNameSplit[2]) == serverNameSplit[2]) && (parseInt(serverNameSplit[3]) == serverNameSplit[3])) {
// Server name is an IPv4 address
var filepath = obj.parent.path.join(__dirname, 'public/scripts/cira_setup_script_ip.mescript');
readEntireTextFile(filepath, function (data) {
if (data == null) { res.sendStatus(404); return; }
var scriptFile = JSON.parse(data);
// Change a few things in the script
scriptFile.scriptBlocks[2].vars.CertBin.value = getRootCertBase64(); // Set the root certificate
scriptFile.scriptBlocks[3].vars.IP.value = obj.certificates.CommonName; // Set the server IPv4 address name
scriptFile.scriptBlocks[3].vars.ServerName.value = obj.certificates.CommonName; // Set the server certificate name
scriptFile.scriptBlocks[3].vars.Port.value = obj.args.mpsport; // Set the server MPS port
scriptFile.scriptBlocks[3].vars.username.value = req.query.meshid; // Set the username
scriptFile.scriptBlocks[3].vars.password.value = obj.args.mpspass ? obj.args.mpspass : 'A@xew9rt'; // Set the password
scriptFile.scriptBlocks[4].vars.AccessInfo1.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the primary server name:port to set periodic timer
//scriptFile.scriptBlocks[4].vars.AccessInfo2.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the secondary server name:port to set periodic timer
if (obj.args.ciralocalfqdn != null) { scriptFile.scriptBlocks[6].vars.DetectionStrings.value = obj.args.ciralocalfqdn; } // Set the environment detection local FQDN's
// Compile the script
var scriptEngine = require('./amtscript.js').CreateAmtScriptEngine();
var runscript = scriptEngine.script_blocksToScript(scriptFile.blocks, scriptFile.scriptBlocks);
scriptFile.mescript = new Buffer(scriptEngine.script_compile(runscript), 'binary').toString('base64');
// Send the script
res.send(new Buffer(JSON.stringify(scriptFile, null, ' ')));
});
} else {
// Server name is a hostname
var filepath = obj.parent.path.join(__dirname, 'public/scripts/cira_setup_script_dns.mescript');
readEntireTextFile(filepath, function (data) {
if (data == null) { res.sendStatus(404); return; }
var scriptFile = JSON.parse(data);
// Change a few things in the script
scriptFile.scriptBlocks[2].vars.CertBin.value = getRootCertBase64(); // Set the root certificate
scriptFile.scriptBlocks[3].vars.FQDN.value = obj.certificates.CommonName; // Set the server DNS name
scriptFile.scriptBlocks[3].vars.Port.value = obj.args.mpsport; // Set the server MPS port
scriptFile.scriptBlocks[3].vars.username.value = req.query.meshid; // Set the username
scriptFile.scriptBlocks[3].vars.password.value = obj.args.mpspass ? obj.args.mpspass : 'A@xew9rt'; // Set the password
scriptFile.scriptBlocks[4].vars.AccessInfo1.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the primary server name:port to set periodic timer
//scriptFile.scriptBlocks[4].vars.AccessInfo2.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the secondary server name:port to set periodic timer
if (obj.args.ciralocalfqdn != null) { scriptFile.scriptBlocks[6].vars.DetectionStrings.value = obj.args.ciralocalfqdn; } // Set the environment detection local FQDN's
// Compile the script
var scriptEngine = require('./amtscript.js').CreateAmtScriptEngine();
var runscript = scriptEngine.script_blocksToScript(scriptFile.blocks, scriptFile.scriptBlocks);
scriptFile.mescript = new Buffer(scriptEngine.script_compile(runscript), 'binary').toString('base64');
// Send the script
res.send(new Buffer(JSON.stringify(scriptFile, null, ' ')));
});
}
}
else if (req.query.type == 2) {
var filename = 'cira_cleanup.mescript';
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=' + filename });
var filepath = obj.parent.path.join(__dirname, 'public/scripts/cira_cleanup.mescript');
readEntireTextFile(filepath, function (data) {
if (data == null) { res.sendStatus(404); return; }
res.send(new Buffer(data));
});
}
}
// Handle user public file downloads
function handleDownloadUserFiles(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req), domainname = 'domain', spliturl = decodeURIComponent(req.path).split('/'), filename = '';
if ((spliturl.length < 3) || (obj.common.IsFilenameValid(spliturl[2]) == false) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
if (domain.id != '') { domainname = 'domain-' + domain.id; }
var path = obj.path.join(obj.filespath, domainname + "/user-" + spliturl[2] + "/Public");
for (var i = 3; i < spliturl.length; i++) { if (obj.common.IsFilenameValid(spliturl[i]) == true) { path += '/' + spliturl[i]; filename = spliturl[i]; } else { res.sendStatus(404); return; } }
var stat = null;
try { stat = obj.fs.statSync(path) } catch (e) { }
if ((stat != null) && ((stat.mode & 0x004000) == 0)) {
if (req.query.download == 1) {
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=\"' + filename + '\"' });
try { res.sendFile(obj.path.resolve(__dirname, path)); } catch (e) { res.sendStatus(404); }
} else {
res.render(obj.path.join(__dirname, 'views/download'), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, message: "<a href='" + req.path + "?download=1'>" + filename + "</a>, " + stat.size + " byte" + ((stat.size < 2)?'':'s') + "." });
}
} else {
res.render(obj.path.join(__dirname, 'views/download'), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, message: "Invalid file link, please check the URL again." });
}
}
// Download a file from the server
function handleDownloadFile(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
if ((req.query.link == null) || (req.session == null) || (req.session.userid == null) || (domain == null) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
var user = obj.users[req.session.userid];
if (user == null) { res.sendStatus(404); return; }
var file = getServerFilePath(user, domain, req.query.link);
if (file == null) { res.sendStatus(404); return; }
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=\"' + file.name + '\"' });
try { res.sendFile(file.fullpath); } catch (e) { res.sendStatus(404); }
}
// Upload a MeshCore.js file to the server
function handleUploadMeshCoreFile(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
var user = obj.users[req.session.userid];
if (user.siteadmin != 0xFFFFFFFF) { res.sendStatus(401); return; } // Check if we have mesh core upload rights (Full admin only)
var multiparty = require('multiparty');
var form = new multiparty.Form();
form.parse(req, function (err, fields, files) {
if ((fields == null) || (fields.attrib == null) || (fields.attrib.length != 1)) { res.sendStatus(404); return; }
for (var i in files.files) {
var file = files.files[i];
readEntireTextFile(file.path, function (data) {
if (data == null) return;
data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
sendMeshAgentCore(user, domain, fields.attrib[0], data); // Upload the core
try { obj.fs.unlinkSync(file.path); } catch (e) { }
});
}
res.send('');
});
}
// Upload a file to the server
function handleUploadFile(req, res) {
if (checkUserIpAddress(req, res) == false) { return; }
var domain = getDomain(req);
if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid) || (domain.userQuota == -1)) { res.sendStatus(401); return; }
var user = obj.users[req.session.userid];
if ((user.siteadmin & 8) == 0) { res.sendStatus(401); return; } // Check if we have file rights
var multiparty = require('multiparty');
var form = new multiparty.Form();
form.parse(req, function (err, fields, files) {
if ((fields == null) || (fields.link == null) || (fields.link.length != 1)) { res.sendStatus(404); return; }
var xfile = getServerFilePath(user, domain, decodeURIComponent(fields.link[0]));
if (xfile == null) { res.sendStatus(404); return; }
// Get total bytes in the path
var totalsize = readTotalFileSize(xfile.fullpath);
if (totalsize < xfile.quota) { // Check if the quota is not already broken
if (fields.name != null) {
// Upload method where all the file data is within the fields.
var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*');
if ((names.length == sizes.length) && (types.length == datas.length) && (names.length == types.length)) {
for (var i = 0; i < names.length; i++) {
if (obj.common.IsFilenameValid(names[i]) == false) { res.sendStatus(404); return; }
var filedata = new Buffer(datas[i].split(',')[1], 'base64');
if ((totalsize + filedata.length) < xfile.quota) { // Check if quota would not be broken if we add this file
obj.fs.writeFileSync(xfile.fullpath + '/' + names[i], filedata);
}
}
}
} else {
// More typical upload method, the file data is in a multipart mime post.
for (var i in files.files) {
var file = files.files[i], fpath = xfile.fullpath + '/' + file.originalFilename;
if (obj.common.IsFilenameValid(file.originalFilename) && ((totalsize + file.size) < xfile.quota)) { // Check if quota would not be broken if we add this file
obj.fs.rename(file.path, fpath);
} else {
try { obj.fs.unlinkSync(file.path); } catch (e) { }
}
}
}
}
res.send('');
obj.parent.DispatchEvent([user._id], obj, 'updatefiles') // Fire an event causing this user to update this files
});
}
// Subscribe to all events we are allowed to receive
obj.subscribe = function (userid, target) {
var user = obj.users[userid];
var subscriptions = [userid, 'server-global'];
if (user.siteadmin != null) {
if (user.siteadmin == 0xFFFFFFFF) subscriptions.push('*');
if ((user.siteadmin & 2) != 0) subscriptions.push('server-users');
}
if (user.links != null) {
for (var i in user.links) { subscriptions.push(i); }
}
obj.parent.RemoveAllEventDispatch(target);
obj.parent.AddEventDispatch(subscriptions, target);
return subscriptions;
}
// Handle a web socket relay request
function handleRelayWebSocket(ws, req) {
if (checkUserIpAddress(ws, req) == false) { return; }
var node, domain = getDomain(req);
// Check if this is a logged in user
var user, peering = true;
if (req.query.auth == null) {
// Use ExpressJS session
if (!req.session || !req.session.userid) { return; } // Web socket attempt without login, disconnect.
if (req.session.domainid != domain.id) { console.log('ERR: Invalid domain'); return; }
user = obj.users[req.session.userid];
} else {
// Get the session from the cookie
if (obj.parent.multiServer == null) { return; }
var session = obj.parent.multiServer.decodeCookie(req.query.auth);
if (session == null) { console.log('ERR: Invalid cookie'); return; }
if (session.domainid != domain.id) { console.log('ERR: Invalid domain'); return; }
user = obj.users[session.userid];
peering = false; // Don't allow the connection to jump again to a different server
}
if (!user) { console.log('ERR: Not a user'); return; }
Debug(1, 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.');
// Hold this socket until we are ready.
ws.pause();
// Fetch information about the target
obj.db.Get(req.query.host, function (err, docs) {
if (docs.length == 0) { console.log('ERR: Node not found'); return; }
node = docs[0];
if (!node.intelamt) { console.log('ERR: Not AMT node'); return; }
// Check if this user has permission to manage this computer
var meshlinks = user.links[node.meshid];
if (!meshlinks || !meshlinks.rights) { console.log('ERR: Access denied (1)'); return; }
if ((meshlinks.rights & 8) == 0) { console.log('ERR: Access denied (2)'); return; }
// Check what connectivity is available for this node
var state = parent.GetConnectivityState(req.query.host);
var conn = 0;
if (!state || state.connectivity == 0) { Debug(1, 'ERR: No routing possible (1)'); try { ws.close(); } catch (e) { } return; } else { conn = state.connectivity; }
// Check what server needs to handle this connection
if ((obj.parent.multiServer != null) && (peering == true)) {
var server = obj.parent.GetRoutingServerId(req.query.host, 2); // Check for Intel CIRA connection
if (server != null) {
if (server.serverid != obj.parent.serverId) {
// Do local Intel CIRA routing using a different server
Debug(1, 'Route Intel AMT CIRA connection to peer server: ' + server.serverid);
obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
return;
}
} else {
server = obj.parent.GetRoutingServerId(req.query.host, 4); // Check for local Intel AMT connection
if ((server != null) && (server.serverid != obj.parent.serverId)) {
// Do local Intel AMT routing using a different server
Debug(1, 'Route Intel AMT direct connection to peer server: ' + server.serverid);
obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
return;
}
}
}
// If Intel AMT CIRA connection is available, use it
if (((conn & 2) != 0) && (parent.mpsserver.ciraConnections[req.query.host] != null)) {
var ciraconn = parent.mpsserver.ciraConnections[req.query.host];
// Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
var port = 16993;
//if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA
if (ciraconn.tag.boundPorts.indexOf(16992) >= 0) port = 16992; // RELEASE: Always use non-TLS mode if available within CIRA
if (req.query.p == 2) port += 2;
// Setup a new CIRA channel
if ((port == 16993) || (port == 16995)) {
// Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why )
var ser = new SerialTunnel();
var chnl = parent.mpsserver.SetupCiraChannel(ciraconn, port);
// let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
// Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
ser.forwardwrite = function (msg) {
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
// TLS ---> CIRA
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
chnl.write(msg2);
};
// When APF tunnel return something, update SerialTunnel buffer
chnl.onData = function (ciraconn, data) {
// CIRA ---> TLS
Debug(3, 'Relay TLS CIRA data', data.length);
if (data.length > 0) { try { ser.updateBuffer(Buffer.from(data, 'binary')); } catch (e) { } }
}
// Handke CIRA tunnel state change
chnl.onStateChange = function (ciraconn, state) {
Debug(2, 'Relay TLS CIRA state change', state);
if (state == 0) { try { ws.close(); } catch (e) { } }
}
// TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
var TLSSocket = require('tls').TLSSocket;
var tlsock = new TLSSocket(ser, { secureProtocol: 'SSLv23_method', rejectUnauthorized: false }); // TLSv1_2_method
tlsock.on('error', function (err) { Debug(1, "CIRA TLS Connection Error ", err); });
tlsock.on('secureConnect', function () { Debug(2, "CIRA Secure TLS Connection"); ws.resume(); });
// Decrypted tunnel from TLS communcation to be forwarded to websocket
tlsock.on('data', function (data) {
// AMT/TLS ---> WS
try {
var data2 = "";
for (var i = 0; i < data.length; i++) { data2 += String.fromCharCode(data[i]); }
if (ws.interceptor) { data2 = ws.interceptor.processAmtData(data2); } // Run data thru interceptor
ws.send(data2);
} catch (e) { }
});
// If TLS is on, forward it through TLSSocket
ws.forwardclient = tlsock;
ws.forwardclient.xtls = 1;
} else {
// Without TLS
ws.forwardclient = parent.mpsserver.SetupCiraChannel(ciraconn, port);
ws.forwardclient.xtls = 0;
ws.resume();
}
// When data is received from the web socket, forward the data into the associated CIRA cahnnel.
// If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
ws.on('message', function (msg) {
// WS ---> AMT/TLS
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor
if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg2, 'binary')); } else { ws.forwardclient.write(msg2); }
});
// If error, do nothing
ws.on('error', function (err) { console.log(err); });
// If the web socket is closed, close the associated TCP connection.
ws.on('close', function (req) {
Debug(1, 'Websocket relay closed.');
if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
});
ws.forwardclient.onStateChange = function (ciraconn, state) {
Debug(2, 'Relay CIRA state change', state);
if (state == 0) { try { ws.close(); } catch (e) { } }
}
ws.forwardclient.onData = function (ciraconn, data) {
Debug(3, 'Relay CIRA data', data.length);
if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
if (data.length > 0) { try { ws.send(data); } catch (e) { } } // TODO: Add TLS support
}
ws.forwardclient.onSendOk = function (ciraconn) {
// TODO: Flow control? (Dont' really need it with AMT, but would be nice)
//console.log('onSendOk');
}
// Fetch Intel AMT credentials & Setup interceptor
if (req.query.p == 1) {
Debug(3, 'INTERCEPTOR1', { host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
}
else if (req.query.p == 2) {
Debug(3, 'INTERCEPTOR2', { user: node.intelamt.user, pass: node.intelamt.pass });
ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass });
}
return;
}
// If Intel AMT direct connection is possible, option a direct socket
if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
Debug(2, 'Opening relay TCP socket connection to ' + req.query.host + '.');
// When data is received from the web socket, forward the data into the associated TCP connection.
ws.on('message', function (msg) {
Debug(1, 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes'); // DEBUG
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor
ws.forwardclient.write(new Buffer(msg2, "ascii")); // Forward data to the associated TCP connection.
});
// If error, do nothing
ws.on('error', function (err) { console.log(err); });
// If the web socket is closed, close the associated TCP connection.
ws.on('close', function (req) {
Debug(1, 'Closing relay web socket connection to ' + ws.upgradeReq.query.host + '.');
if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
});
// Compute target port
var port = 16992;
if (node.intelamt.tls > 0) port = 16993; // This is a direct connection, use TLS when possible
if (req.query.p == 2) port += 2;
if (node.intelamt.tls == 0) {
// If this is TCP (without TLS) set a normal TCP socket
ws.forwardclient = new obj.net.Socket();
ws.forwardclient.setEncoding('binary');
ws.forwardclient.xstate = 0;
ws.forwardclient.forwardwsocket = ws;
ws.resume();
} else {
// If TLS is going to be used, setup a TLS socket
ws.forwardclient = obj.tls.connect(port, node.host, { secureProtocol: 'TLSv1_method', rejectUnauthorized: false }, function () {
// The TLS connection method is the same as TCP, but located a bit differently.
Debug(2, 'TLS connected to ' + node.host + ':' + port + '.');
ws.forwardclient.xstate = 1;
ws.resume();
});
ws.forwardclient.setEncoding('binary');
ws.forwardclient.xstate = 0;
ws.forwardclient.forwardwsocket = ws;
}
// When we receive data on the TCP connection, forward it back into the web socket connection.
ws.forwardclient.on('data', function (data) {
Debug(1, 'TCP relay data from ' + node.host + ', ' + data.length + ' bytes.'); // DEBUG
if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
try { ws.send(new Buffer(data, 'binary')); } catch (e) { }
});
// If the TCP connection closes, disconnect the associated web socket.
ws.forwardclient.on('close', function () {
Debug(1, 'TCP relay disconnected from ' + node.host + '.');
try { ws.close(); } catch (e) { }
});
// If the TCP connection causes an error, disconnect the associated web socket.
ws.forwardclient.on('error', function (err) {
Debug(1, 'TCP relay error from ' + node.host + ': ' + err.errno);
try { ws.close(); } catch (e) { }
});
// Fetch Intel AMT credentials & Setup interceptor
if (req.query.p == 1) { ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); }
else if (req.query.p == 2) { ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass }); }
if (node.intelamt.tls == 0) {
// A TCP connection to Intel AMT just connected, start forwarding.
ws.forwardclient.connect(port, node.host, function () {
Debug(1, 'TCP relay connected to ' + node.host + ':' + port + '.');
ws.forwardclient.xstate = 1;
ws.resume();
});
}
return;
}
});
}
// Handle the web socket echo request, just echo back the data sent
function handleEchoWebSocket(ws, req) {
if (checkUserIpAddress(ws, req) == false) { return; }
// When data is received from the web socket, echo it back
ws.on('message', function (data) {
var cmd = data.toString('utf8');
if (cmd == 'close') {
try { ws.close(); } catch (e) { console.log(e); }
} else {
try { ws.send(data); } catch (e) { console.log(e); }
}
});
// If error, do nothing
ws.on('error', function (err) { console.log(err); });
// If closed, do nothing
ws.on('close', function (req) { });
}
// Read the folder and all sub-folders and serialize that into json.
function readFilesRec(path) {
var r = {}, dir = obj.fs.readdirSync(path);
for (var i in dir) {
var f = { t: 3, d: 111 };
var stat = obj.fs.statSync(path + '/' + dir[i])
if ((stat.mode & 0x004000) == 0) { f.s = stat.size; f.d = stat.mtime.getTime(); } else { f.t = 2; f.f = readFilesRec(path + '/' + dir[i]); }
r[dir[i]] = f;
}
return r;
}
// Get the total size of all files in a folder and all sub-folders
function readTotalFileSize(path) {
var r = 0, dir = obj.fs.readdirSync(path);
for (var i in dir) {
var stat = obj.fs.statSync(path + '/' + dir[i])
if ((stat.mode & 0x004000) == 0) { r += stat.size; } else { r += readTotalFileSize(path + '/' + dir[i]); }
}
return r;
}
// Delete a folder and all sub items.
function deleteFolderRec(path) {
if (obj.fs.existsSync(path) == false) return;
obj.fs.readdirSync(path).forEach(function (file, index) {
var pathx = path + "/" + file;
if (obj.fs.lstatSync(pathx).isDirectory()) { deleteFolderRec(pathx); } else { obj.fs.unlinkSync(pathx); }
});
obj.fs.rmdirSync(path);
};
// Indicates we want to handle websocket requests on "/control.ashx".
function handleControlRequest(ws, req) {
if (checkUserIpAddress(ws, req) == false) { return; }
var domain = getDomain(req);
try {
// Check if the user is logged in
if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) { try { ws.close(); } catch (e) { } return; }
req.session.ws = ws; // Associate this websocket session with the web session
req.session.ws.userid = req.session.userid;
req.session.ws.domainid = domain.id;
var user = obj.users[req.session.userid];
if (user == null) { try { ws.close(); } catch (e) { } return; }
// Add this web socket session to session list
ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2);
obj.wssessions2[ws.sessionId] = ws;
if (!obj.wssessions[user._id]) { obj.wssessions[user._id] = [ws]; } else { obj.wssessions[user._id].push(ws); }
if (obj.parent.multiServer == null) {
obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[user._id].length, nolog: 1, domain: domain.id })
} else {
obj.recountSessions(ws.sessionId); // Recount sessions
}
// If we have peer servers, inform them of the new session
if (obj.parent.multiServer != null) { obj.parent.multiServer.DispatchMessage({ action: 'sessionStart', sessionid: ws.sessionId }); }
// Handle events
ws.HandleEvent = function (source, event) {
if (!event.domain || event.domain == domain.id) {
try {
if (event == 'close') { req.session.destroy(); ws.close(); }
else if (event == 'resubscribe') { user.subscriptions = obj.subscribe(user._id, ws); }
else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); }
else { ws.send(JSON.stringify({ action: 'event', event: event })); }
} catch (e) { }