Relayed device group fixes.
Ylian Saint-Hilaire committed
Apr 14, 2022 at 16:41 UTC
6b828b10e9153a98858ae83ae0461de8da696285
6 files changed
+8
-16422
meshcentral.js
+1
-1
@@ -3500,7 +3500,7 @@ function mainStart() {
3500
if (config.domains[i].yubikey != null) { yubikey = true; }
3501
if (config.domains[i].auth == 'ldap') { ldap = true; }
3502
if (config.domains[i].mstsc == true) { mstsc = true; }
3503
- if (config.domains[i].ssh == true) { if (nodeVersion < 11) { config.domains[i].ssh = false; } else { ssh = true; } }
3503
+ if (config.domains[i].ssh == true) { if (nodeVersion < 11) { config.domains[i].ssh = false; } ssh = true; }
3504
if ((typeof config.domains[i].authstrategies == 'object')) {
3505
if (passport == null) { passport = ['passport']; }
3506
if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); }
meshuser.js
+5
-2
@@ -2907,7 +2907,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2907
event.msg = ': ';
2908
2909
// If we are in WAN-only mode, host is not used
2910
- if ((args.wanonly == true) && (command.host)) { delete command.host; }
2910
+ if ((args.wanonly == true) && (command.host) && (node.mtype != 3) && (node.mtype != 4)) { delete command.host; }
2911
2912
// Look for a change
2913
if ((typeof command.icon == 'number') && (command.icon != node.icon)) { change = 1; node.icon = command.icon; changes.push('icon'); }
@@ -6674,7 +6674,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6674
try { info.uptime = process.uptime(); } catch (ex) { }
6675
try { info.cpuUsage = process.cpuUsage(); } catch (ex) { }
6676
try { info.memoryUsage = process.memoryUsage(); } catch (ex) { }
6677
- try { info.warnings = parent.parent.getServerWarnings(); } catch (ex) { }
6677
+ try {
6678
+ info.warnings = parent.parent.getServerWarnings();
6679
+ console.log(info.warnings);
6680
+ } catch (ex) { console.log(ex); }
6681
try { info.allDevGroupManagers = parent.parent.config.settings.managealldevicegroups; } catch (ex) { }
6682
try { if (process.traceDeprecation == true) { info.traceDeprecation = true; } } catch (ex) { }
6683
cmdData.result = JSON.stringify(info, null, 4);
views/default-mobile.handlebars
+1
-1
@@ -3376,7 +3376,7 @@
3376
if (node.rname != null) { x += addDeviceAttribute('<span>' + "Name" + '</span>', '<span>' + EscapeHtml(node.rname) + '</span>'); }
3377
3378
// Attribute: Host
3379
- if (((features & 1) == 0) && (node.mtype != 4)) { // If not WAN-only, local hostname is in use
3379
+ if ((((features & 1) == 0) && (node.mtype != 4)) || (node.mtype == 3)) { // If not WAN-only, local hostname is in use
3380
if ((meshrights & 4) != 0) {
3381
if (node.host) {
3382
x += addDeviceAttribute("Hostname", '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer>' + EscapeHtml(node.host) + '</span>');
views/default.handlebars
+1
-1
@@ -6757,7 +6757,7 @@
6757
if ((node.rname != null) && (node.name != node.rname)) { x += addDeviceAttribute('<span title="' + "The name of this computer as set in the operating system" + '">' + "OS Name" + '</span>', '<span title="' + "The name of this computer as set in the operating system" + '">' + EscapeHtml(node.rname) + '</span>'); }
6758
6759
// Attribute: Host
6760
- if (((features & 1) == 0) && (node.mtype != 4)) { // If not WAN-only, local hostname is in use
6760
+ if ((((features & 1) == 0) && (node.mtype != 4)) || (node.mtype == 3)) { // If not WAN-only, local hostname is in use
6761
x += addDeviceAttribute("Hostname", addLinkConditional('<span onclick=showEditNodeValueDialog(1) style=cursor:pointer>' + (node.host?EscapeHtml(node.host):('<i>' + "None" + '</i>')) + '</span>', 'showEditNodeValueDialog(1)', meshrights & 4));
6762
}
6763
webserver-broken.js
deleted
-8212
@@ -1,8212 +0,0 @@
1
-/**
2
-* @description MeshCentral web server
3
-* @author Ylian Saint-Hilaire
4
-* @copyright Intel Corporation 2018-2022
5
-* @license Apache-2.0
6
-* @version v0.0.1
7
-*/
8
-
9
-/*jslint node: true */
10
-/*jshint node: true */
11
-/*jshint strict:false */
12
-/*jshint -W097 */
13
-/*jshint esversion: 6 */
14
-'use strict';
15
-
16
-// SerialTunnel object is used to embed TLS within another connection.
17
-function SerialTunnel(options) {
18
- var obj = new require('stream').Duplex(options);
19
- obj.forwardwrite = null;
20
- obj.updateBuffer = function (chunk) { this.push(chunk); };
21
- 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
22
- obj._read = function (size) { }; // Push nothing, anything to read should be pushed from updateBuffer()
23
- return obj;
24
-}
25
-
26
-// ExpressJS login sample
27
-// https://github.com/expressjs/express/blob/master/examples/auth/index.js
28
-
29
-// Polyfill startsWith/endsWith for older NodeJS
30
-if (!String.prototype.startsWith) { String.prototype.startsWith = function (searchString, position) { position = position || 0; return this.substr(position, searchString.length) === searchString; }; }
31
-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; }; }
32
-
33
-// Construct a HTTP server object
34
-module.exports.CreateWebServer = function (parent, db, args, certificates, doneFunc) {
35
- var obj = {}, i = 0;
36
-
37
- // Modules
38
- obj.fs = require('fs');
39
- obj.net = require('net');
40
- obj.tls = require('tls');
41
- obj.path = require('path');
42
- obj.bodyParser = require('body-parser');
43
- obj.session = require('cookie-session');
44
- obj.exphbs = require('express-handlebars');
45
- obj.crypto = require('crypto');
46
- obj.common = require('./common.js');
47
- obj.express = require('express');
48
- obj.meshAgentHandler = require('./meshagent.js');
49
- obj.meshRelayHandler = require('./meshrelay.js');
50
- obj.meshDeviceFileHandler = require('./meshdevicefile.js');
51
- obj.meshDesktopMultiplexHandler = require('./meshdesktopmultiplex.js');
52
- obj.meshIderHandler = require('./amt/amt-ider.js');
53
- obj.meshUserHandler = require('./meshuser.js');
54
- obj.interceptor = require('./interceptor');
55
- obj.uaparser = require('./ua-parser');
56
- const constants = (obj.crypto.constants ? obj.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
57
-
58
- // Setup WebAuthn / FIDO2
59
- obj.webauthn = require('./webauthn.js').CreateWebAuthnModule();
60
-
61
- // Variables
62
- obj.args = args;
63
- obj.parent = parent;
64
- obj.filespath = parent.filespath;
65
- obj.db = db;
66
- obj.app = obj.express();
67
- if (obj.args.agentport) { obj.agentapp = obj.express(); }
68
- if (args.compression !== false) { obj.app.use(require('compression')()); }
69
- obj.app.disable('x-powered-by');
70
- obj.tlsServer = null;
71
- obj.tcpServer = null;
72
- obj.certificates = certificates;
73
- obj.users = {}; // UserID --> User
74
- obj.meshes = {}; // MeshID --> Mesh (also called device group)
75
- obj.userGroups = {}; // UGrpID --> User Group
76
- obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
77
- obj.agentAllowedIp = args.agentallowedip; // List of allowed IP addresses for agents
78
- obj.agentBlockedIp = args.agentblockedip; // List of blocked IP addresses for agents
79
- obj.tlsSniCredentials = null;
80
- obj.dnsDomains = {};
81
- obj.relaySessionCount = 0;
82
- obj.relaySessionErrorCount = 0;
83
- obj.blockedUsers = 0;
84
- obj.blockedAgents = 0;
85
- obj.renderPages = null;
86
- obj.renderLanguages = [];
87
- obj.destroyedSessions = {};
88
-
89
- // Mesh Rights
90
- const MESHRIGHT_EDITMESH = 0x00000001;
91
- const MESHRIGHT_MANAGEUSERS = 0x00000002;
92
- const MESHRIGHT_MANAGECOMPUTERS = 0x00000004;
93
- const MESHRIGHT_REMOTECONTROL = 0x00000008;
94
- const MESHRIGHT_AGENTCONSOLE = 0x00000010;
95
- const MESHRIGHT_SERVERFILES = 0x00000020;
96
- const MESHRIGHT_WAKEDEVICE = 0x00000040;
97
- const MESHRIGHT_SETNOTES = 0x00000080;
98
- const MESHRIGHT_REMOTEVIEWONLY = 0x00000100;
99
- const MESHRIGHT_NOTERMINAL = 0x00000200;
100
- const MESHRIGHT_NOFILES = 0x00000400;
101
- const MESHRIGHT_NOAMT = 0x00000800;
102
- const MESHRIGHT_DESKLIMITEDINPUT = 0x00001000;
103
- const MESHRIGHT_LIMITEVENTS = 0x00002000;
104
- const MESHRIGHT_CHATNOTIFY = 0x00004000;
105
- const MESHRIGHT_UNINSTALL = 0x00008000;
106
- const MESHRIGHT_NODESKTOP = 0x00010000;
107
- const MESHRIGHT_REMOTECOMMAND = 0x00020000;
108
- const MESHRIGHT_RESETOFF = 0x00040000;
109
- const MESHRIGHT_GUESTSHARING = 0x00080000;
110
- const MESHRIGHT_ADMIN = 0xFFFFFFFF;
111
-
112
- // Site rights
113
- const SITERIGHT_SERVERBACKUP = 0x00000001;
114
- const SITERIGHT_MANAGEUSERS = 0x00000002;
115
- const SITERIGHT_SERVERRESTORE = 0x00000004;
116
- const SITERIGHT_FILEACCESS = 0x00000008;
117
- const SITERIGHT_SERVERUPDATE = 0x00000010;
118
- const SITERIGHT_LOCKED = 0x00000020;
119
- const SITERIGHT_NONEWGROUPS = 0x00000040;
120
- const SITERIGHT_NOMESHCMD = 0x00000080;
121
- const SITERIGHT_USERGROUPS = 0x00000100;
122
- const SITERIGHT_RECORDINGS = 0x00000200;
123
- const SITERIGHT_LOCKSETTINGS = 0x00000400;
124
- const SITERIGHT_ALLEVENTS = 0x00000800;
125
- const SITERIGHT_NONEWDEVICES = 0x00001000;
126
- const SITERIGHT_ADMIN = 0xFFFFFFFF;
127
-
128
- // Setup SSPI authentication if needed
129
- if ((obj.parent.platform == 'win32') && (obj.args.nousers != true) && (obj.parent.config != null) && (obj.parent.config.domains != null)) {
130
- for (i in obj.parent.config.domains) { if (obj.parent.config.domains[i].auth == 'sspi') { var nodeSSPI = require('node-sspi'); obj.parent.config.domains[i].sspi = new nodeSSPI({ retrieveGroups: false, offerBasic: false }); } }
131
- }
132
-
133
- // Perform hash on web certificate and agent certificate
134
- obj.webCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert);
135
- obj.webCertificateHashs = { '': obj.webCertificateHash };
136
- obj.webCertificateHashBase64 = Buffer.from(obj.webCertificateHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
137
- obj.webCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert);
138
- obj.webCertificateFullHashs = { '': obj.webCertificateFullHash };
139
- obj.webCertificateExpire = { '': Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.web.cert).validity.notAfter) };
140
- obj.agentCertificateHashHex = parent.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert);
141
- obj.agentCertificateHashBase64 = Buffer.from(obj.agentCertificateHashHex, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
142
- obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
143
- obj.defaultWebCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.webdefault.cert);
144
- obj.defaultWebCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.webdefault.cert);
145
-
146
- // Compute the hash of all of the web certificates for each domain
147
- for (var i in obj.parent.config.domains) {
148
- if (obj.parent.config.domains[i].certhash != null) {
149
- // If the web certificate hash is provided, use it.
150
- obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i] = Buffer.from(obj.parent.config.domains[i].certhash, 'hex').toString('binary');
151
- if (obj.parent.config.domains[i].certkeyhash != null) { obj.webCertificateHashs[i] = Buffer.from(obj.parent.config.domains[i].certkeyhash, 'hex').toString('binary'); }
152
- delete obj.webCertificateExpire[i]; // Expire time is not provided
153
- } else if ((obj.parent.config.domains[i].dns != null) && (obj.parent.config.domains[i].certs != null)) {
154
- // If the domain has a different DNS name, use a different certificate hash.
155
- // Hash the full certificate
156
- obj.webCertificateFullHashs[i] = parent.certificateOperations.getCertHashBinary(obj.parent.config.domains[i].certs.cert);
157
- obj.webCertificateExpire[i] = Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(obj.parent.config.domains[i].certs.cert).validity.notAfter);
158
- try {
159
- // Decode a RSA certificate and hash the public key.
160
- obj.webCertificateHashs[i] = parent.certificateOperations.getPublicKeyHashBinary(obj.parent.config.domains[i].certs.cert);
161
- } catch (ex) {
162
- // This may be a ECDSA certificate, hash the entire cert.
163
- obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i];
164
- }
165
- } else if ((obj.parent.config.domains[i].dns != null) && (obj.certificates.dns[i] != null)) {
166
- // If this domain has a DNS and a matching DNS cert, use it. This case works for wildcard certs.
167
- obj.webCertificateFullHashs[i] = parent.certificateOperations.getCertHashBinary(obj.certificates.dns[i].cert);
168
- obj.webCertificateHashs[i] = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.dns[i].cert);
169
- obj.webCertificateExpire[i] = Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.dns[i].cert).validity.notAfter);
170
- } else if (i != '') {
171
- // For any other domain, use the default cert.
172
- obj.webCertificateFullHashs[i] = obj.webCertificateFullHashs[''];
173
- obj.webCertificateHashs[i] = obj.webCertificateHashs[''];
174
- obj.webCertificateExpire[i] = obj.webCertificateExpire[''];
175
- }
176
- }
177
-
178
- // If we are running the legacy swarm server, compute the hash for that certificate
179
- if (parent.certificates.swarmserver != null) {
180
- obj.swarmCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.swarmserver.cert))).getBytes();
181
- obj.swarmCertificateHash384 = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.swarmserver.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' });
182
- obj.swarmCertificateHash256 = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.swarmserver.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'binary' });
183
- }
184
-
185
- // Main lists
186
- obj.wsagents = {}; // NodeId --> Agent
187
- obj.wsagentsWithBadWebCerts = {}; // NodeId --> Agent
188
- obj.wsagentsDisconnections = {};
189
- obj.wsagentsDisconnectionsTimer = null;
190
- obj.duplicateAgentsLog = {};
191
- obj.wssessions = {}; // UserId --> Array Of Sessions
192
- obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
193
- obj.wsPeerSessions = {}; // ServerId --> Array Of "UserId + SessionRnd"
194
- obj.wsPeerSessions2 = {}; // "UserId + SessionRnd" --> ServerId
195
- obj.wsPeerSessions3 = {}; // ServerId --> UserId --> [ SessionId ]
196
- obj.sessionsCount = {}; // Merged session counters, used when doing server peering. UserId --> SessionCount
197
- obj.wsrelays = {}; // Id -> Relay
198
- obj.desktoprelays = {}; // Id -> Desktop Multiplexor Relay
199
- obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
200
- var tlsSessionStore = {}; // Store TLS session information for quick resume.
201
- var tlsSessionStoreCount = 0; // Number of cached TLS session information in store.
202
-
203
- // Setup randoms
204
- obj.crypto.randomBytes(48, function (err, buf) { obj.httpAuthRandom = buf; });
205
- obj.crypto.randomBytes(16, function (err, buf) { obj.httpAuthRealm = buf.toString('hex'); });
206
- obj.crypto.randomBytes(48, function (err, buf) { obj.relayRandom = buf; });
207
-
208
- // Get non-english web pages and emails
209
- getRenderList();
210
- getEmailLanguageList();
211
-
212
- // Setup DNS domain TLS SNI credentials
213
- {
214
- var dnscount = 0;
215
- obj.tlsSniCredentials = {};
216
- for (i in obj.certificates.dns) { if (obj.parent.config.domains[i].dns != null) { obj.dnsDomains[obj.parent.config.domains[i].dns.toLowerCase()] = obj.parent.config.domains[i]; obj.tlsSniCredentials[obj.parent.config.domains[i].dns] = obj.tls.createSecureContext(obj.certificates.dns[i]).context; dnscount++; } }
217
- if (dnscount > 0) { obj.tlsSniCredentials[''] = obj.tls.createSecureContext({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca }).context; } else { obj.tlsSniCredentials = null; }
218
- }
219
- function TlsSniCallback(name, cb) {
220
- var c = obj.tlsSniCredentials[name];
221
- if (c != null) {
222
- cb(null, c);
223
- } else {
224
- cb(null, obj.tlsSniCredentials['']);
225
- }
226
- }
227
-
228
- 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; }
229
- //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; }
230
- // Fetch all users from the database, keep this in memory
231
- obj.db.GetAllType('user', function (err, docs) {
232
- obj.common.unEscapeAllLinksFieldName(docs);
233
- var domainUserCount = {}, i = 0;
234
- for (i in parent.config.domains) { domainUserCount[i] = 0; }
235
- for (i in docs) { var u = obj.users[docs[i]._id] = docs[i]; domainUserCount[u.domain]++; }
236
- for (i in parent.config.domains) {
237
- if ((parent.config.domains[i].share == null) && (domainUserCount[i] == 0)) {
238
- // If newaccounts is set to no new accounts, but no accounts exists, temporarly allow account creation.
239
- //if ((parent.config.domains[i].newaccounts === 0) || (parent.config.domains[i].newaccounts === false)) { parent.config.domains[i].newaccounts = 2; }
240
- console.log('Server ' + ((i == '') ? '' : (i + ' ')) + 'has no users, next new account will be site administrator.');
241
- }
242
- }
243
-
244
- // Fetch all device groups (meshes) from the database, keep this in memory
245
- // As we load things in memory, we will also be doing some cleaning up.
246
- // We will not save any clean up in the database right now, instead it will be saved next time there is a change.
247
- obj.db.GetAllType('mesh', function (err, docs) {
248
- obj.common.unEscapeAllLinksFieldName(docs);
249
- for (var i in docs) { obj.meshes[docs[i]._id] = docs[i]; } // Get all meshes, including deleted ones.
250
-
251
- // Fetch all user groups from the database, keep this in memory
252
- obj.db.GetAllType('ugrp', function (err, docs) {
253
- obj.common.unEscapeAllLinksFieldName(docs);
254
-
255
- // Perform user group link cleanup
256
- for (var i in docs) {
257
- const ugrp = docs[i];
258
- if (ugrp.links != null) {
259
- for (var j in ugrp.links) {
260
- if (j.startsWith('user/') && (obj.users[j] == null)) { delete ugrp.links[j]; } // User group has a link to a user that does not exist
261
- else if (j.startsWith('mesh/') && ((obj.meshes[j] == null) || (obj.meshes[j].deleted != null))) { delete ugrp.links[j]; } // User has a link to a device group that does not exist
262
- }
263
- }
264
- obj.userGroups[docs[i]._id] = docs[i]; // Get all user groups
265
- }
266
-
267
- // Perform device group link cleanup
268
- for (var i in obj.meshes) {
269
- const mesh = obj.meshes[i];
270
- if (mesh.links != null) {
271
- for (var j in mesh.links) {
272
- if (j.startsWith('ugrp/') && (obj.userGroups[j] == null)) { delete mesh.links[j]; } // Device group has a link to a user group that does not exist
273
- else if (j.startsWith('user/') && (obj.users[j] == null)) { delete mesh.links[j]; } // Device group has a link to a user that does not exist
274
- }
275
- }
276
- }
277
-
278
- // Perform user link cleanup
279
- for (var i in obj.users) {
280
- const user = obj.users[i];
281
- if (user.links != null) {
282
- for (var j in user.links) {
283
- if (j.startsWith('ugrp/') && (obj.userGroups[j] == null)) { delete user.links[j]; } // User has a link to a user group that does not exist
284
- else if (j.startsWith('mesh/') && ((obj.meshes[j] == null) || (obj.meshes[j].deleted != null))) { delete user.links[j]; } // User has a link to a device group that does not exist
285
- //else if (j.startsWith('node/') && (obj.nodes[j] == null)) { delete user.links[j]; } // TODO
286
- }
287
- //if (Object.keys(user.links).length == 0) { delete user.links; }
288
- }
289
- }
290
-
291
- // We loaded the users, device groups and user group state, start the server
292
- serverStart();
293
- });
294
- });
295
- });
296
-
297
- // Clean up a device, used before saving it in the database
298
- obj.cleanDevice = function (device) {
299
- // Check device links, if a link points to an unknown user, remove it.
300
- if (device.links != null) {
301
- for (var j in device.links) {
302
- if ((obj.users[j] == null) && (obj.userGroups[j] == null)) {
303
- delete device.links[j];
304
- if (Object.keys(device.links).length == 0) { delete device.links; }
305
- }
306
- }
307
- }
308
- return device;
309
- }
310
-
311
- // Return statistics about this web server
312
- obj.getStats = function () {
313
- return {
314
- users: Object.keys(obj.users).length,
315
- meshes: Object.keys(obj.meshes).length,
316
- dnsDomains: Object.keys(obj.dnsDomains).length,
317
- relaySessionCount: obj.relaySessionCount,
318
- relaySessionErrorCount: obj.relaySessionErrorCount,
319
- wsagents: Object.keys(obj.wsagents).length,
320
- wsagentsDisconnections: Object.keys(obj.wsagentsDisconnections).length,
321
- wsagentsDisconnectionsTimer: Object.keys(obj.wsagentsDisconnectionsTimer).length,
322
- wssessions: Object.keys(obj.wssessions).length,
323
- wssessions2: Object.keys(obj.wssessions2).length,
324
- wsPeerSessions: Object.keys(obj.wsPeerSessions).length,
325
- wsPeerSessions2: Object.keys(obj.wsPeerSessions2).length,
326
- wsPeerSessions3: Object.keys(obj.wsPeerSessions3).length,
327
- sessionsCount: Object.keys(obj.sessionsCount).length,
328
- wsrelays: Object.keys(obj.wsrelays).length,
329
- wsPeerRelays: Object.keys(obj.wsPeerRelays).length,
330
- tlsSessionStore: Object.keys(tlsSessionStore).length,
331
- blockedUsers: obj.blockedUsers,
332
- blockedAgents: obj.blockedAgents
333
- };
334
- }
335
-
336
- // Agent counters
337
- obj.agentStats = {
338
- createMeshAgentCount: 0,
339
- agentClose: 0,
340
- agentBinaryUpdate: 0,
341
- agentMeshCoreBinaryUpdate: 0,
342
- coreIsStableCount: 0,
343
- verifiedAgentConnectionCount: 0,
344
- clearingCoreCount: 0,
345
- updatingCoreCount: 0,
346
- recoveryCoreIsStableCount: 0,
347
- meshDoesNotExistCount: 0,
348
- invalidPkcsSignatureCount: 0,
349
- invalidRsaSignatureCount: 0,
350
- invalidJsonCount: 0,
351
- unknownAgentActionCount: 0,
352
- agentBadWebCertHashCount: 0,
353
- agentBadSignature1Count: 0,
354
- agentBadSignature2Count: 0,
355
- agentMaxSessionHoldCount: 0,
356
- invalidDomainMeshCount: 0,
357
- invalidMeshTypeCount: 0,
358
- invalidDomainMesh2Count: 0,
359
- invalidMeshType2Count: 0,
360
- duplicateAgentCount: 0,
361
- maxDomainDevicesReached: 0,
362
- agentInTrouble: 0,
363
- agentInBigTrouble: 0
364
- }
365
- obj.getAgentStats = function () { return obj.agentStats; }
366
-
367
- // Traffic counters
368
- obj.trafficStats = {
369
- httpRequestCount: 0,
370
- httpWebSocketCount: 0,
371
- httpIn: 0,
372
- httpOut: 0,
373
- relayCount: {},
374
- relayIn: {},
375
- relayOut: {},
376
- localRelayCount: {},
377
- localRelayIn: {},
378
- localRelayOut: {},
379
- AgentCtrlIn: 0,
380
- AgentCtrlOut: 0,
381
- LMSIn: 0,
382
- LMSOut: 0,
383
- CIRAIn: 0,
384
- CIRAOut: 0
385
- }
386
- obj.trafficStats.time = Date.now();
387
- obj.getTrafficStats = function () { return obj.trafficStats; }
388
- obj.getTrafficDelta = function (oldTraffic) { // Return the difference between the old and new data along with the delta time.
389
- const data = obj.common.Clone(obj.trafficStats);
390
- data.time = Date.now();
391
- const delta = calcDelta(oldTraffic ? oldTraffic : {}, data);
392
- if (oldTraffic && oldTraffic.time) { delta.delta = (data.time - oldTraffic.time); }
393
- delta.time = data.time;
394
- return { current: data, delta: delta }
395
- }
396
- function calcDelta(oldData, newData) { // Recursive function that computes the difference of all numbers
397
- const r = {};
398
- for (var i in newData) {
399
- if (typeof newData[i] == 'object') { r[i] = calcDelta(oldData[i] ? oldData[i] : {}, newData[i]); }
400
- if (typeof newData[i] == 'number') { if (typeof oldData[i] == 'number') { r[i] = (newData[i] - oldData[i]); } else { r[i] = newData[i]; } }
401
- }
402
- return r;
403
- }
404
-
405
- // Keep a record of the last agent issues.
406
- obj.getAgentIssues = function () { return obj.agentIssues; }
407
- obj.setAgentIssue = function (agent, issue) { obj.agentIssues.push([new Date().toLocaleString(), agent.remoteaddrport, issue]); while (obj.setAgentIssue.length > 50) { obj.agentIssues.shift(); } }
408
- obj.agentIssues = [];
409
-
410
- // Authenticate the user
411
- obj.authenticate = function (name, pass, domain, fn) {
412
- if ((typeof (name) != 'string') || (typeof (pass) != 'string') || (typeof (domain) != 'object')) { fn(new Error('invalid fields')); return; }
413
- if (name.startsWith('~t:')) {
414
- // Login token, try to fetch the token from the database
415
- obj.db.Get('logintoken-' + name, function (err, docs) {
416
- if (err != null) { fn(err); return; }
417
- if ((docs == null) || (docs.length != 1)) { fn(new Error('login token not found')); return; }
418
- const loginToken = docs[0];
419
- if ((loginToken.expire != 0) && (loginToken.expire < Date.now())) { fn(new Error('login token expired')); return; }
420
-
421
- // Default strong password hashing (pbkdf2 SHA384)
422
- require('./pass').hash(pass, loginToken.salt, function (err, hash, tag) {
423
- if (err) return fn(err);
424
- if (hash == loginToken.hash) {
425
- // Login username and password are valid.
426
- var user = obj.users[loginToken.userid];
427
- if (!user) { fn(new Error('cannot find user')); return; }
428
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
429
-
430
- // Succesful login token authentication
431
- var loginOptions = { tokenName: loginToken.name, tokenUser: loginToken.tokenUser };
432
- if (loginToken.expire != 0) { loginOptions.expire = loginToken.expire; }
433
- return fn(null, user._id, null, loginOptions);
434
- }
435
- fn(new Error('invalid password'));
436
- }, 0);
437
- });
438
- } else if (domain.auth == 'ldap') {
439
- if (domain.ldapoptions.url == 'test') {
440
- // Fake LDAP login
441
- var xxuser = domain.ldapoptions[name.toLowerCase()];
442
- if (xxuser == null) {
443
- fn(new Error('invalid password'));
444
- return;
445
- } else {
446
- var username = xxuser['displayName'];
447
- if (domain.ldapusername) { username = xxuser[domain.ldapusername]; }
448
- var shortname = null;
449
- if (domain.ldapuserbinarykey) {
450
- // Use a binary key as the userid
451
- if (xxuser[domain.ldapuserbinarykey]) { shortname = Buffer.from(xxuser[domain.ldapuserbinarykey], 'binary').toString('hex'); }
452
- } else if (domain.ldapuserkey) {
453
- // Use a string key as the userid
454
- if (xxuser[domain.ldapuserkey]) { shortname = xxuser[domain.ldapuserkey]; }
455
- } else {
456
- // Use the default key as the userid
457
- if (xxuser.objectSid) { shortname = Buffer.from(xxuser.objectSid, 'binary').toString('hex').toLowerCase(); }
458
- else if (xxuser.objectGUID) { shortname = Buffer.from(xxuser.objectGUID, 'binary').toString('hex').toLowerCase(); }
459
- else if (xxuser.name) { shortname = xxuser.name; }
460
- else if (xxuser.cn) { shortname = xxuser.cn; }
461
- }
462
- if (shortname == null) { fn(new Error('no user identifier')); return; }
463
- if (username == null) { username = shortname; }
464
- var userid = 'user/' + domain.id + '/' + shortname;
465
- var user = obj.users[userid];
466
- var email = null;
467
- if (domain.ldapuseremail) {
468
- email = xxuser[domain.ldapuseremail];
469
- } else if (xxuser.mail) { // use default
470
- email = xxuser.mail;
471
- }
472
- if ('[object Array]' == Object.prototype.toString.call(email)) {
473
- // mail may be multivalued in ldap in which case, answer is an array. Use the 1st value.
474
- email = email[0];
475
- }
476
- if (email) { email = email.toLowerCase(); } // it seems some code otherwhere also lowercase the emailaddress. be compatible.
477
-
478
- if (user == null) {
479
- // Create a new user
480
- var user = { type: 'user', _id: userid, name: username, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
481
- if (email) { user['email'] = email; user['emailVerified'] = true; }
482
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
483
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
484
- var usercount = 0;
485
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
486
- if (usercount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
487
-
488
- // Auto-join any user groups
489
- if (typeof domain.newaccountsusergroups == 'object') {
490
- for (var i in domain.newaccountsusergroups) {
491
- var ugrpid = domain.newaccountsusergroups[i];
492
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
493
- var ugroup = obj.userGroups[ugrpid];
494
- if (ugroup != null) {
495
- // Add group to the user
496
- if (user.links == null) { user.links = {}; }
497
- user.links[ugroup._id] = { rights: 1 };
498
-
499
- // Add user to the group
500
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
501
- db.Set(ugroup);
502
-
503
- // Notify user group change
504
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msgid: 71, msgArgs: [user.name, ugroup.name], msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
505
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
506
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
507
- }
508
- }
509
- }
510
-
511
- obj.users[user._id] = user;
512
- obj.db.SetUser(user);
513
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msgid: 128, msgArgs: [user.name], msg: 'Account created, name is ' + user.name, domain: domain.id };
514
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
515
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
516
- return fn(null, user._id);
517
- } else {
518
- // This is an existing user
519
- // If the display username has changes, update it.
520
- if (user.name != username) {
521
- user.name = username;
522
- obj.db.SetUser(user);
523
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msgid: 127, msgArgs: [user.name], msg: 'Changed account display name to ' + user.name, domain: domain.id };
524
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
525
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
526
- }
527
- // Check if user email has changed
528
- var emailreason = null;
529
- if (user.email && !email) { // email unset in ldap => unset
530
- delete user.email;
531
- delete user.emailVerified;
532
- emailreason = 'Unset email (no more email in LDAP)'
533
- } else if (user.email != email) { // update email
534
- user['email'] = email;
535
- user['emailVerified'] = true;
536
- emailreason = 'Set account email to ' + email + '. Sync with LDAP.';
537
- }
538
- if (emailreason) {
539
- obj.db.SetUser(user);
540
- var event = { etype: 'user', userid: userid, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: emailreason, domain: domain.id };
541
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
542
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
543
- }
544
- // If user is locker out, block here.
545
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
546
- return fn(null, user._id);
547
- }
548
- }
549
- } else {
550
- // LDAP login
551
- var LdapAuth = require('ldapauth-fork');
552
- var ldap = new LdapAuth(domain.ldapoptions);
553
- ldap.on('error', function (err) { console.log('ldap error: ', err); });
554
- ldap.authenticate(name, pass, function (err, xxuser) {
555
- try { ldap.close(); } catch (ex) { console.log(ex); } // Close the LDAP object
556
- if (err) { fn(new Error('invalid password')); return; }
557
- var shortname = null;
558
- var email = null;
559
- if (domain.ldapuseremail) {
560
- email = xxuser[domain.ldapuseremail];
561
- } else if (xxuser.mail) {
562
- email = xxuser.mail;
563
- }
564
- if ('[object Array]' == Object.prototype.toString.call(email)) {
565
- // mail may be multivalued in ldap in which case, answer would be an array. Use the 1st one.
566
- email = email[0];
567
- }
568
- if (email) { email = email.toLowerCase(); } // it seems some code otherwhere also lowercase the emailaddress. be compatible.
569
- var username = xxuser['displayName'];
570
- if (domain.ldapusername) { username = xxuser[domain.ldapusername]; }
571
- if (domain.ldapuserbinarykey) {
572
- // Use a binary key as the userid
573
- if (xxuser[domain.ldapuserbinarykey]) { shortname = Buffer.from(xxuser[domain.ldapuserbinarykey], 'binary').toString('hex').toLowerCase(); }
574
- } else if (domain.ldapuserkey) {
575
- // Use a string key as the userid
576
- if (xxuser[domain.ldapuserkey]) { shortname = xxuser[domain.ldapuserkey]; }
577
- } else {
578
- // Use the default key as the userid
579
- if (xxuser.objectSid) { shortname = Buffer.from(xxuser.objectSid, 'binary').toString('hex').toLowerCase(); }
580
- else if (xxuser.objectGUID) { shortname = Buffer.from(xxuser.objectGUID, 'binary').toString('hex').toLowerCase(); }
581
- else if (xxuser.name) { shortname = xxuser.name; }
582
- else if (xxuser.cn) { shortname = xxuser.cn; }
583
- }
584
- if (shortname == null) { fn(new Error('no user identifier')); return; }
585
- if (username == null) { username = shortname; }
586
- var userid = 'user/' + domain.id + '/' + shortname;
587
- var user = obj.users[userid];
588
-
589
- if (user == null) {
590
- // This user does not exist, create a new account.
591
- var user = { type: 'user', _id: userid, name: username, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
592
- if (email) { user['email'] = email; user['emailVerified'] = true; }
593
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
594
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
595
- var usercount = 0;
596
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
597
- if (usercount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
598
-
599
- // Auto-join any user groups
600
- if (typeof domain.newaccountsusergroups == 'object') {
601
- for (var i in domain.newaccountsusergroups) {
602
- var ugrpid = domain.newaccountsusergroups[i];
603
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
604
- var ugroup = obj.userGroups[ugrpid];
605
- if (ugroup != null) {
606
- // Add group to the user
607
- if (user.links == null) { user.links = {}; }
608
- user.links[ugroup._id] = { rights: 1 };
609
-
610
- // Add user to the group
611
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
612
- db.Set(ugroup);
613
-
614
- // Notify user group change
615
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msgid: 71, msgArgs: [user.name, ugroup.name], msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
616
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
617
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
618
- }
619
- }
620
- }
621
-
622
- obj.users[user._id] = user;
623
- obj.db.SetUser(user);
624
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msgid: 128, msgArgs: [user.name], msg: 'Account created, name is ' + user.name, domain: domain.id };
625
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
626
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
627
- return fn(null, user._id);
628
- } else {
629
- // This is an existing user
630
- // If the display username has changes, update it.
631
- if (user.name != username) {
632
- user.name = username;
633
- obj.db.SetUser(user);
634
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msgid: 127, msgArgs: [user.name], msg: 'Changed account display name to ' + user.name, domain: domain.id };
635
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
636
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
637
- }
638
- // Check if user email has changed
639
- var emailreason = null;
640
- if (user.email && !email) { // email unset in ldap => unset
641
- delete user.email;
642
- delete user.emailVerified;
643
- emailreason = 'Unset email (no more email in LDAP)'
644
- } else if (user.email != email) { // update email
645
- user['email'] = email;
646
- user['emailVerified'] = true;
647
- emailreason = 'Set account email to ' + email + '. Sync with LDAP.';
648
- }
649
- if (emailreason) {
650
- obj.db.SetUser(user);
651
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: emailreason, domain: domain.id };
652
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
653
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
654
- }
655
- // If user is locker out, block here.
656
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
657
- return fn(null, user._id);
658
- }
659
- });
660
- }
661
- } else {
662
- // Regular login
663
- var user = obj.users['user/' + domain.id + '/' + name.toLowerCase()];
664
- // Query the db for the given username
665
- if (!user) { fn(new Error('cannot find user')); return; }
666
- // Apply the same algorithm to the POSTed password, applying the hash against the pass / salt, if there is a match we found the user
667
- if (user.salt == null) {
668
- fn(new Error('invalid password'));
669
- } else {
670
- if (user.passtype != null) {
671
- // IIS default clear or weak password hashing (SHA-1)
672
- require('./pass').iishash(user.passtype, pass, user.salt, function (err, hash) {
673
- if (err) return fn(err);
674
- if (hash == user.hash) {
675
- // Update the password to the stronger format.
676
- require('./pass').hash(pass, function (err, salt, hash, tag) { if (err) throw err; user.salt = salt; user.hash = hash; delete user.passtype; obj.db.SetUser(user); }, 0);
677
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
678
- return fn(null, user._id);
679
- }
680
- fn(new Error('invalid password'), null, user.passhint);
681
- });
682
- } else {
683
- // Default strong password hashing (pbkdf2 SHA384)
684
- require('./pass').hash(pass, user.salt, function (err, hash, tag) {
685
- if (err) return fn(err);
686
- if (hash == user.hash) {
687
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
688
- return fn(null, user._id);
689
- }
690
- fn(new Error('invalid password'), null, user.passhint);
691
- }, 0);
692
- }
693
- }
694
- }
695
- };
696
-
697
- /*
698
- obj.restrict = function (req, res, next) {
699
- console.log('restrict', req.url);
700
- var domain = getDomain(req);
701
- if (req.session.userid) {
702
- next();
703
- } else {
704
- req.session.messageid = 111; // Access denied.
705
- res.redirect(domain.url + 'login');
706
- }
707
- };
708
- */
709
-
710
- // Check if the source IP address is in the IP list, return false if not.
711
- function checkIpAddressEx(req, res, ipList, closeIfThis, redirectUrl) {
712
- try {
713
- if (req.connection) {
714
- // HTTP(S) request
715
- if (req.clientIp) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(req.clientIp, ipList[i])) { if (closeIfThis === true) { if (typeof redirectUrl == 'string') { res.redirect(redirectUrl); } else { res.sendStatus(401); } } return true; } } }
716
- if (closeIfThis === false) { if (typeof redirectUrl == 'string') { res.redirect(redirectUrl); } else { res.sendStatus(401); } }
717
- } else {
718
- // WebSocket request
719
- if (res.clientIp) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(res.clientIp, ipList[i])) { if (closeIfThis === true) { try { req.close(); } catch (e) { } } return true; } } }
720
- if (closeIfThis === false) { try { req.close(); } catch (e) { } }
721
- }
722
- } catch (e) { console.log(e); } // Should never happen
723
- return false;
724
- }
725
-
726
- // Check if the source IP address is allowed, return domain if allowed
727
- // If there is a fail and null is returned, the request or connection is closed already.
728
- function checkUserIpAddress(req, res) {
729
- if ((parent.config.settings.userblockedip != null) && (checkIpAddressEx(req, res, parent.config.settings.userblockedip, true, parent.config.settings.ipblockeduserredirect) == true)) { obj.blockedUsers++; return null; }
730
- if ((parent.config.settings.userallowedip != null) && (checkIpAddressEx(req, res, parent.config.settings.userallowedip, false, parent.config.settings.ipblockeduserredirect) == false)) { obj.blockedUsers++; return null; }
731
- const domain = (req.url ? getDomain(req) : getDomain(res));
732
- if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); try { res.sendStatus(404); } catch (ex) { } return; }
733
- if ((domain.userblockedip != null) && (checkIpAddressEx(req, res, domain.userblockedip, true, domain.ipblockeduserredirect) == true)) { obj.blockedUsers++; return null; }
734
- if ((domain.userallowedip != null) && (checkIpAddressEx(req, res, domain.userallowedip, false, domain.ipblockeduserredirect) == false)) { obj.blockedUsers++; return null; }
735
- return domain;
736
- }
737
-
738
- // Check if the source IP address is allowed, return domain if allowed
739
- // If there is a fail and null is returned, the request or connection is closed already.
740
- function checkAgentIpAddress(req, res) {
741
- if ((parent.config.settings.agentblockedip != null) && (checkIpAddressEx(req, res, parent.config.settings.agentblockedip, null) == true)) { obj.blockedAgents++; return null; }
742
- if ((parent.config.settings.agentallowedip != null) && (checkIpAddressEx(req, res, parent.config.settings.agentallowedip, null) == false)) { obj.blockedAgents++; return null; }
743
- const domain = (req.url ? getDomain(req) : getDomain(res));
744
- if ((domain.agentblockedip != null) && (checkIpAddressEx(req, res, domain.agentblockedip, null) == true)) { obj.blockedAgents++; return null; }
745
- if ((domain.agentallowedip != null) && (checkIpAddressEx(req, res, domain.agentallowedip, null) == false)) { obj.blockedAgents++; return null; }
746
- return domain;
747
- }
748
-
749
- // Return the current domain of the request
750
- // Request or connection says open regardless of the response
751
- function getDomain(req) {
752
- if (req.xdomain != null) { return req.xdomain; } // Domain already set for this request, return it.
753
- if (req.headers.host != null) { var d = obj.dnsDomains[req.headers.host.split(':')[0].toLowerCase()]; if (d != null) return d; } // If this is a DNS name domain, return it here.
754
- var x = req.url.split('/');
755
- if (x.length < 2) return parent.config.domains[''];
756
- var y = parent.config.domains[x[1].toLowerCase()];
757
- if ((y != null) && (y.dns == null)) { return parent.config.domains[x[1].toLowerCase()]; }
758
- return parent.config.domains[''];
759
- }
760
-
761
- function handleLogoutRequest(req, res) {
762
- const domain = checkUserIpAddress(req, res);
763
- if (domain == null) { return; }
764
- if (domain.auth == 'sspi') { parent.debug('web', 'handleLogoutRequest: failed checks.'); res.sendStatus(404); return; }
765
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
766
-
767
- res.set({ 'Cache-Control': 'no-store' });
768
- // Destroy the user's session to log them out will be re-created next request
769
- var userid = req.session.userid;
770
- if (req.session.userid) {
771
- var user = obj.users[req.session.userid];
772
- if (user != null) { obj.parent.DispatchEvent(['*'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'logout', msgid: 2, msg: 'Account logout', domain: domain.id }); }
773
- if (req.session.x) { clearDestroyedSessions(); obj.destroyedSessions[req.session.userid + '/' + req.session.x] = Date.now(); } // Destroy this session
774
- }
775
- req.session = null;
776
- parent.debug('web', 'handleLogoutRequest: success.');
777
-
778
- // If this user was logged in using an authentication strategy and there is a logout URL, use it.
779
- if ((userid != null) && (domain.authstrategies != null)) {
780
- const u = userid.split('/')[2];
781
- if (u.startsWith('~twitter:') && (domain.authstrategies.twitter != null) && (typeof domain.authstrategies.twitter.logouturl == 'string')) { res.redirect(domain.authstrategies.twitter.logouturl); return; }
782
- if (u.startsWith('~google:') && (domain.authstrategies.google != null) && (typeof domain.authstrategies.google.logouturl == 'string')) { res.redirect(domain.authstrategies.google.logouturl); return; }
783
- if (u.startsWith('~github:') && (domain.authstrategies.github != null) && (typeof domain.authstrategies.github.logouturl == 'string')) { res.redirect(domain.authstrategies.github.logouturl); return; }
784
- if (u.startsWith('~reddit:') && (domain.authstrategies.reddit != null) && (typeof domain.authstrategies.reddit.logouturl == 'string')) { res.redirect(domain.authstrategies.reddit.logouturl); return; }
785
- if (u.startsWith('~azure:') && (domain.authstrategies.azure != null) && (typeof domain.authstrategies.azure.logouturl == 'string')) { res.redirect(domain.authstrategies.azure.logouturl); return; }
786
- if (u.startsWith('~oidc:') && (domain.authstrategies.oidc != null) && (typeof domain.authstrategies.oidc.logouturl == 'string')) { res.redirect(domain.authstrategies.oidc.logouturl); return; }
787
- if (u.startsWith('~jumpcloud:') && (domain.authstrategies.jumpcloud != null) && (typeof domain.authstrategies.jumpcloud.logouturl == 'string')) { res.redirect(domain.authstrategies.jumpcloud.logouturl); return; }
788
- if (u.startsWith('~saml:') && (domain.authstrategies.saml != null) && (typeof domain.authstrategies.saml.logouturl == 'string')) { res.redirect(domain.authstrategies.saml.logouturl); return; }
789
- if (u.startsWith('~intel:') && (domain.authstrategies.intel != null) && (typeof domain.authstrategies.intel.logouturl == 'string')) { res.redirect(domain.authstrategies.intel.logouturl); return; }
790
- }
791
-
792
- // This is the default logout redirect to the login page
793
- if (req.query.key != null) { res.redirect(domain.url + '?key=' + req.query.key); } else { res.redirect(domain.url); }
794
- }
795
-
796
- // Return an object with 2FA type if 2-step auth can be skipped
797
- function checkUserOneTimePasswordSkip(domain, user, req, loginOptions) {
798
- if (parent.config.settings.no2factorauth == true) return null;
799
-
800
- // If this login occured using a login token, no 2FA needed.
801
- if ((loginOptions != null) && (typeof loginOptions.tokenName === 'string')) { return { twoFactorType: 'tokenlogin' }; }
802
-
803
- // Check if we can skip 2nd factor auth because of the source IP address
804
- if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
805
- for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) { return { twoFactorType: 'ipaddr' }; } }
806
- }
807
-
808
- // Check if a 2nd factor cookie is present
809
- if (typeof req.headers.cookie == 'string') {
810
- const cookies = req.headers.cookie.split('; ');
811
- for (var i in cookies) {
812
- if (cookies[i].startsWith('twofactor=')) {
813
- var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
814
- if ((twoFactorCookie != null) && ((twoFactorCookie.ip == null) || checkCookieIp(twoFactorCookie.ip, req.clientIp)) && (twoFactorCookie.userid == user._id)) { return { twoFactorType: 'cookie' }; }
815
- }
816
- }
817
- }
818
-
819
- return null;
820
- }
821
-
822
- // Return true if this user has 2-step auth active
823
- function checkUserOneTimePasswordRequired(domain, user, req, loginOptions) {
824
- // If this login occured using a login token, no 2FA needed.
825
- if ((loginOptions != null) && (typeof loginOptions.tokenName === 'string')) { return false; }
826
-
827
- // Check if we can skip 2nd factor auth because of the source IP address
828
- if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
829
- for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) return false; }
830
- }
831
-
832
- // Check if a 2nd factor cookie is present
833
- if (typeof req.headers.cookie == 'string') {
834
- const cookies = req.headers.cookie.split('; ');
835
- for (var i in cookies) {
836
- if (cookies[i].startsWith('twofactor=')) {
837
- var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
838
- if ((twoFactorCookie != null) && ((twoFactorCookie.ip == null) || checkCookieIp(twoFactorCookie.ip, req.clientIp)) && (twoFactorCookie.userid == user._id)) { return false; }
839
- }
840
- }
841
- }
842
-
843
- // See if SMS 2FA is available
844
- var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
845
-
846
- // Check if a 2nd factor is present
847
- return ((parent.config.settings.no2factorauth !== true) && (sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
848
- }
849
-
850
- // Check the 2-step auth token
851
- function checkUserOneTimePassword(req, domain, user, token, hwtoken, func) {
852
- parent.debug('web', 'checkUserOneTimePassword()');
853
- const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (parent.config.settings.no2factorauth !== true));
854
- if (twoStepLoginSupported == false) { parent.debug('web', 'checkUserOneTimePassword: not supported.'); func(true); return; };
855
-
856
- // Check if we can use OTP tokens with email
857
- var otpemail = (domain.mailserver != null);
858
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
859
- var otpsms = (parent.smsserver != null);
860
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
861
-
862
- // Check 2FA login cookie
863
- if ((token != null) && (token.startsWith('cookie='))) {
864
- var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(token.substring(7)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
865
- if ((twoFactorCookie != null) && ((twoFactorCookie.ip == null) || checkCookieIp(twoFactorCookie.ip, req.clientIp)) && (twoFactorCookie.userid == user._id)) { func(true, { twoFactorType: 'cookie' }); return; }
866
- }
867
-
868
- // Check email key
869
- if ((otpemail) && (user.otpekey != null) && (user.otpekey.d != null) && (user.otpekey.k === token)) {
870
- var deltaTime = (Date.now() - user.otpekey.d);
871
- if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the email token (10000 * 60 * 5).
872
- user.otpekey = {};
873
- obj.db.SetUser(user);
874
- parent.debug('web', 'checkUserOneTimePassword: success (email).');
875
- func(true, { twoFactorType: 'email' });
876
- return;
877
- }
878
- }
879
-
880
- // Check sms key
881
- if ((otpsms) && (user.phone != null) && (user.otpsms != null) && (user.otpsms.d != null) && (user.otpsms.k === token)) {
882
- var deltaTime = (Date.now() - user.otpsms.d);
883
- if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the SMS token (10000 * 60 * 5).
884
- delete user.otpsms;
885
- obj.db.SetUser(user);
886
- parent.debug('web', 'checkUserOneTimePassword: success (SMS).');
887
- func(true, { twoFactorType: 'sms' });
888
- return;
889
- }
890
- }
891
-
892
- // Check hardware key
893
- if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) {
894
- var authResponse = null;
895
- try { authResponse = JSON.parse(hwtoken); } catch (ex) { }
896
- if ((authResponse != null) && (authResponse.clientDataJSON)) {
897
- // Get all WebAuthn keys
898
- var webAuthnKeys = [];
899
- for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 3) { webAuthnKeys.push(user.otphkeys[i]); } }
900
- if (webAuthnKeys.length > 0) {
901
- // Decode authentication response
902
- var clientAssertionResponse = { response: {} };
903
- clientAssertionResponse.id = authResponse.id;
904
- clientAssertionResponse.rawId = Buffer.from(authResponse.id, 'base64');
905
- clientAssertionResponse.response.authenticatorData = Buffer.from(authResponse.authenticatorData, 'base64');
906
- clientAssertionResponse.response.clientDataJSON = Buffer.from(authResponse.clientDataJSON, 'base64');
907
- clientAssertionResponse.response.signature = Buffer.from(authResponse.signature, 'base64');
908
- clientAssertionResponse.response.userHandle = Buffer.from(authResponse.userHandle, 'base64');
909
-
910
- // Look for the key with clientAssertionResponse.id
911
- var webAuthnKey = null;
912
- for (var i = 0; i < webAuthnKeys.length; i++) { if (webAuthnKeys[i].keyId == clientAssertionResponse.id) { webAuthnKey = webAuthnKeys[i]; } }
913
-
914
- // If we found a valid key to use, let's validate the response
915
- if (webAuthnKey != null) {
916
- // Figure out the origin
917
- var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
918
- var origin = 'https://' + (domain.dns ? domain.dns : parent.certificates.CommonName);
919
- if (httpport != 443) { origin += ':' + httpport; }
920
-
921
- var assertionExpectations = {
922
- challenge: req.session.u2f,
923
- origin: origin,
924
- factor: 'either',
925
- fmt: 'fido-u2f',
926
- publicKey: webAuthnKey.publicKey,
927
- prevCounter: webAuthnKey.counter,
928
- userHandle: Buffer.from(user._id, 'binary').toString('base64')
929
- };
930
-
931
- var webauthnResponse = null;
932
- try { webauthnResponse = obj.webauthn.verifyAuthenticatorAssertionResponse(clientAssertionResponse.response, assertionExpectations); } catch (ex) { parent.debug('web', 'checkUserOneTimePassword: exception ' + ex); console.log(ex); }
933
- if ((webauthnResponse != null) && (webauthnResponse.verified === true)) {
934
- // Update the hardware key counter and accept the 2nd factor
935
- webAuthnKey.counter = webauthnResponse.counter;
936
- obj.db.SetUser(user);
937
- parent.debug('web', 'checkUserOneTimePassword: success (hardware).');
938
- func(true, { twoFactorType: 'fido' });
939
- } else {
940
- parent.debug('web', 'checkUserOneTimePassword: fail (hardware).');
941
- func(false);
942
- }
943
- return;
944
- }
945
- }
946
- }
947
- }
948
-
949
- // Check Google Authenticator
950
- const otplib = require('otplib')
951
- otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window
952
- if (user.otpsecret && (typeof (token) == 'string') && (token.length == 6) && (otplib.authenticator.check(token, user.otpsecret) == true)) {
953
- parent.debug('web', 'checkUserOneTimePassword: success (authenticator).');
954
- func(true, { twoFactorType: 'otp' });
955
- return;
956
- };
957
-
958
- // Check written down keys
959
- if ((user.otpkeys != null) && (user.otpkeys.keys != null) && (typeof (token) == 'string') && (token.length == 8)) {
960
- var tokenNumber = parseInt(token);
961
- for (var i = 0; i < user.otpkeys.keys.length; i++) {
962
- if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) {
963
- parent.debug('web', 'checkUserOneTimePassword: success (one-time).');
964
- user.otpkeys.keys[i].u = false; func(true, { twoFactorType: 'backup' }); return;
965
- }
966
- }
967
- }
968
-
969
- // Check OTP hardware key (Yubikey OTP)
970
- if ((domain.yubikey != null) && (domain.yubikey.id != null) && (domain.yubikey.secret != null) && (user.otphkeys != null) && (user.otphkeys.length > 0) && (typeof (token) == 'string') && (token.length == 44)) {
971
- var keyId = token.substring(0, 12);
972
-
973
- // Find a matching OTP key
974
- var match = false;
975
- for (var i = 0; i < user.otphkeys.length; i++) { if ((user.otphkeys[i].type === 2) && (user.otphkeys[i].keyid === keyId)) { match = true; } }
976
-
977
- // If we have a match, check the OTP
978
- if (match === true) {
979
- var yubikeyotp = require('yubikeyotp');
980
- var request = { otp: token, id: domain.yubikey.id, key: domain.yubikey.secret, timestamp: true }
981
- if (domain.yubikey.proxy) { request.requestParams = { proxy: domain.yubikey.proxy }; }
982
- yubikeyotp.verifyOTP(request, function (err, results) {
983
- if ((results != null) && (results.status == 'OK')) {
984
- parent.debug('web', 'checkUserOneTimePassword: success (Yubikey).');
985
- func(true, { twoFactorType: 'hwotp' });
986
- } else {
987
- parent.debug('web', 'checkUserOneTimePassword: fail (Yubikey).');
988
- func(false);
989
- }
990
- });
991
- return;
992
- }
993
- }
994
-
995
- parent.debug('web', 'checkUserOneTimePassword: fail (2).');
996
- func(false);
997
- }
998
-
999
- // Return a U2F hardware key challenge
1000
- function getHardwareKeyChallenge(req, domain, user, func) {
1001
- delete req.session.u2f;
1002
- if (user.otphkeys && (user.otphkeys.length > 0)) {
1003
- // Get all WebAuthn keys
1004
- var webAuthnKeys = [];
1005
- for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 3) { webAuthnKeys.push(user.otphkeys[i]); } }
1006
- if (webAuthnKeys.length > 0) {
1007
- // Generate a Webauthn challenge, this is really easy, no need to call any modules to do this.
1008
- var authnOptions = { type: 'webAuthn', keyIds: [], timeout: 60000, challenge: obj.crypto.randomBytes(64).toString('base64') };
1009
- for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[i].keyId); }
1010
- req.session.u2f = authnOptions.challenge;
1011
- parent.debug('web', 'getHardwareKeyChallenge: success');
1012
- func(JSON.stringify(authnOptions));
1013
- return;
1014
- }
1015
- }
1016
- parent.debug('web', 'getHardwareKeyChallenge: fail');
1017
- func('');
1018
- }
1019
-
1020
- // Redirect a root request to a different page
1021
- function handleRootRedirect(req, res, direct) {
1022
- const domain = checkUserIpAddress(req, res);
1023
- if (domain == null) { return; }
1024
- res.redirect(domain.rootredirect + getQueryPortion(req));
1025
- }
1026
-
1027
- function handleLoginRequest(req, res, direct) {
1028
- const domain = checkUserIpAddress(req, res);
1029
- if (domain == null) { return; }
1030
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1031
-
1032
- // Check if this is a banned ip address
1033
- if (obj.checkAllowLogin(req) == false) {
1034
- // Wait and redirect the user
1035
- setTimeout(function () {
1036
- req.session.messageid = 114; // IP address blocked, try again later.
1037
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1038
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
1039
- return;
1040
- }
1041
-
1042
- // Normally, use the body username/password. If this is a token, use the username/password in the session.
1043
- var xusername = req.body.username, xpassword = req.body.password;
1044
- if ((xusername == null) && (xpassword == null) && (req.body.token != null)) { xusername = req.session.tuser; xpassword = req.session.tpass; }
1045
-
1046
- // Authenticate the user
1047
- obj.authenticate(xusername, xpassword, domain, function (err, userid, passhint, loginOptions) {
1048
- if (userid) {
1049
- var user = obj.users[userid];
1050
-
1051
- // Check if we are in maintenance mode
1052
- if ((parent.config.settings.maintenancemode != null) && (user.siteadmin != 4294967295)) {
1053
- req.session.messageid = 115; // Server under maintenance
1054
- req.session.loginmode = 1;
1055
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1056
- return;
1057
- }
1058
-
1059
- var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null) && (user.email != null) && (user.emailVerified == true) && (user.otpekey != null));
1060
- var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
1061
- var push2fa = ((parent.firebase != null) && (user.otpdev != null));
1062
-
1063
- // Check if two factor can be skipped
1064
- const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
1065
-
1066
- // Check if this user has 2-step login active
1067
- if ((twoFactorSkip == null) && (req.session.loginmode != 6) && checkUserOneTimePasswordRequired(domain, user, req, loginOptions)) {
1068
- if ((req.body.hwtoken == '**timeout**')) {
1069
- delete req.session; // Clear the session
1070
- res.redirect(domain.url + getQueryPortion(req));
1071
- return;
1072
- }
1073
-
1074
- if ((req.body.hwtoken == '**email**') && email2fa) {
1075
- user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
1076
- obj.db.SetUser(user);
1077
- parent.debug('web', 'Sending 2FA email to: ' + user.email);
1078
- domain.mailserver.sendAccountLoginMail(domain, user.email, user.otpekey.k, obj.getLanguageCodes(req), req.query.key);
1079
- req.session.messageid = 2; // "Email sent" message
1080
- req.session.loginmode = 4;
1081
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1082
- return;
1083
- }
1084
-
1085
- if ((req.body.hwtoken == '**sms**') && sms2fa) {
1086
- // Cause a token to be sent to the user's phone number
1087
- user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
1088
- obj.db.SetUser(user);
1089
- parent.debug('web', 'Sending 2FA SMS to: ' + user.phone);
1090
- parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
1091
- // Ask for a login token & confirm sms was sent
1092
- req.session.messageid = 4; // "SMS sent" message
1093
- req.session.loginmode = 4;
1094
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1095
- return;
1096
- }
1097
-
1098
- // Handle device push notification 2FA request
1099
- // We create a browser cookie, send it back and when the browser connects it's web socket, it will trigger the push notification.
1100
- if ((req.body.hwtoken == '**push**') && push2fa && ((domain.passwordrequirements == null) || (domain.passwordrequirements.push2factor != false))) {
1101
- const logincodeb64 = Buffer.from(obj.common.zeroPad(getRandomSixDigitInteger(), 6)).toString('base64');
1102
- const sessioncode = obj.crypto.randomBytes(24).toString('base64');
1103
-
1104
- // Create a browser cookie so the browser can connect using websocket and wait for device accept/reject.
1105
- const browserCookie = parent.encodeCookie({ a: 'waitAuth', c: logincodeb64, u: user._id, n: user.otpdev, s: sessioncode, d: domain.id });
1106
-
1107
- // Get the HTTPS port
1108
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port if specified
1109
-
1110
- // Get the agent connection server name
1111
- var serverName = obj.getWebServerName(domain);
1112
- if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
1113
-
1114
- // Build the connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
1115
- var xdomain = (domain.dns == null) ? domain.id : '';
1116
- if (xdomain != '') xdomain += '/';
1117
- var url = 'wss://' + serverName + ':' + httpsPort + '/' + xdomain + '2fahold.ashx?c=' + browserCookie;
1118
-
1119
- // Request that the login page wait for device auth
1120
- req.session.messageid = 5; // "Sending notification..." message
1121
- req.session.passhint = url;
1122
- req.session.loginmode = 8;
1123
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1124
- return;
1125
- }
1126
-
1127
- checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result, authData) {
1128
- if (result == false) {
1129
- var randomWaitTime = 0;
1130
-
1131
- // Check if 2FA is allowed for this IP address
1132
- if (obj.checkAllow2Fa(req) == false) {
1133
- // Wait and redirect the user
1134
- setTimeout(function () {
1135
- req.session.messageid = 114; // IP address blocked, try again later.
1136
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1137
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
1138
- return;
1139
- }
1140
-
1141
- // 2-step auth is required, but the token is not present or not valid.
1142
- if ((req.body.token != null) || (req.body.hwtoken != null)) {
1143
- randomWaitTime = 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095); // This is a fail, wait a random time. 2 to 6 seconds.
1144
- req.session.messageid = 108; // Invalid token, try again.
1145
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr(req.clientIp) + ' port ' + req.port); }
1146
- parent.debug('web', 'handleLoginRequest: invalid 2FA token');
1147
- const ua = getUserAgentInfo(req);
1148
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { action: 'authfail', username: user.name, userid: user._id, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp, msgid: 108, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1149
- obj.setbad2Fa(req);
1150
- } else {
1151
- parent.debug('web', 'handleLoginRequest: 2FA token required');
1152
- }
1153
-
1154
- // Wait and redirect the user
1155
- setTimeout(function () {
1156
- req.session.loginmode = 4;
1157
- if ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) { req.session.temail = 1; }
1158
- if ((user.phone != null) && (parent.smsserver != null)) { req.session.tsms = 1; }
1159
- if ((user.otpdev != null) && (parent.firebase != null)) { req.session.tpush = 1; }
1160
- req.session.tuserid = userid;
1161
- req.session.tuser = xusername;
1162
- req.session.tpass = xpassword;
1163
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1164
- }, randomWaitTime);
1165
- } else {
1166
- // Check if we need to remember this device
1167
- if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
1168
- var maxCookieAge = domain.twofactorcookiedurationdays;
1169
- if (typeof maxCookieAge != 'number') { maxCookieAge = 30; }
1170
- const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, obj.parent.loginCookieEncryptionKey);
1171
- res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: parent.config.settings.cookiesamesite, secure: true });
1172
- }
1173
-
1174
- // Check if email address needs to be confirmed
1175
- const emailcheck = ((domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
1176
- if (emailcheck && (user.emailVerified !== true)) {
1177
- parent.debug('web', 'Redirecting using ' + user.name + ' to email check login page');
1178
- req.session.messageid = 3; // "Email verification required" message
1179
- req.session.loginmode = 7;
1180
- req.session.passhint = user.email;
1181
- req.session.cuserid = userid;
1182
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1183
- return;
1184
- }
1185
-
1186
- // Login successful
1187
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1188
- parent.debug('web', 'handleLoginRequest: successful 2FA login');
1189
- if (authData != null) { if (loginOptions == null) { loginOptions = {}; } loginOptions.twoFactorType = authData.twoFactorType; }
1190
- completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
1191
- }
1192
- });
1193
- return;
1194
- }
1195
-
1196
- // Check if email address needs to be confirmed
1197
- const emailcheck = ((domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
1198
- if (emailcheck && (user.emailVerified !== true)) {
1199
- parent.debug('web', 'Redirecting using ' + user.name + ' to email check login page');
1200
- req.session.messageid = 3; // "Email verification required" message
1201
- req.session.loginmode = 7;
1202
- req.session.passhint = user.email;
1203
- req.session.cuserid = userid;
1204
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1205
- return;
1206
- }
1207
-
1208
- // Login successful
1209
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1210
- parent.debug('web', 'handleLoginRequest: successful login');
1211
- if (twoFactorSkip != null) { if (loginOptions == null) { loginOptions = {}; } loginOptions.twoFactorType = twoFactorSkip.twoFactorType; }
1212
- completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
1213
- } else {
1214
- // Login failed, log the error
1215
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1216
-
1217
- // Wait a random delay
1218
- setTimeout(function () {
1219
- // If the account is locked, display that.
1220
- if (typeof xusername == 'string') {
1221
- var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase();
1222
- if (err == 'locked') {
1223
- parent.debug('web', 'handleLoginRequest: login failed, locked account');
1224
- req.session.messageid = 110; // Account locked.
1225
- const ua = getUserAgentInfo(req);
1226
- obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + req.clientIp, msgid: 109, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1227
- obj.setbadLogin(req);
1228
- } else {
1229
- parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
1230
- req.session.messageid = 112; // Login failed, check username and password.
1231
- const ua = getUserAgentInfo(req);
1232
- obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + req.clientIp, msgid: 110, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1233
- obj.setbadLogin(req);
1234
- }
1235
- }
1236
-
1237
- // Clean up login mode and display password hint if present.
1238
- delete req.session.loginmode;
1239
- if ((passhint != null) && (passhint.length > 0)) {
1240
- req.session.passhint = passhint;
1241
- } else {
1242
- delete req.session.passhint;
1243
- }
1244
-
1245
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1246
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095)); // Wait for 2 to ~6 seconds.
1247
- }
1248
- });
1249
- }
1250
-
1251
- function completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions) {
1252
- // Check if we need to change the password
1253
- if ((typeof user.passchange == 'number') && ((user.passchange == -1) || ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.reset == 'number') && (user.passchange + (domain.passwordrequirements.reset * 86400) < Math.floor(Date.now() / 1000))))) {
1254
- // Request a password change
1255
- parent.debug('web', 'handleLoginRequest: login ok, password change requested');
1256
- req.session.loginmode = 6;
1257
- req.session.messageid = 113; // Password change requested.
1258
- req.session.resettokenuserid = userid;
1259
- req.session.resettokenusername = xusername;
1260
- req.session.resettokenpassword = xpassword;
1261
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1262
- return;
1263
- }
1264
-
1265
- // Save login time
1266
- user.pastlogin = user.login;
1267
- user.login = user.access = Math.floor(Date.now() / 1000);
1268
- obj.db.SetUser(user);
1269
-
1270
- // Notify account login
1271
- const targets = ['*', 'server-users', user._id];
1272
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1273
- const ua = getUserAgentInfo(req);
1274
- const loginEvent = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 107, msgArgs: [req.clientIp, ua.browserStr, ua.osStr], msg: 'Account login', domain: domain.id, ip: req.clientIp, userAgent: req.headers['user-agent'] };
1275
- if (loginOptions != null) {
1276
- if ((loginOptions.tokenName != null) && (loginOptions.tokenUser != null)) { loginEvent.tokenName = loginOptions.tokenName; loginEvent.tokenUser = loginOptions.tokenUser; } // If a login token was used, add it to the event.
1277
- if (loginOptions.twoFactorType != null) { loginEvent.twoFactorType = loginOptions.twoFactorType; }
1278
- }
1279
- obj.parent.DispatchEvent(targets, obj, loginEvent);
1280
-
1281
- // Regenerate session when signing in to prevent fixation
1282
- //req.session.regenerate(function () {
1283
- // Store the user's primary key in the session store to be retrieved, or in this case the entire user object
1284
- delete req.session.u2f;
1285
- delete req.session.loginmode;
1286
- delete req.session.tuserid;
1287
- delete req.session.tuser;
1288
- delete req.session.tpass;
1289
- delete req.session.temail;
1290
- delete req.session.tsms;
1291
- delete req.session.tpush;
1292
- delete req.session.messageid;
1293
- delete req.session.passhint;
1294
- delete req.session.cuserid;
1295
- delete req.session.expire;
1296
- delete req.session.currentNode;
1297
- req.session.userid = userid;
1298
- req.session.ip = req.clientIp;
1299
- setSessionRandom(req);
1300
-
1301
- // If a login token was used, add this information and expire time to the session.
1302
- if ((loginOptions != null) && (loginOptions.tokenName != null) && (loginOptions.tokenUser != null)) {
1303
- req.session.loginToken = loginOptions.tokenUser;
1304
- if (loginOptions.expire != null) { req.session.expire = loginOptions.expire; }
1305
- }
1306
-
1307
- if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
1308
- if (req.body.host) {
1309
- // TODO: This is a terrible search!!! FIX THIS.
1310
- /*
1311
- obj.db.GetAllType('node', function (err, docs) {
1312
- for (var i = 0; i < docs.length; i++) {
1313
- if (docs[i].name == req.body.host) {
1314
- req.session.currentNode = docs[i]._id;
1315
- break;
1316
- }
1317
- }
1318
- console.log("CurrentNode: " + req.session.currentNode);
1319
- // This redirect happens after finding node is completed
1320
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1321
- });
1322
- */
1323
- parent.debug('web', 'handleLoginRequest: login ok (1)');
1324
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } // Temporary
1325
- } else {
1326
- parent.debug('web', 'handleLoginRequest: login ok (2)');
1327
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1328
- }
1329
- //});
1330
- }
1331
-
1332
- function handleCreateAccountRequest(req, res, direct) {
1333
- const domain = checkUserIpAddress(req, res);
1334
- if (domain == null) { return; }
1335
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleCreateAccountRequest: failed checks.'); res.sendStatus(404); return; }
1336
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1337
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1338
-
1339
- // Check if we are in maintenance mode
1340
- if (parent.config.settings.maintenancemode != null) {
1341
- req.session.messageid = 115; // Server under maintenance
1342
- req.session.loginmode = 1;
1343
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1344
- return;
1345
- }
1346
-
1347
- // Always lowercase the email address
1348
- if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1349
-
1350
- // If the email is the username, set this here.
1351
- if (domain.usernameisemail) { req.body.username = req.body.email; }
1352
-
1353
- // Accounts that start with ~ are not allowed
1354
- if ((typeof req.body.username != 'string') || (req.body.username.length < 1) || (req.body.username[0] == '~')) {
1355
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (0)');
1356
- req.session.loginmode = 2;
1357
- req.session.messageid = 100; // Unable to create account.
1358
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1359
- return;
1360
- }
1361
-
1362
- // Count the number of users in this domain
1363
- var domainUserCount = 0;
1364
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { domainUserCount++; } }
1365
-
1366
- // Check if we are allowed to create new users using the login screen
1367
- if ((domain.newaccounts !== 1) && (domain.newaccounts !== true) && (domainUserCount > 0)) {
1368
- parent.debug('web', 'handleCreateAccountRequest: domainUserCount > 1.');
1369
- res.sendStatus(401);
1370
- return;
1371
- }
1372
-
1373
- // Check if this request is for an allows email domain
1374
- if ((domain.newaccountemaildomains != null) && Array.isArray(domain.newaccountemaildomains)) {
1375
- var i = -1;
1376
- if (typeof req.body.email == 'string') { i = req.body.email.indexOf('@'); }
1377
- if (i == -1) {
1378
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
1379
- req.session.loginmode = 2;
1380
- req.session.messageid = 100; // Unable to create account.
1381
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1382
- return;
1383
- }
1384
- var emailok = false, emaildomain = req.body.email.substring(i + 1).toLowerCase();
1385
- for (var i in domain.newaccountemaildomains) { if (emaildomain == domain.newaccountemaildomains[i].toLowerCase()) { emailok = true; } }
1386
- if (emailok == false) {
1387
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)');
1388
- req.session.loginmode = 2;
1389
- req.session.messageid = 100; // Unable to create account.
1390
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1391
- return;
1392
- }
1393
- }
1394
-
1395
- // Check if we exceed the maximum number of user accounts
1396
- obj.db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) {
1397
- if (maxExceed) {
1398
- parent.debug('web', 'handleCreateAccountRequest: account limit reached');
1399
- req.session.loginmode = 2;
1400
- req.session.messageid = 101; // Account limit reached.
1401
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1402
- } else {
1403
- if (!obj.common.validateUsername(req.body.username, 1, 64) || !obj.common.validateEmail(req.body.email, 1, 256) || !obj.common.validateString(req.body.password1, 1, 256) || !obj.common.validateString(req.body.password2, 1, 256) || (req.body.password1 != req.body.password2) || req.body.username == '~' || !obj.common.checkPasswordRequirements(req.body.password1, domain.passwordrequirements)) {
1404
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (3)');
1405
- req.session.loginmode = 2;
1406
- req.session.messageid = 100; // Unable to create account.
1407
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1408
- } else {
1409
- // Check if this email was already verified
1410
- obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) {
1411
- if ((docs != null) && (docs.length > 0)) {
1412
- parent.debug('web', 'handleCreateAccountRequest: Existing account with this email address');
1413
- req.session.loginmode = 2;
1414
- req.session.messageid = 102; // Existing account with this email address.
1415
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1416
- } else {
1417
- // Check if there is domain.newAccountToken, check if supplied token is valid
1418
- if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
1419
- parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token');
1420
- req.session.loginmode = 2;
1421
- req.session.messageid = 103; // Invalid account creation token.
1422
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1423
- return;
1424
- }
1425
- // Check if user exists
1426
- if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
1427
- parent.debug('web', 'handleCreateAccountRequest: Username already exists');
1428
- req.session.loginmode = 2;
1429
- req.session.messageid = 104; // Username already exists.
1430
- } else {
1431
- var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
1432
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
1433
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
1434
- if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) { hint = hint.substring(0, 250); } user.passhint = hint; }
1435
- if (domainUserCount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
1436
-
1437
- // Auto-join any user groups
1438
- if (typeof domain.newaccountsusergroups == 'object') {
1439
- for (var i in domain.newaccountsusergroups) {
1440
- var ugrpid = domain.newaccountsusergroups[i];
1441
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
1442
- var ugroup = obj.userGroups[ugrpid];
1443
- if (ugroup != null) {
1444
- // Add group to the user
1445
- if (user.links == null) { user.links = {}; }
1446
- user.links[ugroup._id] = { rights: 1 };
1447
-
1448
- // Add user to the group
1449
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
1450
- db.Set(ugroup);
1451
-
1452
- // Notify user group change
1453
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
1454
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
1455
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
1456
- }
1457
- }
1458
- }
1459
-
1460
- obj.users[user._id] = user;
1461
- req.session.userid = user._id;
1462
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1463
- setSessionRandom(req);
1464
- // Create a user, generate a salt and hash the password
1465
- require('./pass').hash(req.body.password1, function (err, salt, hash, tag) {
1466
- if (err) throw err;
1467
- user.salt = salt;
1468
- user.hash = hash;
1469
- delete user.passtype;
1470
- obj.db.SetUser(user);
1471
-
1472
- // Send the verification email
1473
- if ((domain.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (obj.common.validateEmail(user.email, 1, 256) == true)) { domain.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key); }
1474
- }, 0);
1475
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id };
1476
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
1477
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
1478
- }
1479
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1480
- }
1481
- });
1482
- }
1483
- }
1484
- });
1485
- }
1486
-
1487
- // Called to process an account password reset
1488
- function handleResetPasswordRequest(req, res, direct) {
1489
- const domain = checkUserIpAddress(req, res);
1490
- if (domain == null) { return; }
1491
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1492
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1493
-
1494
- // Check everything is ok
1495
- const allowAccountReset = ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.allowaccountreset !== false));
1496
- if ((allowAccountReset === false) || (domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (typeof req.body.rpassword1 != 'string') || (typeof req.body.rpassword2 != 'string') || (req.body.rpassword1 != req.body.rpassword2) || (typeof req.body.rpasswordhint != 'string') || (req.session == null) || (typeof req.session.resettokenusername != 'string') || (typeof req.session.resettokenpassword != 'string')) {
1497
- parent.debug('web', 'handleResetPasswordRequest: checks failed');
1498
- delete req.session.u2f;
1499
- delete req.session.loginmode;
1500
- delete req.session.tuserid;
1501
- delete req.session.tuser;
1502
- delete req.session.tpass;
1503
- delete req.session.resettokenuserid;
1504
- delete req.session.resettokenusername;
1505
- delete req.session.resettokenpassword;
1506
- delete req.session.temail;
1507
- delete req.session.tsms;
1508
- delete req.session.tpush;
1509
- delete req.session.messageid;
1510
- delete req.session.passhint;
1511
- delete req.session.cuserid;
1512
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1513
- return;
1514
- }
1515
-
1516
- // Authenticate the user
1517
- obj.authenticate(req.session.resettokenusername, req.session.resettokenpassword, domain, function (err, userid, passhint, loginOptions) {
1518
- if (userid) {
1519
- // Login
1520
- var user = obj.users[userid];
1521
-
1522
- // If we have password requirements, check this here.
1523
- if (!obj.common.checkPasswordRequirements(req.body.rpassword1, domain.passwordrequirements)) {
1524
- parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (1)');
1525
- req.session.loginmode = 6;
1526
- req.session.messageid = 105; // Password rejected, use a different one.
1527
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1528
- return;
1529
- }
1530
-
1531
- // Check if the password is the same as a previous one
1532
- obj.checkOldUserPasswords(domain, user, req.body.rpassword1, function (result) {
1533
- if (result != 0) {
1534
- // This is the same password as an older one, request a password change again
1535
- parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (2)');
1536
- req.session.loginmode = 6;
1537
- req.session.messageid = 105; // Password rejected, use a different one.
1538
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1539
- } else {
1540
- // Update the password, use a different salt.
1541
- require('./pass').hash(req.body.rpassword1, function (err, salt, hash, tag) {
1542
- const nowSeconds = Math.floor(Date.now() / 1000);
1543
- if (err) { parent.debug('web', 'handleResetPasswordRequest: hash error.'); throw err; }
1544
-
1545
- if (domain.passwordrequirements != null) {
1546
- // Save password hint if this feature is enabled
1547
- if ((domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) hint = hint.substring(0, 250); user.passhint = hint; } else { delete user.passhint; }
1548
-
1549
- // Save previous password if this feature is enabled
1550
- if ((typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
1551
- if (user.oldpasswords == null) { user.oldpasswords = []; }
1552
- user.oldpasswords.push({ salt: user.salt, hash: user.hash, start: user.passchange, end: nowSeconds });
1553
- const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
1554
- if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
1555
- }
1556
- }
1557
-
1558
- user.salt = salt;
1559
- user.hash = hash;
1560
- user.passchange = user.access = nowSeconds;
1561
- delete user.passtype;
1562
- obj.db.SetUser(user);
1563
-
1564
- // Event the account change
1565
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'User password reset', domain: domain.id };
1566
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1567
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1568
-
1569
- // Login successful
1570
- parent.debug('web', 'handleResetPasswordRequest: success');
1571
- req.session.userid = userid;
1572
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1573
- setSessionRandom(req);
1574
- completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tuser, req.session.tpass, direct, loginOptions);
1575
- }, 0);
1576
- }
1577
- }, 0);
1578
- } else {
1579
- // Failed, error out.
1580
- parent.debug('web', 'handleResetPasswordRequest: failed authenticate()');
1581
- delete req.session.u2f;
1582
- delete req.session.loginmode;
1583
- delete req.session.tuserid;
1584
- delete req.session.tuser;
1585
- delete req.session.tpass;
1586
- delete req.session.resettokenuserid;
1587
- delete req.session.resettokenusername;
1588
- delete req.session.resettokenpassword;
1589
- delete req.session.temail;
1590
- delete req.session.tsms;
1591
- delete req.session.tpush;
1592
- delete req.session.messageid;
1593
- delete req.session.passhint;
1594
- delete req.session.cuserid;
1595
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1596
- return;
1597
- }
1598
- });
1599
- }
1600
-
1601
- // Called to process an account reset request
1602
- function handleResetAccountRequest(req, res, direct) {
1603
- const domain = checkUserIpAddress(req, res);
1604
- if (domain == null) { return; }
1605
- const allowAccountReset = ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.allowaccountreset !== false));
1606
- if ((allowAccountReset === false) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (obj.args.lanonly == true) || (obj.parent.certificates.CommonName == null) || (obj.parent.certificates.CommonName.indexOf('.') == -1)) { parent.debug('web', 'handleResetAccountRequest: check failed'); res.sendStatus(404); return; }
1607
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1608
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1609
-
1610
- // Always lowercase the email address
1611
- if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1612
-
1613
- // Get the email from the body or session.
1614
- var email = req.body.email;
1615
- if ((email == null) || (email == '')) { email = req.session.temail; }
1616
-
1617
- // Check the email string format
1618
- if (!email || checkEmail(email) == false) {
1619
- parent.debug('web', 'handleResetAccountRequest: Invalid email');
1620
- req.session.loginmode = 3;
1621
- req.session.messageid = 106; // Invalid email.
1622
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1623
- } else {
1624
- obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
1625
- // Remove all accounts that start with ~ since they are special accounts.
1626
- var cleanDocs = [];
1627
- if ((err == null) && (docs.length > 0)) {
1628
- for (var i in docs) {
1629
- const user = docs[i];
1630
- const locked = ((user.siteadmin != null) && (user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)); // No password recovery for locked accounts
1631
- const specialAccount = (user._id.split('/')[2].startsWith('~')); // No password recovery for special accounts
1632
- if ((specialAccount == false) && (locked == false)) { cleanDocs.push(user); }
1633
- }
1634
- }
1635
- docs = cleanDocs;
1636
-
1637
- // Check if we have any account that match this email address
1638
- if ((err != null) || (docs.length == 0)) {
1639
- parent.debug('web', 'handleResetAccountRequest: Account not found');
1640
- req.session.loginmode = 3;
1641
- req.session.messageid = 1; // If valid, reset mail sent. Instead of "Account not found" (107), we send this hold on message so users can't know if this account exists or not.
1642
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1643
- } else {
1644
- // If many accounts have the same validated e-mail, we are going to use the first one for display, but sent a reset email for all accounts.
1645
- var responseSent = false;
1646
- for (var i in docs) {
1647
- var user = docs[i];
1648
- if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
1649
- // Second factor setup, request it now.
1650
- checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result, authData) {
1651
- if (result == false) {
1652
- if (i == 0) {
1653
-
1654
- // Check if 2FA is allowed for this IP address
1655
- if (obj.checkAllow2Fa(req) == false) {
1656
- // Wait and redirect the user
1657
- setTimeout(function () {
1658
- req.session.messageid = 114; // IP address blocked, try again later.
1659
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1660
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
1661
- return;
1662
- }
1663
-
1664
- // 2-step auth is required, but the token is not present or not valid.
1665
- parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again');
1666
- if ((req.body.token != null) || (req.body.hwtoken != null)) {
1667
- var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
1668
- if ((req.body.hwtoken == '**sms**') && sms2fa) {
1669
- // Cause a token to be sent to the user's phone number
1670
- user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
1671
- obj.db.SetUser(user);
1672
- parent.debug('web', 'Sending 2FA SMS for password recovery to: ' + user.phone);
1673
- parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
1674
- req.session.messageid = 4; // SMS sent.
1675
- } else {
1676
- req.session.messageid = 108; // Invalid token, try again.
1677
- const ua = getUserAgentInfo(req);
1678
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { action: 'authfail', username: user.name, userid: user._id, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp, msgid: 108, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1679
- obj.setbad2Fa(req);
1680
- }
1681
- }
1682
- req.session.loginmode = 5;
1683
- req.session.temail = email;
1684
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1685
- }
1686
- } else {
1687
- // Send email to perform recovery.
1688
- delete req.session.temail;
1689
- if (domain.mailserver != null) {
1690
- domain.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1691
- if (i == 0) {
1692
- parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
1693
- req.session.loginmode = 1;
1694
- req.session.messageid = 1; // If valid, reset mail sent.
1695
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1696
- }
1697
- } else {
1698
- if (i == 0) {
1699
- parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
1700
- req.session.loginmode = 3;
1701
- req.session.messageid = 109; // Unable to sent email.
1702
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1703
- }
1704
- }
1705
- }
1706
- });
1707
- } else {
1708
- // No second factor, send email to perform recovery.
1709
- if (domain.mailserver != null) {
1710
- domain.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1711
- if (i == 0) {
1712
- parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
1713
- req.session.loginmode = 1;
1714
- req.session.messageid = 1; // If valid, reset mail sent.
1715
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1716
- }
1717
- } else {
1718
- if (i == 0) {
1719
- parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
1720
- req.session.loginmode = 3;
1721
- req.session.messageid = 109; // Unable to sent email.
1722
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1723
- }
1724
- }
1725
- }
1726
- }
1727
- }
1728
- });
1729
- }
1730
- }
1731
-
1732
- // Handle account email change and email verification request
1733
- function handleCheckAccountEmailRequest(req, res, direct) {
1734
- const domain = checkUserIpAddress(req, res);
1735
- if (domain == null) { return; }
1736
- if ((domain.mailserver == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (typeof req.session.cuserid != 'string') || (obj.users[req.session.cuserid] == null) || (!obj.common.validateEmail(req.body.email, 1, 256))) { parent.debug('web', 'handleCheckAccountEmailRequest: failed checks.'); res.sendStatus(404); return; }
1737
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1738
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1739
-
1740
- // Always lowercase the email address
1741
- if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1742
-
1743
- // Get the email from the body or session.
1744
- var email = req.body.email;
1745
- if ((email == null) || (email == '')) { email = req.session.temail; }
1746
-
1747
- // Check if this request is for an allows email domain
1748
- if ((domain.newaccountemaildomains != null) && Array.isArray(domain.newaccountemaildomains)) {
1749
- var i = -1;
1750
- if (typeof req.body.email == 'string') { i = req.body.email.indexOf('@'); }
1751
- if (i == -1) {
1752
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
1753
- req.session.loginmode = 7;
1754
- req.session.messageid = 106; // Invalid email.
1755
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1756
- return;
1757
- }
1758
- var emailok = false, emaildomain = req.body.email.substring(i + 1).toLowerCase();
1759
- for (var i in domain.newaccountemaildomains) { if (emaildomain == domain.newaccountemaildomains[i].toLowerCase()) { emailok = true; } }
1760
- if (emailok == false) {
1761
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)');
1762
- req.session.loginmode = 7;
1763
- req.session.messageid = 106; // Invalid email.
1764
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1765
- return;
1766
- }
1767
- }
1768
-
1769
- // Check the email string format
1770
- if (!email || checkEmail(email) == false) {
1771
- parent.debug('web', 'handleCheckAccountEmailRequest: Invalid email');
1772
- req.session.loginmode = 7;
1773
- req.session.messageid = 106; // Invalid email.
1774
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1775
- } else {
1776
- // Check is email already exists
1777
- obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
1778
- if ((err != null) || ((docs.length > 0) && (docs.find(function (u) { return (u._id === req.session.cuserid); }) < 0))) {
1779
- // Email already exitst
1780
- req.session.messageid = 102; // Existing account with this email address.
1781
- } else {
1782
- // Update the user and notify of user email address change
1783
- var user = obj.users[req.session.cuserid];
1784
- if (user.email != email) {
1785
- user.email = email;
1786
- db.SetUser(user);
1787
- var targets = ['*', 'server-users', user._id];
1788
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1789
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Account changed: ' + user.name, domain: domain.id };
1790
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1791
- parent.DispatchEvent(targets, obj, event);
1792
- }
1793
-
1794
- // Send the verification email
1795
- domain.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1796
-
1797
- // Send the response
1798
- req.session.messageid = 2; // Email sent.
1799
- }
1800
- req.session.loginmode = 7;
1801
- delete req.session.cuserid;
1802
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1803
- });
1804
- }
1805
- }
1806
-
1807
- // Called to process a web based email verification request
1808
- function handleCheckMailRequest(req, res) {
1809
- const domain = checkUserIpAddress(req, res);
1810
- if (domain == null) { return; }
1811
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap') || (domain.mailserver == null)) { parent.debug('web', 'handleCheckMailRequest: failed checks.'); res.sendStatus(404); return; }
1812
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1813
-
1814
- if (req.query.c != null) {
1815
- var cookie = obj.parent.decodeCookie(req.query.c, domain.mailserver.mailCookieEncryptionKey, 30);
1816
- if ((cookie != null) && (cookie.u != null) && (cookie.u.startsWith('user/')) && (cookie.e != null)) {
1817
- var idsplit = cookie.u.split('/');
1818
- if ((idsplit.length != 3) || (idsplit[1] != domain.id)) {
1819
- parent.debug('web', 'handleCheckMailRequest: Invalid domain.');
1820
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 1, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1821
- } else {
1822
- obj.db.Get(cookie.u, function (err, docs) {
1823
- if (docs.length == 0) {
1824
- parent.debug('web', 'handleCheckMailRequest: Invalid username.');
1825
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 2, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(idsplit[1]).replace(/'/g, '%27') }, req, domain));
1826
- } else {
1827
- var user = docs[0];
1828
- if (user.email != cookie.e) {
1829
- parent.debug('web', 'handleCheckMailRequest: Invalid e-mail.');
1830
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 3, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27'), arg2: encodeURIComponent(user.name).replace(/'/g, '%27') }, req, domain));
1831
- } else {
1832
- if (cookie.a == 1) {
1833
- // Account email verification
1834
- if (user.emailVerified == true) {
1835
- parent.debug('web', 'handleCheckMailRequest: email already verified.');
1836
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 4, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27'), arg2: encodeURIComponent(user.name).replace(/'/g, '%27') }, req, domain));
1837
- } else {
1838
- obj.db.GetUserWithVerifiedEmail(domain.id, user.email, function (err, docs) {
1839
- if ((docs.length > 0) && (docs.find(function (u) { return (u._id === user._id); }) < 0)) {
1840
- parent.debug('web', 'handleCheckMailRequest: email already in use.');
1841
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 5, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27') }, req, domain));
1842
- } else {
1843
- parent.debug('web', 'handleCheckMailRequest: email verification success.');
1844
-
1845
- // Set the verified flag
1846
- obj.users[user._id].emailVerified = true;
1847
- user.emailVerified = true;
1848
- obj.db.SetUser(user);
1849
-
1850
- // Event the change
1851
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Verified email of user ' + EscapeHtml(user.name) + ' (' + EscapeHtml(user.email) + ')', domain: domain.id };
1852
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1853
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1854
-
1855
- // Send the confirmation page
1856
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 6, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27'), arg2: encodeURIComponent(user.name).replace(/'/g, '%27') }, req, domain));
1857
-
1858
- // Send a notification
1859
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: 'Email verified', value: user.email, nolog: 1, id: Math.random() });
1860
-
1861
- // Send to authlog
1862
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Verified email address ' + user.email + ' for user ' + user.name); }
1863
- }
1864
- });
1865
- }
1866
- } else if (cookie.a == 2) {
1867
- // Account reset
1868
- if (user.emailVerified != true) {
1869
- parent.debug('web', 'handleCheckMailRequest: email not verified.');
1870
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 7, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: EscapeHtml(user.email), arg2: EscapeHtml(user.name) }, req, domain));
1871
- } else {
1872
- if (req.query.confirm == 1) {
1873
- // Set a temporary password
1874
- obj.crypto.randomBytes(16, function (err, buf) {
1875
- var newpass = buf.toString('base64').split('=').join('').split('/').join('').split('+').join('');
1876
- require('./pass').hash(newpass, function (err, salt, hash, tag) {
1877
- if (err) throw err;
1878
-
1879
- // Change the password
1880
- var userinfo = obj.users[user._id];
1881
- userinfo.salt = salt;
1882
- userinfo.hash = hash;
1883
- delete userinfo.passtype;
1884
- userinfo.passchange = userinfo.access = Math.floor(Date.now() / 1000);
1885
- delete userinfo.passhint;
1886
- obj.db.SetUser(userinfo);
1887
-
1888
- // Event the change
1889
- var event = { etype: 'user', userid: user._id, username: userinfo.name, account: obj.CloneSafeUser(userinfo), action: 'accountchange', msg: 'Password reset for user ' + EscapeHtml(user.name), domain: domain.id };
1890
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1891
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1892
-
1893
- // Send the new password
1894
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 8, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: EscapeHtml(user.name), arg2: EscapeHtml(newpass) }, req, domain));
1895
- parent.debug('web', 'handleCheckMailRequest: send temporary password.');
1896
-
1897
- // Send to authlog
1898
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Performed account reset for user ' + user.name); }
1899
- }, 0);
1900
- });
1901
- } else {
1902
- // Display a link for the user to confirm password reset
1903
- // We must do this because GMail will also load this URL a few seconds after the user does and we don't want to cause two password resets.
1904
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 14, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1905
- }
1906
- }
1907
- } else {
1908
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 9, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1909
- }
1910
- }
1911
- }
1912
- });
1913
- }
1914
- } else {
1915
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 10, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1916
- }
1917
- }
1918
- }
1919
-
1920
- // Called to process an agent invite GET/POST request
1921
- function handleInviteRequest(req, res) {
1922
- const domain = getDomain(req);
1923
- if (domain == null) { parent.debug('web', 'handleInviteRequest: failed checks.'); res.sendStatus(404); return; }
1924
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1925
- if ((req.body.inviteCode == null) || (req.body.inviteCode == '')) { render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 0 }, req, domain)); return; } // No invitation code
1926
-
1927
- // Each for a device group that has this invite code.
1928
- for (var i in obj.meshes) {
1929
- if ((obj.meshes[i].domain == domain.id) && (obj.meshes[i].deleted == null) && (obj.meshes[i].invite != null) && (obj.meshes[i].invite.codes.indexOf(req.body.inviteCode) >= 0)) {
1930
- // Send invitation link, valid for 1 minute.
1931
- res.redirect(domain.url + 'agentinvite?c=' + parent.encodeCookie({ a: 4, mid: i, f: obj.meshes[i].invite.flags, ag: obj.meshes[i].invite.ag, expire: 1 }, parent.invitationLinkEncryptionKey) + (req.query.key ? ('&key=' + req.query.key) : '') + (req.query.hide ? ('&hide=' + req.query.hide) : ''));
1932
- return;
1933
- }
1934
- }
1935
-
1936
- render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 100 }, req, domain)); // Bad invitation code
1937
- }
1938
-
1939
- // Called to render the MSTSC (RDP) or SSH web page
1940
- function handleMSTSCRequest(req, res, page) {
1941
- const domain = getDomain(req);
1942
- if (domain == null) { parent.debug('web', 'handleMSTSCRequest: failed checks.'); res.sendStatus(404); return; }
1943
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1944
-
1945
- // Check if we are in maintenance mode
1946
- if ((parent.config.settings.maintenancemode != null) && (req.query.loginscreen !== '1')) {
1947
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 3, msgid: 13, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1948
- return;
1949
- }
1950
-
1951
- // Set features we want to send to this page
1952
- var features = 0;
1953
- if (domain.allowsavingdevicecredentials === false) { features |= 1; }
1954
-
1955
- if (req.query.ws != null) {
1956
- // This is a query with a websocket relay cookie, check that the cookie is valid and use it.
1957
- var rcookie = parent.decodeCookie(req.query.ws, parent.loginCookieEncryptionKey, 60); // Cookie with 1 hour timeout
1958
- if ((rcookie != null) && (rcookie.domainid == domain.id) && (rcookie.nodeid != null) && (rcookie.tcpport != null)) {
1959
-
1960
- // Fetch the node from the database
1961
- obj.db.Get(rcookie.nodeid, function (err, nodes) {
1962
- if ((err != null) || (nodes.length != 1)) { res.sendStatus(404); return; }
1963
- const node = nodes[0];
1964
-
1965
- // Check if we have RDP credentials for this device
1966
- var serverCredentials = false;
1967
- if (domain.allowsavingdevicecredentials !== false) {
1968
- if (page == 'ssh') {
1969
- serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string'))
1970
- } else {
1971
- serverCredentials = ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string'))
1972
- }
1973
- }
1974
-
1975
- // Render the page
1976
- render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: req.query.ws, name: encodeURIComponent(req.query.name).replace(/'/g, '%27'), serverCredentials: serverCredentials, features: features }, req, domain));
1977
- });
1978
- return;
1979
- }
1980
- }
1981
-
1982
- // Get the logged in user if present
1983
- var user = null;
1984
-
1985
- // If there is a login token, use that
1986
- if (req.query.login != null) {
1987
- var ucookie = parent.decodeCookie(req.query.login, parent.loginCookieEncryptionKey, 60); // Cookie with 1 hour timeout
1988
- if ((ucookie != null) && (ucookie.a === 3) && (typeof ucookie.u == 'string')) { user = obj.users[ucookie.u]; }
1989
- }
1990
-
1991
- // If no token, see if we have an active session
1992
- if ((user == null) && (req.session.userid != null)) { user = obj.users[req.session.userid]; }
1993
-
1994
- // If still no user, see if we have a default user
1995
- if ((user == null) && (obj.args.user)) { user = obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]; }
1996
-
1997
- // No user login, exit now
1998
- if (user == null) { res.sendStatus(401); return; }
1999
-
2000
- // Check the nodeid
2001
- if (req.query.node != null) {
2002
- var nodeidsplit = req.query.node.split('/');
2003
- if (nodeidsplit.length == 1) {
2004
- req.query.node = 'node/' + domain.id + '/' + nodeidsplit[0]; // Format the nodeid correctly
2005
- } else if (nodeidsplit.length == 3) {
2006
- if ((nodeidsplit[0] != 'node') || (nodeidsplit[1] != domain.id)) { req.query.node = null; } // Check the nodeid format
2007
- } else {
2008
- req.query.node = null; // Bad nodeid
2009
- }
2010
- }
2011
-
2012
- // If there is no nodeid, exit now
2013
- if (req.query.node == null) { render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: '', name: '', features: features }, req, domain)); return; }
2014
-
2015
- // Fetch the node from the database
2016
- obj.db.Get(req.query.node, function (err, nodes) {
2017
- if ((err != null) || (nodes.length != 1)) { res.sendStatus(404); return; }
2018
- const node = nodes[0];
2019
-
2020
- // Check access rights, must have remote control rights
2021
- if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { res.sendStatus(401); return; }
2022
-
2023
- // Figure out the target port
2024
- var port = 0, serverCredentials = false;
2025
- if (page == 'ssh') {
2026
- // SSH port
2027
- port = 22;
2028
- if (typeof node.sshport == 'number') { port = node.sshport; }
2029
-
2030
- // Check if we have SSH credentials for this device
2031
- if (domain.allowsavingdevicecredentials !== false) { serverCredentials = ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string')); }
2032
- } else {
2033
- // RDP port
2034
- port = 3389;
2035
- if (typeof node.rdpport == 'number') { port = node.rdpport; }
2036
-
2037
- // Check if we have RDP credentials for this device
2038
- if (domain.allowsavingdevicecredentials !== false) { serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')); }
2039
- }
2040
- if (req.query.port != null) { var qport = 0; try { qport = parseInt(req.query.port); } catch (ex) { } if ((typeof qport == 'number') && (qport > 0) && (qport < 65536)) { port = qport; } }
2041
-
2042
- // Generate a cookie and respond
2043
- var cookie = parent.encodeCookie({ userid: user._id, domainid: user.domain, nodeid: node._id, tcpport: port }, parent.loginCookieEncryptionKey);
2044
- render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: cookie, name: encodeURIComponent(node.name).replace(/'/g, '%27'), serverCredentials: serverCredentials, features: features }, req, domain));
2045
- });
2046
- }
2047
-
2048
- // Called to handle push-only requests
2049
- function handleFirebasePushOnlyRelayRequest(req, res) {
2050
- parent.debug('email', 'handleFirebasePushOnlyRelayRequest');
2051
- if ((req.body == null) || (req.body.msg == null) || (obj.parent.firebase == null)) { res.sendStatus(404); return; }
2052
- if (obj.parent.config.firebase.pushrelayserver == null) { res.sendStatus(404); return; }
2053
- if ((typeof obj.parent.config.firebase.pushrelayserver == 'string') && (req.query.key != obj.parent.config.firebase.pushrelayserver)) { res.sendStatus(404); return; }
2054
- var data = null;
2055
- try { data = JSON.parse(req.body.msg) } catch (ex) { res.sendStatus(404); return; }
2056
- if (typeof data != 'object') { res.sendStatus(404); return; }
2057
- if (typeof data.pmt != 'string') { res.sendStatus(404); return; }
2058
- if (typeof data.payload != 'object') { res.sendStatus(404); return; }
2059
- if (typeof data.payload.notification != 'object') { res.sendStatus(404); return; }
2060
- if (typeof data.payload.notification.title != 'string') { res.sendStatus(404); return; }
2061
- if (typeof data.payload.notification.body != 'string') { res.sendStatus(404); return; }
2062
- if (typeof data.options != 'object') { res.sendStatus(404); return; }
2063
- if ((data.options.priority != 'Normal') && (data.options.priority != 'High')) { res.sendStatus(404); return; }
2064
- if ((typeof data.options.timeToLive != 'number') || (data.options.timeToLive < 1)) { res.sendStatus(404); return; }
2065
- parent.debug('email', 'handleFirebasePushOnlyRelayRequest - ok');
2066
- obj.parent.firebase.sendToDevice({ pmt: data.pmt }, data.payload, data.options, function (id, err, errdesc) {
2067
- if (err == null) { res.sendStatus(200); } else { res.sendStatus(500); }
2068
- });
2069
- }
2070
-
2071
- // Called to handle two-way push notification relay request
2072
- function handleFirebaseRelayRequest(ws, req) {
2073
- parent.debug('email', 'handleFirebaseRelayRequest');
2074
- if (obj.parent.firebase == null) { try { ws.close(); } catch (e) { } return; }
2075
- if (obj.parent.firebase.setupRelay == null) { try { ws.close(); } catch (e) { } return; }
2076
- if (obj.parent.config.firebase.relayserver == null) { try { ws.close(); } catch (e) { } return; }
2077
- if ((typeof obj.parent.config.firebase.relayserver == 'string') && (req.query.key != obj.parent.config.firebase.relayserver)) { res.sendStatus(404); try { ws.close(); } catch (e) { } return; }
2078
- obj.parent.firebase.setupRelay(ws);
2079
- }
2080
-
2081
- // Called to process an agent invite request
2082
- function handleAgentInviteRequest(req, res) {
2083
- const domain = getDomain(req);
2084
- if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) { parent.debug('web', 'handleAgentInviteRequest: failed checks.'); res.sendStatus(404); return; }
2085
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2086
-
2087
- if (req.query.c != null) {
2088
- // A cookie is specified in the query string, use that
2089
- var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey);
2090
- if (cookie == null) { res.sendStatus(404); return; }
2091
- var mesh = obj.meshes[cookie.mid];
2092
- if (mesh == null) { res.sendStatus(404); return; }
2093
- var installflags = cookie.f;
2094
- if (typeof installflags != 'number') { installflags = 0; }
2095
- var showagents = cookie.ag;
2096
- if (typeof showagents != 'number') { showagents = 0; }
2097
- parent.debug('web', 'handleAgentInviteRequest using cookie.');
2098
-
2099
- // Build the mobile agent URL, this is used to connect mobile devices
2100
- var agentServerName = obj.getWebServerName(domain);
2101
- if (typeof obj.args.agentaliasdns == 'string') { agentServerName = obj.args.agentaliasdns; }
2102
- var xdomain = (domain.dns == null) ? domain.id : '';
2103
- var agentHttpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2104
- if (obj.args.agentport != null) { agentHttpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
2105
- if (obj.args.agentaliasport != null) { agentHttpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
2106
- var magenturl = 'mc://' + agentServerName + ((agentHttpsPort != 443) ? (':' + agentHttpsPort) : '') + ((xdomain != '') ? ('/' + xdomain) : '') + ',' + obj.agentCertificateHashBase64 + ',' + mesh._id.split('/')[2];
2107
-
2108
- var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
2109
- render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, showagents: showagents, magenturl: magenturl }, req, domain));
2110
- } else if (req.query.m != null) {
2111
- // The MeshId is specified in the query string, use that
2112
- var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.m.toLowerCase()];
2113
- if (mesh == null) { res.sendStatus(404); return; }
2114
- var installflags = 0;
2115
- if (req.query.f) { installflags = parseInt(req.query.f); }
2116
- if (typeof installflags != 'number') { installflags = 0; }
2117
- var showagents = 0;
2118
- if (req.query.f) { showagents = parseInt(req.query.ag); }
2119
- if (typeof showagents != 'number') { showagents = 0; }
2120
- parent.debug('web', 'handleAgentInviteRequest using meshid.');
2121
-
2122
- // Build the mobile agent URL, this is used to connect mobile devices
2123
- var agentServerName = obj.getWebServerName(domain);
2124
- if (typeof obj.args.agentaliasdns == 'string') { agentServerName = obj.args.agentaliasdns; }
2125
- var xdomain = (domain.dns == null) ? domain.id : '';
2126
- var agentHttpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2127
- if (obj.args.agentport != null) { agentHttpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
2128
- if (obj.args.agentaliasport != null) { agentHttpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
2129
- var magenturl = 'mc://' + agentServerName + ((agentHttpsPort != 443) ? (':' + agentHttpsPort) : '') + ((xdomain != '') ? ('/' + xdomain) : '') + ',' + obj.agentCertificateHashBase64 + ',' + mesh._id.split('/')[2];
2130
-
2131
- var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
2132
- render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, showagents: showagents, magenturl: magenturl }, req, domain));
2133
- }
2134
- }
2135
-
2136
- // Called to process an agent invite request
2137
- function handleUserImageRequest(req, res) {
2138
- const domain = getDomain(req);
2139
- if (domain == null) { parent.debug('web', 'handleUserImageRequest: failed checks.'); res.sendStatus(404); return; }
2140
- if ((req.session == null) || (req.session.userid == null)) { parent.debug('web', 'handleUserImageRequest: failed checks 2.'); res.sendStatus(404); return; }
2141
- var imageUserId = req.session.userid;
2142
- if ((req.query.id != null)) {
2143
- var user = obj.users[req.session.userid];
2144
- if ((user == null) || (user.siteadmin == null) && ((user.siteadmin & 2) == 0)) { res.sendStatus(404); return; }
2145
- imageUserId = 'user/' + domain.id + '/' + req.query.id;
2146
- }
2147
- obj.db.Get('im' + imageUserId, function (err, docs) {
2148
- if ((err != null) || (docs == null) || (docs.length != 1) || (typeof docs[0].image != 'string')) { res.sendStatus(404); return; }
2149
- var imagebase64 = docs[0].image;
2150
- if (imagebase64.startsWith('data:image/png;base64,')) {
2151
- res.set('Content-Type', 'image/png');
2152
- res.set({ 'Cache-Control': 'no-store' });
2153
- res.send(Buffer.from(imagebase64.substring(22), 'base64'));
2154
- } else if (imagebase64.startsWith('data:image/jpeg;base64,')) {
2155
- res.set('Content-Type', 'image/jpeg');
2156
- res.set({ 'Cache-Control': 'no-store' });
2157
- res.send(Buffer.from(imagebase64.substring(23), 'base64'));
2158
- } else {
2159
- res.sendStatus(404);
2160
- }
2161
- });
2162
- }
2163
-
2164
- function handleDeleteAccountRequest(req, res, direct) {
2165
- parent.debug('web', 'handleDeleteAccountRequest()');
2166
- const domain = checkUserIpAddress(req, res);
2167
- if (domain == null) { return; }
2168
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleDeleteAccountRequest: failed checks.'); res.sendStatus(404); return; }
2169
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2170
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
2171
-
2172
- var user = null;
2173
- if (req.body.authcookie) {
2174
- // If a authentication cookie is provided, decode it here
2175
- var loginCookie = obj.parent.decodeCookie(req.body.authcookie, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
2176
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { user = obj.users[loginCookie.userid]; }
2177
- } else {
2178
- // Check if the user is logged and we have all required parameters
2179
- if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.userid.split('/')[1] != domain.id)) {
2180
- parent.debug('web', 'handleDeleteAccountRequest: required parameters not present.');
2181
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2182
- return;
2183
- } else {
2184
- user = obj.users[req.session.userid];
2185
- }
2186
- }
2187
- if (!user) { parent.debug('web', 'handleDeleteAccountRequest: user not found.'); res.sendStatus(404); return; }
2188
- if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) { parent.debug('web', 'handleDeleteAccountRequest: account settings locked.'); res.sendStatus(404); return; }
2189
-
2190
- // Check if the password is correct
2191
- obj.authenticate(user._id.split('/')[2], req.body.apassword1, domain, function (err, userid, passhint, loginOptions) {
2192
- var deluser = obj.users[userid];
2193
- if ((userid != null) && (deluser != null)) {
2194
- // Remove all links to this user
2195
- if (deluser.links != null) {
2196
- for (var i in deluser.links) {
2197
- if (i.startsWith('mesh/')) {
2198
- // Get the device group
2199
- var mesh = obj.meshes[i];
2200
- if (mesh) {
2201
- // Remove user from the mesh
2202
- if (mesh.links[deluser._id] != null) { delete mesh.links[deluser._id]; parent.db.Set(mesh); }
2203
-
2204
- // Notify mesh change
2205
- var change = 'Removed user ' + deluser.name + ' from group ' + mesh.name;
2206
- var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id, invite: mesh.invite };
2207
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
2208
- parent.DispatchEvent(['*', mesh._id, deluser._id, user._id], obj, event);
2209
- }
2210
- } else if (i.startsWith('node/')) {
2211
- // Get the node and the rights for this node
2212
- obj.GetNodeWithRights(domain, deluser, i, function (node, rights, visible) {
2213
- if ((node == null) || (node.links == null) || (node.links[deluser._id] == null)) return;
2214
-
2215
- // Remove the link and save the node to the database
2216
- delete node.links[deluser._id];
2217
- if (Object.keys(node.links).length == 0) { delete node.links; }
2218
- db.Set(obj.cleanDevice(node));
2219
-
2220
- // Event the node change
2221
- var event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msg: ('Removed user device rights for ' + node.name), node: obj.CloneSafeNode(node) }
2222
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
2223
- parent.DispatchEvent(['*', node.meshid, node._id], obj, event);
2224
- });
2225
- } else if (i.startsWith('ugrp/')) {
2226
- // Get the device group
2227
- var ugroup = obj.userGroups[i];
2228
- if (ugroup) {
2229
- // Remove user from the user group
2230
- if (ugroup.links[deluser._id] != null) { delete ugroup.links[deluser._id]; parent.db.Set(ugroup); }
2231
-
2232
- // Notify user group change
2233
- var change = 'Removed user ' + deluser.name + ' from user group ' + ugroup.name;
2234
- var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Removed user ' + deluser.name + ' from user group ' + ugroup.name, addUserDomain: domain.id };
2235
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
2236
- parent.DispatchEvent(['*', ugroup._id, user._id, deluser._id], obj, event);
2237
- }
2238
- }
2239
- }
2240
- }
2241
-
2242
- obj.db.Remove('ws' + deluser._id); // Remove user web state
2243
- obj.db.Remove('nt' + deluser._id); // Remove notes for this user
2244
- obj.db.Remove('ntp' + deluser._id); // Remove personal notes for this user
2245
- obj.db.Remove('im' + deluser._id); // Remove image for this user
2246
-
2247
- // Delete any login tokens
2248
- parent.db.GetAllTypeNodeFiltered(['logintoken-' + deluser._id], domain.id, 'logintoken', null, function (err, docs) {
2249
- if ((err == null) && (docs != null)) { for (var i = 0; i < docs.length; i++) { parent.db.Remove(docs[i]._id, function () { }); } }
2250
- });
2251
-
2252
- // Delete all files on the server for this account
2253
- try {
2254
- var deluserpath = obj.getServerRootFilePath(deluser);
2255
- if (deluserpath != null) { obj.deleteFolderRec(deluserpath); }
2256
- } catch (e) { }
2257
-
2258
- // Remove the user
2259
- obj.db.Remove(deluser._id);
2260
- delete obj.users[deluser._id];
2261
- req.session = null;
2262
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2263
- obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluser._id, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id });
2264
- parent.debug('web', 'handleDeleteAccountRequest: removed user.');
2265
- } else {
2266
- parent.debug('web', 'handleDeleteAccountRequest: auth failed.');
2267
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2268
- }
2269
- });
2270
- }
2271
-
2272
- // Check a user's password
2273
- obj.checkUserPassword = function (domain, user, password, func) {
2274
- // Check the old password
2275
- if (user.passtype != null) {
2276
- // IIS default clear or weak password hashing (SHA-1)
2277
- require('./pass').iishash(user.passtype, password, user.salt, function (err, hash) {
2278
- if (err) { parent.debug('web', 'checkUserPassword: SHA-1 fail.'); return func(false); }
2279
- if (hash == user.hash) {
2280
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: SHA-1 locked.'); return func(false); } // Account is locked
2281
- parent.debug('web', 'checkUserPassword: SHA-1 ok.');
2282
- return func(true); // Allow password change
2283
- }
2284
- func(false);
2285
- });
2286
- } else {
2287
- // Default strong password hashing (pbkdf2 SHA384)
2288
- require('./pass').hash(password, user.salt, function (err, hash, tag) {
2289
- if (err) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 fail.'); return func(false); }
2290
- if (hash == user.hash) {
2291
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 locked.'); return func(false); } // Account is locked
2292
- parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 ok.');
2293
- return func(true); // Allow password change
2294
- }
2295
- func(false);
2296
- }, 0);
2297
- }
2298
- }
2299
-
2300
- // Check a user's old passwords
2301
- // Callback: 0=OK, 1=OldPass, 2=CommonPass
2302
- obj.checkOldUserPasswords = function (domain, user, password, func) {
2303
- // Check how many old passwords we need to check
2304
- if ((domain.passwordrequirements != null) && (typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
2305
- if (user.oldpasswords != null) {
2306
- const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
2307
- if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
2308
- }
2309
- } else {
2310
- delete user.oldpasswords;
2311
- }
2312
-
2313
- // If there is no old passwords, exit now.
2314
- var oldPassCount = 1;
2315
- if (user.oldpasswords != null) { oldPassCount += user.oldpasswords.length; }
2316
- var oldPassCheckState = { response: 0, count: oldPassCount, user: user, func: func };
2317
-
2318
- // Test against common passwords if this feature is enabled
2319
- // Example of common passwords: 123456789, password123
2320
- if ((domain.passwordrequirements != null) && (domain.passwordrequirements.bancommonpasswords == true)) {
2321
- oldPassCheckState.count++;
2322
- require('wildleek')(password).then(function (wild) {
2323
- if (wild == true) { oldPassCheckState.response = 2; }
2324
- if (--oldPassCheckState.count == 0) { oldPassCheckState.func(oldPassCheckState.response); }
2325
- });
2326
- }
2327
-
2328
- // Try current password
2329
- require('./pass').hash(password, user.salt, function oldPassCheck(err, hash, tag) {
2330
- if ((err == null) && (hash == tag.user.hash)) { tag.response = 1; }
2331
- if (--tag.count == 0) { tag.func(tag.response); }
2332
- }, oldPassCheckState);
2333
-
2334
- // Try each old password
2335
- if (user.oldpasswords != null) {
2336
- for (var i in user.oldpasswords) {
2337
- const oldpassword = user.oldpasswords[i];
2338
- // Default strong password hashing (pbkdf2 SHA384)
2339
- require('./pass').hash(password, oldpassword.salt, function oldPassCheck(err, hash, tag) {
2340
- if ((err == null) && (hash == tag.oldPassword.hash)) { tag.state.response = 1; }
2341
- if (--tag.state.count == 0) { tag.state.func(tag.state.response); }
2342
- }, { oldPassword: oldpassword, state: oldPassCheckState });
2343
- }
2344
- }
2345
- }
2346
-
2347
- // Handle password changes
2348
- function handlePasswordChangeRequest(req, res, direct) {
2349
- const domain = checkUserIpAddress(req, res);
2350
- if (domain == null) { return; }
2351
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).'); res.sendStatus(404); return; }
2352
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2353
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
2354
-
2355
- // Check if the user is logged and we have all required parameters
2356
- if (!req.session || !req.session.userid || !req.body.apassword0 || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.userid.split('/')[1] != domain.id)) {
2357
- parent.debug('web', 'handlePasswordChangeRequest: failed checks (2).');
2358
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2359
- return;
2360
- }
2361
-
2362
- // Get the current user
2363
- var user = obj.users[req.session.userid];
2364
- if (!user) {
2365
- parent.debug('web', 'handlePasswordChangeRequest: user not found.');
2366
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2367
- return;
2368
- }
2369
-
2370
- // Check account settings locked
2371
- if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) {
2372
- parent.debug('web', 'handlePasswordChangeRequest: account settings locked.');
2373
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2374
- return;
2375
- }
2376
-
2377
- // Check old password
2378
- obj.checkUserPassword(domain, user, req.body.apassword1, function (result) {
2379
- if (result == true) {
2380
- // Check if the new password is allowed, only do this if this feature is enabled.
2381
- parent.checkOldUserPasswords(domain, user, command.newpass, function (result) {
2382
- if (result == 1) {
2383
- parent.debug('web', 'handlePasswordChangeRequest: old password reuse attempt.');
2384
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2385
- } else if (result == 2) {
2386
- parent.debug('web', 'handlePasswordChangeRequest: commonly used password use attempt.');
2387
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2388
- } else {
2389
- // Update the password
2390
- require('./pass').hash(req.body.apassword1, function (err, salt, hash, tag) {
2391
- const nowSeconds = Math.floor(Date.now() / 1000);
2392
- if (err) { parent.debug('web', 'handlePasswordChangeRequest: hash error.'); throw err; }
2393
- if (domain.passwordrequirements != null) {
2394
- // Save password hint if this feature is enabled
2395
- if ((domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) hint = hint.substring(0, 250); user.passhint = hint; } else { delete user.passhint; }
2396
-
2397
- // Save previous password if this feature is enabled
2398
- if ((typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
2399
- if (user.oldpasswords == null) { user.oldpasswords = []; }
2400
- user.oldpasswords.push({ salt: user.salt, hash: user.hash, start: user.passchange, end: nowSeconds });
2401
- const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
2402
- if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
2403
- }
2404
- }
2405
- user.salt = salt;
2406
- user.hash = hash;
2407
- user.passchange = user.access = nowSeconds;
2408
- delete user.passtype;
2409
-
2410
- obj.db.SetUser(user);
2411
- req.session.viewmode = 2;
2412
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2413
- obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'passchange', msg: 'Account password changed: ' + user.name, domain: domain.id });
2414
- }, 0);
2415
- }
2416
- });
2417
- }
2418
- });
2419
- }
2420
-
2421
- // Called when a strategy login occured
2422
- // This is called after a succesful Oauth to Twitter, Google, GitHub...
2423
- function handleStrategyLogin(req, res) {
2424
- const domain = checkUserIpAddress(req, res);
2425
- if (domain == null) { return; }
2426
- parent.debug('web', 'handleStrategyLogin: ' + JSON.stringify(req.user));
2427
- if ((req.user != null) && (req.user.sid != null)) {
2428
- const userid = 'user/' + domain.id + '/' + req.user.sid;
2429
- var user = obj.users[userid];
2430
- if (user == null) {
2431
- var newAccountAllowed = false;
2432
- var newAccountRealms = null;
2433
-
2434
- if (domain.newaccounts === true) { newAccountAllowed = true; }
2435
- if (obj.common.validateStrArray(domain.newaccountrealms)) { newAccountRealms = domain.newaccountrealms; }
2436
-
2437
- if ((domain.authstrategies != null) && (domain.authstrategies[req.user.strategy] != null)) {
2438
- if (domain.authstrategies[req.user.strategy].newaccounts === true) { newAccountAllowed = true; }
2439
- if (obj.common.validateStrArray(domain.authstrategies[req.user.strategy].newaccountrealms)) { newAccountRealms = domain.authstrategies[req.user.strategy].newaccountrealms; }
2440
- }
2441
-
2442
- if (newAccountAllowed === true) {
2443
- // Create the user
2444
- parent.debug('web', 'handleStrategyLogin: creating new user: ' + userid);
2445
- user = { type: 'user', _id: userid, name: req.user.name, email: req.user.email, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
2446
- if (req.user.email != null) { user.email = req.user.email; user.emailVerified = true; }
2447
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; } // New accounts automatically assigned server rights.
2448
- if (domain.authstrategies[req.user.strategy].newaccountsrights) { user.siteadmin = obj.common.meshServerRightsArrayToNumber(domain.authstrategies[req.user.strategy].newaccountsrights); } // If there are specific SSO server rights, use these instead.
2449
- if (newAccountRealms) { user.groups = newAccountRealms; } // New accounts automatically part of some groups (Realms).
2450
- obj.users[userid] = user;
2451
-
2452
- // Auto-join any user groups
2453
- var newaccountsusergroups = null;
2454
- if (typeof domain.newaccountsusergroups == 'object') { newaccountsusergroups = domain.newaccountsusergroups; }
2455
- if (typeof domain.authstrategies[req.user.strategy].newaccountsusergroups == 'object') { newaccountsusergroups = domain.authstrategies[req.user.strategy].newaccountsusergroups; }
2456
- if (newaccountsusergroups) {
2457
- for (var i in newaccountsusergroups) {
2458
- var ugrpid = newaccountsusergroups[i];
2459
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
2460
- var ugroup = obj.userGroups[ugrpid];
2461
- if (ugroup != null) {
2462
- // Add group to the user
2463
- if (user.links == null) { user.links = {}; }
2464
- user.links[ugroup._id] = { rights: 1 };
2465
-
2466
- // Add user to the group
2467
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
2468
- db.Set(ugroup);
2469
-
2470
- // Notify user group change
2471
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
2472
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
2473
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
2474
- }
2475
- }
2476
- }
2477
-
2478
- // Save the user
2479
- obj.db.SetUser(user);
2480
-
2481
- // Event user creation
2482
- var targets = ['*', 'server-users'];
2483
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, username is ' + user.name, domain: domain.id };
2484
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
2485
- parent.DispatchEvent(targets, obj, event);
2486
-
2487
- req.session.userid = userid;
2488
- setSessionRandom(req);
2489
-
2490
- // Notify account login using SSO
2491
- var targets = ['*', 'server-users', user._id];
2492
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
2493
- const ua = getUserAgentInfo(req);
2494
- const loginEvent = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 107, msgArgs: [req.clientIp, ua.browserStr, ua.osStr], msg: 'Account login', domain: domain.id, ip: req.clientIp, userAgent: req.headers['user-agent'], twoFactorType: 'sso' };
2495
- obj.parent.DispatchEvent(targets, obj, loginEvent);
2496
- } else {
2497
- // New users not allowed
2498
- parent.debug('web', 'handleStrategyLogin: Can\'t create new accounts');
2499
- req.session.loginmode = 1;
2500
- req.session.messageid = 100; // Unable to create account.
2501
- res.redirect(domain.url + getQueryPortion(req));
2502
- return;
2503
- }
2504
- } else {
2505
- // Login success
2506
- var userChange = false;
2507
- if ((req.user.name != null) && (req.user.name != user.name)) { user.name = req.user.name; userChange = true; }
2508
- if ((req.user.email != null) && (req.user.email != user.email)) { user.email = req.user.email; user.emailVerified = true; userChange = true; }
2509
- if (userChange) {
2510
- obj.db.SetUser(user);
2511
-
2512
- // Event user change
2513
- var targets = ['*', 'server-users'];
2514
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Account changed', domain: domain.id };
2515
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
2516
- parent.DispatchEvent(targets, obj, event);
2517
- }
2518
- parent.debug('web', 'handleStrategyLogin: succesful login: ' + userid);
2519
- req.session.userid = userid;
2520
- setSessionRandom(req);
2521
-
2522
- // Notify account login using SSO
2523
- var targets = ['*', 'server-users', user._id];
2524
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
2525
- const ua = getUserAgentInfo(req);
2526
- const loginEvent = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 107, msgArgs: [req.clientIp, ua.browserStr, ua.osStr], msg: 'Account login', domain: domain.id, ip: req.clientIp, userAgent: req.headers['user-agent'], twoFactorType: 'sso' };
2527
- obj.parent.DispatchEvent(targets, obj, loginEvent);
2528
- }
2529
- }
2530
- //res.redirect(domain.url); // This does not handle cookie correctly.
2531
- res.set('Content-Type', 'text/html');
2532
- res.end('<html><head><meta http-equiv="refresh" content=0;url="' + domain.url + '"></head><body></body></html>');
2533
- }
2534
-
2535
- // Indicates that any request to "/" should render "default" or "login" depending on login state
2536
- function handleRootRequest(req, res, direct) {
2537
- const domain = checkUserIpAddress(req, res);
2538
- if (domain == null) { return; }
2539
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2540
- if (!obj.args) { parent.debug('web', 'handleRootRequest: no obj.args.'); res.sendStatus(500); return; }
2541
-
2542
- // If the session is expired, clear it.
2543
- if ((req.session != null) && (typeof req.session.expire == 'number') && ((req.session.expire - Date.now()) <= 0)) { for (var i in req.session) { delete req.session[i]; } }
2544
-
2545
- // Check if we are in maintenance mode
2546
- if ((parent.config.settings.maintenancemode != null) && (req.query.loginscreen !== '1')) {
2547
- parent.debug('web', 'handleLoginRequest: Server under maintenance.');
2548
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 3, msgid: 13, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
2549
- return;
2550
- }
2551
-
2552
- // If set and there is no user logged in, redirect the root page. Make sure not to redirect if /login is used
2553
- if ((typeof domain.unknownuserrootredirect == 'string') && ((req.session == null) || (req.session.userid == null))) {
2554
- var q = require('url').parse(req.url, true);
2555
- if (!q.pathname.endsWith('/login')) { res.redirect(domain.unknownuserrootredirect + getQueryPortion(req)); return; }
2556
- }
2557
-
2558
- if ((domain.sspi != null) && ((req.query.login == null) || (obj.parent.loginCookieEncryptionKey == null))) {
2559
- // Login using SSPI
2560
- domain.sspi.authenticate(req, res, function (err) {
2561
- if ((err != null) || (req.connection.user == null)) {
2562
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2563
- parent.debug('web', 'handleRootRequest: SSPI auth required.');
2564
- res.end('Authentication Required...');
2565
- } else {
2566
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2567
- parent.debug('web', 'handleRootRequest: SSPI auth ok.');
2568
- handleRootRequestEx(req, res, domain, direct);
2569
- }
2570
- });
2571
- } else if (req.query.user && req.query.pass) {
2572
- // User credentials are being passed in the URL. WARNING: Putting credentials in a URL is bad security... but people are requesting this option.
2573
- obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid, passhint, loginOptions) {
2574
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + userid + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2575
- parent.debug('web', 'handleRootRequest: user/pass in URL auth ok.');
2576
- req.session.userid = userid;
2577
- delete req.session.currentNode;
2578
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2579
- setSessionRandom(req);
2580
- handleRootRequestEx(req, res, domain, direct);
2581
- });
2582
- } else if ((req.session != null) && (typeof req.session.loginToken == 'string')) {
2583
- // Check if the loginToken is still valid
2584
- obj.db.Get('logintoken-' + req.session.loginToken, function (err, docs) {
2585
- if ((err != null) || (docs == null) || (docs.length != 1) || (docs[0].tokenUser != req.session.loginToken)) { for (var i in req.session) { delete req.session[i]; } }
2586
- handleRootRequestEx(req, res, domain, direct); // Login using a different system
2587
- });
2588
- } else {
2589
- // Login using a different system
2590
- handleRootRequestEx(req, res, domain, direct);
2591
- }
2592
- }
2593
-
2594
- function handleRootRequestEx(req, res, domain, direct) {
2595
- var nologout = false, user = null;
2596
- res.set({ 'Cache-Control': 'no-store' });
2597
-
2598
- // Check if we have an incomplete domain name in the path
2599
- if ((domain.id != '') && (domain.dns == null) && (req.url.split('/').length == 2)) {
2600
- parent.debug('web', 'handleRootRequestEx: incomplete domain name in the path.');
2601
- res.redirect(domain.url + getQueryPortion(req)); // BAD***
2602
- return;
2603
- }
2604
-
2605
- if (obj.args.nousers == true) {
2606
- // If in single user mode, setup things here.
2607
- delete req.session.loginmode;
2608
- req.session.userid = 'user/' + domain.id + '/~';
2609
- delete req.session.currentNode;
2610
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2611
- setSessionRandom(req);
2612
- if (obj.users[req.session.userid] == null) {
2613
- // Create the dummy user ~ with impossible password
2614
- parent.debug('web', 'handleRootRequestEx: created dummy user in nouser mode.');
2615
- obj.users[req.session.userid] = { type: 'user', _id: req.session.userid, name: '~', email: '~', domain: domain.id, siteadmin: 4294967295 };
2616
- obj.db.SetUser(obj.users[req.session.userid]);
2617
- }
2618
- } else if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
2619
- // If a default user is active, setup the session here.
2620
- parent.debug('web', 'handleRootRequestEx: auth using default user.');
2621
- delete req.session.loginmode;
2622
- req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
2623
- delete req.session.currentNode;
2624
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2625
- setSessionRandom(req);
2626
- } else if (req.query.login && (obj.parent.loginCookieEncryptionKey != null)) {
2627
- var loginCookie = obj.parent.decodeCookie(req.query.login, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
2628
- //if ((loginCookie != null) && (loginCookie.ip != null) && checkCookieIp(loginCookie.ip, req.clientIp)) { loginCookie = null; } // If the cookie if binded to an IP address, check here.
2629
- if ((loginCookie != null) && (loginCookie.a == 3) && (loginCookie.u != null) && (loginCookie.u.split('/')[1] == domain.id)) {
2630
- // If a login cookie was provided, setup the session here.
2631
- parent.debug('web', 'handleRootRequestEx: cookie auth ok.');
2632
- delete req.session.loginmode;
2633
- req.session.userid = loginCookie.u;
2634
- delete req.session.currentNode;
2635
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2636
- setSessionRandom(req);
2637
- } else {
2638
- parent.debug('web', 'handleRootRequestEx: cookie auth failed.');
2639
- }
2640
- } else if (domain.sspi != null) {
2641
- // SSPI login (Windows only)
2642
- //console.log(req.connection.user, req.connection.userSid);
2643
- if ((req.connection.user == null) || (req.connection.userSid == null)) {
2644
- parent.debug('web', 'handleRootRequestEx: SSPI no user auth.');
2645
- res.sendStatus(404); return;
2646
- } else {
2647
- nologout = true;
2648
- req.session.userid = 'user/' + domain.id + '/' + req.connection.user.toLowerCase();
2649
- req.session.usersid = req.connection.userSid;
2650
- req.session.usersGroups = req.connection.userGroups;
2651
- delete req.session.currentNode;
2652
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2653
- setSessionRandom(req);
2654
-
2655
- // Check if this user exists, create it if not.
2656
- user = obj.users[req.session.userid];
2657
- if ((user == null) || (user.sid != req.session.usersid)) {
2658
- // Create the domain user
2659
- var usercount = 0, user2 = { type: 'user', _id: req.session.userid, name: req.connection.user, domain: domain.id, sid: req.session.usersid, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000) };
2660
- if (domain.newaccountsrights) { user2.siteadmin = domain.newaccountsrights; }
2661
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user2.groups = domain.newaccountrealms; }
2662
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
2663
- if (usercount == 0) { user2.siteadmin = 4294967295; } // If this is the first user, give the account site admin.
2664
-
2665
- // Auto-join any user groups
2666
- if (typeof domain.newaccountsusergroups == 'object') {
2667
- for (var i in domain.newaccountsusergroups) {
2668
- var ugrpid = domain.newaccountsusergroups[i];
2669
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
2670
- var ugroup = obj.userGroups[ugrpid];
2671
- if (ugroup != null) {
2672
- // Add group to the user
2673
- if (user2.links == null) { user2.links = {}; }
2674
- user2.links[ugroup._id] = { rights: 1 };
2675
-
2676
- // Add user to the group
2677
- ugroup.links[user2._id] = { userid: user2._id, name: user2.name, rights: 1 };
2678
- db.Set(ugroup);
2679
-
2680
- // Notify user group change
2681
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Added user ' + user2.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
2682
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
2683
- parent.DispatchEvent(['*', ugroup._id, user2._id], obj, event);
2684
- }
2685
- }
2686
- }
2687
-
2688
- obj.users[req.session.userid] = user2;
2689
- obj.db.SetUser(user2);
2690
- var event = { etype: 'user', userid: req.session.userid, username: req.connection.user, account: obj.CloneSafeUser(user2), action: 'accountcreate', msg: 'Domain account created, user ' + req.connection.user, domain: domain.id };
2691
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
2692
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
2693
- parent.debug('web', 'handleRootRequestEx: SSPI new domain user.');
2694
- }
2695
- }
2696
- }
2697
-
2698
- // Figure out the minimal password requirement
2699
- var passRequirements = null;
2700
- if (domain.passwordrequirements != null) {
2701
- if (domain.passrequirementstr == null) {
2702
- var passRequirements = {};
2703
- if (typeof domain.passwordrequirements.min == 'number') { passRequirements.min = domain.passwordrequirements.min; }
2704
- if (typeof domain.passwordrequirements.max == 'number') { passRequirements.max = domain.passwordrequirements.max; }
2705
- if (typeof domain.passwordrequirements.upper == 'number') { passRequirements.upper = domain.passwordrequirements.upper; }
2706
- if (typeof domain.passwordrequirements.lower == 'number') { passRequirements.lower = domain.passwordrequirements.lower; }
2707
- if (typeof domain.passwordrequirements.numeric == 'number') { passRequirements.numeric = domain.passwordrequirements.numeric; }
2708
- if (typeof domain.passwordrequirements.nonalpha == 'number') { passRequirements.nonalpha = domain.passwordrequirements.nonalpha; }
2709
- domain.passwordrequirementsstr = encodeURIComponent(JSON.stringify(passRequirements));
2710
- }
2711
- passRequirements = domain.passwordrequirementsstr;
2712
- }
2713
-
2714
- // If a user exists and is logged in, serve the default app, otherwise server the login app.
2715
- if (req.session && req.session.userid && obj.users[req.session.userid]) {
2716
- const user = obj.users[req.session.userid];
2717
-
2718
- // Check if we are in maintenance mode
2719
- if ((parent.config.settings.maintenancemode != null) && (user.siteadmin != 4294967295)) {
2720
- req.session.messageid = 115; // Server under maintenance
2721
- req.session.loginmode = 1;
2722
- res.redirect(domain.url);
2723
- return;
2724
- }
2725
-
2726
- // If the request has a "meshmessengerid", redirect to MeshMessenger
2727
- // This situation happens when you get a push notification for a chat session, but are not logged in.
2728
- if (req.query.meshmessengerid != null) {
2729
- res.redirect(domain.url + 'messenger?id=' + req.query.meshmessengerid + ((req.query.key != null) ? ('&key=' + req.query.key) : ''));
2730
- return;
2731
- }
2732
-
2733
- const xdbGetFunc = function dbGetFunc(err, states) {
2734
- if (dbGetFunc.req.session.userid.split('/')[1] != domain.id) { // Check if the session is for the correct domain
2735
- parent.debug('web', 'handleRootRequestEx: incorrect domain.');
2736
- dbGetFunc.req.session = null;
2737
- dbGetFunc.res.redirect(domain.url + getQueryPortion(dbGetFunc.req)); // BAD***
2738
- return;
2739
- }
2740
-
2741
- // Check if this is a locked account
2742
- if ((dbGetFunc.user.siteadmin != null) && ((dbGetFunc.user.siteadmin & 32) != 0) && (dbGetFunc.user.siteadmin != 0xFFFFFFFF)) {
2743
- // Locked account
2744
- parent.debug('web', 'handleRootRequestEx: locked account.');
2745
- delete dbGetFunc.req.session.userid;
2746
- delete dbGetFunc.req.session.currentNode;
2747
- delete dbGetFunc.req.session.passhint;
2748
- delete dbGetFunc.req.session.cuserid;
2749
- dbGetFunc.req.session.messageid = 110; // Account locked.
2750
- dbGetFunc.res.redirect(domain.url + getQueryPortion(dbGetFunc.req)); // BAD***
2751
- return;
2752
- }
2753
-
2754
- var viewmode = 1;
2755
- if (dbGetFunc.req.session.viewmode) {
2756
- viewmode = dbGetFunc.req.session.viewmode;
2757
- delete dbGetFunc.req.session.viewmode;
2758
- } else if (dbGetFunc.req.query.viewmode) {
2759
- viewmode = dbGetFunc.req.query.viewmode;
2760
- }
2761
- var currentNode = '';
2762
- if (dbGetFunc.req.session.currentNode) {
2763
- currentNode = dbGetFunc.req.session.currentNode;
2764
- delete dbGetFunc.req.session.currentNode;
2765
- } else if (dbGetFunc.req.query.node) {
2766
- currentNode = 'node/' + domain.id + '/' + dbGetFunc.req.query.node;
2767
- }
2768
- var logoutcontrols = {};
2769
- if (obj.args.nousers != true) { logoutcontrols.name = user.name; }
2770
-
2771
- // Give the web page a list of supported server features for this domain and user
2772
- const allFeatures = obj.getDomainUserFeatures(domain, dbGetFunc.user, dbGetFunc.req);
2773
-
2774
- // Create a authentication cookie
2775
- const authCookie = obj.parent.encodeCookie({ userid: dbGetFunc.user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey);
2776
- const authRelayCookie = obj.parent.encodeCookie({ ruserid: dbGetFunc.user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
2777
-
2778
- // Send the main web application
2779
- var extras = (dbGetFunc.req.query.key != null) ? ('&key=' + dbGetFunc.req.query.key) : '';
2780
- if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
2781
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2782
-
2783
- // Clean up the U2F challenge if needed
2784
- if (dbGetFunc.req.session.u2f) { delete dbGetFunc.req.session.u2f; };
2785
-
2786
- // Intel AMT Scanning options
2787
- var amtscanoptions = '';
2788
- if (typeof domain.amtscanoptions == 'string') { amtscanoptions = encodeURIComponent(domain.amtscanoptions); }
2789
- else if (obj.common.validateStrArray(domain.amtscanoptions)) { domain.amtscanoptions = domain.amtscanoptions.join(','); amtscanoptions = encodeURIComponent(domain.amtscanoptions); }
2790
-
2791
- // Fetch the web state
2792
- parent.debug('web', 'handleRootRequestEx: success.');
2793
-
2794
- var webstate = '';
2795
- if ((err == null) && (states != null) && (Array.isArray(states)) && (states.length == 1) && (states[0].state != null)) { webstate = obj.filterUserWebState(states[0].state); }
2796
- if ((webstate == '') && (typeof domain.defaultuserwebstate == 'object')) { webstate = JSON.stringify(domain.defaultuserwebstate); } // User has no web state, use defaults.
2797
- if (typeof domain.forceduserwebstate == 'object') { // Forces initial user web state if present, use it.
2798
- var webstate2 = {};
2799
- try { if (webstate != '') { webstate2 = JSON.parse(webstate); } } catch (ex) { }
2800
- for (var i in domain.forceduserwebstate) { webstate2[i] = domain.forceduserwebstate[i]; }
2801
- webstate = JSON.stringify(webstate2);
2802
- }
2803
-
2804
- // Custom user interface
2805
- var customui = '';
2806
- if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
2807
-
2808
- // Server features
2809
- var serverFeatures = 127;
2810
- if (domain.myserver === false) { serverFeatures = 0; } // 64 = Show "My Server" tab
2811
- else if (typeof domain.myserver == 'object') {
2812
- if (domain.myserver.backup !== true) { serverFeatures -= 1; } // Disallow simple server backups
2813
- if (domain.myserver.restore !== true) { serverFeatures -= 2; } // Disallow simple server restore
2814
- if (domain.myserver.upgrade !== true) { serverFeatures -= 4; } // Disallow server upgrade
2815
- if (domain.myserver.errorlog !== true) { serverFeatures -= 8; } // Disallow show server crash log
2816
- if (domain.myserver.console !== true) { serverFeatures -= 16; } // Disallow server console
2817
- if (domain.myserver.trace !== true) { serverFeatures -= 32; } // Disallow server tracing
2818
- }
2819
- if (obj.db.databaseType != 1) { // If not using NeDB, we can't backup using the simple system.
2820
- if ((serverFeatures & 1) != 0) { serverFeatures -= 1; } // Disallow server backups
2821
- if ((serverFeatures & 2) != 0) { serverFeatures -= 2; } // Disallow simple server restore
2822
- }
2823
-
2824
- // Refresh the session
2825
- render(dbGetFunc.req, dbGetFunc.res, getRenderPage('default', dbGetFunc.req, domain), getRenderArgs({
2826
- authCookie: authCookie,
2827
- authRelayCookie: authRelayCookie,
2828
- viewmode: viewmode,
2829
- currentNode: currentNode,
2830
- logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'),
2831
- domain: domain.id,
2832
- debuglevel: parent.debugLevel,
2833
- serverDnsName: obj.getWebServerName(domain),
2834
- serverRedirPort: args.redirport,
2835
- serverPublicPort: httpsPort,
2836
- serverfeatures: serverFeatures,
2837
- features: allFeatures.features,
2838
- features2: allFeatures.features2,
2839
- sessiontime: (args.sessiontime) ? args.sessiontime : 60,
2840
- mpspass: args.mpspass,
2841
- passRequirements: passRequirements,
2842
- customui: customui,
2843
- webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'),
2844
- footer: (domain.footer == null) ? '' : domain.footer,
2845
- webstate: encodeURIComponent(webstate).replace(/'/g, '%27'),
2846
- amtscanoptions: amtscanoptions,
2847
- pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports()
2848
- }, dbGetFunc.req, domain), user);
2849
- }
2850
- xdbGetFunc.req = req;
2851
- xdbGetFunc.res = res;
2852
- xdbGetFunc.user = user;
2853
- obj.db.Get('ws' + user._id, xdbGetFunc);
2854
- } else {
2855
- // Send back the login application
2856
- // If this is a 2 factor auth request, look for a hardware key challenge.
2857
- // Normal login 2 factor request
2858
- if (req.session && (req.session.loginmode == 4) && (req.session.tuserid)) {
2859
- var user = obj.users[req.session.tuserid];
2860
- if (user != null) {
2861
- parent.debug('web', 'handleRootRequestEx: sending 2FA challenge.');
2862
- getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
2863
- return;
2864
- }
2865
- }
2866
- // Password recovery 2 factor request
2867
- if (req.session && (req.session.loginmode == 5) && (req.session.temail)) {
2868
- obj.db.GetUserWithVerifiedEmail(domain.id, req.session.temail, function (err, docs) {
2869
- if ((err != null) || (docs.length == 0)) {
2870
- parent.debug('web', 'handleRootRequestEx: password recover 2FA fail.');
2871
- req.session = null;
2872
- res.redirect(domain.url + getQueryPortion(req)); // BAD***
2873
- } else {
2874
- var user = obj.users[docs[0]._id];
2875
- if (user != null) {
2876
- parent.debug('web', 'handleRootRequestEx: password recover 2FA challenge.');
2877
- getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
2878
- } else {
2879
- parent.debug('web', 'handleRootRequestEx: password recover 2FA no user.');
2880
- req.session = null;
2881
- res.redirect(domain.url + getQueryPortion(req)); // BAD***
2882
- }
2883
- }
2884
- });
2885
- return;
2886
- }
2887
- handleRootRequestLogin(req, res, domain, '', passRequirements);
2888
- }
2889
- }
2890
-
2891
- // Return a list of server supported features for a given domain and user
2892
- obj.getDomainUserFeatures = function(domain, user, req) {
2893
- var features = 0;
2894
- var features2 = 0;
2895
- if (obj.args.wanonly == true) { features += 0x00000001; } // WAN-only mode
2896
- if (obj.args.lanonly == true) { features += 0x00000002; } // LAN-only mode
2897
- if (obj.args.nousers == true) { features += 0x00000004; } // Single user mode
2898
- if (domain.userQuota == -1) { features += 0x00000008; } // No server files mode
2899
- if (obj.args.mpstlsoffload) { features += 0x00000010; } // No mutual-auth CIRA
2900
- if ((parent.config.settings.allowframing != null) || (domain.allowframing != null)) { features += 0x00000020; } // Allow site within iframe
2901
- if ((domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true)) { features += 0x00000040; } // Email invites
2902
- if (obj.args.webrtc == true) { features += 0x00000080; } // Enable WebRTC (Default false for now)
2903
- // 0x00000100 --> This feature flag is free for future use.
2904
- if (obj.args.allowhighqualitydesktop !== false) { features += 0x00000200; } // Enable AllowHighQualityDesktop (Default true)
2905
- if ((obj.args.lanonly == true) || (obj.args.mpsport == 0)) { features += 0x00000400; } // No CIRA
2906
- if ((obj.parent.serverSelfWriteAllowed == true) && (user != null) && ((user.siteadmin & 0x00000010) != 0)) { features += 0x00000800; } // Server can self-write (Allows self-update)
2907
- if ((parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (user._id.split('/')[2][0] != '~')) { features += 0x00001000; } // 2FA login supported
2908
- if (domain.agentnoproxy === true) { features += 0x00002000; } // Indicates that agents should be installed without using a HTTP proxy
2909
- if ((parent.config.settings.no2factorauth !== true) && domain.yubikey && domain.yubikey.id && domain.yubikey.secret && (user._id.split('/')[2][0] != '~')) { features += 0x00004000; } // Indicates Yubikey support
2910
- if (domain.geolocation == true) { features += 0x00008000; } // Enable geo-location features
2911
- if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { features += 0x00010000; } // Enable password hints
2912
- if (parent.config.settings.no2factorauth !== true) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support
2913
- if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true) && (user._id.split('/')[2][0] != '~')) {
2914
- // Check if we can skip 2nd factor auth because of the source IP address
2915
- var skip2factor = false;
2916
- if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
2917
- for (var i in domain.passwordrequirements.skip2factor) {
2918
- if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) { skip2factor = true; }
2919
- }
2920
- }
2921
- if (skip2factor == false) { features += 0x00040000; } // Force 2-factor auth
2922
- }
2923
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { features += 0x00080000; } // LDAP or SSPI in use, warn that users must login first before adding a user to a group.
2924
- if (domain.amtacmactivation) { features += 0x00100000; } // Intel AMT ACM activation/upgrade is possible
2925
- if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
2926
- if (parent.mqttbroker != null) { features += 0x00400000; } // This server supports MQTT channels
2927
- if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null)) { features += 0x00800000; } // using email for 2FA is allowed
2928
- if (domain.agentinvitecodes == true) { features += 0x01000000; } // Support for agent invite codes
2929
- if (parent.smsserver != null) { features += 0x02000000; } // SMS messaging is supported
2930
- if ((parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false))) { features += 0x04000000; } // SMS 2FA is allowed
2931
- if (domain.sessionrecording != null) { features += 0x08000000; } // Server recordings enabled
2932
- if (domain.urlswitching === false) { features += 0x10000000; } // Disables the URL switching feature
2933
- if (domain.novnc === false) { features += 0x20000000; } // Disables noVNC
2934
- if (domain.mstsc !== true) { features += 0x40000000; } // Disables MSTSC.js
2935
- if (obj.isTrustedCert(domain) == false) { features += 0x80000000; } // Indicate we are not using a trusted certificate
2936
- if (obj.parent.amtManager != null) { features2 += 0x00000001; } // Indicates that the Intel AMT manager is active
2937
- if (obj.parent.firebase != null) { features2 += 0x00000002; } // Indicates the server supports Firebase push messaging
2938
- if ((obj.parent.firebase != null) && (obj.parent.firebase.pushOnly != true)) { features2 += 0x00000004; } // Indicates the server supports Firebase two-way push messaging
2939
- if (obj.parent.webpush != null) { features2 += 0x00000008; } // Indicates web push is enabled
2940
- if (((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true))) { features2 += 0x00000010; } // No agent update
2941
- if (parent.amtProvisioningServer != null) { features2 += 0x00000020; } // Intel AMT LAN provisioning server
2942
- if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.push2factor != false)) && (obj.parent.firebase != null)) { features2 += 0x00000040; } // Indicates device push notification 2FA is enabled
2943
- if ((typeof domain.passwordrequirements != 'object') || ((domain.passwordrequirements.logintokens !== false) && ((Array.isArray(domain.passwordrequirements.logintokens) == false) || (domain.passwordrequirements.logintokens.indexOf(user._id) >= 0)))) { features2 += 0x00000080; } // Indicates login tokens are allowed
2944
- if (req.session.loginToken != null) { features2 += 0x00000100; } // LoginToken mode, no account changes.
2945
- if (domain.ssh == true) { features2 += 0x00000200; } // SSH is enabled
2946
- if (domain.localsessionrecording === false) { features2 += 0x00000400; } // Disable local recording feature
2947
- if (domain.clipboardget == false) { features2 += 0x00000800; } // Disable clipboard get
2948
- if (domain.clipboardset == false) { features2 += 0x00001000; } // Disable clipboard set
2949
- if ((typeof domain.desktop == 'object') && (domain.desktop.viewonly == true)) { features2 += 0x00002000; } // Indicates remote desktop is viewonly
2950
- if (domain.mailserver != null) { features2 += 0x00004000; } // Indicates email server is active
2951
- if (domain.devicesearchbarserverandclientname) { features2 += 0x00008000; } // Search bar will find both server name and client name
2952
- if (domain.ipkvm) { features2 += 0x00010000; } // Indicates support for IP KVM device groups
2953
- if ((domain.passwordrequirements) && (domain.passwordrequirements.otp2factor == false)) { features2 += 0x00020000; } // Indicates support for OTP 2FA is disabled
2954
- if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.backupcode2factor === false)) { features2 += 0x00040000; } // Indicates 2FA backup codes are disabled
2955
- if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.single2factorwarning === false)) { features2 += 0x00080000; } // Indicates no warning if a single 2FA is in use
2956
- if (domain.nightmode === 1) { features2 += 0x00100000; } // Always night mode
2957
- if (domain.nightmode === 2) { features2 += 0x00200000; } // Always day mode
2958
- if (domain.allowsavingdevicecredentials == false) { features2 += 0x00400000; } // Do not allow device credentials to be saved on the server
2959
- return { features: features, features2: features2 };
2960
- }
2961
-
2962
- function handleRootRequestLogin(req, res, domain, hardwareKeyChallenge, passRequirements) {
2963
- parent.debug('web', 'handleRootRequestLogin()');
2964
- var features = 0;
2965
- if ((parent.config != null) && (parent.config.settings != null) && ((parent.config.settings.allowframing == true) || (typeof parent.config.settings.allowframing == 'string'))) { features += 32; } // Allow site within iframe
2966
- if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
2967
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2968
- var loginmode = 0;
2969
- if (req.session) { loginmode = req.session.loginmode; delete req.session.loginmode; } // Clear this state, if the user hits refresh, we want to go back to the login page.
2970
-
2971
- // Format an error message if needed
2972
- var passhint = null, msgid = 0;
2973
- if (req.session != null) {
2974
- msgid = req.session.messageid;
2975
- if ((msgid == 5) || (loginmode == 7) || ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true))) { passhint = EscapeHtml(req.session.passhint); }
2976
- delete req.session.messageid;
2977
- delete req.session.passhint;
2978
- }
2979
- const allowAccountReset = ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.allowaccountreset !== false));
2980
- const emailcheck = (allowAccountReset && (domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
2981
-
2982
- // Check if we are allowed to create new users using the login screen
2983
- var newAccountsAllowed = true;
2984
- if ((domain.newaccounts !== 1) && (domain.newaccounts !== true)) { for (var i in obj.users) { if (obj.users[i].domain == domain.id) { newAccountsAllowed = false; break; } } }
2985
- if (parent.config.settings.maintenancemode != null) { newAccountsAllowed = false; }
2986
-
2987
- // Encrypt the hardware key challenge state if needed
2988
- var hwstate = null;
2989
- if (hardwareKeyChallenge) { hwstate = obj.parent.encodeCookie({ u: req.session.tuser, p: req.session.tpass, c: req.session.u2f }, obj.parent.loginCookieEncryptionKey) }
2990
-
2991
- // Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5).
2992
- var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string'));
2993
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
2994
- var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1);
2995
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
2996
- var otppush = (parent.firebase != null) && (req.session != null) && (req.session.tpush === 1);
2997
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { otppush = false; }
2998
- const autofido = ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.autofido2fa == true)); // See if FIDO should be automatically prompted if user account has it.
2999
-
3000
- // See if we support two-factor trusted cookies
3001
- var twoFactorCookieDays = 30;
3002
- if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; }
3003
-
3004
- // See what authentication strategies we have
3005
- var authStrategies = [];
3006
- if (typeof domain.authstrategies == 'object') {
3007
- if (typeof domain.authstrategies.twitter == 'object') { authStrategies.push('twitter'); }
3008
- if (typeof domain.authstrategies.google == 'object') { authStrategies.push('google'); }
3009
- if (typeof domain.authstrategies.github == 'object') { authStrategies.push('github'); }
3010
- if (typeof domain.authstrategies.reddit == 'object') { authStrategies.push('reddit'); }
3011
- if (typeof domain.authstrategies.azure == 'object') { authStrategies.push('azure'); }
3012
- if (typeof domain.authstrategies.oidc == 'object') { authStrategies.push('oidc'); }
3013
- if (typeof domain.authstrategies.intel == 'object') { authStrategies.push('intel'); }
3014
- if (typeof domain.authstrategies.jumpcloud == 'object') { authStrategies.push('jumpcloud'); }
3015
- if (typeof domain.authstrategies.saml == 'object') { authStrategies.push('saml'); }
3016
- }
3017
-
3018
- // Custom user interface
3019
- var customui = '';
3020
- if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
3021
-
3022
- // Get two-factor screen timeout
3023
- var twoFactorTimeout = 300000; // Default is 5 minutes, 0 for no timeout.
3024
- if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.twofactortimeout == 'number')) {
3025
- twoFactorTimeout = domain.passwordrequirements.twofactortimeout * 1000;
3026
- }
3027
-
3028
- // Render the login page
3029
- render(req, res,
3030
- getRenderPage((domain.sitestyle == 2) ? 'login2' : 'login', req, domain),
3031
- getRenderArgs({
3032
- loginmode: loginmode,
3033
- rootCertLink: getRootCertLink(domain),
3034
- newAccount: newAccountsAllowed,
3035
- newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1),
3036
- serverDnsName: obj.getWebServerName(domain),
3037
- serverPublicPort: httpsPort,
3038
- passlogin: (typeof domain.showpasswordlogin == 'boolean') ? domain.showpasswordlogin : true,
3039
- emailcheck: emailcheck,
3040
- features: features,
3041
- sessiontime: (args.sessiontime) ? args.sessiontime : 60,
3042
- passRequirements: passRequirements,
3043
- customui: customui,
3044
- footer: (domain.loginfooter == null) ? '' : domain.loginfooter,
3045
- hkey: encodeURIComponent(hardwareKeyChallenge).replace(/'/g, '%27'),
3046
- messageid: msgid,
3047
- passhint: passhint,
3048
- welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null,
3049
- welcomePictureFullScreen: ((typeof domain.welcomepicturefullscreen == 'boolean') ? domain.welcomepicturefullscreen : false),
3050
- hwstate: hwstate,
3051
- otpemail: otpemail,
3052
- otpsms: otpsms,
3053
- otppush: otppush,
3054
- autofido: autofido,
3055
- twoFactorCookieDays: twoFactorCookieDays,
3056
- authStrategies: authStrategies.join(','),
3057
- loginpicture: (typeof domain.loginpicture == 'string'),
3058
- tokenTimeout: twoFactorTimeout // Two-factor authentication screen timeout in milliseconds
3059
- }, req, domain, (domain.sitestyle == 2) ? 'login2' : 'login'));
3060
- }
3061
-
3062
- // Handle a post request on the root
3063
- function handleRootPostRequest(req, res) {
3064
- const domain = checkUserIpAddress(req, res);
3065
- if (domain == null) { return; }
3066
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.end("Not Found"); return; } // Check 3FA URL key
3067
- parent.debug('web', 'handleRootPostRequest, action: ' + req.body.action);
3068
-
3069
- switch (req.body.action) {
3070
- case 'login': { handleLoginRequest(req, res, true); break; }
3071
- case 'tokenlogin': {
3072
- if (req.body.hwstate) {
3073
- var cookie = obj.parent.decodeCookie(req.body.hwstate, obj.parent.loginCookieEncryptionKey, 10);
3074
- if (cookie != null) { req.session.tuser = cookie.u; req.session.tpass = cookie.p; req.session.u2f = cookie.c; }
3075
- }
3076
- handleLoginRequest(req, res, true); break;
3077
- }
3078
- case 'pushlogin': {
3079
- if (req.body.hwstate) {
3080
- var cookie = obj.parent.decodeCookie(req.body.hwstate, obj.parent.loginCookieEncryptionKey, 1);
3081
- if ((cookie != null) && (typeof cookie.u == 'string') && (cookie.d == domain.id) && (cookie.a == 'pushAuth')) {
3082
- // Push authentication is a success, login the user
3083
- req.session = { userid: cookie.u };
3084
-
3085
- // Check if we need to remember this device
3086
- if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
3087
- var maxCookieAge = domain.twofactorcookiedurationdays;
3088
- if (typeof maxCookieAge != 'number') { maxCookieAge = 30; }
3089
- const twoFactorCookie = obj.parent.encodeCookie({ userid: cookie.u, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, obj.parent.loginCookieEncryptionKey);
3090
- res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: parent.config.settings.cookiesamesite, secure: true });
3091
- }
3092
-
3093
- handleRootRequestEx(req, res, domain);
3094
- return;
3095
- }
3096
- }
3097
- handleLoginRequest(req, res, true); break;
3098
- }
3099
- case 'changepassword': { handlePasswordChangeRequest(req, res, true); break; }
3100
- case 'deleteaccount': { handleDeleteAccountRequest(req, res, true); break; }
3101
- case 'createaccount': { handleCreateAccountRequest(req, res, true); break; }
3102
- case 'resetpassword': { handleResetPasswordRequest(req, res, true); break; }
3103
- case 'resetaccount': { handleResetAccountRequest(req, res, true); break; }
3104
- case 'checkemail': { handleCheckAccountEmailRequest(req, res, true); break; }
3105
- default: { handleLoginRequest(req, res, true); break; }
3106
- }
3107
- }
3108
-
3109
- // Return true if it looks like we are using a real TLS certificate.
3110
- obj.isTrustedCert = function (domain) {
3111
- if ((domain != null) && (typeof domain.trustedcert == 'boolean')) return domain.trustedcert; // If the status of the cert specified, use that.
3112
- if (typeof obj.args.trustedcert == 'boolean') return obj.args.trustedcert; // If the status of the cert specified, use that.
3113
- if (obj.args.tlsoffload != null) return true; // We are using TLS offload, a real cert is likely used.
3114
- if (obj.parent.config.letsencrypt != null) return (obj.parent.config.letsencrypt.production === true); // We are using Let's Encrypt, real cert in use if production is set to true.
3115
- if (obj.certificates.WebIssuer.indexOf('MeshCentralRoot-') == 0) return false; // Our cert is issued by self-signed cert.
3116
- if (obj.certificates.CommonName.indexOf('.') == -1) return false; // Our cert is named with a fake name
3117
- return true; // This is a guess
3118
- }
3119
-
3120
- // Get the link to the root certificate if needed
3121
- function getRootCertLink(domain) {
3122
- // Check if the HTTPS certificate is issued from MeshCentralRoot, if so, add download link to root certificate.
3123
- if (obj.isTrustedCert(domain) == false) {
3124
- // Get the domain suffix
3125
- var xdomain = (domain.dns == null) ? domain.id : '';
3126
- if (xdomain != '') xdomain += '/';
3127
- return '<a href=/' + xdomain + 'MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>';
3128
- }
3129
- return '';
3130
- }
3131
-
3132
- // Serve the xterm page
3133
- function handleXTermRequest(req, res) {
3134
- const domain = checkUserIpAddress(req, res);
3135
- if (domain == null) { return; }
3136
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3137
-
3138
- parent.debug('web', 'handleXTermRequest: sending xterm');
3139
- res.set({ 'Cache-Control': 'no-store' });
3140
- if (req.session && req.session.userid) {
3141
- if (req.session.userid.split('/')[1] != domain.id) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3142
- var user = obj.users[req.session.userid];
3143
- if ((user == null) || (req.query.nodeid == null)) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the user exists
3144
-
3145
- // Check permissions
3146
- obj.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
3147
- if ((node == null) || ((rights & 8) == 0) || ((rights != 0xFFFFFFFF) && ((rights & 512) != 0))) { res.redirect(domain.url + getQueryPortion(req)); return; }
3148
-
3149
- var logoutcontrols = { name: user.name };
3150
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3151
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3152
-
3153
- // Create a authentication cookie
3154
- const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey);
3155
- const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
3156
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3157
- render(req, res, getRenderPage('xterm', req, domain), getRenderArgs({ serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, authCookie: authCookie, authRelayCookie: authRelayCookie, logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'), name: EscapeHtml(node.name) }, req, domain));
3158
- });
3159
- } else {
3160
- res.redirect(domain.url + getQueryPortion(req));
3161
- return;
3162
- }
3163
- }
3164
-
3165
- // Render the terms of service.
3166
- function handleTermsRequest(req, res) {
3167
- const domain = checkUserIpAddress(req, res);
3168
- if (domain == null) { return; }
3169
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3170
-
3171
- // See if term.txt was loaded from the database
3172
- if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) {
3173
- // Send the terms from the database
3174
- res.set({ 'Cache-Control': 'no-store' });
3175
- if (req.session && req.session.userid) {
3176
- if (req.session.userid.split('/')[1] != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3177
- var user = obj.users[req.session.userid];
3178
- var logoutcontrols = { name: user.name };
3179
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3180
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3181
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
3182
- } else {
3183
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: encodeURIComponent('{}') }, req, domain));
3184
- }
3185
- } else {
3186
- // See if there is a terms.txt file in meshcentral-data
3187
- var p = obj.path.join(obj.parent.datapath, 'terms.txt');
3188
- if (obj.fs.existsSync(p)) {
3189
- obj.fs.readFile(p, 'utf8', function (err, data) {
3190
- if (err != null) { parent.debug('web', 'handleTermsRequest: no terms.txt'); res.sendStatus(404); return; }
3191
-
3192
- // Send the terms from terms.txt
3193
- res.set({ 'Cache-Control': 'no-store' });
3194
- if (req.session && req.session.userid) {
3195
- if (req.session.userid.split('/')[1] != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3196
- var user = obj.users[req.session.userid];
3197
- var logoutcontrols = { name: user.name };
3198
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3199
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3200
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
3201
- } else {
3202
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: encodeURIComponent('{}') }, req, domain));
3203
- }
3204
- });
3205
- } else {
3206
- // Send the default terms
3207
- parent.debug('web', 'handleTermsRequest: sending default terms');
3208
- res.set({ 'Cache-Control': 'no-store' });
3209
- if (req.session && req.session.userid) {
3210
- if (req.session.userid.split('/')[1] != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3211
- var user = obj.users[req.session.userid];
3212
- var logoutcontrols = { name: user.name };
3213
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3214
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3215
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
3216
- } else {
3217
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: encodeURIComponent('{}') }, req, domain));
3218
- }
3219
- }
3220
- }
3221
- }
3222
-
3223
- // Render the messenger application.
3224
- function handleMessengerRequest(req, res) {
3225
- const domain = getDomain(req);
3226
- if (domain == null) { parent.debug('web', 'handleMessengerRequest: no domain'); res.sendStatus(404); return; }
3227
- parent.debug('web', 'handleMessengerRequest()');
3228
-
3229
- // Check if we are in maintenance mode
3230
- if (parent.config.settings.maintenancemode != null) {
3231
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 3, msgid: 13, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
3232
- return;
3233
- }
3234
-
3235
- // Check if this session is for a user
3236
- if (req.query.id == null) { res.sendStatus(404); return; }
3237
- var idSplit = decodeURIComponent(req.query.id).split('/');
3238
- if ((idSplit.length != 7) || (idSplit[0] != 'meshmessenger')) { res.sendStatus(404); return; }
3239
- if ((idSplit[1] == 'user') && (idSplit[4] == 'user')) {
3240
- // This is a user to user conversation, both users must be logged in.
3241
- var user1 = idSplit[1] + '/' + idSplit[2] + '/' + idSplit[3]
3242
- var user2 = idSplit[4] + '/' + idSplit[5] + '/' + idSplit[6]
3243
- if (!req.session || !req.session.userid) {
3244
- // Redirect to login page
3245
- if (req.query.key != null) { res.redirect(domain.url + '?key=' + req.query.key + '&meshmessengerid=' + req.query.id); } else { res.redirect(domain.url + '?meshmessengerid=' + req.query.id); }
3246
- return;
3247
- }
3248
- if ((req.session.userid != user1) && (req.session.userid != user2)) { res.sendStatus(404); return; }
3249
- }
3250
-
3251
- // Get WebRTC configuration
3252
- var webRtcConfig = null;
3253
- if (obj.parent.config.settings && obj.parent.config.settings.webrtconfig && (typeof obj.parent.config.settings.webrtconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(obj.parent.config.settings.webrtconfig)).replace(/'/g, '%27'); }
3254
- else if (args.webrtconfig && (typeof args.webrtconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtconfig)).replace(/'/g, '%27'); }
3255
-
3256
- // Setup other options
3257
- var options = { webrtconfig: webRtcConfig };
3258
- if (typeof domain.meshmessengertitle == 'string') { options.meshMessengerTitle = domain.meshmessengertitle; } else { options.meshMessengerTitle = '!'; }
3259
-
3260
- // Get the userid and name
3261
- if ((domain.meshmessengertitle != null) && (req.query.id != null) && (req.query.id.startsWith('meshmessenger/node'))) {
3262
- if (idSplit.length == 7) {
3263
- const user = obj.users[idSplit[4] + '/' + idSplit[5] + '/' + idSplit[6]];
3264
- if (user != null) {
3265
- if (domain.meshmessengertitle.indexOf('{0}') >= 0) { options.username = encodeURIComponent(user.realname ? user.realname : user.name).replace(/'/g, '%27'); }
3266
- if (domain.meshmessengertitle.indexOf('{1}') >= 0) { options.userid = encodeURIComponent(user.name).replace(/'/g, '%27'); }
3267
- }
3268
- }
3269
- }
3270
-
3271
- // Render the page
3272
- res.set({ 'Cache-Control': 'no-store' });
3273
- render(req, res, getRenderPage('messenger', req, domain), getRenderArgs(options, req, domain));
3274
- }
3275
-
3276
- // Handle messenger image request
3277
- function handleMessengerImageRequest(req, res) {
3278
- const domain = getDomain(req);
3279
- if (domain == null) { parent.debug('web', 'handleMessengerImageRequest: no domain'); res.sendStatus(404); return; }
3280
- parent.debug('web', 'handleMessengerImageRequest()');
3281
-
3282
- // Check if we are in maintenance mode
3283
- if (parent.config.settings.maintenancemode != null) { res.sendStatus(404); return; }
3284
-
3285
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3286
- if (domain.meshmessengerpicture) {
3287
- // Use the configured messenger logo picture
3288
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.meshmessengerpicture)); return; } catch (ex) { }
3289
- }
3290
-
3291
- var imagefile = 'images/messenger.png';
3292
- if (domain.webpublicpath != null) {
3293
- obj.fs.exists(obj.path.join(domain.webpublicpath, imagefile), function (exists) {
3294
- if (exists) {
3295
- // Use the domain logo picture
3296
- try { res.sendFile(obj.path.join(domain.webpublicpath, imagefile)); } catch (ex) { res.sendStatus(404); }
3297
- } else {
3298
- // Use the default logo picture
3299
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3300
- }
3301
- });
3302
- } else if (parent.webPublicOverridePath) {
3303
- obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, imagefile), function (exists) {
3304
- if (exists) {
3305
- // Use the override logo picture
3306
- try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, imagefile)); } catch (ex) { res.sendStatus(404); }
3307
- } else {
3308
- // Use the default logo picture
3309
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3310
- }
3311
- });
3312
- } else {
3313
- // Use the default logo picture
3314
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3315
- }
3316
- }
3317
-
3318
- // Returns the server root certificate encoded in base64
3319
- function getRootCertBase64() {
3320
- var rootcert = obj.certificates.root.cert;
3321
- var i = rootcert.indexOf('-----BEGIN CERTIFICATE-----\r\n');
3322
- if (i >= 0) { rootcert = rootcert.substring(i + 29); }
3323
- i = rootcert.indexOf('-----END CERTIFICATE-----');
3324
- if (i >= 0) { rootcert = rootcert.substring(i, 0); }
3325
- return Buffer.from(rootcert, 'base64').toString('base64');
3326
- }
3327
-
3328
- // Returns the mesh server root certificate
3329
- function handleRootCertRequest(req, res) {
3330
- const domain = getDomain(req);
3331
- if (domain == null) { parent.debug('web', 'handleRootCertRequest: no domain'); res.sendStatus(404); return; }
3332
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3333
- if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { parent.debug('web', 'handleRootCertRequest: invalid ip'); return; } // Check server-wide IP filter only.
3334
- parent.debug('web', 'handleRootCertRequest()');
3335
- setContentDispositionHeader(res, 'application/octet-stream', certificates.RootName + '.cer', null, 'rootcert.cer');
3336
- res.send(Buffer.from(getRootCertBase64(), 'base64'));
3337
- }
3338
-
3339
- // Handle user public file downloads
3340
- function handleDownloadUserFiles(req, res) {
3341
- const domain = checkUserIpAddress(req, res);
3342
- if (domain == null) { return; }
3343
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3344
-
3345
- if (obj.common.validateString(req.path, 1, 4096) == false) { res.sendStatus(404); return; }
3346
- var domainname = 'domain', spliturl = decodeURIComponent(req.path).split('/'), filename = '';
3347
- if ((spliturl.length < 3) || (obj.common.IsFilenameValid(spliturl[2]) == false) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
3348
- if (domain.id != '') { domainname = 'domain-' + domain.id; }
3349
- var path = obj.path.join(obj.filespath, domainname + '/user-' + spliturl[2] + '/Public');
3350
- 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; } }
3351
-
3352
- var stat = null;
3353
- try { stat = obj.fs.statSync(path); } catch (e) { }
3354
- if ((stat != null) && ((stat.mode & 0x004000) == 0)) {
3355
- if (req.query.download == 1) {
3356
- setContentDispositionHeader(res, 'application/octet-stream', filename, null, 'file.bin');
3357
- try { res.sendFile(obj.path.resolve(__dirname, path)); } catch (e) { res.sendStatus(404); }
3358
- } else {
3359
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'download2' : 'download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(domain), messageid: 1, fileurl: req.path + '?download=1', filename: filename, filesize: stat.size }, req, domain));
3360
- }
3361
- } else {
3362
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'download2' : 'download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(domain), messageid: 2 }, req, domain));
3363
- }
3364
- }
3365
-
3366
- // Handle device file request
3367
- function handleDeviceFile(req, res) {
3368
- const domain = checkUserIpAddress(req, res);
3369
- if (domain == null) { return; }
3370
- if ((req.query.c == null) || (req.query.f == null)) { res.sendStatus(404); return; }
3371
-
3372
- // Check the inbound desktop sharing cookie
3373
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3374
- if ((c == null) || (c.domainid !== domain.id)) { res.sendStatus(404); return; }
3375
-
3376
- // Check userid
3377
- const user = obj.users[c.userid];
3378
- if ((c == user)) { res.sendStatus(404); return; }
3379
-
3380
- // If this cookie has restricted usages, check that it's allowed to perform downloads
3381
- if (Array.isArray(c.usages) && (c.usages.indexOf(10) < 0)) { res.sendStatus(404); return; } // Check protocol #10
3382
-
3383
- if (c.nid != null) { req.query.n = c.nid.split('/')[2]; } // This cookie is restricted to a specific nodeid.
3384
- if (req.query.n == null) { res.sendStatus(404); return; }
3385
-
3386
- // Check if this user has permission to manage this computer
3387
- obj.GetNodeWithRights(domain, user, 'node/' + domain.id + '/' + req.query.n, function (node, rights, visible) {
3388
- if ((node == null) || ((rights & MESHRIGHT_REMOTECONTROL) == 0) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
3389
-
3390
- // All good, start the file transfer
3391
- req.query.id = getRandomLowerCase(12);
3392
- obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, null, res, req, domain, user, node.meshid, node._id);
3393
- });
3394
- }
3395
-
3396
- // Handle download of a server file by an agent
3397
- function handleAgentDownloadFile(req, res) {
3398
- const domain = checkUserIpAddress(req, res);
3399
- if (domain == null) { return; }
3400
- if (req.query.c == null) { res.sendStatus(404); return; }
3401
-
3402
- // Check the inbound desktop sharing cookie
3403
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 5); // 5 minute timeout
3404
- if ((c == null) || (c.a != 'tmpdl') || (c.d != domain.id) || (c.nid == null) || (c.f == null) || (obj.common.IsFilenameValid(c.f) == false)) { res.sendStatus(404); return; }
3405
-
3406
- // Send the file back
3407
- try { res.sendFile(obj.path.join(obj.filespath, 'tmp', c.f)); return; } catch (ex) { res.sendStatus(404); }
3408
- }
3409
-
3410
- // Handle logo request
3411
- function handleLogoRequest(req, res) {
3412
- const domain = checkUserIpAddress(req, res);
3413
- if (domain == null) { return; }
3414
-
3415
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3416
- if (domain.titlepicture) {
3417
- if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.titlepicture] != null)) {
3418
- // Use the logo in the database
3419
- res.set({ 'Content-Type': domain.titlepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
3420
- res.send(parent.configurationFiles[domain.titlepicture]);
3421
- return;
3422
- } else {
3423
- // Use the logo on file
3424
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.titlepicture)); return; } catch (ex) { }
3425
- }
3426
- }
3427
-
3428
- if ((domain.webpublicpath != null) && (obj.fs.existsSync(obj.path.join(domain.webpublicpath, 'images/logoback.png')))) {
3429
- // Use the domain logo picture
3430
- try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
3431
- } else if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png'))) {
3432
- // Use the override logo picture
3433
- try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
3434
- } else {
3435
- // Use the default logo picture
3436
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
3437
- }
3438
- }
3439
-
3440
- // Handle login logo request
3441
- function handleLoginLogoRequest(req, res) {
3442
- const domain = checkUserIpAddress(req, res);
3443
- if (domain == null) { return; }
3444
-
3445
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3446
- if (domain.loginpicture) {
3447
- if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.loginpicture] != null)) {
3448
- // Use the logo in the database
3449
- res.set({ 'Content-Type': domain.loginpicture.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg' });
3450
- res.send(parent.configurationFiles[domain.loginpicture]);
3451
- return;
3452
- } else {
3453
- // Use the logo on file
3454
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.loginpicture)); return; } catch (ex) { res.sendStatus(404); }
3455
- }
3456
- } else {
3457
- res.sendStatus(404);
3458
- }
3459
- }
3460
-
3461
- // Handle translation request
3462
- function handleTranslationsRequest(req, res) {
3463
- const domain = checkUserIpAddress(req, res);
3464
- if (domain == null) { return; }
3465
- //if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3466
- if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { return; } // Check server-wide IP filter only.
3467
-
3468
- var user = null;
3469
- if (obj.args.user != null) {
3470
- // A default user is active
3471
- user = obj.users['user/' + domain.id + '/' + obj.args.user];
3472
- if (!user) { parent.debug('web', 'handleTranslationsRequest: user not found.'); res.sendStatus(401); return; }
3473
- } else {
3474
- // Check if the user is logged and we have all required parameters
3475
- if (!req.session || !req.session.userid) { parent.debug('web', 'handleTranslationsRequest: failed checks (2).'); res.sendStatus(401); return; }
3476
-
3477
- // Get the current user
3478
- user = obj.users[req.session.userid];
3479
- if (!user) { parent.debug('web', 'handleTranslationsRequest: user not found.'); res.sendStatus(401); return; }
3480
- if (user.siteadmin != 0xFFFFFFFF) { parent.debug('web', 'handleTranslationsRequest: user not site administrator.'); res.sendStatus(401); return; }
3481
- }
3482
-
3483
- var data = '';
3484
- req.setEncoding('utf8');
3485
- req.on('data', function (chunk) { data += chunk; });
3486
- req.on('end', function () {
3487
- try { data = JSON.parse(data); } catch (ex) { data = null; }
3488
- if (data == null) { res.sendStatus(404); return; }
3489
- if (data.action == 'getTranslations') {
3490
- if (obj.fs.existsSync(obj.path.join(obj.parent.datapath, 'translate.json'))) {
3491
- // Return the translation file (JSON)
3492
- try { res.sendFile(obj.path.join(obj.parent.datapath, 'translate.json')); } catch (ex) { res.sendStatus(404); }
3493
- } else if (obj.fs.existsSync(obj.path.join(__dirname, 'translate', 'translate.json'))) {
3494
- // Return the default translation file (JSON)
3495
- try { res.sendFile(obj.path.join(__dirname, 'translate', 'translate.json')); } catch (ex) { res.sendStatus(404); }
3496
- } else { res.sendStatus(404); }
3497
- } else if (data.action == 'setTranslations') {
3498
- obj.fs.writeFile(obj.path.join(obj.parent.datapath, 'translate.json'), obj.common.translationsToJson({ strings: data.strings }), function (err) { if (err == null) { res.send(JSON.stringify({ response: 'ok' })); } else { res.send(JSON.stringify({ response: err })); } });
3499
- } else if (data.action == 'translateServer') {
3500
- if (obj.pendingTranslation === true) { res.send(JSON.stringify({ response: 'Server is already performing a translation.' })); return; }
3501
- const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
3502
- if (nodeVersion < 8) { res.send(JSON.stringify({ response: 'Server requires NodeJS 8.x or better.' })); return; }
3503
- var translateFile = obj.path.join(obj.parent.datapath, 'translate.json');
3504
- if (obj.fs.existsSync(translateFile) == false) { translateFile = obj.path.join(__dirname, 'translate', 'translate.json'); }
3505
- if (obj.fs.existsSync(translateFile) == false) { res.send(JSON.stringify({ response: 'Unable to find translate.js file on the server.' })); return; }
3506
- res.send(JSON.stringify({ response: 'ok' }));
3507
- console.log('Started server translation...');
3508
- obj.pendingTranslation = true;
3509
- require('child_process').exec('node translate.js translateall \"' + translateFile + '\"', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, 'translate') }, function (error, stdout, stderr) {
3510
- delete obj.pendingTranslation;
3511
- //console.log('error', error);
3512
- //console.log('stdout', stdout);
3513
- //console.log('stderr', stderr);
3514
- //console.log('Server restart...'); // Perform a server restart
3515
- //process.exit(0);
3516
- console.log('Server translation completed.');
3517
- });
3518
- } else {
3519
- // Unknown request
3520
- res.sendStatus(404);
3521
- }
3522
- });
3523
- }
3524
-
3525
- // Handle welcome image request
3526
- function handleWelcomeImageRequest(req, res) {
3527
- const domain = checkUserIpAddress(req, res);
3528
- if (domain == null) { return; }
3529
-
3530
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3531
- if (domain.welcomepicture) {
3532
- if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.welcomepicture] != null)) {
3533
- // Use the welcome image in the database
3534
- res.set({ 'Content-Type': domain.welcomepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
3535
- res.send(parent.configurationFiles[domain.welcomepicture]);
3536
- return;
3537
- }
3538
-
3539
- // Use the configured logo picture
3540
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.welcomepicture)); return; } catch (ex) { }
3541
- }
3542
-
3543
- var imagefile = 'images/mainwelcome.jpg';
3544
- if (domain.sitestyle == 2) { imagefile = 'images/login/back.png'; }
3545
- if (domain.webpublicpath != null) {
3546
- obj.fs.exists(obj.path.join(domain.webpublicpath, imagefile), function (exists) {
3547
- if (exists) {
3548
- // Use the domain logo picture
3549
- try { res.sendFile(obj.path.join(domain.webpublicpath, imagefile)); } catch (ex) { res.sendStatus(404); }
3550
- } else {
3551
- // Use the default logo picture
3552
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3553
- }
3554
- });
3555
- } else if (parent.webPublicOverridePath) {
3556
- obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, imagefile), function (exists) {
3557
- if (exists) {
3558
- // Use the override logo picture
3559
- try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, imagefile)); } catch (ex) { res.sendStatus(404); }
3560
- } else {
3561
- // Use the default logo picture
3562
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3563
- }
3564
- });
3565
- } else {
3566
- // Use the default logo picture
3567
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3568
- }
3569
- }
3570
-
3571
- // Download a session recording
3572
- function handleGetRecordings(req, res) {
3573
- const domain = checkUserIpAddress(req, res);
3574
- if (domain == null) return;
3575
-
3576
- // Check the query
3577
- if ((domain.sessionrecording == null) || (req.query.file == null) || (obj.common.IsFilenameValid(req.query.file) !== true) || (req.query.file.endsWith('.mcrec') == false)) { res.sendStatus(401); return; }
3578
-
3579
- // Get the recording path
3580
- var recordingsPath = null;
3581
- if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.recordpath; }
3582
- if (recordingsPath == null) { res.sendStatus(401); return; }
3583
-
3584
- // Get the user and check user rights
3585
- var authUserid = null;
3586
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3587
- if (authUserid == null) { res.sendStatus(401); return; }
3588
- const user = obj.users[authUserid];
3589
- if (user == null) { res.sendStatus(401); return; }
3590
- if ((user.siteadmin & 512) == 0) { res.sendStatus(401); return; } // Check if we have right to get recordings
3591
-
3592
- // Send the recorded file
3593
- setContentDispositionHeader(res, 'application/octet-stream', req.query.file, null, 'recording.mcrec');
3594
- try { res.sendFile(obj.path.join(recordingsPath, req.query.file)); } catch (ex) { res.sendStatus(404); }
3595
- }
3596
-
3597
- // Stream a session recording
3598
- function handleGetRecordingsWebSocket(ws, req) {
3599
- var domain = checkAgentIpAddress(ws, req);
3600
- if (domain == null) { parent.debug('web', 'Got recordings file transfer connection with bad domain or blocked IP address ' + req.clientIp + ', dropping.'); try { ws.close(); } catch (ex) { } return; }
3601
-
3602
- // Check the query
3603
- if ((domain.sessionrecording == null) || (req.query.file == null) || (obj.common.IsFilenameValid(req.query.file) !== true) || (req.query.file.endsWith('.mcrec') == false)) { try { ws.close(); } catch (ex) { } return; }
3604
-
3605
- // Get the recording path
3606
- var recordingsPath = null;
3607
- if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.recordpath; }
3608
- if (recordingsPath == null) { try { ws.close(); } catch (ex) { } return; }
3609
-
3610
- // Get the user and check user rights
3611
- var authUserid = null;
3612
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3613
- if (authUserid == null) { try { ws.close(); } catch (ex) { } return; }
3614
- const user = obj.users[authUserid];
3615
- if (user == null) { try { ws.close(); } catch (ex) { } return; }
3616
- if ((user.siteadmin & 512) == 0) { try { ws.close(); } catch (ex) { } return; } // Check if we have right to get recordings
3617
- const filefullpath = obj.path.join(recordingsPath, req.query.file);
3618
-
3619
- obj.fs.stat(filefullpath, function(err, stats) {
3620
- if (err) {
3621
- try { ws.close(); } catch (ex) { } // File does not exist
3622
- } else {
3623
- obj.fs.open(filefullpath, 'r', function (err, fd) {
3624
- if (err == null) {
3625
- // When data is received from the web socket
3626
- ws.on('message', function (msg) {
3627
- if (typeof msg != 'string') return;
3628
- var command;
3629
- try { command = JSON.parse(msg); } catch (e) { return; }
3630
- if ((command == null) || (typeof command.action != 'string')) return;
3631
- switch (command.action) {
3632
- case 'get': {
3633
- const buffer = Buffer.alloc(8 + command.size);
3634
- //buffer.writeUInt32BE((command.ptr >> 32), 0);
3635
- buffer.writeUInt32BE((command.ptr & 0xFFFFFFFF), 4);
3636
- obj.fs.read(fd, buffer, 8, command.size, command.ptr, function (err, bytesRead, buffer) { if (bytesRead > (buffer.length - 8)) { buffer = buffer.slice(0, bytesRead + 8); } ws.send(buffer); });
3637
- break;
3638
- }
3639
- }
3640
- });
3641
-
3642
- // If error, do nothing
3643
- ws.on('error', function (err) { try { ws.close(); } catch (ex) { } obj.fs.close(fd, function (err) { }); });
3644
-
3645
- // If the web socket is closed
3646
- ws.on('close', function (req) { try { ws.close(); } catch (ex) { } obj.fs.close(fd, function (err) { }); });
3647
-
3648
- ws.send(JSON.stringify({ "action": "info", "name": req.query.file, "size": stats.size }));
3649
- } else {
3650
- try { ws.close(); } catch (ex) { }
3651
- }
3652
- });
3653
- }
3654
- });
3655
- }
3656
-
3657
- // Serve the player page
3658
- function handlePlayerRequest(req, res) {
3659
- const domain = checkUserIpAddress(req, res);
3660
- if (domain == null) { return; }
3661
-
3662
- parent.debug('web', 'handlePlayerRequest: sending player');
3663
- res.set({ 'Cache-Control': 'no-store' });
3664
- render(req, res, getRenderPage('player', req, domain), getRenderArgs({}, req, domain));
3665
- }
3666
-
3667
- // Serve the guest sharing page
3668
- function handleSharingRequest(req, res) {
3669
- const domain = getDomain(req, res);
3670
- if (domain == null) { return; }
3671
- if (req.query.c == null) { res.sendStatus(404); return; }
3672
- if (domain.guestdevicesharing === false) { res.sendStatus(404); return; } // This feature is not allowed.
3673
-
3674
- // Check the inbound guest sharing cookie
3675
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey, 9999999999); // Decode cookies with unlimited time.
3676
- if (c == null) { res.sendStatus(404); return; }
3677
-
3678
- if (c.a === 5) {
3679
- // This is the older style sharing cookie with everything encoded within it.
3680
- // This cookie style gives a very large URL, so it's not used anymore.
3681
- if ((typeof c.p !== 'number') || (c.p < 1) || (c.p > 7) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.pid != 'string')) { res.sendStatus(404); return; }
3682
- handleSharingRequestEx(req, res, domain, c);
3683
- return;
3684
- }
3685
- if (c.a === 6) {
3686
- // This is the new style sharing cookie, just encodes the pointer to the sharing information in the database.
3687
- // Gives a much more compact URL.
3688
- if (typeof c.pid != 'string') { res.sendStatus(404); return; }
3689
-
3690
- // Check the expired time, expire message.
3691
- if ((c.e != null) && (c.e <= Date.now())) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3692
-
3693
- obj.db.Get('deviceshare-' + c.pid, function (err, docs) {
3694
- if ((err != null) || (docs == null) || (docs.length != 1)) { res.sendStatus(404); return; }
3695
- const doc = docs[0];
3696
-
3697
- // If this is a recurrent share, check if we are at the currect time to make use of it
3698
- if (typeof doc.recurring == 'number') {
3699
- const now = Date.now();
3700
- if (now >= doc.startTime) { // We don't want to move the validity window before the start time
3701
- const deltaTime = (now - doc.startTime);
3702
- if (doc.recurring === 1) {
3703
- // This moves the start time to the next valid daily window
3704
- const oneDay = (24 * 60 * 60 * 1000);
3705
- var addition = Math.floor(deltaTime / oneDay);
3706
- if ((deltaTime - (addition * oneDay)) > (doc.duration * 60000)) { addition++; } // If we are passed the current windows, move to the next one. This will show link as not being valid yet.
3707
- doc.startTime += (addition * oneDay);
3708
- } else if (doc.recurring === 2) {
3709
- // This moves the start time to the next valid weekly window
3710
- const oneWeek = (7 * 24 * 60 * 60 * 1000);
3711
- var addition = Math.floor(deltaTime / oneWeek);
3712
- if ((deltaTime - (addition * oneWeek)) > (doc.duration * 60000)) { addition++; } // If we are passed the current windows, move to the next one. This will show link as not being valid yet.
3713
- doc.startTime += (addition * oneWeek);
3714
- }
3715
- }
3716
- }
3717
-
3718
- // Generate an old style cookie from the information in the database
3719
- var cookie = { a: 5, p: doc.p, gn: doc.guestName, nid: doc.nodeid, cf: doc.consent, pid: doc.publicid, k: doc.extrakey };
3720
- if (doc.userid) { cookie.uid = doc.userid; }
3721
- if ((cookie.userid == null) && (cookie.pid.startsWith('AS:node/'))) { cookie.nouser = 1; }
3722
- if (doc.startTime != null) {
3723
- if (doc.expireTime != null) { cookie.start = doc.startTime; cookie.expire = doc.expireTime; }
3724
- else if (doc.duration != null) { cookie.start = doc.startTime; cookie.expire = doc.startTime + (doc.duration * 60000); }
3725
- }
3726
- if (doc.viewOnly === true) { cookie.vo = 1; }
3727
- handleSharingRequestEx(req, res, domain, cookie);
3728
- });
3729
- return;
3730
- }
3731
- res.sendStatus(404); return;
3732
- }
3733
-
3734
- // Serve the guest sharing page
3735
- function handleSharingRequestEx(req, res, domain, c) {
3736
- // Check the expired time, expire message.
3737
- if ((c.expire != null) && (c.expire <= Date.now())) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3738
-
3739
- // Check the public id
3740
- obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
3741
- // Check if any desktop sharing links are present, expire message.
3742
- if ((err != null) || (docs.length == 0)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3743
-
3744
- // Search for the device share public identifier, expire message.
3745
- var found = false;
3746
- for (var i = 0; i < docs.length; i++) { if ((docs[i].publicid == c.pid) && ((docs[i].extrakey == null) || (docs[i].extrakey === c.k))) { found = true; } }
3747
- if (found == false) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3748
-
3749
- // Get information about this node
3750
- obj.db.Get(c.nid, function (err, nodes) {
3751
- if ((err != null) || (nodes == null) || (nodes.length != 1)) { res.sendStatus(404); return; }
3752
- var node = nodes[0];
3753
-
3754
- // Check the start time, not yet valid message.
3755
- if ((c.start != null) && (c.expire != null) && ((c.start > Date.now()) || (c.start > c.expire))) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3756
-
3757
- // Looks good, let's create the outbound session cookies.
3758
- // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
3759
- const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo };
3760
- if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; }
3761
- if (c.k != null) { authCookieData.k = c.k; }
3762
- const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey);
3763
-
3764
- // Server features
3765
- var features2 = 0;
3766
- if (obj.args.allowhighqualitydesktop !== false) { features2 += 1; } // Enable AllowHighQualityDesktop (Default true)
3767
-
3768
- // Lets respond by sending out the desktop viewer.
3769
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3770
- parent.debug('web', 'handleSharingRequest: Sending guest sharing page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
3771
- res.set({ 'Cache-Control': 'no-store' });
3772
- render(req, res, getRenderPage('sharing', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire, viewOnly: (c.vo == 1) ? 1 : 0, nodeName: encodeURIComponent(node.name).replace(/'/g, '%27'), features: c.p, features2: features2 }, req, domain));
3773
- });
3774
- });
3775
- }
3776
-
3777
- // Handle domain redirection
3778
- obj.handleDomainRedirect = function (req, res) {
3779
- const domain = checkUserIpAddress(req, res);
3780
- if (domain == null) { return; }
3781
- if (domain.redirects == null) { res.sendStatus(404); return; }
3782
- var urlArgs = '', urlName = null, splitUrl = req.originalUrl.split('?');
3783
- if (splitUrl.length > 1) { urlArgs = '?' + splitUrl[1]; }
3784
- if ((splitUrl.length > 0) && (splitUrl[0].length > 1)) { urlName = splitUrl[0].substring(1).toLowerCase(); }
3785
- if ((urlName == null) || (domain.redirects[urlName] == null) || (urlName[0] == '_')) { res.sendStatus(404); return; }
3786
- if (domain.redirects[urlName] == '~showversion') {
3787
- // Show the current version
3788
- res.end('MeshCentral v' + obj.parent.currentVer);
3789
- } else {
3790
- // Perform redirection
3791
- res.redirect(domain.redirects[urlName] + urlArgs + getQueryPortion(req));
3792
- }
3793
- }
3794
-
3795
- // Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
3796
- obj.getServerFilePath = function (user, domain, path) {
3797
- var splitpath = path.split('/'), serverpath = obj.path.join(obj.filespath, 'domain'), filename = '';
3798
- if ((splitpath.length < 3) || (splitpath[0] != 'user' && splitpath[0] != 'mesh') || (splitpath[1] != domain.id)) return null; // Basic validation
3799
- var objid = splitpath[0] + '/' + splitpath[1] + '/' + splitpath[2];
3800
- if (splitpath[0] == 'user' && (objid != user._id)) return null; // User validation, only self allowed
3801
- if (splitpath[0] == 'mesh') { if ((obj.GetMeshRights(user, objid) & 32) == 0) { return null; } } // Check mesh server file rights
3802
- if (splitpath[1] != '') { serverpath += '-' + splitpath[1]; } // Add the domain if needed
3803
- serverpath += ('/' + splitpath[0] + '-' + splitpath[2]);
3804
- for (var i = 3; i < splitpath.length; i++) { if (obj.common.IsFilenameValid(splitpath[i]) == true) { serverpath += '/' + splitpath[i]; filename = splitpath[i]; } else { return null; } } // Check that each folder is correct
3805
- return { fullpath: obj.path.resolve(obj.filespath, serverpath), path: serverpath, name: filename, quota: obj.getQuota(objid, domain) };
3806
- };
3807
-
3808
- // Return the maximum number of bytes allowed in the user account "My Files".
3809
- obj.getQuota = function (objid, domain) {
3810
- if (objid == null) return 0;
3811
- if (objid.startsWith('user/')) {
3812
- var user = obj.users[objid];
3813
- if (user == null) return 0;
3814
- if (user.siteadmin == 0xFFFFFFFF) return null; // Administrators have no user limit
3815
- if ((user.quota != null) && (typeof user.quota == 'number')) { return user.quota; }
3816
- if ((domain != null) && (domain.userquota != null) && (typeof domain.userquota == 'number')) { return domain.userquota; }
3817
- return null; // By default, the user will have no limit
3818
- } else if (objid.startsWith('mesh/')) {
3819
- var mesh = obj.meshes[objid];
3820
- if (mesh == null) return 0;
3821
- if ((mesh.quota != null) && (typeof mesh.quota == 'number')) { return mesh.quota; }
3822
- if ((domain != null) && (domain.meshquota != null) && (typeof domain.meshquota == 'number')) { return domain.meshquota; }
3823
- return null; // By default, the mesh will have no limit
3824
- }
3825
- return 0;
3826
- };
3827
-
3828
- // Download a file from the server
3829
- function handleDownloadFile(req, res) {
3830
- const domain = checkUserIpAddress(req, res);
3831
- if (domain == null) { return; }
3832
- if ((req.query.link == null) || (req.session == null) || (req.session.userid == null) || (domain == null) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
3833
- const user = obj.users[req.session.userid];
3834
- if (user == null) { res.sendStatus(404); return; }
3835
- const file = obj.getServerFilePath(user, domain, req.query.link);
3836
- if (file == null) { res.sendStatus(404); return; }
3837
- setContentDispositionHeader(res, 'application/octet-stream', file.name, null, 'file.bin');
3838
- obj.fs.exists(file.fullpath, function (exists) { if (exists == true) { res.sendFile(file.fullpath); } else { res.sendStatus(404); } });
3839
- }
3840
-
3841
- // Upload a MeshCore.js file to the server
3842
- function handleUploadMeshCoreFile(req, res) {
3843
- const domain = checkUserIpAddress(req, res);
3844
- if (domain == null) { return; }
3845
- if (domain.id !== '') { res.sendStatus(401); return; }
3846
-
3847
- var authUserid = null;
3848
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3849
-
3850
- const multiparty = require('multiparty');
3851
- const form = new multiparty.Form();
3852
- form.parse(req, function (err, fields, files) {
3853
- // If an authentication cookie is embedded in the form, use that.
3854
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3855
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3856
- if ((loginCookie != null) && (loginCookie.ip != null) && checkCookieIp(loginCookie.ip, req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3857
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3858
- }
3859
- if (authUserid == null) { res.sendStatus(401); return; }
3860
- if ((fields == null) || (fields.attrib == null) || (fields.attrib.length != 1)) { res.sendStatus(404); return; }
3861
-
3862
- // Get the user
3863
- const user = obj.users[authUserid];
3864
- if (user == null) { res.sendStatus(401); return; } // Check this user exists
3865
-
3866
- // Get the node and check node rights
3867
- const nodeid = fields.attrib[0];
3868
- obj.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
3869
- if ((node == null) || (rights != 0xFFFFFFFF) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
3870
- for (var i in files.files) {
3871
- var file = files.files[i];
3872
- obj.fs.readFile(file.path, 'utf8', function (err, data) {
3873
- if (err != null) return;
3874
- data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
3875
- obj.sendMeshAgentCore(user, domain, fields.attrib[0], 'custom', data); // Upload the core
3876
- try { obj.fs.unlinkSync(file.path); } catch (e) { }
3877
- });
3878
- }
3879
- res.send('');
3880
- });
3881
- });
3882
- }
3883
-
3884
- // Upload a MeshCore.js file to the server
3885
- function handleOneClickRecoveryFile(req, res) {
3886
- const domain = checkUserIpAddress(req, res);
3887
- if (domain == null) { return; }
3888
- if (domain.id !== '') { res.sendStatus(401); return; }
3889
-
3890
- var authUserid = null;
3891
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3892
-
3893
- const multiparty = require('multiparty');
3894
- const form = new multiparty.Form();
3895
- form.parse(req, function (err, fields, files) {
3896
- // If an authentication cookie is embedded in the form, use that.
3897
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3898
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3899
- if ((loginCookie != null) && (loginCookie.ip != null) && checkCookieIp(loginCookie.ip, req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3900
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3901
- }
3902
- if (authUserid == null) { res.sendStatus(401); return; }
3903
- if ((fields == null) || (fields.attrib == null) || (fields.attrib.length != 1)) { res.sendStatus(404); return; }
3904
-
3905
- // Get the user
3906
- const user = obj.users[authUserid];
3907
- if (user == null) { res.sendStatus(401); return; } // Check this user exists
3908
-
3909
- // Get the node and check node rights
3910
- const nodeid = fields.attrib[0];
3911
- obj.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
3912
- if ((node == null) || (rights != 0xFFFFFFFF) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
3913
- for (var i in files.files) {
3914
- var file = files.files[i];
3915
-
3916
- // Event Intel AMT One Click Recovery, this will cause Intel AMT wake operations on this and other servers.
3917
- parent.DispatchEvent('*', obj, { action: 'oneclickrecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.path });
3918
-
3919
- //try { obj.fs.unlinkSync(file.path); } catch (e) { } // TODO: Remove this file after 30 minutes.
3920
- }
3921
- res.send('');
3922
- });
3923
- });
3924
- }
3925
-
3926
- // Upload a file to the server
3927
- function handleUploadFile(req, res) {
3928
- const domain = checkUserIpAddress(req, res);
3929
- if (domain == null) { return; }
3930
- if (domain.userQuota == -1) { res.sendStatus(401); return; }
3931
- var authUserid = null;
3932
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3933
- const multiparty = require('multiparty');
3934
- const form = new multiparty.Form();
3935
- form.parse(req, function (err, fields, files) {
3936
- // If an authentication cookie is embedded in the form, use that.
3937
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3938
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3939
- if ((loginCookie != null) && (loginCookie.ip != null) && checkCookieIp(loginCookie.ip, req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3940
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3941
- }
3942
- if (authUserid == null) { res.sendStatus(401); return; }
3943
-
3944
- // Get the user
3945
- const user = obj.users[authUserid];
3946
- if ((user == null) || (user.siteadmin & 8) == 0) { res.sendStatus(401); return; } // Check if we have file rights
3947
-
3948
- if ((fields == null) || (fields.link == null) || (fields.link.length != 1)) { /*console.log('UploadFile, Invalid Fields:', fields, files);*/ console.log('err4'); res.sendStatus(404); return; }
3949
- var xfile = null;
3950
- try { xfile = obj.getServerFilePath(user, domain, decodeURIComponent(fields.link[0])); } catch (ex) { }
3951
- if (xfile == null) { res.sendStatus(404); return; }
3952
- // Get total bytes in the path
3953
- var totalsize = readTotalFileSize(xfile.fullpath);
3954
- if ((xfile.quota == null) || (totalsize < xfile.quota)) { // Check if the quota is not already broken
3955
- if (fields.name != null) {
3956
-
3957
- // See if we need to create the folder
3958
- var domainx = 'domain';
3959
- if (domain.id.length > 0) { domainx = 'domain-' + usersplit[1]; }
3960
- try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
3961
- try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (ex) { }
3962
- try { obj.fs.mkdirSync(xfile.fullpath); } catch (ex) { }
3963
-
3964
- // Upload method where all the file data is within the fields.
3965
- var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*');
3966
- if ((names.length == sizes.length) && (types.length == datas.length) && (names.length == types.length)) {
3967
- for (var i = 0; i < names.length; i++) {
3968
- if (obj.common.IsFilenameValid(names[i]) == false) { res.sendStatus(404); return; }
3969
- var filedata = Buffer.from(datas[i].split(',')[1], 'base64');
3970
- if ((xfile.quota == null) || ((totalsize + filedata.length) < xfile.quota)) { // Check if quota would not be broken if we add this file
3971
- // Create the user folder if needed
3972
- (function (fullpath, filename, filedata) {
3973
- obj.fs.mkdir(xfile.fullpath, function () {
3974
- // Write the file
3975
- obj.fs.writeFile(obj.path.join(xfile.fullpath, filename), filedata, function () {
3976
- obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
3977
- });
3978
- });
3979
- })(xfile.fullpath, names[i], filedata);
3980
- } else {
3981
- // Send a notification
3982
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: names[i], nolog: 1, id: Math.random() });
3983
- }
3984
- }
3985
- }
3986
- } else {
3987
- // More typical upload method, the file data is in a multipart mime post.
3988
- for (var i in files.files) {
3989
- var file = files.files[i], fpath = obj.path.join(xfile.fullpath, file.originalFilename);
3990
- if (obj.common.IsFilenameValid(file.originalFilename) && ((xfile.quota == null) || ((totalsize + file.size) < xfile.quota))) { // Check if quota would not be broken if we add this file
3991
-
3992
- // See if we need to create the folder
3993
- var domainx = 'domain';
3994
- if (domain.id.length > 0) { domainx = 'domain-' + domain.id; }
3995
- try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
3996
- try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (e) { }
3997
- try { obj.fs.mkdirSync(xfile.fullpath); } catch (e) { }
3998
-
3999
- // Rename the file
4000
- obj.fs.rename(file.path, fpath, function (err) {
4001
- if (err && (err.code === 'EXDEV')) {
4002
- // On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
4003
- obj.common.copyFile(file.path, fpath, function (err) {
4004
- obj.fs.unlink(file.path, function (err) {
4005
- obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
4006
- });
4007
- });
4008
- } else {
4009
- obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
4010
- }
4011
- });
4012
- } else {
4013
- // Send a notification
4014
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: file.originalFilename, nolog: 1, id: Math.random() });
4015
- try { obj.fs.unlink(file.path, function (err) { }); } catch (e) { }
4016
- }
4017
- }
4018
- }
4019
- } else {
4020
- // Send a notification
4021
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: "Disk quota exceed", nolog: 1, id: Math.random() });
4022
- }
4023
- res.send('');
4024
- });
4025
- }
4026
-
4027
- // Upload a file to the server and then batch upload to many agents
4028
- function handleUploadFileBatch(req, res) {
4029
- const domain = checkUserIpAddress(req, res);
4030
- if (domain == null) { return; }
4031
- var authUserid = null;
4032
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
4033
- const multiparty = require('multiparty');
4034
- const form = new multiparty.Form();
4035
- form.parse(req, function (err, fields, files) {
4036
- // If an authentication cookie is embedded in the form, use that.
4037
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
4038
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
4039
- if ((loginCookie != null) && (loginCookie.ip != null) && checkCookieIp(loginCookie.ip, req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
4040
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
4041
- }
4042
- if (authUserid == null) { res.sendStatus(401); return; }
4043
-
4044
- // Get the user
4045
- const user = obj.users[authUserid];
4046
- if (user == null) { parent.debug('web', 'Batch upload error, invalid user.'); res.sendStatus(401); return; } // Check if user exists
4047
-
4048
- // Get fields
4049
- if ((fields == null) || (fields.nodeIds == null) || (fields.nodeIds.length != 1)) { res.sendStatus(404); return; }
4050
- var cmd = { nodeids: fields.nodeIds[0].split(','), files: [], user: user, domain: domain, overwrite: false, createFolder: false };
4051
- if ((fields.winpath != null) && (fields.winpath.length == 1)) { cmd.windowsPath = fields.winpath[0]; }
4052
- if ((fields.linuxpath != null) && (fields.linuxpath.length == 1)) { cmd.linuxPath = fields.linuxpath[0]; }
4053
- if ((fields.overwriteFiles != null) && (fields.overwriteFiles.length == 1) && (fields.overwriteFiles[0] == 'on')) { cmd.overwrite = true; }
4054
- if ((fields.createFolder != null) && (fields.createFolder.length == 1) && (fields.createFolder[0] == 'on')) { cmd.createFolder = true; }
4055
-
4056
- // Check if we have at least one target path
4057
- if ((cmd.windowsPath == null) && (cmd.linuxPath == null)) {
4058
- parent.debug('web', 'Batch upload error, invalid fields: ' + JSON.stringify(fields));
4059
- res.send('');
4060
- return;
4061
- }
4062
-
4063
- // Get server temporary path
4064
- var serverpath = obj.path.join(obj.filespath, 'tmp')
4065
- try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
4066
- try { obj.fs.mkdirSync(serverpath); } catch (ex) { }
4067
-
4068
- // More typical upload method, the file data is in a multipart mime post.
4069
- for (var i in files.files) {
4070
- var file = files.files[i], ftarget = getRandomPassword() + '-' + file.originalFilename, fpath = obj.path.join(serverpath, ftarget);
4071
- cmd.files.push({ name: file.originalFilename, target: ftarget });
4072
- // Rename the file
4073
- obj.fs.rename(file.path, fpath, function (err) {
4074
- if (err && (err.code === 'EXDEV')) {
4075
- // On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
4076
- obj.common.copyFile(file.path, fpath, function (err) { obj.fs.unlink(file.path, function (err) { }); });
4077
- }
4078
- });
4079
- }
4080
-
4081
- // Instruct one of more agents to download a URL to a given local drive location.
4082
- var tlsCertHash = null;
4083
- if ((parent.args.ignoreagenthashcheck == null) || (parent.args.ignoreagenthashcheck === false)) { // TODO: If ignoreagenthashcheck is an array of IP addresses, not sure how to handle this.
4084
- tlsCertHash = obj.webCertificateFullHashs[cmd.domain.id];
4085
- if (tlsCertHash != null) { tlsCertHash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
4086
- }
4087
- for (var i in cmd.nodeids) {
4088
- obj.GetNodeWithRights(cmd.domain, cmd.user, cmd.nodeids[i], function (node, rights, visible) {
4089
- if ((node == null) || ((rights & 8) == 0) || (visible == false)) return; // We don't have remote control rights to this device
4090
- var agentPath = (((node.agent.id > 0) && (node.agent.id < 5)) || (node.agent.id == 34)) ? cmd.windowsPath : cmd.linuxPath;
4091
- if (agentPath == null) return;
4092
-
4093
- // Compute user consent
4094
- var consent = 0;
4095
- var mesh = obj.meshes[node.meshid];
4096
- if (typeof domain.userconsentflags == 'number') { consent |= domain.userconsentflags; } // Add server required consent flags
4097
- if ((mesh != null) && (typeof mesh.consent == 'number')) { consent |= mesh.consent; } // Add device group user consent
4098
- if (typeof node.consent == 'number') { consent |= node.consent; } // Add node user consent
4099
- if (typeof user.consent == 'number') { consent |= user.consent; } // Add user consent
4100
-
4101
- // Check if we need to add consent flags because of a user group link
4102
- if ((mesh != null) && (user.links != null) && (user.links[mesh._id] == null) && (user.links[node._id] == null)) {
4103
- // This user does not have a direct link to the device group or device. Find all user groups the would cause the link.
4104
- for (var i in user.links) {
4105
- var ugrp = obj.userGroups[i];
4106
- if ((ugrp != null) && (ugrp.consent != null) && (ugrp.links != null) && ((ugrp.links[mesh._id] != null) || (ugrp.links[node._id] != null))) {
4107
- consent |= ugrp.consent; // Add user group consent flags
4108
- }
4109
- }
4110
- }
4111
-
4112
- // Event that this operation is being performed.
4113
- var targets = obj.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', cmd.user._id]);
4114
- var msgid = 103; // "Batch upload of {0} file(s) to folder {1}"
4115
- var event = { etype: 'node', userid: cmd.user._id, username: cmd.user.name, nodeid: node._id, action: 'batchupload', msg: 'Performing batch upload of ' + cmd.files.length + ' file(s) to ' + agentPath, msgid: msgid, msgArgs: [cmd.files.length, agentPath], domain: cmd.domain.id };
4116
- parent.DispatchEvent(targets, obj, event);
4117
-
4118
- // Send the agent commands to perform the batch upload operation
4119
- for (var f in cmd.files) {
4120
- if (cmd.files[f].name != null) {
4121
- const acmd = { action: 'wget', userid: user._id, username: user.name, realname: user.realname, remoteaddr: req.clientIp, consent: consent, rights: rights, overwrite: cmd.overwrite, createFolder: cmd.createFolder, urlpath: '/agentdownload.ashx?c=' + obj.parent.encodeCookie({ a: 'tmpdl', d: cmd.domain.id, nid: node._id, f: cmd.files[f].target }, obj.parent.loginCookieEncryptionKey), path: obj.path.join(agentPath, cmd.files[f].name), folder: agentPath, servertlshash: tlsCertHash };
4122
- var agent = obj.wsagents[node._id];
4123
- if (agent != null) { try { agent.send(JSON.stringify(acmd)); } catch (ex) { } }
4124
- // TODO: Add support for peer servers.
4125
- }
4126
- }
4127
- });
4128
- }
4129
-
4130
- res.send('');
4131
- });
4132
- }
4133
-
4134
- // Subscribe to all events we are allowed to receive
4135
- obj.subscribe = function (userid, target) {
4136
- const user = obj.users[userid];
4137
- const subscriptions = [userid, 'server-allusers'];
4138
- if (user.siteadmin != null) {
4139
- // Allow full site administrators of users with all events rights to see all events.
4140
- if ((user.siteadmin == 0xFFFFFFFF) || ((user.siteadmin & 2048) != 0)) { subscriptions.push('*'); }
4141
- else if ((user.siteadmin & 2) != 0) {
4142
- if ((user.groups == null) || (user.groups.length == 0)) {
4143
- // Subscribe to all user changes
4144
- subscriptions.push('server-users');
4145
- } else {
4146
- // Subscribe to user changes for some groups
4147
- for (var i in user.groups) { subscriptions.push('server-users:' + i); }
4148
- }
4149
- }
4150
- }
4151
- if (user.links != null) { for (var i in user.links) { subscriptions.push(i); } }
4152
- obj.parent.RemoveAllEventDispatch(target);
4153
- obj.parent.AddEventDispatch(subscriptions, target);
4154
- return subscriptions;
4155
- };
4156
-
4157
- // Handle a web socket relay request
4158
- function handleRelayWebSocket(ws, req, domain, user, cookie) {
4159
- if (!(req.query.host)) { console.log('ERR: No host target specified'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4160
- parent.debug('web', 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.');
4161
-
4162
- try { ws._socket.setKeepAlive(true, 240000); } catch (ex) { } // Set TCP keep alive
4163
-
4164
- // Fetch information about the target
4165
- obj.db.Get(req.query.host, function (err, docs) {
4166
- if (docs.length == 0) { console.log('ERR: Node not found'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4167
- var node = docs[0];
4168
- if (!node.intelamt) { console.log('ERR: Not AMT node'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4169
-
4170
- // Check if this user has permission to manage this computer
4171
- if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (3)'); try { ws.close(); } catch (e) { } return; }
4172
-
4173
- // Check what connectivity is available for this node
4174
- var state = parent.GetConnectivityState(req.query.host);
4175
- var conn = 0;
4176
- if (!state || state.connectivity == 0) { parent.debug('web', 'ERR: No routing possible (1)'); try { ws.close(); } catch (e) { } return; } else { conn = state.connectivity; }
4177
-
4178
- // Check what server needs to handle this connection
4179
- if ((obj.parent.multiServer != null) && ((cookie == null) || (cookie.ps != 1))) { // If a cookie is provided and is from a peer server, don't allow the connection to jump again to a different server
4180
- var server = obj.parent.GetRoutingServerId(req.query.host, 2); // Check for Intel CIRA connection
4181
- if (server != null) {
4182
- if (server.serverid != obj.parent.serverId) {
4183
- // Do local Intel CIRA routing using a different server
4184
- parent.debug('web', 'Route Intel AMT CIRA connection to peer server: ' + server.serverid);
4185
- obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
4186
- return;
4187
- }
4188
- } else {
4189
- server = obj.parent.GetRoutingServerId(req.query.host, 4); // Check for local Intel AMT connection
4190
- if ((server != null) && (server.serverid != obj.parent.serverId)) {
4191
- // Do local Intel AMT routing using a different server
4192
- parent.debug('web', 'Route Intel AMT direct connection to peer server: ' + server.serverid);
4193
- obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
4194
- return;
4195
- }
4196
- }
4197
- }
4198
-
4199
- // Setup session recording if needed
4200
- if (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf((req.query.p == 2) ? 101 : 100) >= 0)))) { // TODO 100
4201
- // Check again if we need to do recording
4202
- var record = true;
4203
-
4204
- // Check user or device group recording
4205
- if ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.onlyselectedusers === true) || (domain.sessionrecording.onlyselecteddevicegroups === true))) {
4206
- record = false;
4207
-
4208
- // Check device group recording
4209
- if (domain.sessionrecording.onlyselecteddevicegroups === true) {
4210
- var mesh = obj.meshes[node.meshid];
4211
- if ((mesh.flags != null) && ((mesh.flags & 4) != 0)) { record = true; } // Record the session
4212
- }
4213
-
4214
- // Check user recording
4215
- if (domain.sessionrecording.onlyselectedusers === true) {
4216
- if ((user.flags != null) && ((user.flags & 2) != 0)) { record = true; } // Record the session
4217
- }
4218
- }
4219
-
4220
- if (record == true) {
4221
- var now = new Date(Date.now());
4222
- var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + now.getUTCFullYear() + '-' + obj.common.zeroPad(now.getUTCMonth() + 1, 2) + '-' + obj.common.zeroPad(now.getUTCDate(), 2) + '-' + obj.common.zeroPad(now.getUTCHours(), 2) + '-' + obj.common.zeroPad(now.getUTCMinutes(), 2) + '-' + obj.common.zeroPad(now.getUTCSeconds(), 2) + '-' + getRandomPassword() + '.mcrec'
4223
- var recFullFilename = null;
4224
- if (domain.sessionrecording.filepath) {
4225
- try { obj.fs.mkdirSync(domain.sessionrecording.filepath); } catch (e) { }
4226
- recFullFilename = obj.path.join(domain.sessionrecording.filepath, recFilename);
4227
- } else {
4228
- try { obj.fs.mkdirSync(parent.recordpath); } catch (e) { }
4229
- recFullFilename = obj.path.join(parent.recordpath, recFilename);
4230
- }
4231
- var fd = obj.fs.openSync(recFullFilename, 'w');
4232
- if (fd != null) {
4233
- // Write the recording file header
4234
- var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: req.clientIp, nodeid: node._id, intelamt: true, protocol: (req.query.p == 2) ? 101 : 100, time: new Date().toLocaleString() })
4235
- recordingEntry(fd, 1, 0, firstBlock, function () { });
4236
- ws.logfile = { fd: fd, lock: false };
4237
- if (req.query.p == 2) { ws.send(Buffer.from(String.fromCharCode(0xF0), 'binary')); } // Intel AMT Redirection: Indicate the session is being recorded
4238
- }
4239
- }
4240
- }
4241
-
4242
- // If Intel AMT CIRA connection is available, use it
4243
- var ciraconn = parent.mpsserver.GetConnectionToNode(req.query.host, null, false);
4244
- if (ciraconn != null) {
4245
- parent.debug('web', 'Opening relay CIRA channel connection to ' + req.query.host + '.');
4246
-
4247
- // TODO: If the CIRA connection is a relay or LMS connection, we can't detect the TLS state like this.
4248
- // Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
4249
- var port = 16993;
4250
- //if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA
4251
- if (ciraconn.tag.boundPorts.indexOf(16992) >= 0) port = 16992; // RELEASE: Always use non-TLS mode if available within CIRA
4252
- if (req.query.p == 2) port += 2;
4253
-
4254
- // Setup a new CIRA channel
4255
- if ((port == 16993) || (port == 16995)) {
4256
- // Perform TLS
4257
- var ser = new SerialTunnel();
4258
- var chnl = parent.mpsserver.SetupChannel(ciraconn, port);
4259
-
4260
- // Let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
4261
- // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
4262
- ser.forwardwrite = function (data) { if (data.length > 0) { chnl.write(data); } }; // TLS ---> CIRA
4263
-
4264
- // When APF tunnel return something, update SerialTunnel buffer
4265
- chnl.onData = function (ciraconn, data) { if (data.length > 0) { try { ser.updateBuffer(data); } catch (ex) { console.log(ex); } } }; // CIRA ---> TLS
4266
-
4267
- // Handle CIRA tunnel state change
4268
- chnl.onStateChange = function (ciraconn, state) {
4269
- parent.debug('webrelay', 'Relay TLS CIRA state change', state);
4270
- if (state == 0) { try { ws.close(); } catch (e) { } }
4271
- if (state == 2) {
4272
- // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
4273
- const tlsoptions = { socket: ser, ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
4274
- if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
4275
- var tlsock = obj.tls.connect(tlsoptions, function () { parent.debug('webrelay', "CIRA Secure TLS Connection"); ws._socket.resume(); });
4276
- tlsock.chnl = chnl;
4277
- tlsock.setEncoding('binary');
4278
- tlsock.on('error', function (err) { parent.debug('webrelay', "CIRA TLS Connection Error", err); });
4279
-
4280
- // Decrypted tunnel from TLS communcation to be forwarded to websocket
4281
- tlsock.on('data', function (data) {
4282
- // AMT/TLS ---> WS
4283
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
4284
- try { ws.send(data); } catch (ex) { }
4285
- });
4286
-
4287
- // If TLS is on, forward it through TLSSocket
4288
- ws.forwardclient = tlsock;
4289
- ws.forwardclient.xtls = 1;
4290
-
4291
- ws.forwardclient.onStateChange = function (ciraconn, state) {
4292
- parent.debug('webrelay', 'Relay CIRA state change', state);
4293
- if (state == 0) { try { ws.close(); } catch (e) { } }
4294
- };
4295
-
4296
- ws.forwardclient.onData = function (ciraconn, data) {
4297
- // Run data thru interceptor
4298
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); }
4299
-
4300
- if (data.length > 0) {
4301
- if (ws.logfile == null) {
4302
- try { ws.send(data); } catch (e) { }
4303
- } else {
4304
- // Log to recording file
4305
- recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } }); // TODO: Add TLS support
4306
- }
4307
- }
4308
- };
4309
-
4310
- // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
4311
- ws.forwardclient.onSendOk = function (ciraconn) { };
4312
- }
4313
- };
4314
- } else {
4315
- // Without TLS
4316
- ws.forwardclient = parent.mpsserver.SetupChannel(ciraconn, port);
4317
- ws.forwardclient.xtls = 0;
4318
- ws._socket.resume();
4319
-
4320
- ws.forwardclient.onStateChange = function (ciraconn, state) {
4321
- parent.debug('webrelay', 'Relay CIRA state change', state);
4322
- if (state == 0) { try { ws.close(); } catch (e) { } }
4323
- };
4324
-
4325
- ws.forwardclient.onData = function (ciraconn, data) {
4326
- //parent.debug('webrelaydata', 'Relay CIRA data to WS', data.length);
4327
-
4328
- // Run data thru interceptorp
4329
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); }
4330
-
4331
- //console.log('AMT --> WS', Buffer.from(data, 'binary').toString('hex'));
4332
- if (data.length > 0) {
4333
- if (ws.logfile == null) {
4334
- try { ws.send(data); } catch (e) { }
4335
- } else {
4336
- // Log to recording file
4337
- recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } });
4338
- }
4339
- }
4340
- };
4341
-
4342
- // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
4343
- ws.forwardclient.onSendOk = function (ciraconn) { };
4344
- }
4345
-
4346
- // When data is received from the web socket, forward the data into the associated CIRA cahnnel.
4347
- // If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
4348
- ws.on('message', function (data) {
4349
- //parent.debug('webrelaydata', 'Relay WS data to CIRA', data.length);
4350
- if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
4351
-
4352
- // WS ---> AMT/TLS
4353
- if (ws.interceptor) { data = ws.interceptor.processBrowserData(data); } // Run data thru interceptor
4354
-
4355
- // Log to recording file
4356
- if (ws.logfile == null) {
4357
- // Forward data to the associated TCP connection.
4358
- try { ws.forwardclient.write(data); } catch (ex) { }
4359
- } else {
4360
- // Log to recording file
4361
- recordingEntry(ws.logfile.fd, 2, 2, data, function () { try { ws.forwardclient.write(data); } catch (ex) { } });
4362
- }
4363
- });
4364
-
4365
- // If error, close the associated TCP connection.
4366
- ws.on('error', function (err) {
4367
- console.log('CIRA server websocket error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
4368
- parent.debug('webrelay', 'Websocket relay closed on error.');
4369
-
4370
- // Websocket closed, close the CIRA channel and TLS session.
4371
- if (ws.forwardclient) {
4372
- if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
4373
- if (ws.forwardclient.end) { ws.forwardclient.end(); } // TLS, close the TLS session
4374
- if (ws.forwardclient.chnl) { ws.forwardclient.chnl.close(); } // TLS, close the CIRA channel
4375
- delete ws.forwardclient;
4376
- }
4377
-
4378
- // Close the recording file
4379
- if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
4380
- });
4381
-
4382
- // If the web socket is closed, close the associated TCP connection.
4383
- ws.on('close', function (req) {
4384
- parent.debug('webrelay', 'Websocket relay closed.');
4385
-
4386
- // Websocket closed, close the CIRA channel and TLS session.
4387
- if (ws.forwardclient) {
4388
- if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
4389
- if (ws.forwardclient.end) { ws.forwardclient.end(); } // TLS, close the TLS session
4390
- if (ws.forwardclient.chnl) { ws.forwardclient.chnl.close(); } // TLS, close the CIRA channel
4391
- delete ws.forwardclient;
4392
- }
4393
-
4394
- // Close the recording file
4395
- if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
4396
- });
4397
-
4398
- // Note that here, req.query.p: 1 = WSMAN with server auth, 2 = REDIR with server auth, 3 = WSMAN without server auth, 4 = REDIR with server auth
4399
-
4400
- // Fetch Intel AMT credentials & Setup interceptor
4401
- if (req.query.p == 1) {
4402
- parent.debug('webrelaydata', 'INTERCEPTOR1', { host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
4403
- ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
4404
- ws.interceptor.blockAmtStorage = true;
4405
- } else if (req.query.p == 2) {
4406
- parent.debug('webrelaydata', 'INTERCEPTOR2', { user: node.intelamt.user, pass: node.intelamt.pass });
4407
- ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass });
4408
- ws.interceptor.blockAmtStorage = true;
4409
- }
4410
-
4411
- return;
4412
- }
4413
-
4414
- // If Intel AMT direct connection is possible, option a direct socket
4415
- if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
4416
- parent.debug('webrelay', 'Opening relay TCP socket connection to ' + req.query.host + '.');
4417
-
4418
- // When data is received from the web socket, forward the data into the associated TCP connection.
4419
- ws.on('message', function (msg) {
4420
- //parent.debug('webrelaydata', 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes');
4421
-
4422
- if (typeof msg == 'string') { msg = Buffer.from(msg, 'binary'); }
4423
- if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
4424
-
4425
- // Log to recording file
4426
- if (ws.logfile == null) {
4427
- // Forward data to the associated TCP connection.
4428
- try { ws.forwardclient.write(msg); } catch (ex) { }
4429
- } else {
4430
- // Log to recording file
4431
- recordingEntry(ws.logfile.fd, 2, 2, msg, function () { try { ws.forwardclient.write(msg); } catch (ex) { } });
4432
- }
4433
- });
4434
-
4435
- // If error, close the associated TCP connection.
4436
- ws.on('error', function (err) {
4437
- console.log('Error with relay web socket connection from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
4438
- parent.debug('webrelay', 'Error with relay web socket connection from ' + req.clientIp + '.');
4439
- if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
4440
-
4441
- // Close the recording file
4442
- if (ws.logfile != null) {
4443
- recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
4444
- obj.fs.close(fd);
4445
- ws.logfile = null;
4446
- });
4447
- }
4448
- });
4449
-
4450
- // If the web socket is closed, close the associated TCP connection.
4451
- ws.on('close', function () {
4452
- parent.debug('webrelay', 'Closing relay web socket connection to ' + req.query.host + '.');
4453
- if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
4454
-
4455
- // Close the recording file
4456
- if (ws.logfile != null) {
4457
- recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
4458
- obj.fs.close(fd);
4459
- ws.logfile = null;
4460
- });
4461
- }
4462
- });
4463
-
4464
- // Compute target port
4465
- var port = 16992;
4466
- if (node.intelamt.tls > 0) port = 16993; // This is a direct connection, use TLS when possible
4467
- if ((req.query.p == 2) || (req.query.p == 4)) port += 2;
4468
-
4469
- if (node.intelamt.tls == 0) {
4470
- // If this is TCP (without TLS) set a normal TCP socket
4471
- ws.forwardclient = new obj.net.Socket();
4472
- ws.forwardclient.setEncoding('binary');
4473
- ws.forwardclient.xstate = 0;
4474
- ws.forwardclient.forwardwsocket = ws;
4475
- ws._socket.resume();
4476
- } else {
4477
- // If TLS is going to be used, setup a TLS socket
4478
- var tlsoptions = { ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
4479
- if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
4480
- ws.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
4481
- // The TLS connection method is the same as TCP, but located a bit differently.
4482
- parent.debug('webrelay', 'TLS connected to ' + node.host + ':' + port + '.');
4483
- ws.forwardclient.xstate = 1;
4484
- ws._socket.resume();
4485
- });
4486
- ws.forwardclient.setEncoding('binary');
4487
- ws.forwardclient.xstate = 0;
4488
- ws.forwardclient.forwardwsocket = ws;
4489
- }
4490
-
4491
- // When we receive data on the TCP connection, forward it back into the web socket connection.
4492
- ws.forwardclient.on('data', function (data) {
4493
- if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
4494
- if (obj.parent.debugLevel >= 1) { // DEBUG
4495
- parent.debug('webrelaydata', 'TCP relay data from ' + node.host + ', ' + data.length + ' bytes.');
4496
- //if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
4497
- }
4498
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
4499
- if (ws.logfile == null) {
4500
- // No logging
4501
- try { ws.send(data); } catch (e) { }
4502
- } else {
4503
- // Log to recording file
4504
- recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (e) { } });
4505
- }
4506
- });
4507
-
4508
- // If the TCP connection closes, disconnect the associated web socket.
4509
- ws.forwardclient.on('close', function () {
4510
- parent.debug('webrelay', 'TCP relay disconnected from ' + node.host + ':' + port + '.');
4511
- try { ws.close(); } catch (e) { }
4512
- });
4513
-
4514
- // If the TCP connection causes an error, disconnect the associated web socket.
4515
- ws.forwardclient.on('error', function (err) {
4516
- parent.debug('webrelay', 'TCP relay error from ' + node.host + ':' + port + ': ' + err);
4517
- try { ws.close(); } catch (e) { }
4518
- });
4519
-
4520
- // Fetch Intel AMT credentials & Setup interceptor
4521
- if (req.query.p == 1) { ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); }
4522
- else if (req.query.p == 2) { ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass }); }
4523
-
4524
- if (node.intelamt.tls == 0) {
4525
- // A TCP connection to Intel AMT just connected, start forwarding.
4526
- ws.forwardclient.connect(port, node.host, function () {
4527
- parent.debug('webrelay', 'TCP relay connected to ' + node.host + ':' + port + '.');
4528
- ws.forwardclient.xstate = 1;
4529
- ws._socket.resume();
4530
- });
4531
- }
4532
- return;
4533
- }
4534
-
4535
- });
4536
- }
4537
-
4538
- // Setup agent to/from server file transfer handler
4539
- function handleAgentFileTransfer(ws, req) {
4540
- var domain = checkAgentIpAddress(ws, req);
4541
- if (domain == null) { parent.debug('web', 'Got agent file transfer connection with bad domain or blocked IP address ' + req.clientIp + ', dropping.'); ws.close(); return; }
4542
- if (req.query.c == null) { parent.debug('web', 'Got agent file transfer connection without a cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
4543
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
4544
- if ((c == null) || (c.a != 'aft')) { parent.debug('web', 'Got agent file transfer connection with invalid cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
4545
- ws.xcmd = c.b; ws.xarg = c.c, ws.xfilelen = 0;
4546
- ws.send('c'); // Indicate connection of the tunnel. In this case, we are the termination point.
4547
- ws.send('5'); // Indicate we want to perform file transfers (5 = Files).
4548
- if (ws.xcmd == 'coredump') {
4549
- // Check the agent core dump folder if not already present.
4550
- var coreDumpPath = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps');
4551
- if (obj.fs.existsSync(coreDumpPath) == false) { try { obj.fs.mkdirSync(coreDumpPath); } catch (ex) { } }
4552
- ws.xfilepath = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps', ws.xarg);
4553
- ws.xid = 'coredump';
4554
- ws.send(JSON.stringify({ action: 'download', sub: 'start', ask: 'coredump', id: 'coredump' })); // Ask for a core dump file
4555
- }
4556
-
4557
- // When data is received from the web socket, echo it back
4558
- ws.on('message', function (data) {
4559
- if (typeof data == 'string') {
4560
- // Control message
4561
- var cmd = null;
4562
- try { cmd = JSON.parse(data); } catch (ex) { }
4563
- if ((cmd == null) || (cmd.action != 'download') || (cmd.sub == null)) return;
4564
- switch (cmd.sub) {
4565
- case 'start': {
4566
- // Perform an async file open
4567
- var callback = function onFileOpen(err, fd) {
4568
- onFileOpen.xws.xfile = fd;
4569
- try { onFileOpen.xws.send(JSON.stringify({ action: 'download', sub: 'startack', id: onFileOpen.xws.xid, ack: 1 })); } catch (ex) { } // Ask for a directory (test)
4570
- };
4571
- callback.xws = this;
4572
- obj.fs.open(this.xfilepath + '.part', 'w', callback);
4573
- break;
4574
- }
4575
- }
4576
- } else {
4577
- // Binary message
4578
- if (data.length < 4) return;
4579
- var flags = data.readInt32BE(0);
4580
- if ((data.length > 4)) {
4581
- // Write the file
4582
- this.xfilelen += (data.length - 4);
4583
- try {
4584
- var callback = function onFileDataWritten(err, bytesWritten, buffer) {
4585
- if (onFileDataWritten.xflags & 1) {
4586
- // End of file
4587
- parent.debug('web', "Completed downloads of agent dumpfile, " + onFileDataWritten.xws.xfilelen + " bytes.");
4588
- if (onFileDataWritten.xws.xfile) {
4589
- obj.fs.close(onFileDataWritten.xws.xfile, function (err) { });
4590
- obj.fs.rename(onFileDataWritten.xws.xfilepath + '.part', onFileDataWritten.xws.xfilepath, function (err) { });
4591
- onFileDataWritten.xws.xfile = null;
4592
- }
4593
- try { onFileDataWritten.xws.send(JSON.stringify({ action: 'markcoredump' })); } catch (ex) { } // Ask to delete the core dump file
4594
- try { onFileDataWritten.xws.close(); } catch (ex) { }
4595
- } else {
4596
- // Send ack
4597
- try { onFileDataWritten.xws.send(JSON.stringify({ action: 'download', sub: 'ack', id: onFileDataWritten.xws.xid })); } catch (ex) { } // Ask for a directory (test)
4598
- }
4599
- };
4600
- callback.xws = this;
4601
- callback.xflags = flags;
4602
- obj.fs.write(this.xfile, data, 4, data.length - 4, callback);
4603
- } catch (ex) { }
4604
- } else {
4605
- if (flags & 1) {
4606
- // End of file
4607
- parent.debug('web', "Completed downloads of agent dumpfile, " + this.xfilelen + " bytes.");
4608
- if (this.xfile) {
4609
- obj.fs.close(this.xfile, function (err) { });
4610
- obj.fs.rename(this.xfilepath + '.part', this.xfilepath, function (err) { });
4611
- this.xfile = null;
4612
- }
4613
- this.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file
4614
- try { this.close(); } catch (ex) { }
4615
- } else {
4616
- // Send ack
4617
- this.send(JSON.stringify({ action: 'download', sub: 'ack', id: this.xid })); // Ask for a directory (test)
4618
- }
4619
- }
4620
- }
4621
- });
4622
-
4623
- // If error, do nothing.
4624
- ws.on('error', function (err) { console.log('Agent file transfer server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); });
4625
-
4626
- // If closed, do nothing
4627
- ws.on('close', function (req) {
4628
- if (this.xfile) {
4629
- obj.fs.close(this.xfile, function (err) { });
4630
- obj.fs.unlink(this.xfilepath + '.part', function (err) { }); // Remove a partial file
4631
- }
4632
- });
4633
- }
4634
-
4635
- // Handle the web socket echo request, just echo back the data sent
4636
- function handleEchoWebSocket(ws, req) {
4637
- const domain = checkUserIpAddress(ws, req);
4638
- if (domain == null) { return; }
4639
- ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
4640
-
4641
- // When data is received from the web socket, echo it back
4642
- ws.on('message', function (data) {
4643
- if (data.toString('utf8') == 'close') {
4644
- try { ws.close(); } catch (e) { console.log(e); }
4645
- } else {
4646
- try { ws.send(data); } catch (e) { console.log(e); }
4647
- }
4648
- });
4649
-
4650
- // If error, do nothing.
4651
- ws.on('error', function (err) { console.log('Echo server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); });
4652
-
4653
- // If closed, do nothing
4654
- ws.on('close', function (req) { });
4655
- }
4656
-
4657
- // Handle the 2FA hold web socket
4658
- // Accept an hold a web socket connection until the 2FA response is received.
4659
- function handle2faHoldWebSocket(ws, req) {
4660
- const domain = checkUserIpAddress(ws, req);
4661
- if (domain == null) { return; }
4662
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { ws.close(); return; } // Push 2FA is disabled
4663
- if (typeof req.query.c !== 'string') { ws.close(); return; }
4664
- const cookie = parent.decodeCookie(req.query.c, null, 1);
4665
- if ((cookie == null) || (cookie.d != domain.id)) { ws.close(); return; }
4666
- var user = obj.users[cookie.u];
4667
- if ((user == null) || (typeof user.otpdev != 'string')) { ws.close(); return; }
4668
- ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
4669
-
4670
- // 2FA event subscription
4671
- obj.parent.AddEventDispatch(['2fadev-' + cookie.s], ws);
4672
- ws.cookie = cookie;
4673
- ws.HandleEvent = function (source, event, ids, id) {
4674
- obj.parent.RemoveAllEventDispatch(this);
4675
- if ((event.approved === true) && (event.userid == this.cookie.u)) {
4676
- // Create a login cookie
4677
- const loginCookie = obj.parent.encodeCookie({ a: 'pushAuth', u: event.userid, d: event.domain }, obj.parent.loginCookieEncryptionKey);
4678
- try { ws.send(JSON.stringify({ approved: true, token: loginCookie })); } catch (ex) { }
4679
- } else {
4680
- // Reject the login
4681
- try { ws.send(JSON.stringify({ approved: false })); } catch (ex) { }
4682
- }
4683
- }
4684
-
4685
- // We do not accept any data on this connection.
4686
- ws.on('message', function (data) { this.close(); });
4687
-
4688
- // If error, do nothing.
4689
- ws.on('error', function (err) { });
4690
-
4691
- // If closed, unsubscribe
4692
- ws.on('close', function (req) { obj.parent.RemoveAllEventDispatch(this); });
4693
-
4694
- // Perform push notification to device
4695
- try {
4696
- const deviceCookie = parent.encodeCookie({ a: 'checkAuth', c: cookie.c, u: cookie.u, n: cookie.n, s: cookie.s });
4697
- var code = Buffer.from(cookie.c, 'base64').toString();
4698
- var payload = { notification: { title: (domain.title ? domain.title : 'MeshCentral'), body: "Authentication - " + code }, data: { url: '2fa://auth?code=' + cookie.c + '&c=' + deviceCookie } };
4699
- var options = { priority: 'High', timeToLive: 60 }; // TTL: 1 minute
4700
- parent.firebase.sendToDevice(user.otpdev, payload, options, function (id, err, errdesc) {
4701
- if (err == null) {
4702
- try { ws.send(JSON.stringify({ sent: true, code: code })); } catch (ex) { }
4703
- } else {
4704
- try { ws.send(JSON.stringify({ sent: false })); } catch (ex) { }
4705
- }
4706
- });
4707
- } catch (ex) { console.log(ex); }
4708
- }
4709
-
4710
- // Get the total size of all files in a folder and all sub-folders. (TODO: try to make all async version)
4711
- function readTotalFileSize(path) {
4712
- var r = 0, dir;
4713
- try { dir = obj.fs.readdirSync(path); } catch (e) { return 0; }
4714
- for (var i in dir) {
4715
- var stat = obj.fs.statSync(path + '/' + dir[i]);
4716
- if ((stat.mode & 0x004000) == 0) { r += stat.size; } else { r += readTotalFileSize(path + '/' + dir[i]); }
4717
- }
4718
- return r;
4719
- }
4720
-
4721
- // Delete a folder and all sub items. (TODO: try to make all async version)
4722
- function deleteFolderRec(path) {
4723
- if (obj.fs.existsSync(path) == false) return;
4724
- try {
4725
- obj.fs.readdirSync(path).forEach(function (file, index) {
4726
- var pathx = path + '/' + file;
4727
- if (obj.fs.lstatSync(pathx).isDirectory()) { deleteFolderRec(pathx); } else { obj.fs.unlinkSync(pathx); }
4728
- });
4729
- obj.fs.rmdirSync(path);
4730
- } catch (ex) { }
4731
- }
4732
-
4733
- // Handle Intel AMT events
4734
- // To subscribe, add "http://server:port/amtevents.ashx" to Intel AMT subscriptions.
4735
- obj.handleAmtEventRequest = function (req, res) {
4736
- const domain = getDomain(req);
4737
- try {
4738
- if (req.headers.authorization) {
4739
- var authstr = req.headers.authorization;
4740
- if (authstr.substring(0, 7) == 'Digest ') {
4741
- var auth = obj.common.parseNameValueList(obj.common.quoteSplit(authstr.substring(7)));
4742
- if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) {
4743
-
4744
- // Read the data, we need to get the arg field
4745
- var eventData = '';
4746
- req.on('data', function (chunk) { eventData += chunk; });
4747
- req.on('end', function () {
4748
-
4749
- // Completed event read, let get the argument that must contain the nodeid
4750
- var i = eventData.indexOf('<m:arg xmlns:m="http://x.com">');
4751
- if (i > 0) {
4752
- var nodeid = eventData.substring(i + 30, i + 30 + 64);
4753
- if (nodeid.length == 64) {
4754
- var nodekey = 'node/' + domain.id + '/' + nodeid;
4755
-
4756
- // See if this node exists in the database
4757
- obj.db.Get(nodekey, function (err, nodes) {
4758
- if (nodes.length == 1) {
4759
- // Yes, the node exists, compute Intel AMT digest password
4760
- var node = nodes[0];
4761
- var amtpass = obj.crypto.createHash('sha384').update(auth.username.toLowerCase() + ':' + nodeid + ":" + obj.parent.dbconfig.amtWsEventSecret).digest('base64').substring(0, 12).split('/').join('x').split('\\').join('x');
4762
-
4763
- // Check the MD5 hash
4764
- if (auth.response === obj.common.ComputeDigesthash(auth.username, amtpass, auth.realm, 'POST', auth.uri, auth.qop, auth.nonce, auth.nc, auth.cnonce)) {
4765
-
4766
- // This is an authenticated Intel AMT event, update the host address
4767
- var amthost = req.clientIp;
4768
- if (amthost.substring(0, 7) === '::ffff:') { amthost = amthost.substring(7); }
4769
- if (node.host != amthost) {
4770
- // Get the mesh for this device
4771
- var mesh = obj.meshes[node.meshid];
4772
- if (mesh) {
4773
- // Update the database
4774
- var oldname = node.host;
4775
- node.host = amthost;
4776
- obj.db.Set(obj.cleanDevice(node));
4777
-
4778
- // Event the node change
4779
- var event = { etype: 'node', action: 'changenode', nodeid: node._id, domain: domain.id, msg: 'Intel(R) AMT host change ' + node.name + ' from group ' + mesh.name + ': ' + oldname + ' to ' + amthost };
4780
-
4781
- // Remove the Intel AMT password before eventing this.
4782
- event.node = node;
4783
- if (event.node.intelamt && event.node.intelamt.pass) {
4784
- event.node = Object.assign({}, event.node); // Shallow clone
4785
- event.node.intelamt = Object.assign({}, event.node.intelamt); // Shallow clone
4786
- delete event.node.intelamt.pass;
4787
- }
4788
-
4789
- 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.
4790
- obj.parent.DispatchEvent(['*', node.meshid], obj, event);
4791
- }
4792
- }
4793
-
4794
- if (parent.amtEventHandler) { parent.amtEventHandler.handleAmtEvent(eventData, nodeid, amthost); }
4795
- //res.send('OK');
4796
-
4797
- return;
4798
- }
4799
- }
4800
- });
4801
- }
4802
- }
4803
- });
4804
- }
4805
- }
4806
- }
4807
- } catch (e) { console.log(e); }
4808
-
4809
- // Send authentication response
4810
- obj.crypto.randomBytes(48, function (err, buf) {
4811
- var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(nonce).digest('hex');
4812
- res.set({ 'WWW-Authenticate': 'Digest realm="' + obj.httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' });
4813
- res.sendStatus(401);
4814
- });
4815
- };
4816
-
4817
- // Handle a server backup request
4818
- function handleBackupRequest(req, res) {
4819
- const domain = checkUserIpAddress(req, res);
4820
- if (domain == null) { return; }
4821
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4822
- if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4823
- if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver.backup !== true))) { res.sendStatus(401); return; }
4824
-
4825
- var user = obj.users[req.session.userid];
4826
- if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights
4827
-
4828
- // Require modules
4829
- const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method to maximum.
4830
-
4831
- // Good practice to catch this error explicitly
4832
- archive.on('error', function (err) { throw err; });
4833
-
4834
- // Set the archive name
4835
- res.attachment((domain.title ? domain.title : 'MeshCentral') + '-Backup-' + new Date().toLocaleDateString().replace('/', '-').replace('/', '-') + '.zip');
4836
-
4837
- // Pipe archive data to the file
4838
- archive.pipe(res);
4839
-
4840
- // Append files from a glob pattern
4841
- archive.directory(obj.parent.datapath, false);
4842
-
4843
- // Finalize the archive (ie we are done appending files but streams have to finish yet)
4844
- archive.finalize();
4845
- }
4846
-
4847
- // Handle a server restore request
4848
- function handleRestoreRequest(req, res) {
4849
- const domain = checkUserIpAddress(req, res);
4850
- if (domain == null) { return; }
4851
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4852
- if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver.restore !== true))) { res.sendStatus(401); return; }
4853
-
4854
- var authUserid = null;
4855
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
4856
- const multiparty = require('multiparty');
4857
- const form = new multiparty.Form();
4858
- form.parse(req, function (err, fields, files) {
4859
- // If an authentication cookie is embedded in the form, use that.
4860
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
4861
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
4862
- if ((loginCookie != null) && (loginCookie.ip != null) && checkCookieIp(loginCookie.ip, req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
4863
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
4864
- }
4865
- if (authUserid == null) { res.sendStatus(401); return; }
4866
-
4867
- // Get the user
4868
- const user = obj.users[req.session.userid];
4869
- if ((user == null) || ((user.siteadmin & 4) == 0)) { res.sendStatus(401); return; } // Check if we have server restore rights
4870
-
4871
- res.set('Content-Type', 'text/html');
4872
- res.end('<html><body>Server must be restarted, <a href="' + domain.url + '">click here to login</a>.</body></html>');
4873
- parent.Stop(files.datafile[0].path);
4874
- });
4875
- }
4876
-
4877
- // Handle a request to download a mesh agent
4878
- obj.handleMeshAgentRequest = function (req, res) {
4879
- var domain = getDomain(req, res);
4880
- if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); try { res.sendStatus(404); } catch (ex) { } return; }
4881
-
4882
- // If required, check if this user has rights to do this
4883
- if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true)) && (req.session.userid == null)) { res.sendStatus(401); return; }
4884
-
4885
- if ((req.query.meshinstall != null) && (req.query.id != null)) {
4886
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { try { res.sendStatus(404); } catch (ex) { } return; } // Check 3FA URL key
4887
-
4888
- // Send meshagent with included self installer for a specific platform back
4889
- // Start by getting the .msh for this request
4890
- var meshsettings = getMshFromRequest(req, res, domain);
4891
- if (meshsettings == null) { try { res.sendStatus(401); } catch (ex) { } return; }
4892
-
4893
- // Get the interactive install script, this only works for non-Windows agents
4894
- var agentid = parseInt(req.query.meshinstall);
4895
- var argentInfo = obj.parent.meshAgentBinaries[agentid];
4896
- if (domain.meshAgentBinaries && domain.meshAgentBinaries[agentid]) { argentInfo = domain.meshAgentBinaries[agentid]; }
4897
- var scriptInfo = obj.parent.meshAgentInstallScripts[6];
4898
- if ((argentInfo == null) || (scriptInfo == null) || (argentInfo.platform == 'win32')) { try { res.sendStatus(404); } catch (ex) { } return; }
4899
-
4900
- // Change the .msh file into JSON format and merge it into the install script
4901
- var tokens, msh = {}, meshsettingslines = meshsettings.split('\r').join('').split('\n');
4902
- for (var i in meshsettingslines) { tokens = meshsettingslines[i].split('='); if (tokens.length == 2) { msh[tokens[0]] = tokens[1]; } }
4903
- var js = scriptInfo.data.replace('var msh = {};', 'var msh = ' + JSON.stringify(msh) + ';');
4904
-
4905
- // Get the agent filename
4906
- var meshagentFilename = 'meshagent';
4907
- if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) { meshagentFilename = domain.agentcustomization.filename; }
4908
-
4909
- setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename, null, 'meshagent');
4910
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4911
- res.statusCode = 200;
4912
- obj.parent.exeHandler.streamExeWithJavaScript({ platform: argentInfo.platform, sourceFileName: argentInfo.path, destinationStream: res, js: Buffer.from(js, 'utf8'), peinfo: argentInfo.pe });
4913
- } else if (req.query.id != null) {
4914
- // Send a specific mesh agent back
4915
- var argentInfo = obj.parent.meshAgentBinaries[req.query.id];
4916
- if (domain.meshAgentBinaries && domain.meshAgentBinaries[req.query.id]) { argentInfo = domain.meshAgentBinaries[req.query.id]; }
4917
- if (argentInfo == null) { try { res.sendStatus(404); } catch (ex) { } return; }
4918
-
4919
- // Download PDB debug files, only allowed for administrator or accounts with agent dump access
4920
- if (req.query.pdb == 1) {
4921
- if ((req.session == null) || (req.session.userid == null)) { try { res.sendStatus(404); } catch (ex) { } return; }
4922
- var user = obj.users[req.session.userid];
4923
- if (user == null) { try { res.sendStatus(404); } catch (ex) { } return; }
4924
- if ((user != null) && ((user.siteadmin == 0xFFFFFFFF) || ((Array.isArray(obj.parent.config.settings.agentcoredumpusers)) && (obj.parent.config.settings.agentcoredumpusers.indexOf(user._id) >= 0)))) {
4925
- if (argentInfo.id == 3) {
4926
- setContentDispositionHeader(res, 'application/octet-stream', 'MeshService.pdb', null, 'MeshService.pdb');
4927
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4928
- try { res.sendFile(argentInfo.path.split('MeshService-signed.exe').join('MeshService.pdb')); } catch (ex) { }
4929
- return;
4930
- }
4931
- if (argentInfo.id == 4) {
4932
- setContentDispositionHeader(res, 'application/octet-stream', 'MeshService64.pdb', null, 'MeshService64.pdb');
4933
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4934
- try { res.sendFile(argentInfo.path.split('MeshService64-signed.exe').join('MeshService64.pdb')); } catch (ex) { }
4935
- return;
4936
- }
4937
- }
4938
- try { res.sendStatus(404); } catch (ex) { }
4939
- return;
4940
- }
4941
-
4942
- if ((req.query.meshid == null) || (argentInfo.platform != 'win32')) {
4943
- // Get the agent filename
4944
- var meshagentFilename = argentInfo.rname;
4945
- if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) { meshagentFilename = domain.agentcustomization.filename; }
4946
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4947
- if (req.query.zip == 1) { if (argentInfo.zdata != null) { setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename + '.zip', null, 'meshagent.zip'); res.send(argentInfo.zdata); } else { try { res.sendStatus(404); } catch (ex) { } } return; } // Send compressed agent
4948
- setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename, null, 'meshagent');
4949
- if (argentInfo.data == null) { res.sendFile(argentInfo.path); } else { res.send(argentInfo.data); }
4950
- return;
4951
- } else {
4952
- // Check if the meshid is a time limited, encrypted cookie
4953
- var meshcookie = obj.parent.decodeCookie(req.query.meshid, obj.parent.invitationLinkEncryptionKey);
4954
- if ((meshcookie != null) && (meshcookie.m != null)) { req.query.meshid = meshcookie.m; }
4955
-
4956
- // We are going to embed the .msh file into the Windows executable (signed or not).
4957
- // First, fetch the mesh object to build the .msh file
4958
- var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.meshid];
4959
- if (mesh == null) { try { res.sendStatus(401); } catch (ex) { } return; }
4960
-
4961
- // If required, check if this user has rights to do this
4962
- if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true))) {
4963
- if ((domain.id != mesh.domain) || ((obj.GetMeshRights(req.session.userid, mesh) & 1) == 0)) { try { res.sendStatus(401); } catch (ex) { } return; }
4964
- }
4965
-
4966
- var meshidhex = Buffer.from(req.query.meshid.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4967
- var serveridhex = Buffer.from(obj.agentCertificateHashBase64.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4968
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port if specified
4969
- if (obj.args.agentport != null) { httpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
4970
- if (obj.args.agentaliasport != null) { httpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
4971
-
4972
- // Prepare a mesh agent file name using the device group name.
4973
- var meshfilename = mesh.name
4974
- meshfilename = meshfilename.split('\\').join('').split('/').join('').split(':').join('').split('*').join('').split('?').join('').split('"').join('').split('<').join('').split('>').join('').split('|').join('').split(' ').join('').split('\'').join('');
4975
- if (argentInfo.rname.endsWith('.exe')) { meshfilename = argentInfo.rname.substring(0, argentInfo.rname.length - 4) + '-' + meshfilename + '.exe'; } else { meshfilename = argentInfo.rname + '-' + meshfilename; }
4976
-
4977
- // Customize the mesh agent file name
4978
- if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) {
4979
- meshfilename = meshfilename.split('meshagent').join(domain.agentcustomization.filename).split('MeshAgent').join(domain.agentcustomization.filename);
4980
- }
4981
-
4982
- // Get the agent connection server name
4983
- var serverName = obj.getWebServerName(domain);
4984
- if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
4985
-
4986
- // Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
4987
- var xdomain = (domain.dns == null) ? domain.id : '';
4988
- if (xdomain != '') xdomain += '/';
4989
- var meshsettings = '\r\nMeshName=' + mesh.name + '\r\nMeshType=' + mesh.mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n';
4990
- if (obj.args.lanonly != true) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n'; } else {
4991
- meshsettings += 'MeshServer=local\r\n';
4992
- if ((obj.args.localdiscovery != null) && (typeof obj.args.localdiscovery.key == 'string') && (obj.args.localdiscovery.key.length > 0)) { meshsettings += 'DiscoveryKey=' + obj.args.localdiscovery.key + '\r\n'; }
4993
- }
4994
- if ((req.query.tag != null) && (typeof req.query.tag == 'string') && (obj.common.isAlphaNumeric(req.query.tag) == true)) { meshsettings += 'Tag=' + req.query.tag + '\r\n'; }
4995
- if ((req.query.installflags != null) && (req.query.installflags != 0) && (parseInt(req.query.installflags) == req.query.installflags)) { meshsettings += 'InstallFlags=' + parseInt(req.query.installflags) + '\r\n'; }
4996
- if (req.query.id == '10006') { // Assistant settings and customizations
4997
- if ((req.query.ac != null)) { meshsettings += 'AutoConnect=' + req.query.ac + '\r\n'; } // Set MeshCentral Assistant flags if needed. 0x01 = Always Connected, 0x02 = Not System Tray
4998
- if (obj.args.assistantconfig) { for (var i in obj.args.assistantconfig) { meshsettings += obj.args.assistantconfig[i] + '\r\n'; } }
4999
- if (domain.assistantconfig) { for (var i in domain.assistantconfig) { meshsettings += domain.assistantconfig[i] + '\r\n'; } }
This file is too large to show in full.
webserver-old.js
deleted
-8205
@@ -1,8205 +0,0 @@
1
-/**
2
-* @description MeshCentral web server
3
-* @author Ylian Saint-Hilaire
4
-* @copyright Intel Corporation 2018-2022
5
-* @license Apache-2.0
6
-* @version v0.0.1
7
-*/
8
-
9
-/*jslint node: true */
10
-/*jshint node: true */
11
-/*jshint strict:false */
12
-/*jshint -W097 */
13
-/*jshint esversion: 6 */
14
-'use strict';
15
-
16
-// SerialTunnel object is used to embed TLS within another connection.
17
-function SerialTunnel(options) {
18
- var obj = new require('stream').Duplex(options);
19
- obj.forwardwrite = null;
20
- obj.updateBuffer = function (chunk) { this.push(chunk); };
21
- 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
22
- obj._read = function (size) { }; // Push nothing, anything to read should be pushed from updateBuffer()
23
- return obj;
24
-}
25
-
26
-// ExpressJS login sample
27
-// https://github.com/expressjs/express/blob/master/examples/auth/index.js
28
-
29
-// Polyfill startsWith/endsWith for older NodeJS
30
-if (!String.prototype.startsWith) { String.prototype.startsWith = function (searchString, position) { position = position || 0; return this.substr(position, searchString.length) === searchString; }; }
31
-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; }; }
32
-
33
-// Construct a HTTP server object
34
-module.exports.CreateWebServer = function (parent, db, args, certificates, doneFunc) {
35
- var obj = {}, i = 0;
36
-
37
- // Modules
38
- obj.fs = require('fs');
39
- obj.net = require('net');
40
- obj.tls = require('tls');
41
- obj.path = require('path');
42
- obj.bodyParser = require('body-parser');
43
- obj.session = require('cookie-session');
44
- obj.exphbs = require('express-handlebars');
45
- obj.crypto = require('crypto');
46
- obj.common = require('./common.js');
47
- obj.express = require('express');
48
- obj.meshAgentHandler = require('./meshagent.js');
49
- obj.meshRelayHandler = require('./meshrelay.js');
50
- obj.meshDeviceFileHandler = require('./meshdevicefile.js');
51
- obj.meshDesktopMultiplexHandler = require('./meshdesktopmultiplex.js');
52
- obj.meshIderHandler = require('./amt/amt-ider.js');
53
- obj.meshUserHandler = require('./meshuser.js');
54
- obj.interceptor = require('./interceptor');
55
- obj.uaparser = require('./ua-parser');
56
- const constants = (obj.crypto.constants ? obj.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
57
-
58
- // Setup WebAuthn / FIDO2
59
- obj.webauthn = require('./webauthn.js').CreateWebAuthnModule();
60
-
61
- // Variables
62
- obj.args = args;
63
- obj.parent = parent;
64
- obj.filespath = parent.filespath;
65
- obj.db = db;
66
- obj.app = obj.express();
67
- if (obj.args.agentport) { obj.agentapp = obj.express(); }
68
- if (args.compression !== false) { obj.app.use(require('compression')()); }
69
- obj.app.disable('x-powered-by');
70
- obj.tlsServer = null;
71
- obj.tcpServer = null;
72
- obj.certificates = certificates;
73
- obj.users = {}; // UserID --> User
74
- obj.meshes = {}; // MeshID --> Mesh (also called device group)
75
- obj.userGroups = {}; // UGrpID --> User Group
76
- obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
77
- obj.agentAllowedIp = args.agentallowedip; // List of allowed IP addresses for agents
78
- obj.agentBlockedIp = args.agentblockedip; // List of blocked IP addresses for agents
79
- obj.tlsSniCredentials = null;
80
- obj.dnsDomains = {};
81
- obj.relaySessionCount = 0;
82
- obj.relaySessionErrorCount = 0;
83
- obj.blockedUsers = 0;
84
- obj.blockedAgents = 0;
85
- obj.renderPages = null;
86
- obj.renderLanguages = [];
87
- obj.destroyedSessions = {};
88
-
89
- // Mesh Rights
90
- const MESHRIGHT_EDITMESH = 0x00000001;
91
- const MESHRIGHT_MANAGEUSERS = 0x00000002;
92
- const MESHRIGHT_MANAGECOMPUTERS = 0x00000004;
93
- const MESHRIGHT_REMOTECONTROL = 0x00000008;
94
- const MESHRIGHT_AGENTCONSOLE = 0x00000010;
95
- const MESHRIGHT_SERVERFILES = 0x00000020;
96
- const MESHRIGHT_WAKEDEVICE = 0x00000040;
97
- const MESHRIGHT_SETNOTES = 0x00000080;
98
- const MESHRIGHT_REMOTEVIEWONLY = 0x00000100;
99
- const MESHRIGHT_NOTERMINAL = 0x00000200;
100
- const MESHRIGHT_NOFILES = 0x00000400;
101
- const MESHRIGHT_NOAMT = 0x00000800;
102
- const MESHRIGHT_DESKLIMITEDINPUT = 0x00001000;
103
- const MESHRIGHT_LIMITEVENTS = 0x00002000;
104
- const MESHRIGHT_CHATNOTIFY = 0x00004000;
105
- const MESHRIGHT_UNINSTALL = 0x00008000;
106
- const MESHRIGHT_NODESKTOP = 0x00010000;
107
- const MESHRIGHT_REMOTECOMMAND = 0x00020000;
108
- const MESHRIGHT_RESETOFF = 0x00040000;
109
- const MESHRIGHT_GUESTSHARING = 0x00080000;
110
- const MESHRIGHT_ADMIN = 0xFFFFFFFF;
111
-
112
- // Site rights
113
- const SITERIGHT_SERVERBACKUP = 0x00000001;
114
- const SITERIGHT_MANAGEUSERS = 0x00000002;
115
- const SITERIGHT_SERVERRESTORE = 0x00000004;
116
- const SITERIGHT_FILEACCESS = 0x00000008;
117
- const SITERIGHT_SERVERUPDATE = 0x00000010;
118
- const SITERIGHT_LOCKED = 0x00000020;
119
- const SITERIGHT_NONEWGROUPS = 0x00000040;
120
- const SITERIGHT_NOMESHCMD = 0x00000080;
121
- const SITERIGHT_USERGROUPS = 0x00000100;
122
- const SITERIGHT_RECORDINGS = 0x00000200;
123
- const SITERIGHT_LOCKSETTINGS = 0x00000400;
124
- const SITERIGHT_ALLEVENTS = 0x00000800;
125
- const SITERIGHT_NONEWDEVICES = 0x00001000;
126
- const SITERIGHT_ADMIN = 0xFFFFFFFF;
127
-
128
- // Setup SSPI authentication if needed
129
- if ((obj.parent.platform == 'win32') && (obj.args.nousers != true) && (obj.parent.config != null) && (obj.parent.config.domains != null)) {
130
- for (i in obj.parent.config.domains) { if (obj.parent.config.domains[i].auth == 'sspi') { var nodeSSPI = require('node-sspi'); obj.parent.config.domains[i].sspi = new nodeSSPI({ retrieveGroups: false, offerBasic: false }); } }
131
- }
132
-
133
- // Perform hash on web certificate and agent certificate
134
- obj.webCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert);
135
- obj.webCertificateHashs = { '': obj.webCertificateHash };
136
- obj.webCertificateHashBase64 = Buffer.from(obj.webCertificateHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
137
- obj.webCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert);
138
- obj.webCertificateFullHashs = { '': obj.webCertificateFullHash };
139
- obj.webCertificateExpire = { '': Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.web.cert).validity.notAfter) };
140
- obj.agentCertificateHashHex = parent.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert);
141
- obj.agentCertificateHashBase64 = Buffer.from(obj.agentCertificateHashHex, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
142
- obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
143
- obj.defaultWebCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.webdefault.cert);
144
- obj.defaultWebCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.webdefault.cert);
145
-
146
- // Compute the hash of all of the web certificates for each domain
147
- for (var i in obj.parent.config.domains) {
148
- if (obj.parent.config.domains[i].certhash != null) {
149
- // If the web certificate hash is provided, use it.
150
- obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i] = Buffer.from(obj.parent.config.domains[i].certhash, 'hex').toString('binary');
151
- if (obj.parent.config.domains[i].certkeyhash != null) { obj.webCertificateHashs[i] = Buffer.from(obj.parent.config.domains[i].certkeyhash, 'hex').toString('binary'); }
152
- delete obj.webCertificateExpire[i]; // Expire time is not provided
153
- } else if ((obj.parent.config.domains[i].dns != null) && (obj.parent.config.domains[i].certs != null)) {
154
- // If the domain has a different DNS name, use a different certificate hash.
155
- // Hash the full certificate
156
- obj.webCertificateFullHashs[i] = parent.certificateOperations.getCertHashBinary(obj.parent.config.domains[i].certs.cert);
157
- obj.webCertificateExpire[i] = Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(obj.parent.config.domains[i].certs.cert).validity.notAfter);
158
- try {
159
- // Decode a RSA certificate and hash the public key.
160
- obj.webCertificateHashs[i] = parent.certificateOperations.getPublicKeyHashBinary(obj.parent.config.domains[i].certs.cert);
161
- } catch (ex) {
162
- // This may be a ECDSA certificate, hash the entire cert.
163
- obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i];
164
- }
165
- } else if ((obj.parent.config.domains[i].dns != null) && (obj.certificates.dns[i] != null)) {
166
- // If this domain has a DNS and a matching DNS cert, use it. This case works for wildcard certs.
167
- obj.webCertificateFullHashs[i] = parent.certificateOperations.getCertHashBinary(obj.certificates.dns[i].cert);
168
- obj.webCertificateHashs[i] = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.dns[i].cert);
169
- obj.webCertificateExpire[i] = Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.dns[i].cert).validity.notAfter);
170
- } else if (i != '') {
171
- // For any other domain, use the default cert.
172
- obj.webCertificateFullHashs[i] = obj.webCertificateFullHashs[''];
173
- obj.webCertificateHashs[i] = obj.webCertificateHashs[''];
174
- obj.webCertificateExpire[i] = obj.webCertificateExpire[''];
175
- }
176
- }
177
-
178
- // If we are running the legacy swarm server, compute the hash for that certificate
179
- if (parent.certificates.swarmserver != null) {
180
- obj.swarmCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.swarmserver.cert))).getBytes();
181
- obj.swarmCertificateHash384 = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.swarmserver.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' });
182
- obj.swarmCertificateHash256 = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.swarmserver.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'binary' });
183
- }
184
-
185
- // Main lists
186
- obj.wsagents = {}; // NodeId --> Agent
187
- obj.wsagentsWithBadWebCerts = {}; // NodeId --> Agent
188
- obj.wsagentsDisconnections = {};
189
- obj.wsagentsDisconnectionsTimer = null;
190
- obj.duplicateAgentsLog = {};
191
- obj.wssessions = {}; // UserId --> Array Of Sessions
192
- obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
193
- obj.wsPeerSessions = {}; // ServerId --> Array Of "UserId + SessionRnd"
194
- obj.wsPeerSessions2 = {}; // "UserId + SessionRnd" --> ServerId
195
- obj.wsPeerSessions3 = {}; // ServerId --> UserId --> [ SessionId ]
196
- obj.sessionsCount = {}; // Merged session counters, used when doing server peering. UserId --> SessionCount
197
- obj.wsrelays = {}; // Id -> Relay
198
- obj.desktoprelays = {}; // Id -> Desktop Multiplexor Relay
199
- obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
200
- var tlsSessionStore = {}; // Store TLS session information for quick resume.
201
- var tlsSessionStoreCount = 0; // Number of cached TLS session information in store.
202
-
203
- // Setup randoms
204
- obj.crypto.randomBytes(48, function (err, buf) { obj.httpAuthRandom = buf; });
205
- obj.crypto.randomBytes(16, function (err, buf) { obj.httpAuthRealm = buf.toString('hex'); });
206
- obj.crypto.randomBytes(48, function (err, buf) { obj.relayRandom = buf; });
207
-
208
- // Get non-english web pages and emails
209
- getRenderList();
210
- getEmailLanguageList();
211
-
212
- // Setup DNS domain TLS SNI credentials
213
- {
214
- var dnscount = 0;
215
- obj.tlsSniCredentials = {};
216
- for (i in obj.certificates.dns) { if (obj.parent.config.domains[i].dns != null) { obj.dnsDomains[obj.parent.config.domains[i].dns.toLowerCase()] = obj.parent.config.domains[i]; obj.tlsSniCredentials[obj.parent.config.domains[i].dns] = obj.tls.createSecureContext(obj.certificates.dns[i]).context; dnscount++; } }
217
- if (dnscount > 0) { obj.tlsSniCredentials[''] = obj.tls.createSecureContext({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca }).context; } else { obj.tlsSniCredentials = null; }
218
- }
219
- function TlsSniCallback(name, cb) {
220
- var c = obj.tlsSniCredentials[name];
221
- if (c != null) {
222
- cb(null, c);
223
- } else {
224
- cb(null, obj.tlsSniCredentials['']);
225
- }
226
- }
227
-
228
- 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; }
229
- //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; }
230
- // Fetch all users from the database, keep this in memory
231
- obj.db.GetAllType('user', function (err, docs) {
232
- obj.common.unEscapeAllLinksFieldName(docs);
233
- var domainUserCount = {}, i = 0;
234
- for (i in parent.config.domains) { domainUserCount[i] = 0; }
235
- for (i in docs) { var u = obj.users[docs[i]._id] = docs[i]; domainUserCount[u.domain]++; }
236
- for (i in parent.config.domains) {
237
- if ((parent.config.domains[i].share == null) && (domainUserCount[i] == 0)) {
238
- // If newaccounts is set to no new accounts, but no accounts exists, temporarly allow account creation.
239
- //if ((parent.config.domains[i].newaccounts === 0) || (parent.config.domains[i].newaccounts === false)) { parent.config.domains[i].newaccounts = 2; }
240
- console.log('Server ' + ((i == '') ? '' : (i + ' ')) + 'has no users, next new account will be site administrator.');
241
- }
242
- }
243
-
244
- // Fetch all device groups (meshes) from the database, keep this in memory
245
- // As we load things in memory, we will also be doing some cleaning up.
246
- // We will not save any clean up in the database right now, instead it will be saved next time there is a change.
247
- obj.db.GetAllType('mesh', function (err, docs) {
248
- obj.common.unEscapeAllLinksFieldName(docs);
249
- for (var i in docs) { obj.meshes[docs[i]._id] = docs[i]; } // Get all meshes, including deleted ones.
250
-
251
- // Fetch all user groups from the database, keep this in memory
252
- obj.db.GetAllType('ugrp', function (err, docs) {
253
- obj.common.unEscapeAllLinksFieldName(docs);
254
-
255
- // Perform user group link cleanup
256
- for (var i in docs) {
257
- const ugrp = docs[i];
258
- if (ugrp.links != null) {
259
- for (var j in ugrp.links) {
260
- if (j.startsWith('user/') && (obj.users[j] == null)) { delete ugrp.links[j]; } // User group has a link to a user that does not exist
261
- else if (j.startsWith('mesh/') && ((obj.meshes[j] == null) || (obj.meshes[j].deleted != null))) { delete ugrp.links[j]; } // User has a link to a device group that does not exist
262
- }
263
- }
264
- obj.userGroups[docs[i]._id] = docs[i]; // Get all user groups
265
- }
266
-
267
- // Perform device group link cleanup
268
- for (var i in obj.meshes) {
269
- const mesh = obj.meshes[i];
270
- if (mesh.links != null) {
271
- for (var j in mesh.links) {
272
- if (j.startsWith('ugrp/') && (obj.userGroups[j] == null)) { delete mesh.links[j]; } // Device group has a link to a user group that does not exist
273
- else if (j.startsWith('user/') && (obj.users[j] == null)) { delete mesh.links[j]; } // Device group has a link to a user that does not exist
274
- }
275
- }
276
- }
277
-
278
- // Perform user link cleanup
279
- for (var i in obj.users) {
280
- const user = obj.users[i];
281
- if (user.links != null) {
282
- for (var j in user.links) {
283
- if (j.startsWith('ugrp/') && (obj.userGroups[j] == null)) { delete user.links[j]; } // User has a link to a user group that does not exist
284
- else if (j.startsWith('mesh/') && ((obj.meshes[j] == null) || (obj.meshes[j].deleted != null))) { delete user.links[j]; } // User has a link to a device group that does not exist
285
- //else if (j.startsWith('node/') && (obj.nodes[j] == null)) { delete user.links[j]; } // TODO
286
- }
287
- //if (Object.keys(user.links).length == 0) { delete user.links; }
288
- }
289
- }
290
-
291
- // We loaded the users, device groups and user group state, start the server
292
- serverStart();
293
- });
294
- });
295
- });
296
-
297
- // Clean up a device, used before saving it in the database
298
- obj.cleanDevice = function (device) {
299
- // Check device links, if a link points to an unknown user, remove it.
300
- if (device.links != null) {
301
- for (var j in device.links) {
302
- if ((obj.users[j] == null) && (obj.userGroups[j] == null)) {
303
- delete device.links[j];
304
- if (Object.keys(device.links).length == 0) { delete device.links; }
305
- }
306
- }
307
- }
308
- return device;
309
- }
310
-
311
- // Return statistics about this web server
312
- obj.getStats = function () {
313
- return {
314
- users: Object.keys(obj.users).length,
315
- meshes: Object.keys(obj.meshes).length,
316
- dnsDomains: Object.keys(obj.dnsDomains).length,
317
- relaySessionCount: obj.relaySessionCount,
318
- relaySessionErrorCount: obj.relaySessionErrorCount,
319
- wsagents: Object.keys(obj.wsagents).length,
320
- wsagentsDisconnections: Object.keys(obj.wsagentsDisconnections).length,
321
- wsagentsDisconnectionsTimer: Object.keys(obj.wsagentsDisconnectionsTimer).length,
322
- wssessions: Object.keys(obj.wssessions).length,
323
- wssessions2: Object.keys(obj.wssessions2).length,
324
- wsPeerSessions: Object.keys(obj.wsPeerSessions).length,
325
- wsPeerSessions2: Object.keys(obj.wsPeerSessions2).length,
326
- wsPeerSessions3: Object.keys(obj.wsPeerSessions3).length,
327
- sessionsCount: Object.keys(obj.sessionsCount).length,
328
- wsrelays: Object.keys(obj.wsrelays).length,
329
- wsPeerRelays: Object.keys(obj.wsPeerRelays).length,
330
- tlsSessionStore: Object.keys(tlsSessionStore).length,
331
- blockedUsers: obj.blockedUsers,
332
- blockedAgents: obj.blockedAgents
333
- };
334
- }
335
-
336
- // Agent counters
337
- obj.agentStats = {
338
- createMeshAgentCount: 0,
339
- agentClose: 0,
340
- agentBinaryUpdate: 0,
341
- agentMeshCoreBinaryUpdate: 0,
342
- coreIsStableCount: 0,
343
- verifiedAgentConnectionCount: 0,
344
- clearingCoreCount: 0,
345
- updatingCoreCount: 0,
346
- recoveryCoreIsStableCount: 0,
347
- meshDoesNotExistCount: 0,
348
- invalidPkcsSignatureCount: 0,
349
- invalidRsaSignatureCount: 0,
350
- invalidJsonCount: 0,
351
- unknownAgentActionCount: 0,
352
- agentBadWebCertHashCount: 0,
353
- agentBadSignature1Count: 0,
354
- agentBadSignature2Count: 0,
355
- agentMaxSessionHoldCount: 0,
356
- invalidDomainMeshCount: 0,
357
- invalidMeshTypeCount: 0,
358
- invalidDomainMesh2Count: 0,
359
- invalidMeshType2Count: 0,
360
- duplicateAgentCount: 0,
361
- maxDomainDevicesReached: 0,
362
- agentInTrouble: 0,
363
- agentInBigTrouble: 0
364
- }
365
- obj.getAgentStats = function () { return obj.agentStats; }
366
-
367
- // Traffic counters
368
- obj.trafficStats = {
369
- httpRequestCount: 0,
370
- httpWebSocketCount: 0,
371
- httpIn: 0,
372
- httpOut: 0,
373
- relayCount: {},
374
- relayIn: {},
375
- relayOut: {},
376
- localRelayCount: {},
377
- localRelayIn: {},
378
- localRelayOut: {},
379
- AgentCtrlIn: 0,
380
- AgentCtrlOut: 0,
381
- LMSIn: 0,
382
- LMSOut: 0,
383
- CIRAIn: 0,
384
- CIRAOut: 0
385
- }
386
- obj.trafficStats.time = Date.now();
387
- obj.getTrafficStats = function () { return obj.trafficStats; }
388
- obj.getTrafficDelta = function (oldTraffic) { // Return the difference between the old and new data along with the delta time.
389
- const data = obj.common.Clone(obj.trafficStats);
390
- data.time = Date.now();
391
- const delta = calcDelta(oldTraffic ? oldTraffic : {}, data);
392
- if (oldTraffic && oldTraffic.time) { delta.delta = (data.time - oldTraffic.time); }
393
- delta.time = data.time;
394
- return { current: data, delta: delta }
395
- }
396
- function calcDelta(oldData, newData) { // Recursive function that computes the difference of all numbers
397
- const r = {};
398
- for (var i in newData) {
399
- if (typeof newData[i] == 'object') { r[i] = calcDelta(oldData[i] ? oldData[i] : {}, newData[i]); }
400
- if (typeof newData[i] == 'number') { if (typeof oldData[i] == 'number') { r[i] = (newData[i] - oldData[i]); } else { r[i] = newData[i]; } }
401
- }
402
- return r;
403
- }
404
-
405
- // Keep a record of the last agent issues.
406
- obj.getAgentIssues = function () { return obj.agentIssues; }
407
- obj.setAgentIssue = function (agent, issue) { obj.agentIssues.push([new Date().toLocaleString(), agent.remoteaddrport, issue]); while (obj.setAgentIssue.length > 50) { obj.agentIssues.shift(); } }
408
- obj.agentIssues = [];
409
-
410
- // Authenticate the user
411
- obj.authenticate = function (name, pass, domain, fn) {
412
- if ((typeof (name) != 'string') || (typeof (pass) != 'string') || (typeof (domain) != 'object')) { fn(new Error('invalid fields')); return; }
413
- if (name.startsWith('~t:')) {
414
- // Login token, try to fetch the token from the database
415
- obj.db.Get('logintoken-' + name, function (err, docs) {
416
- if (err != null) { fn(err); return; }
417
- if ((docs == null) || (docs.length != 1)) { fn(new Error('login token not found')); return; }
418
- const loginToken = docs[0];
419
- if ((loginToken.expire != 0) && (loginToken.expire < Date.now())) { fn(new Error('login token expired')); return; }
420
-
421
- // Default strong password hashing (pbkdf2 SHA384)
422
- require('./pass').hash(pass, loginToken.salt, function (err, hash, tag) {
423
- if (err) return fn(err);
424
- if (hash == loginToken.hash) {
425
- // Login username and password are valid.
426
- var user = obj.users[loginToken.userid];
427
- if (!user) { fn(new Error('cannot find user')); return; }
428
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
429
-
430
- // Succesful login token authentication
431
- var loginOptions = { tokenName: loginToken.name, tokenUser: loginToken.tokenUser };
432
- if (loginToken.expire != 0) { loginOptions.expire = loginToken.expire; }
433
- return fn(null, user._id, null, loginOptions);
434
- }
435
- fn(new Error('invalid password'));
436
- }, 0);
437
- });
438
- } else if (domain.auth == 'ldap') {
439
- if (domain.ldapoptions.url == 'test') {
440
- // Fake LDAP login
441
- var xxuser = domain.ldapoptions[name.toLowerCase()];
442
- if (xxuser == null) {
443
- fn(new Error('invalid password'));
444
- return;
445
- } else {
446
- var username = xxuser['displayName'];
447
- if (domain.ldapusername) { username = xxuser[domain.ldapusername]; }
448
- var shortname = null;
449
- if (domain.ldapuserbinarykey) {
450
- // Use a binary key as the userid
451
- if (xxuser[domain.ldapuserbinarykey]) { shortname = Buffer.from(xxuser[domain.ldapuserbinarykey], 'binary').toString('hex'); }
452
- } else if (domain.ldapuserkey) {
453
- // Use a string key as the userid
454
- if (xxuser[domain.ldapuserkey]) { shortname = xxuser[domain.ldapuserkey]; }
455
- } else {
456
- // Use the default key as the userid
457
- if (xxuser.objectSid) { shortname = Buffer.from(xxuser.objectSid, 'binary').toString('hex').toLowerCase(); }
458
- else if (xxuser.objectGUID) { shortname = Buffer.from(xxuser.objectGUID, 'binary').toString('hex').toLowerCase(); }
459
- else if (xxuser.name) { shortname = xxuser.name; }
460
- else if (xxuser.cn) { shortname = xxuser.cn; }
461
- }
462
- if (shortname == null) { fn(new Error('no user identifier')); return; }
463
- if (username == null) { username = shortname; }
464
- var userid = 'user/' + domain.id + '/' + shortname;
465
- var user = obj.users[userid];
466
- var email = null;
467
- if (domain.ldapuseremail) {
468
- email = xxuser[domain.ldapuseremail];
469
- } else if (xxuser.mail) { // use default
470
- email = xxuser.mail;
471
- }
472
- if ('[object Array]' == Object.prototype.toString.call(email)) {
473
- // mail may be multivalued in ldap in which case, answer is an array. Use the 1st value.
474
- email = email[0];
475
- }
476
- if (email) { email = email.toLowerCase(); } // it seems some code otherwhere also lowercase the emailaddress. be compatible.
477
-
478
- if (user == null) {
479
- // Create a new user
480
- var user = { type: 'user', _id: userid, name: username, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
481
- if (email) { user['email'] = email; user['emailVerified'] = true; }
482
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
483
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
484
- var usercount = 0;
485
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
486
- if (usercount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
487
-
488
- // Auto-join any user groups
489
- if (typeof domain.newaccountsusergroups == 'object') {
490
- for (var i in domain.newaccountsusergroups) {
491
- var ugrpid = domain.newaccountsusergroups[i];
492
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
493
- var ugroup = obj.userGroups[ugrpid];
494
- if (ugroup != null) {
495
- // Add group to the user
496
- if (user.links == null) { user.links = {}; }
497
- user.links[ugroup._id] = { rights: 1 };
498
-
499
- // Add user to the group
500
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
501
- db.Set(ugroup);
502
-
503
- // Notify user group change
504
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msgid: 71, msgArgs: [user.name, ugroup.name], msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
505
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
506
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
507
- }
508
- }
509
- }
510
-
511
- obj.users[user._id] = user;
512
- obj.db.SetUser(user);
513
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msgid: 128, msgArgs: [user.name], msg: 'Account created, name is ' + user.name, domain: domain.id };
514
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
515
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
516
- return fn(null, user._id);
517
- } else {
518
- // This is an existing user
519
- // If the display username has changes, update it.
520
- if (user.name != username) {
521
- user.name = username;
522
- obj.db.SetUser(user);
523
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msgid: 127, msgArgs: [user.name], msg: 'Changed account display name to ' + user.name, domain: domain.id };
524
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
525
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
526
- }
527
- // Check if user email has changed
528
- var emailreason = null;
529
- if (user.email && !email) { // email unset in ldap => unset
530
- delete user.email;
531
- delete user.emailVerified;
532
- emailreason = 'Unset email (no more email in LDAP)'
533
- } else if (user.email != email) { // update email
534
- user['email'] = email;
535
- user['emailVerified'] = true;
536
- emailreason = 'Set account email to ' + email + '. Sync with LDAP.';
537
- }
538
- if (emailreason) {
539
- obj.db.SetUser(user);
540
- var event = { etype: 'user', userid: userid, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: emailreason, domain: domain.id };
541
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
542
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
543
- }
544
- // If user is locker out, block here.
545
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
546
- return fn(null, user._id);
547
- }
548
- }
549
- } else {
550
- // LDAP login
551
- var LdapAuth = require('ldapauth-fork');
552
- var ldap = new LdapAuth(domain.ldapoptions);
553
- ldap.on('error', function (err) { console.log('ldap error: ', err); });
554
- ldap.authenticate(name, pass, function (err, xxuser) {
555
- try { ldap.close(); } catch (ex) { console.log(ex); } // Close the LDAP object
556
- if (err) { fn(new Error('invalid password')); return; }
557
- var shortname = null;
558
- var email = null;
559
- if (domain.ldapuseremail) {
560
- email = xxuser[domain.ldapuseremail];
561
- } else if (xxuser.mail) {
562
- email = xxuser.mail;
563
- }
564
- if ('[object Array]' == Object.prototype.toString.call(email)) {
565
- // mail may be multivalued in ldap in which case, answer would be an array. Use the 1st one.
566
- email = email[0];
567
- }
568
- if (email) { email = email.toLowerCase(); } // it seems some code otherwhere also lowercase the emailaddress. be compatible.
569
- var username = xxuser['displayName'];
570
- if (domain.ldapusername) { username = xxuser[domain.ldapusername]; }
571
- if (domain.ldapuserbinarykey) {
572
- // Use a binary key as the userid
573
- if (xxuser[domain.ldapuserbinarykey]) { shortname = Buffer.from(xxuser[domain.ldapuserbinarykey], 'binary').toString('hex').toLowerCase(); }
574
- } else if (domain.ldapuserkey) {
575
- // Use a string key as the userid
576
- if (xxuser[domain.ldapuserkey]) { shortname = xxuser[domain.ldapuserkey]; }
577
- } else {
578
- // Use the default key as the userid
579
- if (xxuser.objectSid) { shortname = Buffer.from(xxuser.objectSid, 'binary').toString('hex').toLowerCase(); }
580
- else if (xxuser.objectGUID) { shortname = Buffer.from(xxuser.objectGUID, 'binary').toString('hex').toLowerCase(); }
581
- else if (xxuser.name) { shortname = xxuser.name; }
582
- else if (xxuser.cn) { shortname = xxuser.cn; }
583
- }
584
- if (shortname == null) { fn(new Error('no user identifier')); return; }
585
- if (username == null) { username = shortname; }
586
- var userid = 'user/' + domain.id + '/' + shortname;
587
- var user = obj.users[userid];
588
-
589
- if (user == null) {
590
- // This user does not exist, create a new account.
591
- var user = { type: 'user', _id: userid, name: username, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
592
- if (email) { user['email'] = email; user['emailVerified'] = true; }
593
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
594
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
595
- var usercount = 0;
596
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
597
- if (usercount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
598
-
599
- // Auto-join any user groups
600
- if (typeof domain.newaccountsusergroups == 'object') {
601
- for (var i in domain.newaccountsusergroups) {
602
- var ugrpid = domain.newaccountsusergroups[i];
603
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
604
- var ugroup = obj.userGroups[ugrpid];
605
- if (ugroup != null) {
606
- // Add group to the user
607
- if (user.links == null) { user.links = {}; }
608
- user.links[ugroup._id] = { rights: 1 };
609
-
610
- // Add user to the group
611
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
612
- db.Set(ugroup);
613
-
614
- // Notify user group change
615
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msgid: 71, msgArgs: [user.name, ugroup.name], msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
616
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
617
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
618
- }
619
- }
620
- }
621
-
622
- obj.users[user._id] = user;
623
- obj.db.SetUser(user);
624
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msgid: 128, msgArgs: [user.name], msg: 'Account created, name is ' + user.name, domain: domain.id };
625
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
626
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
627
- return fn(null, user._id);
628
- } else {
629
- // This is an existing user
630
- // If the display username has changes, update it.
631
- if (user.name != username) {
632
- user.name = username;
633
- obj.db.SetUser(user);
634
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msgid: 127, msgArgs: [user.name], msg: 'Changed account display name to ' + user.name, domain: domain.id };
635
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
636
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
637
- }
638
- // Check if user email has changed
639
- var emailreason = null;
640
- if (user.email && !email) { // email unset in ldap => unset
641
- delete user.email;
642
- delete user.emailVerified;
643
- emailreason = 'Unset email (no more email in LDAP)'
644
- } else if (user.email != email) { // update email
645
- user['email'] = email;
646
- user['emailVerified'] = true;
647
- emailreason = 'Set account email to ' + email + '. Sync with LDAP.';
648
- }
649
- if (emailreason) {
650
- obj.db.SetUser(user);
651
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: emailreason, domain: domain.id };
652
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
653
- parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
654
- }
655
- // If user is locker out, block here.
656
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
657
- return fn(null, user._id);
658
- }
659
- });
660
- }
661
- } else {
662
- // Regular login
663
- var user = obj.users['user/' + domain.id + '/' + name.toLowerCase()];
664
- // Query the db for the given username
665
- if (!user) { fn(new Error('cannot find user')); return; }
666
- // Apply the same algorithm to the POSTed password, applying the hash against the pass / salt, if there is a match we found the user
667
- if (user.salt == null) {
668
- fn(new Error('invalid password'));
669
- } else {
670
- if (user.passtype != null) {
671
- // IIS default clear or weak password hashing (SHA-1)
672
- require('./pass').iishash(user.passtype, pass, user.salt, function (err, hash) {
673
- if (err) return fn(err);
674
- if (hash == user.hash) {
675
- // Update the password to the stronger format.
676
- require('./pass').hash(pass, function (err, salt, hash, tag) { if (err) throw err; user.salt = salt; user.hash = hash; delete user.passtype; obj.db.SetUser(user); }, 0);
677
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
678
- return fn(null, user._id);
679
- }
680
- fn(new Error('invalid password'), null, user.passhint);
681
- });
682
- } else {
683
- // Default strong password hashing (pbkdf2 SHA384)
684
- require('./pass').hash(pass, user.salt, function (err, hash, tag) {
685
- if (err) return fn(err);
686
- if (hash == user.hash) {
687
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
688
- return fn(null, user._id);
689
- }
690
- fn(new Error('invalid password'), null, user.passhint);
691
- }, 0);
692
- }
693
- }
694
- }
695
- };
696
-
697
- /*
698
- obj.restrict = function (req, res, next) {
699
- console.log('restrict', req.url);
700
- var domain = getDomain(req);
701
- if (req.session.userid) {
702
- next();
703
- } else {
704
- req.session.messageid = 111; // Access denied.
705
- res.redirect(domain.url + 'login');
706
- }
707
- };
708
- */
709
-
710
- // Check if the source IP address is in the IP list, return false if not.
711
- function checkIpAddressEx(req, res, ipList, closeIfThis, redirectUrl) {
712
- try {
713
- if (req.connection) {
714
- // HTTP(S) request
715
- if (req.clientIp) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(req.clientIp, ipList[i])) { if (closeIfThis === true) { if (typeof redirectUrl == 'string') { res.redirect(redirectUrl); } else { res.sendStatus(401); } } return true; } } }
716
- if (closeIfThis === false) { if (typeof redirectUrl == 'string') { res.redirect(redirectUrl); } else { res.sendStatus(401); } }
717
- } else {
718
- // WebSocket request
719
- if (res.clientIp) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(res.clientIp, ipList[i])) { if (closeIfThis === true) { try { req.close(); } catch (e) { } } return true; } } }
720
- if (closeIfThis === false) { try { req.close(); } catch (e) { } }
721
- }
722
- } catch (e) { console.log(e); } // Should never happen
723
- return false;
724
- }
725
-
726
- // Check if the source IP address is allowed, return domain if allowed
727
- // If there is a fail and null is returned, the request or connection is closed already.
728
- function checkUserIpAddress(req, res) {
729
- if ((parent.config.settings.userblockedip != null) && (checkIpAddressEx(req, res, parent.config.settings.userblockedip, true, parent.config.settings.ipblockeduserredirect) == true)) { obj.blockedUsers++; return null; }
730
- if ((parent.config.settings.userallowedip != null) && (checkIpAddressEx(req, res, parent.config.settings.userallowedip, false, parent.config.settings.ipblockeduserredirect) == false)) { obj.blockedUsers++; return null; }
731
- const domain = (req.url ? getDomain(req) : getDomain(res));
732
- if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); try { res.sendStatus(404); } catch (ex) { } return; }
733
- if ((domain.userblockedip != null) && (checkIpAddressEx(req, res, domain.userblockedip, true, domain.ipblockeduserredirect) == true)) { obj.blockedUsers++; return null; }
734
- if ((domain.userallowedip != null) && (checkIpAddressEx(req, res, domain.userallowedip, false, domain.ipblockeduserredirect) == false)) { obj.blockedUsers++; return null; }
735
- return domain;
736
- }
737
-
738
- // Check if the source IP address is allowed, return domain if allowed
739
- // If there is a fail and null is returned, the request or connection is closed already.
740
- function checkAgentIpAddress(req, res) {
741
- if ((parent.config.settings.agentblockedip != null) && (checkIpAddressEx(req, res, parent.config.settings.agentblockedip, null) == true)) { obj.blockedAgents++; return null; }
742
- if ((parent.config.settings.agentallowedip != null) && (checkIpAddressEx(req, res, parent.config.settings.agentallowedip, null) == false)) { obj.blockedAgents++; return null; }
743
- const domain = (req.url ? getDomain(req) : getDomain(res));
744
- if ((domain.agentblockedip != null) && (checkIpAddressEx(req, res, domain.agentblockedip, null) == true)) { obj.blockedAgents++; return null; }
745
- if ((domain.agentallowedip != null) && (checkIpAddressEx(req, res, domain.agentallowedip, null) == false)) { obj.blockedAgents++; return null; }
746
- return domain;
747
- }
748
-
749
- // Return the current domain of the request
750
- // Request or connection says open regardless of the response
751
- function getDomain(req) {
752
- if (req.xdomain != null) { return req.xdomain; } // Domain already set for this request, return it.
753
- if (req.headers.host != null) { var d = obj.dnsDomains[req.headers.host.split(':')[0].toLowerCase()]; if (d != null) return d; } // If this is a DNS name domain, return it here.
754
- var x = req.url.split('/');
755
- if (x.length < 2) return parent.config.domains[''];
756
- var y = parent.config.domains[x[1].toLowerCase()];
757
- if ((y != null) && (y.dns == null)) { return parent.config.domains[x[1].toLowerCase()]; }
758
- return parent.config.domains[''];
759
- }
760
-
761
- function handleLogoutRequest(req, res) {
762
- const domain = checkUserIpAddress(req, res);
763
- if (domain == null) { return; }
764
- if (domain.auth == 'sspi') { parent.debug('web', 'handleLogoutRequest: failed checks.'); res.sendStatus(404); return; }
765
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
766
-
767
- res.set({ 'Cache-Control': 'no-store' });
768
- // Destroy the user's session to log them out will be re-created next request
769
- var userid = req.session.userid;
770
- if (req.session.userid) {
771
- var user = obj.users[req.session.userid];
772
- if (user != null) { obj.parent.DispatchEvent(['*'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'logout', msgid: 2, msg: 'Account logout', domain: domain.id }); }
773
- if (req.session.x) { clearDestroyedSessions(); obj.destroyedSessions[req.session.userid + '/' + req.session.x] = Date.now(); } // Destroy this session
774
- }
775
- req.session = null;
776
- parent.debug('web', 'handleLogoutRequest: success.');
777
-
778
- // If this user was logged in using an authentication strategy and there is a logout URL, use it.
779
- if ((userid != null) && (domain.authstrategies != null)) {
780
- const u = userid.split('/')[2];
781
- if (u.startsWith('~twitter:') && (domain.authstrategies.twitter != null) && (typeof domain.authstrategies.twitter.logouturl == 'string')) { res.redirect(domain.authstrategies.twitter.logouturl); return; }
782
- if (u.startsWith('~google:') && (domain.authstrategies.google != null) && (typeof domain.authstrategies.google.logouturl == 'string')) { res.redirect(domain.authstrategies.google.logouturl); return; }
783
- if (u.startsWith('~github:') && (domain.authstrategies.github != null) && (typeof domain.authstrategies.github.logouturl == 'string')) { res.redirect(domain.authstrategies.github.logouturl); return; }
784
- if (u.startsWith('~reddit:') && (domain.authstrategies.reddit != null) && (typeof domain.authstrategies.reddit.logouturl == 'string')) { res.redirect(domain.authstrategies.reddit.logouturl); return; }
785
- if (u.startsWith('~azure:') && (domain.authstrategies.azure != null) && (typeof domain.authstrategies.azure.logouturl == 'string')) { res.redirect(domain.authstrategies.azure.logouturl); return; }
786
- if (u.startsWith('~oidc:') && (domain.authstrategies.oidc != null) && (typeof domain.authstrategies.oidc.logouturl == 'string')) { res.redirect(domain.authstrategies.oidc.logouturl); return; }
787
- if (u.startsWith('~jumpcloud:') && (domain.authstrategies.jumpcloud != null) && (typeof domain.authstrategies.jumpcloud.logouturl == 'string')) { res.redirect(domain.authstrategies.jumpcloud.logouturl); return; }
788
- if (u.startsWith('~saml:') && (domain.authstrategies.saml != null) && (typeof domain.authstrategies.saml.logouturl == 'string')) { res.redirect(domain.authstrategies.saml.logouturl); return; }
789
- if (u.startsWith('~intel:') && (domain.authstrategies.intel != null) && (typeof domain.authstrategies.intel.logouturl == 'string')) { res.redirect(domain.authstrategies.intel.logouturl); return; }
790
- }
791
-
792
- // This is the default logout redirect to the login page
793
- if (req.query.key != null) { res.redirect(domain.url + '?key=' + req.query.key); } else { res.redirect(domain.url); }
794
- }
795
-
796
- // Return an object with 2FA type if 2-step auth can be skipped
797
- function checkUserOneTimePasswordSkip(domain, user, req, loginOptions) {
798
- if (parent.config.settings.no2factorauth == true) return null;
799
-
800
- // If this login occured using a login token, no 2FA needed.
801
- if ((loginOptions != null) && (typeof loginOptions.tokenName === 'string')) { return { twoFactorType: 'tokenlogin' }; }
802
-
803
- // Check if we can skip 2nd factor auth because of the source IP address
804
- if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
805
- for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) { return { twoFactorType: 'ipaddr' }; } }
806
- }
807
-
808
- // Check if a 2nd factor cookie is present
809
- if (typeof req.headers.cookie == 'string') {
810
- const cookies = req.headers.cookie.split('; ');
811
- for (var i in cookies) {
812
- if (cookies[i].startsWith('twofactor=')) {
813
- var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
814
- if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { return { twoFactorType: 'cookie' }; }
815
- }
816
- }
817
- }
818
-
819
- return null;
820
- }
821
-
822
- // Return true if this user has 2-step auth active
823
- function checkUserOneTimePasswordRequired(domain, user, req, loginOptions) {
824
- // If this login occured using a login token, no 2FA needed.
825
- if ((loginOptions != null) && (typeof loginOptions.tokenName === 'string')) { return false; }
826
-
827
- // Check if we can skip 2nd factor auth because of the source IP address
828
- if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
829
- for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) return false; }
830
- }
831
-
832
- // Check if a 2nd factor cookie is present
833
- if (typeof req.headers.cookie == 'string') {
834
- const cookies = req.headers.cookie.split('; ');
835
- for (var i in cookies) {
836
- if (cookies[i].startsWith('twofactor=')) {
837
- var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
838
- if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { return false; }
839
- }
840
- }
841
- }
842
-
843
- // See if SMS 2FA is available
844
- var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
845
-
846
- // Check if a 2nd factor is present
847
- return ((parent.config.settings.no2factorauth !== true) && (sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
848
- }
849
-
850
- // Check the 2-step auth token
851
- function checkUserOneTimePassword(req, domain, user, token, hwtoken, func) {
852
- parent.debug('web', 'checkUserOneTimePassword()');
853
- const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (parent.config.settings.no2factorauth !== true));
854
- if (twoStepLoginSupported == false) { parent.debug('web', 'checkUserOneTimePassword: not supported.'); func(true); return; };
855
-
856
- // Check if we can use OTP tokens with email
857
- var otpemail = (domain.mailserver != null);
858
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
859
- var otpsms = (parent.smsserver != null);
860
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
861
-
862
- // Check 2FA login cookie
863
- if ((token != null) && (token.startsWith('cookie='))) {
864
- var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(token.substring(7)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
865
- if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { func(true, { twoFactorType: 'cookie' }); return; }
866
- }
867
-
868
- // Check email key
869
- if ((otpemail) && (user.otpekey != null) && (user.otpekey.d != null) && (user.otpekey.k === token)) {
870
- var deltaTime = (Date.now() - user.otpekey.d);
871
- if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the email token (10000 * 60 * 5).
872
- user.otpekey = {};
873
- obj.db.SetUser(user);
874
- parent.debug('web', 'checkUserOneTimePassword: success (email).');
875
- func(true, { twoFactorType: 'email' });
876
- return;
877
- }
878
- }
879
-
880
- // Check sms key
881
- if ((otpsms) && (user.phone != null) && (user.otpsms != null) && (user.otpsms.d != null) && (user.otpsms.k === token)) {
882
- var deltaTime = (Date.now() - user.otpsms.d);
883
- if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the SMS token (10000 * 60 * 5).
884
- delete user.otpsms;
885
- obj.db.SetUser(user);
886
- parent.debug('web', 'checkUserOneTimePassword: success (SMS).');
887
- func(true, { twoFactorType: 'sms' });
888
- return;
889
- }
890
- }
891
-
892
- // Check hardware key
893
- if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) {
894
- var authResponse = null;
895
- try { authResponse = JSON.parse(hwtoken); } catch (ex) { }
896
- if ((authResponse != null) && (authResponse.clientDataJSON)) {
897
- // Get all WebAuthn keys
898
- var webAuthnKeys = [];
899
- for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 3) { webAuthnKeys.push(user.otphkeys[i]); } }
900
- if (webAuthnKeys.length > 0) {
901
- // Decode authentication response
902
- var clientAssertionResponse = { response: {} };
903
- clientAssertionResponse.id = authResponse.id;
904
- clientAssertionResponse.rawId = Buffer.from(authResponse.id, 'base64');
905
- clientAssertionResponse.response.authenticatorData = Buffer.from(authResponse.authenticatorData, 'base64');
906
- clientAssertionResponse.response.clientDataJSON = Buffer.from(authResponse.clientDataJSON, 'base64');
907
- clientAssertionResponse.response.signature = Buffer.from(authResponse.signature, 'base64');
908
- clientAssertionResponse.response.userHandle = Buffer.from(authResponse.userHandle, 'base64');
909
-
910
- // Look for the key with clientAssertionResponse.id
911
- var webAuthnKey = null;
912
- for (var i = 0; i < webAuthnKeys.length; i++) { if (webAuthnKeys[i].keyId == clientAssertionResponse.id) { webAuthnKey = webAuthnKeys[i]; } }
913
-
914
- // If we found a valid key to use, let's validate the response
915
- if (webAuthnKey != null) {
916
- // Figure out the origin
917
- var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
918
- var origin = 'https://' + (domain.dns ? domain.dns : parent.certificates.CommonName);
919
- if (httpport != 443) { origin += ':' + httpport; }
920
-
921
- var assertionExpectations = {
922
- challenge: req.session.u2f,
923
- origin: origin,
924
- factor: 'either',
925
- fmt: 'fido-u2f',
926
- publicKey: webAuthnKey.publicKey,
927
- prevCounter: webAuthnKey.counter,
928
- userHandle: Buffer.from(user._id, 'binary').toString('base64')
929
- };
930
-
931
- var webauthnResponse = null;
932
- try { webauthnResponse = obj.webauthn.verifyAuthenticatorAssertionResponse(clientAssertionResponse.response, assertionExpectations); } catch (ex) { parent.debug('web', 'checkUserOneTimePassword: exception ' + ex); console.log(ex); }
933
- if ((webauthnResponse != null) && (webauthnResponse.verified === true)) {
934
- // Update the hardware key counter and accept the 2nd factor
935
- webAuthnKey.counter = webauthnResponse.counter;
936
- obj.db.SetUser(user);
937
- parent.debug('web', 'checkUserOneTimePassword: success (hardware).');
938
- func(true, { twoFactorType: 'fido' });
939
- } else {
940
- parent.debug('web', 'checkUserOneTimePassword: fail (hardware).');
941
- func(false);
942
- }
943
- return;
944
- }
945
- }
946
- }
947
- }
948
-
949
- // Check Google Authenticator
950
- const otplib = require('otplib')
951
- otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window
952
- if (user.otpsecret && (typeof (token) == 'string') && (token.length == 6) && (otplib.authenticator.check(token, user.otpsecret) == true)) {
953
- parent.debug('web', 'checkUserOneTimePassword: success (authenticator).');
954
- func(true, { twoFactorType: 'otp' });
955
- return;
956
- };
957
-
958
- // Check written down keys
959
- if ((user.otpkeys != null) && (user.otpkeys.keys != null) && (typeof (token) == 'string') && (token.length == 8)) {
960
- var tokenNumber = parseInt(token);
961
- for (var i = 0; i < user.otpkeys.keys.length; i++) {
962
- if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) {
963
- parent.debug('web', 'checkUserOneTimePassword: success (one-time).');
964
- user.otpkeys.keys[i].u = false; func(true, { twoFactorType: 'backup' }); return;
965
- }
966
- }
967
- }
968
-
969
- // Check OTP hardware key (Yubikey OTP)
970
- if ((domain.yubikey != null) && (domain.yubikey.id != null) && (domain.yubikey.secret != null) && (user.otphkeys != null) && (user.otphkeys.length > 0) && (typeof (token) == 'string') && (token.length == 44)) {
971
- var keyId = token.substring(0, 12);
972
-
973
- // Find a matching OTP key
974
- var match = false;
975
- for (var i = 0; i < user.otphkeys.length; i++) { if ((user.otphkeys[i].type === 2) && (user.otphkeys[i].keyid === keyId)) { match = true; } }
976
-
977
- // If we have a match, check the OTP
978
- if (match === true) {
979
- var yubikeyotp = require('yubikeyotp');
980
- var request = { otp: token, id: domain.yubikey.id, key: domain.yubikey.secret, timestamp: true }
981
- if (domain.yubikey.proxy) { request.requestParams = { proxy: domain.yubikey.proxy }; }
982
- yubikeyotp.verifyOTP(request, function (err, results) {
983
- if ((results != null) && (results.status == 'OK')) {
984
- parent.debug('web', 'checkUserOneTimePassword: success (Yubikey).');
985
- func(true, { twoFactorType: 'hwotp' });
986
- } else {
987
- parent.debug('web', 'checkUserOneTimePassword: fail (Yubikey).');
988
- func(false);
989
- }
990
- });
991
- return;
992
- }
993
- }
994
-
995
- parent.debug('web', 'checkUserOneTimePassword: fail (2).');
996
- func(false);
997
- }
998
-
999
- // Return a U2F hardware key challenge
1000
- function getHardwareKeyChallenge(req, domain, user, func) {
1001
- delete req.session.u2f;
1002
- if (user.otphkeys && (user.otphkeys.length > 0)) {
1003
- // Get all WebAuthn keys
1004
- var webAuthnKeys = [];
1005
- for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 3) { webAuthnKeys.push(user.otphkeys[i]); } }
1006
- if (webAuthnKeys.length > 0) {
1007
- // Generate a Webauthn challenge, this is really easy, no need to call any modules to do this.
1008
- var authnOptions = { type: 'webAuthn', keyIds: [], timeout: 60000, challenge: obj.crypto.randomBytes(64).toString('base64') };
1009
- for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[i].keyId); }
1010
- req.session.u2f = authnOptions.challenge;
1011
- parent.debug('web', 'getHardwareKeyChallenge: success');
1012
- func(JSON.stringify(authnOptions));
1013
- return;
1014
- }
1015
- }
1016
- parent.debug('web', 'getHardwareKeyChallenge: fail');
1017
- func('');
1018
- }
1019
-
1020
- // Redirect a root request to a different page
1021
- function handleRootRedirect(req, res, direct) {
1022
- const domain = checkUserIpAddress(req, res);
1023
- if (domain == null) { return; }
1024
- res.redirect(domain.rootredirect + getQueryPortion(req));
1025
- }
1026
-
1027
- function handleLoginRequest(req, res, direct) {
1028
- const domain = checkUserIpAddress(req, res);
1029
- if (domain == null) { return; }
1030
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1031
-
1032
- // Check if this is a banned ip address
1033
- if (obj.checkAllowLogin(req) == false) {
1034
- // Wait and redirect the user
1035
- setTimeout(function () {
1036
- req.session.messageid = 114; // IP address blocked, try again later.
1037
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1038
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
1039
- return;
1040
- }
1041
-
1042
- // Normally, use the body username/password. If this is a token, use the username/password in the session.
1043
- var xusername = req.body.username, xpassword = req.body.password;
1044
- if ((xusername == null) && (xpassword == null) && (req.body.token != null)) { xusername = req.session.tuser; xpassword = req.session.tpass; }
1045
-
1046
- // Authenticate the user
1047
- obj.authenticate(xusername, xpassword, domain, function (err, userid, passhint, loginOptions) {
1048
- if (userid) {
1049
- var user = obj.users[userid];
1050
-
1051
- // Check if we are in maintenance mode
1052
- if ((parent.config.settings.maintenancemode != null) && (user.siteadmin != 4294967295)) {
1053
- req.session.messageid = 115; // Server under maintenance
1054
- req.session.loginmode = 1;
1055
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1056
- return;
1057
- }
1058
-
1059
- var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null) && (user.email != null) && (user.emailVerified == true) && (user.otpekey != null));
1060
- var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
1061
- var push2fa = ((parent.firebase != null) && (user.otpdev != null));
1062
-
1063
- // Check if two factor can be skipped
1064
- const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
1065
-
1066
- // Check if this user has 2-step login active
1067
- if ((twoFactorSkip == null) && (req.session.loginmode != 6) && checkUserOneTimePasswordRequired(domain, user, req, loginOptions)) {
1068
- if ((req.body.hwtoken == '**timeout**')) {
1069
- delete req.session; // Clear the session
1070
- res.redirect(domain.url + getQueryPortion(req));
1071
- return;
1072
- }
1073
-
1074
- if ((req.body.hwtoken == '**email**') && email2fa) {
1075
- user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
1076
- obj.db.SetUser(user);
1077
- parent.debug('web', 'Sending 2FA email to: ' + user.email);
1078
- domain.mailserver.sendAccountLoginMail(domain, user.email, user.otpekey.k, obj.getLanguageCodes(req), req.query.key);
1079
- req.session.messageid = 2; // "Email sent" message
1080
- req.session.loginmode = 4;
1081
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1082
- return;
1083
- }
1084
-
1085
- if ((req.body.hwtoken == '**sms**') && sms2fa) {
1086
- // Cause a token to be sent to the user's phone number
1087
- user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
1088
- obj.db.SetUser(user);
1089
- parent.debug('web', 'Sending 2FA SMS to: ' + user.phone);
1090
- parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
1091
- // Ask for a login token & confirm sms was sent
1092
- req.session.messageid = 4; // "SMS sent" message
1093
- req.session.loginmode = 4;
1094
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1095
- return;
1096
- }
1097
-
1098
- // Handle device push notification 2FA request
1099
- // We create a browser cookie, send it back and when the browser connects it's web socket, it will trigger the push notification.
1100
- if ((req.body.hwtoken == '**push**') && push2fa && ((domain.passwordrequirements == null) || (domain.passwordrequirements.push2factor != false))) {
1101
- const logincodeb64 = Buffer.from(obj.common.zeroPad(getRandomSixDigitInteger(), 6)).toString('base64');
1102
- const sessioncode = obj.crypto.randomBytes(24).toString('base64');
1103
-
1104
- // Create a browser cookie so the browser can connect using websocket and wait for device accept/reject.
1105
- const browserCookie = parent.encodeCookie({ a: 'waitAuth', c: logincodeb64, u: user._id, n: user.otpdev, s: sessioncode, d: domain.id });
1106
-
1107
- // Get the HTTPS port
1108
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port if specified
1109
-
1110
- // Get the agent connection server name
1111
- var serverName = obj.getWebServerName(domain);
1112
- if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
1113
-
1114
- // Build the connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
1115
- var xdomain = (domain.dns == null) ? domain.id : '';
1116
- if (xdomain != '') xdomain += '/';
1117
- var url = 'wss://' + serverName + ':' + httpsPort + '/' + xdomain + '2fahold.ashx?c=' + browserCookie;
1118
-
1119
- // Request that the login page wait for device auth
1120
- req.session.messageid = 5; // "Sending notification..." message
1121
- req.session.passhint = url;
1122
- req.session.loginmode = 8;
1123
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1124
- return;
1125
- }
1126
-
1127
- checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result, authData) {
1128
- if (result == false) {
1129
- var randomWaitTime = 0;
1130
-
1131
- // Check if 2FA is allowed for this IP address
1132
- if (obj.checkAllow2Fa(req) == false) {
1133
- // Wait and redirect the user
1134
- setTimeout(function () {
1135
- req.session.messageid = 114; // IP address blocked, try again later.
1136
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1137
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
1138
- return;
1139
- }
1140
-
1141
- // 2-step auth is required, but the token is not present or not valid.
1142
- if ((req.body.token != null) || (req.body.hwtoken != null)) {
1143
- randomWaitTime = 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095); // This is a fail, wait a random time. 2 to 6 seconds.
1144
- req.session.messageid = 108; // Invalid token, try again.
1145
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr(req.clientIp) + ' port ' + req.port); }
1146
- parent.debug('web', 'handleLoginRequest: invalid 2FA token');
1147
- const ua = getUserAgentInfo(req);
1148
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { action: 'authfail', username: user.name, userid: user._id, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp, msgid: 108, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1149
- obj.setbad2Fa(req);
1150
- } else {
1151
- parent.debug('web', 'handleLoginRequest: 2FA token required');
1152
- }
1153
-
1154
- // Wait and redirect the user
1155
- setTimeout(function () {
1156
- req.session.loginmode = 4;
1157
- if ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) { req.session.temail = 1; }
1158
- if ((user.phone != null) && (parent.smsserver != null)) { req.session.tsms = 1; }
1159
- if ((user.otpdev != null) && (parent.firebase != null)) { req.session.tpush = 1; }
1160
- req.session.tuserid = userid;
1161
- req.session.tuser = xusername;
1162
- req.session.tpass = xpassword;
1163
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1164
- }, randomWaitTime);
1165
- } else {
1166
- // Check if we need to remember this device
1167
- if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
1168
- var maxCookieAge = domain.twofactorcookiedurationdays;
1169
- if (typeof maxCookieAge != 'number') { maxCookieAge = 30; }
1170
- const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, obj.parent.loginCookieEncryptionKey);
1171
- res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: ((parent.config.settings.cookieipcheck === false) ? 'none' : 'strict'), secure: true });
1172
- }
1173
-
1174
- // Check if email address needs to be confirmed
1175
- const emailcheck = ((domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
1176
- if (emailcheck && (user.emailVerified !== true)) {
1177
- parent.debug('web', 'Redirecting using ' + user.name + ' to email check login page');
1178
- req.session.messageid = 3; // "Email verification required" message
1179
- req.session.loginmode = 7;
1180
- req.session.passhint = user.email;
1181
- req.session.cuserid = userid;
1182
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1183
- return;
1184
- }
1185
-
1186
- // Login successful
1187
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1188
- parent.debug('web', 'handleLoginRequest: successful 2FA login');
1189
- if (authData != null) { if (loginOptions == null) { loginOptions = {}; } loginOptions.twoFactorType = authData.twoFactorType; }
1190
- completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
1191
- }
1192
- });
1193
- return;
1194
- }
1195
-
1196
- // Check if email address needs to be confirmed
1197
- const emailcheck = ((domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
1198
- if (emailcheck && (user.emailVerified !== true)) {
1199
- parent.debug('web', 'Redirecting using ' + user.name + ' to email check login page');
1200
- req.session.messageid = 3; // "Email verification required" message
1201
- req.session.loginmode = 7;
1202
- req.session.passhint = user.email;
1203
- req.session.cuserid = userid;
1204
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1205
- return;
1206
- }
1207
-
1208
- // Login successful
1209
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1210
- parent.debug('web', 'handleLoginRequest: successful login');
1211
- if (twoFactorSkip != null) { if (loginOptions == null) { loginOptions = {}; } loginOptions.twoFactorType = twoFactorSkip.twoFactorType; }
1212
- completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
1213
- } else {
1214
- // Login failed, log the error
1215
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1216
-
1217
- // Wait a random delay
1218
- setTimeout(function () {
1219
- // If the account is locked, display that.
1220
- if (typeof xusername == 'string') {
1221
- var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase();
1222
- if (err == 'locked') {
1223
- parent.debug('web', 'handleLoginRequest: login failed, locked account');
1224
- req.session.messageid = 110; // Account locked.
1225
- const ua = getUserAgentInfo(req);
1226
- obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + req.clientIp, msgid: 109, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1227
- obj.setbadLogin(req);
1228
- } else {
1229
- parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
1230
- req.session.messageid = 112; // Login failed, check username and password.
1231
- const ua = getUserAgentInfo(req);
1232
- obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + req.clientIp, msgid: 110, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1233
- obj.setbadLogin(req);
1234
- }
1235
- }
1236
-
1237
- // Clean up login mode and display password hint if present.
1238
- delete req.session.loginmode;
1239
- if ((passhint != null) && (passhint.length > 0)) {
1240
- req.session.passhint = passhint;
1241
- } else {
1242
- delete req.session.passhint;
1243
- }
1244
-
1245
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1246
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095)); // Wait for 2 to ~6 seconds.
1247
- }
1248
- });
1249
- }
1250
-
1251
- function completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions) {
1252
- // Check if we need to change the password
1253
- if ((typeof user.passchange == 'number') && ((user.passchange == -1) || ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.reset == 'number') && (user.passchange + (domain.passwordrequirements.reset * 86400) < Math.floor(Date.now() / 1000))))) {
1254
- // Request a password change
1255
- parent.debug('web', 'handleLoginRequest: login ok, password change requested');
1256
- req.session.loginmode = 6;
1257
- req.session.messageid = 113; // Password change requested.
1258
- req.session.resettokenuserid = userid;
1259
- req.session.resettokenusername = xusername;
1260
- req.session.resettokenpassword = xpassword;
1261
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1262
- return;
1263
- }
1264
-
1265
- // Save login time
1266
- user.pastlogin = user.login;
1267
- user.login = user.access = Math.floor(Date.now() / 1000);
1268
- obj.db.SetUser(user);
1269
-
1270
- // Notify account login
1271
- const targets = ['*', 'server-users', user._id];
1272
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1273
- const ua = getUserAgentInfo(req);
1274
- const loginEvent = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 107, msgArgs: [req.clientIp, ua.browserStr, ua.osStr], msg: 'Account login', domain: domain.id, ip: req.clientIp, userAgent: req.headers['user-agent'] };
1275
- if (loginOptions != null) {
1276
- if ((loginOptions.tokenName != null) && (loginOptions.tokenUser != null)) { loginEvent.tokenName = loginOptions.tokenName; loginEvent.tokenUser = loginOptions.tokenUser; } // If a login token was used, add it to the event.
1277
- if (loginOptions.twoFactorType != null) { loginEvent.twoFactorType = loginOptions.twoFactorType; }
1278
- }
1279
- obj.parent.DispatchEvent(targets, obj, loginEvent);
1280
-
1281
- // Regenerate session when signing in to prevent fixation
1282
- //req.session.regenerate(function () {
1283
- // Store the user's primary key in the session store to be retrieved, or in this case the entire user object
1284
- delete req.session.u2f;
1285
- delete req.session.loginmode;
1286
- delete req.session.tuserid;
1287
- delete req.session.tuser;
1288
- delete req.session.tpass;
1289
- delete req.session.temail;
1290
- delete req.session.tsms;
1291
- delete req.session.tpush;
1292
- delete req.session.messageid;
1293
- delete req.session.passhint;
1294
- delete req.session.cuserid;
1295
- delete req.session.expire;
1296
- delete req.session.currentNode;
1297
- req.session.userid = userid;
1298
- req.session.ip = req.clientIp;
1299
- setSessionRandom(req);
1300
-
1301
- // If a login token was used, add this information and expire time to the session.
1302
- if ((loginOptions != null) && (loginOptions.tokenName != null) && (loginOptions.tokenUser != null)) {
1303
- req.session.loginToken = loginOptions.tokenUser;
1304
- if (loginOptions.expire != null) { req.session.expire = loginOptions.expire; }
1305
- }
1306
-
1307
- if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
1308
- if (req.body.host) {
1309
- // TODO: This is a terrible search!!! FIX THIS.
1310
- /*
1311
- obj.db.GetAllType('node', function (err, docs) {
1312
- for (var i = 0; i < docs.length; i++) {
1313
- if (docs[i].name == req.body.host) {
1314
- req.session.currentNode = docs[i]._id;
1315
- break;
1316
- }
1317
- }
1318
- console.log("CurrentNode: " + req.session.currentNode);
1319
- // This redirect happens after finding node is completed
1320
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1321
- });
1322
- */
1323
- parent.debug('web', 'handleLoginRequest: login ok (1)');
1324
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } // Temporary
1325
- } else {
1326
- parent.debug('web', 'handleLoginRequest: login ok (2)');
1327
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1328
- }
1329
- //});
1330
- }
1331
-
1332
- function handleCreateAccountRequest(req, res, direct) {
1333
- const domain = checkUserIpAddress(req, res);
1334
- if (domain == null) { return; }
1335
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleCreateAccountRequest: failed checks.'); res.sendStatus(404); return; }
1336
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1337
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1338
-
1339
- // Check if we are in maintenance mode
1340
- if (parent.config.settings.maintenancemode != null) {
1341
- req.session.messageid = 115; // Server under maintenance
1342
- req.session.loginmode = 1;
1343
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1344
- return;
1345
- }
1346
-
1347
- // Always lowercase the email address
1348
- if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1349
-
1350
- // If the email is the username, set this here.
1351
- if (domain.usernameisemail) { req.body.username = req.body.email; }
1352
-
1353
- // Accounts that start with ~ are not allowed
1354
- if ((typeof req.body.username != 'string') || (req.body.username.length < 1) || (req.body.username[0] == '~')) {
1355
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (0)');
1356
- req.session.loginmode = 2;
1357
- req.session.messageid = 100; // Unable to create account.
1358
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1359
- return;
1360
- }
1361
-
1362
- // Count the number of users in this domain
1363
- var domainUserCount = 0;
1364
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { domainUserCount++; } }
1365
-
1366
- // Check if we are allowed to create new users using the login screen
1367
- if ((domain.newaccounts !== 1) && (domain.newaccounts !== true) && (domainUserCount > 0)) {
1368
- parent.debug('web', 'handleCreateAccountRequest: domainUserCount > 1.');
1369
- res.sendStatus(401);
1370
- return;
1371
- }
1372
-
1373
- // Check if this request is for an allows email domain
1374
- if ((domain.newaccountemaildomains != null) && Array.isArray(domain.newaccountemaildomains)) {
1375
- var i = -1;
1376
- if (typeof req.body.email == 'string') { i = req.body.email.indexOf('@'); }
1377
- if (i == -1) {
1378
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
1379
- req.session.loginmode = 2;
1380
- req.session.messageid = 100; // Unable to create account.
1381
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1382
- return;
1383
- }
1384
- var emailok = false, emaildomain = req.body.email.substring(i + 1).toLowerCase();
1385
- for (var i in domain.newaccountemaildomains) { if (emaildomain == domain.newaccountemaildomains[i].toLowerCase()) { emailok = true; } }
1386
- if (emailok == false) {
1387
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)');
1388
- req.session.loginmode = 2;
1389
- req.session.messageid = 100; // Unable to create account.
1390
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1391
- return;
1392
- }
1393
- }
1394
-
1395
- // Check if we exceed the maximum number of user accounts
1396
- obj.db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) {
1397
- if (maxExceed) {
1398
- parent.debug('web', 'handleCreateAccountRequest: account limit reached');
1399
- req.session.loginmode = 2;
1400
- req.session.messageid = 101; // Account limit reached.
1401
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1402
- } else {
1403
- if (!obj.common.validateUsername(req.body.username, 1, 64) || !obj.common.validateEmail(req.body.email, 1, 256) || !obj.common.validateString(req.body.password1, 1, 256) || !obj.common.validateString(req.body.password2, 1, 256) || (req.body.password1 != req.body.password2) || req.body.username == '~' || !obj.common.checkPasswordRequirements(req.body.password1, domain.passwordrequirements)) {
1404
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (3)');
1405
- req.session.loginmode = 2;
1406
- req.session.messageid = 100; // Unable to create account.
1407
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1408
- } else {
1409
- // Check if this email was already verified
1410
- obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) {
1411
- if ((docs != null) && (docs.length > 0)) {
1412
- parent.debug('web', 'handleCreateAccountRequest: Existing account with this email address');
1413
- req.session.loginmode = 2;
1414
- req.session.messageid = 102; // Existing account with this email address.
1415
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1416
- } else {
1417
- // Check if there is domain.newAccountToken, check if supplied token is valid
1418
- if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
1419
- parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token');
1420
- req.session.loginmode = 2;
1421
- req.session.messageid = 103; // Invalid account creation token.
1422
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1423
- return;
1424
- }
1425
- // Check if user exists
1426
- if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
1427
- parent.debug('web', 'handleCreateAccountRequest: Username already exists');
1428
- req.session.loginmode = 2;
1429
- req.session.messageid = 104; // Username already exists.
1430
- } else {
1431
- var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
1432
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
1433
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
1434
- if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) { hint = hint.substring(0, 250); } user.passhint = hint; }
1435
- if (domainUserCount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
1436
-
1437
- // Auto-join any user groups
1438
- if (typeof domain.newaccountsusergroups == 'object') {
1439
- for (var i in domain.newaccountsusergroups) {
1440
- var ugrpid = domain.newaccountsusergroups[i];
1441
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
1442
- var ugroup = obj.userGroups[ugrpid];
1443
- if (ugroup != null) {
1444
- // Add group to the user
1445
- if (user.links == null) { user.links = {}; }
1446
- user.links[ugroup._id] = { rights: 1 };
1447
-
1448
- // Add user to the group
1449
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
1450
- db.Set(ugroup);
1451
-
1452
- // Notify user group change
1453
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
1454
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
1455
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
1456
- }
1457
- }
1458
- }
1459
-
1460
- obj.users[user._id] = user;
1461
- req.session.userid = user._id;
1462
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1463
- setSessionRandom(req);
1464
- // Create a user, generate a salt and hash the password
1465
- require('./pass').hash(req.body.password1, function (err, salt, hash, tag) {
1466
- if (err) throw err;
1467
- user.salt = salt;
1468
- user.hash = hash;
1469
- delete user.passtype;
1470
- obj.db.SetUser(user);
1471
-
1472
- // Send the verification email
1473
- if ((domain.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (obj.common.validateEmail(user.email, 1, 256) == true)) { domain.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key); }
1474
- }, 0);
1475
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id };
1476
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
1477
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
1478
- }
1479
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1480
- }
1481
- });
1482
- }
1483
- }
1484
- });
1485
- }
1486
-
1487
- // Called to process an account password reset
1488
- function handleResetPasswordRequest(req, res, direct) {
1489
- const domain = checkUserIpAddress(req, res);
1490
- if (domain == null) { return; }
1491
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1492
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1493
-
1494
- // Check everything is ok
1495
- const allowAccountReset = ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.allowaccountreset !== false));
1496
- if ((allowAccountReset === false) || (domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (typeof req.body.rpassword1 != 'string') || (typeof req.body.rpassword2 != 'string') || (req.body.rpassword1 != req.body.rpassword2) || (typeof req.body.rpasswordhint != 'string') || (req.session == null) || (typeof req.session.resettokenusername != 'string') || (typeof req.session.resettokenpassword != 'string')) {
1497
- parent.debug('web', 'handleResetPasswordRequest: checks failed');
1498
- delete req.session.u2f;
1499
- delete req.session.loginmode;
1500
- delete req.session.tuserid;
1501
- delete req.session.tuser;
1502
- delete req.session.tpass;
1503
- delete req.session.resettokenuserid;
1504
- delete req.session.resettokenusername;
1505
- delete req.session.resettokenpassword;
1506
- delete req.session.temail;
1507
- delete req.session.tsms;
1508
- delete req.session.tpush;
1509
- delete req.session.messageid;
1510
- delete req.session.passhint;
1511
- delete req.session.cuserid;
1512
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1513
- return;
1514
- }
1515
-
1516
- // Authenticate the user
1517
- obj.authenticate(req.session.resettokenusername, req.session.resettokenpassword, domain, function (err, userid, passhint, loginOptions) {
1518
- if (userid) {
1519
- // Login
1520
- var user = obj.users[userid];
1521
-
1522
- // If we have password requirements, check this here.
1523
- if (!obj.common.checkPasswordRequirements(req.body.rpassword1, domain.passwordrequirements)) {
1524
- parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (1)');
1525
- req.session.loginmode = 6;
1526
- req.session.messageid = 105; // Password rejected, use a different one.
1527
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1528
- return;
1529
- }
1530
-
1531
- // Check if the password is the same as a previous one
1532
- obj.checkOldUserPasswords(domain, user, req.body.rpassword1, function (result) {
1533
- if (result != 0) {
1534
- // This is the same password as an older one, request a password change again
1535
- parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (2)');
1536
- req.session.loginmode = 6;
1537
- req.session.messageid = 105; // Password rejected, use a different one.
1538
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1539
- } else {
1540
- // Update the password, use a different salt.
1541
- require('./pass').hash(req.body.rpassword1, function (err, salt, hash, tag) {
1542
- const nowSeconds = Math.floor(Date.now() / 1000);
1543
- if (err) { parent.debug('web', 'handleResetPasswordRequest: hash error.'); throw err; }
1544
-
1545
- if (domain.passwordrequirements != null) {
1546
- // Save password hint if this feature is enabled
1547
- if ((domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) hint = hint.substring(0, 250); user.passhint = hint; } else { delete user.passhint; }
1548
-
1549
- // Save previous password if this feature is enabled
1550
- if ((typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
1551
- if (user.oldpasswords == null) { user.oldpasswords = []; }
1552
- user.oldpasswords.push({ salt: user.salt, hash: user.hash, start: user.passchange, end: nowSeconds });
1553
- const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
1554
- if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
1555
- }
1556
- }
1557
-
1558
- user.salt = salt;
1559
- user.hash = hash;
1560
- user.passchange = user.access = nowSeconds;
1561
- delete user.passtype;
1562
- obj.db.SetUser(user);
1563
-
1564
- // Event the account change
1565
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'User password reset', domain: domain.id };
1566
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1567
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1568
-
1569
- // Login successful
1570
- parent.debug('web', 'handleResetPasswordRequest: success');
1571
- req.session.userid = userid;
1572
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1573
- setSessionRandom(req);
1574
- completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tuser, req.session.tpass, direct, loginOptions);
1575
- }, 0);
1576
- }
1577
- }, 0);
1578
- } else {
1579
- // Failed, error out.
1580
- parent.debug('web', 'handleResetPasswordRequest: failed authenticate()');
1581
- delete req.session.u2f;
1582
- delete req.session.loginmode;
1583
- delete req.session.tuserid;
1584
- delete req.session.tuser;
1585
- delete req.session.tpass;
1586
- delete req.session.resettokenuserid;
1587
- delete req.session.resettokenusername;
1588
- delete req.session.resettokenpassword;
1589
- delete req.session.temail;
1590
- delete req.session.tsms;
1591
- delete req.session.tpush;
1592
- delete req.session.messageid;
1593
- delete req.session.passhint;
1594
- delete req.session.cuserid;
1595
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1596
- return;
1597
- }
1598
- });
1599
- }
1600
-
1601
- // Called to process an account reset request
1602
- function handleResetAccountRequest(req, res, direct) {
1603
- const domain = checkUserIpAddress(req, res);
1604
- if (domain == null) { return; }
1605
- const allowAccountReset = ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.allowaccountreset !== false));
1606
- if ((allowAccountReset === false) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (obj.args.lanonly == true) || (obj.parent.certificates.CommonName == null) || (obj.parent.certificates.CommonName.indexOf('.') == -1)) { parent.debug('web', 'handleResetAccountRequest: check failed'); res.sendStatus(404); return; }
1607
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1608
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1609
-
1610
- // Always lowercase the email address
1611
- if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1612
-
1613
- // Get the email from the body or session.
1614
- var email = req.body.email;
1615
- if ((email == null) || (email == '')) { email = req.session.temail; }
1616
-
1617
- // Check the email string format
1618
- if (!email || checkEmail(email) == false) {
1619
- parent.debug('web', 'handleResetAccountRequest: Invalid email');
1620
- req.session.loginmode = 3;
1621
- req.session.messageid = 106; // Invalid email.
1622
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1623
- } else {
1624
- obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
1625
- // Remove all accounts that start with ~ since they are special accounts.
1626
- var cleanDocs = [];
1627
- if ((err == null) && (docs.length > 0)) {
1628
- for (var i in docs) {
1629
- const user = docs[i];
1630
- const locked = ((user.siteadmin != null) && (user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)); // No password recovery for locked accounts
1631
- const specialAccount = (user._id.split('/')[2].startsWith('~')); // No password recovery for special accounts
1632
- if ((specialAccount == false) && (locked == false)) { cleanDocs.push(user); }
1633
- }
1634
- }
1635
- docs = cleanDocs;
1636
-
1637
- // Check if we have any account that match this email address
1638
- if ((err != null) || (docs.length == 0)) {
1639
- parent.debug('web', 'handleResetAccountRequest: Account not found');
1640
- req.session.loginmode = 3;
1641
- req.session.messageid = 1; // If valid, reset mail sent. Instead of "Account not found" (107), we send this hold on message so users can't know if this account exists or not.
1642
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1643
- } else {
1644
- // If many accounts have the same validated e-mail, we are going to use the first one for display, but sent a reset email for all accounts.
1645
- var responseSent = false;
1646
- for (var i in docs) {
1647
- var user = docs[i];
1648
- if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
1649
- // Second factor setup, request it now.
1650
- checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result, authData) {
1651
- if (result == false) {
1652
- if (i == 0) {
1653
-
1654
- // Check if 2FA is allowed for this IP address
1655
- if (obj.checkAllow2Fa(req) == false) {
1656
- // Wait and redirect the user
1657
- setTimeout(function () {
1658
- req.session.messageid = 114; // IP address blocked, try again later.
1659
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1660
- }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
1661
- return;
1662
- }
1663
-
1664
- // 2-step auth is required, but the token is not present or not valid.
1665
- parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again');
1666
- if ((req.body.token != null) || (req.body.hwtoken != null)) {
1667
- var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
1668
- if ((req.body.hwtoken == '**sms**') && sms2fa) {
1669
- // Cause a token to be sent to the user's phone number
1670
- user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
1671
- obj.db.SetUser(user);
1672
- parent.debug('web', 'Sending 2FA SMS for password recovery to: ' + user.phone);
1673
- parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
1674
- req.session.messageid = 4; // SMS sent.
1675
- } else {
1676
- req.session.messageid = 108; // Invalid token, try again.
1677
- const ua = getUserAgentInfo(req);
1678
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { action: 'authfail', username: user.name, userid: user._id, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp, msgid: 108, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
1679
- obj.setbad2Fa(req);
1680
- }
1681
- }
1682
- req.session.loginmode = 5;
1683
- req.session.temail = email;
1684
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1685
- }
1686
- } else {
1687
- // Send email to perform recovery.
1688
- delete req.session.temail;
1689
- if (domain.mailserver != null) {
1690
- domain.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1691
- if (i == 0) {
1692
- parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
1693
- req.session.loginmode = 1;
1694
- req.session.messageid = 1; // If valid, reset mail sent.
1695
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1696
- }
1697
- } else {
1698
- if (i == 0) {
1699
- parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
1700
- req.session.loginmode = 3;
1701
- req.session.messageid = 109; // Unable to sent email.
1702
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1703
- }
1704
- }
1705
- }
1706
- });
1707
- } else {
1708
- // No second factor, send email to perform recovery.
1709
- if (domain.mailserver != null) {
1710
- domain.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1711
- if (i == 0) {
1712
- parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
1713
- req.session.loginmode = 1;
1714
- req.session.messageid = 1; // If valid, reset mail sent.
1715
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1716
- }
1717
- } else {
1718
- if (i == 0) {
1719
- parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
1720
- req.session.loginmode = 3;
1721
- req.session.messageid = 109; // Unable to sent email.
1722
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1723
- }
1724
- }
1725
- }
1726
- }
1727
- }
1728
- });
1729
- }
1730
- }
1731
-
1732
- // Handle account email change and email verification request
1733
- function handleCheckAccountEmailRequest(req, res, direct) {
1734
- const domain = checkUserIpAddress(req, res);
1735
- if (domain == null) { return; }
1736
- if ((domain.mailserver == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (typeof req.session.cuserid != 'string') || (obj.users[req.session.cuserid] == null) || (!obj.common.validateEmail(req.body.email, 1, 256))) { parent.debug('web', 'handleCheckAccountEmailRequest: failed checks.'); res.sendStatus(404); return; }
1737
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1738
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
1739
-
1740
- // Always lowercase the email address
1741
- if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1742
-
1743
- // Get the email from the body or session.
1744
- var email = req.body.email;
1745
- if ((email == null) || (email == '')) { email = req.session.temail; }
1746
-
1747
- // Check if this request is for an allows email domain
1748
- if ((domain.newaccountemaildomains != null) && Array.isArray(domain.newaccountemaildomains)) {
1749
- var i = -1;
1750
- if (typeof req.body.email == 'string') { i = req.body.email.indexOf('@'); }
1751
- if (i == -1) {
1752
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
1753
- req.session.loginmode = 7;
1754
- req.session.messageid = 106; // Invalid email.
1755
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1756
- return;
1757
- }
1758
- var emailok = false, emaildomain = req.body.email.substring(i + 1).toLowerCase();
1759
- for (var i in domain.newaccountemaildomains) { if (emaildomain == domain.newaccountemaildomains[i].toLowerCase()) { emailok = true; } }
1760
- if (emailok == false) {
1761
- parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)');
1762
- req.session.loginmode = 7;
1763
- req.session.messageid = 106; // Invalid email.
1764
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1765
- return;
1766
- }
1767
- }
1768
-
1769
- // Check the email string format
1770
- if (!email || checkEmail(email) == false) {
1771
- parent.debug('web', 'handleCheckAccountEmailRequest: Invalid email');
1772
- req.session.loginmode = 7;
1773
- req.session.messageid = 106; // Invalid email.
1774
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1775
- } else {
1776
- // Check is email already exists
1777
- obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
1778
- if ((err != null) || ((docs.length > 0) && (docs.find(function (u) { return (u._id === req.session.cuserid); }) < 0))) {
1779
- // Email already exitst
1780
- req.session.messageid = 102; // Existing account with this email address.
1781
- } else {
1782
- // Update the user and notify of user email address change
1783
- var user = obj.users[req.session.cuserid];
1784
- if (user.email != email) {
1785
- user.email = email;
1786
- db.SetUser(user);
1787
- var targets = ['*', 'server-users', user._id];
1788
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1789
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Account changed: ' + user.name, domain: domain.id };
1790
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1791
- parent.DispatchEvent(targets, obj, event);
1792
- }
1793
-
1794
- // Send the verification email
1795
- domain.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1796
-
1797
- // Send the response
1798
- req.session.messageid = 2; // Email sent.
1799
- }
1800
- req.session.loginmode = 7;
1801
- delete req.session.cuserid;
1802
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1803
- });
1804
- }
1805
- }
1806
-
1807
- // Called to process a web based email verification request
1808
- function handleCheckMailRequest(req, res) {
1809
- const domain = checkUserIpAddress(req, res);
1810
- if (domain == null) { return; }
1811
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap') || (domain.mailserver == null)) { parent.debug('web', 'handleCheckMailRequest: failed checks.'); res.sendStatus(404); return; }
1812
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1813
-
1814
- if (req.query.c != null) {
1815
- var cookie = obj.parent.decodeCookie(req.query.c, domain.mailserver.mailCookieEncryptionKey, 30);
1816
- if ((cookie != null) && (cookie.u != null) && (cookie.u.startsWith('user/')) && (cookie.e != null)) {
1817
- var idsplit = cookie.u.split('/');
1818
- if ((idsplit.length != 3) || (idsplit[1] != domain.id)) {
1819
- parent.debug('web', 'handleCheckMailRequest: Invalid domain.');
1820
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 1, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1821
- } else {
1822
- obj.db.Get(cookie.u, function (err, docs) {
1823
- if (docs.length == 0) {
1824
- parent.debug('web', 'handleCheckMailRequest: Invalid username.');
1825
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 2, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(idsplit[1]).replace(/'/g, '%27') }, req, domain));
1826
- } else {
1827
- var user = docs[0];
1828
- if (user.email != cookie.e) {
1829
- parent.debug('web', 'handleCheckMailRequest: Invalid e-mail.');
1830
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 3, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27'), arg2: encodeURIComponent(user.name).replace(/'/g, '%27') }, req, domain));
1831
- } else {
1832
- if (cookie.a == 1) {
1833
- // Account email verification
1834
- if (user.emailVerified == true) {
1835
- parent.debug('web', 'handleCheckMailRequest: email already verified.');
1836
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 4, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27'), arg2: encodeURIComponent(user.name).replace(/'/g, '%27') }, req, domain));
1837
- } else {
1838
- obj.db.GetUserWithVerifiedEmail(domain.id, user.email, function (err, docs) {
1839
- if ((docs.length > 0) && (docs.find(function (u) { return (u._id === user._id); }) < 0)) {
1840
- parent.debug('web', 'handleCheckMailRequest: email already in use.');
1841
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 5, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27') }, req, domain));
1842
- } else {
1843
- parent.debug('web', 'handleCheckMailRequest: email verification success.');
1844
-
1845
- // Set the verified flag
1846
- obj.users[user._id].emailVerified = true;
1847
- user.emailVerified = true;
1848
- obj.db.SetUser(user);
1849
-
1850
- // Event the change
1851
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Verified email of user ' + EscapeHtml(user.name) + ' (' + EscapeHtml(user.email) + ')', domain: domain.id };
1852
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1853
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1854
-
1855
- // Send the confirmation page
1856
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 6, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(user.email).replace(/'/g, '%27'), arg2: encodeURIComponent(user.name).replace(/'/g, '%27') }, req, domain));
1857
-
1858
- // Send a notification
1859
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: 'Email verified', value: user.email, nolog: 1, id: Math.random() });
1860
-
1861
- // Send to authlog
1862
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Verified email address ' + user.email + ' for user ' + user.name); }
1863
- }
1864
- });
1865
- }
1866
- } else if (cookie.a == 2) {
1867
- // Account reset
1868
- if (user.emailVerified != true) {
1869
- parent.debug('web', 'handleCheckMailRequest: email not verified.');
1870
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 7, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: EscapeHtml(user.email), arg2: EscapeHtml(user.name) }, req, domain));
1871
- } else {
1872
- if (req.query.confirm == 1) {
1873
- // Set a temporary password
1874
- obj.crypto.randomBytes(16, function (err, buf) {
1875
- var newpass = buf.toString('base64').split('=').join('').split('/').join('').split('+').join('');
1876
- require('./pass').hash(newpass, function (err, salt, hash, tag) {
1877
- if (err) throw err;
1878
-
1879
- // Change the password
1880
- var userinfo = obj.users[user._id];
1881
- userinfo.salt = salt;
1882
- userinfo.hash = hash;
1883
- delete userinfo.passtype;
1884
- userinfo.passchange = userinfo.access = Math.floor(Date.now() / 1000);
1885
- delete userinfo.passhint;
1886
- obj.db.SetUser(userinfo);
1887
-
1888
- // Event the change
1889
- var event = { etype: 'user', userid: user._id, username: userinfo.name, account: obj.CloneSafeUser(userinfo), action: 'accountchange', msg: 'Password reset for user ' + EscapeHtml(user.name), domain: domain.id };
1890
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
1891
- obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1892
-
1893
- // Send the new password
1894
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 8, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: EscapeHtml(user.name), arg2: EscapeHtml(newpass) }, req, domain));
1895
- parent.debug('web', 'handleCheckMailRequest: send temporary password.');
1896
-
1897
- // Send to authlog
1898
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Performed account reset for user ' + user.name); }
1899
- }, 0);
1900
- });
1901
- } else {
1902
- // Display a link for the user to confirm password reset
1903
- // We must do this because GMail will also load this URL a few seconds after the user does and we don't want to cause two password resets.
1904
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 14, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1905
- }
1906
- }
1907
- } else {
1908
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 9, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1909
- }
1910
- }
1911
- }
1912
- });
1913
- }
1914
- } else {
1915
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 10, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1916
- }
1917
- }
1918
- }
1919
-
1920
- // Called to process an agent invite GET/POST request
1921
- function handleInviteRequest(req, res) {
1922
- const domain = getDomain(req);
1923
- if (domain == null) { parent.debug('web', 'handleInviteRequest: failed checks.'); res.sendStatus(404); return; }
1924
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1925
- if ((req.body.inviteCode == null) || (req.body.inviteCode == '')) { render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 0 }, req, domain)); return; } // No invitation code
1926
-
1927
- // Each for a device group that has this invite code.
1928
- for (var i in obj.meshes) {
1929
- if ((obj.meshes[i].domain == domain.id) && (obj.meshes[i].deleted == null) && (obj.meshes[i].invite != null) && (obj.meshes[i].invite.codes.indexOf(req.body.inviteCode) >= 0)) {
1930
- // Send invitation link, valid for 1 minute.
1931
- res.redirect(domain.url + 'agentinvite?c=' + parent.encodeCookie({ a: 4, mid: i, f: obj.meshes[i].invite.flags, ag: obj.meshes[i].invite.ag, expire: 1 }, parent.invitationLinkEncryptionKey) + (req.query.key ? ('&key=' + req.query.key) : '') + (req.query.hide ? ('&hide=' + req.query.hide) : ''));
1932
- return;
1933
- }
1934
- }
1935
-
1936
- render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 100 }, req, domain)); // Bad invitation code
1937
- }
1938
-
1939
- // Called to render the MSTSC (RDP) or SSH web page
1940
- function handleMSTSCRequest(req, res, page) {
1941
- const domain = getDomain(req);
1942
- if (domain == null) { parent.debug('web', 'handleMSTSCRequest: failed checks.'); res.sendStatus(404); return; }
1943
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1944
-
1945
- // Check if we are in maintenance mode
1946
- if ((parent.config.settings.maintenancemode != null) && (req.query.loginscreen !== '1')) {
1947
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 3, msgid: 13, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
1948
- return;
1949
- }
1950
-
1951
- // Set features we want to send to this page
1952
- var features = 0;
1953
- if (domain.allowsavingdevicecredentials === false) { features |= 1; }
1954
-
1955
- if (req.query.ws != null) {
1956
- // This is a query with a websocket relay cookie, check that the cookie is valid and use it.
1957
- var rcookie = parent.decodeCookie(req.query.ws, parent.loginCookieEncryptionKey, 60); // Cookie with 1 hour timeout
1958
- if ((rcookie != null) && (rcookie.domainid == domain.id) && (rcookie.nodeid != null) && (rcookie.tcpport != null)) {
1959
-
1960
- // Fetch the node from the database
1961
- obj.db.Get(rcookie.nodeid, function (err, nodes) {
1962
- if ((err != null) || (nodes.length != 1)) { res.sendStatus(404); return; }
1963
- const node = nodes[0];
1964
-
1965
- // Check if we have RDP credentials for this device
1966
- var serverCredentials = false;
1967
- if (domain.allowsavingdevicecredentials !== false) {
1968
- if (page == 'ssh') {
1969
- serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string'))
1970
- } else {
1971
- serverCredentials = ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string'))
1972
- }
1973
- }
1974
-
1975
- // Render the page
1976
- render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: req.query.ws, name: encodeURIComponent(req.query.name).replace(/'/g, '%27'), serverCredentials: serverCredentials, features: features }, req, domain));
1977
- });
1978
- return;
1979
- }
1980
- }
1981
-
1982
- // Get the logged in user if present
1983
- var user = null;
1984
-
1985
- // If there is a login token, use that
1986
- if (req.query.login != null) {
1987
- var ucookie = parent.decodeCookie(req.query.login, parent.loginCookieEncryptionKey, 60); // Cookie with 1 hour timeout
1988
- if ((ucookie != null) && (ucookie.a === 3) && (typeof ucookie.u == 'string')) { user = obj.users[ucookie.u]; }
1989
- }
1990
-
1991
- // If no token, see if we have an active session
1992
- if ((user == null) && (req.session.userid != null)) { user = obj.users[req.session.userid]; }
1993
-
1994
- // If still no user, see if we have a default user
1995
- if ((user == null) && (obj.args.user)) { user = obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]; }
1996
-
1997
- // No user login, exit now
1998
- if (user == null) { res.sendStatus(401); return; }
1999
-
2000
- // Check the nodeid
2001
- if (req.query.node != null) {
2002
- var nodeidsplit = req.query.node.split('/');
2003
- if (nodeidsplit.length == 1) {
2004
- req.query.node = 'node/' + domain.id + '/' + nodeidsplit[0]; // Format the nodeid correctly
2005
- } else if (nodeidsplit.length == 3) {
2006
- if ((nodeidsplit[0] != 'node') || (nodeidsplit[1] != domain.id)) { req.query.node = null; } // Check the nodeid format
2007
- } else {
2008
- req.query.node = null; // Bad nodeid
2009
- }
2010
- }
2011
-
2012
- // If there is no nodeid, exit now
2013
- if (req.query.node == null) { render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: '', name: '', features: features }, req, domain)); return; }
2014
-
2015
- // Fetch the node from the database
2016
- obj.db.Get(req.query.node, function (err, nodes) {
2017
- if ((err != null) || (nodes.length != 1)) { res.sendStatus(404); return; }
2018
- const node = nodes[0];
2019
-
2020
- // Check access rights, must have remote control rights
2021
- if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { res.sendStatus(401); return; }
2022
-
2023
- // Figure out the target port
2024
- var port = 0, serverCredentials = false;
2025
- if (page == 'ssh') {
2026
- // SSH port
2027
- port = 22;
2028
- if (typeof node.sshport == 'number') { port = node.sshport; }
2029
-
2030
- // Check if we have SSH credentials for this device
2031
- if (domain.allowsavingdevicecredentials !== false) { serverCredentials = ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string')); }
2032
- } else {
2033
- // RDP port
2034
- port = 3389;
2035
- if (typeof node.rdpport == 'number') { port = node.rdpport; }
2036
-
2037
- // Check if we have RDP credentials for this device
2038
- if (domain.allowsavingdevicecredentials !== false) { serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')); }
2039
- }
2040
- if (req.query.port != null) { var qport = 0; try { qport = parseInt(req.query.port); } catch (ex) { } if ((typeof qport == 'number') && (qport > 0) && (qport < 65536)) { port = qport; } }
2041
-
2042
- // Generate a cookie and respond
2043
- var cookie = parent.encodeCookie({ userid: user._id, domainid: user.domain, nodeid: node._id, tcpport: port }, parent.loginCookieEncryptionKey);
2044
- render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: cookie, name: encodeURIComponent(node.name).replace(/'/g, '%27'), serverCredentials: serverCredentials, features: features }, req, domain));
2045
- });
2046
- }
2047
-
2048
- // Called to handle push-only requests
2049
- function handleFirebasePushOnlyRelayRequest(req, res) {
2050
- parent.debug('email', 'handleFirebasePushOnlyRelayRequest');
2051
- if ((req.body == null) || (req.body.msg == null) || (obj.parent.firebase == null)) { res.sendStatus(404); return; }
2052
- if (obj.parent.config.firebase.pushrelayserver == null) { res.sendStatus(404); return; }
2053
- if ((typeof obj.parent.config.firebase.pushrelayserver == 'string') && (req.query.key != obj.parent.config.firebase.pushrelayserver)) { res.sendStatus(404); return; }
2054
- var data = null;
2055
- try { data = JSON.parse(req.body.msg) } catch (ex) { res.sendStatus(404); return; }
2056
- if (typeof data != 'object') { res.sendStatus(404); return; }
2057
- if (typeof data.pmt != 'string') { res.sendStatus(404); return; }
2058
- if (typeof data.payload != 'object') { res.sendStatus(404); return; }
2059
- if (typeof data.payload.notification != 'object') { res.sendStatus(404); return; }
2060
- if (typeof data.payload.notification.title != 'string') { res.sendStatus(404); return; }
2061
- if (typeof data.payload.notification.body != 'string') { res.sendStatus(404); return; }
2062
- if (typeof data.options != 'object') { res.sendStatus(404); return; }
2063
- if ((data.options.priority != 'Normal') && (data.options.priority != 'High')) { res.sendStatus(404); return; }
2064
- if ((typeof data.options.timeToLive != 'number') || (data.options.timeToLive < 1)) { res.sendStatus(404); return; }
2065
- parent.debug('email', 'handleFirebasePushOnlyRelayRequest - ok');
2066
- obj.parent.firebase.sendToDevice({ pmt: data.pmt }, data.payload, data.options, function (id, err, errdesc) {
2067
- if (err == null) { res.sendStatus(200); } else { res.sendStatus(500); }
2068
- });
2069
- }
2070
-
2071
- // Called to handle two-way push notification relay request
2072
- function handleFirebaseRelayRequest(ws, req) {
2073
- parent.debug('email', 'handleFirebaseRelayRequest');
2074
- if (obj.parent.firebase == null) { try { ws.close(); } catch (e) { } return; }
2075
- if (obj.parent.firebase.setupRelay == null) { try { ws.close(); } catch (e) { } return; }
2076
- if (obj.parent.config.firebase.relayserver == null) { try { ws.close(); } catch (e) { } return; }
2077
- if ((typeof obj.parent.config.firebase.relayserver == 'string') && (req.query.key != obj.parent.config.firebase.relayserver)) { res.sendStatus(404); try { ws.close(); } catch (e) { } return; }
2078
- obj.parent.firebase.setupRelay(ws);
2079
- }
2080
-
2081
- // Called to process an agent invite request
2082
- function handleAgentInviteRequest(req, res) {
2083
- const domain = getDomain(req);
2084
- if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) { parent.debug('web', 'handleAgentInviteRequest: failed checks.'); res.sendStatus(404); return; }
2085
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2086
-
2087
- if (req.query.c != null) {
2088
- // A cookie is specified in the query string, use that
2089
- var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey);
2090
- if (cookie == null) { res.sendStatus(404); return; }
2091
- var mesh = obj.meshes[cookie.mid];
2092
- if (mesh == null) { res.sendStatus(404); return; }
2093
- var installflags = cookie.f;
2094
- if (typeof installflags != 'number') { installflags = 0; }
2095
- var showagents = cookie.ag;
2096
- if (typeof showagents != 'number') { showagents = 0; }
2097
- parent.debug('web', 'handleAgentInviteRequest using cookie.');
2098
-
2099
- // Build the mobile agent URL, this is used to connect mobile devices
2100
- var agentServerName = obj.getWebServerName(domain);
2101
- if (typeof obj.args.agentaliasdns == 'string') { agentServerName = obj.args.agentaliasdns; }
2102
- var xdomain = (domain.dns == null) ? domain.id : '';
2103
- var agentHttpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2104
- if (obj.args.agentport != null) { agentHttpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
2105
- if (obj.args.agentaliasport != null) { agentHttpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
2106
- var magenturl = 'mc://' + agentServerName + ((agentHttpsPort != 443) ? (':' + agentHttpsPort) : '') + ((xdomain != '') ? ('/' + xdomain) : '') + ',' + obj.agentCertificateHashBase64 + ',' + mesh._id.split('/')[2];
2107
-
2108
- var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
2109
- render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, showagents: showagents, magenturl: magenturl }, req, domain));
2110
- } else if (req.query.m != null) {
2111
- // The MeshId is specified in the query string, use that
2112
- var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.m.toLowerCase()];
2113
- if (mesh == null) { res.sendStatus(404); return; }
2114
- var installflags = 0;
2115
- if (req.query.f) { installflags = parseInt(req.query.f); }
2116
- if (typeof installflags != 'number') { installflags = 0; }
2117
- var showagents = 0;
2118
- if (req.query.f) { showagents = parseInt(req.query.ag); }
2119
- if (typeof showagents != 'number') { showagents = 0; }
2120
- parent.debug('web', 'handleAgentInviteRequest using meshid.');
2121
-
2122
- // Build the mobile agent URL, this is used to connect mobile devices
2123
- var agentServerName = obj.getWebServerName(domain);
2124
- if (typeof obj.args.agentaliasdns == 'string') { agentServerName = obj.args.agentaliasdns; }
2125
- var xdomain = (domain.dns == null) ? domain.id : '';
2126
- var agentHttpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2127
- if (obj.args.agentport != null) { agentHttpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
2128
- if (obj.args.agentaliasport != null) { agentHttpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
2129
- var magenturl = 'mc://' + agentServerName + ((agentHttpsPort != 443) ? (':' + agentHttpsPort) : '') + ((xdomain != '') ? ('/' + xdomain) : '') + ',' + obj.agentCertificateHashBase64 + ',' + mesh._id.split('/')[2];
2130
-
2131
- var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
2132
- render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, showagents: showagents, magenturl: magenturl }, req, domain));
2133
- }
2134
- }
2135
-
2136
- // Called to process an agent invite request
2137
- function handleUserImageRequest(req, res) {
2138
- const domain = getDomain(req);
2139
- if (domain == null) { parent.debug('web', 'handleUserImageRequest: failed checks.'); res.sendStatus(404); return; }
2140
- if ((req.session == null) || (req.session.userid == null)) { parent.debug('web', 'handleUserImageRequest: failed checks 2.'); res.sendStatus(404); return; }
2141
- var imageUserId = req.session.userid;
2142
- if ((req.query.id != null)) {
2143
- var user = obj.users[req.session.userid];
2144
- if ((user == null) || (user.siteadmin == null) && ((user.siteadmin & 2) == 0)) { res.sendStatus(404); return; }
2145
- imageUserId = 'user/' + domain.id + '/' + req.query.id;
2146
- }
2147
- obj.db.Get('im' + imageUserId, function (err, docs) {
2148
- if ((err != null) || (docs == null) || (docs.length != 1) || (typeof docs[0].image != 'string')) { res.sendStatus(404); return; }
2149
- var imagebase64 = docs[0].image;
2150
- if (imagebase64.startsWith('data:image/png;base64,')) {
2151
- res.set('Content-Type', 'image/png');
2152
- res.set({ 'Cache-Control': 'no-store' });
2153
- res.send(Buffer.from(imagebase64.substring(22), 'base64'));
2154
- } else if (imagebase64.startsWith('data:image/jpeg;base64,')) {
2155
- res.set('Content-Type', 'image/jpeg');
2156
- res.set({ 'Cache-Control': 'no-store' });
2157
- res.send(Buffer.from(imagebase64.substring(23), 'base64'));
2158
- } else {
2159
- res.sendStatus(404);
2160
- }
2161
- });
2162
- }
2163
-
2164
- function handleDeleteAccountRequest(req, res, direct) {
2165
- parent.debug('web', 'handleDeleteAccountRequest()');
2166
- const domain = checkUserIpAddress(req, res);
2167
- if (domain == null) { return; }
2168
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleDeleteAccountRequest: failed checks.'); res.sendStatus(404); return; }
2169
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2170
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
2171
-
2172
- var user = null;
2173
- if (req.body.authcookie) {
2174
- // If a authentication cookie is provided, decode it here
2175
- var loginCookie = obj.parent.decodeCookie(req.body.authcookie, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
2176
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { user = obj.users[loginCookie.userid]; }
2177
- } else {
2178
- // Check if the user is logged and we have all required parameters
2179
- if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.userid.split('/')[1] != domain.id)) {
2180
- parent.debug('web', 'handleDeleteAccountRequest: required parameters not present.');
2181
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2182
- return;
2183
- } else {
2184
- user = obj.users[req.session.userid];
2185
- }
2186
- }
2187
- if (!user) { parent.debug('web', 'handleDeleteAccountRequest: user not found.'); res.sendStatus(404); return; }
2188
- if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) { parent.debug('web', 'handleDeleteAccountRequest: account settings locked.'); res.sendStatus(404); return; }
2189
-
2190
- // Check if the password is correct
2191
- obj.authenticate(user._id.split('/')[2], req.body.apassword1, domain, function (err, userid, passhint, loginOptions) {
2192
- var deluser = obj.users[userid];
2193
- if ((userid != null) && (deluser != null)) {
2194
- // Remove all links to this user
2195
- if (deluser.links != null) {
2196
- for (var i in deluser.links) {
2197
- if (i.startsWith('mesh/')) {
2198
- // Get the device group
2199
- var mesh = obj.meshes[i];
2200
- if (mesh) {
2201
- // Remove user from the mesh
2202
- if (mesh.links[deluser._id] != null) { delete mesh.links[deluser._id]; parent.db.Set(mesh); }
2203
-
2204
- // Notify mesh change
2205
- var change = 'Removed user ' + deluser.name + ' from group ' + mesh.name;
2206
- var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id, invite: mesh.invite };
2207
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
2208
- parent.DispatchEvent(['*', mesh._id, deluser._id, user._id], obj, event);
2209
- }
2210
- } else if (i.startsWith('node/')) {
2211
- // Get the node and the rights for this node
2212
- obj.GetNodeWithRights(domain, deluser, i, function (node, rights, visible) {
2213
- if ((node == null) || (node.links == null) || (node.links[deluser._id] == null)) return;
2214
-
2215
- // Remove the link and save the node to the database
2216
- delete node.links[deluser._id];
2217
- if (Object.keys(node.links).length == 0) { delete node.links; }
2218
- db.Set(obj.cleanDevice(node));
2219
-
2220
- // Event the node change
2221
- var event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msg: ('Removed user device rights for ' + node.name), node: obj.CloneSafeNode(node) }
2222
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
2223
- parent.DispatchEvent(['*', node.meshid, node._id], obj, event);
2224
- });
2225
- } else if (i.startsWith('ugrp/')) {
2226
- // Get the device group
2227
- var ugroup = obj.userGroups[i];
2228
- if (ugroup) {
2229
- // Remove user from the user group
2230
- if (ugroup.links[deluser._id] != null) { delete ugroup.links[deluser._id]; parent.db.Set(ugroup); }
2231
-
2232
- // Notify user group change
2233
- var change = 'Removed user ' + deluser.name + ' from user group ' + ugroup.name;
2234
- var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Removed user ' + deluser.name + ' from user group ' + ugroup.name, addUserDomain: domain.id };
2235
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
2236
- parent.DispatchEvent(['*', ugroup._id, user._id, deluser._id], obj, event);
2237
- }
2238
- }
2239
- }
2240
- }
2241
-
2242
- obj.db.Remove('ws' + deluser._id); // Remove user web state
2243
- obj.db.Remove('nt' + deluser._id); // Remove notes for this user
2244
- obj.db.Remove('ntp' + deluser._id); // Remove personal notes for this user
2245
- obj.db.Remove('im' + deluser._id); // Remove image for this user
2246
-
2247
- // Delete any login tokens
2248
- parent.db.GetAllTypeNodeFiltered(['logintoken-' + deluser._id], domain.id, 'logintoken', null, function (err, docs) {
2249
- if ((err == null) && (docs != null)) { for (var i = 0; i < docs.length; i++) { parent.db.Remove(docs[i]._id, function () { }); } }
2250
- });
2251
-
2252
- // Delete all files on the server for this account
2253
- try {
2254
- var deluserpath = obj.getServerRootFilePath(deluser);
2255
- if (deluserpath != null) { obj.deleteFolderRec(deluserpath); }
2256
- } catch (e) { }
2257
-
2258
- // Remove the user
2259
- obj.db.Remove(deluser._id);
2260
- delete obj.users[deluser._id];
2261
- req.session = null;
2262
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2263
- obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluser._id, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id });
2264
- parent.debug('web', 'handleDeleteAccountRequest: removed user.');
2265
- } else {
2266
- parent.debug('web', 'handleDeleteAccountRequest: auth failed.');
2267
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2268
- }
2269
- });
2270
- }
2271
-
2272
- // Check a user's password
2273
- obj.checkUserPassword = function (domain, user, password, func) {
2274
- // Check the old password
2275
- if (user.passtype != null) {
2276
- // IIS default clear or weak password hashing (SHA-1)
2277
- require('./pass').iishash(user.passtype, password, user.salt, function (err, hash) {
2278
- if (err) { parent.debug('web', 'checkUserPassword: SHA-1 fail.'); return func(false); }
2279
- if (hash == user.hash) {
2280
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: SHA-1 locked.'); return func(false); } // Account is locked
2281
- parent.debug('web', 'checkUserPassword: SHA-1 ok.');
2282
- return func(true); // Allow password change
2283
- }
2284
- func(false);
2285
- });
2286
- } else {
2287
- // Default strong password hashing (pbkdf2 SHA384)
2288
- require('./pass').hash(password, user.salt, function (err, hash, tag) {
2289
- if (err) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 fail.'); return func(false); }
2290
- if (hash == user.hash) {
2291
- if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 locked.'); return func(false); } // Account is locked
2292
- parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 ok.');
2293
- return func(true); // Allow password change
2294
- }
2295
- func(false);
2296
- }, 0);
2297
- }
2298
- }
2299
-
2300
- // Check a user's old passwords
2301
- // Callback: 0=OK, 1=OldPass, 2=CommonPass
2302
- obj.checkOldUserPasswords = function (domain, user, password, func) {
2303
- // Check how many old passwords we need to check
2304
- if ((domain.passwordrequirements != null) && (typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
2305
- if (user.oldpasswords != null) {
2306
- const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
2307
- if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
2308
- }
2309
- } else {
2310
- delete user.oldpasswords;
2311
- }
2312
-
2313
- // If there is no old passwords, exit now.
2314
- var oldPassCount = 1;
2315
- if (user.oldpasswords != null) { oldPassCount += user.oldpasswords.length; }
2316
- var oldPassCheckState = { response: 0, count: oldPassCount, user: user, func: func };
2317
-
2318
- // Test against common passwords if this feature is enabled
2319
- // Example of common passwords: 123456789, password123
2320
- if ((domain.passwordrequirements != null) && (domain.passwordrequirements.bancommonpasswords == true)) {
2321
- oldPassCheckState.count++;
2322
- require('wildleek')(password).then(function (wild) {
2323
- if (wild == true) { oldPassCheckState.response = 2; }
2324
- if (--oldPassCheckState.count == 0) { oldPassCheckState.func(oldPassCheckState.response); }
2325
- });
2326
- }
2327
-
2328
- // Try current password
2329
- require('./pass').hash(password, user.salt, function oldPassCheck(err, hash, tag) {
2330
- if ((err == null) && (hash == tag.user.hash)) { tag.response = 1; }
2331
- if (--tag.count == 0) { tag.func(tag.response); }
2332
- }, oldPassCheckState);
2333
-
2334
- // Try each old password
2335
- if (user.oldpasswords != null) {
2336
- for (var i in user.oldpasswords) {
2337
- const oldpassword = user.oldpasswords[i];
2338
- // Default strong password hashing (pbkdf2 SHA384)
2339
- require('./pass').hash(password, oldpassword.salt, function oldPassCheck(err, hash, tag) {
2340
- if ((err == null) && (hash == tag.oldPassword.hash)) { tag.state.response = 1; }
2341
- if (--tag.state.count == 0) { tag.state.func(tag.state.response); }
2342
- }, { oldPassword: oldpassword, state: oldPassCheckState });
2343
- }
2344
- }
2345
- }
2346
-
2347
- // Handle password changes
2348
- function handlePasswordChangeRequest(req, res, direct) {
2349
- const domain = checkUserIpAddress(req, res);
2350
- if (domain == null) { return; }
2351
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).'); res.sendStatus(404); return; }
2352
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2353
- if (req.session.loginToken != null) { res.sendStatus(404); return; } // Do not allow this command when logged in using a login token
2354
-
2355
- // Check if the user is logged and we have all required parameters
2356
- if (!req.session || !req.session.userid || !req.body.apassword0 || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.userid.split('/')[1] != domain.id)) {
2357
- parent.debug('web', 'handlePasswordChangeRequest: failed checks (2).');
2358
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2359
- return;
2360
- }
2361
-
2362
- // Get the current user
2363
- var user = obj.users[req.session.userid];
2364
- if (!user) {
2365
- parent.debug('web', 'handlePasswordChangeRequest: user not found.');
2366
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2367
- return;
2368
- }
2369
-
2370
- // Check account settings locked
2371
- if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) {
2372
- parent.debug('web', 'handlePasswordChangeRequest: account settings locked.');
2373
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2374
- return;
2375
- }
2376
-
2377
- // Check old password
2378
- obj.checkUserPassword(domain, user, req.body.apassword1, function (result) {
2379
- if (result == true) {
2380
- // Check if the new password is allowed, only do this if this feature is enabled.
2381
- parent.checkOldUserPasswords(domain, user, command.newpass, function (result) {
2382
- if (result == 1) {
2383
- parent.debug('web', 'handlePasswordChangeRequest: old password reuse attempt.');
2384
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2385
- } else if (result == 2) {
2386
- parent.debug('web', 'handlePasswordChangeRequest: commonly used password use attempt.');
2387
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2388
- } else {
2389
- // Update the password
2390
- require('./pass').hash(req.body.apassword1, function (err, salt, hash, tag) {
2391
- const nowSeconds = Math.floor(Date.now() / 1000);
2392
- if (err) { parent.debug('web', 'handlePasswordChangeRequest: hash error.'); throw err; }
2393
- if (domain.passwordrequirements != null) {
2394
- // Save password hint if this feature is enabled
2395
- if ((domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) hint = hint.substring(0, 250); user.passhint = hint; } else { delete user.passhint; }
2396
-
2397
- // Save previous password if this feature is enabled
2398
- if ((typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
2399
- if (user.oldpasswords == null) { user.oldpasswords = []; }
2400
- user.oldpasswords.push({ salt: user.salt, hash: user.hash, start: user.passchange, end: nowSeconds });
2401
- const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
2402
- if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
2403
- }
2404
- }
2405
- user.salt = salt;
2406
- user.hash = hash;
2407
- user.passchange = user.access = nowSeconds;
2408
- delete user.passtype;
2409
-
2410
- obj.db.SetUser(user);
2411
- req.session.viewmode = 2;
2412
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2413
- obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'passchange', msg: 'Account password changed: ' + user.name, domain: domain.id });
2414
- }, 0);
2415
- }
2416
- });
2417
- }
2418
- });
2419
- }
2420
-
2421
- // Called when a strategy login occured
2422
- // This is called after a succesful Oauth to Twitter, Google, GitHub...
2423
- function handleStrategyLogin(req, res) {
2424
- const domain = checkUserIpAddress(req, res);
2425
- if (domain == null) { return; }
2426
- parent.debug('web', 'handleStrategyLogin: ' + JSON.stringify(req.user));
2427
- if ((req.user != null) && (req.user.sid != null)) {
2428
- const userid = 'user/' + domain.id + '/' + req.user.sid;
2429
- var user = obj.users[userid];
2430
- if (user == null) {
2431
- var newAccountAllowed = false;
2432
- var newAccountRealms = null;
2433
-
2434
- if (domain.newaccounts === true) { newAccountAllowed = true; }
2435
- if (obj.common.validateStrArray(domain.newaccountrealms)) { newAccountRealms = domain.newaccountrealms; }
2436
-
2437
- if ((domain.authstrategies != null) && (domain.authstrategies[req.user.strategy] != null)) {
2438
- if (domain.authstrategies[req.user.strategy].newaccounts === true) { newAccountAllowed = true; }
2439
- if (obj.common.validateStrArray(domain.authstrategies[req.user.strategy].newaccountrealms)) { newAccountRealms = domain.authstrategies[req.user.strategy].newaccountrealms; }
2440
- }
2441
-
2442
- if (newAccountAllowed === true) {
2443
- // Create the user
2444
- parent.debug('web', 'handleStrategyLogin: creating new user: ' + userid);
2445
- user = { type: 'user', _id: userid, name: req.user.name, email: req.user.email, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000), domain: domain.id };
2446
- if (req.user.email != null) { user.email = req.user.email; user.emailVerified = true; }
2447
- if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; } // New accounts automatically assigned server rights.
2448
- if (domain.authstrategies[req.user.strategy].newaccountsrights) { user.siteadmin = obj.common.meshServerRightsArrayToNumber(domain.authstrategies[req.user.strategy].newaccountsrights); } // If there are specific SSO server rights, use these instead.
2449
- if (newAccountRealms) { user.groups = newAccountRealms; } // New accounts automatically part of some groups (Realms).
2450
- obj.users[userid] = user;
2451
-
2452
- // Auto-join any user groups
2453
- var newaccountsusergroups = null;
2454
- if (typeof domain.newaccountsusergroups == 'object') { newaccountsusergroups = domain.newaccountsusergroups; }
2455
- if (typeof domain.authstrategies[req.user.strategy].newaccountsusergroups == 'object') { newaccountsusergroups = domain.authstrategies[req.user.strategy].newaccountsusergroups; }
2456
- if (newaccountsusergroups) {
2457
- for (var i in newaccountsusergroups) {
2458
- var ugrpid = newaccountsusergroups[i];
2459
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
2460
- var ugroup = obj.userGroups[ugrpid];
2461
- if (ugroup != null) {
2462
- // Add group to the user
2463
- if (user.links == null) { user.links = {}; }
2464
- user.links[ugroup._id] = { rights: 1 };
2465
-
2466
- // Add user to the group
2467
- ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
2468
- db.Set(ugroup);
2469
-
2470
- // Notify user group change
2471
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Added user ' + user.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
2472
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
2473
- parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
2474
- }
2475
- }
2476
- }
2477
-
2478
- // Save the user
2479
- obj.db.SetUser(user);
2480
-
2481
- // Event user creation
2482
- var targets = ['*', 'server-users'];
2483
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, username is ' + user.name, domain: domain.id };
2484
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
2485
- parent.DispatchEvent(targets, obj, event);
2486
-
2487
- req.session.userid = userid;
2488
- setSessionRandom(req);
2489
-
2490
- // Notify account login using SSO
2491
- var targets = ['*', 'server-users', user._id];
2492
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
2493
- const ua = getUserAgentInfo(req);
2494
- const loginEvent = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 107, msgArgs: [req.clientIp, ua.browserStr, ua.osStr], msg: 'Account login', domain: domain.id, ip: req.clientIp, userAgent: req.headers['user-agent'], twoFactorType: 'sso' };
2495
- obj.parent.DispatchEvent(targets, obj, loginEvent);
2496
- } else {
2497
- // New users not allowed
2498
- parent.debug('web', 'handleStrategyLogin: Can\'t create new accounts');
2499
- req.session.loginmode = 1;
2500
- req.session.messageid = 100; // Unable to create account.
2501
- res.redirect(domain.url + getQueryPortion(req));
2502
- return;
2503
- }
2504
- } else {
2505
- // Login success
2506
- var userChange = false;
2507
- if ((req.user.name != null) && (req.user.name != user.name)) { user.name = req.user.name; userChange = true; }
2508
- if ((req.user.email != null) && (req.user.email != user.email)) { user.email = req.user.email; user.emailVerified = true; userChange = true; }
2509
- if (userChange) {
2510
- obj.db.SetUser(user);
2511
-
2512
- // Event user change
2513
- var targets = ['*', 'server-users'];
2514
- var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Account changed', domain: domain.id };
2515
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
2516
- parent.DispatchEvent(targets, obj, event);
2517
- }
2518
- parent.debug('web', 'handleStrategyLogin: succesful login: ' + userid);
2519
- req.session.userid = userid;
2520
- setSessionRandom(req);
2521
-
2522
- // Notify account login using SSO
2523
- var targets = ['*', 'server-users', user._id];
2524
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
2525
- const ua = getUserAgentInfo(req);
2526
- const loginEvent = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 107, msgArgs: [req.clientIp, ua.browserStr, ua.osStr], msg: 'Account login', domain: domain.id, ip: req.clientIp, userAgent: req.headers['user-agent'], twoFactorType: 'sso' };
2527
- obj.parent.DispatchEvent(targets, obj, loginEvent);
2528
- }
2529
- }
2530
- //res.redirect(domain.url); // This does not handle cookie correctly.
2531
- res.set('Content-Type', 'text/html');
2532
- res.end('<html><head><meta http-equiv="refresh" content=0;url="' + domain.url + '"></head><body></body></html>');
2533
- }
2534
-
2535
- // Indicates that any request to "/" should render "default" or "login" depending on login state
2536
- function handleRootRequest(req, res, direct) {
2537
- const domain = checkUserIpAddress(req, res);
2538
- if (domain == null) { return; }
2539
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2540
- if (!obj.args) { parent.debug('web', 'handleRootRequest: no obj.args.'); res.sendStatus(500); return; }
2541
-
2542
- // If the session is expired, clear it.
2543
- if ((req.session != null) && (typeof req.session.expire == 'number') && ((req.session.expire - Date.now()) <= 0)) { for (var i in req.session) { delete req.session[i]; } }
2544
-
2545
- // Check if we are in maintenance mode
2546
- if ((parent.config.settings.maintenancemode != null) && (req.query.loginscreen !== '1')) {
2547
- parent.debug('web', 'handleLoginRequest: Server under maintenance.');
2548
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 3, msgid: 13, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
2549
- return;
2550
- }
2551
-
2552
- // If set and there is no user logged in, redirect the root page. Make sure not to redirect if /login is used
2553
- if ((typeof domain.unknownuserrootredirect == 'string') && ((req.session == null) || (req.session.userid == null))) {
2554
- var q = require('url').parse(req.url, true);
2555
- if (!q.pathname.endsWith('/login')) { res.redirect(domain.unknownuserrootredirect + getQueryPortion(req)); return; }
2556
- }
2557
-
2558
- if ((domain.sspi != null) && ((req.query.login == null) || (obj.parent.loginCookieEncryptionKey == null))) {
2559
- // Login using SSPI
2560
- domain.sspi.authenticate(req, res, function (err) {
2561
- if ((err != null) || (req.connection.user == null)) {
2562
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2563
- parent.debug('web', 'handleRootRequest: SSPI auth required.');
2564
- res.end('Authentication Required...');
2565
- } else {
2566
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2567
- parent.debug('web', 'handleRootRequest: SSPI auth ok.');
2568
- handleRootRequestEx(req, res, domain, direct);
2569
- }
2570
- });
2571
- } else if (req.query.user && req.query.pass) {
2572
- // User credentials are being passed in the URL. WARNING: Putting credentials in a URL is bad security... but people are requesting this option.
2573
- obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid, passhint, loginOptions) {
2574
- if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + userid + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2575
- parent.debug('web', 'handleRootRequest: user/pass in URL auth ok.');
2576
- req.session.userid = userid;
2577
- delete req.session.currentNode;
2578
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2579
- setSessionRandom(req);
2580
- handleRootRequestEx(req, res, domain, direct);
2581
- });
2582
- } else if ((req.session != null) && (typeof req.session.loginToken == 'string')) {
2583
- // Check if the loginToken is still valid
2584
- obj.db.Get('logintoken-' + req.session.loginToken, function (err, docs) {
2585
- if ((err != null) || (docs == null) || (docs.length != 1) || (docs[0].tokenUser != req.session.loginToken)) { for (var i in req.session) { delete req.session[i]; } }
2586
- handleRootRequestEx(req, res, domain, direct); // Login using a different system
2587
- });
2588
- } else {
2589
- // Login using a different system
2590
- handleRootRequestEx(req, res, domain, direct);
2591
- }
2592
- }
2593
-
2594
- function handleRootRequestEx(req, res, domain, direct) {
2595
- var nologout = false, user = null;
2596
- res.set({ 'Cache-Control': 'no-store' });
2597
-
2598
- // Check if we have an incomplete domain name in the path
2599
- if ((domain.id != '') && (domain.dns == null) && (req.url.split('/').length == 2)) {
2600
- parent.debug('web', 'handleRootRequestEx: incomplete domain name in the path.');
2601
- res.redirect(domain.url + getQueryPortion(req)); // BAD***
2602
- return;
2603
- }
2604
-
2605
- if (obj.args.nousers == true) {
2606
- // If in single user mode, setup things here.
2607
- delete req.session.loginmode;
2608
- req.session.userid = 'user/' + domain.id + '/~';
2609
- delete req.session.currentNode;
2610
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2611
- setSessionRandom(req);
2612
- if (obj.users[req.session.userid] == null) {
2613
- // Create the dummy user ~ with impossible password
2614
- parent.debug('web', 'handleRootRequestEx: created dummy user in nouser mode.');
2615
- obj.users[req.session.userid] = { type: 'user', _id: req.session.userid, name: '~', email: '~', domain: domain.id, siteadmin: 4294967295 };
2616
- obj.db.SetUser(obj.users[req.session.userid]);
2617
- }
2618
- } else if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
2619
- // If a default user is active, setup the session here.
2620
- parent.debug('web', 'handleRootRequestEx: auth using default user.');
2621
- delete req.session.loginmode;
2622
- req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
2623
- delete req.session.currentNode;
2624
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2625
- setSessionRandom(req);
2626
- } else if (req.query.login && (obj.parent.loginCookieEncryptionKey != null)) {
2627
- var loginCookie = obj.parent.decodeCookie(req.query.login, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
2628
- //if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // If the cookie if binded to an IP address, check here.
2629
- if ((loginCookie != null) && (loginCookie.a == 3) && (loginCookie.u != null) && (loginCookie.u.split('/')[1] == domain.id)) {
2630
- // If a login cookie was provided, setup the session here.
2631
- parent.debug('web', 'handleRootRequestEx: cookie auth ok.');
2632
- delete req.session.loginmode;
2633
- req.session.userid = loginCookie.u;
2634
- delete req.session.currentNode;
2635
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2636
- setSessionRandom(req);
2637
- } else {
2638
- parent.debug('web', 'handleRootRequestEx: cookie auth failed.');
2639
- }
2640
- } else if (domain.sspi != null) {
2641
- // SSPI login (Windows only)
2642
- //console.log(req.connection.user, req.connection.userSid);
2643
- if ((req.connection.user == null) || (req.connection.userSid == null)) {
2644
- parent.debug('web', 'handleRootRequestEx: SSPI no user auth.');
2645
- res.sendStatus(404); return;
2646
- } else {
2647
- nologout = true;
2648
- req.session.userid = 'user/' + domain.id + '/' + req.connection.user.toLowerCase();
2649
- req.session.usersid = req.connection.userSid;
2650
- req.session.usersGroups = req.connection.userGroups;
2651
- delete req.session.currentNode;
2652
- req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2653
- setSessionRandom(req);
2654
-
2655
- // Check if this user exists, create it if not.
2656
- user = obj.users[req.session.userid];
2657
- if ((user == null) || (user.sid != req.session.usersid)) {
2658
- // Create the domain user
2659
- var usercount = 0, user2 = { type: 'user', _id: req.session.userid, name: req.connection.user, domain: domain.id, sid: req.session.usersid, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), access: Math.floor(Date.now() / 1000) };
2660
- if (domain.newaccountsrights) { user2.siteadmin = domain.newaccountsrights; }
2661
- if (obj.common.validateStrArray(domain.newaccountrealms)) { user2.groups = domain.newaccountrealms; }
2662
- for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
2663
- if (usercount == 0) { user2.siteadmin = 4294967295; } // If this is the first user, give the account site admin.
2664
-
2665
- // Auto-join any user groups
2666
- if (typeof domain.newaccountsusergroups == 'object') {
2667
- for (var i in domain.newaccountsusergroups) {
2668
- var ugrpid = domain.newaccountsusergroups[i];
2669
- if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
2670
- var ugroup = obj.userGroups[ugrpid];
2671
- if (ugroup != null) {
2672
- // Add group to the user
2673
- if (user2.links == null) { user2.links = {}; }
2674
- user2.links[ugroup._id] = { rights: 1 };
2675
-
2676
- // Add user to the group
2677
- ugroup.links[user2._id] = { userid: user2._id, name: user2.name, rights: 1 };
2678
- db.Set(ugroup);
2679
-
2680
- // Notify user group change
2681
- var event = { etype: 'ugrp', ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msg: 'Added user ' + user2.name + ' to user group ' + ugroup.name, addUserDomain: domain.id };
2682
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
2683
- parent.DispatchEvent(['*', ugroup._id, user2._id], obj, event);
2684
- }
2685
- }
2686
- }
2687
-
2688
- obj.users[req.session.userid] = user2;
2689
- obj.db.SetUser(user2);
2690
- var event = { etype: 'user', userid: req.session.userid, username: req.connection.user, account: obj.CloneSafeUser(user2), action: 'accountcreate', msg: 'Domain account created, user ' + req.connection.user, domain: domain.id };
2691
- if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
2692
- obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
2693
- parent.debug('web', 'handleRootRequestEx: SSPI new domain user.');
2694
- }
2695
- }
2696
- }
2697
-
2698
- // Figure out the minimal password requirement
2699
- var passRequirements = null;
2700
- if (domain.passwordrequirements != null) {
2701
- if (domain.passrequirementstr == null) {
2702
- var passRequirements = {};
2703
- if (typeof domain.passwordrequirements.min == 'number') { passRequirements.min = domain.passwordrequirements.min; }
2704
- if (typeof domain.passwordrequirements.max == 'number') { passRequirements.max = domain.passwordrequirements.max; }
2705
- if (typeof domain.passwordrequirements.upper == 'number') { passRequirements.upper = domain.passwordrequirements.upper; }
2706
- if (typeof domain.passwordrequirements.lower == 'number') { passRequirements.lower = domain.passwordrequirements.lower; }
2707
- if (typeof domain.passwordrequirements.numeric == 'number') { passRequirements.numeric = domain.passwordrequirements.numeric; }
2708
- if (typeof domain.passwordrequirements.nonalpha == 'number') { passRequirements.nonalpha = domain.passwordrequirements.nonalpha; }
2709
- domain.passwordrequirementsstr = encodeURIComponent(JSON.stringify(passRequirements));
2710
- }
2711
- passRequirements = domain.passwordrequirementsstr;
2712
- }
2713
-
2714
- // If a user exists and is logged in, serve the default app, otherwise server the login app.
2715
- if (req.session && req.session.userid && obj.users[req.session.userid]) {
2716
- const user = obj.users[req.session.userid];
2717
-
2718
- // Check if we are in maintenance mode
2719
- if ((parent.config.settings.maintenancemode != null) && (user.siteadmin != 4294967295)) {
2720
- req.session.messageid = 115; // Server under maintenance
2721
- req.session.loginmode = 1;
2722
- res.redirect(domain.url);
2723
- return;
2724
- }
2725
-
2726
- // If the request has a "meshmessengerid", redirect to MeshMessenger
2727
- // This situation happens when you get a push notification for a chat session, but are not logged in.
2728
- if (req.query.meshmessengerid != null) {
2729
- res.redirect(domain.url + 'messenger?id=' + req.query.meshmessengerid + ((req.query.key != null) ? ('&key=' + req.query.key) : ''));
2730
- return;
2731
- }
2732
-
2733
- const xdbGetFunc = function dbGetFunc(err, states) {
2734
- if (dbGetFunc.req.session.userid.split('/')[1] != domain.id) { // Check if the session is for the correct domain
2735
- parent.debug('web', 'handleRootRequestEx: incorrect domain.');
2736
- dbGetFunc.req.session = null;
2737
- dbGetFunc.res.redirect(domain.url + getQueryPortion(dbGetFunc.req)); // BAD***
2738
- return;
2739
- }
2740
-
2741
- // Check if this is a locked account
2742
- if ((dbGetFunc.user.siteadmin != null) && ((dbGetFunc.user.siteadmin & 32) != 0) && (dbGetFunc.user.siteadmin != 0xFFFFFFFF)) {
2743
- // Locked account
2744
- parent.debug('web', 'handleRootRequestEx: locked account.');
2745
- delete dbGetFunc.req.session.userid;
2746
- delete dbGetFunc.req.session.currentNode;
2747
- delete dbGetFunc.req.session.passhint;
2748
- delete dbGetFunc.req.session.cuserid;
2749
- dbGetFunc.req.session.messageid = 110; // Account locked.
2750
- dbGetFunc.res.redirect(domain.url + getQueryPortion(dbGetFunc.req)); // BAD***
2751
- return;
2752
- }
2753
-
2754
- var viewmode = 1;
2755
- if (dbGetFunc.req.session.viewmode) {
2756
- viewmode = dbGetFunc.req.session.viewmode;
2757
- delete dbGetFunc.req.session.viewmode;
2758
- } else if (dbGetFunc.req.query.viewmode) {
2759
- viewmode = dbGetFunc.req.query.viewmode;
2760
- }
2761
- var currentNode = '';
2762
- if (dbGetFunc.req.session.currentNode) {
2763
- currentNode = dbGetFunc.req.session.currentNode;
2764
- delete dbGetFunc.req.session.currentNode;
2765
- } else if (dbGetFunc.req.query.node) {
2766
- currentNode = 'node/' + domain.id + '/' + dbGetFunc.req.query.node;
2767
- }
2768
- var logoutcontrols = {};
2769
- if (obj.args.nousers != true) { logoutcontrols.name = user.name; }
2770
-
2771
- // Give the web page a list of supported server features for this domain and user
2772
- const allFeatures = obj.getDomainUserFeatures(domain, dbGetFunc.user, dbGetFunc.req);
2773
-
2774
- // Create a authentication cookie
2775
- const authCookie = obj.parent.encodeCookie({ userid: dbGetFunc.user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey);
2776
- const authRelayCookie = obj.parent.encodeCookie({ ruserid: dbGetFunc.user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
2777
-
2778
- // Send the main web application
2779
- var extras = (dbGetFunc.req.query.key != null) ? ('&key=' + dbGetFunc.req.query.key) : '';
2780
- if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
2781
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2782
-
2783
- // Clean up the U2F challenge if needed
2784
- if (dbGetFunc.req.session.u2f) { delete dbGetFunc.req.session.u2f; };
2785
-
2786
- // Intel AMT Scanning options
2787
- var amtscanoptions = '';
2788
- if (typeof domain.amtscanoptions == 'string') { amtscanoptions = encodeURIComponent(domain.amtscanoptions); }
2789
- else if (obj.common.validateStrArray(domain.amtscanoptions)) { domain.amtscanoptions = domain.amtscanoptions.join(','); amtscanoptions = encodeURIComponent(domain.amtscanoptions); }
2790
-
2791
- // Fetch the web state
2792
- parent.debug('web', 'handleRootRequestEx: success.');
2793
-
2794
- var webstate = '';
2795
- if ((err == null) && (states != null) && (Array.isArray(states)) && (states.length == 1) && (states[0].state != null)) { webstate = obj.filterUserWebState(states[0].state); }
2796
- if ((webstate == '') && (typeof domain.defaultuserwebstate == 'object')) { webstate = JSON.stringify(domain.defaultuserwebstate); } // User has no web state, use defaults.
2797
- if (typeof domain.forceduserwebstate == 'object') { // Forces initial user web state if present, use it.
2798
- var webstate2 = {};
2799
- try { if (webstate != '') { webstate2 = JSON.parse(webstate); } } catch (ex) { }
2800
- for (var i in domain.forceduserwebstate) { webstate2[i] = domain.forceduserwebstate[i]; }
2801
- webstate = JSON.stringify(webstate2);
2802
- }
2803
-
2804
- // Custom user interface
2805
- var customui = '';
2806
- if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
2807
-
2808
- // Server features
2809
- var serverFeatures = 127;
2810
- if (domain.myserver === false) { serverFeatures = 0; } // 64 = Show "My Server" tab
2811
- else if (typeof domain.myserver == 'object') {
2812
- if (domain.myserver.backup !== true) { serverFeatures -= 1; } // Disallow simple server backups
2813
- if (domain.myserver.restore !== true) { serverFeatures -= 2; } // Disallow simple server restore
2814
- if (domain.myserver.upgrade !== true) { serverFeatures -= 4; } // Disallow server upgrade
2815
- if (domain.myserver.errorlog !== true) { serverFeatures -= 8; } // Disallow show server crash log
2816
- if (domain.myserver.console !== true) { serverFeatures -= 16; } // Disallow server console
2817
- if (domain.myserver.trace !== true) { serverFeatures -= 32; } // Disallow server tracing
2818
- }
2819
- if (obj.db.databaseType != 1) { // If not using NeDB, we can't backup using the simple system.
2820
- if ((serverFeatures & 1) != 0) { serverFeatures -= 1; } // Disallow server backups
2821
- if ((serverFeatures & 2) != 0) { serverFeatures -= 2; } // Disallow simple server restore
2822
- }
2823
-
2824
- // Refresh the session
2825
- render(dbGetFunc.req, dbGetFunc.res, getRenderPage('default', dbGetFunc.req, domain), getRenderArgs({
2826
- authCookie: authCookie,
2827
- authRelayCookie: authRelayCookie,
2828
- viewmode: viewmode,
2829
- currentNode: currentNode,
2830
- logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'),
2831
- domain: domain.id,
2832
- debuglevel: parent.debugLevel,
2833
- serverDnsName: obj.getWebServerName(domain),
2834
- serverRedirPort: args.redirport,
2835
- serverPublicPort: httpsPort,
2836
- serverfeatures: serverFeatures,
2837
- features: allFeatures.features,
2838
- features2: allFeatures.features2,
2839
- sessiontime: (args.sessiontime) ? args.sessiontime : 60,
2840
- mpspass: args.mpspass,
2841
- passRequirements: passRequirements,
2842
- customui: customui,
2843
- webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'),
2844
- footer: (domain.footer == null) ? '' : domain.footer,
2845
- webstate: encodeURIComponent(webstate).replace(/'/g, '%27'),
2846
- amtscanoptions: amtscanoptions,
2847
- pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports()
2848
- }, dbGetFunc.req, domain), user);
2849
- }
2850
- xdbGetFunc.req = req;
2851
- xdbGetFunc.res = res;
2852
- xdbGetFunc.user = user;
2853
- obj.db.Get('ws' + user._id, xdbGetFunc);
2854
- } else {
2855
- // Send back the login application
2856
- // If this is a 2 factor auth request, look for a hardware key challenge.
2857
- // Normal login 2 factor request
2858
- if (req.session && (req.session.loginmode == 4) && (req.session.tuserid)) {
2859
- var user = obj.users[req.session.tuserid];
2860
- if (user != null) {
2861
- parent.debug('web', 'handleRootRequestEx: sending 2FA challenge.');
2862
- getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
2863
- return;
2864
- }
2865
- }
2866
- // Password recovery 2 factor request
2867
- if (req.session && (req.session.loginmode == 5) && (req.session.temail)) {
2868
- obj.db.GetUserWithVerifiedEmail(domain.id, req.session.temail, function (err, docs) {
2869
- if ((err != null) || (docs.length == 0)) {
2870
- parent.debug('web', 'handleRootRequestEx: password recover 2FA fail.');
2871
- req.session = null;
2872
- res.redirect(domain.url + getQueryPortion(req)); // BAD***
2873
- } else {
2874
- var user = obj.users[docs[0]._id];
2875
- if (user != null) {
2876
- parent.debug('web', 'handleRootRequestEx: password recover 2FA challenge.');
2877
- getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
2878
- } else {
2879
- parent.debug('web', 'handleRootRequestEx: password recover 2FA no user.');
2880
- req.session = null;
2881
- res.redirect(domain.url + getQueryPortion(req)); // BAD***
2882
- }
2883
- }
2884
- });
2885
- return;
2886
- }
2887
- handleRootRequestLogin(req, res, domain, '', passRequirements);
2888
- }
2889
- }
2890
-
2891
- // Return a list of server supported features for a given domain and user
2892
- obj.getDomainUserFeatures = function(domain, user, req) {
2893
- var features = 0;
2894
- var features2 = 0;
2895
- if (obj.args.wanonly == true) { features += 0x00000001; } // WAN-only mode
2896
- if (obj.args.lanonly == true) { features += 0x00000002; } // LAN-only mode
2897
- if (obj.args.nousers == true) { features += 0x00000004; } // Single user mode
2898
- if (domain.userQuota == -1) { features += 0x00000008; } // No server files mode
2899
- if (obj.args.mpstlsoffload) { features += 0x00000010; } // No mutual-auth CIRA
2900
- if ((parent.config.settings.allowframing != null) || (domain.allowframing != null)) { features += 0x00000020; } // Allow site within iframe
2901
- if ((domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true)) { features += 0x00000040; } // Email invites
2902
- if (obj.args.webrtc == true) { features += 0x00000080; } // Enable WebRTC (Default false for now)
2903
- // 0x00000100 --> This feature flag is free for future use.
2904
- if (obj.args.allowhighqualitydesktop !== false) { features += 0x00000200; } // Enable AllowHighQualityDesktop (Default true)
2905
- if ((obj.args.lanonly == true) || (obj.args.mpsport == 0)) { features += 0x00000400; } // No CIRA
2906
- if ((obj.parent.serverSelfWriteAllowed == true) && (user != null) && ((user.siteadmin & 0x00000010) != 0)) { features += 0x00000800; } // Server can self-write (Allows self-update)
2907
- if ((parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (user._id.split('/')[2][0] != '~')) { features += 0x00001000; } // 2FA login supported
2908
- if (domain.agentnoproxy === true) { features += 0x00002000; } // Indicates that agents should be installed without using a HTTP proxy
2909
- if ((parent.config.settings.no2factorauth !== true) && domain.yubikey && domain.yubikey.id && domain.yubikey.secret && (user._id.split('/')[2][0] != '~')) { features += 0x00004000; } // Indicates Yubikey support
2910
- if (domain.geolocation == true) { features += 0x00008000; } // Enable geo-location features
2911
- if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { features += 0x00010000; } // Enable password hints
2912
- if (parent.config.settings.no2factorauth !== true) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support
2913
- if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true) && (user._id.split('/')[2][0] != '~')) {
2914
- // Check if we can skip 2nd factor auth because of the source IP address
2915
- var skip2factor = false;
2916
- if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
2917
- for (var i in domain.passwordrequirements.skip2factor) {
2918
- if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) { skip2factor = true; }
2919
- }
2920
- }
2921
- if (skip2factor == false) { features += 0x00040000; } // Force 2-factor auth
2922
- }
2923
- if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { features += 0x00080000; } // LDAP or SSPI in use, warn that users must login first before adding a user to a group.
2924
- if (domain.amtacmactivation) { features += 0x00100000; } // Intel AMT ACM activation/upgrade is possible
2925
- if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
2926
- if (parent.mqttbroker != null) { features += 0x00400000; } // This server supports MQTT channels
2927
- if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null)) { features += 0x00800000; } // using email for 2FA is allowed
2928
- if (domain.agentinvitecodes == true) { features += 0x01000000; } // Support for agent invite codes
2929
- if (parent.smsserver != null) { features += 0x02000000; } // SMS messaging is supported
2930
- if ((parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false))) { features += 0x04000000; } // SMS 2FA is allowed
2931
- if (domain.sessionrecording != null) { features += 0x08000000; } // Server recordings enabled
2932
- if (domain.urlswitching === false) { features += 0x10000000; } // Disables the URL switching feature
2933
- if (domain.novnc === false) { features += 0x20000000; } // Disables noVNC
2934
- if (domain.mstsc !== true) { features += 0x40000000; } // Disables MSTSC.js
2935
- if (obj.isTrustedCert(domain) == false) { features += 0x80000000; } // Indicate we are not using a trusted certificate
2936
- if (obj.parent.amtManager != null) { features2 += 0x00000001; } // Indicates that the Intel AMT manager is active
2937
- if (obj.parent.firebase != null) { features2 += 0x00000002; } // Indicates the server supports Firebase push messaging
2938
- if ((obj.parent.firebase != null) && (obj.parent.firebase.pushOnly != true)) { features2 += 0x00000004; } // Indicates the server supports Firebase two-way push messaging
2939
- if (obj.parent.webpush != null) { features2 += 0x00000008; } // Indicates web push is enabled
2940
- if (((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true))) { features2 += 0x00000010; } // No agent update
2941
- if (parent.amtProvisioningServer != null) { features2 += 0x00000020; } // Intel AMT LAN provisioning server
2942
- if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.push2factor != false)) && (obj.parent.firebase != null)) { features2 += 0x00000040; } // Indicates device push notification 2FA is enabled
2943
- if ((typeof domain.passwordrequirements != 'object') || ((domain.passwordrequirements.logintokens !== false) && ((Array.isArray(domain.passwordrequirements.logintokens) == false) || (domain.passwordrequirements.logintokens.indexOf(user._id) >= 0)))) { features2 += 0x00000080; } // Indicates login tokens are allowed
2944
- if (req.session.loginToken != null) { features2 += 0x00000100; } // LoginToken mode, no account changes.
2945
- if (domain.ssh == true) { features2 += 0x00000200; } // SSH is enabled
2946
- if (domain.localsessionrecording === false) { features2 += 0x00000400; } // Disable local recording feature
2947
- if (domain.clipboardget == false) { features2 += 0x00000800; } // Disable clipboard get
2948
- if (domain.clipboardset == false) { features2 += 0x00001000; } // Disable clipboard set
2949
- if ((typeof domain.desktop == 'object') && (domain.desktop.viewonly == true)) { features2 += 0x00002000; } // Indicates remote desktop is viewonly
2950
- if (domain.mailserver != null) { features2 += 0x00004000; } // Indicates email server is active
2951
- if (domain.devicesearchbarserverandclientname) { features2 += 0x00008000; } // Search bar will find both server name and client name
2952
- if (domain.ipkvm) { features2 += 0x00010000; } // Indicates support for IP KVM device groups
2953
- if ((domain.passwordrequirements) && (domain.passwordrequirements.otp2factor == false)) { features2 += 0x00020000; } // Indicates support for OTP 2FA is disabled
2954
- if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.backupcode2factor === false)) { features2 += 0x00040000; } // Indicates 2FA backup codes are disabled
2955
- if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.single2factorwarning === false)) { features2 += 0x00080000; } // Indicates no warning if a single 2FA is in use
2956
- if (domain.nightmode === 1) { features2 += 0x00100000; } // Always night mode
2957
- if (domain.nightmode === 2) { features2 += 0x00200000; } // Always day mode
2958
- if (domain.allowsavingdevicecredentials == false) { features2 += 0x00400000; } // Do not allow device credentials to be saved on the server
2959
- return { features: features, features2: features2 };
2960
- }
2961
-
2962
- function handleRootRequestLogin(req, res, domain, hardwareKeyChallenge, passRequirements) {
2963
- parent.debug('web', 'handleRootRequestLogin()');
2964
- var features = 0;
2965
- if ((parent.config != null) && (parent.config.settings != null) && ((parent.config.settings.allowframing == true) || (typeof parent.config.settings.allowframing == 'string'))) { features += 32; } // Allow site within iframe
2966
- if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
2967
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2968
- var loginmode = 0;
2969
- if (req.session) { loginmode = req.session.loginmode; delete req.session.loginmode; } // Clear this state, if the user hits refresh, we want to go back to the login page.
2970
-
2971
- // Format an error message if needed
2972
- var passhint = null, msgid = 0;
2973
- if (req.session != null) {
2974
- msgid = req.session.messageid;
2975
- if ((msgid == 5) || (loginmode == 7) || ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true))) { passhint = EscapeHtml(req.session.passhint); }
2976
- delete req.session.messageid;
2977
- delete req.session.passhint;
2978
- }
2979
- const allowAccountReset = ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.allowaccountreset !== false));
2980
- const emailcheck = (allowAccountReset && (domain.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
2981
-
2982
- // Check if we are allowed to create new users using the login screen
2983
- var newAccountsAllowed = true;
2984
- if ((domain.newaccounts !== 1) && (domain.newaccounts !== true)) { for (var i in obj.users) { if (obj.users[i].domain == domain.id) { newAccountsAllowed = false; break; } } }
2985
- if (parent.config.settings.maintenancemode != null) { newAccountsAllowed = false; }
2986
-
2987
- // Encrypt the hardware key challenge state if needed
2988
- var hwstate = null;
2989
- if (hardwareKeyChallenge) { hwstate = obj.parent.encodeCookie({ u: req.session.tuser, p: req.session.tpass, c: req.session.u2f }, obj.parent.loginCookieEncryptionKey) }
2990
-
2991
- // Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5).
2992
- var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string'));
2993
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
2994
- var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1);
2995
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
2996
- var otppush = (parent.firebase != null) && (req.session != null) && (req.session.tpush === 1);
2997
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { otppush = false; }
2998
- const autofido = ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.autofido2fa == true)); // See if FIDO should be automatically prompted if user account has it.
2999
-
3000
- // See if we support two-factor trusted cookies
3001
- var twoFactorCookieDays = 30;
3002
- if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; }
3003
-
3004
- // See what authentication strategies we have
3005
- var authStrategies = [];
3006
- if (typeof domain.authstrategies == 'object') {
3007
- if (typeof domain.authstrategies.twitter == 'object') { authStrategies.push('twitter'); }
3008
- if (typeof domain.authstrategies.google == 'object') { authStrategies.push('google'); }
3009
- if (typeof domain.authstrategies.github == 'object') { authStrategies.push('github'); }
3010
- if (typeof domain.authstrategies.reddit == 'object') { authStrategies.push('reddit'); }
3011
- if (typeof domain.authstrategies.azure == 'object') { authStrategies.push('azure'); }
3012
- if (typeof domain.authstrategies.oidc == 'object') { authStrategies.push('oidc'); }
3013
- if (typeof domain.authstrategies.intel == 'object') { authStrategies.push('intel'); }
3014
- if (typeof domain.authstrategies.jumpcloud == 'object') { authStrategies.push('jumpcloud'); }
3015
- if (typeof domain.authstrategies.saml == 'object') { authStrategies.push('saml'); }
3016
- }
3017
-
3018
- // Custom user interface
3019
- var customui = '';
3020
- if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
3021
-
3022
- // Get two-factor screen timeout
3023
- var twoFactorTimeout = 300000; // Default is 5 minutes, 0 for no timeout.
3024
- if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.twofactortimeout == 'number')) {
3025
- twoFactorTimeout = domain.passwordrequirements.twofactortimeout * 1000;
3026
- }
3027
-
3028
- // Render the login page
3029
- render(req, res,
3030
- getRenderPage((domain.sitestyle == 2) ? 'login2' : 'login', req, domain),
3031
- getRenderArgs({
3032
- loginmode: loginmode,
3033
- rootCertLink: getRootCertLink(domain),
3034
- newAccount: newAccountsAllowed,
3035
- newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1),
3036
- serverDnsName: obj.getWebServerName(domain),
3037
- serverPublicPort: httpsPort,
3038
- passlogin: (typeof domain.showpasswordlogin == 'boolean') ? domain.showpasswordlogin : true,
3039
- emailcheck: emailcheck,
3040
- features: features,
3041
- sessiontime: (args.sessiontime) ? args.sessiontime : 60,
3042
- passRequirements: passRequirements,
3043
- customui: customui,
3044
- footer: (domain.loginfooter == null) ? '' : domain.loginfooter,
3045
- hkey: encodeURIComponent(hardwareKeyChallenge).replace(/'/g, '%27'),
3046
- messageid: msgid,
3047
- passhint: passhint,
3048
- welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null,
3049
- welcomePictureFullScreen: ((typeof domain.welcomepicturefullscreen == 'boolean') ? domain.welcomepicturefullscreen : false),
3050
- hwstate: hwstate,
3051
- otpemail: otpemail,
3052
- otpsms: otpsms,
3053
- otppush: otppush,
3054
- autofido: autofido,
3055
- twoFactorCookieDays: twoFactorCookieDays,
3056
- authStrategies: authStrategies.join(','),
3057
- loginpicture: (typeof domain.loginpicture == 'string'),
3058
- tokenTimeout: twoFactorTimeout // Two-factor authentication screen timeout in milliseconds
3059
- }, req, domain, (domain.sitestyle == 2) ? 'login2' : 'login'));
3060
- }
3061
-
3062
- // Handle a post request on the root
3063
- function handleRootPostRequest(req, res) {
3064
- const domain = checkUserIpAddress(req, res);
3065
- if (domain == null) { return; }
3066
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.end("Not Found"); return; } // Check 3FA URL key
3067
- parent.debug('web', 'handleRootPostRequest, action: ' + req.body.action);
3068
-
3069
- switch (req.body.action) {
3070
- case 'login': { handleLoginRequest(req, res, true); break; }
3071
- case 'tokenlogin': {
3072
- if (req.body.hwstate) {
3073
- var cookie = obj.parent.decodeCookie(req.body.hwstate, obj.parent.loginCookieEncryptionKey, 10);
3074
- if (cookie != null) { req.session.tuser = cookie.u; req.session.tpass = cookie.p; req.session.u2f = cookie.c; }
3075
- }
3076
- handleLoginRequest(req, res, true); break;
3077
- }
3078
- case 'pushlogin': {
3079
- if (req.body.hwstate) {
3080
- var cookie = obj.parent.decodeCookie(req.body.hwstate, obj.parent.loginCookieEncryptionKey, 1);
3081
- if ((cookie != null) && (typeof cookie.u == 'string') && (cookie.d == domain.id) && (cookie.a == 'pushAuth')) {
3082
- // Push authentication is a success, login the user
3083
- req.session = { userid: cookie.u };
3084
-
3085
- // Check if we need to remember this device
3086
- if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
3087
- var maxCookieAge = domain.twofactorcookiedurationdays;
3088
- if (typeof maxCookieAge != 'number') { maxCookieAge = 30; }
3089
- const twoFactorCookie = obj.parent.encodeCookie({ userid: cookie.u, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, obj.parent.loginCookieEncryptionKey);
3090
- res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: ((parent.config.settings.cookieipcheck === false) ? 'none' : 'strict'), secure: true });
3091
- }
3092
-
3093
- handleRootRequestEx(req, res, domain);
3094
- return;
3095
- }
3096
- }
3097
- handleLoginRequest(req, res, true); break;
3098
- }
3099
- case 'changepassword': { handlePasswordChangeRequest(req, res, true); break; }
3100
- case 'deleteaccount': { handleDeleteAccountRequest(req, res, true); break; }
3101
- case 'createaccount': { handleCreateAccountRequest(req, res, true); break; }
3102
- case 'resetpassword': { handleResetPasswordRequest(req, res, true); break; }
3103
- case 'resetaccount': { handleResetAccountRequest(req, res, true); break; }
3104
- case 'checkemail': { handleCheckAccountEmailRequest(req, res, true); break; }
3105
- default: { handleLoginRequest(req, res, true); break; }
3106
- }
3107
- }
3108
-
3109
- // Return true if it looks like we are using a real TLS certificate.
3110
- obj.isTrustedCert = function (domain) {
3111
- if ((domain != null) && (typeof domain.trustedcert == 'boolean')) return domain.trustedcert; // If the status of the cert specified, use that.
3112
- if (typeof obj.args.trustedcert == 'boolean') return obj.args.trustedcert; // If the status of the cert specified, use that.
3113
- if (obj.args.tlsoffload != null) return true; // We are using TLS offload, a real cert is likely used.
3114
- if (obj.parent.config.letsencrypt != null) return (obj.parent.config.letsencrypt.production === true); // We are using Let's Encrypt, real cert in use if production is set to true.
3115
- if (obj.certificates.WebIssuer.indexOf('MeshCentralRoot-') == 0) return false; // Our cert is issued by self-signed cert.
3116
- if (obj.certificates.CommonName.indexOf('.') == -1) return false; // Our cert is named with a fake name
3117
- return true; // This is a guess
3118
- }
3119
-
3120
- // Get the link to the root certificate if needed
3121
- function getRootCertLink(domain) {
3122
- // Check if the HTTPS certificate is issued from MeshCentralRoot, if so, add download link to root certificate.
3123
- if (obj.isTrustedCert(domain) == false) {
3124
- // Get the domain suffix
3125
- var xdomain = (domain.dns == null) ? domain.id : '';
3126
- if (xdomain != '') xdomain += '/';
3127
- return '<a href=/' + xdomain + 'MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>';
3128
- }
3129
- return '';
3130
- }
3131
-
3132
- // Serve the xterm page
3133
- function handleXTermRequest(req, res) {
3134
- const domain = checkUserIpAddress(req, res);
3135
- if (domain == null) { return; }
3136
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3137
-
3138
- parent.debug('web', 'handleXTermRequest: sending xterm');
3139
- res.set({ 'Cache-Control': 'no-store' });
3140
- if (req.session && req.session.userid) {
3141
- if (req.session.userid.split('/')[1] != domain.id) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3142
- var user = obj.users[req.session.userid];
3143
- if ((user == null) || (req.query.nodeid == null)) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the user exists
3144
-
3145
- // Check permissions
3146
- obj.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
3147
- if ((node == null) || ((rights & 8) == 0) || ((rights != 0xFFFFFFFF) && ((rights & 512) != 0))) { res.redirect(domain.url + getQueryPortion(req)); return; }
3148
-
3149
- var logoutcontrols = { name: user.name };
3150
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3151
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3152
-
3153
- // Create a authentication cookie
3154
- const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey);
3155
- const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
3156
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3157
- render(req, res, getRenderPage('xterm', req, domain), getRenderArgs({ serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, authCookie: authCookie, authRelayCookie: authRelayCookie, logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'), name: EscapeHtml(node.name) }, req, domain));
3158
- });
3159
- } else {
3160
- res.redirect(domain.url + getQueryPortion(req));
3161
- return;
3162
- }
3163
- }
3164
-
3165
- // Render the terms of service.
3166
- function handleTermsRequest(req, res) {
3167
- const domain = checkUserIpAddress(req, res);
3168
- if (domain == null) { return; }
3169
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3170
-
3171
- // See if term.txt was loaded from the database
3172
- if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) {
3173
- // Send the terms from the database
3174
- res.set({ 'Cache-Control': 'no-store' });
3175
- if (req.session && req.session.userid) {
3176
- if (req.session.userid.split('/')[1] != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3177
- var user = obj.users[req.session.userid];
3178
- var logoutcontrols = { name: user.name };
3179
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3180
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3181
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
3182
- } else {
3183
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: encodeURIComponent('{}') }, req, domain));
3184
- }
3185
- } else {
3186
- // See if there is a terms.txt file in meshcentral-data
3187
- var p = obj.path.join(obj.parent.datapath, 'terms.txt');
3188
- if (obj.fs.existsSync(p)) {
3189
- obj.fs.readFile(p, 'utf8', function (err, data) {
3190
- if (err != null) { parent.debug('web', 'handleTermsRequest: no terms.txt'); res.sendStatus(404); return; }
3191
-
3192
- // Send the terms from terms.txt
3193
- res.set({ 'Cache-Control': 'no-store' });
3194
- if (req.session && req.session.userid) {
3195
- if (req.session.userid.split('/')[1] != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3196
- var user = obj.users[req.session.userid];
3197
- var logoutcontrols = { name: user.name };
3198
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3199
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3200
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
3201
- } else {
3202
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: encodeURIComponent('{}') }, req, domain));
3203
- }
3204
- });
3205
- } else {
3206
- // Send the default terms
3207
- parent.debug('web', 'handleTermsRequest: sending default terms');
3208
- res.set({ 'Cache-Control': 'no-store' });
3209
- if (req.session && req.session.userid) {
3210
- if (req.session.userid.split('/')[1] != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
3211
- var user = obj.users[req.session.userid];
3212
- var logoutcontrols = { name: user.name };
3213
- var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
3214
- if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
3215
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
3216
- } else {
3217
- render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: encodeURIComponent('{}') }, req, domain));
3218
- }
3219
- }
3220
- }
3221
- }
3222
-
3223
- // Render the messenger application.
3224
- function handleMessengerRequest(req, res) {
3225
- const domain = getDomain(req);
3226
- if (domain == null) { parent.debug('web', 'handleMessengerRequest: no domain'); res.sendStatus(404); return; }
3227
- parent.debug('web', 'handleMessengerRequest()');
3228
-
3229
- // Check if we are in maintenance mode
3230
- if (parent.config.settings.maintenancemode != null) {
3231
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 3, msgid: 13, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain));
3232
- return;
3233
- }
3234
-
3235
- // Check if this session is for a user
3236
- if (req.query.id == null) { res.sendStatus(404); return; }
3237
- var idSplit = decodeURIComponent(req.query.id).split('/');
3238
- if ((idSplit.length != 7) || (idSplit[0] != 'meshmessenger')) { res.sendStatus(404); return; }
3239
- if ((idSplit[1] == 'user') && (idSplit[4] == 'user')) {
3240
- // This is a user to user conversation, both users must be logged in.
3241
- var user1 = idSplit[1] + '/' + idSplit[2] + '/' + idSplit[3]
3242
- var user2 = idSplit[4] + '/' + idSplit[5] + '/' + idSplit[6]
3243
- if (!req.session || !req.session.userid) {
3244
- // Redirect to login page
3245
- if (req.query.key != null) { res.redirect(domain.url + '?key=' + req.query.key + '&meshmessengerid=' + req.query.id); } else { res.redirect(domain.url + '?meshmessengerid=' + req.query.id); }
3246
- return;
3247
- }
3248
- if ((req.session.userid != user1) && (req.session.userid != user2)) { res.sendStatus(404); return; }
3249
- }
3250
-
3251
- // Get WebRTC configuration
3252
- var webRtcConfig = null;
3253
- if (obj.parent.config.settings && obj.parent.config.settings.webrtconfig && (typeof obj.parent.config.settings.webrtconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(obj.parent.config.settings.webrtconfig)).replace(/'/g, '%27'); }
3254
- else if (args.webrtconfig && (typeof args.webrtconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtconfig)).replace(/'/g, '%27'); }
3255
-
3256
- // Setup other options
3257
- var options = { webrtconfig: webRtcConfig };
3258
- if (typeof domain.meshmessengertitle == 'string') { options.meshMessengerTitle = domain.meshmessengertitle; } else { options.meshMessengerTitle = '!'; }
3259
-
3260
- // Get the userid and name
3261
- if ((domain.meshmessengertitle != null) && (req.query.id != null) && (req.query.id.startsWith('meshmessenger/node'))) {
3262
- if (idSplit.length == 7) {
3263
- const user = obj.users[idSplit[4] + '/' + idSplit[5] + '/' + idSplit[6]];
3264
- if (user != null) {
3265
- if (domain.meshmessengertitle.indexOf('{0}') >= 0) { options.username = encodeURIComponent(user.realname ? user.realname : user.name).replace(/'/g, '%27'); }
3266
- if (domain.meshmessengertitle.indexOf('{1}') >= 0) { options.userid = encodeURIComponent(user.name).replace(/'/g, '%27'); }
3267
- }
3268
- }
3269
- }
3270
-
3271
- // Render the page
3272
- res.set({ 'Cache-Control': 'no-store' });
3273
- render(req, res, getRenderPage('messenger', req, domain), getRenderArgs(options, req, domain));
3274
- }
3275
-
3276
- // Handle messenger image request
3277
- function handleMessengerImageRequest(req, res) {
3278
- const domain = getDomain(req);
3279
- if (domain == null) { parent.debug('web', 'handleMessengerImageRequest: no domain'); res.sendStatus(404); return; }
3280
- parent.debug('web', 'handleMessengerImageRequest()');
3281
-
3282
- // Check if we are in maintenance mode
3283
- if (parent.config.settings.maintenancemode != null) { res.sendStatus(404); return; }
3284
-
3285
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3286
- if (domain.meshmessengerpicture) {
3287
- // Use the configured messenger logo picture
3288
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.meshmessengerpicture)); return; } catch (ex) { }
3289
- }
3290
-
3291
- var imagefile = 'images/messenger.png';
3292
- if (domain.webpublicpath != null) {
3293
- obj.fs.exists(obj.path.join(domain.webpublicpath, imagefile), function (exists) {
3294
- if (exists) {
3295
- // Use the domain logo picture
3296
- try { res.sendFile(obj.path.join(domain.webpublicpath, imagefile)); } catch (ex) { res.sendStatus(404); }
3297
- } else {
3298
- // Use the default logo picture
3299
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3300
- }
3301
- });
3302
- } else if (parent.webPublicOverridePath) {
3303
- obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, imagefile), function (exists) {
3304
- if (exists) {
3305
- // Use the override logo picture
3306
- try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, imagefile)); } catch (ex) { res.sendStatus(404); }
3307
- } else {
3308
- // Use the default logo picture
3309
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3310
- }
3311
- });
3312
- } else {
3313
- // Use the default logo picture
3314
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3315
- }
3316
- }
3317
-
3318
- // Returns the server root certificate encoded in base64
3319
- function getRootCertBase64() {
3320
- var rootcert = obj.certificates.root.cert;
3321
- var i = rootcert.indexOf('-----BEGIN CERTIFICATE-----\r\n');
3322
- if (i >= 0) { rootcert = rootcert.substring(i + 29); }
3323
- i = rootcert.indexOf('-----END CERTIFICATE-----');
3324
- if (i >= 0) { rootcert = rootcert.substring(i, 0); }
3325
- return Buffer.from(rootcert, 'base64').toString('base64');
3326
- }
3327
-
3328
- // Returns the mesh server root certificate
3329
- function handleRootCertRequest(req, res) {
3330
- const domain = getDomain(req);
3331
- if (domain == null) { parent.debug('web', 'handleRootCertRequest: no domain'); res.sendStatus(404); return; }
3332
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3333
- if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { parent.debug('web', 'handleRootCertRequest: invalid ip'); return; } // Check server-wide IP filter only.
3334
- parent.debug('web', 'handleRootCertRequest()');
3335
- setContentDispositionHeader(res, 'application/octet-stream', certificates.RootName + '.cer', null, 'rootcert.cer');
3336
- res.send(Buffer.from(getRootCertBase64(), 'base64'));
3337
- }
3338
-
3339
- // Handle user public file downloads
3340
- function handleDownloadUserFiles(req, res) {
3341
- const domain = checkUserIpAddress(req, res);
3342
- if (domain == null) { return; }
3343
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3344
-
3345
- if (obj.common.validateString(req.path, 1, 4096) == false) { res.sendStatus(404); return; }
3346
- var domainname = 'domain', spliturl = decodeURIComponent(req.path).split('/'), filename = '';
3347
- if ((spliturl.length < 3) || (obj.common.IsFilenameValid(spliturl[2]) == false) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
3348
- if (domain.id != '') { domainname = 'domain-' + domain.id; }
3349
- var path = obj.path.join(obj.filespath, domainname + '/user-' + spliturl[2] + '/Public');
3350
- 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; } }
3351
-
3352
- var stat = null;
3353
- try { stat = obj.fs.statSync(path); } catch (e) { }
3354
- if ((stat != null) && ((stat.mode & 0x004000) == 0)) {
3355
- if (req.query.download == 1) {
3356
- setContentDispositionHeader(res, 'application/octet-stream', filename, null, 'file.bin');
3357
- try { res.sendFile(obj.path.resolve(__dirname, path)); } catch (e) { res.sendStatus(404); }
3358
- } else {
3359
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'download2' : 'download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(domain), messageid: 1, fileurl: req.path + '?download=1', filename: filename, filesize: stat.size }, req, domain));
3360
- }
3361
- } else {
3362
- render(req, res, getRenderPage((domain.sitestyle == 2) ? 'download2' : 'download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(domain), messageid: 2 }, req, domain));
3363
- }
3364
- }
3365
-
3366
- // Handle device file request
3367
- function handleDeviceFile(req, res) {
3368
- const domain = checkUserIpAddress(req, res);
3369
- if (domain == null) { return; }
3370
- if ((req.query.c == null) || (req.query.f == null)) { res.sendStatus(404); return; }
3371
-
3372
- // Check the inbound desktop sharing cookie
3373
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3374
- if ((c == null) || (c.domainid !== domain.id)) { res.sendStatus(404); return; }
3375
-
3376
- // Check userid
3377
- const user = obj.users[c.userid];
3378
- if ((c == user)) { res.sendStatus(404); return; }
3379
-
3380
- // If this cookie has restricted usages, check that it's allowed to perform downloads
3381
- if (Array.isArray(c.usages) && (c.usages.indexOf(10) < 0)) { res.sendStatus(404); return; } // Check protocol #10
3382
-
3383
- if (c.nid != null) { req.query.n = c.nid.split('/')[2]; } // This cookie is restricted to a specific nodeid.
3384
- if (req.query.n == null) { res.sendStatus(404); return; }
3385
-
3386
- // Check if this user has permission to manage this computer
3387
- obj.GetNodeWithRights(domain, user, 'node/' + domain.id + '/' + req.query.n, function (node, rights, visible) {
3388
- if ((node == null) || ((rights & MESHRIGHT_REMOTECONTROL) == 0) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
3389
-
3390
- // All good, start the file transfer
3391
- req.query.id = getRandomLowerCase(12);
3392
- obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, null, res, req, domain, user, node.meshid, node._id);
3393
- });
3394
- }
3395
-
3396
- // Handle download of a server file by an agent
3397
- function handleAgentDownloadFile(req, res) {
3398
- const domain = checkUserIpAddress(req, res);
3399
- if (domain == null) { return; }
3400
- if (req.query.c == null) { res.sendStatus(404); return; }
3401
-
3402
- // Check the inbound desktop sharing cookie
3403
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 5); // 5 minute timeout
3404
- if ((c == null) || (c.a != 'tmpdl') || (c.d != domain.id) || (c.nid == null) || (c.f == null) || (obj.common.IsFilenameValid(c.f) == false)) { res.sendStatus(404); return; }
3405
-
3406
- // Send the file back
3407
- try { res.sendFile(obj.path.join(obj.filespath, 'tmp', c.f)); return; } catch (ex) { res.sendStatus(404); }
3408
- }
3409
-
3410
- // Handle logo request
3411
- function handleLogoRequest(req, res) {
3412
- const domain = checkUserIpAddress(req, res);
3413
- if (domain == null) { return; }
3414
-
3415
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3416
- if (domain.titlepicture) {
3417
- if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.titlepicture] != null)) {
3418
- // Use the logo in the database
3419
- res.set({ 'Content-Type': domain.titlepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
3420
- res.send(parent.configurationFiles[domain.titlepicture]);
3421
- return;
3422
- } else {
3423
- // Use the logo on file
3424
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.titlepicture)); return; } catch (ex) { }
3425
- }
3426
- }
3427
-
3428
- if ((domain.webpublicpath != null) && (obj.fs.existsSync(obj.path.join(domain.webpublicpath, 'images/logoback.png')))) {
3429
- // Use the domain logo picture
3430
- try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
3431
- } else if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png'))) {
3432
- // Use the override logo picture
3433
- try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
3434
- } else {
3435
- // Use the default logo picture
3436
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
3437
- }
3438
- }
3439
-
3440
- // Handle login logo request
3441
- function handleLoginLogoRequest(req, res) {
3442
- const domain = checkUserIpAddress(req, res);
3443
- if (domain == null) { return; }
3444
-
3445
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3446
- if (domain.loginpicture) {
3447
- if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.loginpicture] != null)) {
3448
- // Use the logo in the database
3449
- res.set({ 'Content-Type': domain.loginpicture.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg' });
3450
- res.send(parent.configurationFiles[domain.loginpicture]);
3451
- return;
3452
- } else {
3453
- // Use the logo on file
3454
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.loginpicture)); return; } catch (ex) { res.sendStatus(404); }
3455
- }
3456
- } else {
3457
- res.sendStatus(404);
3458
- }
3459
- }
3460
-
3461
- // Handle translation request
3462
- function handleTranslationsRequest(req, res) {
3463
- const domain = checkUserIpAddress(req, res);
3464
- if (domain == null) { return; }
3465
- //if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
3466
- if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { return; } // Check server-wide IP filter only.
3467
-
3468
- var user = null;
3469
- if (obj.args.user != null) {
3470
- // A default user is active
3471
- user = obj.users['user/' + domain.id + '/' + obj.args.user];
3472
- if (!user) { parent.debug('web', 'handleTranslationsRequest: user not found.'); res.sendStatus(401); return; }
3473
- } else {
3474
- // Check if the user is logged and we have all required parameters
3475
- if (!req.session || !req.session.userid) { parent.debug('web', 'handleTranslationsRequest: failed checks (2).'); res.sendStatus(401); return; }
3476
-
3477
- // Get the current user
3478
- user = obj.users[req.session.userid];
3479
- if (!user) { parent.debug('web', 'handleTranslationsRequest: user not found.'); res.sendStatus(401); return; }
3480
- if (user.siteadmin != 0xFFFFFFFF) { parent.debug('web', 'handleTranslationsRequest: user not site administrator.'); res.sendStatus(401); return; }
3481
- }
3482
-
3483
- var data = '';
3484
- req.setEncoding('utf8');
3485
- req.on('data', function (chunk) { data += chunk; });
3486
- req.on('end', function () {
3487
- try { data = JSON.parse(data); } catch (ex) { data = null; }
3488
- if (data == null) { res.sendStatus(404); return; }
3489
- if (data.action == 'getTranslations') {
3490
- if (obj.fs.existsSync(obj.path.join(obj.parent.datapath, 'translate.json'))) {
3491
- // Return the translation file (JSON)
3492
- try { res.sendFile(obj.path.join(obj.parent.datapath, 'translate.json')); } catch (ex) { res.sendStatus(404); }
3493
- } else if (obj.fs.existsSync(obj.path.join(__dirname, 'translate', 'translate.json'))) {
3494
- // Return the default translation file (JSON)
3495
- try { res.sendFile(obj.path.join(__dirname, 'translate', 'translate.json')); } catch (ex) { res.sendStatus(404); }
3496
- } else { res.sendStatus(404); }
3497
- } else if (data.action == 'setTranslations') {
3498
- obj.fs.writeFile(obj.path.join(obj.parent.datapath, 'translate.json'), obj.common.translationsToJson({ strings: data.strings }), function (err) { if (err == null) { res.send(JSON.stringify({ response: 'ok' })); } else { res.send(JSON.stringify({ response: err })); } });
3499
- } else if (data.action == 'translateServer') {
3500
- if (obj.pendingTranslation === true) { res.send(JSON.stringify({ response: 'Server is already performing a translation.' })); return; }
3501
- const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
3502
- if (nodeVersion < 8) { res.send(JSON.stringify({ response: 'Server requires NodeJS 8.x or better.' })); return; }
3503
- var translateFile = obj.path.join(obj.parent.datapath, 'translate.json');
3504
- if (obj.fs.existsSync(translateFile) == false) { translateFile = obj.path.join(__dirname, 'translate', 'translate.json'); }
3505
- if (obj.fs.existsSync(translateFile) == false) { res.send(JSON.stringify({ response: 'Unable to find translate.js file on the server.' })); return; }
3506
- res.send(JSON.stringify({ response: 'ok' }));
3507
- console.log('Started server translation...');
3508
- obj.pendingTranslation = true;
3509
- require('child_process').exec('node translate.js translateall \"' + translateFile + '\"', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, 'translate') }, function (error, stdout, stderr) {
3510
- delete obj.pendingTranslation;
3511
- //console.log('error', error);
3512
- //console.log('stdout', stdout);
3513
- //console.log('stderr', stderr);
3514
- //console.log('Server restart...'); // Perform a server restart
3515
- //process.exit(0);
3516
- console.log('Server translation completed.');
3517
- });
3518
- } else {
3519
- // Unknown request
3520
- res.sendStatus(404);
3521
- }
3522
- });
3523
- }
3524
-
3525
- // Handle welcome image request
3526
- function handleWelcomeImageRequest(req, res) {
3527
- const domain = checkUserIpAddress(req, res);
3528
- if (domain == null) { return; }
3529
-
3530
- //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
3531
- if (domain.welcomepicture) {
3532
- if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.welcomepicture] != null)) {
3533
- // Use the welcome image in the database
3534
- res.set({ 'Content-Type': domain.welcomepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
3535
- res.send(parent.configurationFiles[domain.welcomepicture]);
3536
- return;
3537
- }
3538
-
3539
- // Use the configured logo picture
3540
- try { res.sendFile(obj.common.joinPath(obj.parent.datapath, domain.welcomepicture)); return; } catch (ex) { }
3541
- }
3542
-
3543
- var imagefile = 'images/mainwelcome.jpg';
3544
- if (domain.sitestyle == 2) { imagefile = 'images/login/back.png'; }
3545
- if (domain.webpublicpath != null) {
3546
- obj.fs.exists(obj.path.join(domain.webpublicpath, imagefile), function (exists) {
3547
- if (exists) {
3548
- // Use the domain logo picture
3549
- try { res.sendFile(obj.path.join(domain.webpublicpath, imagefile)); } catch (ex) { res.sendStatus(404); }
3550
- } else {
3551
- // Use the default logo picture
3552
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3553
- }
3554
- });
3555
- } else if (parent.webPublicOverridePath) {
3556
- obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, imagefile), function (exists) {
3557
- if (exists) {
3558
- // Use the override logo picture
3559
- try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, imagefile)); } catch (ex) { res.sendStatus(404); }
3560
- } else {
3561
- // Use the default logo picture
3562
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3563
- }
3564
- });
3565
- } else {
3566
- // Use the default logo picture
3567
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
3568
- }
3569
- }
3570
-
3571
- // Download a session recording
3572
- function handleGetRecordings(req, res) {
3573
- const domain = checkUserIpAddress(req, res);
3574
- if (domain == null) return;
3575
-
3576
- // Check the query
3577
- if ((domain.sessionrecording == null) || (req.query.file == null) || (obj.common.IsFilenameValid(req.query.file) !== true) || (req.query.file.endsWith('.mcrec') == false)) { res.sendStatus(401); return; }
3578
-
3579
- // Get the recording path
3580
- var recordingsPath = null;
3581
- if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.recordpath; }
3582
- if (recordingsPath == null) { res.sendStatus(401); return; }
3583
-
3584
- // Get the user and check user rights
3585
- var authUserid = null;
3586
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3587
- if (authUserid == null) { res.sendStatus(401); return; }
3588
- const user = obj.users[authUserid];
3589
- if (user == null) { res.sendStatus(401); return; }
3590
- if ((user.siteadmin & 512) == 0) { res.sendStatus(401); return; } // Check if we have right to get recordings
3591
-
3592
- // Send the recorded file
3593
- setContentDispositionHeader(res, 'application/octet-stream', req.query.file, null, 'recording.mcrec');
3594
- try { res.sendFile(obj.path.join(recordingsPath, req.query.file)); } catch (ex) { res.sendStatus(404); }
3595
- }
3596
-
3597
- // Stream a session recording
3598
- function handleGetRecordingsWebSocket(ws, req) {
3599
- var domain = checkAgentIpAddress(ws, req);
3600
- if (domain == null) { parent.debug('web', 'Got recordings file transfer connection with bad domain or blocked IP address ' + req.clientIp + ', dropping.'); try { ws.close(); } catch (ex) { } return; }
3601
-
3602
- // Check the query
3603
- if ((domain.sessionrecording == null) || (req.query.file == null) || (obj.common.IsFilenameValid(req.query.file) !== true) || (req.query.file.endsWith('.mcrec') == false)) { try { ws.close(); } catch (ex) { } return; }
3604
-
3605
- // Get the recording path
3606
- var recordingsPath = null;
3607
- if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.recordpath; }
3608
- if (recordingsPath == null) { try { ws.close(); } catch (ex) { } return; }
3609
-
3610
- // Get the user and check user rights
3611
- var authUserid = null;
3612
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3613
- if (authUserid == null) { try { ws.close(); } catch (ex) { } return; }
3614
- const user = obj.users[authUserid];
3615
- if (user == null) { try { ws.close(); } catch (ex) { } return; }
3616
- if ((user.siteadmin & 512) == 0) { try { ws.close(); } catch (ex) { } return; } // Check if we have right to get recordings
3617
- const filefullpath = obj.path.join(recordingsPath, req.query.file);
3618
-
3619
- obj.fs.stat(filefullpath, function(err, stats) {
3620
- if (err) {
3621
- try { ws.close(); } catch (ex) { } // File does not exist
3622
- } else {
3623
- obj.fs.open(filefullpath, 'r', function (err, fd) {
3624
- if (err == null) {
3625
- // When data is received from the web socket
3626
- ws.on('message', function (msg) {
3627
- if (typeof msg != 'string') return;
3628
- var command;
3629
- try { command = JSON.parse(msg); } catch (e) { return; }
3630
- if ((command == null) || (typeof command.action != 'string')) return;
3631
- switch (command.action) {
3632
- case 'get': {
3633
- const buffer = Buffer.alloc(8 + command.size);
3634
- //buffer.writeUInt32BE((command.ptr >> 32), 0);
3635
- buffer.writeUInt32BE((command.ptr & 0xFFFFFFFF), 4);
3636
- obj.fs.read(fd, buffer, 8, command.size, command.ptr, function (err, bytesRead, buffer) { if (bytesRead > (buffer.length - 8)) { buffer = buffer.slice(0, bytesRead + 8); } ws.send(buffer); });
3637
- break;
3638
- }
3639
- }
3640
- });
3641
-
3642
- // If error, do nothing
3643
- ws.on('error', function (err) { try { ws.close(); } catch (ex) { } obj.fs.close(fd, function (err) { }); });
3644
-
3645
- // If the web socket is closed
3646
- ws.on('close', function (req) { try { ws.close(); } catch (ex) { } obj.fs.close(fd, function (err) { }); });
3647
-
3648
- ws.send(JSON.stringify({ "action": "info", "name": req.query.file, "size": stats.size }));
3649
- } else {
3650
- try { ws.close(); } catch (ex) { }
3651
- }
3652
- });
3653
- }
3654
- });
3655
- }
3656
-
3657
- // Serve the player page
3658
- function handlePlayerRequest(req, res) {
3659
- const domain = checkUserIpAddress(req, res);
3660
- if (domain == null) { return; }
3661
-
3662
- parent.debug('web', 'handlePlayerRequest: sending player');
3663
- res.set({ 'Cache-Control': 'no-store' });
3664
- render(req, res, getRenderPage('player', req, domain), getRenderArgs({}, req, domain));
3665
- }
3666
-
3667
- // Serve the guest sharing page
3668
- function handleSharingRequest(req, res) {
3669
- const domain = getDomain(req, res);
3670
- if (domain == null) { return; }
3671
- if (req.query.c == null) { res.sendStatus(404); return; }
3672
- if (domain.guestdevicesharing === false) { res.sendStatus(404); return; } // This feature is not allowed.
3673
-
3674
- // Check the inbound guest sharing cookie
3675
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey, 9999999999); // Decode cookies with unlimited time.
3676
- if (c == null) { res.sendStatus(404); return; }
3677
-
3678
- if (c.a === 5) {
3679
- // This is the older style sharing cookie with everything encoded within it.
3680
- // This cookie style gives a very large URL, so it's not used anymore.
3681
- if ((typeof c.p !== 'number') || (c.p < 1) || (c.p > 7) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.pid != 'string')) { res.sendStatus(404); return; }
3682
- handleSharingRequestEx(req, res, domain, c);
3683
- return;
3684
- }
3685
- if (c.a === 6) {
3686
- // This is the new style sharing cookie, just encodes the pointer to the sharing information in the database.
3687
- // Gives a much more compact URL.
3688
- if (typeof c.pid != 'string') { res.sendStatus(404); return; }
3689
-
3690
- // Check the expired time, expire message.
3691
- if ((c.e != null) && (c.e <= Date.now())) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3692
-
3693
- obj.db.Get('deviceshare-' + c.pid, function (err, docs) {
3694
- if ((err != null) || (docs == null) || (docs.length != 1)) { res.sendStatus(404); return; }
3695
- const doc = docs[0];
3696
-
3697
- // If this is a recurrent share, check if we are at the currect time to make use of it
3698
- if (typeof doc.recurring == 'number') {
3699
- const now = Date.now();
3700
- if (now >= doc.startTime) { // We don't want to move the validity window before the start time
3701
- const deltaTime = (now - doc.startTime);
3702
- if (doc.recurring === 1) {
3703
- // This moves the start time to the next valid daily window
3704
- const oneDay = (24 * 60 * 60 * 1000);
3705
- var addition = Math.floor(deltaTime / oneDay);
3706
- if ((deltaTime - (addition * oneDay)) > (doc.duration * 60000)) { addition++; } // If we are passed the current windows, move to the next one. This will show link as not being valid yet.
3707
- doc.startTime += (addition * oneDay);
3708
- } else if (doc.recurring === 2) {
3709
- // This moves the start time to the next valid weekly window
3710
- const oneWeek = (7 * 24 * 60 * 60 * 1000);
3711
- var addition = Math.floor(deltaTime / oneWeek);
3712
- if ((deltaTime - (addition * oneWeek)) > (doc.duration * 60000)) { addition++; } // If we are passed the current windows, move to the next one. This will show link as not being valid yet.
3713
- doc.startTime += (addition * oneWeek);
3714
- }
3715
- }
3716
- }
3717
-
3718
- // Generate an old style cookie from the information in the database
3719
- var cookie = { a: 5, p: doc.p, gn: doc.guestName, nid: doc.nodeid, cf: doc.consent, pid: doc.publicid, k: doc.extrakey };
3720
- if (doc.userid) { cookie.uid = doc.userid; }
3721
- if ((cookie.userid == null) && (cookie.pid.startsWith('AS:node/'))) { cookie.nouser = 1; }
3722
- if (doc.startTime != null) {
3723
- if (doc.expireTime != null) { cookie.start = doc.startTime; cookie.expire = doc.expireTime; }
3724
- else if (doc.duration != null) { cookie.start = doc.startTime; cookie.expire = doc.startTime + (doc.duration * 60000); }
3725
- }
3726
- if (doc.viewOnly === true) { cookie.vo = 1; }
3727
- handleSharingRequestEx(req, res, domain, cookie);
3728
- });
3729
- return;
3730
- }
3731
- res.sendStatus(404); return;
3732
- }
3733
-
3734
- // Serve the guest sharing page
3735
- function handleSharingRequestEx(req, res, domain, c) {
3736
- // Check the expired time, expire message.
3737
- if ((c.expire != null) && (c.expire <= Date.now())) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3738
-
3739
- // Check the public id
3740
- obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
3741
- // Check if any desktop sharing links are present, expire message.
3742
- if ((err != null) || (docs.length == 0)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3743
-
3744
- // Search for the device share public identifier, expire message.
3745
- var found = false;
3746
- for (var i = 0; i < docs.length; i++) { if ((docs[i].publicid == c.pid) && ((docs[i].extrakey == null) || (docs[i].extrakey === c.k))) { found = true; } }
3747
- if (found == false) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3748
-
3749
- // Get information about this node
3750
- obj.db.Get(c.nid, function (err, nodes) {
3751
- if ((err != null) || (nodes == null) || (nodes.length != 1)) { res.sendStatus(404); return; }
3752
- var node = nodes[0];
3753
-
3754
- // Check the start time, not yet valid message.
3755
- if ((c.start != null) && (c.expire != null) && ((c.start > Date.now()) || (c.start > c.expire))) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3756
-
3757
- // Looks good, let's create the outbound session cookies.
3758
- // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
3759
- const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo };
3760
- if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; }
3761
- if (c.k != null) { authCookieData.k = c.k; }
3762
- const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey);
3763
-
3764
- // Server features
3765
- var features2 = 0;
3766
- if (obj.args.allowhighqualitydesktop !== false) { features2 += 1; } // Enable AllowHighQualityDesktop (Default true)
3767
-
3768
- // Lets respond by sending out the desktop viewer.
3769
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3770
- parent.debug('web', 'handleSharingRequest: Sending guest sharing page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
3771
- res.set({ 'Cache-Control': 'no-store' });
3772
- render(req, res, getRenderPage('sharing', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire, viewOnly: (c.vo == 1) ? 1 : 0, nodeName: encodeURIComponent(node.name).replace(/'/g, '%27'), features: c.p, features2: features2 }, req, domain));
3773
- });
3774
- });
3775
- }
3776
-
3777
- // Handle domain redirection
3778
- obj.handleDomainRedirect = function (req, res) {
3779
- const domain = checkUserIpAddress(req, res);
3780
- if (domain == null) { return; }
3781
- if (domain.redirects == null) { res.sendStatus(404); return; }
3782
- var urlArgs = '', urlName = null, splitUrl = req.originalUrl.split('?');
3783
- if (splitUrl.length > 1) { urlArgs = '?' + splitUrl[1]; }
3784
- if ((splitUrl.length > 0) && (splitUrl[0].length > 1)) { urlName = splitUrl[0].substring(1).toLowerCase(); }
3785
- if ((urlName == null) || (domain.redirects[urlName] == null) || (urlName[0] == '_')) { res.sendStatus(404); return; }
3786
- if (domain.redirects[urlName] == '~showversion') {
3787
- // Show the current version
3788
- res.end('MeshCentral v' + obj.parent.currentVer);
3789
- } else {
3790
- // Perform redirection
3791
- res.redirect(domain.redirects[urlName] + urlArgs + getQueryPortion(req));
3792
- }
3793
- }
3794
-
3795
- // Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
3796
- obj.getServerFilePath = function (user, domain, path) {
3797
- var splitpath = path.split('/'), serverpath = obj.path.join(obj.filespath, 'domain'), filename = '';
3798
- if ((splitpath.length < 3) || (splitpath[0] != 'user' && splitpath[0] != 'mesh') || (splitpath[1] != domain.id)) return null; // Basic validation
3799
- var objid = splitpath[0] + '/' + splitpath[1] + '/' + splitpath[2];
3800
- if (splitpath[0] == 'user' && (objid != user._id)) return null; // User validation, only self allowed
3801
- if (splitpath[0] == 'mesh') { if ((obj.GetMeshRights(user, objid) & 32) == 0) { return null; } } // Check mesh server file rights
3802
- if (splitpath[1] != '') { serverpath += '-' + splitpath[1]; } // Add the domain if needed
3803
- serverpath += ('/' + splitpath[0] + '-' + splitpath[2]);
3804
- for (var i = 3; i < splitpath.length; i++) { if (obj.common.IsFilenameValid(splitpath[i]) == true) { serverpath += '/' + splitpath[i]; filename = splitpath[i]; } else { return null; } } // Check that each folder is correct
3805
- return { fullpath: obj.path.resolve(obj.filespath, serverpath), path: serverpath, name: filename, quota: obj.getQuota(objid, domain) };
3806
- };
3807
-
3808
- // Return the maximum number of bytes allowed in the user account "My Files".
3809
- obj.getQuota = function (objid, domain) {
3810
- if (objid == null) return 0;
3811
- if (objid.startsWith('user/')) {
3812
- var user = obj.users[objid];
3813
- if (user == null) return 0;
3814
- if (user.siteadmin == 0xFFFFFFFF) return null; // Administrators have no user limit
3815
- if ((user.quota != null) && (typeof user.quota == 'number')) { return user.quota; }
3816
- if ((domain != null) && (domain.userquota != null) && (typeof domain.userquota == 'number')) { return domain.userquota; }
3817
- return null; // By default, the user will have no limit
3818
- } else if (objid.startsWith('mesh/')) {
3819
- var mesh = obj.meshes[objid];
3820
- if (mesh == null) return 0;
3821
- if ((mesh.quota != null) && (typeof mesh.quota == 'number')) { return mesh.quota; }
3822
- if ((domain != null) && (domain.meshquota != null) && (typeof domain.meshquota == 'number')) { return domain.meshquota; }
3823
- return null; // By default, the mesh will have no limit
3824
- }
3825
- return 0;
3826
- };
3827
-
3828
- // Download a file from the server
3829
- function handleDownloadFile(req, res) {
3830
- const domain = checkUserIpAddress(req, res);
3831
- if (domain == null) { return; }
3832
- if ((req.query.link == null) || (req.session == null) || (req.session.userid == null) || (domain == null) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
3833
- const user = obj.users[req.session.userid];
3834
- if (user == null) { res.sendStatus(404); return; }
3835
- const file = obj.getServerFilePath(user, domain, req.query.link);
3836
- if (file == null) { res.sendStatus(404); return; }
3837
- setContentDispositionHeader(res, 'application/octet-stream', file.name, null, 'file.bin');
3838
- obj.fs.exists(file.fullpath, function (exists) { if (exists == true) { res.sendFile(file.fullpath); } else { res.sendStatus(404); } });
3839
- }
3840
-
3841
- // Upload a MeshCore.js file to the server
3842
- function handleUploadMeshCoreFile(req, res) {
3843
- const domain = checkUserIpAddress(req, res);
3844
- if (domain == null) { return; }
3845
- if (domain.id !== '') { res.sendStatus(401); return; }
3846
-
3847
- var authUserid = null;
3848
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3849
-
3850
- const multiparty = require('multiparty');
3851
- const form = new multiparty.Form();
3852
- form.parse(req, function (err, fields, files) {
3853
- // If an authentication cookie is embedded in the form, use that.
3854
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3855
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3856
- if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3857
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3858
- }
3859
- if (authUserid == null) { res.sendStatus(401); return; }
3860
- if ((fields == null) || (fields.attrib == null) || (fields.attrib.length != 1)) { res.sendStatus(404); return; }
3861
-
3862
- // Get the user
3863
- const user = obj.users[authUserid];
3864
- if (user == null) { res.sendStatus(401); return; } // Check this user exists
3865
-
3866
- // Get the node and check node rights
3867
- const nodeid = fields.attrib[0];
3868
- obj.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
3869
- if ((node == null) || (rights != 0xFFFFFFFF) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
3870
- for (var i in files.files) {
3871
- var file = files.files[i];
3872
- obj.fs.readFile(file.path, 'utf8', function (err, data) {
3873
- if (err != null) return;
3874
- data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
3875
- obj.sendMeshAgentCore(user, domain, fields.attrib[0], 'custom', data); // Upload the core
3876
- try { obj.fs.unlinkSync(file.path); } catch (e) { }
3877
- });
3878
- }
3879
- res.send('');
3880
- });
3881
- });
3882
- }
3883
-
3884
- // Upload a MeshCore.js file to the server
3885
- function handleOneClickRecoveryFile(req, res) {
3886
- const domain = checkUserIpAddress(req, res);
3887
- if (domain == null) { return; }
3888
- if (domain.id !== '') { res.sendStatus(401); return; }
3889
-
3890
- var authUserid = null;
3891
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3892
-
3893
- const multiparty = require('multiparty');
3894
- const form = new multiparty.Form();
3895
- form.parse(req, function (err, fields, files) {
3896
- // If an authentication cookie is embedded in the form, use that.
3897
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3898
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3899
- if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3900
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3901
- }
3902
- if (authUserid == null) { res.sendStatus(401); return; }
3903
- if ((fields == null) || (fields.attrib == null) || (fields.attrib.length != 1)) { res.sendStatus(404); return; }
3904
-
3905
- // Get the user
3906
- const user = obj.users[authUserid];
3907
- if (user == null) { res.sendStatus(401); return; } // Check this user exists
3908
-
3909
- // Get the node and check node rights
3910
- const nodeid = fields.attrib[0];
3911
- obj.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
3912
- if ((node == null) || (rights != 0xFFFFFFFF) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
3913
- for (var i in files.files) {
3914
- var file = files.files[i];
3915
-
3916
- // Event Intel AMT One Click Recovery, this will cause Intel AMT wake operations on this and other servers.
3917
- parent.DispatchEvent('*', obj, { action: 'oneclickrecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.path });
3918
-
3919
- //try { obj.fs.unlinkSync(file.path); } catch (e) { } // TODO: Remove this file after 30 minutes.
3920
- }
3921
- res.send('');
3922
- });
3923
- });
3924
- }
3925
-
3926
- // Upload a file to the server
3927
- function handleUploadFile(req, res) {
3928
- const domain = checkUserIpAddress(req, res);
3929
- if (domain == null) { return; }
3930
- if (domain.userQuota == -1) { res.sendStatus(401); return; }
3931
- var authUserid = null;
3932
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3933
- const multiparty = require('multiparty');
3934
- const form = new multiparty.Form();
3935
- form.parse(req, function (err, fields, files) {
3936
- // If an authentication cookie is embedded in the form, use that.
3937
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3938
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3939
- if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3940
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3941
- }
3942
- if (authUserid == null) { res.sendStatus(401); return; }
3943
-
3944
- // Get the user
3945
- const user = obj.users[authUserid];
3946
- if ((user == null) || (user.siteadmin & 8) == 0) { res.sendStatus(401); return; } // Check if we have file rights
3947
-
3948
- if ((fields == null) || (fields.link == null) || (fields.link.length != 1)) { /*console.log('UploadFile, Invalid Fields:', fields, files);*/ console.log('err4'); res.sendStatus(404); return; }
3949
- var xfile = null;
3950
- try { xfile = obj.getServerFilePath(user, domain, decodeURIComponent(fields.link[0])); } catch (ex) { }
3951
- if (xfile == null) { res.sendStatus(404); return; }
3952
- // Get total bytes in the path
3953
- var totalsize = readTotalFileSize(xfile.fullpath);
3954
- if ((xfile.quota == null) || (totalsize < xfile.quota)) { // Check if the quota is not already broken
3955
- if (fields.name != null) {
3956
-
3957
- // See if we need to create the folder
3958
- var domainx = 'domain';
3959
- if (domain.id.length > 0) { domainx = 'domain-' + usersplit[1]; }
3960
- try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
3961
- try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (ex) { }
3962
- try { obj.fs.mkdirSync(xfile.fullpath); } catch (ex) { }
3963
-
3964
- // Upload method where all the file data is within the fields.
3965
- var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*');
3966
- if ((names.length == sizes.length) && (types.length == datas.length) && (names.length == types.length)) {
3967
- for (var i = 0; i < names.length; i++) {
3968
- if (obj.common.IsFilenameValid(names[i]) == false) { res.sendStatus(404); return; }
3969
- var filedata = Buffer.from(datas[i].split(',')[1], 'base64');
3970
- if ((xfile.quota == null) || ((totalsize + filedata.length) < xfile.quota)) { // Check if quota would not be broken if we add this file
3971
- // Create the user folder if needed
3972
- (function (fullpath, filename, filedata) {
3973
- obj.fs.mkdir(xfile.fullpath, function () {
3974
- // Write the file
3975
- obj.fs.writeFile(obj.path.join(xfile.fullpath, filename), filedata, function () {
3976
- obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
3977
- });
3978
- });
3979
- })(xfile.fullpath, names[i], filedata);
3980
- } else {
3981
- // Send a notification
3982
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: names[i], nolog: 1, id: Math.random() });
3983
- }
3984
- }
3985
- }
3986
- } else {
3987
- // More typical upload method, the file data is in a multipart mime post.
3988
- for (var i in files.files) {
3989
- var file = files.files[i], fpath = obj.path.join(xfile.fullpath, file.originalFilename);
3990
- if (obj.common.IsFilenameValid(file.originalFilename) && ((xfile.quota == null) || ((totalsize + file.size) < xfile.quota))) { // Check if quota would not be broken if we add this file
3991
-
3992
- // See if we need to create the folder
3993
- var domainx = 'domain';
3994
- if (domain.id.length > 0) { domainx = 'domain-' + domain.id; }
3995
- try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
3996
- try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (e) { }
3997
- try { obj.fs.mkdirSync(xfile.fullpath); } catch (e) { }
3998
-
3999
- // Rename the file
4000
- obj.fs.rename(file.path, fpath, function (err) {
4001
- if (err && (err.code === 'EXDEV')) {
4002
- // On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
4003
- obj.common.copyFile(file.path, fpath, function (err) {
4004
- obj.fs.unlink(file.path, function (err) {
4005
- obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
4006
- });
4007
- });
4008
- } else {
4009
- obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
4010
- }
4011
- });
4012
- } else {
4013
- // Send a notification
4014
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: file.originalFilename, nolog: 1, id: Math.random() });
4015
- try { obj.fs.unlink(file.path, function (err) { }); } catch (e) { }
4016
- }
4017
- }
4018
- }
4019
- } else {
4020
- // Send a notification
4021
- obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: "Disk quota exceed", nolog: 1, id: Math.random() });
4022
- }
4023
- res.send('');
4024
- });
4025
- }
4026
-
4027
- // Upload a file to the server and then batch upload to many agents
4028
- function handleUploadFileBatch(req, res) {
4029
- const domain = checkUserIpAddress(req, res);
4030
- if (domain == null) { return; }
4031
- var authUserid = null;
4032
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
4033
- const multiparty = require('multiparty');
4034
- const form = new multiparty.Form();
4035
- form.parse(req, function (err, fields, files) {
4036
- // If an authentication cookie is embedded in the form, use that.
4037
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
4038
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
4039
- if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
4040
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
4041
- }
4042
- if (authUserid == null) { res.sendStatus(401); return; }
4043
-
4044
- // Get the user
4045
- const user = obj.users[authUserid];
4046
- if (user == null) { parent.debug('web', 'Batch upload error, invalid user.'); res.sendStatus(401); return; } // Check if user exists
4047
-
4048
- // Get fields
4049
- if ((fields == null) || (fields.nodeIds == null) || (fields.nodeIds.length != 1)) { res.sendStatus(404); return; }
4050
- var cmd = { nodeids: fields.nodeIds[0].split(','), files: [], user: user, domain: domain, overwrite: false, createFolder: false };
4051
- if ((fields.winpath != null) && (fields.winpath.length == 1)) { cmd.windowsPath = fields.winpath[0]; }
4052
- if ((fields.linuxpath != null) && (fields.linuxpath.length == 1)) { cmd.linuxPath = fields.linuxpath[0]; }
4053
- if ((fields.overwriteFiles != null) && (fields.overwriteFiles.length == 1) && (fields.overwriteFiles[0] == 'on')) { cmd.overwrite = true; }
4054
- if ((fields.createFolder != null) && (fields.createFolder.length == 1) && (fields.createFolder[0] == 'on')) { cmd.createFolder = true; }
4055
-
4056
- // Check if we have at least one target path
4057
- if ((cmd.windowsPath == null) && (cmd.linuxPath == null)) {
4058
- parent.debug('web', 'Batch upload error, invalid fields: ' + JSON.stringify(fields));
4059
- res.send('');
4060
- return;
4061
- }
4062
-
4063
- // Get server temporary path
4064
- var serverpath = obj.path.join(obj.filespath, 'tmp')
4065
- try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
4066
- try { obj.fs.mkdirSync(serverpath); } catch (ex) { }
4067
-
4068
- // More typical upload method, the file data is in a multipart mime post.
4069
- for (var i in files.files) {
4070
- var file = files.files[i], ftarget = getRandomPassword() + '-' + file.originalFilename, fpath = obj.path.join(serverpath, ftarget);
4071
- cmd.files.push({ name: file.originalFilename, target: ftarget });
4072
- // Rename the file
4073
- obj.fs.rename(file.path, fpath, function (err) {
4074
- if (err && (err.code === 'EXDEV')) {
4075
- // On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
4076
- obj.common.copyFile(file.path, fpath, function (err) { obj.fs.unlink(file.path, function (err) { }); });
4077
- }
4078
- });
4079
- }
4080
-
4081
- // Instruct one of more agents to download a URL to a given local drive location.
4082
- var tlsCertHash = null;
4083
- if ((parent.args.ignoreagenthashcheck == null) || (parent.args.ignoreagenthashcheck === false)) { // TODO: If ignoreagenthashcheck is an array of IP addresses, not sure how to handle this.
4084
- tlsCertHash = obj.webCertificateFullHashs[cmd.domain.id];
4085
- if (tlsCertHash != null) { tlsCertHash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
4086
- }
4087
- for (var i in cmd.nodeids) {
4088
- obj.GetNodeWithRights(cmd.domain, cmd.user, cmd.nodeids[i], function (node, rights, visible) {
4089
- if ((node == null) || ((rights & 8) == 0) || (visible == false)) return; // We don't have remote control rights to this device
4090
- var agentPath = (((node.agent.id > 0) && (node.agent.id < 5)) || (node.agent.id == 34)) ? cmd.windowsPath : cmd.linuxPath;
4091
- if (agentPath == null) return;
4092
-
4093
- // Compute user consent
4094
- var consent = 0;
4095
- var mesh = obj.meshes[node.meshid];
4096
- if (typeof domain.userconsentflags == 'number') { consent |= domain.userconsentflags; } // Add server required consent flags
4097
- if ((mesh != null) && (typeof mesh.consent == 'number')) { consent |= mesh.consent; } // Add device group user consent
4098
- if (typeof node.consent == 'number') { consent |= node.consent; } // Add node user consent
4099
- if (typeof user.consent == 'number') { consent |= user.consent; } // Add user consent
4100
-
4101
- // Check if we need to add consent flags because of a user group link
4102
- if ((mesh != null) && (user.links != null) && (user.links[mesh._id] == null) && (user.links[node._id] == null)) {
4103
- // This user does not have a direct link to the device group or device. Find all user groups the would cause the link.
4104
- for (var i in user.links) {
4105
- var ugrp = obj.userGroups[i];
4106
- if ((ugrp != null) && (ugrp.consent != null) && (ugrp.links != null) && ((ugrp.links[mesh._id] != null) || (ugrp.links[node._id] != null))) {
4107
- consent |= ugrp.consent; // Add user group consent flags
4108
- }
4109
- }
4110
- }
4111
-
4112
- // Event that this operation is being performed.
4113
- var targets = obj.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', cmd.user._id]);
4114
- var msgid = 103; // "Batch upload of {0} file(s) to folder {1}"
4115
- var event = { etype: 'node', userid: cmd.user._id, username: cmd.user.name, nodeid: node._id, action: 'batchupload', msg: 'Performing batch upload of ' + cmd.files.length + ' file(s) to ' + agentPath, msgid: msgid, msgArgs: [cmd.files.length, agentPath], domain: cmd.domain.id };
4116
- parent.DispatchEvent(targets, obj, event);
4117
-
4118
- // Send the agent commands to perform the batch upload operation
4119
- for (var f in cmd.files) {
4120
- if (cmd.files[f].name != null) {
4121
- const acmd = { action: 'wget', userid: user._id, username: user.name, realname: user.realname, remoteaddr: req.clientIp, consent: consent, rights: rights, overwrite: cmd.overwrite, createFolder: cmd.createFolder, urlpath: '/agentdownload.ashx?c=' + obj.parent.encodeCookie({ a: 'tmpdl', d: cmd.domain.id, nid: node._id, f: cmd.files[f].target }, obj.parent.loginCookieEncryptionKey), path: obj.path.join(agentPath, cmd.files[f].name), folder: agentPath, servertlshash: tlsCertHash };
4122
- var agent = obj.wsagents[node._id];
4123
- if (agent != null) { try { agent.send(JSON.stringify(acmd)); } catch (ex) { } }
4124
- // TODO: Add support for peer servers.
4125
- }
4126
- }
4127
- });
4128
- }
4129
-
4130
- res.send('');
4131
- });
4132
- }
4133
-
4134
- // Subscribe to all events we are allowed to receive
4135
- obj.subscribe = function (userid, target) {
4136
- const user = obj.users[userid];
4137
- const subscriptions = [userid, 'server-allusers'];
4138
- if (user.siteadmin != null) {
4139
- // Allow full site administrators of users with all events rights to see all events.
4140
- if ((user.siteadmin == 0xFFFFFFFF) || ((user.siteadmin & 2048) != 0)) { subscriptions.push('*'); }
4141
- else if ((user.siteadmin & 2) != 0) {
4142
- if ((user.groups == null) || (user.groups.length == 0)) {
4143
- // Subscribe to all user changes
4144
- subscriptions.push('server-users');
4145
- } else {
4146
- // Subscribe to user changes for some groups
4147
- for (var i in user.groups) { subscriptions.push('server-users:' + i); }
4148
- }
4149
- }
4150
- }
4151
- if (user.links != null) { for (var i in user.links) { subscriptions.push(i); } }
4152
- obj.parent.RemoveAllEventDispatch(target);
4153
- obj.parent.AddEventDispatch(subscriptions, target);
4154
- return subscriptions;
4155
- };
4156
-
4157
- // Handle a web socket relay request
4158
- function handleRelayWebSocket(ws, req, domain, user, cookie) {
4159
- if (!(req.query.host)) { console.log('ERR: No host target specified'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4160
- parent.debug('web', 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.');
4161
-
4162
- try { ws._socket.setKeepAlive(true, 240000); } catch (ex) { } // Set TCP keep alive
4163
-
4164
- // Fetch information about the target
4165
- obj.db.Get(req.query.host, function (err, docs) {
4166
- if (docs.length == 0) { console.log('ERR: Node not found'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4167
- var node = docs[0];
4168
- if (!node.intelamt) { console.log('ERR: Not AMT node'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4169
-
4170
- // Check if this user has permission to manage this computer
4171
- if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (3)'); try { ws.close(); } catch (e) { } return; }
4172
-
4173
- // Check what connectivity is available for this node
4174
- var state = parent.GetConnectivityState(req.query.host);
4175
- var conn = 0;
4176
- if (!state || state.connectivity == 0) { parent.debug('web', 'ERR: No routing possible (1)'); try { ws.close(); } catch (e) { } return; } else { conn = state.connectivity; }
4177
-
4178
- // Check what server needs to handle this connection
4179
- if ((obj.parent.multiServer != null) && ((cookie == null) || (cookie.ps != 1))) { // If a cookie is provided and is from a peer server, don't allow the connection to jump again to a different server
4180
- var server = obj.parent.GetRoutingServerId(req.query.host, 2); // Check for Intel CIRA connection
4181
- if (server != null) {
4182
- if (server.serverid != obj.parent.serverId) {
4183
- // Do local Intel CIRA routing using a different server
4184
- parent.debug('web', 'Route Intel AMT CIRA connection to peer server: ' + server.serverid);
4185
- obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
4186
- return;
4187
- }
4188
- } else {
4189
- server = obj.parent.GetRoutingServerId(req.query.host, 4); // Check for local Intel AMT connection
4190
- if ((server != null) && (server.serverid != obj.parent.serverId)) {
4191
- // Do local Intel AMT routing using a different server
4192
- parent.debug('web', 'Route Intel AMT direct connection to peer server: ' + server.serverid);
4193
- obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
4194
- return;
4195
- }
4196
- }
4197
- }
4198
-
4199
- // Setup session recording if needed
4200
- if (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf((req.query.p == 2) ? 101 : 100) >= 0)))) { // TODO 100
4201
- // Check again if we need to do recording
4202
- var record = true;
4203
-
4204
- // Check user or device group recording
4205
- if ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.onlyselectedusers === true) || (domain.sessionrecording.onlyselecteddevicegroups === true))) {
4206
- record = false;
4207
-
4208
- // Check device group recording
4209
- if (domain.sessionrecording.onlyselecteddevicegroups === true) {
4210
- var mesh = obj.meshes[node.meshid];
4211
- if ((mesh.flags != null) && ((mesh.flags & 4) != 0)) { record = true; } // Record the session
4212
- }
4213
-
4214
- // Check user recording
4215
- if (domain.sessionrecording.onlyselectedusers === true) {
4216
- if ((user.flags != null) && ((user.flags & 2) != 0)) { record = true; } // Record the session
4217
- }
4218
- }
4219
-
4220
- if (record == true) {
4221
- var now = new Date(Date.now());
4222
- var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + now.getUTCFullYear() + '-' + obj.common.zeroPad(now.getUTCMonth() + 1, 2) + '-' + obj.common.zeroPad(now.getUTCDate(), 2) + '-' + obj.common.zeroPad(now.getUTCHours(), 2) + '-' + obj.common.zeroPad(now.getUTCMinutes(), 2) + '-' + obj.common.zeroPad(now.getUTCSeconds(), 2) + '-' + getRandomPassword() + '.mcrec'
4223
- var recFullFilename = null;
4224
- if (domain.sessionrecording.filepath) {
4225
- try { obj.fs.mkdirSync(domain.sessionrecording.filepath); } catch (e) { }
4226
- recFullFilename = obj.path.join(domain.sessionrecording.filepath, recFilename);
4227
- } else {
4228
- try { obj.fs.mkdirSync(parent.recordpath); } catch (e) { }
4229
- recFullFilename = obj.path.join(parent.recordpath, recFilename);
4230
- }
4231
- var fd = obj.fs.openSync(recFullFilename, 'w');
4232
- if (fd != null) {
4233
- // Write the recording file header
4234
- var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: req.clientIp, nodeid: node._id, intelamt: true, protocol: (req.query.p == 2) ? 101 : 100, time: new Date().toLocaleString() })
4235
- recordingEntry(fd, 1, 0, firstBlock, function () { });
4236
- ws.logfile = { fd: fd, lock: false };
4237
- if (req.query.p == 2) { ws.send(Buffer.from(String.fromCharCode(0xF0), 'binary')); } // Intel AMT Redirection: Indicate the session is being recorded
4238
- }
4239
- }
4240
- }
4241
-
4242
- // If Intel AMT CIRA connection is available, use it
4243
- var ciraconn = parent.mpsserver.GetConnectionToNode(req.query.host, null, false);
4244
- if (ciraconn != null) {
4245
- parent.debug('web', 'Opening relay CIRA channel connection to ' + req.query.host + '.');
4246
-
4247
- // TODO: If the CIRA connection is a relay or LMS connection, we can't detect the TLS state like this.
4248
- // Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
4249
- var port = 16993;
4250
- //if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA
4251
- if (ciraconn.tag.boundPorts.indexOf(16992) >= 0) port = 16992; // RELEASE: Always use non-TLS mode if available within CIRA
4252
- if (req.query.p == 2) port += 2;
4253
-
4254
- // Setup a new CIRA channel
4255
- if ((port == 16993) || (port == 16995)) {
4256
- // Perform TLS
4257
- var ser = new SerialTunnel();
4258
- var chnl = parent.mpsserver.SetupChannel(ciraconn, port);
4259
-
4260
- // Let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
4261
- // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
4262
- ser.forwardwrite = function (data) { if (data.length > 0) { chnl.write(data); } }; // TLS ---> CIRA
4263
-
4264
- // When APF tunnel return something, update SerialTunnel buffer
4265
- chnl.onData = function (ciraconn, data) { if (data.length > 0) { try { ser.updateBuffer(data); } catch (ex) { console.log(ex); } } }; // CIRA ---> TLS
4266
-
4267
- // Handle CIRA tunnel state change
4268
- chnl.onStateChange = function (ciraconn, state) {
4269
- parent.debug('webrelay', 'Relay TLS CIRA state change', state);
4270
- if (state == 0) { try { ws.close(); } catch (e) { } }
4271
- if (state == 2) {
4272
- // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
4273
- const tlsoptions = { socket: ser, ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
4274
- if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
4275
- var tlsock = obj.tls.connect(tlsoptions, function () { parent.debug('webrelay', "CIRA Secure TLS Connection"); ws._socket.resume(); });
4276
- tlsock.chnl = chnl;
4277
- tlsock.setEncoding('binary');
4278
- tlsock.on('error', function (err) { parent.debug('webrelay', "CIRA TLS Connection Error", err); });
4279
-
4280
- // Decrypted tunnel from TLS communcation to be forwarded to websocket
4281
- tlsock.on('data', function (data) {
4282
- // AMT/TLS ---> WS
4283
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
4284
- try { ws.send(data); } catch (ex) { }
4285
- });
4286
-
4287
- // If TLS is on, forward it through TLSSocket
4288
- ws.forwardclient = tlsock;
4289
- ws.forwardclient.xtls = 1;
4290
-
4291
- ws.forwardclient.onStateChange = function (ciraconn, state) {
4292
- parent.debug('webrelay', 'Relay CIRA state change', state);
4293
- if (state == 0) { try { ws.close(); } catch (e) { } }
4294
- };
4295
-
4296
- ws.forwardclient.onData = function (ciraconn, data) {
4297
- // Run data thru interceptor
4298
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); }
4299
-
4300
- if (data.length > 0) {
4301
- if (ws.logfile == null) {
4302
- try { ws.send(data); } catch (e) { }
4303
- } else {
4304
- // Log to recording file
4305
- recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } }); // TODO: Add TLS support
4306
- }
4307
- }
4308
- };
4309
-
4310
- // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
4311
- ws.forwardclient.onSendOk = function (ciraconn) { };
4312
- }
4313
- };
4314
- } else {
4315
- // Without TLS
4316
- ws.forwardclient = parent.mpsserver.SetupChannel(ciraconn, port);
4317
- ws.forwardclient.xtls = 0;
4318
- ws._socket.resume();
4319
-
4320
- ws.forwardclient.onStateChange = function (ciraconn, state) {
4321
- parent.debug('webrelay', 'Relay CIRA state change', state);
4322
- if (state == 0) { try { ws.close(); } catch (e) { } }
4323
- };
4324
-
4325
- ws.forwardclient.onData = function (ciraconn, data) {
4326
- //parent.debug('webrelaydata', 'Relay CIRA data to WS', data.length);
4327
-
4328
- // Run data thru interceptorp
4329
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); }
4330
-
4331
- //console.log('AMT --> WS', Buffer.from(data, 'binary').toString('hex'));
4332
- if (data.length > 0) {
4333
- if (ws.logfile == null) {
4334
- try { ws.send(data); } catch (e) { }
4335
- } else {
4336
- // Log to recording file
4337
- recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } });
4338
- }
4339
- }
4340
- };
4341
-
4342
- // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
4343
- ws.forwardclient.onSendOk = function (ciraconn) { };
4344
- }
4345
-
4346
- // When data is received from the web socket, forward the data into the associated CIRA cahnnel.
4347
- // If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
4348
- ws.on('message', function (data) {
4349
- //parent.debug('webrelaydata', 'Relay WS data to CIRA', data.length);
4350
- if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
4351
-
4352
- // WS ---> AMT/TLS
4353
- if (ws.interceptor) { data = ws.interceptor.processBrowserData(data); } // Run data thru interceptor
4354
-
4355
- // Log to recording file
4356
- if (ws.logfile == null) {
4357
- // Forward data to the associated TCP connection.
4358
- try { ws.forwardclient.write(data); } catch (ex) { }
4359
- } else {
4360
- // Log to recording file
4361
- recordingEntry(ws.logfile.fd, 2, 2, data, function () { try { ws.forwardclient.write(data); } catch (ex) { } });
4362
- }
4363
- });
4364
-
4365
- // If error, close the associated TCP connection.
4366
- ws.on('error', function (err) {
4367
- console.log('CIRA server websocket error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
4368
- parent.debug('webrelay', 'Websocket relay closed on error.');
4369
-
4370
- // Websocket closed, close the CIRA channel and TLS session.
4371
- if (ws.forwardclient) {
4372
- if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
4373
- if (ws.forwardclient.end) { ws.forwardclient.end(); } // TLS, close the TLS session
4374
- if (ws.forwardclient.chnl) { ws.forwardclient.chnl.close(); } // TLS, close the CIRA channel
4375
- delete ws.forwardclient;
4376
- }
4377
-
4378
- // Close the recording file
4379
- if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
4380
- });
4381
-
4382
- // If the web socket is closed, close the associated TCP connection.
4383
- ws.on('close', function (req) {
4384
- parent.debug('webrelay', 'Websocket relay closed.');
4385
-
4386
- // Websocket closed, close the CIRA channel and TLS session.
4387
- if (ws.forwardclient) {
4388
- if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
4389
- if (ws.forwardclient.end) { ws.forwardclient.end(); } // TLS, close the TLS session
4390
- if (ws.forwardclient.chnl) { ws.forwardclient.chnl.close(); } // TLS, close the CIRA channel
4391
- delete ws.forwardclient;
4392
- }
4393
-
4394
- // Close the recording file
4395
- if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
4396
- });
4397
-
4398
- // Note that here, req.query.p: 1 = WSMAN with server auth, 2 = REDIR with server auth, 3 = WSMAN without server auth, 4 = REDIR with server auth
4399
-
4400
- // Fetch Intel AMT credentials & Setup interceptor
4401
- if (req.query.p == 1) {
4402
- parent.debug('webrelaydata', 'INTERCEPTOR1', { host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
4403
- ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
4404
- ws.interceptor.blockAmtStorage = true;
4405
- } else if (req.query.p == 2) {
4406
- parent.debug('webrelaydata', 'INTERCEPTOR2', { user: node.intelamt.user, pass: node.intelamt.pass });
4407
- ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass });
4408
- ws.interceptor.blockAmtStorage = true;
4409
- }
4410
-
4411
- return;
4412
- }
4413
-
4414
- // If Intel AMT direct connection is possible, option a direct socket
4415
- if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
4416
- parent.debug('webrelay', 'Opening relay TCP socket connection to ' + req.query.host + '.');
4417
-
4418
- // When data is received from the web socket, forward the data into the associated TCP connection.
4419
- ws.on('message', function (msg) {
4420
- //parent.debug('webrelaydata', 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes');
4421
-
4422
- if (typeof msg == 'string') { msg = Buffer.from(msg, 'binary'); }
4423
- if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
4424
-
4425
- // Log to recording file
4426
- if (ws.logfile == null) {
4427
- // Forward data to the associated TCP connection.
4428
- try { ws.forwardclient.write(msg); } catch (ex) { }
4429
- } else {
4430
- // Log to recording file
4431
- recordingEntry(ws.logfile.fd, 2, 2, msg, function () { try { ws.forwardclient.write(msg); } catch (ex) { } });
4432
- }
4433
- });
4434
-
4435
- // If error, close the associated TCP connection.
4436
- ws.on('error', function (err) {
4437
- console.log('Error with relay web socket connection from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
4438
- parent.debug('webrelay', 'Error with relay web socket connection from ' + req.clientIp + '.');
4439
- if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
4440
-
4441
- // Close the recording file
4442
- if (ws.logfile != null) {
4443
- recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
4444
- obj.fs.close(fd);
4445
- ws.logfile = null;
4446
- });
4447
- }
4448
- });
4449
-
4450
- // If the web socket is closed, close the associated TCP connection.
4451
- ws.on('close', function () {
4452
- parent.debug('webrelay', 'Closing relay web socket connection to ' + req.query.host + '.');
4453
- if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
4454
-
4455
- // Close the recording file
4456
- if (ws.logfile != null) {
4457
- recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
4458
- obj.fs.close(fd);
4459
- ws.logfile = null;
4460
- });
4461
- }
4462
- });
4463
-
4464
- // Compute target port
4465
- var port = 16992;
4466
- if (node.intelamt.tls > 0) port = 16993; // This is a direct connection, use TLS when possible
4467
- if ((req.query.p == 2) || (req.query.p == 4)) port += 2;
4468
-
4469
- if (node.intelamt.tls == 0) {
4470
- // If this is TCP (without TLS) set a normal TCP socket
4471
- ws.forwardclient = new obj.net.Socket();
4472
- ws.forwardclient.setEncoding('binary');
4473
- ws.forwardclient.xstate = 0;
4474
- ws.forwardclient.forwardwsocket = ws;
4475
- ws._socket.resume();
4476
- } else {
4477
- // If TLS is going to be used, setup a TLS socket
4478
- var tlsoptions = { ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
4479
- if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
4480
- ws.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
4481
- // The TLS connection method is the same as TCP, but located a bit differently.
4482
- parent.debug('webrelay', 'TLS connected to ' + node.host + ':' + port + '.');
4483
- ws.forwardclient.xstate = 1;
4484
- ws._socket.resume();
4485
- });
4486
- ws.forwardclient.setEncoding('binary');
4487
- ws.forwardclient.xstate = 0;
4488
- ws.forwardclient.forwardwsocket = ws;
4489
- }
4490
-
4491
- // When we receive data on the TCP connection, forward it back into the web socket connection.
4492
- ws.forwardclient.on('data', function (data) {
4493
- if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
4494
- if (obj.parent.debugLevel >= 1) { // DEBUG
4495
- parent.debug('webrelaydata', 'TCP relay data from ' + node.host + ', ' + data.length + ' bytes.');
4496
- //if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
4497
- }
4498
- if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
4499
- if (ws.logfile == null) {
4500
- // No logging
4501
- try { ws.send(data); } catch (e) { }
4502
- } else {
4503
- // Log to recording file
4504
- recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (e) { } });
4505
- }
4506
- });
4507
-
4508
- // If the TCP connection closes, disconnect the associated web socket.
4509
- ws.forwardclient.on('close', function () {
4510
- parent.debug('webrelay', 'TCP relay disconnected from ' + node.host + ':' + port + '.');
4511
- try { ws.close(); } catch (e) { }
4512
- });
4513
-
4514
- // If the TCP connection causes an error, disconnect the associated web socket.
4515
- ws.forwardclient.on('error', function (err) {
4516
- parent.debug('webrelay', 'TCP relay error from ' + node.host + ':' + port + ': ' + err);
4517
- try { ws.close(); } catch (e) { }
4518
- });
4519
-
4520
- // Fetch Intel AMT credentials & Setup interceptor
4521
- if (req.query.p == 1) { ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); }
4522
- else if (req.query.p == 2) { ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass }); }
4523
-
4524
- if (node.intelamt.tls == 0) {
4525
- // A TCP connection to Intel AMT just connected, start forwarding.
4526
- ws.forwardclient.connect(port, node.host, function () {
4527
- parent.debug('webrelay', 'TCP relay connected to ' + node.host + ':' + port + '.');
4528
- ws.forwardclient.xstate = 1;
4529
- ws._socket.resume();
4530
- });
4531
- }
4532
- return;
4533
- }
4534
-
4535
- });
4536
- }
4537
-
4538
- // Setup agent to/from server file transfer handler
4539
- function handleAgentFileTransfer(ws, req) {
4540
- var domain = checkAgentIpAddress(ws, req);
4541
- if (domain == null) { parent.debug('web', 'Got agent file transfer connection with bad domain or blocked IP address ' + req.clientIp + ', dropping.'); ws.close(); return; }
4542
- if (req.query.c == null) { parent.debug('web', 'Got agent file transfer connection without a cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
4543
- var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
4544
- if ((c == null) || (c.a != 'aft')) { parent.debug('web', 'Got agent file transfer connection with invalid cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
4545
- ws.xcmd = c.b; ws.xarg = c.c, ws.xfilelen = 0;
4546
- ws.send('c'); // Indicate connection of the tunnel. In this case, we are the termination point.
4547
- ws.send('5'); // Indicate we want to perform file transfers (5 = Files).
4548
- if (ws.xcmd == 'coredump') {
4549
- // Check the agent core dump folder if not already present.
4550
- var coreDumpPath = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps');
4551
- if (obj.fs.existsSync(coreDumpPath) == false) { try { obj.fs.mkdirSync(coreDumpPath); } catch (ex) { } }
4552
- ws.xfilepath = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps', ws.xarg);
4553
- ws.xid = 'coredump';
4554
- ws.send(JSON.stringify({ action: 'download', sub: 'start', ask: 'coredump', id: 'coredump' })); // Ask for a core dump file
4555
- }
4556
-
4557
- // When data is received from the web socket, echo it back
4558
- ws.on('message', function (data) {
4559
- if (typeof data == 'string') {
4560
- // Control message
4561
- var cmd = null;
4562
- try { cmd = JSON.parse(data); } catch (ex) { }
4563
- if ((cmd == null) || (cmd.action != 'download') || (cmd.sub == null)) return;
4564
- switch (cmd.sub) {
4565
- case 'start': {
4566
- // Perform an async file open
4567
- var callback = function onFileOpen(err, fd) {
4568
- onFileOpen.xws.xfile = fd;
4569
- try { onFileOpen.xws.send(JSON.stringify({ action: 'download', sub: 'startack', id: onFileOpen.xws.xid, ack: 1 })); } catch (ex) { } // Ask for a directory (test)
4570
- };
4571
- callback.xws = this;
4572
- obj.fs.open(this.xfilepath + '.part', 'w', callback);
4573
- break;
4574
- }
4575
- }
4576
- } else {
4577
- // Binary message
4578
- if (data.length < 4) return;
4579
- var flags = data.readInt32BE(0);
4580
- if ((data.length > 4)) {
4581
- // Write the file
4582
- this.xfilelen += (data.length - 4);
4583
- try {
4584
- var callback = function onFileDataWritten(err, bytesWritten, buffer) {
4585
- if (onFileDataWritten.xflags & 1) {
4586
- // End of file
4587
- parent.debug('web', "Completed downloads of agent dumpfile, " + onFileDataWritten.xws.xfilelen + " bytes.");
4588
- if (onFileDataWritten.xws.xfile) {
4589
- obj.fs.close(onFileDataWritten.xws.xfile, function (err) { });
4590
- obj.fs.rename(onFileDataWritten.xws.xfilepath + '.part', onFileDataWritten.xws.xfilepath, function (err) { });
4591
- onFileDataWritten.xws.xfile = null;
4592
- }
4593
- try { onFileDataWritten.xws.send(JSON.stringify({ action: 'markcoredump' })); } catch (ex) { } // Ask to delete the core dump file
4594
- try { onFileDataWritten.xws.close(); } catch (ex) { }
4595
- } else {
4596
- // Send ack
4597
- try { onFileDataWritten.xws.send(JSON.stringify({ action: 'download', sub: 'ack', id: onFileDataWritten.xws.xid })); } catch (ex) { } // Ask for a directory (test)
4598
- }
4599
- };
4600
- callback.xws = this;
4601
- callback.xflags = flags;
4602
- obj.fs.write(this.xfile, data, 4, data.length - 4, callback);
4603
- } catch (ex) { }
4604
- } else {
4605
- if (flags & 1) {
4606
- // End of file
4607
- parent.debug('web', "Completed downloads of agent dumpfile, " + this.xfilelen + " bytes.");
4608
- if (this.xfile) {
4609
- obj.fs.close(this.xfile, function (err) { });
4610
- obj.fs.rename(this.xfilepath + '.part', this.xfilepath, function (err) { });
4611
- this.xfile = null;
4612
- }
4613
- this.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file
4614
- try { this.close(); } catch (ex) { }
4615
- } else {
4616
- // Send ack
4617
- this.send(JSON.stringify({ action: 'download', sub: 'ack', id: this.xid })); // Ask for a directory (test)
4618
- }
4619
- }
4620
- }
4621
- });
4622
-
4623
- // If error, do nothing.
4624
- ws.on('error', function (err) { console.log('Agent file transfer server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); });
4625
-
4626
- // If closed, do nothing
4627
- ws.on('close', function (req) {
4628
- if (this.xfile) {
4629
- obj.fs.close(this.xfile, function (err) { });
4630
- obj.fs.unlink(this.xfilepath + '.part', function (err) { }); // Remove a partial file
4631
- }
4632
- });
4633
- }
4634
-
4635
- // Handle the web socket echo request, just echo back the data sent
4636
- function handleEchoWebSocket(ws, req) {
4637
- const domain = checkUserIpAddress(ws, req);
4638
- if (domain == null) { return; }
4639
- ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
4640
-
4641
- // When data is received from the web socket, echo it back
4642
- ws.on('message', function (data) {
4643
- if (data.toString('utf8') == 'close') {
4644
- try { ws.close(); } catch (e) { console.log(e); }
4645
- } else {
4646
- try { ws.send(data); } catch (e) { console.log(e); }
4647
- }
4648
- });
4649
-
4650
- // If error, do nothing.
4651
- ws.on('error', function (err) { console.log('Echo server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); });
4652
-
4653
- // If closed, do nothing
4654
- ws.on('close', function (req) { });
4655
- }
4656
-
4657
- // Handle the 2FA hold web socket
4658
- // Accept an hold a web socket connection until the 2FA response is received.
4659
- function handle2faHoldWebSocket(ws, req) {
4660
- const domain = checkUserIpAddress(ws, req);
4661
- if (domain == null) { return; }
4662
- if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { ws.close(); return; } // Push 2FA is disabled
4663
- if (typeof req.query.c !== 'string') { ws.close(); return; }
4664
- const cookie = parent.decodeCookie(req.query.c, null, 1);
4665
- if ((cookie == null) || (cookie.d != domain.id)) { ws.close(); return; }
4666
- var user = obj.users[cookie.u];
4667
- if ((user == null) || (typeof user.otpdev != 'string')) { ws.close(); return; }
4668
- ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
4669
-
4670
- // 2FA event subscription
4671
- obj.parent.AddEventDispatch(['2fadev-' + cookie.s], ws);
4672
- ws.cookie = cookie;
4673
- ws.HandleEvent = function (source, event, ids, id) {
4674
- obj.parent.RemoveAllEventDispatch(this);
4675
- if ((event.approved === true) && (event.userid == this.cookie.u)) {
4676
- // Create a login cookie
4677
- const loginCookie = obj.parent.encodeCookie({ a: 'pushAuth', u: event.userid, d: event.domain }, obj.parent.loginCookieEncryptionKey);
4678
- try { ws.send(JSON.stringify({ approved: true, token: loginCookie })); } catch (ex) { }
4679
- } else {
4680
- // Reject the login
4681
- try { ws.send(JSON.stringify({ approved: false })); } catch (ex) { }
4682
- }
4683
- }
4684
-
4685
- // We do not accept any data on this connection.
4686
- ws.on('message', function (data) { this.close(); });
4687
-
4688
- // If error, do nothing.
4689
- ws.on('error', function (err) { });
4690
-
4691
- // If closed, unsubscribe
4692
- ws.on('close', function (req) { obj.parent.RemoveAllEventDispatch(this); });
4693
-
4694
- // Perform push notification to device
4695
- try {
4696
- const deviceCookie = parent.encodeCookie({ a: 'checkAuth', c: cookie.c, u: cookie.u, n: cookie.n, s: cookie.s });
4697
- var code = Buffer.from(cookie.c, 'base64').toString();
4698
- var payload = { notification: { title: (domain.title ? domain.title : 'MeshCentral'), body: "Authentication - " + code }, data: { url: '2fa://auth?code=' + cookie.c + '&c=' + deviceCookie } };
4699
- var options = { priority: 'High', timeToLive: 60 }; // TTL: 1 minute
4700
- parent.firebase.sendToDevice(user.otpdev, payload, options, function (id, err, errdesc) {
4701
- if (err == null) {
4702
- try { ws.send(JSON.stringify({ sent: true, code: code })); } catch (ex) { }
4703
- } else {
4704
- try { ws.send(JSON.stringify({ sent: false })); } catch (ex) { }
4705
- }
4706
- });
4707
- } catch (ex) { console.log(ex); }
4708
- }
4709
-
4710
- // Get the total size of all files in a folder and all sub-folders. (TODO: try to make all async version)
4711
- function readTotalFileSize(path) {
4712
- var r = 0, dir;
4713
- try { dir = obj.fs.readdirSync(path); } catch (e) { return 0; }
4714
- for (var i in dir) {
4715
- var stat = obj.fs.statSync(path + '/' + dir[i]);
4716
- if ((stat.mode & 0x004000) == 0) { r += stat.size; } else { r += readTotalFileSize(path + '/' + dir[i]); }
4717
- }
4718
- return r;
4719
- }
4720
-
4721
- // Delete a folder and all sub items. (TODO: try to make all async version)
4722
- function deleteFolderRec(path) {
4723
- if (obj.fs.existsSync(path) == false) return;
4724
- try {
4725
- obj.fs.readdirSync(path).forEach(function (file, index) {
4726
- var pathx = path + '/' + file;
4727
- if (obj.fs.lstatSync(pathx).isDirectory()) { deleteFolderRec(pathx); } else { obj.fs.unlinkSync(pathx); }
4728
- });
4729
- obj.fs.rmdirSync(path);
4730
- } catch (ex) { }
4731
- }
4732
-
4733
- // Handle Intel AMT events
4734
- // To subscribe, add "http://server:port/amtevents.ashx" to Intel AMT subscriptions.
4735
- obj.handleAmtEventRequest = function (req, res) {
4736
- const domain = getDomain(req);
4737
- try {
4738
- if (req.headers.authorization) {
4739
- var authstr = req.headers.authorization;
4740
- if (authstr.substring(0, 7) == 'Digest ') {
4741
- var auth = obj.common.parseNameValueList(obj.common.quoteSplit(authstr.substring(7)));
4742
- if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) {
4743
-
4744
- // Read the data, we need to get the arg field
4745
- var eventData = '';
4746
- req.on('data', function (chunk) { eventData += chunk; });
4747
- req.on('end', function () {
4748
-
4749
- // Completed event read, let get the argument that must contain the nodeid
4750
- var i = eventData.indexOf('<m:arg xmlns:m="http://x.com">');
4751
- if (i > 0) {
4752
- var nodeid = eventData.substring(i + 30, i + 30 + 64);
4753
- if (nodeid.length == 64) {
4754
- var nodekey = 'node/' + domain.id + '/' + nodeid;
4755
-
4756
- // See if this node exists in the database
4757
- obj.db.Get(nodekey, function (err, nodes) {
4758
- if (nodes.length == 1) {
4759
- // Yes, the node exists, compute Intel AMT digest password
4760
- var node = nodes[0];
4761
- var amtpass = obj.crypto.createHash('sha384').update(auth.username.toLowerCase() + ':' + nodeid + ":" + obj.parent.dbconfig.amtWsEventSecret).digest('base64').substring(0, 12).split('/').join('x').split('\\').join('x');
4762
-
4763
- // Check the MD5 hash
4764
- if (auth.response === obj.common.ComputeDigesthash(auth.username, amtpass, auth.realm, 'POST', auth.uri, auth.qop, auth.nonce, auth.nc, auth.cnonce)) {
4765
-
4766
- // This is an authenticated Intel AMT event, update the host address
4767
- var amthost = req.clientIp;
4768
- if (amthost.substring(0, 7) === '::ffff:') { amthost = amthost.substring(7); }
4769
- if (node.host != amthost) {
4770
- // Get the mesh for this device
4771
- var mesh = obj.meshes[node.meshid];
4772
- if (mesh) {
4773
- // Update the database
4774
- var oldname = node.host;
4775
- node.host = amthost;
4776
- obj.db.Set(obj.cleanDevice(node));
4777
-
4778
- // Event the node change
4779
- var event = { etype: 'node', action: 'changenode', nodeid: node._id, domain: domain.id, msg: 'Intel(R) AMT host change ' + node.name + ' from group ' + mesh.name + ': ' + oldname + ' to ' + amthost };
4780
-
4781
- // Remove the Intel AMT password before eventing this.
4782
- event.node = node;
4783
- if (event.node.intelamt && event.node.intelamt.pass) {
4784
- event.node = Object.assign({}, event.node); // Shallow clone
4785
- event.node.intelamt = Object.assign({}, event.node.intelamt); // Shallow clone
4786
- delete event.node.intelamt.pass;
4787
- }
4788
-
4789
- 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.
4790
- obj.parent.DispatchEvent(['*', node.meshid], obj, event);
4791
- }
4792
- }
4793
-
4794
- if (parent.amtEventHandler) { parent.amtEventHandler.handleAmtEvent(eventData, nodeid, amthost); }
4795
- //res.send('OK');
4796
-
4797
- return;
4798
- }
4799
- }
4800
- });
4801
- }
4802
- }
4803
- });
4804
- }
4805
- }
4806
- }
4807
- } catch (e) { console.log(e); }
4808
-
4809
- // Send authentication response
4810
- obj.crypto.randomBytes(48, function (err, buf) {
4811
- var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(nonce).digest('hex');
4812
- res.set({ 'WWW-Authenticate': 'Digest realm="' + obj.httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' });
4813
- res.sendStatus(401);
4814
- });
4815
- };
4816
-
4817
- // Handle a server backup request
4818
- function handleBackupRequest(req, res) {
4819
- const domain = checkUserIpAddress(req, res);
4820
- if (domain == null) { return; }
4821
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4822
- if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4823
- if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver.backup !== true))) { res.sendStatus(401); return; }
4824
-
4825
- var user = obj.users[req.session.userid];
4826
- if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights
4827
-
4828
- // Require modules
4829
- const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method to maximum.
4830
-
4831
- // Good practice to catch this error explicitly
4832
- archive.on('error', function (err) { throw err; });
4833
-
4834
- // Set the archive name
4835
- res.attachment((domain.title ? domain.title : 'MeshCentral') + '-Backup-' + new Date().toLocaleDateString().replace('/', '-').replace('/', '-') + '.zip');
4836
-
4837
- // Pipe archive data to the file
4838
- archive.pipe(res);
4839
-
4840
- // Append files from a glob pattern
4841
- archive.directory(obj.parent.datapath, false);
4842
-
4843
- // Finalize the archive (ie we are done appending files but streams have to finish yet)
4844
- archive.finalize();
4845
- }
4846
-
4847
- // Handle a server restore request
4848
- function handleRestoreRequest(req, res) {
4849
- const domain = checkUserIpAddress(req, res);
4850
- if (domain == null) { return; }
4851
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4852
- if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver.restore !== true))) { res.sendStatus(401); return; }
4853
-
4854
- var authUserid = null;
4855
- if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
4856
- const multiparty = require('multiparty');
4857
- const form = new multiparty.Form();
4858
- form.parse(req, function (err, fields, files) {
4859
- // If an authentication cookie is embedded in the form, use that.
4860
- if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
4861
- var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
4862
- if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
4863
- if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
4864
- }
4865
- if (authUserid == null) { res.sendStatus(401); return; }
4866
-
4867
- // Get the user
4868
- const user = obj.users[req.session.userid];
4869
- if ((user == null) || ((user.siteadmin & 4) == 0)) { res.sendStatus(401); return; } // Check if we have server restore rights
4870
-
4871
- res.set('Content-Type', 'text/html');
4872
- res.end('<html><body>Server must be restarted, <a href="' + domain.url + '">click here to login</a>.</body></html>');
4873
- parent.Stop(files.datafile[0].path);
4874
- });
4875
- }
4876
-
4877
- // Handle a request to download a mesh agent
4878
- obj.handleMeshAgentRequest = function (req, res) {
4879
- var domain = getDomain(req, res);
4880
- if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); try { res.sendStatus(404); } catch (ex) { } return; }
4881
-
4882
- // If required, check if this user has rights to do this
4883
- if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true)) && (req.session.userid == null)) { res.sendStatus(401); return; }
4884
-
4885
- if ((req.query.meshinstall != null) && (req.query.id != null)) {
4886
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { try { res.sendStatus(404); } catch (ex) { } return; } // Check 3FA URL key
4887
-
4888
- // Send meshagent with included self installer for a specific platform back
4889
- // Start by getting the .msh for this request
4890
- var meshsettings = getMshFromRequest(req, res, domain);
4891
- if (meshsettings == null) { try { res.sendStatus(401); } catch (ex) { } return; }
4892
-
4893
- // Get the interactive install script, this only works for non-Windows agents
4894
- var agentid = parseInt(req.query.meshinstall);
4895
- var argentInfo = obj.parent.meshAgentBinaries[agentid];
4896
- if (domain.meshAgentBinaries && domain.meshAgentBinaries[agentid]) { argentInfo = domain.meshAgentBinaries[agentid]; }
4897
- var scriptInfo = obj.parent.meshAgentInstallScripts[6];
4898
- if ((argentInfo == null) || (scriptInfo == null) || (argentInfo.platform == 'win32')) { try { res.sendStatus(404); } catch (ex) { } return; }
4899
-
4900
- // Change the .msh file into JSON format and merge it into the install script
4901
- var tokens, msh = {}, meshsettingslines = meshsettings.split('\r').join('').split('\n');
4902
- for (var i in meshsettingslines) { tokens = meshsettingslines[i].split('='); if (tokens.length == 2) { msh[tokens[0]] = tokens[1]; } }
4903
- var js = scriptInfo.data.replace('var msh = {};', 'var msh = ' + JSON.stringify(msh) + ';');
4904
-
4905
- // Get the agent filename
4906
- var meshagentFilename = 'meshagent';
4907
- if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) { meshagentFilename = domain.agentcustomization.filename; }
4908
-
4909
- setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename, null, 'meshagent');
4910
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4911
- res.statusCode = 200;
4912
- obj.parent.exeHandler.streamExeWithJavaScript({ platform: argentInfo.platform, sourceFileName: argentInfo.path, destinationStream: res, js: Buffer.from(js, 'utf8'), peinfo: argentInfo.pe });
4913
- } else if (req.query.id != null) {
4914
- // Send a specific mesh agent back
4915
- var argentInfo = obj.parent.meshAgentBinaries[req.query.id];
4916
- if (domain.meshAgentBinaries && domain.meshAgentBinaries[req.query.id]) { argentInfo = domain.meshAgentBinaries[req.query.id]; }
4917
- if (argentInfo == null) { try { res.sendStatus(404); } catch (ex) { } return; }
4918
-
4919
- // Download PDB debug files, only allowed for administrator or accounts with agent dump access
4920
- if (req.query.pdb == 1) {
4921
- if ((req.session == null) || (req.session.userid == null)) { try { res.sendStatus(404); } catch (ex) { } return; }
4922
- var user = obj.users[req.session.userid];
4923
- if (user == null) { try { res.sendStatus(404); } catch (ex) { } return; }
4924
- if ((user != null) && ((user.siteadmin == 0xFFFFFFFF) || ((Array.isArray(obj.parent.config.settings.agentcoredumpusers)) && (obj.parent.config.settings.agentcoredumpusers.indexOf(user._id) >= 0)))) {
4925
- if (argentInfo.id == 3) {
4926
- setContentDispositionHeader(res, 'application/octet-stream', 'MeshService.pdb', null, 'MeshService.pdb');
4927
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4928
- try { res.sendFile(argentInfo.path.split('MeshService-signed.exe').join('MeshService.pdb')); } catch (ex) { }
4929
- return;
4930
- }
4931
- if (argentInfo.id == 4) {
4932
- setContentDispositionHeader(res, 'application/octet-stream', 'MeshService64.pdb', null, 'MeshService64.pdb');
4933
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4934
- try { res.sendFile(argentInfo.path.split('MeshService64-signed.exe').join('MeshService64.pdb')); } catch (ex) { }
4935
- return;
4936
- }
4937
- }
4938
- try { res.sendStatus(404); } catch (ex) { }
4939
- return;
4940
- }
4941
-
4942
- if ((req.query.meshid == null) || (argentInfo.platform != 'win32')) {
4943
- // Get the agent filename
4944
- var meshagentFilename = argentInfo.rname;
4945
- if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) { meshagentFilename = domain.agentcustomization.filename; }
4946
- if (argentInfo.mtime != null) { res.setHeader('Last-Modified', argentInfo.mtime.toUTCString()); }
4947
- if (req.query.zip == 1) { if (argentInfo.zdata != null) { setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename + '.zip', null, 'meshagent.zip'); res.send(argentInfo.zdata); } else { try { res.sendStatus(404); } catch (ex) { } } return; } // Send compressed agent
4948
- setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename, null, 'meshagent');
4949
- if (argentInfo.data == null) { res.sendFile(argentInfo.path); } else { res.send(argentInfo.data); }
4950
- return;
4951
- } else {
4952
- // Check if the meshid is a time limited, encrypted cookie
4953
- var meshcookie = obj.parent.decodeCookie(req.query.meshid, obj.parent.invitationLinkEncryptionKey);
4954
- if ((meshcookie != null) && (meshcookie.m != null)) { req.query.meshid = meshcookie.m; }
4955
-
4956
- // We are going to embed the .msh file into the Windows executable (signed or not).
4957
- // First, fetch the mesh object to build the .msh file
4958
- var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.meshid];
4959
- if (mesh == null) { try { res.sendStatus(401); } catch (ex) { } return; }
4960
-
4961
- // If required, check if this user has rights to do this
4962
- if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true))) {
4963
- if ((domain.id != mesh.domain) || ((obj.GetMeshRights(req.session.userid, mesh) & 1) == 0)) { try { res.sendStatus(401); } catch (ex) { } return; }
4964
- }
4965
-
4966
- var meshidhex = Buffer.from(req.query.meshid.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4967
- var serveridhex = Buffer.from(obj.agentCertificateHashBase64.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4968
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port if specified
4969
- if (obj.args.agentport != null) { httpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
4970
- if (obj.args.agentaliasport != null) { httpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
4971
-
4972
- // Prepare a mesh agent file name using the device group name.
4973
- var meshfilename = mesh.name
4974
- meshfilename = meshfilename.split('\\').join('').split('/').join('').split(':').join('').split('*').join('').split('?').join('').split('"').join('').split('<').join('').split('>').join('').split('|').join('').split(' ').join('').split('\'').join('');
4975
- if (argentInfo.rname.endsWith('.exe')) { meshfilename = argentInfo.rname.substring(0, argentInfo.rname.length - 4) + '-' + meshfilename + '.exe'; } else { meshfilename = argentInfo.rname + '-' + meshfilename; }
4976
-
4977
- // Customize the mesh agent file name
4978
- if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) {
4979
- meshfilename = meshfilename.split('meshagent').join(domain.agentcustomization.filename).split('MeshAgent').join(domain.agentcustomization.filename);
4980
- }
4981
-
4982
- // Get the agent connection server name
4983
- var serverName = obj.getWebServerName(domain);
4984
- if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
4985
-
4986
- // Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
4987
- var xdomain = (domain.dns == null) ? domain.id : '';
4988
- if (xdomain != '') xdomain += '/';
4989
- var meshsettings = '\r\nMeshName=' + mesh.name + '\r\nMeshType=' + mesh.mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n';
4990
- if (obj.args.lanonly != true) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n'; } else {
4991
- meshsettings += 'MeshServer=local\r\n';
4992
- if ((obj.args.localdiscovery != null) && (typeof obj.args.localdiscovery.key == 'string') && (obj.args.localdiscovery.key.length > 0)) { meshsettings += 'DiscoveryKey=' + obj.args.localdiscovery.key + '\r\n'; }
4993
- }
4994
- if ((req.query.tag != null) && (typeof req.query.tag == 'string') && (obj.common.isAlphaNumeric(req.query.tag) == true)) { meshsettings += 'Tag=' + req.query.tag + '\r\n'; }
4995
- if ((req.query.installflags != null) && (req.query.installflags != 0) && (parseInt(req.query.installflags) == req.query.installflags)) { meshsettings += 'InstallFlags=' + parseInt(req.query.installflags) + '\r\n'; }
4996
- if (req.query.id == '10006') { // Assistant settings and customizations
4997
- if ((req.query.ac != null)) { meshsettings += 'AutoConnect=' + req.query.ac + '\r\n'; } // Set MeshCentral Assistant flags if needed. 0x01 = Always Connected, 0x02 = Not System Tray
4998
- if (obj.args.assistantconfig) { for (var i in obj.args.assistantconfig) { meshsettings += obj.args.assistantconfig[i] + '\r\n'; } }
4999
- if (domain.assistantconfig) { for (var i in domain.assistantconfig) { meshsettings += domain.assistantconfig[i] + '\r\n'; } }
This file is too large to show in full.