Fixed messenger real name display.

Ylian Saint-Hilaire committed Jan 27, 2021 at 15:32 UTC 7bbba5215a2afda729ec6d0829d99cd81192c4a0
2 files changed +1 -6620
webserver-old.js deleted
-6619
@@ -1,6619 +0,0 @@
1 -/**
2 -* @description MeshCentral web server
3 -* @author Ylian Saint-Hilaire
4 -* @copyright Intel Corporation 2018-2021
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) {
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 - const constants = (obj.crypto.constants ? obj.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
56 -
57 - // Setup WebAuthn / FIDO2
58 - obj.webauthn = require('./webauthn.js').CreateWebAuthnModule();
59 -
60 - // Variables
61 - obj.args = args;
62 - obj.parent = parent;
63 - obj.filespath = parent.filespath;
64 - obj.db = db;
65 - obj.app = obj.express();
66 - if (obj.args.agentport) { obj.agentapp = obj.express(); }
67 - if (args.compression !== false) { obj.app.use(require('compression')()); }
68 - obj.app.disable('x-powered-by');
69 - obj.tlsServer = null;
70 - obj.tcpServer = null;
71 - obj.certificates = certificates;
72 - obj.users = {}; // UserID --> User
73 - obj.meshes = {}; // MeshID --> Mesh (also called device group)
74 - obj.userGroups = {}; // UGrpID --> User Group
75 - obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
76 - obj.agentAllowedIp = args.agentallowedip; // List of allowed IP addresses for agents
77 - obj.agentBlockedIp = args.agentblockedip; // List of blocked IP addresses for agents
78 - obj.tlsSniCredentials = null;
79 - obj.dnsDomains = {};
80 - obj.relaySessionCount = 0;
81 - obj.relaySessionErrorCount = 0;
82 - obj.blockedUsers = 0;
83 - obj.blockedAgents = 0;
84 - obj.renderPages = null;
85 - obj.renderLanguages = [];
86 -
87 - // Mesh Rights
88 - const MESHRIGHT_EDITMESH = 1;
89 - const MESHRIGHT_MANAGEUSERS = 2;
90 - const MESHRIGHT_MANAGECOMPUTERS = 4;
91 - const MESHRIGHT_REMOTECONTROL = 8;
92 - const MESHRIGHT_AGENTCONSOLE = 16;
93 - const MESHRIGHT_SERVERFILES = 32;
94 - const MESHRIGHT_WAKEDEVICE = 64;
95 - const MESHRIGHT_SETNOTES = 128;
96 -
97 - // Site rights
98 - const SITERIGHT_SERVERBACKUP = 1;
99 - const SITERIGHT_MANAGEUSERS = 2;
100 - const SITERIGHT_SERVERRESTORE = 4;
101 - const SITERIGHT_FILEACCESS = 8;
102 - const SITERIGHT_SERVERUPDATE = 16;
103 - const SITERIGHT_LOCKED = 32;
104 -
105 - // Setup SSPI authentication if needed
106 - if ((obj.parent.platform == 'win32') && (obj.args.nousers != true) && (obj.parent.config != null) && (obj.parent.config.domains != null)) {
107 - 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: true, offerBasic: false }); } }
108 - }
109 -
110 - // Perform hash on web certificate and agent certificate
111 - obj.webCertificateHash = obj.defaultWebCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert);
112 - obj.webCertificateHashs = { '': obj.webCertificateHash };
113 - obj.webCertificateHashBase64 = Buffer.from(obj.webCertificateHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
114 - obj.webCertificateFullHash = obj.defaultWebCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert);
115 - obj.webCertificateFullHashs = { '': obj.webCertificateFullHash };
116 - obj.agentCertificateHashHex = parent.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert);
117 - obj.agentCertificateHashBase64 = Buffer.from(obj.agentCertificateHashHex, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
118 - obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
119 -
120 - // Compute the hash of all of the web certificates for each domain
121 - for (var i in obj.parent.config.domains) {
122 - if (obj.parent.config.domains[i].certhash != null) {
123 - // If the web certificate hash is provided, use it.
124 - obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i] = Buffer.from(obj.parent.config.domains[i].certhash, 'hex').toString('binary');
125 - if (obj.parent.config.domains[i].certkeyhash != null) { obj.webCertificateHashs[i] = Buffer.from(obj.parent.config.domains[i].certkeyhash, 'hex').toString('binary'); }
126 - } else if ((obj.parent.config.domains[i].dns != null) && (obj.parent.config.domains[i].certs != null)) {
127 - // If the domain has a different DNS name, use a different certificate hash.
128 - // Hash the full certificate
129 - obj.webCertificateFullHashs[i] = parent.certificateOperations.getCertHashBinary(obj.parent.config.domains[i].certs.cert);
130 - try {
131 - // Decode a RSA certificate and hash the public key.
132 - obj.webCertificateHashs[i] = parent.certificateOperations.getPublicKeyHashBinary(obj.parent.config.domains[i].certs.cert);
133 - } catch (ex) {
134 - // This may be a ECDSA certificate, hash the entire cert.
135 - obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i];
136 - }
137 - } else if ((obj.parent.config.domains[i].dns != null) && (obj.certificates.dns[i] != null)) {
138 - // If this domain has a DNS and a matching DNS cert, use it. This case works for wildcard certs.
139 - obj.webCertificateFullHashs[i] = parent.certificateOperations.getCertHashBinary(obj.certificates.dns[i].cert);
140 - obj.webCertificateHashs[i] = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.dns[i].cert);
141 - } else if (i != '') {
142 - // For any other domain, use the default cert.
143 - obj.webCertificateFullHashs[i] = obj.webCertificateFullHashs[''];
144 - obj.webCertificateHashs[i] = obj.webCertificateHashs[''];
145 - }
146 - }
147 -
148 - // If we are running the legacy swarm server, compute the hash for that certificate
149 - if (parent.certificates.swarmserver != null) {
150 - obj.swarmCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.swarmserver.cert))).getBytes();
151 - 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' });
152 - 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' });
153 - }
154 -
155 - // Main lists
156 - obj.wsagents = {}; // NodeId --> Agent
157 - obj.wsagentsWithBadWebCerts = {}; // NodeId --> Agent
158 - obj.wsagentsDisconnections = {};
159 - obj.wsagentsDisconnectionsTimer = null;
160 - obj.duplicateAgentsLog = {};
161 - obj.wssessions = {}; // UserId --> Array Of Sessions
162 - obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
163 - obj.wsPeerSessions = {}; // ServerId --> Array Of "UserId + SessionRnd"
164 - obj.wsPeerSessions2 = {}; // "UserId + SessionRnd" --> ServerId
165 - obj.wsPeerSessions3 = {}; // ServerId --> UserId --> [ SessionId ]
166 - obj.sessionsCount = {}; // Merged session counters, used when doing server peering. UserId --> SessionCount
167 - obj.wsrelays = {}; // Id -> Relay
168 - obj.desktoprelays = {}; // Id -> Desktop Multiplexor Relay
169 - obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
170 - var tlsSessionStore = {}; // Store TLS session information for quick resume.
171 - var tlsSessionStoreCount = 0; // Number of cached TLS session information in store.
172 -
173 - // Setup randoms
174 - obj.crypto.randomBytes(48, function (err, buf) { obj.httpAuthRandom = buf; });
175 - obj.crypto.randomBytes(16, function (err, buf) { obj.httpAuthRealm = buf.toString('hex'); });
176 - obj.crypto.randomBytes(48, function (err, buf) { obj.relayRandom = buf; });
177 -
178 - // Get non-english web pages and emails
179 - getRenderList();
180 - getEmailLanguageList();
181 -
182 - // Setup DNS domain TLS SNI credentials
183 - {
184 - var dnscount = 0;
185 - obj.tlsSniCredentials = {};
186 - 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++; } }
187 - 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; }
188 - }
189 - function TlsSniCallback(name, cb) {
190 - var c = obj.tlsSniCredentials[name];
191 - if (c != null) {
192 - cb(null, c);
193 - } else {
194 - cb(null, obj.tlsSniCredentials['']);
195 - }
196 - }
197 -
198 - function EscapeHtml(x) { if (typeof x == 'string') return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == 'boolean') return x; if (typeof x == 'number') return x; }
199 - //function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
200 - // Fetch all users from the database, keep this in memory
201 - obj.db.GetAllType('user', function (err, docs) {
202 - obj.common.unEscapeAllLinksFieldName(docs);
203 - var domainUserCount = {}, i = 0;
204 - for (i in parent.config.domains) { domainUserCount[i] = 0; }
205 - for (i in docs) { var u = obj.users[docs[i]._id] = docs[i]; domainUserCount[u.domain]++; }
206 - for (i in parent.config.domains) {
207 - if ((parent.config.domains[i].share == null) && (domainUserCount[i] == 0)) {
208 - // If newaccounts is set to no new accounts, but no accounts exists, temporarly allow account creation.
209 - //if ((parent.config.domains[i].newaccounts === 0) || (parent.config.domains[i].newaccounts === false)) { parent.config.domains[i].newaccounts = 2; }
210 - console.log('Server ' + ((i == '') ? '' : (i + ' ')) + 'has no users, next new account will be site administrator.');
211 - }
212 - }
213 -
214 - // Fetch all device groups (meshes) from the database, keep this in memory
215 - // As we load things in memory, we will also be doing some cleaning up.
216 - // We will not save any clean up in the database right now, instead it will be saved next time there is a change.
217 - obj.db.GetAllType('mesh', function (err, docs) {
218 - obj.common.unEscapeAllLinksFieldName(docs);
219 - for (var i in docs) { obj.meshes[docs[i]._id] = docs[i]; } // Get all meshes, including deleted ones.
220 -
221 - // Fetch all user groups from the database, keep this in memory
222 - obj.db.GetAllType('ugrp', function (err, docs) {
223 - obj.common.unEscapeAllLinksFieldName(docs);
224 -
225 - // Perform user group link cleanup
226 - for (var i in docs) {
227 - const ugrp = docs[i];
228 - if (ugrp.links != null) {
229 - for (var j in ugrp.links) {
230 - if (j.startsWith('user/') && (obj.users[j] == null)) { delete ugrp.links[j]; } // User group has a link to a user that does not exist
231 - 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
232 - }
233 - }
234 - obj.userGroups[docs[i]._id] = docs[i]; // Get all user groups
235 - }
236 -
237 - // Perform device group link cleanup
238 - for (var i in obj.meshes) {
239 - const mesh = obj.meshes[i];
240 - if (mesh.links != null) {
241 - for (var j in mesh.links) {
242 - 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
243 - 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
244 - }
245 - }
246 - }
247 -
248 - // Perform user link cleanup
249 - for (var i in obj.users) {
250 - const user = obj.users[i];
251 - if (user.links != null) {
252 - for (var j in user.links) {
253 - if (j.startsWith('ugrp/') && (obj.userGroups[j] == null)) { delete user.links[j]; } // User has a link to a user group that does not exist
254 - 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
255 - //else if (j.startsWith('node/') && (obj.nodes[j] == null)) { delete user.links[j]; } // TODO
256 - }
257 - //if (Object.keys(user.links).length == 0) { delete user.links; }
258 - }
259 - }
260 -
261 - // We loaded the users, device groups and user group state, start the server
262 - serverStart();
263 - });
264 - });
265 - });
266 -
267 - // Clean up a device, used before saving it in the database
268 - obj.cleanDevice = function (device) {
269 - // Check device links, if a link points to an unknown user, remove it.
270 - if (device.links != null) {
271 - for (var j in device.links) {
272 - if ((obj.users[j] == null) && (obj.userGroups[j] == null)) {
273 - delete device.links[j];
274 - if (Object.keys(device.links).length == 0) { delete device.links; }
275 - }
276 - }
277 - }
278 - return device;
279 - }
280 -
281 - // Return statistics about this web server
282 - obj.getStats = function () {
283 - return {
284 - users: Object.keys(obj.users).length,
285 - meshes: Object.keys(obj.meshes).length,
286 - dnsDomains: Object.keys(obj.dnsDomains).length,
287 - relaySessionCount: obj.relaySessionCount,
288 - relaySessionErrorCount: obj.relaySessionErrorCount,
289 - wsagents: Object.keys(obj.wsagents).length,
290 - wsagentsDisconnections: Object.keys(obj.wsagentsDisconnections).length,
291 - wsagentsDisconnectionsTimer: Object.keys(obj.wsagentsDisconnectionsTimer).length,
292 - wssessions: Object.keys(obj.wssessions).length,
293 - wssessions2: Object.keys(obj.wssessions2).length,
294 - wsPeerSessions: Object.keys(obj.wsPeerSessions).length,
295 - wsPeerSessions2: Object.keys(obj.wsPeerSessions2).length,
296 - wsPeerSessions3: Object.keys(obj.wsPeerSessions3).length,
297 - sessionsCount: Object.keys(obj.sessionsCount).length,
298 - wsrelays: Object.keys(obj.wsrelays).length,
299 - wsPeerRelays: Object.keys(obj.wsPeerRelays).length,
300 - tlsSessionStore: Object.keys(tlsSessionStore).length,
301 - blockedUsers: obj.blockedUsers,
302 - blockedAgents: obj.blockedAgents
303 - };
304 - }
305 -
306 - // Agent counters
307 - obj.agentStats = {
308 - createMeshAgentCount: 0,
309 - agentClose: 0,
310 - agentBinaryUpdate: 0,
311 - coreIsStableCount: 0,
312 - verifiedAgentConnectionCount: 0,
313 - clearingCoreCount: 0,
314 - updatingCoreCount: 0,
315 - recoveryCoreIsStableCount: 0,
316 - meshDoesNotExistCount: 0,
317 - invalidPkcsSignatureCount: 0,
318 - invalidRsaSignatureCount: 0,
319 - invalidJsonCount: 0,
320 - unknownAgentActionCount: 0,
321 - agentBadWebCertHashCount: 0,
322 - agentBadSignature1Count: 0,
323 - agentBadSignature2Count: 0,
324 - agentMaxSessionHoldCount: 0,
325 - invalidDomainMeshCount: 0,
326 - invalidMeshTypeCount: 0,
327 - invalidDomainMesh2Count: 0,
328 - invalidMeshType2Count: 0,
329 - duplicateAgentCount: 0,
330 - maxDomainDevicesReached: 0
331 - }
332 - obj.getAgentStats = function () { return obj.agentStats; }
333 -
334 - // Authenticate the user
335 - obj.authenticate = function (name, pass, domain, fn) {
336 - if ((typeof (name) != 'string') || (typeof (pass) != 'string') || (typeof (domain) != 'object')) { fn(new Error('invalid fields')); return; }
337 - if (domain.auth == 'ldap') {
338 - if (domain.ldapoptions.url == 'test') {
339 - // Fake LDAP login
340 - var xxuser = domain.ldapoptions[name.toLowerCase()];
341 - if (xxuser == null) {
342 - fn(new Error('invalid password'));
343 - return;
344 - } else {
345 - var username = xxuser['displayName'];
346 - if (domain.ldapusername) { username = xxuser[domain.ldapusername]; }
347 - var shortname = null;
348 - if (domain.ldapuserbinarykey) {
349 - // Use a binary key as the userid
350 - if (xxuser[domain.ldapuserbinarykey]) { shortname = Buffer.from(xxuser[domain.ldapuserbinarykey], 'binary').toString('hex'); }
351 - } else if (domain.ldapuserkey) {
352 - // Use a string key as the userid
353 - if (xxuser[domain.ldapuserkey]) { shortname = xxuser[domain.ldapuserkey]; }
354 - } else {
355 - // Use the default key as the userid
356 - if (xxuser.objectSid) { shortname = Buffer.from(xxuser.objectSid, 'binary').toString('hex').toLowerCase(); }
357 - else if (xxuser.objectGUID) { shortname = Buffer.from(xxuser.objectGUID, 'binary').toString('hex').toLowerCase(); }
358 - else if (xxuser.name) { shortname = xxuser.name; }
359 - else if (xxuser.cn) { shortname = xxuser.cn; }
360 - }
361 - if (username == null) { fn(new Error('no user name')); return; }
362 - if (shortname == null) { fn(new Error('no user identifier')); return; }
363 - var userid = 'user/' + domain.id + '/' + shortname;
364 - var user = obj.users[userid];
365 - var email = null;
366 - if (domain.ldapuseremail) {
367 - email = xxuser[domain.ldapuseremail];
368 - } else if (xxuser.mail) { // use default
369 - email = xxuser.mail;
370 - }
371 - if ('[object Array]' == Object.prototype.toString.call(email)) {
372 - // mail may be multivalued in ldap in which case, answer is an array. Use the 1st value.
373 - email = email[0];
374 - }
375 - if (email) { email = email.toLowerCase(); } // it seems some code otherwhere also lowercase the emailaddress. be compatible.
376 -
377 - if (user == null) {
378 - // Create a new user
379 - var user = { type: 'user', _id: userid, name: username, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), domain: domain.id };
380 - if (email) { user['email'] = email; user['emailVerified'] = true; }
381 - if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
382 - if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
383 - var usercount = 0;
384 - for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
385 - if (usercount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
386 -
387 - // Auto-join any user groups
388 - if (typeof domain.newaccountsusergroups == 'object') {
389 - for (var i in domain.newaccountsusergroups) {
390 - var ugrpid = domain.newaccountsusergroups[i];
391 - if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
392 - var ugroup = obj.userGroups[ugrpid];
393 - if (ugroup != null) {
394 - // Add group to the user
395 - if (user.links == null) { user.links = {}; }
396 - user.links[ugroup._id] = { rights: 1 };
397 -
398 - // Add user to the group
399 - ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
400 - db.Set(ugroup);
401 -
402 - // Notify user group change
403 - 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 };
404 - 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.
405 - parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
406 - }
407 - }
408 - }
409 -
410 - obj.users[user._id] = user;
411 - obj.db.SetUser(user);
412 - var event = { etype: 'user', userid: userid, username: username, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, name is ' + name, domain: domain.id };
413 - 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.
414 - obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
415 - return fn(null, user._id);
416 - } else {
417 - // This is an existing user
418 - // If the display username has changes, update it.
419 - if (user.name != username) {
420 - user.name = username;
421 - obj.db.SetUser(user);
422 - var event = { etype: 'user', userid: userid, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Changed account display name to ' + username, domain: domain.id };
423 - 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.
424 - parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
425 - }
426 - // Check if user email has changed
427 - var emailreason = null;
428 - if (user.email && !email) { // email unset in ldap => unset
429 - delete user.email;
430 - delete user.emailVerified;
431 - emailreason = 'Unset email (no more email in LDAP)'
432 - } else if (user.email != email) { // update email
433 - user['email'] = email;
434 - user['emailVerified'] = true;
435 - emailreason = 'Set account email to ' + email + '. Sync with LDAP.';
436 - }
437 - if (emailreason) {
438 - obj.db.SetUser(user);
439 - var event = { etype: 'user', userid: userid, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: emailreason, domain: domain.id };
440 - 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.
441 - parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
442 - }
443 - // If user is locker out, block here.
444 - if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
445 - return fn(null, user._id);
446 - }
447 - }
448 - } else {
449 - // LDAP login
450 - var LdapAuth = require('ldapauth-fork');
451 - var ldap = new LdapAuth(domain.ldapoptions);
452 - ldap.authenticate(name, pass, function (err, xxuser) {
453 - try { ldap.close(); } catch (ex) { console.log(ex); } // Close the LDAP object
454 - if (err) { fn(new Error('invalid password')); return; }
455 - var shortname = null;
456 - var email = null;
457 - if (domain.ldapuseremail) {
458 - email = xxuser[domain.ldapuseremail];
459 - } else if (xxuser.mail) {
460 - email = xxuser.mail;
461 - }
462 - if ('[object Array]' == Object.prototype.toString.call(email)) {
463 - // mail may be multivalued in ldap in which case, answer would be an array. Use the 1st one.
464 - email = email[0];
465 - }
466 - if (email) { email = email.toLowerCase(); } // it seems some code otherwhere also lowercase the emailaddress. be compatible.
467 - var username = xxuser['displayName'];
468 - if (domain.ldapusername) { username = xxuser[domain.ldapusername]; }
469 - if (domain.ldapuserbinarykey) {
470 - // Use a binary key as the userid
471 - if (xxuser[domain.ldapuserbinarykey]) { shortname = Buffer.from(xxuser[domain.ldapuserbinarykey], 'binary').toString('hex').toLowerCase(); }
472 - } else if (domain.ldapuserkey) {
473 - // Use a string key as the userid
474 - if (xxuser[domain.ldapuserkey]) { shortname = xxuser[domain.ldapuserkey]; }
475 - } else {
476 - // Use the default key as the userid
477 - if (xxuser.objectSid) { shortname = Buffer.from(xxuser.objectSid, 'binary').toString('hex').toLowerCase(); }
478 - else if (xxuser.objectGUID) { shortname = Buffer.from(xxuser.objectGUID, 'binary').toString('hex').toLowerCase(); }
479 - else if (xxuser.name) { shortname = xxuser.name; }
480 - else if (xxuser.cn) { shortname = xxuser.cn; }
481 - }
482 - if (username == null) { fn(new Error('no user name')); return; }
483 - if (shortname == null) { fn(new Error('no user identifier')); return; }
484 - var userid = 'user/' + domain.id + '/' + shortname;
485 - var user = obj.users[userid];
486 -
487 - if (user == null) {
488 - // This user does not exist, create a new account.
489 - var user = { type: 'user', _id: userid, name: shortname, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), domain: domain.id };
490 - if (email) {
491 - user['email'] = email;
492 - user['emailVerified'] = true;
493 - }
494 - if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
495 - if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
496 - var usercount = 0;
497 - for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
498 - if (usercount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
499 -
500 - // Auto-join any user groups
501 - if (typeof domain.newaccountsusergroups == 'object') {
502 - for (var i in domain.newaccountsusergroups) {
503 - var ugrpid = domain.newaccountsusergroups[i];
504 - if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
505 - var ugroup = obj.userGroups[ugrpid];
506 - if (ugroup != null) {
507 - // Add group to the user
508 - if (user.links == null) { user.links = {}; }
509 - user.links[ugroup._id] = { rights: 1 };
510 -
511 - // Add user to the group
512 - ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
513 - db.Set(ugroup);
514 -
515 - // Notify user group change
516 - 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 };
517 - 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.
518 - parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
519 - }
520 - }
521 - }
522 -
523 - obj.users[user._id] = user;
524 - obj.db.SetUser(user);
525 - var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, name is ' + name, domain: domain.id };
526 - 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.
527 - obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
528 - return fn(null, user._id);
529 - } else {
530 - // This is an existing user
531 - // If the display username has changes, update it.
532 - if (user.name != username) {
533 - user.name = username;
534 - obj.db.SetUser(user);
535 - var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Changed account display name to ' + username, domain: domain.id };
536 - 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.
537 - parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
538 - }
539 - // Check if user email has changed
540 - var emailreason = null;
541 - if (user.email && !email) { // email unset in ldap => unset
542 - delete user.email;
543 - delete user.emailVerified;
544 - emailreason = 'Unset email (no more email in LDAP)'
545 - } else if (user.email != email) { // update email
546 - user['email'] = email;
547 - user['emailVerified'] = true;
548 - emailreason = 'Set account email to ' + email + '. Sync with LDAP.';
549 - }
550 - if (emailreason) {
551 - obj.db.SetUser(user);
552 - var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: emailreason, domain: domain.id };
553 - 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.
554 - parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
555 - }
556 - // If user is locker out, block here.
557 - if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
558 - return fn(null, user._id);
559 - }
560 - });
561 - }
562 - } else {
563 - // Regular login
564 - var user = obj.users['user/' + domain.id + '/' + name.toLowerCase()];
565 - // Query the db for the given username
566 - if (!user) { fn(new Error('cannot find user')); return; }
567 - // Apply the same algorithm to the POSTed password, applying the hash against the pass / salt, if there is a match we found the user
568 - if (user.salt == null) {
569 - fn(new Error('invalid password'));
570 - } else {
571 - if (user.passtype != null) {
572 - // IIS default clear or weak password hashing (SHA-1)
573 - require('./pass').iishash(user.passtype, pass, user.salt, function (err, hash) {
574 - if (err) return fn(err);
575 - if (hash == user.hash) {
576 - // Update the password to the stronger format.
577 - 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);
578 - if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
579 - return fn(null, user._id);
580 - }
581 - fn(new Error('invalid password'), null, user.passhint);
582 - });
583 - } else {
584 - // Default strong password hashing (pbkdf2 SHA384)
585 - require('./pass').hash(pass, user.salt, function (err, hash, tag) {
586 - if (err) return fn(err);
587 - if (hash == user.hash) {
588 - if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
589 - return fn(null, user._id);
590 - }
591 - fn(new Error('invalid password'), null, user.passhint);
592 - }, 0);
593 - }
594 - }
595 - }
596 - };
597 -
598 - /*
599 - obj.restrict = function (req, res, next) {
600 - console.log('restrict', req.url);
601 - var domain = getDomain(req);
602 - if (req.session.userid) {
603 - next();
604 - } else {
605 - req.session.messageid = 111; // Access denied.
606 - res.redirect(domain.url + 'login');
607 - }
608 - };
609 - */
610 -
611 - // Check if the source IP address is in the IP list, return false if not.
612 - function checkIpAddressEx(req, res, ipList, closeIfThis) {
613 - try {
614 - if (req.connection) {
615 - // HTTP(S) request
616 - if (req.clientIp) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(req.clientIp, ipList[i])) { if (closeIfThis === true) { res.sendStatus(401); } return true; } } }
617 - if (closeIfThis === false) { res.sendStatus(401); }
618 - } else {
619 - // WebSocket request
620 - 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; } } }
621 - if (closeIfThis === false) { try { req.close(); } catch (e) { } }
622 - }
623 - } catch (e) { console.log(e); } // Should never happen
624 - return false;
625 - }
626 -
627 - // Check if the source IP address is allowed, return domain if allowed
628 - // If there is a fail and null is returned, the request or connection is closed already.
629 - function checkUserIpAddress(req, res) {
630 - if ((parent.config.settings.userblockedip != null) && (checkIpAddressEx(req, res, parent.config.settings.userblockedip, true) == true)) { obj.blockedUsers++; return null; }
631 - if ((parent.config.settings.userallowedip != null) && (checkIpAddressEx(req, res, parent.config.settings.userallowedip, false) == false)) { obj.blockedUsers++; return null; }
632 - const domain = (req.url ? getDomain(req) : getDomain(res));
633 - if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); try { res.sendStatus(404); } catch (ex) { } return; }
634 - if ((domain.userblockedip != null) && (checkIpAddressEx(req, res, domain.userblockedip, true) == true)) { obj.blockedUsers++; return null; }
635 - if ((domain.userallowedip != null) && (checkIpAddressEx(req, res, domain.userallowedip, false) == false)) { obj.blockedUsers++; return null; }
636 - return domain;
637 - }
638 -
639 - // Check if the source IP address is allowed, return domain if allowed
640 - // If there is a fail and null is returned, the request or connection is closed already.
641 - function checkAgentIpAddress(req, res) {
642 - if ((parent.config.settings.agentblockedip != null) && (checkIpAddressEx(req, res, parent.config.settings.agentblockedip, null) == true)) { obj.blockedAgents++; return null; }
643 - if ((parent.config.settings.agentallowedip != null) && (checkIpAddressEx(req, res, parent.config.settings.agentallowedip, null) == false)) { obj.blockedAgents++; return null; }
644 - const domain = (req.url ? getDomain(req) : getDomain(res));
645 - if ((domain.agentblockedip != null) && (checkIpAddressEx(req, res, domain.agentblockedip, null) == true)) { obj.blockedAgents++; return null; }
646 - if ((domain.agentallowedip != null) && (checkIpAddressEx(req, res, domain.agentallowedip, null) == false)) { obj.blockedAgents++; return null; }
647 - return domain;
648 - }
649 -
650 - // Return the current domain of the request
651 - // Request or connection says open regardless of the response
652 - function getDomain(req) {
653 - if (req.xdomain != null) { return req.xdomain; } // Domain already set for this request, return it.
654 - 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.
655 - var x = req.url.split('/');
656 - if (x.length < 2) return parent.config.domains[''];
657 - var y = parent.config.domains[x[1].toLowerCase()];
658 - if ((y != null) && (y.dns == null)) { return parent.config.domains[x[1].toLowerCase()]; }
659 - return parent.config.domains[''];
660 - }
661 -
662 - function handleLogoutRequest(req, res) {
663 - const domain = checkUserIpAddress(req, res);
664 - if (domain == null) { return; }
665 - if (domain.auth == 'sspi') { parent.debug('web', 'handleLogoutRequest: failed checks.'); res.sendStatus(404); return; }
666 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
667 -
668 - res.set({ 'Cache-Control': 'no-store' });
669 - // Destroy the user's session to log them out will be re-created next request
670 - if (req.session.userid) {
671 - var user = obj.users[req.session.userid];
672 - 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 }); }
673 - }
674 - req.session = null;
675 - if (req.query.key != null) { res.redirect(domain.url + '?key=' + req.query.key); } else { res.redirect(domain.url); }
676 - parent.debug('web', 'handleLogoutRequest: success.');
677 - }
678 -
679 - // Return true if this user has 2-step auth active
680 - function checkUserOneTimePasswordRequired(domain, user, req) {
681 - // Check if we can skip 2nd factor auth because of the source IP address
682 - if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
683 - for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) return false; }
684 - }
685 -
686 - // Check if a 2nd factor cookie is present
687 - if (typeof req.headers.cookie == 'string') {
688 - const cookies = req.headers.cookie.split('; ');
689 - for (var i in cookies) {
690 - if (cookies[i].startsWith('twofactor=')) {
691 - 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.
692 - if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { return false; }
693 - }
694 - }
695 - }
696 -
697 - // See if SMS 2FA is available
698 - var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
699 -
700 - // Check if a 2nd factor is present
701 - return ((parent.config.settings.no2factorauth !== true) && (sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (parent.mailserver != null) && (user.otpekey != null)) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
702 - }
703 -
704 - // Check the 2-step auth token
705 - function checkUserOneTimePassword(req, domain, user, token, hwtoken, func) {
706 - parent.debug('web', 'checkUserOneTimePassword()');
707 - const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (parent.config.settings.no2factorauth !== true));
708 - if (twoStepLoginSupported == false) { parent.debug('web', 'checkUserOneTimePassword: not supported.'); func(true); return; };
709 -
710 - // Check if we can use OTP tokens with email
711 - var otpemail = (parent.mailserver != null);
712 - if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
713 - var otpsms = (parent.smsserver != null);
714 - if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
715 -
716 - // Check 2FA login cookie
717 - if ((token != null) && (token.startsWith('cookie='))) {
718 - 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.
719 - if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { func(true); return; }
720 - }
721 -
722 - // Check email key
723 - if ((otpemail) && (user.otpekey != null) && (user.otpekey.d != null) && (user.otpekey.k === token)) {
724 - var deltaTime = (Date.now() - user.otpekey.d);
725 - if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the email token (10000 * 60 * 5).
726 - user.otpekey = {};
727 - obj.db.SetUser(user);
728 - parent.debug('web', 'checkUserOneTimePassword: success (email).');
729 - func(true);
730 - return;
731 - }
732 - }
733 -
734 - // Check sms key
735 - if ((otpsms) && (user.phone != null) && (user.otpsms != null) && (user.otpsms.d != null) && (user.otpsms.k === token)) {
736 - var deltaTime = (Date.now() - user.otpsms.d);
737 - if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the SMS token (10000 * 60 * 5).
738 - delete user.otpsms;
739 - obj.db.SetUser(user);
740 - parent.debug('web', 'checkUserOneTimePassword: success (SMS).');
741 - func(true);
742 - return;
743 - }
744 - }
745 -
746 - // Check hardware key
747 - if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) {
748 - var authResponse = null;
749 - try { authResponse = JSON.parse(hwtoken); } catch (ex) { }
750 - if ((authResponse != null) && (authResponse.clientDataJSON)) {
751 - // Get all WebAuthn keys
752 - var webAuthnKeys = [];
753 - for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 3) { webAuthnKeys.push(user.otphkeys[i]); } }
754 - if (webAuthnKeys.length > 0) {
755 - // Decode authentication response
756 - var clientAssertionResponse = { response: {} };
757 - clientAssertionResponse.id = authResponse.id;
758 - clientAssertionResponse.rawId = Buffer.from(authResponse.id, 'base64');
759 - clientAssertionResponse.response.authenticatorData = Buffer.from(authResponse.authenticatorData, 'base64');
760 - clientAssertionResponse.response.clientDataJSON = Buffer.from(authResponse.clientDataJSON, 'base64');
761 - clientAssertionResponse.response.signature = Buffer.from(authResponse.signature, 'base64');
762 - clientAssertionResponse.response.userHandle = Buffer.from(authResponse.userHandle, 'base64');
763 -
764 - // Look for the key with clientAssertionResponse.id
765 - var webAuthnKey = null;
766 - for (var i = 0; i < webAuthnKeys.length; i++) { if (webAuthnKeys[i].keyId == clientAssertionResponse.id) { webAuthnKey = webAuthnKeys[i]; } }
767 -
768 - // If we found a valid key to use, let's validate the response
769 - if (webAuthnKey != null) {
770 - // Figure out the origin
771 - var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
772 - var origin = 'https://' + (domain.dns ? domain.dns : parent.certificates.CommonName);
773 - if (httpport != 443) { origin += ':' + httpport; }
774 -
775 - var assertionExpectations = {
776 - challenge: req.session.u2fchallenge,
777 - origin: origin,
778 - factor: 'either',
779 - fmt: 'fido-u2f',
780 - publicKey: webAuthnKey.publicKey,
781 - prevCounter: webAuthnKey.counter,
782 - userHandle: Buffer.from(user._id, 'binary').toString('base64')
783 - };
784 -
785 - var webauthnResponse = null;
786 - try { webauthnResponse = obj.webauthn.verifyAuthenticatorAssertionResponse(clientAssertionResponse.response, assertionExpectations); } catch (ex) { parent.debug('web', 'checkUserOneTimePassword: exception ' + ex); console.log(ex); }
787 - if ((webauthnResponse != null) && (webauthnResponse.verified === true)) {
788 - // Update the hardware key counter and accept the 2nd factor
789 - webAuthnKey.counter = webauthnResponse.counter;
790 - obj.db.SetUser(user);
791 - parent.debug('web', 'checkUserOneTimePassword: success (hardware).');
792 - func(true);
793 - } else {
794 - parent.debug('web', 'checkUserOneTimePassword: fail (hardware).');
795 - func(false);
796 - }
797 - return;
798 - }
799 - }
800 - }
801 - }
802 -
803 - // Check Google Authenticator
804 - const otplib = require('otplib')
805 - otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window
806 - if (user.otpsecret && (typeof (token) == 'string') && (token.length == 6) && (otplib.authenticator.check(token, user.otpsecret) == true)) {
807 - parent.debug('web', 'checkUserOneTimePassword: success (authenticator).');
808 - func(true);
809 - return;
810 - };
811 -
812 - // Check written down keys
813 - if ((user.otpkeys != null) && (user.otpkeys.keys != null) && (typeof (token) == 'string') && (token.length == 8)) {
814 - var tokenNumber = parseInt(token);
815 - for (var i = 0; i < user.otpkeys.keys.length; i++) {
816 - if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) {
817 - parent.debug('web', 'checkUserOneTimePassword: success (one-time).');
818 - user.otpkeys.keys[i].u = false; func(true); return;
819 - }
820 - }
821 - }
822 -
823 - // Check OTP hardware key
824 - 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)) {
825 - var keyId = token.substring(0, 12);
826 -
827 - // Find a matching OTP key
828 - var match = false;
829 - for (var i = 0; i < user.otphkeys.length; i++) { if ((user.otphkeys[i].type === 2) && (user.otphkeys[i].keyid === keyId)) { match = true; } }
830 -
831 - // If we have a match, check the OTP
832 - if (match === true) {
833 - var yubikeyotp = require('yubikeyotp');
834 - var request = { otp: token, id: domain.yubikey.id, key: domain.yubikey.secret, timestamp: true }
835 - if (domain.yubikey.proxy) { request.requestParams = { proxy: domain.yubikey.proxy }; }
836 - yubikeyotp.verifyOTP(request, function (err, results) {
837 - if ((results != null) && (results.status == 'OK')) {
838 - parent.debug('web', 'checkUserOneTimePassword: success (Yubikey).');
839 - func(true);
840 - } else {
841 - parent.debug('web', 'checkUserOneTimePassword: fail (Yubikey).');
842 - func(false);
843 - }
844 - });
845 - return;
846 - }
847 - }
848 -
849 - parent.debug('web', 'checkUserOneTimePassword: fail (2).');
850 - func(false);
851 - }
852 -
853 - // Return a U2F hardware key challenge
854 - function getHardwareKeyChallenge(req, domain, user, func) {
855 - if (req.session.u2fchallenge) { delete req.session.u2fchallenge; };
856 - if (user.otphkeys && (user.otphkeys.length > 0)) {
857 - // Get all WebAuthn keys
858 - var webAuthnKeys = [];
859 - for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 3) { webAuthnKeys.push(user.otphkeys[i]); } }
860 - if (webAuthnKeys.length > 0) {
861 - // Generate a Webauthn challenge, this is really easy, no need to call any modules to do this.
862 - var authnOptions = { type: 'webAuthn', keyIds: [], timeout: 60000, challenge: obj.crypto.randomBytes(64).toString('base64') };
863 - for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[i].keyId); }
864 - req.session.u2fchallenge = authnOptions.challenge;
865 - parent.debug('web', 'getHardwareKeyChallenge: success');
866 - func(JSON.stringify(authnOptions));
867 - return;
868 - }
869 - }
870 - parent.debug('web', 'getHardwareKeyChallenge: fail');
871 - func('');
872 - }
873 -
874 - // Redirect a root request to a different page
875 - function handleRootRedirect(req, res, direct) {
876 - const domain = checkUserIpAddress(req, res);
877 - if (domain == null) { return; }
878 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
879 - res.redirect(domain.rootredirect + getQueryPortion(req));
880 - }
881 -
882 - function handleLoginRequest(req, res, direct) {
883 - const domain = checkUserIpAddress(req, res);
884 - if (domain == null) { return; }
885 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
886 -
887 - // Check if this is a banned ip address
888 - if (obj.checkAllowLogin(req) == false) {
889 - // Wait and redirect the user
890 - setTimeout(function () {
891 - req.session.messageid = 114; // IP address blocked, try again later.
892 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
893 - }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
894 - return;
895 - }
896 -
897 - // Normally, use the body username/password. If this is a token, use the username/password in the session.
898 - var xusername = req.body.username, xpassword = req.body.password;
899 - if ((xusername == null) && (xpassword == null) && (req.body.token != null)) { xusername = req.session.tokenusername; xpassword = req.session.tokenpassword; }
900 -
901 - // Authenticate the user
902 - obj.authenticate(xusername, xpassword, domain, function (err, userid, passhint) {
903 - if (userid) {
904 - var user = obj.users[userid];
905 -
906 - // Check if we are in maintenance mode
907 - if ((parent.config.settings.maintenancemode != null) && (user.siteadmin != 4294967295)) {
908 - req.session.messageid = 115; // Server under maintenance
909 - req.session.loginmode = '1';
910 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
911 - return;
912 - }
913 -
914 - var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (parent.mailserver != null) && (user.email != null) && (user.emailVerified == true) && (user.otpekey != null));
915 - var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
916 -
917 - // Check if this user has 2-step login active
918 - if ((req.session.loginmode != '6') && checkUserOneTimePasswordRequired(domain, user, req)) {
919 - if ((req.body.hwtoken == '**email**') && email2fa) {
920 - user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
921 - obj.db.SetUser(user);
922 - parent.debug('web', 'Sending 2FA email to: ' + user.email);
923 - parent.mailserver.sendAccountLoginMail(domain, user.email, user.otpekey.k, obj.getLanguageCodes(req), req.query.key);
924 - req.session.messageid = 2; // "Email sent" message
925 - req.session.loginmode = '4';
926 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
927 - return;
928 - }
929 -
930 - if ((req.body.hwtoken == '**sms**') && sms2fa) {
931 - // Cause a token to be sent to the user's phone number
932 - user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
933 - obj.db.SetUser(user);
934 - parent.debug('web', 'Sending 2FA SMS to: ' + user.phone);
935 - parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
936 - // Ask for a login token & confirm sms was sent
937 - req.session.messageid = 4; // "SMS sent" message
938 - req.session.loginmode = '4';
939 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
940 - return;
941 - }
942 -
943 - checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
944 - if (result == false) {
945 - var randomWaitTime = 0;
946 -
947 - // 2-step auth is required, but the token is not present or not valid.
948 - if ((req.body.token != null) || (req.body.hwtoken != null)) {
949 - randomWaitTime = 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095); // This is a fail, wait a random time. 2 to 6 seconds.
950 - req.session.messageid = 108; // Invalid token, try again.
951 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr(req.clientIp) + ' port ' + req.port); }
952 - parent.debug('web', 'handleLoginRequest: invalid 2FA token');
953 - 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 });
954 - obj.setbadLogin(req);
955 - } else {
956 - parent.debug('web', 'handleLoginRequest: 2FA token required');
957 - }
958 -
959 - // Wait and redirect the user
960 - setTimeout(function () {
961 - req.session.loginmode = '4';
962 - req.session.tokenemail = ((user.email != null) && (user.emailVerified == true) && (parent.mailserver != null) && (user.otpekey != null));
963 - req.session.tokensms = ((user.phone != null) && (parent.smsserver != null));
964 - req.session.tokenuserid = userid;
965 - req.session.tokenusername = xusername;
966 - req.session.tokenpassword = xpassword;
967 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
968 - }, randomWaitTime);
969 - } else {
970 - // Check if we need to remember this device
971 - if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
972 - var maxCookieAge = domain.twofactorcookiedurationdays;
973 - if (typeof maxCookieAge != 'number') { maxCookieAge = 30; }
974 - const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, obj.parent.loginCookieEncryptionKey);
975 - res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true });
976 - }
977 -
978 - // Check if email address needs to be confirmed
979 - var emailcheck = ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
980 - if (emailcheck && (user.emailVerified !== true)) {
981 - parent.debug('web', 'Redirecting using ' + user.name + ' to email check login page');
982 - req.session.messageid = 3; // "Email verification required" message
983 - req.session.loginmode = '7';
984 - req.session.passhint = user.email;
985 - req.session.cuserid = userid;
986 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
987 - return;
988 - }
989 -
990 - // Login successful
991 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
992 - parent.debug('web', 'handleLoginRequest: successful 2FA login');
993 - completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct);
994 - }
995 - });
996 - return;
997 - }
998 -
999 - // Check if email address needs to be confirmed
1000 - var emailcheck = ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
1001 - if (emailcheck && (user.emailVerified !== true)) {
1002 - parent.debug('web', 'Redirecting using ' + user.name + ' to email check login page');
1003 - req.session.messageid = 3; // "Email verification required" message
1004 - req.session.loginmode = '7';
1005 - req.session.passhint = user.email;
1006 - req.session.cuserid = userid;
1007 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1008 - return;
1009 - }
1010 -
1011 - // Login successful
1012 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1013 - parent.debug('web', 'handleLoginRequest: successful login');
1014 - completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct);
1015 - } else {
1016 - // Login failed, log the error
1017 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
1018 -
1019 - // Wait a random delay
1020 - setTimeout(function () {
1021 - // If the account is locked, display that.
1022 - if (typeof xusername == 'string') {
1023 - var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase();
1024 - if (err == 'locked') {
1025 - parent.debug('web', 'handleLoginRequest: login failed, locked account');
1026 - req.session.messageid = 110; // Account locked.
1027 - 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 });
1028 - obj.setbadLogin(req);
1029 - } else {
1030 - parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
1031 - req.session.messageid = 112; // Login failed, check username and password.
1032 - obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + req.clientIp });
1033 - obj.setbadLogin(req);
1034 - }
1035 - }
1036 -
1037 - // Clean up login mode and display password hint if present.
1038 - delete req.session.loginmode;
1039 - if ((passhint != null) && (passhint.length > 0)) {
1040 - req.session.passhint = passhint;
1041 - } else {
1042 - delete req.session.passhint;
1043 - }
1044 -
1045 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1046 - }, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095)); // Wait for 2 to ~6 seconds.
1047 - }
1048 - });
1049 - }
1050 -
1051 - function completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct) {
1052 - // Check if we need to change the password
1053 - 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))))) {
1054 - // Request a password change
1055 - parent.debug('web', 'handleLoginRequest: login ok, password change requested');
1056 - req.session.loginmode = '6';
1057 - req.session.messageid = 113; // Password change requested.
1058 - req.session.resettokenuserid = userid;
1059 - req.session.resettokenusername = xusername;
1060 - req.session.resettokenpassword = xpassword;
1061 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1062 - return;
1063 - }
1064 -
1065 - // Save login time
1066 - user.pastlogin = user.login;
1067 - user.login = Math.floor(Date.now() / 1000);
1068 - obj.db.SetUser(user);
1069 -
1070 - // Notify account login
1071 - var targets = ['*', 'server-users'];
1072 - if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1073 - obj.parent.DispatchEvent(targets, obj, { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 1, msg: 'Account login', domain: domain.id });
1074 -
1075 - // Regenerate session when signing in to prevent fixation
1076 - //req.session.regenerate(function () {
1077 - // Store the user's primary key in the session store to be retrieved, or in this case the entire user object
1078 - delete req.session.loginmode;
1079 - delete req.session.tokenuserid;
1080 - delete req.session.tokenusername;
1081 - delete req.session.tokenpassword;
1082 - delete req.session.tokenemail;
1083 - delete req.session.tokensms;
1084 - delete req.session.messageid;
1085 - delete req.session.passhint;
1086 - delete req.session.cuserid;
1087 - req.session.userid = userid;
1088 - req.session.domainid = domain.id;
1089 - req.session.currentNode = '';
1090 - req.session.ip = req.clientIp;
1091 - if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
1092 - if (req.body.host) {
1093 - // TODO: This is a terrible search!!! FIX THIS.
1094 - /*
1095 - obj.db.GetAllType('node', function (err, docs) {
1096 - for (var i = 0; i < docs.length; i++) {
1097 - if (docs[i].name == req.body.host) {
1098 - req.session.currentNode = docs[i]._id;
1099 - break;
1100 - }
1101 - }
1102 - console.log("CurrentNode: " + req.session.currentNode);
1103 - // This redirect happens after finding node is completed
1104 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1105 - });
1106 - */
1107 - parent.debug('web', 'handleLoginRequest: login ok (1)');
1108 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } // Temporary
1109 - } else {
1110 - parent.debug('web', 'handleLoginRequest: login ok (2)');
1111 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1112 - }
1113 - //});
1114 - }
1115 -
1116 - function handleCreateAccountRequest(req, res, direct) {
1117 - const domain = checkUserIpAddress(req, res);
1118 - if (domain == null) { return; }
1119 - if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleCreateAccountRequest: failed checks.'); res.sendStatus(404); return; }
1120 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1121 -
1122 - // Check if we are in maintenance mode
1123 - if (parent.config.settings.maintenancemode != null) {
1124 - req.session.messageid = 115; // Server under maintenance
1125 - req.session.loginmode = '1';
1126 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1127 - return;
1128 - }
1129 -
1130 - // Always lowercase the email address
1131 - if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1132 -
1133 - // If the email is the username, set this here.
1134 - if (domain.usernameisemail) { req.body.username = req.body.email; }
1135 -
1136 - // Accounts that start with ~ are not allowed
1137 - if ((typeof req.body.username != 'string') || (req.body.username.length < 1) || (req.body.username[0] == '~')) {
1138 - parent.debug('web', 'handleCreateAccountRequest: unable to create account (0)');
1139 - req.session.loginmode = '2';
1140 - req.session.messageid = 100; // Unable to create account.
1141 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1142 - return;
1143 - }
1144 -
1145 - // Count the number of users in this domain
1146 - var domainUserCount = 0;
1147 - for (var i in obj.users) { if (obj.users[i].domain == domain.id) { domainUserCount++; } }
1148 -
1149 - // Check if we are allowed to create new users using the login screen
1150 - if ((domain.newaccounts !== 1) && (domain.newaccounts !== true) && (domainUserCount > 0)) {
1151 - parent.debug('web', 'handleCreateAccountRequest: domainUserCount > 1.');
1152 - res.sendStatus(401);
1153 - return;
1154 - }
1155 -
1156 - // Check if this request is for an allows email domain
1157 - if ((domain.newaccountemaildomains != null) && Array.isArray(domain.newaccountemaildomains)) {
1158 - var i = -1;
1159 - if (typeof req.body.email == 'string') { i = req.body.email.indexOf('@'); }
1160 - if (i == -1) {
1161 - parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
1162 - req.session.loginmode = '2';
1163 - req.session.messageid = 100; // Unable to create account.
1164 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1165 - return;
1166 - }
1167 - var emailok = false, emaildomain = req.body.email.substring(i + 1).toLowerCase();
1168 - for (var i in domain.newaccountemaildomains) { if (emaildomain == domain.newaccountemaildomains[i].toLowerCase()) { emailok = true; } }
1169 - if (emailok == false) {
1170 - parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)');
1171 - req.session.loginmode = '2';
1172 - req.session.messageid = 100; // Unable to create account.
1173 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1174 - return;
1175 - }
1176 - }
1177 -
1178 - // Check if we exceed the maximum number of user accounts
1179 - obj.db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) {
1180 - if (maxExceed) {
1181 - parent.debug('web', 'handleCreateAccountRequest: account limit reached');
1182 - req.session.loginmode = '2';
1183 - req.session.messageid = 101; // Account limit reached.
1184 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1185 - } else {
1186 - 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)) {
1187 - parent.debug('web', 'handleCreateAccountRequest: unable to create account (3)');
1188 - req.session.loginmode = '2';
1189 - req.session.messageid = 100; // Unable to create account.
1190 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1191 - } else {
1192 - // Check if this email was already verified
1193 - obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) {
1194 - if ((docs != null) && (docs.length > 0)) {
1195 - parent.debug('web', 'handleCreateAccountRequest: Existing account with this email address');
1196 - req.session.loginmode = '2';
1197 - req.session.messageid = 102; // Existing account with this email address.
1198 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1199 - } else {
1200 - // Check if there is domain.newAccountToken, check if supplied token is valid
1201 - if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
1202 - parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token');
1203 - req.session.loginmode = '2';
1204 - req.session.messageid = 103; // Invalid account creation token.
1205 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1206 - return;
1207 - }
1208 - // Check if user exists
1209 - if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
1210 - parent.debug('web', 'handleCreateAccountRequest: Username already exists');
1211 - req.session.loginmode = '2';
1212 - req.session.messageid = 104; // Username already exists.
1213 - } else {
1214 - 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), domain: domain.id };
1215 - if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
1216 - if (obj.common.validateStrArray(domain.newaccountrealms)) { user.groups = domain.newaccountrealms; }
1217 - 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; }
1218 - if (domainUserCount == 0) { user.siteadmin = 4294967295; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
1219 -
1220 - // Auto-join any user groups
1221 - if (typeof domain.newaccountsusergroups == 'object') {
1222 - for (var i in domain.newaccountsusergroups) {
1223 - var ugrpid = domain.newaccountsusergroups[i];
1224 - if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
1225 - var ugroup = obj.userGroups[ugrpid];
1226 - if (ugroup != null) {
1227 - // Add group to the user
1228 - if (user.links == null) { user.links = {}; }
1229 - user.links[ugroup._id] = { rights: 1 };
1230 -
1231 - // Add user to the group
1232 - ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
1233 - db.Set(ugroup);
1234 -
1235 - // Notify user group change
1236 - 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 };
1237 - 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.
1238 - parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
1239 - }
1240 - }
1241 - }
1242 -
1243 - obj.users[user._id] = user;
1244 - req.session.userid = user._id;
1245 - req.session.domainid = domain.id;
1246 - req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1247 - // Create a user, generate a salt and hash the password
1248 - require('./pass').hash(req.body.password1, function (err, salt, hash, tag) {
1249 - if (err) throw err;
1250 - user.salt = salt;
1251 - user.hash = hash;
1252 - delete user.passtype;
1253 - obj.db.SetUser(user);
1254 -
1255 - // Send the verification email
1256 - if ((obj.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (obj.common.validateEmail(user.email, 1, 256) == true)) { obj.parent.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key); }
1257 - }, 0);
1258 - 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 };
1259 - 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.
1260 - obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
1261 - }
1262 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1263 - }
1264 - });
1265 - }
1266 - }
1267 - });
1268 - }
1269 -
1270 - // Called to process an account password reset
1271 - function handleResetPasswordRequest(req, res, direct) {
1272 - const domain = checkUserIpAddress(req, res);
1273 - if (domain == null) { return; }
1274 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1275 -
1276 - // Check everything is ok
1277 - if ((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')) {
1278 - parent.debug('web', 'handleResetPasswordRequest: checks failed');
1279 - delete req.session.loginmode;
1280 - delete req.session.tokenuserid;
1281 - delete req.session.tokenusername;
1282 - delete req.session.tokenpassword;
1283 - delete req.session.resettokenuserid;
1284 - delete req.session.resettokenusername;
1285 - delete req.session.resettokenpassword;
1286 - delete req.session.tokenemail;
1287 - delete req.session.tokensms;
1288 - delete req.session.messageid;
1289 - delete req.session.passhint;
1290 - delete req.session.cuserid;
1291 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1292 - return;
1293 - }
1294 -
1295 - // Authenticate the user
1296 - obj.authenticate(req.session.resettokenusername, req.session.resettokenpassword, domain, function (err, userid, passhint) {
1297 - if (userid) {
1298 - // Login
1299 - var user = obj.users[userid];
1300 -
1301 - // If we have password requirements, check this here.
1302 - if (!obj.common.checkPasswordRequirements(req.body.rpassword1, domain.passwordrequirements)) {
1303 - parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (1)');
1304 - req.session.loginmode = '6';
1305 - req.session.messageid = 105; // Password rejected, use a different one.
1306 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1307 - return;
1308 - }
1309 -
1310 - // Check if the password is the same as a previous one
1311 - obj.checkOldUserPasswords(domain, user, req.body.rpassword1, function (result) {
1312 - if (result != 0) {
1313 - // This is the same password as an older one, request a password change again
1314 - parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (2)');
1315 - req.session.loginmode = '6';
1316 - req.session.messageid = 105; // Password rejected, use a different one.
1317 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1318 - } else {
1319 - // Update the password, use a different salt.
1320 - require('./pass').hash(req.body.rpassword1, function (err, salt, hash, tag) {
1321 - const nowSeconds = Math.floor(Date.now() / 1000);
1322 - if (err) { parent.debug('web', 'handleResetPasswordRequest: hash error.'); throw err; }
1323 -
1324 - if (domain.passwordrequirements != null) {
1325 - // Save password hint if this feature is enabled
1326 - 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; }
1327 -
1328 - // Save previous password if this feature is enabled
1329 - if ((typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
1330 - if (user.oldpasswords == null) { user.oldpasswords = []; }
1331 - user.oldpasswords.push({ salt: user.salt, hash: user.hash, start: user.passchange, end: nowSeconds });
1332 - const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
1333 - if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
1334 - }
1335 - }
1336 -
1337 - user.salt = salt;
1338 - user.hash = hash;
1339 - user.passchange = nowSeconds;
1340 - delete user.passtype;
1341 - obj.db.SetUser(user);
1342 -
1343 - // Event the account change
1344 - var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'User password reset', domain: domain.id };
1345 - 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.
1346 - obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1347 -
1348 - // Login successful
1349 - parent.debug('web', 'handleResetPasswordRequest: success');
1350 - req.session.userid = userid;
1351 - req.session.domainid = domain.id;
1352 - req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1353 - completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tokenusername, req.session.tokenpassword, direct);
1354 - }, 0);
1355 - }
1356 - }, 0);
1357 - } else {
1358 - // Failed, error out.
1359 - parent.debug('web', 'handleResetPasswordRequest: failed authenticate()');
1360 - delete req.session.loginmode;
1361 - delete req.session.tokenuserid;
1362 - delete req.session.tokenusername;
1363 - delete req.session.tokenpassword;
1364 - delete req.session.resettokenuserid;
1365 - delete req.session.resettokenusername;
1366 - delete req.session.resettokenpassword;
1367 - delete req.session.tokenemail;
1368 - delete req.session.tokensms;
1369 - delete req.session.messageid;
1370 - delete req.session.passhint;
1371 - delete req.session.cuserid;
1372 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1373 - return;
1374 - }
1375 - });
1376 - }
1377 -
1378 - // Called to process an account reset request
1379 - function handleResetAccountRequest(req, res, direct) {
1380 - const domain = checkUserIpAddress(req, res);
1381 - if (domain == null) { return; }
1382 - if ((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; }
1383 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1384 -
1385 - // Always lowercase the email address
1386 - if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1387 -
1388 - // Get the email from the body or session.
1389 - var email = req.body.email;
1390 - if ((email == null) || (email == '')) { email = req.session.tokenemail; }
1391 -
1392 - // Check the email string format
1393 - if (!email || checkEmail(email) == false) {
1394 - parent.debug('web', 'handleResetAccountRequest: Invalid email');
1395 - req.session.loginmode = '3';
1396 - req.session.messageid = 106; // Invalid email.
1397 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1398 - } else {
1399 - obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
1400 - // Remove all accounts that start with ~ since they are special accounts.
1401 - var cleanDocs = [];
1402 - if ((err == null) && (docs.length > 0)) {
1403 - for (var i in docs) {
1404 - const user = docs[i];
1405 - const locked = ((user.siteadmin != null) && (user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)); // No password recovery for locked accounts
1406 - const specialAccount = (user._id.split('/')[2].startsWith('~')); // No password recovery for special accounts
1407 - if ((specialAccount == false) && (locked == false)) { cleanDocs.push(user); }
1408 - }
1409 - }
1410 - docs = cleanDocs;
1411 -
1412 - // Check if we have any account that match this email address
1413 - if ((err != null) || (docs.length == 0)) {
1414 - parent.debug('web', 'handleResetAccountRequest: Account not found');
1415 - req.session.loginmode = '3';
1416 - 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.
1417 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1418 - } else {
1419 - // 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.
1420 - var responseSent = false;
1421 - for (var i in docs) {
1422 - var user = docs[i];
1423 - if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
1424 - // Second factor setup, request it now.
1425 - checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
1426 - if (result == false) {
1427 - if (i == 0) {
1428 - // 2-step auth is required, but the token is not present or not valid.
1429 - parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again');
1430 - if ((req.body.token != null) || (req.body.hwtoken != null)) {
1431 - var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
1432 - if ((req.body.hwtoken == '**sms**') && sms2fa) {
1433 - // Cause a token to be sent to the user's phone number
1434 - user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
1435 - obj.db.SetUser(user);
1436 - parent.debug('web', 'Sending 2FA SMS for password recovery to: ' + user.phone);
1437 - parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
1438 - req.session.messageid = 4; // SMS sent.
1439 - } else {
1440 - req.session.messageid = 108; // Invalid token, try again.
1441 - 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 });
1442 - obj.setbadLogin(req);
1443 - }
1444 - }
1445 - req.session.loginmode = '5';
1446 - delete req.session.tokenemail;
1447 - req.session.tokenemail = email;
1448 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1449 - }
1450 - } else {
1451 - // Send email to perform recovery.
1452 - delete req.session.tokenemail;
1453 - if (obj.parent.mailserver != null) {
1454 - obj.parent.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1455 - if (i == 0) {
1456 - parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
1457 - req.session.loginmode = '1';
1458 - req.session.messageid = 1; // If valid, reset mail sent.
1459 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1460 - }
1461 - } else {
1462 - if (i == 0) {
1463 - parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
1464 - req.session.loginmode = '3';
1465 - req.session.messageid = 109; // Unable to sent email.
1466 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1467 - }
1468 - }
1469 - }
1470 - });
1471 - } else {
1472 - // No second factor, send email to perform recovery.
1473 - if (obj.parent.mailserver != null) {
1474 - obj.parent.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1475 - if (i == 0) {
1476 - parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
1477 - req.session.loginmode = '1';
1478 - req.session.messageid = 1; // If valid, reset mail sent.
1479 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1480 - }
1481 - } else {
1482 - if (i == 0) {
1483 - parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
1484 - req.session.loginmode = '3';
1485 - req.session.messageid = 109; // Unable to sent email.
1486 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1487 - }
1488 - }
1489 - }
1490 - }
1491 - }
1492 - });
1493 - }
1494 - }
1495 -
1496 - // Handle account email change and email verification request
1497 - function handleCheckAccountEmailRequest(req, res, direct) {
1498 - const domain = checkUserIpAddress(req, res);
1499 - if (domain == null) { return; }
1500 - if ((obj.parent.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; }
1501 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1502 -
1503 - // Always lowercase the email address
1504 - if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
1505 -
1506 - // Get the email from the body or session.
1507 - var email = req.body.email;
1508 - if ((email == null) || (email == '')) { email = req.session.tokenemail; }
1509 -
1510 - // Check if this request is for an allows email domain
1511 - if ((domain.newaccountemaildomains != null) && Array.isArray(domain.newaccountemaildomains)) {
1512 - var i = -1;
1513 - if (typeof req.body.email == 'string') { i = req.body.email.indexOf('@'); }
1514 - if (i == -1) {
1515 - parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
1516 - req.session.loginmode = '7';
1517 - req.session.messageid = 106; // Invalid email.
1518 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1519 - return;
1520 - }
1521 - var emailok = false, emaildomain = req.body.email.substring(i + 1).toLowerCase();
1522 - for (var i in domain.newaccountemaildomains) { if (emaildomain == domain.newaccountemaildomains[i].toLowerCase()) { emailok = true; } }
1523 - if (emailok == false) {
1524 - parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)');
1525 - req.session.loginmode = '7';
1526 - req.session.messageid = 106; // Invalid email.
1527 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1528 - return;
1529 - }
1530 - }
1531 -
1532 - // Check the email string format
1533 - if (!email || checkEmail(email) == false) {
1534 - parent.debug('web', 'handleCheckAccountEmailRequest: Invalid email');
1535 - req.session.loginmode = '7';
1536 - req.session.messageid = 106; // Invalid email.
1537 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1538 - } else {
1539 - // Check is email already exists
1540 - obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
1541 - if ((err != null) || (docs.length > 0)) {
1542 - // Email already exitst
1543 - req.session.messageid = 102; // Existing account with this email address.
1544 - } else {
1545 - // Update the user and notify of user email address change
1546 - var user = obj.users[req.session.cuserid];
1547 - if (user.email != email) {
1548 - user.email = email;
1549 - db.SetUser(user);
1550 - var targets = ['*', 'server-users', user._id];
1551 - if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1552 - var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Account changed: ' + user.name, domain: domain.id };
1553 - 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.
1554 - parent.DispatchEvent(targets, obj, event);
1555 - }
1556 -
1557 - // Send the verification email
1558 - obj.parent.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key);
1559 -
1560 - // Send the response
1561 - req.session.messageid = 2; // Email sent.
1562 - }
1563 - req.session.loginmode = '7';
1564 - delete req.session.cuserid;
1565 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1566 - });
1567 - }
1568 - }
1569 -
1570 - // Called to process a web based email verification request
1571 - function handleCheckMailRequest(req, res) {
1572 - const domain = checkUserIpAddress(req, res);
1573 - if (domain == null) { return; }
1574 - if ((domain.auth == 'sspi') || (domain.auth == 'ldap') || (obj.parent.mailserver == null)) { parent.debug('web', 'handleCheckMailRequest: failed checks.'); res.sendStatus(404); return; }
1575 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1576 -
1577 - if (req.query.c != null) {
1578 - var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.mailserver.mailCookieEncryptionKey, 30);
1579 - if ((cookie != null) && (cookie.u != null) && (cookie.u.startsWith('user/')) && (cookie.e != null)) {
1580 - var idsplit = cookie.u.split('/');
1581 - if ((idsplit.length != 3) || (idsplit[1] != domain.id)) {
1582 - parent.debug('web', 'handleCheckMailRequest: Invalid domain.');
1583 - 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));
1584 - } else {
1585 - obj.db.Get(cookie.u, function (err, docs) {
1586 - if (docs.length == 0) {
1587 - parent.debug('web', 'handleCheckMailRequest: Invalid username.');
1588 - 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));
1589 - } else {
1590 - var user = docs[0];
1591 - if (user.email != cookie.e) {
1592 - parent.debug('web', 'handleCheckMailRequest: Invalid e-mail.');
1593 - 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));
1594 - } else {
1595 - if (cookie.a == 1) {
1596 - // Account email verification
1597 - if (user.emailVerified == true) {
1598 - parent.debug('web', 'handleCheckMailRequest: email already verified.');
1599 - 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));
1600 - } else {
1601 - obj.db.GetUserWithVerifiedEmail(domain.id, user.email, function (err, docs) {
1602 - if (docs.length > 0) {
1603 - parent.debug('web', 'handleCheckMailRequest: email already in use.');
1604 - 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));
1605 - } else {
1606 - parent.debug('web', 'handleCheckMailRequest: email verification success.');
1607 -
1608 - // Set the verified flag
1609 - obj.users[user._id].emailVerified = true;
1610 - user.emailVerified = true;
1611 - obj.db.SetUser(user);
1612 -
1613 - // Event the change
1614 - 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 };
1615 - 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.
1616 - obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1617 -
1618 - // Send the confirmation page
1619 - 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));
1620 -
1621 - // Send a notification
1622 - obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: 'Email verified', value: user.email, nolog: 1, id: Math.random() });
1623 -
1624 - // Send to authlog
1625 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Verified email address ' + user.email + ' for user ' + user.name); }
1626 - }
1627 - });
1628 - }
1629 - } else if (cookie.a == 2) {
1630 - // Account reset
1631 - if (user.emailVerified != true) {
1632 - parent.debug('web', 'handleCheckMailRequest: email not verified.');
1633 - 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));
1634 - } else {
1635 - if (req.query.confirm == 1) {
1636 - // Set a temporary password
1637 - obj.crypto.randomBytes(16, function (err, buf) {
1638 - var newpass = buf.toString('base64').split('=').join('').split('/').join('').split('+').join('');
1639 - require('./pass').hash(newpass, function (err, salt, hash, tag) {
1640 - if (err) throw err;
1641 -
1642 - // Change the password
1643 - var userinfo = obj.users[user._id];
1644 - userinfo.salt = salt;
1645 - userinfo.hash = hash;
1646 - delete userinfo.passtype;
1647 - userinfo.passchange = Math.floor(Date.now() / 1000);
1648 - delete userinfo.passhint;
1649 - obj.db.SetUser(userinfo);
1650 -
1651 - // Event the change
1652 - 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 };
1653 - 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.
1654 - obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1655 -
1656 - // Send the new password
1657 - 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));
1658 - parent.debug('web', 'handleCheckMailRequest: send temporary password.');
1659 -
1660 - // Send to authlog
1661 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Performed account reset for user ' + user.name); }
1662 - }, 0);
1663 - });
1664 - } else {
1665 - // Display a link for the user to confirm password reset
1666 - // 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.
1667 - 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));
1668 - }
1669 - }
1670 - } else {
1671 - 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));
1672 - }
1673 - }
1674 - }
1675 - });
1676 - }
1677 - } else {
1678 - 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));
1679 - }
1680 - }
1681 - }
1682 -
1683 - // Called to process an agent invite GET/POST request
1684 - function handleInviteRequest(req, res) {
1685 - const domain = getDomain(req);
1686 - if (domain == null) { parent.debug('web', 'handleInviteRequest: failed checks.'); res.sendStatus(404); return; }
1687 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1688 - if ((req.body.inviteCode == null) || (req.body.inviteCode == '')) { render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 0 }, req, domain)); return; } // No invitation code
1689 -
1690 - // Each for a device group that has this invite code.
1691 - for (var i in obj.meshes) {
1692 - if ((obj.meshes[i].domain == domain.id) && (obj.meshes[i].invite != null) && (obj.meshes[i].invite.codes.indexOf(req.body.inviteCode) >= 0)) {
1693 - // Send invitation link, valid for 1 minute.
1694 - res.redirect(domain.url + 'agentinvite?c=' + parent.encodeCookie({ a: 4, mid: i, f: obj.meshes[i].invite.flags, expire: 1 }, parent.invitationLinkEncryptionKey) + (req.query.key ? ('&key=' + req.query.key) : ''));
1695 - return;
1696 - }
1697 - }
1698 -
1699 - render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 100 }, req, domain)); // Bad invitation code
1700 - }
1701 -
1702 - // Called to render the MSTSC (RDP) web page
1703 - function handleMSTSCRequest(req, res) {
1704 - const domain = getDomain(req);
1705 - if (domain == null) { parent.debug('web', 'handleMSTSCRequest: failed checks.'); res.sendStatus(404); return; }
1706 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1707 -
1708 - // Check if we are in maintenance mode
1709 - if ((parent.config.settings.maintenancemode != null) && (req.query.admin !== '1')) {
1710 - 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));
1711 - return;
1712 - }
1713 -
1714 - if (req.query.ws != null) {
1715 - // This is a query with a websocket relay cookie, check that the cookie is valid and use it.
1716 - var rcookie = parent.decodeCookie(req.query.ws, parent.loginCookieEncryptionKey, 60); // Cookie with 1 hour timeout
1717 - if ((rcookie != null) && (rcookie.domainid == domain.id) && (rcookie.nodeid != null) && (rcookie.tcpport != null)) {
1718 - render(req, res, getRenderPage('mstsc', req, domain), getRenderArgs({ cookie: req.query.ws, name: encodeURIComponent(req.query.name).replace(/'/g, '%27') }, req, domain)); return;
1719 - }
1720 - }
1721 -
1722 - // Get the logged in user if present
1723 - var user = null;
1724 -
1725 - // If there is a login token, use that
1726 - if (req.query.login != null) {
1727 - var ucookie = parent.decodeCookie(req.query.login, parent.loginCookieEncryptionKey, 60); // Cookie with 1 hour timeout
1728 - if ((ucookie != null) && (ucookie.a === 3) && (typeof ucookie.u == 'string')) { user = obj.users[ucookie.u]; }
1729 - }
1730 -
1731 - // If no token, see if we have an active session
1732 - if ((user == null) && (req.session.userid != null)) { user = obj.users[req.session.userid]; }
1733 -
1734 - // If still no user, see if we have a default user
1735 - if ((user == null) && (obj.args.user)) { user = obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]; }
1736 -
1737 - // No user login, exit now
1738 - if (user == null) { res.sendStatus(401); return; }
1739 -
1740 - // Check the nodeid
1741 - if (req.query.node != null) {
1742 - var nodeidsplit = req.query.node.split('/');
1743 - if (nodeidsplit.length == 1) {
1744 - req.query.node = 'node/' + domain.id + '/' + nodeidsplit[0]; // Format the nodeid correctly
1745 - } else if (nodeidsplit.length == 3) {
1746 - if ((nodeidsplit[0] != 'node') || (nodeidsplit[1] != domain.id)) { req.query.node = null; } // Check the nodeid format
1747 - } else {
1748 - req.query.node = null; // Bad nodeid
1749 - }
1750 - }
1751 -
1752 - // If there is no nodeid, exit now
1753 - if (req.query.node == null) { render(req, res, getRenderPage('mstsc', req, domain), getRenderArgs({ cookie: '', name: '' }, req, domain)); return; }
1754 -
1755 - // Fetch the node from the database
1756 - obj.db.Get(req.query.node, function (err, nodes) {
1757 - if ((err != null) || (nodes.length != 1)) { res.sendStatus(404); return; }
1758 - const node = nodes[0];
1759 -
1760 - // Check access rights, must have remote control rights
1761 - if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { res.sendStatus(401); return; }
1762 -
1763 - // Figure out the target port
1764 - var port = 3389;
1765 - if (typeof node.rdpport == 'number') { port = node.rdpport; }
1766 - 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; } }
1767 -
1768 - // Generate a cookie and respond
1769 - var cookie = parent.encodeCookie({ userid: user._id, domainid: user.domain, nodeid: node._id, tcpport: port }, parent.loginCookieEncryptionKey);
1770 - render(req, res, getRenderPage('mstsc', req, domain), getRenderArgs({ cookie: cookie, name: encodeURIComponent(node.name).replace(/'/g, '%27') }, req, domain));
1771 - });
1772 - }
1773 -
1774 - // Called to process an agent invite request
1775 - function handleAgentInviteRequest(req, res) {
1776 - const domain = getDomain(req);
1777 - if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) { parent.debug('web', 'handleAgentInviteRequest: failed checks.'); res.sendStatus(404); return; }
1778 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1779 -
1780 - if (req.query.c != null) {
1781 - // A cookie is specified in the query string, use that
1782 - var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey);
1783 - if (cookie == null) { res.sendStatus(404); return; }
1784 - var mesh = obj.meshes[cookie.mid];
1785 - if (mesh == null) { res.sendStatus(404); return; }
1786 - var installflags = cookie.f;
1787 - if (typeof installflags != 'number') { installflags = 0; }
1788 - parent.debug('web', 'handleAgentInviteRequest using cookie.');
1789 - var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
1790 - 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 }, req, domain));
1791 - } else if (req.query.m != null) {
1792 - // The MeshId is specified in the query string, use that
1793 - var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.m.toLowerCase()];
1794 - if (mesh == null) { res.sendStatus(404); return; }
1795 - var installflags = 0;
1796 - if (req.query.f) { installflags = parseInt(req.query.f); }
1797 - if (typeof installflags != 'number') { installflags = 0; }
1798 - parent.debug('web', 'handleAgentInviteRequest using meshid.');
1799 - var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
1800 - 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 }, req, domain));
1801 - }
1802 - }
1803 -
1804 - function handleDeleteAccountRequest(req, res, direct) {
1805 - parent.debug('web', 'handleDeleteAccountRequest()');
1806 - const domain = checkUserIpAddress(req, res);
1807 - if (domain == null) { return; }
1808 - if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleDeleteAccountRequest: failed checks.'); res.sendStatus(404); return; }
1809 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1810 -
1811 - var user = null;
1812 - if (req.body.authcookie) {
1813 - // If a authentication cookie is provided, decode it here
1814 - var loginCookie = obj.parent.decodeCookie(req.body.authcookie, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
1815 - if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { user = obj.users[loginCookie.userid]; }
1816 - } else {
1817 - // Check if the user is logged and we have all required parameters
1818 - if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) {
1819 - parent.debug('web', 'handleDeleteAccountRequest: required parameters not present.');
1820 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1821 - return;
1822 - } else {
1823 - user = obj.users[req.session.userid];
1824 - }
1825 - }
1826 - if (!user) { parent.debug('web', 'handleDeleteAccountRequest: user not found.'); res.sendStatus(404); return; }
1827 - if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) { parent.debug('web', 'handleDeleteAccountRequest: account settings locked.'); res.sendStatus(404); return; }
1828 -
1829 - // Check if the password is correct
1830 - obj.authenticate(user._id.split('/')[2], req.body.apassword1, domain, function (err, userid) {
1831 - var deluser = obj.users[userid];
1832 - if ((userid != null) && (deluser != null)) {
1833 - // Remove all links to this user
1834 - if (deluser.links != null) {
1835 - for (var i in deluser.links) {
1836 - if (i.startsWith('mesh/')) {
1837 - // Get the device group
1838 - var mesh = obj.meshes[i];
1839 - if (mesh) {
1840 - // Remove user from the mesh
1841 - if (mesh.links[deluser._id] != null) { delete mesh.links[deluser._id]; parent.db.Set(mesh); }
1842 -
1843 - // Notify mesh change
1844 - var change = 'Removed user ' + deluser.name + ' from group ' + mesh.name;
1845 - 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 };
1846 - 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.
1847 - parent.DispatchEvent(['*', mesh._id, deluser._id, user._id], obj, event);
1848 - }
1849 - } else if (i.startsWith('node/')) {
1850 - // Get the node and the rights for this node
1851 - obj.GetNodeWithRights(domain, deluser, i, function (node, rights, visible) {
1852 - if ((node == null) || (node.links == null) || (node.links[deluser._id] == null)) return;
1853 -
1854 - // Remove the link and save the node to the database
1855 - delete node.links[deluser._id];
1856 - if (Object.keys(node.links).length == 0) { delete node.links; }
1857 - db.Set(obj.cleanDevice(node));
1858 -
1859 - // Event the node change
1860 - 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) }
1861 - 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.
1862 - parent.DispatchEvent(['*', node.meshid, node._id], obj, event);
1863 - });
1864 - } else if (i.startsWith('ugrp/')) {
1865 - // Get the device group
1866 - var ugroup = obj.userGroups[i];
1867 - if (ugroup) {
1868 - // Remove user from the user group
1869 - if (ugroup.links[deluser._id] != null) { delete ugroup.links[deluser._id]; parent.db.Set(ugroup); }
1870 -
1871 - // Notify user group change
1872 - var change = 'Removed user ' + deluser.name + ' from user group ' + ugroup.name;
1873 - 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 };
1874 - 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.
1875 - parent.DispatchEvent(['*', ugroup._id, user._id, deluser._id], obj, event);
1876 - }
1877 - }
1878 - }
1879 - }
1880 -
1881 - // Remove notes for this user
1882 - obj.db.Remove('nt' + deluser._id);
1883 -
1884 - // Remove the user
1885 - obj.db.Remove(deluser._id);
1886 - delete obj.users[deluser._id];
1887 - req.session = null;
1888 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1889 - obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluser._id, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id });
1890 - parent.debug('web', 'handleDeleteAccountRequest: removed user.');
1891 - } else {
1892 - parent.debug('web', 'handleDeleteAccountRequest: auth failed.');
1893 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1894 - }
1895 - });
1896 - }
1897 -
1898 - // Check a user's password
1899 - obj.checkUserPassword = function (domain, user, password, func) {
1900 - // Check the old password
1901 - if (user.passtype != null) {
1902 - // IIS default clear or weak password hashing (SHA-1)
1903 - require('./pass').iishash(user.passtype, password, user.salt, function (err, hash) {
1904 - if (err) { parent.debug('web', 'checkUserPassword: SHA-1 fail.'); return func(false); }
1905 - if (hash == user.hash) {
1906 - if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: SHA-1 locked.'); return func(false); } // Account is locked
1907 - parent.debug('web', 'checkUserPassword: SHA-1 ok.');
1908 - return func(true); // Allow password change
1909 - }
1910 - func(false);
1911 - });
1912 - } else {
1913 - // Default strong password hashing (pbkdf2 SHA384)
1914 - require('./pass').hash(password, user.salt, function (err, hash, tag) {
1915 - if (err) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 fail.'); return func(false); }
1916 - if (hash == user.hash) {
1917 - if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 locked.'); return func(false); } // Account is locked
1918 - parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 ok.');
1919 - return func(true); // Allow password change
1920 - }
1921 - func(false);
1922 - }, 0);
1923 - }
1924 - }
1925 -
1926 - // Check a user's old passwords
1927 - // Callback: 0=OK, 1=OldPass, 2=CommonPass
1928 - obj.checkOldUserPasswords = function (domain, user, password, func) {
1929 - // Check how many old passwords we need to check
1930 - if ((domain.passwordrequirements != null) && (typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
1931 - if (user.oldpasswords != null) {
1932 - const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
1933 - if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
1934 - }
1935 - } else {
1936 - delete user.oldpasswords;
1937 - }
1938 -
1939 - // If there is no old passwords, exit now.
1940 - var oldPassCount = 1;
1941 - if (user.oldpasswords != null) { oldPassCount += user.oldpasswords.length; }
1942 - var oldPassCheckState = { response: 0, count: oldPassCount, user: user, func: func };
1943 -
1944 - // Test against common passwords if this feature is enabled
1945 - // Example of common passwords: 123456789, password123
1946 - if ((domain.passwordrequirements != null) && (domain.passwordrequirements.bancommonpasswords == true)) {
1947 - oldPassCheckState.count++;
1948 - require('wildleek')(password).then(function (wild) {
1949 - if (wild == true) { oldPassCheckState.response = 2; }
1950 - if (--oldPassCheckState.count == 0) { oldPassCheckState.func(oldPassCheckState.response); }
1951 - });
1952 - }
1953 -
1954 - // Try current password
1955 - require('./pass').hash(password, user.salt, function oldPassCheck(err, hash, tag) {
1956 - if ((err == null) && (hash == tag.user.hash)) { tag.response = 1; }
1957 - if (--tag.count == 0) { tag.func(tag.response); }
1958 - }, oldPassCheckState);
1959 -
1960 - // Try each old password
1961 - if (user.oldpasswords != null) {
1962 - for (var i in user.oldpasswords) {
1963 - const oldpassword = user.oldpasswords[i];
1964 - // Default strong password hashing (pbkdf2 SHA384)
1965 - require('./pass').hash(password, oldpassword.salt, function oldPassCheck(err, hash, tag) {
1966 - if ((err == null) && (hash == tag.oldPassword.hash)) { tag.state.response = 1; }
1967 - if (--tag.state.count == 0) { tag.state.func(tag.state.response); }
1968 - }, { oldPassword: oldpassword, state: oldPassCheckState });
1969 - }
1970 - }
1971 - }
1972 -
1973 - // Handle password changes
1974 - function handlePasswordChangeRequest(req, res, direct) {
1975 - const domain = checkUserIpAddress(req, res);
1976 - if (domain == null) { return; }
1977 - if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).'); res.sendStatus(404); return; }
1978 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
1979 -
1980 - // Check if the user is logged and we have all required parameters
1981 - if (!req.session || !req.session.userid || !req.body.apassword0 || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) {
1982 - parent.debug('web', 'handlePasswordChangeRequest: failed checks (2).');
1983 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1984 - return;
1985 - }
1986 -
1987 - // Get the current user
1988 - var user = obj.users[req.session.userid];
1989 - if (!user) {
1990 - parent.debug('web', 'handlePasswordChangeRequest: user not found.');
1991 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1992 - return;
1993 - }
1994 -
1995 - // Check account settings locked
1996 - if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) {
1997 - parent.debug('web', 'handlePasswordChangeRequest: account settings locked.');
1998 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1999 - return;
2000 - }
2001 -
2002 - // Check old password
2003 - obj.checkUserPassword(domain, user, req.body.apassword1, function (result) {
2004 - if (result == true) {
2005 - // Check if the new password is allowed, only do this if this feature is enabled.
2006 - parent.checkOldUserPasswords(domain, user, command.newpass, function (result) {
2007 - if (result == 1) {
2008 - parent.debug('web', 'handlePasswordChangeRequest: old password reuse attempt.');
2009 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2010 - } else if (result == 2) {
2011 - parent.debug('web', 'handlePasswordChangeRequest: commonly used password use attempt.');
2012 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2013 - } else {
2014 - // Update the password
2015 - require('./pass').hash(req.body.apassword1, function (err, salt, hash, tag) {
2016 - const nowSeconds = Math.floor(Date.now() / 1000);
2017 - if (err) { parent.debug('web', 'handlePasswordChangeRequest: hash error.'); throw err; }
2018 - if (domain.passwordrequirements != null) {
2019 - // Save password hint if this feature is enabled
2020 - 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; }
2021 -
2022 - // Save previous password if this feature is enabled
2023 - if ((typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
2024 - if (user.oldpasswords == null) { user.oldpasswords = []; }
2025 - user.oldpasswords.push({ salt: user.salt, hash: user.hash, start: user.passchange, end: nowSeconds });
2026 - const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
2027 - if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
2028 - }
2029 - }
2030 - user.salt = salt;
2031 - user.hash = hash;
2032 - user.passchange = nowSeconds;
2033 - delete user.passtype;
2034 -
2035 - obj.db.SetUser(user);
2036 - req.session.viewmode = 2;
2037 - if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
2038 - 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 });
2039 - }, 0);
2040 - }
2041 - });
2042 - }
2043 - });
2044 - }
2045 -
2046 - // Called when a strategy login occured
2047 - // This is called after a succesful Oauth to Twitter, Google, GitHub...
2048 - function handleStrategyLogin(req, res) {
2049 - const domain = checkUserIpAddress(req, res);
2050 - if (domain == null) { return; }
2051 - parent.debug('web', 'handleStrategyLogin: ' + JSON.stringify(req.user));
2052 - if ((req.user != null) && (req.user.sid != null)) {
2053 - const userid = 'user/' + domain.id + '/' + req.user.sid;
2054 - var user = obj.users[userid];
2055 - if (user == null) {
2056 - var newAccountAllowed = false;
2057 - var newAccountRealms = null;
2058 -
2059 - if (domain.newaccounts === true) { newAccountAllowed = true; }
2060 - if (obj.common.validateStrArray(domain.newaccountrealms)) { newAccountRealms = domain.newaccountrealms; }
2061 -
2062 - if ((domain.authstrategies != null) && (domain.authstrategies[req.user.strategy] != null)) {
2063 - if (domain.authstrategies[req.user.strategy].newaccounts === true) { newAccountAllowed = true; }
2064 - if (obj.common.validateStrArray(domain.authstrategies[req.user.strategy].newaccountrealms)) { newAccountRealms = domain.authstrategies[req.user.strategy].newaccountrealms; }
2065 - }
2066 -
2067 - if (newAccountAllowed === true) {
2068 - // Create the user
2069 - parent.debug('web', 'handleStrategyLogin: creating new user: ' + userid);
2070 - user = { type: 'user', _id: userid, name: req.user.name, email: req.user.email, creation: Math.floor(Date.now() / 1000), domain: domain.id };
2071 - if (req.user.email != null) { user.email = req.user.email; user.emailVerified = true; }
2072 - if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; } // New accounts automatically assigned server rights.
2073 - 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.
2074 - if (newAccountRealms) { user.groups = newAccountRealms; } // New accounts automatically part of some groups (Realms).
2075 - obj.users[userid] = user;
2076 -
2077 - // Auto-join any user groups
2078 - var newaccountsusergroups = null;
2079 - if (typeof domain.newaccountsusergroups == 'object') { newaccountsusergroups = domain.newaccountsusergroups; }
2080 - if (typeof domain.authstrategies[req.user.strategy].newaccountsusergroups == 'object') { newaccountsusergroups = domain.authstrategies[req.user.strategy].newaccountsusergroups; }
2081 - if (newaccountsusergroups) {
2082 - for (var i in newaccountsusergroups) {
2083 - var ugrpid = newaccountsusergroups[i];
2084 - if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
2085 - var ugroup = obj.userGroups[ugrpid];
2086 - if (ugroup != null) {
2087 - // Add group to the user
2088 - if (user.links == null) { user.links = {}; }
2089 - user.links[ugroup._id] = { rights: 1 };
2090 -
2091 - // Add user to the group
2092 - ugroup.links[user._id] = { userid: user._id, name: user.name, rights: 1 };
2093 - db.Set(ugroup);
2094 -
2095 - // Notify user group change
2096 - 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 };
2097 - 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.
2098 - parent.DispatchEvent(['*', ugroup._id, user._id], obj, event);
2099 - }
2100 - }
2101 - }
2102 -
2103 - // Save the user
2104 - obj.db.SetUser(user);
2105 -
2106 - // Event user creation
2107 - var targets = ['*', 'server-users'];
2108 - 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 };
2109 - 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.
2110 - parent.DispatchEvent(targets, obj, event);
2111 -
2112 - req.session.userid = userid;
2113 - req.session.domainid = domain.id;
2114 - } else {
2115 - // New users not allowed
2116 - parent.debug('web', 'handleStrategyLogin: Can\'t create new accounts');
2117 - req.session.loginmode = '1';
2118 - req.session.messageid = 100; // Unable to create account.
2119 - res.redirect(domain.url + getQueryPortion(req));
2120 - return;
2121 - }
2122 - } else {
2123 - // Login success
2124 - var userChange = false;
2125 - if ((req.user.name != null) && (req.user.name != user.name)) { user.name = req.user.name; userChange = true; }
2126 - if ((req.user.email != null) && (req.user.email != user.email)) { user.email = req.user.email; user.emailVerified = true; userChange = true; }
2127 - if (userChange) {
2128 - obj.db.SetUser(user);
2129 -
2130 - // Event user creation
2131 - var targets = ['*', 'server-users'];
2132 - var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Account changed', domain: domain.id };
2133 - 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.
2134 - parent.DispatchEvent(targets, obj, event);
2135 - }
2136 - parent.debug('web', 'handleStrategyLogin: succesful login: ' + userid);
2137 - req.session.userid = userid;
2138 - req.session.domainid = domain.id;
2139 - }
2140 - }
2141 - //res.redirect(domain.url); // This does not handle cookie correctly.
2142 - res.set('Content-Type', 'text/html');
2143 - res.end('<html><head><meta http-equiv="refresh" content=0;url="' + domain.url + '"></head><body></body></html>');
2144 - }
2145 -
2146 - // Indicates that any request to "/" should render "default" or "login" depending on login state
2147 - function handleRootRequest(req, res, direct) {
2148 - const domain = checkUserIpAddress(req, res);
2149 - if (domain == null) { return; }
2150 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2151 - if (!obj.args) { parent.debug('web', 'handleRootRequest: no obj.args.'); res.sendStatus(500); return; }
2152 -
2153 - // Check if we are in maintenance mode
2154 - if ((parent.config.settings.maintenancemode != null) && (req.query.admin !== '1')) {
2155 - parent.debug('web', 'handleLoginRequest: Server under maintenance.');
2156 - 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));
2157 - return;
2158 - }
2159 -
2160 - if ((domain.sspi != null) && ((req.query.login == null) || (obj.parent.loginCookieEncryptionKey == null))) {
2161 - // Login using SSPI
2162 - domain.sspi.authenticate(req, res, function (err) {
2163 - if ((err != null) || (req.connection.user == null)) {
2164 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2165 - parent.debug('web', 'handleRootRequest: SSPI auth required.');
2166 - res.end('Authentication Required...');
2167 - } else {
2168 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2169 - parent.debug('web', 'handleRootRequest: SSPI auth ok.');
2170 - handleRootRequestEx(req, res, domain, direct);
2171 - }
2172 - });
2173 - } else if (req.query.user && req.query.pass) {
2174 - // User credentials are being passed in the URL. WARNING: Putting credentials in a URL is bad security... but people are requesting this option.
2175 - obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) {
2176 - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
2177 - parent.debug('web', 'handleRootRequest: user/pass in URL auth ok.');
2178 - req.session.userid = userid;
2179 - req.session.domainid = domain.id;
2180 - req.session.currentNode = '';
2181 - req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2182 - handleRootRequestEx(req, res, domain, direct);
2183 - });
2184 - } else {
2185 - // Login using a different system
2186 - handleRootRequestEx(req, res, domain, direct);
2187 - }
2188 - }
2189 -
2190 - function handleRootRequestEx(req, res, domain, direct) {
2191 - var nologout = false, user = null, features = 0, features2 = 0;
2192 - res.set({ 'Cache-Control': 'no-store' });
2193 -
2194 - // Check if we have an incomplete domain name in the path
2195 - if ((domain.id != '') && (domain.dns == null) && (req.url.split('/').length == 2)) {
2196 - parent.debug('web', 'handleRootRequestEx: incomplete domain name in the path.');
2197 - res.redirect(domain.url + getQueryPortion(req)); // BAD***
2198 - return;
2199 - }
2200 -
2201 - if (obj.args.nousers == true) {
2202 - // If in single user mode, setup things here.
2203 - if (req.session && req.session.loginmode) { delete req.session.loginmode; }
2204 - req.session.userid = 'user/' + domain.id + '/~';
2205 - req.session.domainid = domain.id;
2206 - req.session.currentNode = '';
2207 - req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2208 - if (obj.users[req.session.userid] == null) {
2209 - // Create the dummy user ~ with impossible password
2210 - parent.debug('web', 'handleRootRequestEx: created dummy user in nouser mode.');
2211 - obj.users[req.session.userid] = { type: 'user', _id: req.session.userid, name: '~', email: '~', domain: domain.id, siteadmin: 4294967295 };
2212 - obj.db.SetUser(obj.users[req.session.userid]);
2213 - }
2214 - } else if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
2215 - // If a default user is active, setup the session here.
2216 - parent.debug('web', 'handleRootRequestEx: auth using default user.');
2217 - if (req.session && req.session.loginmode) { delete req.session.loginmode; }
2218 - req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
2219 - req.session.domainid = domain.id;
2220 - req.session.currentNode = '';
2221 - req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2222 - } else if (req.query.login && (obj.parent.loginCookieEncryptionKey != null)) {
2223 - var loginCookie = obj.parent.decodeCookie(req.query.login, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
2224 - //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.
2225 - if ((loginCookie != null) && (loginCookie.a == 3) && (loginCookie.u != null) && (loginCookie.u.split('/')[1] == domain.id)) {
2226 - // If a login cookie was provided, setup the session here.
2227 - parent.debug('web', 'handleRootRequestEx: cookie auth ok.');
2228 - if (req.session && req.session.loginmode) { delete req.session.loginmode; }
2229 - req.session.userid = loginCookie.u;
2230 - req.session.domainid = domain.id;
2231 - req.session.currentNode = '';
2232 - req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2233 - } else {
2234 - parent.debug('web', 'handleRootRequestEx: cookie auth failed.');
2235 - }
2236 - } else if (domain.sspi != null) {
2237 - // SSPI login (Windows only)
2238 - //console.log(req.connection.user, req.connection.userSid);
2239 - if ((req.connection.user == null) || (req.connection.userSid == null)) {
2240 - parent.debug('web', 'handleRootRequestEx: SSPI no user auth.');
2241 - res.sendStatus(404); return;
2242 - } else {
2243 - nologout = true;
2244 - req.session.userid = 'user/' + domain.id + '/' + req.connection.user.toLowerCase();
2245 - req.session.usersid = req.connection.userSid;
2246 - req.session.usersGroups = req.connection.userGroups;
2247 - req.session.domainid = domain.id;
2248 - req.session.currentNode = '';
2249 - req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2250 -
2251 - // Check if this user exists, create it if not.
2252 - user = obj.users[req.session.userid];
2253 - if ((user == null) || (user.sid != req.session.usersid)) {
2254 - // Create the domain user
2255 - 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) };
2256 - if (domain.newaccountsrights) { user2.siteadmin = domain.newaccountsrights; }
2257 - if (obj.common.validateStrArray(domain.newaccountrealms)) { user2.groups = domain.newaccountrealms; }
2258 - for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
2259 - if (usercount == 0) { user2.siteadmin = 4294967295; } // If this is the first user, give the account site admin.
2260 -
2261 - // Auto-join any user groups
2262 - if (typeof domain.newaccountsusergroups == 'object') {
2263 - for (var i in domain.newaccountsusergroups) {
2264 - var ugrpid = domain.newaccountsusergroups[i];
2265 - if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + domain.id + '/' + ugrpid; }
2266 - var ugroup = obj.userGroups[ugrpid];
2267 - if (ugroup != null) {
2268 - // Add group to the user
2269 - if (user2.links == null) { user2.links = {}; }
2270 - user2.links[ugroup._id] = { rights: 1 };
2271 -
2272 - // Add user to the group
2273 - ugroup.links[user2._id] = { userid: user2._id, name: user2.name, rights: 1 };
2274 - db.Set(ugroup);
2275 -
2276 - // Notify user group change
2277 - 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 };
2278 - 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.
2279 - parent.DispatchEvent(['*', ugroup._id, user2._id], obj, event);
2280 - }
2281 - }
2282 - }
2283 -
2284 - obj.users[req.session.userid] = user2;
2285 - obj.db.SetUser(user2);
2286 - 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 };
2287 - 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.
2288 - obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
2289 - parent.debug('web', 'handleRootRequestEx: SSPI new domain user.');
2290 - }
2291 - }
2292 - }
2293 -
2294 - // Figure out the minimal password requirement
2295 - var passRequirements = null;
2296 - if (domain.passwordrequirements != null) {
2297 - if (domain.passrequirementstr == null) {
2298 - var passRequirements = {};
2299 - if (typeof domain.passwordrequirements.min == 'number') { passRequirements.min = domain.passwordrequirements.min; }
2300 - if (typeof domain.passwordrequirements.max == 'number') { passRequirements.max = domain.passwordrequirements.max; }
2301 - if (typeof domain.passwordrequirements.upper == 'number') { passRequirements.upper = domain.passwordrequirements.upper; }
2302 - if (typeof domain.passwordrequirements.lower == 'number') { passRequirements.lower = domain.passwordrequirements.lower; }
2303 - if (typeof domain.passwordrequirements.numeric == 'number') { passRequirements.numeric = domain.passwordrequirements.numeric; }
2304 - if (typeof domain.passwordrequirements.nonalpha == 'number') { passRequirements.nonalpha = domain.passwordrequirements.nonalpha; }
2305 - domain.passwordrequirementsstr = encodeURIComponent(JSON.stringify(passRequirements));
2306 - }
2307 - passRequirements = domain.passwordrequirementsstr;
2308 - }
2309 -
2310 - // If a user exists and is logged in, serve the default app, otherwise server the login app.
2311 - if (req.session && req.session.userid && obj.users[req.session.userid]) {
2312 - var user = obj.users[req.session.userid];
2313 - if (req.session.domainid != domain.id) { // Check if the session is for the correct domain
2314 - parent.debug('web', 'handleRootRequestEx: incorrect domain.');
2315 - req.session = null;
2316 - res.redirect(domain.url + getQueryPortion(req)); // BAD***
2317 - return;
2318 - }
2319 -
2320 - // Check if this is a locked account
2321 - if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) {
2322 - // Locked account
2323 - parent.debug('web', 'handleRootRequestEx: locked account.');
2324 - delete req.session.userid;
2325 - delete req.session.domainid;
2326 - delete req.session.currentNode;
2327 - delete req.session.passhint;
2328 - delete req.session.cuserid;
2329 - req.session.messageid = 110; // Account locked.
2330 - res.redirect(domain.url + getQueryPortion(req)); // BAD***
2331 - return;
2332 - }
2333 -
2334 - var viewmode = 1;
2335 - if (req.session.viewmode) {
2336 - viewmode = req.session.viewmode;
2337 - delete req.session.viewmode;
2338 - } else if (req.query.viewmode) {
2339 - viewmode = req.query.viewmode;
2340 - }
2341 - var currentNode = '';
2342 - if (req.session.currentNode) {
2343 - currentNode = req.session.currentNode;
2344 - delete req.session.currentNode;
2345 - } else if (req.query.node) {
2346 - currentNode = 'node/' + domain.id + '/' + req.query.node;
2347 - }
2348 - var logoutcontrols = {};
2349 - if (obj.args.nousers != true) { logoutcontrols.name = user.name; }
2350 -
2351 - // Give the web page a list of supported server features
2352 - features = 0;
2353 - features2 = 0;
2354 - if (obj.args.wanonly == true) { features += 0x00000001; } // WAN-only mode
2355 - if (obj.args.lanonly == true) { features += 0x00000002; } // LAN-only mode
2356 - if (obj.args.nousers == true) { features += 0x00000004; } // Single user mode
2357 - if (domain.userQuota == -1) { features += 0x00000008; } // No server files mode
2358 - if (obj.args.mpstlsoffload) { features += 0x00000010; } // No mutual-auth CIRA
2359 - if ((parent.config.settings.allowframing != null) || (domain.allowframing != null)) { features += 0x00000020; } // Allow site within iframe
2360 - if ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true)) { features += 0x00000040; } // Email invites
2361 - if (obj.args.webrtc == true) { features += 0x00000080; } // Enable WebRTC (Default false for now)
2362 - // 0x00000100 --> This feature flag is free for future use.
2363 - if (obj.args.allowhighqualitydesktop !== false) { features += 0x00000200; } // Enable AllowHighQualityDesktop (Default true)
2364 - if ((obj.args.lanonly == true) || (obj.args.mpsport == 0)) { features += 0x00000400; } // No CIRA
2365 - if ((obj.parent.serverSelfWriteAllowed == true) && (user != null) && (user.siteadmin == 0xFFFFFFFF)) { features += 0x00000800; } // Server can self-write (Allows self-update)
2366 - 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
2367 - if (domain.agentnoproxy === true) { features += 0x00002000; } // Indicates that agents should be installed without using a HTTP proxy
2368 - if ((parent.config.settings.no2factorauth !== true) && domain.yubikey && domain.yubikey.id && domain.yubikey.secret && (user._id.split('/')[2][0] != '~')) { features += 0x00004000; } // Indicates Yubikey support
2369 - if (domain.geolocation == true) { features += 0x00008000; } // Enable geo-location features
2370 - if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { features += 0x00010000; } // Enable password hints
2371 - if (parent.config.settings.no2factorauth !== true) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support
2372 - if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true) && (user._id.split('/')[2][0] != '~')) {
2373 - // Check if we can skip 2nd factor auth because of the source IP address
2374 - var skip2factor = false;
2375 - if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
2376 - for (var i in domain.passwordrequirements.skip2factor) {
2377 - if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) { skip2factor = true; }
2378 - }
2379 - }
2380 - if (skip2factor == false) { features += 0x00040000; } // Force 2-factor auth
2381 - }
2382 - 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.
2383 - if (domain.amtacmactivation) { features += 0x00100000; } // Intel AMT ACM activation/upgrade is possible
2384 - if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
2385 - if (parent.mqttbroker != null) { features += 0x00400000; } // This server supports MQTT channels
2386 - if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (parent.mailserver != null)) { features += 0x00800000; } // using email for 2FA is allowed
2387 - if (domain.agentinvitecodes == true) { features += 0x01000000; } // Support for agent invite codes
2388 - if (parent.smsserver != null) { features += 0x02000000; } // SMS messaging is supported
2389 - if ((parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false))) { features += 0x04000000; } // SMS 2FA is allowed
2390 - if (domain.sessionrecording != null) { features += 0x08000000; } // Server recordings enabled
2391 - if (domain.urlswitching === false) { features += 0x10000000; } // Disables the URL switching feature
2392 - if (domain.novnc === false) { features += 0x20000000; } // Disables noVNC
2393 - if (domain.mstsc !== true) { features += 0x40000000; } // Disables MSTSC.js
2394 - if (obj.isTrustedCert(domain) == false) { features += 0x80000000; } // Indicate we are not using a trusted certificate
2395 - if (obj.parent.amtManager != null) { features2 += 1; } // Indicates that the Intel AMT manager is active
2396 -
2397 - // Create a authentication cookie
2398 - const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey);
2399 - const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
2400 -
2401 - // Send the main web application
2402 - var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
2403 - 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
2404 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2405 -
2406 - // Clean up the U2F challenge if needed
2407 - if (req.session.u2fchallenge) { delete req.session.u2fchallenge; };
2408 -
2409 - // Intel AMT Scanning options
2410 - var amtscanoptions = '';
2411 - if (typeof domain.amtscanoptions == 'string') { amtscanoptions = encodeURIComponent(domain.amtscanoptions); }
2412 - else if (obj.common.validateStrArray(domain.amtscanoptions)) { domain.amtscanoptions = domain.amtscanoptions.join(','); amtscanoptions = encodeURIComponent(domain.amtscanoptions); }
2413 -
2414 - // Fetch the web state
2415 - parent.debug('web', 'handleRootRequestEx: success.');
2416 - obj.db.Get('ws' + user._id, function (err, states) {
2417 - var webstate = '';
2418 - if ((err == null) && (states != null) && (Array.isArray(states))) {
2419 - webstate = (states.length == 1) ? obj.filterUserWebState(states[0].state) : '';
2420 - if ((webstate == '') && (typeof domain.defaultuserwebstate == 'object')) { webstate = JSON.stringify(domain.defaultuserwebstate); } // User has no web state, use defaults.
2421 - if (typeof domain.forceduserwebstate == 'object') { // Forces initial user web state is present, use it.
2422 - var webstate2 = {};
2423 - try { if (webstate != '') { webstate2 = JSON.parse(webstate); } } catch (ex) { }
2424 - for (var i in domain.forceduserwebstate) { webstate2[i] = domain.forceduserwebstate[i]; }
2425 - webstate = JSON.stringify(webstate2);
2426 - }
2427 - }
2428 -
2429 - // Custom user interface
2430 - var customui = '';
2431 - if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
2432 -
2433 - // Server features
2434 - var serverFeatures = 127;
2435 - if (domain.myserver === false) { serverFeatures = 0; } // 64 = Show "My Server" tab
2436 - else if (typeof domain.myserver == 'object') {
2437 - if (domain.myserver.backup !== true) { serverFeatures -= 1; } // Disallow simple server backups
2438 - if (domain.myserver.restore !== true) { serverFeatures -= 2; } // Disallow simple server restore
2439 - if (domain.myserver.upgrade !== true) { serverFeatures -= 4; } // Disallow server upgrade
2440 - if (domain.myserver.errorlog !== true) { serverFeatures -= 8; } // Disallow show server crash log
2441 - if (domain.myserver.console !== true) { serverFeatures -= 16; } // Disallow server console
2442 - if (domain.myserver.trace !== true) { serverFeatures -= 32; } // Disallow server tracing
2443 - }
2444 - if (obj.db.databaseType != 1) { // If not using NeDB, we can't backup using the simple system.
2445 - if ((serverFeatures & 1) != 0) { serverFeatures -= 1; } // Disallow server backups
2446 - if ((serverFeatures & 2) != 0) { serverFeatures -= 2; } // Disallow simple server restore
2447 - }
2448 -
2449 - // Refresh the session
2450 - render(req, res, getRenderPage('default', req, domain), getRenderArgs({
2451 - authCookie: authCookie,
2452 - authRelayCookie: authRelayCookie,
2453 - viewmode: viewmode,
2454 - currentNode: currentNode,
2455 - logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'),
2456 - domain: domain.id,
2457 - debuglevel: parent.debugLevel,
2458 - serverDnsName: obj.getWebServerName(domain),
2459 - serverRedirPort: args.redirport,
2460 - serverPublicPort: httpsPort,
2461 - serverfeatures: serverFeatures,
2462 - features: features,
2463 - features2: features2,
2464 - sessiontime: (args.sessiontime) ? args.sessiontime : 60,
2465 - mpspass: args.mpspass,
2466 - passRequirements: passRequirements,
2467 - customui: customui,
2468 - webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'),
2469 - footer: (domain.footer == null) ? '' : domain.footer,
2470 - webstate: encodeURIComponent(webstate).replace(/'/g, '%27'),
2471 - amtscanoptions: amtscanoptions,
2472 - pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports()
2473 - }, req, domain));
2474 - });
2475 - } else {
2476 - // Send back the login application
2477 - // If this is a 2 factor auth request, look for a hardware key challenge.
2478 - // Normal login 2 factor request
2479 - if (req.session && (req.session.loginmode == '4') && (req.session.tokenuserid)) {
2480 - var user = obj.users[req.session.tokenuserid];
2481 - if (user != null) {
2482 - parent.debug('web', 'handleRootRequestEx: sending 2FA challenge.');
2483 - getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
2484 - return;
2485 - }
2486 - }
2487 - // Password recovery 2 factor request
2488 - if (req.session && (req.session.loginmode == '5') && (req.session.tokenemail)) {
2489 - obj.db.GetUserWithVerifiedEmail(domain.id, req.session.tokenemail, function (err, docs) {
2490 - if ((err != null) || (docs.length == 0)) {
2491 - parent.debug('web', 'handleRootRequestEx: password recover 2FA fail.');
2492 - req.session = null;
2493 - res.redirect(domain.url + getQueryPortion(req)); // BAD***
2494 - } else {
2495 - var user = obj.users[docs[0]._id];
2496 - if (user != null) {
2497 - parent.debug('web', 'handleRootRequestEx: password recover 2FA challenge.');
2498 - getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
2499 - } else {
2500 - parent.debug('web', 'handleRootRequestEx: password recover 2FA no user.');
2501 - req.session = null;
2502 - res.redirect(domain.url + getQueryPortion(req)); // BAD***
2503 - }
2504 - }
2505 - });
2506 - return;
2507 - }
2508 - handleRootRequestLogin(req, res, domain, '', passRequirements);
2509 - }
2510 - }
2511 -
2512 - function handleRootRequestLogin(req, res, domain, hardwareKeyChallenge, passRequirements) {
2513 - parent.debug('web', 'handleRootRequestLogin()');
2514 - var features = 0;
2515 - 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
2516 - if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
2517 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2518 - var loginmode = '';
2519 - 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.
2520 -
2521 - // Format an error message if needed
2522 - var passhint = null, msgid = 0;
2523 - if (req.session != null) {
2524 - msgid = req.session.messageid;
2525 - if ((loginmode == '7') || ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true))) { passhint = EscapeHtml(req.session.passhint); }
2526 - delete req.session.messageid;
2527 - delete req.session.passhint;
2528 - }
2529 - var emailcheck = ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
2530 -
2531 - // Check if we are allowed to create new users using the login screen
2532 - var newAccountsAllowed = true;
2533 - if ((domain.newaccounts !== 1) && (domain.newaccounts !== true)) { for (var i in obj.users) { if (obj.users[i].domain == domain.id) { newAccountsAllowed = false; break; } } }
2534 - if (parent.config.settings.maintenancemode != null) { newAccountsAllowed = false; }
2535 -
2536 - // Encrypt the hardware key challenge state if needed
2537 - var hwstate = null;
2538 - if (hardwareKeyChallenge) { hwstate = obj.parent.encodeCookie({ u: req.session.tokenusername, p: req.session.tokenpassword, c: req.session.u2fchallenge }, obj.parent.loginCookieEncryptionKey) }
2539 -
2540 - // Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5).
2541 - var otpemail = (loginmode != 5) && (parent.mailserver != null) && (req.session != null) && ((req.session.tokenemail == true) || (typeof req.session.tokenemail == 'string'));
2542 - if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
2543 - var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tokensms == true);
2544 - if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
2545 -
2546 - // See if we support two-factor trusted cookies
2547 - var twoFactorCookieDays = 30;
2548 - if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; }
2549 -
2550 - // See what authentication strategies we have
2551 - var authStrategies = [];
2552 - if (typeof domain.authstrategies == 'object') {
2553 - if (typeof domain.authstrategies.twitter == 'object') { authStrategies.push('twitter'); }
2554 - if (typeof domain.authstrategies.google == 'object') { authStrategies.push('google'); }
2555 - if (typeof domain.authstrategies.github == 'object') { authStrategies.push('github'); }
2556 - if (typeof domain.authstrategies.reddit == 'object') { authStrategies.push('reddit'); }
2557 - if (typeof domain.authstrategies.azure == 'object') { authStrategies.push('azure'); }
2558 - if (typeof domain.authstrategies.intel == 'object') { authStrategies.push('intel'); }
2559 - if (typeof domain.authstrategies.jumpcloud == 'object') { authStrategies.push('jumpcloud'); }
2560 - if (typeof domain.authstrategies.saml == 'object') { authStrategies.push('saml'); }
2561 - }
2562 -
2563 - // Custom user interface
2564 - var customui = '';
2565 - if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
2566 -
2567 - // Render the login page
2568 - render(req, res,
2569 - getRenderPage((domain.sitestyle == 2) ? 'login2' : 'login', req, domain),
2570 - getRenderArgs({
2571 - loginmode: loginmode,
2572 - rootCertLink: getRootCertLink(),
2573 - newAccount: newAccountsAllowed,
2574 - newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1),
2575 - serverDnsName: obj.getWebServerName(domain),
2576 - serverPublicPort: httpsPort,
2577 - passlogin: (typeof domain.showpasswordlogin == 'boolean') ? domain.showpasswordlogin : true,
2578 - emailcheck: emailcheck,
2579 - features: features,
2580 - sessiontime: (args.sessiontime) ? args.sessiontime : 60,
2581 - passRequirements: passRequirements,
2582 - customui: customui,
2583 - footer: (domain.loginfooter == null) ? '' : domain.loginfooter,
2584 - hkey: encodeURIComponent(hardwareKeyChallenge).replace(/'/g, '%27'),
2585 - messageid: msgid,
2586 - passhint: passhint,
2587 - welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null,
2588 - hwstate: hwstate,
2589 - otpemail: otpemail,
2590 - otpsms: otpsms,
2591 - twoFactorCookieDays: twoFactorCookieDays,
2592 - authStrategies: authStrategies.join(','),
2593 - loginpicture: (typeof domain.loginpicture == 'string')
2594 - }, req, domain, (domain.sitestyle == 2) ? 'login2' : 'login'));
2595 - }
2596 -
2597 - // Handle a post request on the root
2598 - function handleRootPostRequest(req, res) {
2599 - const domain = checkUserIpAddress(req, res);
2600 - if (domain == null) { return; }
2601 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.end("Not Found"); return; } // Check 3FA URL key
2602 - parent.debug('web', 'handleRootPostRequest, action: ' + req.body.action);
2603 -
2604 - switch (req.body.action) {
2605 - case 'login': { handleLoginRequest(req, res, true); break; }
2606 - case 'tokenlogin': {
2607 - if (req.body.hwstate) {
2608 - var cookie = obj.parent.decodeCookie(req.body.hwstate, obj.parent.loginCookieEncryptionKey, 10);
2609 - if (cookie != null) { req.session.tokenusername = cookie.u; req.session.tokenpassword = cookie.p; req.session.u2fchallenge = cookie.c; }
2610 - }
2611 - handleLoginRequest(req, res, true); break;
2612 - }
2613 - case 'changepassword': { handlePasswordChangeRequest(req, res, true); break; }
2614 - case 'deleteaccount': { handleDeleteAccountRequest(req, res, true); break; }
2615 - case 'createaccount': { handleCreateAccountRequest(req, res, true); break; }
2616 - case 'resetpassword': { handleResetPasswordRequest(req, res, true); break; }
2617 - case 'resetaccount': { handleResetAccountRequest(req, res, true); break; }
2618 - case 'checkemail': { handleCheckAccountEmailRequest(req, res, true); break; }
2619 - default: { handleLoginRequest(req, res, true); break; }
2620 - }
2621 - }
2622 -
2623 - // Return true if it looks like we are using a real TLS certificate.
2624 - obj.isTrustedCert = function (domain) {
2625 - if ((domain != null) && (typeof domain.trustedcert == 'boolean')) return domain.trustedcert; // If the status of the cert specified, use that.
2626 - if (typeof obj.args.trustedcert == 'boolean') return obj.args.trustedcert; // If the status of the cert specified, use that.
2627 - if (obj.args.tlsoffload != null) return true; // We are using TLS offload, a real cert is likely used.
2628 - 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.
2629 - if (obj.certificates.WebIssuer.indexOf('MeshCentralRoot-') == 0) return false; // Our cert is issued by self-signed cert.
2630 - if (obj.certificates.CommonName.indexOf('.') == -1) return false; // Our cert is named with a fake name
2631 - return true; // This is a guess
2632 - }
2633 -
2634 - // Get the link to the root certificate if needed
2635 - function getRootCertLink() {
2636 - // Check if the HTTPS certificate is issued from MeshCentralRoot, if so, add download link to root certificate.
2637 - if ((obj.args.tlsoffload == null) && (obj.parent.config.letsencrypt == null) && (obj.tlsSniCredentials == null) && (obj.certificates.WebIssuer.indexOf('MeshCentralRoot-') == 0) && (obj.certificates.CommonName.indexOf('.') != -1)) { return '<a href=/MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>'; }
2638 - return '';
2639 - }
2640 -
2641 - // Serve the xterm page
2642 - function handleXTermRequest(req, res) {
2643 - const domain = checkUserIpAddress(req, res);
2644 - if (domain == null) { return; }
2645 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2646 -
2647 - parent.debug('web', 'handleXTermRequest: sending xterm');
2648 - res.set({ 'Cache-Control': 'no-store' });
2649 - if (req.session && req.session.userid) {
2650 - if (req.session.domainid != domain.id) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
2651 - var user = obj.users[req.session.userid];
2652 - if ((user == null) || (req.query.nodeid == null)) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the user exists
2653 -
2654 - // Check permissions
2655 - obj.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
2656 - if ((node == null) || ((rights & 8) == 0) || ((rights != 0xFFFFFFFF) && ((rights & 512) != 0))) { res.redirect(domain.url + getQueryPortion(req)); return; }
2657 -
2658 - var logoutcontrols = { name: user.name };
2659 - var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
2660 - 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
2661 -
2662 - // Create a authentication cookie
2663 - const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey);
2664 - const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
2665 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2666 - 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));
2667 - });
2668 - } else {
2669 - res.redirect(domain.url + getQueryPortion(req));
2670 - return;
2671 - }
2672 - }
2673 -
2674 - // Render the terms of service.
2675 - function handleTermsRequest(req, res) {
2676 - const domain = checkUserIpAddress(req, res);
2677 - if (domain == null) { return; }
2678 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2679 -
2680 - // See if term.txt was loaded from the database
2681 - if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) {
2682 - // Send the terms from the database
2683 - res.set({ 'Cache-Control': 'no-store' });
2684 - if (req.session && req.session.userid) {
2685 - if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
2686 - var user = obj.users[req.session.userid];
2687 - var logoutcontrols = { name: user.name };
2688 - var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
2689 - 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
2690 - 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));
2691 - } else {
2692 - render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: encodeURIComponent('{}') }, req, domain));
2693 - }
2694 - } else {
2695 - // See if there is a terms.txt file in meshcentral-data
2696 - var p = obj.path.join(obj.parent.datapath, 'terms.txt');
2697 - if (obj.fs.existsSync(p)) {
2698 - obj.fs.readFile(p, 'utf8', function (err, data) {
2699 - if (err != null) { parent.debug('web', 'handleTermsRequest: no terms.txt'); res.sendStatus(404); return; }
2700 -
2701 - // Send the terms from terms.txt
2702 - res.set({ 'Cache-Control': 'no-store' });
2703 - if (req.session && req.session.userid) {
2704 - if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
2705 - var user = obj.users[req.session.userid];
2706 - var logoutcontrols = { name: user.name };
2707 - var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
2708 - 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
2709 - render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
2710 - } else {
2711 - render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: encodeURIComponent('{}') }, req, domain));
2712 - }
2713 - });
2714 - } else {
2715 - // Send the default terms
2716 - parent.debug('web', 'handleTermsRequest: sending default terms');
2717 - res.set({ 'Cache-Control': 'no-store' });
2718 - if (req.session && req.session.userid) {
2719 - if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
2720 - var user = obj.users[req.session.userid];
2721 - var logoutcontrols = { name: user.name };
2722 - var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
2723 - 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
2724 - render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27') }, req, domain));
2725 - } else {
2726 - render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: encodeURIComponent('{}') }, req, domain));
2727 - }
2728 - }
2729 - }
2730 - }
2731 -
2732 - // Render the messenger application.
2733 - function handleMessengerRequest(req, res) {
2734 - const domain = getDomain(req);
2735 - if (domain == null) { parent.debug('web', 'handleMessengerRequest: no domain'); res.sendStatus(404); return; }
2736 - parent.debug('web', 'handleMessengerRequest()');
2737 -
2738 - // Check if we are in maintenance mode
2739 - if (parent.config.settings.maintenancemode != null) {
2740 - 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));
2741 - return;
2742 - }
2743 -
2744 - var webRtcConfig = null;
2745 - 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'); }
2746 - else if (args.webrtconfig && (typeof args.webrtconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtconfig)).replace(/'/g, '%27'); }
2747 - res.set({ 'Cache-Control': 'no-store' });
2748 - render(req, res, getRenderPage('messenger', req, domain), getRenderArgs({ webrtconfig: webRtcConfig }, req, domain));
2749 - }
2750 -
2751 - // Returns the server root certificate encoded in base64
2752 - function getRootCertBase64() {
2753 - var rootcert = obj.certificates.root.cert;
2754 - var i = rootcert.indexOf('-----BEGIN CERTIFICATE-----\r\n');
2755 - if (i >= 0) { rootcert = rootcert.substring(i + 29); }
2756 - i = rootcert.indexOf('-----END CERTIFICATE-----');
2757 - if (i >= 0) { rootcert = rootcert.substring(i, 0); }
2758 - return Buffer.from(rootcert, 'base64').toString('base64');
2759 - }
2760 -
2761 - // Returns the mesh server root certificate
2762 - function handleRootCertRequest(req, res) {
2763 - const domain = getDomain(req);
2764 - if (domain == null) { parent.debug('web', 'handleRootCertRequest: no domain'); res.sendStatus(404); return; }
2765 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2766 - if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { parent.debug('web', 'handleRootCertRequest: invalid ip'); return; } // Check server-wide IP filter only.
2767 - parent.debug('web', 'handleRootCertRequest()');
2768 - setContentDispositionHeader(res, 'application/octet-stream', certificates.RootName + '.cer', null, 'rootcert.cer');
2769 - res.send(Buffer.from(getRootCertBase64(), 'base64'));
2770 - }
2771 -
2772 - // Handle user public file downloads
2773 - function handleDownloadUserFiles(req, res) {
2774 - const domain = checkUserIpAddress(req, res);
2775 - if (domain == null) { return; }
2776 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2777 -
2778 - if (obj.common.validateString(req.path, 1, 4096) == false) { res.sendStatus(404); return; }
2779 - var domainname = 'domain', spliturl = decodeURIComponent(req.path).split('/'), filename = '';
2780 - if ((spliturl.length < 3) || (obj.common.IsFilenameValid(spliturl[2]) == false) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
2781 - if (domain.id != '') { domainname = 'domain-' + domain.id; }
2782 - var path = obj.path.join(obj.filespath, domainname + '/user-' + spliturl[2] + '/Public');
2783 - 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; } }
2784 -
2785 - var stat = null;
2786 - try { stat = obj.fs.statSync(path); } catch (e) { }
2787 - if ((stat != null) && ((stat.mode & 0x004000) == 0)) {
2788 - if (req.query.download == 1) {
2789 - setContentDispositionHeader(res, 'application/octet-stream', filename, null, 'file.bin');
2790 - try { res.sendFile(obj.path.resolve(__dirname, path)); } catch (e) { res.sendStatus(404); }
2791 - } else {
2792 - render(req, res, getRenderPage((domain.sitestyle == 2) ? 'download2' : 'download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(), messageid: 1, fileurl: req.path + '?download=1', filename: filename, filesize: stat.size }, req, domain));
2793 - }
2794 - } else {
2795 - render(req, res, getRenderPage((domain.sitestyle == 2) ? 'download2' : 'download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(), messageid: 2 }, req, domain));
2796 - }
2797 - }
2798 -
2799 - // Handle device file request
2800 - function handleDeviceFile(req, res) {
2801 - const domain = checkUserIpAddress(req, res);
2802 - if (domain == null) { return; }
2803 - if ((req.query.c == null) || (req.query.m == null) || (req.query.n == null) || (req.query.f == null)) { res.sendStatus(404); return; }
2804 -
2805 - // Check the inbound desktop sharing cookie
2806 - var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
2807 - if ((c == null) || (c.domainid !== domain.id)) { res.sendStatus(404); return; }
2808 -
2809 - // Check userid
2810 - const user = obj.users[c.userid];
2811 - if ((c == user)) { res.sendStatus(404); return; }
2812 -
2813 - // Check if this user has permission to manage this computer
2814 - const meshid = 'mesh/' + domain.id + '/' + req.query.m;
2815 - const nodeid = 'node/' + domain.id + '/' + req.query.n;
2816 - if ((obj.GetNodeRights(c.userid, meshid, nodeid) & MESHRIGHT_REMOTECONTROL) == 0) { res.sendStatus(404); return; }
2817 -
2818 - // All good, start the file transfer
2819 - req.query.id = getRandomLowerCase(12);
2820 - obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, null, res, req, domain, user, meshid, nodeid);
2821 - }
2822 -
2823 - // Handle download of a server file by an agent
2824 - function handleAgentDownloadFile(req, res) {
2825 - const domain = checkUserIpAddress(req, res);
2826 - if (domain == null) { return; }
2827 - if (req.query.c == null) { res.sendStatus(404); return; }
2828 -
2829 - // Check the inbound desktop sharing cookie
2830 - var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 5); // 5 minute timeout
2831 - 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; }
2832 -
2833 - // Send the file back
2834 - try { res.sendFile(obj.path.join(obj.filespath, 'tmp', c.f)); return; } catch (ex) { res.sendStatus(404); }
2835 - }
2836 -
2837 - // Handle logo request
2838 - function handleLogoRequest(req, res) {
2839 - const domain = checkUserIpAddress(req, res);
2840 - if (domain == null) { return; }
2841 -
2842 - //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
2843 - if (domain.titlepicture) {
2844 - if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.titlepicture] != null)) {
2845 - // Use the logo in the database
2846 - res.set({ 'Content-Type': domain.titlepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
2847 - res.send(parent.configurationFiles[domain.titlepicture]);
2848 - return;
2849 - } else {
2850 - // Use the logo on file
2851 - try { res.sendFile(obj.path.join(obj.parent.datapath, domain.titlepicture)); return; } catch (ex) { }
2852 - }
2853 - }
2854 -
2855 - if ((domain.webpublicpath != null) && (obj.fs.existsSync(obj.path.join(domain.webpublicpath, 'images/logoback.png')))) {
2856 - // Use the domain logo picture
2857 - try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
2858 - } else if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png'))) {
2859 - // Use the override logo picture
2860 - try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
2861 - } else {
2862 - // Use the default logo picture
2863 - try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); }
2864 - }
2865 - }
2866 -
2867 - // Handle login logo request
2868 - function handleLoginLogoRequest(req, res) {
2869 - const domain = checkUserIpAddress(req, res);
2870 - if (domain == null) { return; }
2871 -
2872 - //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
2873 - if (domain.loginpicture) {
2874 - if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.loginpicture] != null)) {
2875 - // Use the logo in the database
2876 - res.set({ 'Content-Type': domain.loginpicture.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg' });
2877 - res.send(parent.configurationFiles[domain.loginpicture]);
2878 - return;
2879 - } else {
2880 - // Use the logo on file
2881 - try { res.sendFile(obj.path.join(obj.parent.datapath, domain.loginpicture)); return; } catch (ex) { res.sendStatus(404); }
2882 - }
2883 - } else {
2884 - res.sendStatus(404);
2885 - }
2886 - }
2887 -
2888 - // Handle translation request
2889 - function handleTranslationsRequest(req, res) {
2890 - const domain = checkUserIpAddress(req, res);
2891 - if (domain == null) { return; }
2892 - //if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
2893 - if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { return; } // Check server-wide IP filter only.
2894 -
2895 - var user = null;
2896 - if (obj.args.user != null) {
2897 - // A default user is active
2898 - user = obj.users['user/' + domain.id + '/' + obj.args.user];
2899 - if (!user) { parent.debug('web', 'handleTranslationsRequest: user not found.'); res.sendStatus(401); return; }
2900 - } else {
2901 - // Check if the user is logged and we have all required parameters
2902 - if (!req.session || !req.session.userid) { parent.debug('web', 'handleTranslationsRequest: failed checks (2).'); res.sendStatus(401); return; }
2903 -
2904 - // Get the current user
2905 - user = obj.users[req.session.userid];
2906 - if (!user) { parent.debug('web', 'handleTranslationsRequest: user not found.'); res.sendStatus(401); return; }
2907 - if (user.siteadmin != 0xFFFFFFFF) { parent.debug('web', 'handleTranslationsRequest: user not site administrator.'); res.sendStatus(401); return; }
2908 - }
2909 -
2910 - var data = '';
2911 - req.setEncoding('utf8');
2912 - req.on('data', function (chunk) { data += chunk; });
2913 - req.on('end', function () {
2914 - try { data = JSON.parse(data); } catch (ex) { data = null; }
2915 - if (data == null) { res.sendStatus(404); return; }
2916 - if (data.action == 'getTranslations') {
2917 - if (obj.fs.existsSync(obj.path.join(obj.parent.datapath, 'translate.json'))) {
2918 - // Return the translation file (JSON)
2919 - try { res.sendFile(obj.path.join(obj.parent.datapath, 'translate.json')); } catch (ex) { res.sendStatus(404); }
2920 - } else if (obj.fs.existsSync(obj.path.join(__dirname, 'translate', 'translate.json'))) {
2921 - // Return the default translation file (JSON)
2922 - try { res.sendFile(obj.path.join(__dirname, 'translate', 'translate.json')); } catch (ex) { res.sendStatus(404); }
2923 - } else { res.sendStatus(404); }
2924 - } else if (data.action == 'setTranslations') {
2925 - 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 })); } });
2926 - } else if (data.action == 'translateServer') {
2927 - if (obj.pendingTranslation === true) { res.send(JSON.stringify({ response: 'Server is already performing a translation.' })); return; }
2928 - const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
2929 - if (nodeVersion < 8) { res.send(JSON.stringify({ response: 'Server requires NodeJS 8.x or better.' })); return; }
2930 - var translateFile = obj.path.join(obj.parent.datapath, 'translate.json');
2931 - if (obj.fs.existsSync(translateFile) == false) { translateFile = obj.path.join(__dirname, 'translate', 'translate.json'); }
2932 - if (obj.fs.existsSync(translateFile) == false) { res.send(JSON.stringify({ response: 'Unable to find translate.js file on the server.' })); return; }
2933 - res.send(JSON.stringify({ response: 'ok' }));
2934 - console.log('Started server translation...');
2935 - obj.pendingTranslation = true;
2936 - require('child_process').exec('node translate.js translateall \"' + translateFile + '\"', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, 'translate') }, function (error, stdout, stderr) {
2937 - delete obj.pendingTranslation;
2938 - //console.log('error', error);
2939 - //console.log('stdout', stdout);
2940 - //console.log('stderr', stderr);
2941 - //console.log('Server restart...'); // Perform a server restart
2942 - //process.exit(0);
2943 - console.log('Server translation completed.');
2944 - });
2945 - } else {
2946 - // Unknown request
2947 - res.sendStatus(404);
2948 - }
2949 - });
2950 - }
2951 -
2952 - // Handle welcome image request
2953 - function handleWelcomeImageRequest(req, res) {
2954 - const domain = checkUserIpAddress(req, res);
2955 - if (domain == null) { return; }
2956 -
2957 - //res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
2958 - if (domain.welcomepicture) {
2959 - if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.welcomepicture] != null)) {
2960 - // Use the welcome image in the database
2961 - res.set({ 'Content-Type': domain.welcomepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
2962 - res.send(parent.configurationFiles[domain.welcomepicture]);
2963 - return;
2964 - }
2965 -
2966 - // Use the configured logo picture
2967 - try { res.sendFile(obj.path.join(obj.parent.datapath, domain.welcomepicture)); return; } catch (ex) { }
2968 - }
2969 -
2970 - var imagefile = 'images/mainwelcome.jpg';
2971 - if (domain.sitestyle == 2) { imagefile = 'images/login/back.png'; }
2972 - if (domain.webpublicpath != null) {
2973 - obj.fs.exists(obj.path.join(domain.webpublicpath, imagefile), function (exists) {
2974 - if (exists) {
2975 - // Use the domain logo picture
2976 - try { res.sendFile(obj.path.join(domain.webpublicpath, imagefile)); } catch (ex) { res.sendStatus(404); }
2977 - } else {
2978 - // Use the default logo picture
2979 - try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
2980 - }
2981 - });
2982 - } else if (parent.webPublicOverridePath) {
2983 - obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, imagefile), function (exists) {
2984 - if (exists) {
2985 - // Use the override logo picture
2986 - try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, imagefile)); } catch (ex) { res.sendStatus(404); }
2987 - } else {
2988 - // Use the default logo picture
2989 - try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
2990 - }
2991 - });
2992 - } else {
2993 - // Use the default logo picture
2994 - try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
2995 - }
2996 - }
2997 -
2998 - // Download a desktop recording
2999 - function handleGetRecordings(req, res) {
3000 - const domain = checkUserIpAddress(req, res);
3001 - if (domain == null) return;
3002 -
3003 - // Check the query
3004 - if ((domain.sessionrecording == null) || (req.query.file == null) || (obj.common.IsFilenameValid(req.query.file) !== true)) { res.sendStatus(401); return; }
3005 -
3006 - // Get the recording path
3007 - var recordingsPath = null;
3008 - if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.recordpath; }
3009 - if (recordingsPath == null) { res.sendStatus(401); return; }
3010 -
3011 - // Get the user and check user rights
3012 - var authUserid = null;
3013 - if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3014 - if (authUserid == null) { res.sendStatus(401); return; }
3015 - const user = obj.users[authUserid];
3016 - if (user == null) { res.sendStatus(401); return; }
3017 - if ((user.siteadmin & 512) == 0) { res.sendStatus(401); return; } // Check if we have right to get recordings
3018 -
3019 - // Send the recorded file
3020 - setContentDispositionHeader(res, 'application/octet-stream', req.query.file, null, 'recording.mcrec');
3021 - try { res.sendFile(obj.path.join(recordingsPath, req.query.file)); } catch (ex) { res.sendStatus(404); }
3022 - }
3023 -
3024 - // Serve the player page
3025 - function handlePlayerRequest(req, res) {
3026 - const domain = checkUserIpAddress(req, res);
3027 - if (domain == null) { return; }
3028 -
3029 - parent.debug('web', 'handlePlayerRequest: sending player');
3030 - res.set({ 'Cache-Control': 'no-store' });
3031 - render(req, res, getRenderPage('player', req, domain), getRenderArgs({}, req, domain));
3032 - }
3033 -
3034 - // Serve the guest desktop page
3035 - function handleDesktopRequest(req, res) {
3036 - const domain = getDomain(req, res);
3037 - if (domain == null) { return; }
3038 - if (req.query.c == null) { res.sendStatus(404); return; }
3039 - if (domain.guestdevicesharing === false) { res.sendStatus(404); return; } // This feature is not allowed.
3040 -
3041 - // Check the inbound desktop sharing cookie
3042 - var c = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey, 60); // 60 minute timeout
3043 - if ((c == null) || (c.a !== 5) || ((c.p !== 2) && (c.p != null)) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.start != 'number') || (typeof c.expire != 'number') || (typeof c.pid != 'string')) { res.sendStatus(404); return; }
3044 -
3045 - // Check the expired time, expire message.
3046 - if (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; }
3047 -
3048 - // Check the public id
3049 - obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
3050 - // Check if any desktop sharing links are present, expire message.
3051 - 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; }
3052 -
3053 - // Search for the device share public identifier, expire message.
3054 - var found = false;
3055 - for (var i = 0; i < docs.length; i++) { if (docs[i].publicid == c.pid) { found = true; } }
3056 - 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; }
3057 -
3058 - // Check the start time, not yet valid message.
3059 - if ((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; }
3060 -
3061 - // Looks good, let's create the outbound session cookies.
3062 - // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
3063 - const authCookie = obj.parent.encodeCookie({ userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: 2, gn: c.gn, cf: 65 | c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo }, obj.parent.loginCookieEncryptionKey);
3064 -
3065 - // Lets respond by sending out the desktop viewer.
3066 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3067 - parent.debug('web', 'handleDesktopRequest: Sending guest desktop page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
3068 - res.set({ 'Cache-Control': 'no-store' });
3069 - render(req, res, getRenderPage('desktop', 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 }, req, domain));
3070 - });
3071 - }
3072 -
3073 - // Serve the guest terminal page
3074 - function handleTerminalRequest(req, res) {
3075 - const domain = getDomain(req, res);
3076 - if (domain == null) { return; }
3077 - if (req.query.c == null) { res.sendStatus(404); return; }
3078 - if (domain.guestdevicesharing === false) { res.sendStatus(404); return; } // This feature is not allowed.
3079 -
3080 - // Check the inbound desktop sharing cookie
3081 - var c = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey, 60); // 60 minute timeout
3082 - if ((c == null) || (c.a !== 5) || (c.p !== 1) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.start != 'number') || (typeof c.expire != 'number') || (typeof c.pid != 'string')) { res.sendStatus(404); return; }
3083 -
3084 - // Check the expired time, expire message.
3085 - if (c.expire <= Date.now()) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 4, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3086 -
3087 - // Check the public id
3088 - obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
3089 - // Check if any desktop sharing links are present, expire message.
3090 - if ((err != null) || (docs.length == 0)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 4, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3091 -
3092 - // Search for the device share public identifier, expire message.
3093 - var found = false;
3094 - for (var i = 0; i < docs.length; i++) { if (docs[i].publicid == c.pid) { found = true; } }
3095 - if (found == false) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 4, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3096 -
3097 - // Check the start time, not yet valid message.
3098 - if ((c.start > Date.now()) || (c.start > c.expire)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 4, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3099 -
3100 - // Looks good, let's create the outbound session cookies.
3101 - // Consent flags are 2 = Notify, 16 = Prompt
3102 - const authCookie = obj.parent.encodeCookie({ userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: 1, gn: c.gn, cf: 2 | c.cf, r: 8, expire: c.expire, pid: c.pid }, obj.parent.loginCookieEncryptionKey);
3103 -
3104 - // Lets respond by sending out the desktop viewer.
3105 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3106 - parent.debug('web', 'handleTerminalRequest: Sending guest terminal page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
3107 - res.set({ 'Cache-Control': 'no-store' });
3108 - render(req, res, getRenderPage('terminal', 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 }, req, domain));
3109 - });
3110 - }
3111 -
3112 - // Handle domain redirection
3113 - obj.handleDomainRedirect = function (req, res) {
3114 - const domain = checkUserIpAddress(req, res);
3115 - if (domain == null) { return; }
3116 - if (domain.redirects == null) { res.sendStatus(404); return; }
3117 - var urlArgs = '', urlName = null, splitUrl = req.originalUrl.split('?');
3118 - if (splitUrl.length > 1) { urlArgs = '?' + splitUrl[1]; }
3119 - if ((splitUrl.length > 0) && (splitUrl[0].length > 1)) { urlName = splitUrl[0].substring(1).toLowerCase(); }
3120 - if ((urlName == null) || (domain.redirects[urlName] == null) || (urlName[0] == '_')) { res.sendStatus(404); return; }
3121 - if (domain.redirects[urlName] == '~showversion') {
3122 - // Show the current version
3123 - res.end('MeshCentral v' + obj.parent.currentVer);
3124 - } else {
3125 - // Perform redirection
3126 - res.redirect(domain.redirects[urlName] + urlArgs + getQueryPortion(req));
3127 - }
3128 - }
3129 -
3130 - // Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
3131 - obj.getServerFilePath = function (user, domain, path) {
3132 - var splitpath = path.split('/'), serverpath = obj.path.join(obj.filespath, 'domain'), filename = '';
3133 - if ((splitpath.length < 3) || (splitpath[0] != 'user' && splitpath[0] != 'mesh') || (splitpath[1] != domain.id)) return null; // Basic validation
3134 - var objid = splitpath[0] + '/' + splitpath[1] + '/' + splitpath[2];
3135 - if (splitpath[0] == 'user' && (objid != user._id)) return null; // User validation, only self allowed
3136 - if (splitpath[0] == 'mesh') { if ((obj.GetMeshRights(user, objid) & 32) == 0) { return null; } } // Check mesh server file rights
3137 - if (splitpath[1] != '') { serverpath += '-' + splitpath[1]; } // Add the domain if needed
3138 - serverpath += ('/' + splitpath[0] + '-' + splitpath[2]);
3139 - 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
3140 - return { fullpath: obj.path.resolve(obj.filespath, serverpath), path: serverpath, name: filename, quota: obj.getQuota(objid, domain) };
3141 - };
3142 -
3143 - // Return the maximum number of bytes allowed in the user account "My Files".
3144 - obj.getQuota = function (objid, domain) {
3145 - if (objid == null) return 0;
3146 - if (objid.startsWith('user/')) {
3147 - var user = obj.users[objid];
3148 - if (user == null) return 0;
3149 - if (user.siteadmin == 0xFFFFFFFF) return null; // Administrators have no user limit
3150 - if ((user.quota != null) && (typeof user.quota == 'number')) { return user.quota; }
3151 - if ((domain != null) && (domain.userquota != null) && (typeof domain.userquota == 'number')) { return domain.userquota; }
3152 - return null; // By default, the user will have no limit
3153 - } else if (objid.startsWith('mesh/')) {
3154 - var mesh = obj.meshes[objid];
3155 - if (mesh == null) return 0;
3156 - if ((mesh.quota != null) && (typeof mesh.quota == 'number')) { return mesh.quota; }
3157 - if ((domain != null) && (domain.meshquota != null) && (typeof domain.meshquota == 'number')) { return domain.meshquota; }
3158 - return null; // By default, the mesh will have no limit
3159 - }
3160 - return 0;
3161 - };
3162 -
3163 - // Download a file from the server
3164 - function handleDownloadFile(req, res) {
3165 - const domain = checkUserIpAddress(req, res);
3166 - if (domain == null) { return; }
3167 - if ((req.query.link == null) || (req.session == null) || (req.session.userid == null) || (domain == null) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
3168 - const user = obj.users[req.session.userid];
3169 - if (user == null) { res.sendStatus(404); return; }
3170 - const file = obj.getServerFilePath(user, domain, req.query.link);
3171 - if (file == null) { res.sendStatus(404); return; }
3172 - setContentDispositionHeader(res, 'application/octet-stream', file.name, null, 'file.bin');
3173 - obj.fs.exists(file.fullpath, function (exists) { if (exists == true) { res.sendFile(file.fullpath); } else { res.sendStatus(404); } });
3174 - }
3175 -
3176 - // Upload a MeshCore.js file to the server
3177 - function handleUploadMeshCoreFile(req, res) {
3178 - const domain = checkUserIpAddress(req, res);
3179 - if (domain == null) { return; }
3180 - if (domain.id !== '') { res.sendStatus(401); return; }
3181 -
3182 - var authUserid = null;
3183 - if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3184 -
3185 - const multiparty = require('multiparty');
3186 - const form = new multiparty.Form();
3187 - form.parse(req, function (err, fields, files) {
3188 - // If an authentication cookie is embedded in the form, use that.
3189 - if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3190 - var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3191 - if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3192 - if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3193 - }
3194 - if (authUserid == null) { res.sendStatus(401); return; }
3195 -
3196 - // Get the user
3197 - const user = obj.users[authUserid];
3198 - if (user.siteadmin != 0xFFFFFFFF) { res.sendStatus(401); return; } // Check if we have mesh core upload rights (Full admin only)
3199 -
3200 - if ((fields == null) || (fields.attrib == null) || (fields.attrib.length != 1)) { res.sendStatus(404); return; }
3201 - for (var i in files.files) {
3202 - var file = files.files[i];
3203 - obj.fs.readFile(file.path, 'utf8', function (err, data) {
3204 - if (err != null) return;
3205 - data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
3206 - obj.sendMeshAgentCore(user, domain, fields.attrib[0], 'custom', data); // Upload the core
3207 - try { obj.fs.unlinkSync(file.path); } catch (e) { }
3208 - });
3209 - }
3210 - res.send('');
3211 - });
3212 - }
3213 -
3214 - // Upload a file to the server
3215 - function handleUploadFile(req, res) {
3216 - const domain = checkUserIpAddress(req, res);
3217 - if (domain == null) { return; }
3218 - if (domain.userQuota == -1) { res.sendStatus(401); return; }
3219 - var authUserid = null;
3220 - if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3221 - const multiparty = require('multiparty');
3222 - const form = new multiparty.Form();
3223 - form.parse(req, function (err, fields, files) {
3224 - // If an authentication cookie is embedded in the form, use that.
3225 - if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3226 - var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3227 - if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3228 - if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3229 - }
3230 - if (authUserid == null) { res.sendStatus(401); return; }
3231 -
3232 - // Get the user
3233 - const user = obj.users[authUserid];
3234 - if ((user == null) || (user.siteadmin & 8) == 0) { res.sendStatus(401); return; } // Check if we have file rights
3235 -
3236 - if ((fields == null) || (fields.link == null) || (fields.link.length != 1)) { /*console.log('UploadFile, Invalid Fields:', fields, files);*/ console.log('err4'); res.sendStatus(404); return; }
3237 - var xfile = null;
3238 - try { xfile = obj.getServerFilePath(user, domain, decodeURIComponent(fields.link[0])); } catch (ex) { }
3239 - if (xfile == null) { res.sendStatus(404); return; }
3240 - // Get total bytes in the path
3241 - var totalsize = readTotalFileSize(xfile.fullpath);
3242 - if ((xfile.quota == null) || (totalsize < xfile.quota)) { // Check if the quota is not already broken
3243 - if (fields.name != null) {
3244 -
3245 - // See if we need to create the folder
3246 - var domainx = 'domain';
3247 - if (domain.id.length > 0) { domainx = 'domain-' + usersplit[1]; }
3248 - try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
3249 - try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (ex) { }
3250 - try { obj.fs.mkdirSync(xfile.fullpath); } catch (ex) { }
3251 -
3252 - // Upload method where all the file data is within the fields.
3253 - var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*');
3254 - if ((names.length == sizes.length) && (types.length == datas.length) && (names.length == types.length)) {
3255 - for (var i = 0; i < names.length; i++) {
3256 - if (obj.common.IsFilenameValid(names[i]) == false) { res.sendStatus(404); return; }
3257 - var filedata = Buffer.from(datas[i].split(',')[1], 'base64');
3258 - if ((xfile.quota == null) || ((totalsize + filedata.length) < xfile.quota)) { // Check if quota would not be broken if we add this file
3259 - // Create the user folder if needed
3260 - (function (fullpath, filename, filedata) {
3261 - obj.fs.mkdir(xfile.fullpath, function () {
3262 - // Write the file
3263 - obj.fs.writeFile(obj.path.join(xfile.fullpath, filename), filedata, function () {
3264 - obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
3265 - });
3266 - });
3267 - })(xfile.fullpath, names[i], filedata);
3268 - } else {
3269 - // Send a notification
3270 - obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: names[i], nolog: 1, id: Math.random() });
3271 - }
3272 - }
3273 - }
3274 - } else {
3275 - // More typical upload method, the file data is in a multipart mime post.
3276 - for (var i in files.files) {
3277 - var file = files.files[i], fpath = obj.path.join(xfile.fullpath, file.originalFilename);
3278 - 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
3279 -
3280 - // See if we need to create the folder
3281 - var domainx = 'domain';
3282 - if (domain.id.length > 0) { domainx = 'domain-' + domain.id; }
3283 - try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
3284 - try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (e) { }
3285 - try { obj.fs.mkdirSync(xfile.fullpath); } catch (e) { }
3286 -
3287 - // Rename the file
3288 - obj.fs.rename(file.path, fpath, function (err) {
3289 - if (err && (err.code === 'EXDEV')) {
3290 - // On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
3291 - obj.common.copyFile(file.path, fpath, function (err) {
3292 - obj.fs.unlink(file.path, function (err) {
3293 - obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
3294 - });
3295 - });
3296 - } else {
3297 - obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
3298 - }
3299 - });
3300 - } else {
3301 - // Send a notification
3302 - obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: file.originalFilename, nolog: 1, id: Math.random() });
3303 - try { obj.fs.unlink(file.path, function (err) { }); } catch (e) { }
3304 - }
3305 - }
3306 - }
3307 - } else {
3308 - // Send a notification
3309 - obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: "Disk quota exceed", nolog: 1, id: Math.random() });
3310 - }
3311 - res.send('');
3312 - });
3313 - }
3314 -
3315 - // Upload a file to the server and then batch upload to many agents
3316 - function handleUploadFileBatch(req, res) {
3317 - const domain = checkUserIpAddress(req, res);
3318 - if (domain == null) { return; }
3319 - var authUserid = null;
3320 - if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
3321 - const multiparty = require('multiparty');
3322 - const form = new multiparty.Form();
3323 - form.parse(req, function (err, fields, files) {
3324 - // If an authentication cookie is embedded in the form, use that.
3325 - if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
3326 - var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
3327 - if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
3328 - if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
3329 - }
3330 - if (authUserid == null) { res.sendStatus(401); return; }
3331 -
3332 - // Get the user
3333 - const user = obj.users[authUserid];
3334 - if (user == null) { parent.debug('web', 'Batch upload error, invalid user.'); res.sendStatus(401); return; } // Check if user exists
3335 -
3336 - // Get fields
3337 - if ((fields == null) || (fields.nodeIds == null) || (fields.nodeIds.length != 1)) { res.sendStatus(404); return; }
3338 - var cmd = { nodeids: fields.nodeIds[0].split(','), files: [], user: user, domain: domain };
3339 - if ((fields.winpath != null) && (fields.winpath.length == 1)) { cmd.windowsPath = fields.winpath[0]; }
3340 - if ((fields.linuxpath != null) && (fields.linuxpath.length == 1)) { cmd.linuxPath = fields.linuxpath[0]; }
3341 - if ((fields.overwriteFiles != null) && (fields.overwriteFiles.length == 1) && (fields.overwriteFiles[0] == 'on')) { cmd.overwrite = true; }
3342 - if ((fields.createFolder != null) && (fields.createFolder.length == 1) && (fields.createFolder[0] == 'on')) { cmd.createFolder = true; }
3343 -
3344 - // Check if we have at least one target path
3345 - if ((cmd.windowsPath == null) && (cmd.linuxPath == null)) {
3346 - parent.debug('web', 'Batch upload error, invalid fields: ' + JSON.stringify(fields));
3347 - res.send('');
3348 - return;
3349 - }
3350 -
3351 - // Get server temporary path
3352 - var serverpath = obj.path.join(obj.filespath, 'tmp')
3353 - try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
3354 - try { obj.fs.mkdirSync(serverpath); } catch (ex) { }
3355 -
3356 - // More typical upload method, the file data is in a multipart mime post.
3357 - for (var i in files.files) {
3358 - var file = files.files[i], ftarget = getRandomPassword() + '-' + file.originalFilename, fpath = obj.path.join(serverpath, ftarget);
3359 - cmd.files.push({ name: file.originalFilename, target: ftarget });
3360 - // Rename the file
3361 - obj.fs.rename(file.path, fpath, function (err) {
3362 - if (err && (err.code === 'EXDEV')) {
3363 - // On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
3364 - obj.common.copyFile(file.path, fpath, function (err) { obj.fs.unlink(file.path, function (err) { }); });
3365 - }
3366 - });
3367 - }
3368 -
3369 - // Instruct one of more agents to download a URL to a given local drive location.
3370 - var tlsCertHash = null;
3371 - if (parent.args.ignoreagenthashcheck !== true) {
3372 - tlsCertHash = obj.webCertificateFullHashs[cmd.domain.id];
3373 - if (tlsCertHash != null) { tlsCertHash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
3374 - }
3375 - for (var i in cmd.nodeids) {
3376 - obj.GetNodeWithRights(cmd.domain, cmd.user, cmd.nodeids[i], function (node, rights, visible) {
3377 - if ((node == null) || ((rights & 8) == 0) || (visible == false)) return; // We don't have remote control rights to this device
3378 - var agentPath = ((node.agent.id > 0) && (node.agent.id < 5)) ? cmd.windowsPath : cmd.linuxPath;
3379 - if (agentPath == null) return;
3380 -
3381 - // Event that this operation is being performed.
3382 - var targets = obj.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', cmd.user._id]);
3383 - var msgid = 103; // "Batch upload of {0} file(s) to folder {1}"
3384 - 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 };
3385 - parent.DispatchEvent(targets, obj, event);
3386 -
3387 - // Send the agent commands to perform the batch upload operation
3388 - for (var f in cmd.files) {
3389 - if (cmd.files[f].name != null) {
3390 - const acmd = { action: 'wget', 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 };
3391 - var agent = obj.wsagents[node._id];
3392 - if (agent != null) { try { agent.send(JSON.stringify(acmd)); } catch (ex) { } }
3393 - // TODO: Add support for peer servers.
3394 - }
3395 - }
3396 - });
3397 - }
3398 -
3399 - res.send('');
3400 - });
3401 - }
3402 -
3403 - // Subscribe to all events we are allowed to receive
3404 - obj.subscribe = function (userid, target) {
3405 - const user = obj.users[userid];
3406 - const subscriptions = [userid, 'server-global'];
3407 - if (user.siteadmin != null) {
3408 - // Allow full site administrators of users with all events rights to see all events.
3409 - if ((user.siteadmin == 0xFFFFFFFF) || ((user.siteadmin & 2048) != 0)) { subscriptions.push('*'); }
3410 - else if ((user.siteadmin & 2) != 0) {
3411 - if ((user.groups == null) || (user.groups.length == 0)) {
3412 - // Subscribe to all user changes
3413 - subscriptions.push('server-users');
3414 - } else {
3415 - // Subscribe to user changes for some groups
3416 - for (var i in user.groups) { subscriptions.push('server-users:' + i); }
3417 - }
3418 - }
3419 - }
3420 - if (user.links != null) { for (var i in user.links) { subscriptions.push(i); } }
3421 - obj.parent.RemoveAllEventDispatch(target);
3422 - obj.parent.AddEventDispatch(subscriptions, target);
3423 - return subscriptions;
3424 - };
3425 -
3426 - // Handle a web socket relay request
3427 - function handleRelayWebSocket(ws, req, domain, user, cookie) {
3428 - if (!(req.query.host)) { console.log('ERR: No host target specified'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
3429 - parent.debug('web', 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.');
3430 -
3431 - try { ws._socket.setKeepAlive(true, 240000); } catch (ex) { } // Set TCP keep alive
3432 -
3433 - // Fetch information about the target
3434 - obj.db.Get(req.query.host, function (err, docs) {
3435 - if (docs.length == 0) { console.log('ERR: Node not found'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
3436 - var node = docs[0];
3437 - if (!node.intelamt) { console.log('ERR: Not AMT node'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
3438 -
3439 - // Check if this user has permission to manage this computer
3440 - if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (3)'); try { ws.close(); } catch (e) { } return; }
3441 -
3442 - // Check what connectivity is available for this node
3443 - var state = parent.GetConnectivityState(req.query.host);
3444 - var conn = 0;
3445 - if (!state || state.connectivity == 0) { parent.debug('web', 'ERR: No routing possible (1)'); try { ws.close(); } catch (e) { } return; } else { conn = state.connectivity; }
3446 -
3447 - // Check what server needs to handle this connection
3448 - 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
3449 - var server = obj.parent.GetRoutingServerId(req.query.host, 2); // Check for Intel CIRA connection
3450 - if (server != null) {
3451 - if (server.serverid != obj.parent.serverId) {
3452 - // Do local Intel CIRA routing using a different server
3453 - parent.debug('web', 'Route Intel AMT CIRA connection to peer server: ' + server.serverid);
3454 - obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
3455 - return;
3456 - }
3457 - } else {
3458 - server = obj.parent.GetRoutingServerId(req.query.host, 4); // Check for local Intel AMT connection
3459 - if ((server != null) && (server.serverid != obj.parent.serverId)) {
3460 - // Do local Intel AMT routing using a different server
3461 - parent.debug('web', 'Route Intel AMT direct connection to peer server: ' + server.serverid);
3462 - obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
3463 - return;
3464 - }
3465 - }
3466 - }
3467 -
3468 - // Setup session recording if needed
3469 - 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
3470 - // Check again if we need to do recording
3471 - var record = true;
3472 - if (domain.sessionrecording.onlyselecteddevicegroups === true) {
3473 - var mesh = obj.meshes[node.meshid];
3474 - if ((mesh.flags == null) || ((mesh.flags & 4) == 0)) { record = false; } // Do not record the session
3475 - }
3476 -
3477 - if (record == true) {
3478 - var now = new Date(Date.now());
3479 - var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + now.getUTCFullYear() + '-' + obj.common.zeroPad(now.getUTCMonth(), 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'
3480 - var recFullFilename = null;
3481 - if (domain.sessionrecording.filepath) {
3482 - try { obj.fs.mkdirSync(domain.sessionrecording.filepath); } catch (e) { }
3483 - recFullFilename = obj.path.join(domain.sessionrecording.filepath, recFilename);
3484 - } else {
3485 - try { obj.fs.mkdirSync(parent.recordpath); } catch (e) { }
3486 - recFullFilename = obj.path.join(parent.recordpath, recFilename);
3487 - }
3488 - var fd = obj.fs.openSync(recFullFilename, 'w');
3489 - if (fd != null) {
3490 - // Write the recording file header
3491 - 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() })
3492 - recordingEntry(fd, 1, 0, firstBlock, function () { });
3493 - ws.logfile = { fd: fd, lock: false };
3494 - if (req.query.p == 2) { ws.send(Buffer.from(String.fromCharCode(0xF0), 'binary')); } // Intel AMT Redirection: Indicate the session is being recorded
3495 - }
3496 - }
3497 - }
3498 -
3499 - // If Intel AMT CIRA connection is available, use it
3500 - var ciraconn = parent.mpsserver.GetConnectionToNode(req.query.host, null, false);
3501 - if (ciraconn != null) {
3502 - parent.debug('web', 'Opening relay CIRA channel connection to ' + req.query.host + '.');
3503 -
3504 - // TODO: If the CIRA connection is a relay or LMS connection, we can't detect the TLS state like this.
3505 - // Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
3506 - var port = 16993;
3507 - //if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA
3508 - if (ciraconn.tag.boundPorts.indexOf(16992) >= 0) port = 16992; // RELEASE: Always use non-TLS mode if available within CIRA
3509 - if (req.query.p == 2) port += 2;
3510 -
3511 - // Setup a new CIRA channel
3512 - if ((port == 16993) || (port == 16995)) {
3513 - // Perform TLS
3514 - var ser = new SerialTunnel();
3515 - var chnl = parent.mpsserver.SetupChannel(ciraconn, port);
3516 -
3517 - // Let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
3518 - // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
3519 - ser.forwardwrite = function (data) { if (data.length > 0) { chnl.write(data); } }; // TLS ---> CIRA
3520 -
3521 - // When APF tunnel return something, update SerialTunnel buffer
3522 - chnl.onData = function (ciraconn, data) { if (data.length > 0) { try { ser.updateBuffer(data); } catch (ex) { console.log(ex); } } }; // CIRA ---> TLS
3523 -
3524 - // Handle CIRA tunnel state change
3525 - chnl.onStateChange = function (ciraconn, state) {
3526 - parent.debug('webrelay', 'Relay TLS CIRA state change', state);
3527 - if (state == 0) { try { ws.close(); } catch (e) { } }
3528 - if (state == 2) {
3529 - // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
3530 - 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 };
3531 - if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
3532 - var tlsock = obj.tls.connect(tlsoptions, function () { parent.debug('webrelay', "CIRA Secure TLS Connection"); ws._socket.resume(); });
3533 - tlsock.chnl = chnl;
3534 - tlsock.setEncoding('binary');
3535 - tlsock.on('error', function (err) { parent.debug('webrelay', "CIRA TLS Connection Error", err); });
3536 -
3537 - // Decrypted tunnel from TLS communcation to be forwarded to websocket
3538 - tlsock.on('data', function (data) {
3539 - // AMT/TLS ---> WS
3540 - if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
3541 - try { ws.send(data); } catch (ex) { }
3542 - });
3543 -
3544 - // If TLS is on, forward it through TLSSocket
3545 - ws.forwardclient = tlsock;
3546 - ws.forwardclient.xtls = 1;
3547 -
3548 - ws.forwardclient.onStateChange = function (ciraconn, state) {
3549 - parent.debug('webrelay', 'Relay CIRA state change', state);
3550 - if (state == 0) { try { ws.close(); } catch (e) { } }
3551 - };
3552 -
3553 - ws.forwardclient.onData = function (ciraconn, data) {
3554 - // Run data thru interceptor
3555 - if (ws.interceptor) { data = ws.interceptor.processAmtData(data); }
3556 -
3557 - if (data.length > 0) {
3558 - if (ws.logfile == null) {
3559 - try { ws.send(data); } catch (e) { }
3560 - } else {
3561 - // Log to recording file
3562 - recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } }); // TODO: Add TLS support
3563 - }
3564 - }
3565 - };
3566 -
3567 - // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
3568 - ws.forwardclient.onSendOk = function (ciraconn) { };
3569 - }
3570 - };
3571 - } else {
3572 - // Without TLS
3573 - ws.forwardclient = parent.mpsserver.SetupChannel(ciraconn, port);
3574 - ws.forwardclient.xtls = 0;
3575 - ws._socket.resume();
3576 -
3577 - ws.forwardclient.onStateChange = function (ciraconn, state) {
3578 - parent.debug('webrelay', 'Relay CIRA state change', state);
3579 - if (state == 0) { try { ws.close(); } catch (e) { } }
3580 - };
3581 -
3582 - ws.forwardclient.onData = function (ciraconn, data) {
3583 - //parent.debug('webrelaydata', 'Relay CIRA data to WS', data.length);
3584 -
3585 - // Run data thru interceptorp
3586 - if (ws.interceptor) { data = ws.interceptor.processAmtData(data); }
3587 -
3588 - //console.log('AMT --> WS', Buffer.from(data, 'binary').toString('hex'));
3589 - if (data.length > 0) {
3590 - if (ws.logfile == null) {
3591 - try { ws.send(data); } catch (e) { }
3592 - } else {
3593 - // Log to recording file
3594 - recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } });
3595 - }
3596 - }
3597 - };
3598 -
3599 - // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
3600 - ws.forwardclient.onSendOk = function (ciraconn) { };
3601 - }
3602 -
3603 - // When data is received from the web socket, forward the data into the associated CIRA cahnnel.
3604 - // If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
3605 - ws.on('message', function (data) {
3606 - //parent.debug('webrelaydata', 'Relay WS data to CIRA', data.length);
3607 - if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
3608 -
3609 - // WS ---> AMT/TLS
3610 - if (ws.interceptor) { data = ws.interceptor.processBrowserData(data); } // Run data thru interceptor
3611 -
3612 - // Log to recording file
3613 - if (ws.logfile == null) {
3614 - // Forward data to the associated TCP connection.
3615 - ws.forwardclient.write(data);
3616 - } else {
3617 - // Log to recording file
3618 - recordingEntry(ws.logfile.fd, 2, 2, data, function () { try { ws.forwardclient.write(data); } catch (ex) { } });
3619 - }
3620 - });
3621 -
3622 - // If error, close the associated TCP connection.
3623 - ws.on('error', function (err) {
3624 - console.log('CIRA server websocket error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
3625 - parent.debug('webrelay', 'Websocket relay closed on error.');
3626 -
3627 - // Websocket closed, close the CIRA channel and TLS session.
3628 - if (ws.forwardclient) {
3629 - if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
3630 - if (ws.forwardclient.end) { ws.forwardclient.end(); } // TLS, close the TLS session
3631 - if (ws.forwardclient.chnl) { ws.forwardclient.chnl.close(); } // TLS, close the CIRA channel
3632 - delete ws.forwardclient;
3633 - }
3634 -
3635 - // Close the recording file
3636 - if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
3637 - });
3638 -
3639 - // If the web socket is closed, close the associated TCP connection.
3640 - ws.on('close', function (req) {
3641 - parent.debug('webrelay', 'Websocket relay closed.');
3642 -
3643 - // Websocket closed, close the CIRA channel and TLS session.
3644 - if (ws.forwardclient) {
3645 - if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
3646 - if (ws.forwardclient.end) { ws.forwardclient.end(); } // TLS, close the TLS session
3647 - if (ws.forwardclient.chnl) { ws.forwardclient.chnl.close(); } // TLS, close the CIRA channel
3648 - delete ws.forwardclient;
3649 - }
3650 -
3651 - // Close the recording file
3652 - if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
3653 - });
3654 -
3655 - // 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
3656 -
3657 - // Fetch Intel AMT credentials & Setup interceptor
3658 - if (req.query.p == 1) {
3659 - parent.debug('webrelaydata', 'INTERCEPTOR1', { host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
3660 - ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
3661 - ws.interceptor.blockAmtStorage = true;
3662 - } else if (req.query.p == 2) {
3663 - parent.debug('webrelaydata', 'INTERCEPTOR2', { user: node.intelamt.user, pass: node.intelamt.pass });
3664 - ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass });
3665 - ws.interceptor.blockAmtStorage = true;
3666 - }
3667 -
3668 - return;
3669 - }
3670 -
3671 - // If Intel AMT direct connection is possible, option a direct socket
3672 - if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
3673 - parent.debug('webrelay', 'Opening relay TCP socket connection to ' + req.query.host + '.');
3674 -
3675 - // When data is received from the web socket, forward the data into the associated TCP connection.
3676 - ws.on('message', function (msg) {
3677 - //parent.debug('webrelaydata', 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes');
3678 -
3679 - if (typeof msg == 'string') { msg = Buffer.from(msg, 'binary'); }
3680 - if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
3681 -
3682 - // Log to recording file
3683 - if (ws.logfile == null) {
3684 - // Forward data to the associated TCP connection.
3685 - try { ws.forwardclient.write(msg); } catch (ex) { }
3686 - } else {
3687 - // Log to recording file
3688 - recordingEntry(ws.logfile.fd, 2, 2, msg, function () { try { ws.forwardclient.write(msg); } catch (ex) { } });
3689 - }
3690 - });
3691 -
3692 - // If error, close the associated TCP connection.
3693 - ws.on('error', function (err) {
3694 - console.log('Error with relay web socket connection from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
3695 - parent.debug('webrelay', 'Error with relay web socket connection from ' + req.clientIp + '.');
3696 - if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
3697 -
3698 - // Close the recording file
3699 - if (ws.logfile != null) {
3700 - recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
3701 - obj.fs.close(fd);
3702 - ws.logfile = null;
3703 - });
3704 - }
3705 - });
3706 -
3707 - // If the web socket is closed, close the associated TCP connection.
3708 - ws.on('close', function () {
3709 - parent.debug('webrelay', 'Closing relay web socket connection to ' + req.query.host + '.');
3710 - if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
3711 -
3712 - // Close the recording file
3713 - if (ws.logfile != null) {
3714 - recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
3715 - obj.fs.close(fd);
3716 - ws.logfile = null;
3717 - });
3718 - }
3719 - });
3720 -
3721 - // Compute target port
3722 - var port = 16992;
3723 - if (node.intelamt.tls > 0) port = 16993; // This is a direct connection, use TLS when possible
3724 - if ((req.query.p == 2) || (req.query.p == 4)) port += 2;
3725 -
3726 - if (node.intelamt.tls == 0) {
3727 - // If this is TCP (without TLS) set a normal TCP socket
3728 - ws.forwardclient = new obj.net.Socket();
3729 - ws.forwardclient.setEncoding('binary');
3730 - ws.forwardclient.xstate = 0;
3731 - ws.forwardclient.forwardwsocket = ws;
3732 - ws._socket.resume();
3733 - } else {
3734 - // If TLS is going to be used, setup a TLS socket
3735 - 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 };
3736 - if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
3737 - ws.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
3738 - // The TLS connection method is the same as TCP, but located a bit differently.
3739 - parent.debug('webrelay', 'TLS connected to ' + node.host + ':' + port + '.');
3740 - ws.forwardclient.xstate = 1;
3741 - ws._socket.resume();
3742 - });
3743 - ws.forwardclient.setEncoding('binary');
3744 - ws.forwardclient.xstate = 0;
3745 - ws.forwardclient.forwardwsocket = ws;
3746 - }
3747 -
3748 - // When we receive data on the TCP connection, forward it back into the web socket connection.
3749 - ws.forwardclient.on('data', function (data) {
3750 - if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
3751 - if (obj.parent.debugLevel >= 1) { // DEBUG
3752 - parent.debug('webrelaydata', 'TCP relay data from ' + node.host + ', ' + data.length + ' bytes.');
3753 - //if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
3754 - }
3755 - if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
3756 - if (ws.logfile == null) {
3757 - // No logging
3758 - try { ws.send(data); } catch (e) { }
3759 - } else {
3760 - // Log to recording file
3761 - recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (e) { } });
3762 - }
3763 - });
3764 -
3765 - // If the TCP connection closes, disconnect the associated web socket.
3766 - ws.forwardclient.on('close', function () {
3767 - parent.debug('webrelay', 'TCP relay disconnected from ' + node.host + ':' + port + '.');
3768 - try { ws.close(); } catch (e) { }
3769 - });
3770 -
3771 - // If the TCP connection causes an error, disconnect the associated web socket.
3772 - ws.forwardclient.on('error', function (err) {
3773 - parent.debug('webrelay', 'TCP relay error from ' + node.host + ':' + port + ': ' + err);
3774 - try { ws.close(); } catch (e) { }
3775 - });
3776 -
3777 - // Fetch Intel AMT credentials & Setup interceptor
3778 - if (req.query.p == 1) { ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); }
3779 - else if (req.query.p == 2) { ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass }); }
3780 -
3781 - if (node.intelamt.tls == 0) {
3782 - // A TCP connection to Intel AMT just connected, start forwarding.
3783 - ws.forwardclient.connect(port, node.host, function () {
3784 - parent.debug('webrelay', 'TCP relay connected to ' + node.host + ':' + port + '.');
3785 - ws.forwardclient.xstate = 1;
3786 - ws._socket.resume();
3787 - });
3788 - }
3789 - return;
3790 - }
3791 -
3792 - });
3793 - }
3794 -
3795 - // Setup agent to/from server file transfer handler
3796 - function handleAgentFileTransfer(ws, req) {
3797 - var domain = checkAgentIpAddress(ws, req);
3798 - if (domain == null) { parent.debug('web', 'Got agent file transfer connection with bad domain or blocked IP address ' + req.clientIp + ', dropping.'); ws.close(); return; }
3799 - if (req.query.c == null) { parent.debug('web', 'Got agent file transfer connection without a cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
3800 - var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
3801 - if ((c == null) || (c.a != 'aft')) { parent.debug('web', 'Got agent file transfer connection with invalid cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
3802 - ws.xcmd = c.b; ws.xarg = c.c, ws.xfilelen = 0;
3803 - ws.send('c'); // Indicate connection of the tunnel. In this case, we are the termination point.
3804 - ws.send('5'); // Indicate we want to perform file transfers (5 = Files).
3805 - if (ws.xcmd == 'coredump') {
3806 - // Check the agent core dump folder if not already present.
3807 - var coreDumpPath = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps');
3808 - if (obj.fs.existsSync(coreDumpPath) == false) { try { obj.fs.mkdirSync(coreDumpPath); } catch (ex) { } }
3809 - ws.xfilepath = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps', ws.xarg);
3810 - ws.xid = 'coredump';
3811 - ws.send(JSON.stringify({ action: 'download', sub: 'start', ask: 'coredump', id: 'coredump' })); // Ask for a core dump file
3812 - }
3813 -
3814 - // When data is received from the web socket, echo it back
3815 - ws.on('message', function (data) {
3816 - if (typeof data == 'string') {
3817 - // Control message
3818 - var cmd = null;
3819 - try { cmd = JSON.parse(data); } catch (ex) { }
3820 - if ((cmd == null) || (cmd.action != 'download') || (cmd.sub == null)) return;
3821 - switch (cmd.sub) {
3822 - case 'start': {
3823 - // Perform an async file open
3824 - var callback = function onFileOpen(err, fd) {
3825 - onFileOpen.xws.xfile = fd;
3826 - onFileOpen.xws.send(JSON.stringify({ action: 'download', sub: 'startack', id: onFileOpen.xws.xid, ack: 1 })); // Ask for a directory (test)
3827 - };
3828 - callback.xws = this;
3829 - obj.fs.open(this.xfilepath + '.part', 'w', callback);
3830 - break;
3831 - }
3832 - }
3833 - } else {
3834 - // Binary message
3835 - if (data.length < 4) return;
3836 - var flags = data.readInt32BE(0);
3837 - if ((data.length > 4)) {
3838 - // Write the file
3839 - this.xfilelen += (data.length - 4);
3840 - try {
3841 - var callback = function onFileDataWritten(err, bytesWritten, buffer) {
3842 - if (onFileDataWritten.xflags & 1) {
3843 - // End of file
3844 - parent.debug('web', "Completed downloads of agent dumpfile, " + onFileDataWritten.xws.xfilelen + " bytes.");
3845 - if (onFileDataWritten.xws.xfile) {
3846 - obj.fs.close(onFileDataWritten.xws.xfile, function (err) { });
3847 - obj.fs.rename(onFileDataWritten.xws.xfilepath + '.part', onFileDataWritten.xws.xfilepath, function (err) { });
3848 - onFileDataWritten.xws.xfile = null;
3849 - }
3850 - onFileDataWritten.xws.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file
3851 - try { onFileDataWritten.xws.close(); } catch (ex) { }
3852 - } else {
3853 - // Send ack
3854 - onFileDataWritten.xws.send(JSON.stringify({ action: 'download', sub: 'ack', id: onFileDataWritten.xws.xid })); // Ask for a directory (test)
3855 - }
3856 - };
3857 - callback.xws = this;
3858 - callback.xflags = flags;
3859 - obj.fs.write(this.xfile, data, 4, data.length - 4, callback);
3860 - } catch (ex) { }
3861 - } else {
3862 - if (flags & 1) {
3863 - // End of file
3864 - parent.debug('web', "Completed downloads of agent dumpfile, " + this.xfilelen + " bytes.");
3865 - if (this.xfile) {
3866 - obj.fs.close(this.xfile, function (err) { });
3867 - obj.fs.rename(this.xfilepath + '.part', this.xfilepath, function (err) { });
3868 - this.xfile = null;
3869 - }
3870 - this.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file
3871 - try { this.close(); } catch (ex) { }
3872 - } else {
3873 - // Send ack
3874 - this.send(JSON.stringify({ action: 'download', sub: 'ack', id: this.xid })); // Ask for a directory (test)
3875 - }
3876 - }
3877 - }
3878 - });
3879 -
3880 - // If error, do nothing.
3881 - ws.on('error', function (err) { console.log('Agent file transfer server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); });
3882 -
3883 - // If closed, do nothing
3884 - ws.on('close', function (req) {
3885 - if (this.xfile) {
3886 - obj.fs.close(this.xfile, function (err) { });
3887 - obj.fs.unlink(this.xfilepath + '.part', function (err) { }); // Remove a partial file
3888 - }
3889 - });
3890 - }
3891 -
3892 - // Handle the web socket echo request, just echo back the data sent
3893 - function handleEchoWebSocket(ws, req) {
3894 - const domain = checkUserIpAddress(ws, req);
3895 - if (domain == null) { return; }
3896 - ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
3897 -
3898 - // When data is received from the web socket, echo it back
3899 - ws.on('message', function (data) {
3900 - if (data.toString('utf8') == 'close') {
3901 - try { ws.close(); } catch (e) { console.log(e); }
3902 - } else {
3903 - try { ws.send(data); } catch (e) { console.log(e); }
3904 - }
3905 - });
3906 -
3907 - // If error, do nothing.
3908 - ws.on('error', function (err) { console.log('Echo server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); });
3909 -
3910 - // If closed, do nothing
3911 - ws.on('close', function (req) { });
3912 - }
3913 -
3914 - // Get the total size of all files in a folder and all sub-folders. (TODO: try to make all async version)
3915 - function readTotalFileSize(path) {
3916 - var r = 0, dir;
3917 - try { dir = obj.fs.readdirSync(path); } catch (e) { return 0; }
3918 - for (var i in dir) {
3919 - var stat = obj.fs.statSync(path + '/' + dir[i]);
3920 - if ((stat.mode & 0x004000) == 0) { r += stat.size; } else { r += readTotalFileSize(path + '/' + dir[i]); }
3921 - }
3922 - return r;
3923 - }
3924 -
3925 - // Delete a folder and all sub items. (TODO: try to make all async version)
3926 - function deleteFolderRec(path) {
3927 - if (obj.fs.existsSync(path) == false) return;
3928 - try {
3929 - obj.fs.readdirSync(path).forEach(function (file, index) {
3930 - var pathx = path + '/' + file;
3931 - if (obj.fs.lstatSync(pathx).isDirectory()) { deleteFolderRec(pathx); } else { obj.fs.unlinkSync(pathx); }
3932 - });
3933 - obj.fs.rmdirSync(path);
3934 - } catch (ex) { }
3935 - }
3936 -
3937 - // Handle Intel AMT events
3938 - // To subscribe, add "http://server:port/amtevents.ashx" to Intel AMT subscriptions.
3939 - obj.handleAmtEventRequest = function (req, res) {
3940 - const domain = getDomain(req);
3941 - try {
3942 - if (req.headers.authorization) {
3943 - var authstr = req.headers.authorization;
3944 - if (authstr.substring(0, 7) == 'Digest ') {
3945 - var auth = obj.common.parseNameValueList(obj.common.quoteSplit(authstr.substring(7)));
3946 - if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) {
3947 -
3948 - // Read the data, we need to get the arg field
3949 - var eventData = '';
3950 - req.on('data', function (chunk) { eventData += chunk; });
3951 - req.on('end', function () {
3952 -
3953 - // Completed event read, let get the argument that must contain the nodeid
3954 - var i = eventData.indexOf('<m:arg xmlns:m="http://x.com">');
3955 - if (i > 0) {
3956 - var nodeid = eventData.substring(i + 30, i + 30 + 64);
3957 - if (nodeid.length == 64) {
3958 - var nodekey = 'node/' + domain.id + '/' + nodeid;
3959 -
3960 - // See if this node exists in the database
3961 - obj.db.Get(nodekey, function (err, nodes) {
3962 - if (nodes.length == 1) {
3963 - // Yes, the node exists, compute Intel AMT digest password
3964 - var node = nodes[0];
3965 - 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');
3966 -
3967 - // Check the MD5 hash
3968 - if (auth.response === obj.common.ComputeDigesthash(auth.username, amtpass, auth.realm, 'POST', auth.uri, auth.qop, auth.nonce, auth.nc, auth.cnonce)) {
3969 -
3970 - // This is an authenticated Intel AMT event, update the host address
3971 - var amthost = req.clientIp;
3972 - if (amthost.substring(0, 7) === '::ffff:') { amthost = amthost.substring(7); }
3973 - if (node.host != amthost) {
3974 - // Get the mesh for this device
3975 - var mesh = obj.meshes[node.meshid];
3976 - if (mesh) {
3977 - // Update the database
3978 - var oldname = node.host;
3979 - node.host = amthost;
3980 - obj.db.Set(obj.cleanDevice(node));
3981 -
3982 - // Event the node change
3983 - 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 };
3984 -
3985 - // Remove the Intel AMT password before eventing this.
3986 - event.node = node;
3987 - if (event.node.intelamt && event.node.intelamt.pass) {
3988 - event.node = Object.assign({}, event.node); // Shallow clone
3989 - event.node.intelamt = Object.assign({}, event.node.intelamt); // Shallow clone
3990 - delete event.node.intelamt.pass;
3991 - }
3992 -
3993 - 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.
3994 - obj.parent.DispatchEvent(['*', node.meshid], obj, event);
3995 - }
3996 - }
3997 -
3998 - parent.amtEventHandler.handleAmtEvent(eventData, nodeid, amthost);
3999 - //res.send('OK');
4000 -
4001 - return;
4002 - }
4003 - }
4004 - });
4005 - }
4006 - }
4007 - });
4008 - }
4009 - }
4010 - }
4011 - } catch (e) { console.log(e); }
4012 -
4013 - // Send authentication response
4014 - obj.crypto.randomBytes(48, function (err, buf) {
4015 - var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(nonce).digest('hex');
4016 - res.set({ 'WWW-Authenticate': 'Digest realm="' + obj.httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' });
4017 - res.sendStatus(401);
4018 - });
4019 - };
4020 -
4021 - // Handle a server backup request
4022 - function handleBackupRequest(req, res) {
4023 - const domain = checkUserIpAddress(req, res);
4024 - if (domain == null) { return; }
4025 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4026 - if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4027 - if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver.backup !== true))) { res.sendStatus(401); return; }
4028 -
4029 - var user = obj.users[req.session.userid];
4030 - if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights
4031 -
4032 - // Require modules
4033 - const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method to maximum.
4034 -
4035 - // Good practice to catch this error explicitly
4036 - archive.on('error', function (err) { throw err; });
4037 -
4038 - // Set the archive name
4039 - res.attachment((domain.title ? domain.title : 'MeshCentral') + '-Backup-' + new Date().toLocaleDateString().replace('/', '-').replace('/', '-') + '.zip');
4040 -
4041 - // Pipe archive data to the file
4042 - archive.pipe(res);
4043 -
4044 - // Append files from a glob pattern
4045 - archive.directory(obj.parent.datapath, false);
4046 -
4047 - // Finalize the archive (ie we are done appending files but streams have to finish yet)
4048 - archive.finalize();
4049 - }
4050 -
4051 - // Handle a server restore request
4052 - function handleRestoreRequest(req, res) {
4053 - const domain = checkUserIpAddress(req, res);
4054 - if (domain == null) { return; }
4055 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4056 - if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver.restore !== true))) { res.sendStatus(401); return; }
4057 -
4058 - var authUserid = null;
4059 - if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
4060 - const multiparty = require('multiparty');
4061 - const form = new multiparty.Form();
4062 - form.parse(req, function (err, fields, files) {
4063 - // If an authentication cookie is embedded in the form, use that.
4064 - if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
4065 - var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
4066 - if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
4067 - if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
4068 - }
4069 - if (authUserid == null) { res.sendStatus(401); return; }
4070 -
4071 - // Get the user
4072 - const user = obj.users[req.session.userid];
4073 - if ((user == null) || ((user.siteadmin & 4) == 0)) { res.sendStatus(401); return; } // Check if we have server restore rights
4074 -
4075 - res.set('Content-Type', 'text/html');
4076 - res.end('<html><body>Server must be restarted, <a href="' + domain.url + '">click here to login</a>.</body></html>');
4077 - parent.Stop(files.datafile[0].path);
4078 - });
4079 - }
4080 -
4081 - // Handle a request to download a mesh agent
4082 - obj.handleMeshAgentRequest = function (req, res) {
4083 - var domain = getDomain(req, res);
4084 - if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); try { res.sendStatus(404); } catch (ex) { } return; }
4085 -
4086 - // If required, check if this user has rights to do this
4087 - if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true)) && (req.session.userid == null)) { res.sendStatus(401); return; }
4088 -
4089 - if ((req.query.meshinstall != null) && (req.query.id != null)) {
4090 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4091 -
4092 - // Send meshagent with included self installer for a specific platform back
4093 - // Start by getting the .msh for this request
4094 - var meshsettings = getMshFromRequest(req, res, domain);
4095 - if (meshsettings == null) { res.sendStatus(401); return; }
4096 -
4097 - // Get the interactive install script, this only works for non-Windows agents
4098 - var agentid = parseInt(req.query.meshinstall);
4099 - var argentInfo = obj.parent.meshAgentBinaries[agentid];
4100 - var scriptInfo = obj.parent.meshAgentInstallScripts[6];
4101 - if ((argentInfo == null) || (scriptInfo == null) || (argentInfo.platform == 'win32')) { res.sendStatus(404); return; }
4102 -
4103 - // Change the .msh file into JSON format and merge it into the install script
4104 - var tokens, msh = {}, meshsettingslines = meshsettings.split('\r').join('').split('\n');
4105 - for (var i in meshsettingslines) { tokens = meshsettingslines[i].split('='); if (tokens.length == 2) { msh[tokens[0]] = tokens[1]; } }
4106 - var js = scriptInfo.data.replace('var msh = {};', 'var msh = ' + JSON.stringify(msh) + ';');
4107 -
4108 - // Get the agent filename
4109 - var meshagentFilename = 'meshagent';
4110 - if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) { meshagentFilename = domain.agentcustomization.filename; }
4111 -
4112 - setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename, null, 'meshagent');
4113 - res.statusCode = 200;
4114 - obj.parent.exeHandler.streamExeWithJavaScript({ platform: argentInfo.platform, sourceFileName: argentInfo.path, destinationStream: res, js: Buffer.from(js, 'utf8'), peinfo: argentInfo.pe });
4115 - } else if (req.query.id != null) {
4116 - // Send a specific mesh agent back
4117 - var argentInfo = obj.parent.meshAgentBinaries[req.query.id];
4118 - if (argentInfo == null) { res.sendStatus(404); return; }
4119 -
4120 - // Download PDB debug files, only allowed for administrator or accounts with agent dump access
4121 - if (req.query.pdb == 1) {
4122 - if ((req.session == null) || (req.session.userid == null)) { res.sendStatus(404); return; }
4123 - var user = obj.users[req.session.userid];
4124 - if (user == null) { res.sendStatus(404); return; }
4125 - if ((user != null) && ((user.siteadmin == 0xFFFFFFFF) || ((Array.isArray(obj.parent.config.settings.agentcoredumpusers)) && (obj.parent.config.settings.agentcoredumpusers.indexOf(user._id) >= 0)))) {
4126 - if (argentInfo.id == 3) { setContentDispositionHeader(res, 'application/octet-stream', 'MeshService.pdb', null, 'MeshService.pdb'); res.sendFile(argentInfo.path.split('MeshService-signed.exe').join('MeshService.pdb')); return; }
4127 - if (argentInfo.id == 4) { setContentDispositionHeader(res, 'application/octet-stream', 'MeshService64.pdb', null, 'MeshService64.pdb'); res.sendFile(argentInfo.path.split('MeshService64-signed.exe').join('MeshService64.pdb')); return; }
4128 - }
4129 - res.sendStatus(404); return;
4130 - }
4131 -
4132 - if ((req.query.meshid == null) || (argentInfo.platform != 'win32')) {
4133 - // Get the agent filename
4134 - var meshagentFilename = argentInfo.rname;
4135 - if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) { meshagentFilename = domain.agentcustomization.filename; }
4136 - setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename, null, 'meshagent');
4137 - if (argentInfo.data == null) { res.sendFile(argentInfo.path); } else { res.end(argentInfo.data); }
4138 - } else {
4139 - // Check if the meshid is a time limited, encrypted cookie
4140 - var meshcookie = obj.parent.decodeCookie(req.query.meshid, obj.parent.invitationLinkEncryptionKey);
4141 - if ((meshcookie != null) && (meshcookie.m != null)) { req.query.meshid = meshcookie.m; }
4142 -
4143 - // We are going to embed the .msh file into the Windows executable (signed or not).
4144 - // First, fetch the mesh object to build the .msh file
4145 - var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.meshid];
4146 - if (mesh == null) { res.sendStatus(401); return; }
4147 -
4148 - // If required, check if this user has rights to do this
4149 - if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true))) {
4150 - if ((domain.id != mesh.domain) || ((obj.GetMeshRights(req.session.userid, mesh) & 1) == 0)) { res.sendStatus(401); return; }
4151 - }
4152 -
4153 - var meshidhex = Buffer.from(req.query.meshid.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4154 - var serveridhex = Buffer.from(obj.agentCertificateHashBase64.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4155 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port if specified
4156 - if (obj.args.agentport != null) { httpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
4157 - if (obj.args.agentaliasport != null) { httpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
4158 -
4159 - // Prepare a mesh agent file name using the device group name.
4160 - var meshfilename = mesh.name
4161 - meshfilename = meshfilename.split('\\').join('').split('/').join('').split(':').join('').split('*').join('').split('?').join('').split('"').join('').split('<').join('').split('>').join('').split('|').join('').split(' ').join('').split('\'').join('');
4162 - if (argentInfo.rname.endsWith('.exe')) { meshfilename = argentInfo.rname.substring(0, argentInfo.rname.length - 4) + '-' + meshfilename + '.exe'; } else { meshfilename = argentInfo.rname + '-' + meshfilename; }
4163 -
4164 - // Customize the mesh agent file name
4165 - if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) {
4166 - meshfilename = meshfilename.split('meshagent').join(domain.agentcustomization.filename);
4167 - meshfilename = meshfilename.split('MeshAgent').join(domain.agentcustomization.filename);
4168 - }
4169 -
4170 - // Get the agent connection server name
4171 - var serverName = obj.getWebServerName(domain);
4172 - if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
4173 -
4174 - // Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
4175 - var xdomain = (domain.dns == null) ? domain.id : '';
4176 - if (xdomain != '') xdomain += '/';
4177 - var meshsettings = '\r\nMeshName=' + mesh.name + '\r\nMeshType=' + mesh.mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n';
4178 - if (obj.args.lanonly != true) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n'; } else {
4179 - meshsettings += 'MeshServer=local\r\n';
4180 - 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'; }
4181 - }
4182 - if ((req.query.tag != null) && (typeof req.query.tag == 'string') && (obj.common.isAlphaNumeric(req.query.tag) == true)) { meshsettings += 'Tag=' + req.query.tag + '\r\n'; }
4183 - if ((req.query.installflags != null) && (req.query.installflags != 0) && (parseInt(req.query.installflags) == req.query.installflags)) { meshsettings += 'InstallFlags=' + parseInt(req.query.installflags) + '\r\n'; }
4184 - if ((domain.agentnoproxy === true) || (obj.args.lanonly == true)) { meshsettings += 'ignoreProxyFile=1\r\n'; }
4185 - if (obj.args.agentconfig) { for (var i in obj.args.agentconfig) { meshsettings += obj.args.agentconfig[i] + '\r\n'; } }
4186 - if (domain.agentconfig) { for (var i in domain.agentconfig) { meshsettings += domain.agentconfig[i] + '\r\n'; } }
4187 - if (domain.agentcustomization != null) { // Add agent customization
4188 - if (domain.agentcustomization.displayname != null) { meshsettings += 'displayName=' + domain.agentcustomization.displayname + '\r\n'; }
4189 - if (domain.agentcustomization.description != null) { meshsettings += 'description=' + domain.agentcustomization.description + '\r\n'; }
4190 - if (domain.agentcustomization.companyname != null) { meshsettings += 'companyName=' + domain.agentcustomization.companyname + '\r\n'; }
4191 - if (domain.agentcustomization.servicename != null) { meshsettings += 'meshServiceName=' + domain.agentcustomization.servicename + '\r\n'; }
4192 - if (domain.agentcustomization.filename != null) { meshsettings += 'fileName=' + domain.agentcustomization.filename + '\r\n'; }
4193 - }
4194 - if (parent.agentTranslations != null) { meshsettings += 'translation=' + parent.agentTranslations + '\r\n'; }
4195 - setContentDispositionHeader(res, 'application/octet-stream', meshfilename, null, argentInfo.rname);
4196 - obj.parent.exeHandler.streamExeWithMeshPolicy({ platform: 'win32', sourceFileName: obj.parent.meshAgentBinaries[req.query.id].path, destinationStream: res, msh: meshsettings, peinfo: obj.parent.meshAgentBinaries[req.query.id].pe });
4197 - }
4198 - } else if (req.query.script != null) {
4199 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4200 -
4201 - // Send a specific mesh install script back
4202 - var scriptInfo = obj.parent.meshAgentInstallScripts[req.query.script];
4203 - if (scriptInfo == null) { res.sendStatus(404); return; }
4204 - setContentDispositionHeader(res, 'application/octet-stream', scriptInfo.rname, null, 'script');
4205 - var data = scriptInfo.data;
4206 - var cmdoptions = { wgetoptionshttp: '', wgetoptionshttps: '', curloptionshttp: '-L ', curloptionshttps: '-L ' }
4207 - if (obj.isTrustedCert(domain) != true) {
4208 - cmdoptions.wgetoptionshttps += '--no-check-certificate ';
4209 - cmdoptions.curloptionshttps += '-k ';
4210 - }
4211 - if (domain.agentnoproxy === true) {
4212 - cmdoptions.wgetoptionshttp += '--no-proxy ';
4213 - cmdoptions.wgetoptionshttps += '--no-proxy ';
4214 - cmdoptions.curloptionshttp += '--noproxy \'*\' ';
4215 - cmdoptions.curloptionshttps += '--noproxy \'*\' ';
4216 - }
4217 - for (var i in cmdoptions) { data = data.split('{{{' + i + '}}}').join(cmdoptions[i]); }
4218 - res.send(data);
4219 - } else if (req.query.meshcmd != null) {
4220 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4221 -
4222 - // Send meshcmd for a specific platform back
4223 - var agentid = parseInt(req.query.meshcmd);
4224 - // If the agentid is 3 or 4, check if we have a signed MeshCmd.exe
4225 - if ((agentid == 3)) { // Signed Windows MeshCmd.exe x86
4226 - var stats = null, meshCmdPath = obj.path.join(__dirname, 'agents', 'MeshCmd-signed.exe');
4227 - try { stats = obj.fs.statSync(meshCmdPath); } catch (e) { }
4228 - if ((stats != null)) {
4229 - setContentDispositionHeader(res, 'application/octet-stream', 'meshcmd' + ((req.query.meshcmd <= 3) ? '.exe' : ''), null, 'meshcmd');
4230 - res.sendFile(meshCmdPath); return;
4231 - }
4232 - } else if ((agentid == 4)) { // Signed Windows MeshCmd64.exe x64
4233 - var stats = null, meshCmd64Path = obj.path.join(__dirname, 'agents', 'MeshCmd64-signed.exe');
4234 - try { stats = obj.fs.statSync(meshCmd64Path); } catch (e) { }
4235 - if ((stats != null)) {
4236 - setContentDispositionHeader(res, 'application/octet-stream', 'meshcmd' + ((req.query.meshcmd <= 4) ? '.exe' : ''), null, 'meshcmd');
4237 - res.sendFile(meshCmd64Path); return;
4238 - }
4239 - }
4240 - // No signed agents, we are going to merge a new MeshCmd.
4241 - if ((agentid < 10000) && (obj.parent.meshAgentBinaries[agentid + 10000] != null)) { agentid += 10000; } // Avoid merging javascript to a signed mesh agent.
4242 - var argentInfo = obj.parent.meshAgentBinaries[agentid];
4243 - if ((argentInfo == null) || (obj.parent.defaultMeshCmd == null)) { res.sendStatus(404); return; }
4244 - setContentDispositionHeader(res, 'application/octet-stream', 'meshcmd' + ((req.query.meshcmd <= 4) ? '.exe' : ''), null, 'meshcmd');
4245 - res.statusCode = 200;
4246 - if (argentInfo.signedMeshCmdPath != null) {
4247 - // If we have a pre-signed MeshCmd, send that.
4248 - res.sendFile(argentInfo.signedMeshCmdPath);
4249 - } else {
4250 - // Merge JavaScript to a unsigned agent and send that.
4251 - obj.parent.exeHandler.streamExeWithJavaScript({ platform: argentInfo.platform, sourceFileName: argentInfo.path, destinationStream: res, js: Buffer.from(obj.parent.defaultMeshCmd, 'utf8'), peinfo: argentInfo.pe });
4252 - }
4253 - } else if (req.query.meshaction != null) {
4254 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4255 - var user = obj.users[req.session.userid];
4256 - if (user == null) {
4257 - // Check if we have an authentication cookie
4258 - var c = obj.parent.decodeCookie(req.query.auth, obj.parent.loginCookieEncryptionKey);
4259 - if (c == null) { res.sendStatus(404); return; }
4260 -
4261 - // Download tools using a cookie
4262 - if (c.download == req.query.meshaction) {
4263 - if (req.query.meshaction == 'winrouter') {
4264 - var p = obj.path.join(__dirname, 'agents', 'MeshCentralRouter.exe');
4265 - if (obj.fs.existsSync(p)) {
4266 - setContentDispositionHeader(res, 'application/octet-stream', 'MeshCentralRouter.exe', null, 'MeshCentralRouter.exe');
4267 - try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
4268 - } else { res.sendStatus(404); }
4269 - } else if (req.query.meshaction == 'winassistant') {
4270 - var p = obj.path.join(__dirname, 'agents', 'MeshCentralAssistant.exe');
4271 - if (obj.fs.existsSync(p)) {
4272 - setContentDispositionHeader(res, 'application/octet-stream', 'MeshCentralAssistant.exe', null, 'MeshCentralAssistant.exe');
4273 - try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
4274 - } else { res.sendStatus(404); }
4275 - } else if (req.query.meshaction == 'macrouter') {
4276 - var p = obj.path.join(__dirname, 'agents', 'MeshCentralRouter.dmg');
4277 - if (obj.fs.existsSync(p)) {
4278 - setContentDispositionHeader(res, 'application/octet-stream', 'MeshCentralRouter.dmg', null, 'MeshCentralRouter.dmg');
4279 - try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
4280 - } else { res.sendStatus(404); }
4281 - }
4282 - return;
4283 - }
4284 -
4285 - // Check if the cookie authenticates a user
4286 - if (c.userid == null) { res.sendStatus(404); return; }
4287 - user = obj.users[c.userid];
4288 - if (user == null) { res.sendStatus(404); return; }
4289 - }
4290 - if ((req.query.meshaction == 'route') && (req.query.nodeid != null)) {
4291 - obj.db.Get(req.query.nodeid, function (err, nodes) {
4292 - if (nodes.length != 1) { res.sendStatus(401); return; }
4293 - var node = nodes[0];
4294 -
4295 - // Create the meshaction.txt file for meshcmd.exe
4296 - var meshaction = {
4297 - action: req.query.meshaction,
4298 - localPort: 1234,
4299 - remoteName: node.name,
4300 - remoteNodeId: node._id,
4301 - remoteTarget: null,
4302 - remotePort: 3389,
4303 - username: '',
4304 - password: '',
4305 - serverId: obj.agentCertificateHashHex.toUpperCase(), // SHA384 of server HTTPS public key
4306 - serverHttpsHash: Buffer.from(obj.webCertificateHashs[domain.id], 'binary').toString('hex').toUpperCase(), // SHA384 of server HTTPS certificate
4307 - debugLevel: 0
4308 - };
4309 - if (user != null) { meshaction.username = user.name; }
4310 - if (req.query.key != null) { meshaction.loginKey = req.query.key; }
4311 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
4312 - if (obj.args.lanonly != true) { meshaction.serverUrl = 'wss://' + obj.getWebServerName(domain) + ':' + httpsPort + '/' + ((domain.id == '') ? '' : ('/' + domain.id)) + 'meshrelay.ashx'; }
4313 -
4314 - setContentDispositionHeader(res, 'application/octet-stream', 'meshaction.txt', null, 'meshaction.txt');
4315 - res.send(JSON.stringify(meshaction, null, ' '));
4316 - });
4317 - } else if (req.query.meshaction == 'generic') {
4318 - var meshaction = {
4319 - username: user.name,
4320 - password: '',
4321 - serverId: obj.agentCertificateHashHex.toUpperCase(), // SHA384 of server HTTPS public key
4322 - serverHttpsHash: Buffer.from(obj.webCertificateHashs[domain.id], 'binary').toString('hex').toUpperCase(), // SHA384 of server HTTPS certificate
4323 - debugLevel: 0
4324 - };
4325 - if (user != null) { meshaction.username = user.name; }
4326 - if (req.query.key != null) { meshaction.loginKey = req.query.key; }
4327 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
4328 - if (obj.args.lanonly != true) { meshaction.serverUrl = 'wss://' + obj.getWebServerName(domain) + ':' + httpsPort + '/' + ((domain.id == '') ? '' : ('/' + domain.id)) + 'meshrelay.ashx'; }
4329 - setContentDispositionHeader(res, 'application/octet-stream', 'meshaction.txt', null, 'meshaction.txt');
4330 - res.send(JSON.stringify(meshaction, null, ' '));
4331 - } else if (req.query.meshaction == 'winrouter') {
4332 - console.log('t2');
4333 - var p = obj.path.join(__dirname, 'agents', 'MeshCentralRouter.exe');
4334 - if (obj.fs.existsSync(p)) {
4335 - setContentDispositionHeader(res, 'application/octet-stream', 'MeshCentralRouter.exe', null, 'MeshCentralRouter.exe');
4336 - try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
4337 - } else { res.sendStatus(404); }
4338 - } else if (req.query.meshaction == 'winassistant') {
4339 - var p = obj.path.join(__dirname, 'agents', 'MeshCentralAssistant.exe');
4340 - if (obj.fs.existsSync(p)) {
4341 - setContentDispositionHeader(res, 'application/octet-stream', 'MeshCentralAssistant.exe', null, 'MeshCentralAssistant.exe');
4342 - try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
4343 - } else { res.sendStatus(404); }
4344 - } else if (req.query.meshaction == 'macrouter') {
4345 - var p = obj.path.join(__dirname, 'agents', 'MeshCentralRouter.dmg');
4346 - if (obj.fs.existsSync(p)) {
4347 - setContentDispositionHeader(res, 'application/octet-stream', 'MeshCentralRouter.dmg', null, 'MeshCentralRouter.dmg');
4348 - try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
4349 - } else { res.sendStatus(404); }
4350 - } else {
4351 - res.sendStatus(401);
4352 - }
4353 - } else {
4354 - domain = checkUserIpAddress(req, res); // Recheck the domain to apply user IP filtering.
4355 - if (domain == null) return;
4356 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4357 - if ((req.session == null) || (req.session.userid == null)) { res.sendStatus(404); return; }
4358 - var user = null, coreDumpsAllowed = false;
4359 - if (typeof req.session.userid == 'string') { user = obj.users[req.session.userid]; }
4360 - if (user == null) { res.sendStatus(404); return; }
4361 -
4362 - // Check if this user has access to agent core dumps
4363 - if ((obj.parent.config.settings.agentcoredump === true) && ((user.siteadmin == 0xFFFFFFFF) || ((Array.isArray(obj.parent.config.settings.agentcoredumpusers)) && (obj.parent.config.settings.agentcoredumpusers.indexOf(user._id) >= 0)))) {
4364 - coreDumpsAllowed = true;
4365 -
4366 - if ((req.query.dldump != null) && obj.common.IsFilenameValid(req.query.dldump)) {
4367 - // Download a dump file
4368 - var dumpFile = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps', req.query.dldump);
4369 - if (obj.fs.existsSync(dumpFile)) {
4370 - setContentDispositionHeader(res, 'application/octet-stream', req.query.dldump, null, 'file.bin');
4371 - res.sendFile(dumpFile); return;
4372 - } else {
4373 - res.sendStatus(404); return;
4374 - }
4375 - }
4376 -
4377 - if ((req.query.deldump != null) && obj.common.IsFilenameValid(req.query.deldump)) {
4378 - // Delete a dump file
4379 - try { obj.fs.unlinkSync(obj.path.join(parent.datapath, '..', 'meshcentral-coredumps', req.query.deldump)); } catch (ex) { console.log(ex); }
4380 - }
4381 -
4382 - if ((req.query.dumps != null) || (req.query.deldump != null)) {
4383 - // Send list of agent core dumps
4384 - var response = '<html><head><title>Mesh Agents Core Dumps</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body style=overflow:auto><table>';
4385 - response += '<tr style="background-color:lightgray"><th>ID</th><th>Upload Date</th><th>Description</th><th>Current</th><th>Dump</th><th>Size</th><th>Agent</th><th>Agent SHA384</th><th>NodeID</th><th></th></tr>';
4386 -
4387 - var coreDumpPath = obj.path.join(parent.datapath, '..', 'meshcentral-coredumps');
4388 - if (obj.fs.existsSync(coreDumpPath)) {
4389 - var files = obj.fs.readdirSync(coreDumpPath);
4390 - var coredumps = [];
4391 - for (var i in files) {
4392 - var file = files[i];
4393 - if (file.endsWith('.dmp')) {
4394 - var fileSplit = file.substring(0, file.length - 4).split('-');
4395 - if (fileSplit.length == 3) {
4396 - var agentid = parseInt(fileSplit[0]);
4397 - if ((isNaN(agentid) == false) && (obj.parent.meshAgentBinaries[agentid] != null)) {
4398 - var agentinfo = obj.parent.meshAgentBinaries[agentid];
4399 - var filestats = obj.fs.statSync(obj.path.join(parent.datapath, '..', 'meshcentral-coredumps', file));
4400 - coredumps.push({
4401 - fileSplit: fileSplit,
4402 - agentinfo: agentinfo,
4403 - filestats: filestats,
4404 - currentAgent: agentinfo.hashhex.startsWith(fileSplit[1].toLowerCase()),
4405 - downloadUrl: req.originalUrl.split('?')[0] + '?dldump=' + file + (req.query.key ? ('&key=' + req.query.key) : ''),
4406 - deleteUrl: req.originalUrl.split('?')[0] + '?deldump=' + file + (req.query.key ? ('&key=' + req.query.key) : ''),
4407 - agentUrl: req.originalUrl.split('?')[0] + '?id=' + agentinfo.id + (req.query.key ? ('&key=' + req.query.key) : ''),
4408 - time: new Date(filestats.ctime)
4409 - });
4410 - }
4411 - }
4412 - }
4413 - }
4414 - coredumps.sort(function (a, b) { if (a.time > b.time) return -1; if (a.time < b.time) return 1; return 0; });
4415 - for (var i in coredumps) {
4416 - var d = coredumps[i];
4417 - response += '<tr><td>' + d.agentinfo.id + '</td><td>' + d.time.toDateString().split(' ').join('&nbsp;') + '</td><td>' + d.agentinfo.desc.split(' ').join('&nbsp;') + '</td>';
4418 - response += '<td style=text-align:center>' + d.currentAgent + '</td><td><a download href="' + d.downloadUrl + '">Download</a></td><td style=text-align:right>' + d.filestats.size + '</td>';
4419 - if (d.currentAgent) { response += '<td><a download href="' + d.agentUrl + '">Download</a></td>'; } else { response += '<td></td>'; }
4420 - response += '<td>' + d.fileSplit[1].toLowerCase() + '</td><td>' + d.fileSplit[2] + '</td><td><a href="' + d.deleteUrl + '">Delete</a></td></tr>';
4421 - }
4422 - }
4423 - response += '</table><a href="' + req.originalUrl.split('?')[0] + (req.query.key ? ('?key=' + req.query.key) : '') + '">Mesh Agents</a></body></html>';
4424 - res.send(response);
4425 - return;
4426 - }
4427 - }
4428 -
4429 - if (req.query.cores != null) {
4430 - // Send list of agent cores
4431 - var response = '<html><head><title>Mesh Agents Cores</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body style=overflow:auto><table>';
4432 - response += '<tr style="background-color:lightgray"><th>Name</th><th>Size</th><th>Comp</th><th>Decompressed Hash SHA384</th></tr>';
4433 - for (var i in parent.defaultMeshCores) {
4434 - response += '<tr><td>' + i.split(' ').join('&nbsp;') + '</td><td style="text-align:right"><a download href="/meshagents?dlcore=' + i + '">' + parent.defaultMeshCores[i].length + (req.query.key ? ('?key=' + req.query.key) : '') + '</a></td><td style="text-align:right"><a download href="/meshagents?dlccore=' + i + (req.query.key ? ('?key=' + req.query.key) : '') + '">' + parent.defaultMeshCoresDeflate[i].length + '</a></td><td>' + Buffer.from(parent.defaultMeshCoresHash[i], 'binary').toString('hex') + '</td></tr>';
4435 - }
4436 - response += '</table><a href="' + req.originalUrl.split('?')[0] + (req.query.key ? ('?key=' + req.query.key) : '') + '">Mesh Agents</a></body></html>';
4437 - res.send(response);
4438 - return;
4439 - }
4440 -
4441 - if (req.query.dlcore != null) {
4442 - // Download mesh core
4443 - var bin = parent.defaultMeshCores[req.query.dlcore];
4444 - if (bin == null) { res.sendStatus(404); return; }
4445 - setContentDispositionHeader(res, 'application/octet-stream', req.query.dlcore + '.js', null, 'meshcore.js');
4446 - res.send(bin);
4447 - return;
4448 - }
4449 -
4450 - if (req.query.dlccore != null) {
4451 - // Download compressed mesh core
4452 - var bin = parent.defaultMeshCoresDeflate[req.query.dlccore];
4453 - if (bin == null) { res.sendStatus(404); return; }
4454 - setContentDispositionHeader(res, 'application/octet-stream', req.query.dlccore + '.js.deflate', null, 'meshcore.js.deflate');
4455 - res.send(bin);
4456 - return;
4457 - }
4458 -
4459 - // Send a list of available mesh agents
4460 - var response = '<html><head><title>Mesh Agents</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body style=overflow:auto><table>';
4461 - response += '<tr style="background-color:lightgray"><th>ID</th><th>Description</th><th>Link</th><th>Size</th><th>SHA384</th><th>MeshCmd</th></tr>';
4462 - var originalUrl = req.originalUrl.split('?')[0];
4463 - for (var agentid in obj.parent.meshAgentBinaries) {
4464 - if ((agentid >= 10000) && (agentid != 10005)) continue;
4465 - var agentinfo = obj.parent.meshAgentBinaries[agentid];
4466 - response += '<tr><td>' + agentinfo.id + '</td><td>' + agentinfo.desc.split(' ').join('&nbsp;') + '</td>';
4467 - response += '<td><a download href="' + originalUrl + '?id=' + agentinfo.id + (req.query.key ? ('&key=' + req.query.key) : '') + '">' + agentinfo.rname + '</a>';
4468 - if ((user.siteadmin == 0xFFFFFFFF) || ((Array.isArray(obj.parent.config.settings.agentcoredumpusers)) && (obj.parent.config.settings.agentcoredumpusers.indexOf(user._id) >= 0))) {
4469 - if ((agentid == 3) || (agentid == 4)) { response += ', <a download href="' + originalUrl + '?id=' + agentinfo.id + '&pdb=1' + (req.query.key ? ('&key=' + req.query.key) : '') + '">PDB</a>'; }
4470 - }
4471 - response += '</td>';
4472 - response += '<td>' + agentinfo.size + '</td><td>' + agentinfo.hashhex + '</td>';
4473 - response += '<td><a download href="' + originalUrl + '?meshcmd=' + agentinfo.id + (req.query.key ? ('&key=' + req.query.key) : '') + '">' + agentinfo.rname.replace('agent', 'cmd') + '</a></td></tr>';
4474 - }
4475 - response += '</table>';
4476 - response += '<a href="' + originalUrl + '?cores=1' + (req.query.key ? ('&key=' + req.query.key) : '') + '">MeshCores</a> ';
4477 - if (coreDumpsAllowed) { response += '<a href="' + originalUrl + '?dumps=1' + (req.query.key ? ('&key=' + req.query.key) : '') + '">MeshAgent Crash Dumps</a>'; }
4478 - response += '</body></html>';
4479 - res.send(response);
4480 - }
4481 - };
4482 -
4483 - // Get the web server hostname. This may change if using a domain with a DNS name.
4484 - obj.getWebServerName = function (domain) {
4485 - if (domain.dns != null) return domain.dns;
4486 - return obj.certificates.CommonName;
4487 - }
4488 -
4489 - // Create a OSX mesh agent installer
4490 - obj.handleMeshOsxAgentRequest = function (req, res) {
4491 - const domain = getDomain(req, res);
4492 - if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); try { res.sendStatus(404); } catch (ex) { } return; }
4493 - if (req.query.id == null) { res.sendStatus(404); return; }
4494 -
4495 - // If required, check if this user has rights to do this
4496 - if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true)) && (req.session.userid == null)) { res.sendStatus(401); return; }
4497 -
4498 - // Send a specific mesh agent back
4499 - var argentInfo = obj.parent.meshAgentBinaries[req.query.id];
4500 - if ((argentInfo == null) || (req.query.meshid == null)) { res.sendStatus(404); return; }
4501 -
4502 - // Check if the meshid is a time limited, encrypted cookie
4503 - var meshcookie = obj.parent.decodeCookie(req.query.meshid, obj.parent.invitationLinkEncryptionKey);
4504 - if ((meshcookie != null) && (meshcookie.m != null)) { req.query.meshid = meshcookie.m; }
4505 -
4506 - // We are going to embed the .msh file into the Windows executable (signed or not).
4507 - // First, fetch the mesh object to build the .msh file
4508 - var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.meshid];
4509 - if (mesh == null) { res.sendStatus(401); return; }
4510 -
4511 - // If required, check if this user has rights to do this
4512 - if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true))) {
4513 - if ((domain.id != mesh.domain) || ((obj.GetMeshRights(req.session.userid, mesh) & 1) == 0)) { res.sendStatus(401); return; }
4514 - }
4515 -
4516 - var meshidhex = Buffer.from(req.query.meshid.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4517 - var serveridhex = Buffer.from(obj.agentCertificateHashBase64.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4518 -
4519 - // Get the agent connection server name
4520 - var serverName = obj.getWebServerName(domain);
4521 - if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
4522 -
4523 - // Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
4524 - var xdomain = (domain.dns == null) ? domain.id : '';
4525 - if (xdomain != '') xdomain += '/';
4526 - var meshsettings = '\r\nMeshName=' + mesh.name + '\r\nMeshType=' + mesh.mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n';
4527 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
4528 - if (obj.args.agentport != null) { httpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
4529 - if (obj.args.agentaliasport != null) { httpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
4530 - if (obj.args.lanonly != true) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n'; } else {
4531 - meshsettings += 'MeshServer=local\r\n';
4532 - 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'; }
4533 - }
4534 - if ((req.query.tag != null) && (typeof req.query.tag == 'string') && (obj.common.isAlphaNumeric(req.query.tag) == true)) { meshsettings += 'Tag=' + req.query.tag + '\r\n'; }
4535 - if ((req.query.installflags != null) && (req.query.installflags != 0) && (parseInt(req.query.installflags) == req.query.installflags)) { meshsettings += 'InstallFlags=' + parseInt(req.query.installflags) + '\r\n'; }
4536 - if ((domain.agentnoproxy === true) || (obj.args.lanonly == true)) { meshsettings += 'ignoreProxyFile=1\r\n'; }
4537 - if (obj.args.agentconfig) { for (var i in obj.args.agentconfig) { meshsettings += obj.args.agentconfig[i] + '\r\n'; } }
4538 - if (domain.agentconfig) { for (var i in domain.agentconfig) { meshsettings += domain.agentconfig[i] + '\r\n'; } }
4539 - if (domain.agentcustomization != null) { // Add agent customization
4540 - if (domain.agentcustomization.displayname != null) { meshsettings += 'displayName=' + domain.agentcustomization.displayname + '\r\n'; }
4541 - if (domain.agentcustomization.description != null) { meshsettings += 'description=' + domain.agentcustomization.description + '\r\n'; }
4542 - if (domain.agentcustomization.companyname != null) { meshsettings += 'companyName=' + domain.agentcustomization.companyname + '\r\n'; }
4543 - if (domain.agentcustomization.servicename != null) { meshsettings += 'meshServiceName=' + domain.agentcustomization.servicename + '\r\n'; }
4544 - if (domain.agentcustomization.filename != null) { meshsettings += 'fileName=' + domain.agentcustomization.filename + '\r\n'; }
4545 - }
4546 - if (parent.agentTranslations != null) { meshsettings += 'translation=' + parent.agentTranslations + '\r\n'; }
4547 -
4548 - // Setup the response output
4549 - var archive = require('archiver')('zip', { level: 5 }); // Sets the compression method.
4550 - archive.on('error', function (err) { throw err; });
4551 -
4552 - // Set the agent download including the mesh name.
4553 - setContentDispositionHeader(res, 'application/octet-stream', 'MeshAgent-' + mesh.name + '.zip', null, 'MeshAgent.zip');
4554 - archive.pipe(res);
4555 -
4556 - // Opens the "MeshAgentOSXPackager.zip"
4557 - var yauzl = require('yauzl');
4558 - yauzl.open(obj.path.join(__dirname, 'agents', 'MeshAgentOSXPackager.zip'), { lazyEntries: true }, function (err, zipfile) {
4559 - if (err) { res.sendStatus(500); return; }
4560 - zipfile.readEntry();
4561 - zipfile.on('entry', function (entry) {
4562 - if (/\/$/.test(entry.fileName)) {
4563 - // Skip all folder entries
4564 - zipfile.readEntry();
4565 - } else {
4566 - if (entry.fileName == 'MeshAgent.mpkg/Contents/distribution.dist') {
4567 - // This is a special file entry, we need to fix it.
4568 - zipfile.openReadStream(entry, function (err, readStream) {
4569 - readStream.on('data', function (data) { if (readStream.xxdata) { readStream.xxdata += data; } else { readStream.xxdata = data; } });
4570 - readStream.on('end', function () {
4571 - var meshname = mesh.name.split(']').join('').split('[').join(''); // We can't have ']]' in the string since it will terminate the CDATA.
4572 - var welcomemsg = 'Welcome to the MeshCentral agent for MacOS\n\nThis installer will install the mesh agent for "' + meshname + '" and allow the administrator to remotely monitor and control this computer over the internet. For more information, go to https://www.meshcommander.com/meshcentral2.\n\nThis software is provided under Apache 2.0 license.\n';
4573 - var installsize = Math.floor((argentInfo.size + meshsettings.length) / 1024);
4574 - archive.append(readStream.xxdata.toString().split('###WELCOMEMSG###').join(welcomemsg).split('###INSTALLSIZE###').join(installsize), { name: entry.fileName });
4575 - zipfile.readEntry();
4576 - });
4577 - });
4578 - } else {
4579 - // Normal file entry
4580 - zipfile.openReadStream(entry, function (err, readStream) {
4581 - if (err) { throw err; }
4582 - var options = { name: entry.fileName };
4583 - if (entry.fileName.endsWith('postflight') || entry.fileName.endsWith('Uninstall.command')) { options.mode = 493; }
4584 - archive.append(readStream, options);
4585 - readStream.on('end', function () { zipfile.readEntry(); });
4586 - });
4587 - }
4588 - }
4589 - });
4590 - zipfile.on('end', function () {
4591 - archive.file(argentInfo.path, { name: 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64.bin' });
4592 - archive.append(meshsettings, { name: 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64.msh' });
4593 - archive.finalize();
4594 - });
4595 - });
4596 - }
4597 -
4598 - // Return a .msh file from a given request, id is the device group identifier or encrypted cookie with the identifier.
4599 - function getMshFromRequest(req, res, domain) {
4600 - // If required, check if this user has rights to do this
4601 - if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true)) && (req.session.userid == null)) { return null; }
4602 -
4603 - // Check if the meshid is a time limited, encrypted cookie
4604 - var meshcookie = obj.parent.decodeCookie(req.query.id, obj.parent.invitationLinkEncryptionKey);
4605 - if ((meshcookie != null) && (meshcookie.m != null)) { req.query.id = meshcookie.m; }
4606 -
4607 - // Fetch the mesh object
4608 - var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.id];
4609 - if (mesh == null) { return null; }
4610 -
4611 - // If needed, check if this user has rights to do this
4612 - if ((obj.parent.config.settings != null) && ((obj.parent.config.settings.lockagentdownload == true) || (domain.lockagentdownload == true))) {
4613 - if ((domain.id != mesh.domain) || ((obj.GetMeshRights(req.session.userid, mesh) & 1) == 0)) { return null; }
4614 - }
4615 -
4616 - var meshidhex = Buffer.from(req.query.id.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4617 - var serveridhex = Buffer.from(obj.agentCertificateHashBase64.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase();
4618 -
4619 - // Get the agent connection server name
4620 - var serverName = obj.getWebServerName(domain);
4621 - if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
4622 -
4623 - // Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
4624 - var xdomain = (domain.dns == null) ? domain.id : '';
4625 - if (xdomain != '') xdomain += '/';
4626 - var meshsettings = '\r\nMeshName=' + mesh.name + '\r\nMeshType=' + mesh.mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n';
4627 - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
4628 - if (obj.args.agentport != null) { httpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
4629 - if (obj.args.agentaliasport != null) { httpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
4630 - if (obj.args.lanonly != true) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n'; } else {
4631 - meshsettings += 'MeshServer=local\r\n';
4632 - 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'; }
4633 - }
4634 - if ((req.query.tag != null) && (typeof req.query.tag == 'string') && (obj.common.isAlphaNumeric(req.query.tag) == true)) { meshsettings += 'Tag=' + req.query.tag + '\r\n'; }
4635 - if ((req.query.installflags != null) && (req.query.installflags != 0) && (parseInt(req.query.installflags) == req.query.installflags)) { meshsettings += 'InstallFlags=' + parseInt(req.query.installflags) + '\r\n'; }
4636 - if ((domain.agentnoproxy === true) || (obj.args.lanonly == true)) { meshsettings += 'ignoreProxyFile=1\r\n'; }
4637 - if (obj.args.agentconfig) { for (var i in obj.args.agentconfig) { meshsettings += obj.args.agentconfig[i] + '\r\n'; } }
4638 - if (domain.agentconfig) { for (var i in domain.agentconfig) { meshsettings += domain.agentconfig[i] + '\r\n'; } }
4639 - if (domain.agentcustomization != null) { // Add agent customization
4640 - if (domain.agentcustomization.displayname != null) { meshsettings += 'displayName=' + domain.agentcustomization.displayname + '\r\n'; }
4641 - if (domain.agentcustomization.description != null) { meshsettings += 'description=' + domain.agentcustomization.description + '\r\n'; }
4642 - if (domain.agentcustomization.companyname != null) { meshsettings += 'companyName=' + domain.agentcustomization.companyname + '\r\n'; }
4643 - if (domain.agentcustomization.servicename != null) { meshsettings += 'meshServiceName=' + domain.agentcustomization.servicename + '\r\n'; }
4644 - if (domain.agentcustomization.filename != null) { meshsettings += 'fileName=' + domain.agentcustomization.filename + '\r\n'; }
4645 - }
4646 - if (parent.agentTranslations != null) { meshsettings += 'translation=' + parent.agentTranslations + '\r\n'; }
4647 - return meshsettings;
4648 - }
4649 -
4650 - // Handle a request to download a mesh settings
4651 - obj.handleMeshSettingsRequest = function (req, res) {
4652 - const domain = getDomain(req);
4653 - if (domain == null) { return; }
4654 - //if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4655 -
4656 - var meshsettings = getMshFromRequest(req, res, domain);
4657 - if (meshsettings == null) { res.sendStatus(401); return; }
4658 -
4659 - // Get the agent filename
4660 - var meshagentFilename = 'meshagent';
4661 - if ((domain.agentcustomization != null) && (typeof domain.agentcustomization.filename == 'string')) { meshagentFilename = domain.agentcustomization.filename; }
4662 -
4663 - setContentDispositionHeader(res, 'application/octet-stream', meshagentFilename + '.msh', null, 'meshagent.msh');
4664 - res.send(meshsettings);
4665 - };
4666 -
4667 - // Handle a request for power events
4668 - obj.handleDevicePowerEvents = function (req, res) {
4669 - const domain = checkUserIpAddress(req, res);
4670 - if (domain == null) { return; }
4671 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4672 - if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid) || (req.query.id == null) || (typeof req.query.id != 'string')) { res.sendStatus(401); return; }
4673 - var x = req.query.id.split('/');
4674 - var user = obj.users[req.session.userid];
4675 - if ((x.length != 3) || (x[0] != 'node') || (x[1] != domain.id) || (user == null) || (user.links == null)) { res.sendStatus(401); return; }
4676 -
4677 - obj.db.Get(req.query.id, function (err, docs) {
4678 - if (docs.length != 1) {
4679 - res.sendStatus(401);
4680 - } else {
4681 - var node = docs[0];
4682 -
4683 - // Check if we have right to this node
4684 - if (obj.GetNodeRights(user, node.meshid, node._id) == 0) { res.sendStatus(401); return; }
4685 -
4686 - // Get the list of power events and send them
4687 - setContentDispositionHeader(res, 'application/octet-stream', 'powerevents.csv', null, 'powerevents.csv');
4688 - obj.db.getPowerTimeline(node._id, function (err, docs) {
4689 - var xevents = ['Time, State, Previous State'], prevState = 0;
4690 - for (var i in docs) {
4691 - if (docs[i].power != prevState) {
4692 - prevState = docs[i].power;
4693 - if (docs[i].oldPower != null) {
4694 - xevents.push(docs[i].time.toString() + ',' + docs[i].power + ',' + docs[i].oldPower);
4695 - } else {
4696 - xevents.push(docs[i].time.toString() + ',' + docs[i].power);
4697 - }
4698 - }
4699 - }
4700 - res.send(xevents.join('\r\n'));
4701 - });
4702 - }
4703 - });
4704 - }
4705 -
4706 - if (parent.pluginHandler != null) {
4707 - // Handle a plugin admin request
4708 - obj.handlePluginAdminReq = function (req, res) {
4709 - const domain = checkUserIpAddress(req, res);
4710 - if (domain == null) { return; }
4711 - if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4712 - var user = obj.users[req.session.userid];
4713 - if (user == null) { res.sendStatus(401); return; }
4714 -
4715 - parent.pluginHandler.handleAdminReq(req, res, user, obj);
4716 - }
4717 -
4718 - obj.handlePluginAdminPostReq = function (req, res) {
4719 - const domain = checkUserIpAddress(req, res);
4720 - if (domain == null) { return; }
4721 - if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4722 - var user = obj.users[req.session.userid];
4723 - if (user == null) { res.sendStatus(401); return; }
4724 -
4725 - parent.pluginHandler.handleAdminPostReq(req, res, user, obj);
4726 - }
4727 -
4728 - obj.handlePluginJS = function (req, res) {
4729 - const domain = checkUserIpAddress(req, res);
4730 - if (domain == null) { return; }
4731 - if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4732 - var user = obj.users[req.session.userid];
4733 - if (user == null) { res.sendStatus(401); return; }
4734 -
4735 - parent.pluginHandler.refreshJS(req, res);
4736 - }
4737 - }
4738 -
4739 - // Starts the HTTPS server, this should be called after the user/mesh tables are loaded
4740 - function serverStart() {
4741 - // Start the server, only after users and meshes are loaded from the database.
4742 - if (obj.args.tlsoffload) {
4743 - // Setup the HTTP server without TLS
4744 - obj.expressWs = require('express-ws')(obj.app, null, { wsOptions: { perMessageDeflate: (args.wscompression === true) } });
4745 - } else {
4746 - // Setup the HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
4747 - //const tlsOptions = { cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!RSA:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 }; // This does not work with TLS 1.3
4748 - const tlsOptions = { cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true, ciphers: "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 };
4749 - if (obj.tlsSniCredentials != null) { tlsOptions.SNICallback = TlsSniCallback; } // We have multiple web server certificate used depending on the domain name
4750 - obj.tlsServer = require('https').createServer(tlsOptions, obj.app);
4751 - obj.tlsServer.on('secureConnection', function () { /*console.log('tlsServer secureConnection');*/ });
4752 - obj.tlsServer.on('error', function (err) { console.log('tlsServer error', err); });
4753 - //obj.tlsServer.on('tlsClientError', function (err) { console.log('tlsClientError', err); });
4754 - obj.tlsServer.on('newSession', function (id, data, cb) { if (tlsSessionStoreCount > 1000) { tlsSessionStoreCount = 0; tlsSessionStore = {}; } tlsSessionStore[id.toString('hex')] = data; tlsSessionStoreCount++; cb(); });
4755 - obj.tlsServer.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); });
4756 - obj.expressWs = require('express-ws')(obj.app, obj.tlsServer, { wsOptions: { perMessageDeflate: (args.wscompression === true) } });
4757 - }
4758 -
4759 - // Start a second agent-only server if needed
4760 - if (obj.args.agentport) {
4761 - var agentPortTls = true;
4762 - if (obj.args.tlsoffload != null) { agentPortTls = false; }
4763 - if (typeof obj.args.agentporttls == 'boolean') { agentPortTls = obj.args.agentporttls; }
4764 - if (obj.certificates.webdefault == null) { agentPortTls = false; }
4765 -
4766 - if (agentPortTls == false) {
4767 - // Setup the HTTP server without TLS
4768 - obj.expressWsAlt = require('express-ws')(obj.agentapp, null, { wsOptions: { perMessageDeflate: (args.wscompression === true) } });
4769 - } else {
4770 - // Setup the agent HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
4771 - // If TLS is used on the agent port, we always use the default TLS certificate.
4772 - const tlsOptions = { cert: obj.certificates.webdefault.cert, key: obj.certificates.webdefault.key, ca: obj.certificates.webdefault.ca, rejectUnauthorized: true, ciphers: "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 };
4773 - obj.tlsAltServer = require('https').createServer(tlsOptions, obj.agentapp);
4774 - obj.tlsAltServer.on('secureConnection', function () { /*console.log('tlsAltServer secureConnection');*/ });
4775 - obj.tlsAltServer.on('error', function (err) { console.log('tlsAltServer error', err); });
4776 - //obj.tlsAltServer.on('tlsClientError', function (err) { console.log('tlsClientError', err); });
4777 - obj.tlsAltServer.on('newSession', function (id, data, cb) { if (tlsSessionStoreCount > 1000) { tlsSessionStoreCount = 0; tlsSessionStore = {}; } tlsSessionStore[id.toString('hex')] = data; tlsSessionStoreCount++; cb(); });
4778 - obj.tlsAltServer.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); });
4779 - obj.expressWsAlt = require('express-ws')(obj.agentapp, obj.tlsAltServer, { wsOptions: { perMessageDeflate: (args.wscompression === true) } });
4780 - }
4781 - }
4782 -
4783 - // Setup middleware
4784 - obj.app.engine('handlebars', obj.exphbs({ defaultLayout: null })); // defaultLayout: 'main'
4785 - obj.app.set('view engine', 'handlebars');
4786 - if (obj.args.trustedproxy) {
4787 - // Reverse proxy should add the "X-Forwarded-*" headers
4788 - try {
4789 - obj.app.set('trust proxy', obj.args.trustedproxy);
4790 - } catch (ex) {
4791 - // If there is an error, try to resolve the string
4792 - if ((obj.args.trustedproxy.length == 1) && (typeof obj.args.trustedproxy[0] == 'string')) {
4793 - require('dns').lookup(obj.args.trustedproxy[0], function (err, address, family) { if (err == null) { obj.app.set('trust proxy', address); obj.args.trustedproxy = [address]; } });
4794 - }
4795 - }
4796 - }
4797 - else if (typeof obj.args.tlsoffload == 'object') {
4798 - // Reverse proxy should add the "X-Forwarded-*" headers
4799 - try {
4800 - obj.app.set('trust proxy', obj.args.tlsoffload);
4801 - } catch (ex) {
4802 - // If there is an error, try to resolve the string
4803 - if ((Array.isArray(obj.args.tlsoffload)) && (obj.args.tlsoffload.length == 1) && (typeof obj.args.tlsoffload[0] == 'string')) {
4804 - require('dns').lookup(obj.args.tlsoffload[0], function (err, address, family) { if (err == null) { obj.app.set('trust proxy', address); obj.args.tlsoffload = [address]; } });
4805 - }
4806 - }
4807 - }
4808 - obj.app.use(obj.bodyParser.urlencoded({ extended: false }));
4809 - var sessionOptions = {
4810 - name: 'xid', // Recommended security practice to not use the default cookie name
4811 - httpOnly: true,
4812 - keys: [obj.args.sessionkey], // If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances
4813 - secure: (obj.args.tlsoffload == null) // Use this cookie only over TLS (Check this: https://expressjs.com/en/guide/behind-proxies.html)
4814 - }
4815 - if (obj.args.sessionsamesite != null) { sessionOptions.sameSite = obj.args.sessionsamesite; } else { sessionOptions.sameSite = 'strict'; }
4816 - if (obj.args.sessiontime != null) { sessionOptions.maxAge = (obj.args.sessiontime * 60 * 1000); }
4817 - obj.app.use(obj.session(sessionOptions));
4818 -
4819 - // Add HTTP security headers to all responses
4820 - obj.app.use(function (req, res, next) {
4821 - // Useful for debugging reverse proxy issues
4822 - parent.debug('httpheaders', req.method, req.url, req.headers);
4823 -
4824 - // Set the real IP address of the request
4825 - // If a trusted reverse-proxy is sending us the remote IP address, use it.
4826 - var ipex = '0.0.0.0', xforwardedhost = req.headers.host;
4827 - if (typeof req.connection.remoteAddress == 'string') { ipex = (req.connection.remoteAddress.startsWith('::ffff:')) ? req.connection.remoteAddress.substring(7) : req.connection.remoteAddress; }
4828 - if (
4829 - (obj.args.trustedproxy === true) || (obj.args.tlsoffload === true) ||
4830 - ((typeof obj.args.trustedproxy == 'object') && (isIPMatch(ipex, obj.args.trustedproxy))) ||
4831 - ((typeof obj.args.tlsoffload == 'object') && (isIPMatch(ipex, obj.args.tlsoffload)))
4832 - ) {
4833 - // Get client IP
4834 - if (req.headers['cf-connecting-ip']) { // Use CloudFlare IP address if present
4835 - req.clientIp = req.headers['cf-connecting-ip'].split(',')[0].trim();
4836 - } else if (req.headers['x-forwarded-for']) {
4837 - req.clientIp = req.headers['x-forwarded-for'].split(',')[0].trim();
4838 - } else if (req.headers['x-real-ip']) {
4839 - req.clientIp = req.headers['x-real-ip'].split(',')[0].trim();
4840 - } else {
4841 - req.clientIp = ipex;
4842 - }
4843 -
4844 - // If there is a port number, remove it. This will only work for IPv4, but nice for people that have a bad reverse proxy config.
4845 - const clientIpSplit = req.clientIp.split(':');
4846 - if (clientIpSplit.length == 2) { req.clientIp = clientIpSplit[0]; }
4847 -
4848 - // Get server host
4849 - if (req.headers['x-forwarded-host']) { xforwardedhost = req.headers['x-forwarded-host']; }
4850 - } else {
4851 - req.clientIp = ipex;
4852 - }
4853 -
4854 - // Get the domain for this request
4855 - const domain = req.xdomain = getDomain(req);
4856 - parent.debug('webrequest', '(' + req.clientIp + ') ' + req.url);
4857 -
4858 - // Skip the rest is this is an agent connection
4859 - if ((req.url.indexOf('/meshrelay.ashx/.websocket') >= 0) || (req.url.indexOf('/agent.ashx/.websocket') >= 0)) { next(); return; }
4860 -
4861 - // If this domain has configured headers, use them.
4862 - // Example headers: { 'Strict-Transport-Security': 'max-age=360000;includeSubDomains' };
4863 - // { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src http: ws: data: 'self';script-src http: 'unsafe-inline';style-src http: 'unsafe-inline'" };
4864 - if ((domain != null) && (domain.httpheaders != null) && (typeof domain.httpheaders == 'object')) {
4865 - res.set(domain.httpheaders);
4866 - } else {
4867 - // Use default security headers
4868 - const geourl = (domain.geolocation ? ' *.openstreetmap.org' : '');
4869 - var selfurl = ' wss://' + req.headers.host;
4870 - if ((xforwardedhost != null) && (xforwardedhost != req.headers.host)) { selfurl += ' wss://' + xforwardedhost; }
4871 - const extraScriptSrc = (parent.config.settings.extrascriptsrc != null) ? (' ' + parent.config.settings.extrascriptsrc) : '';
4872 - const headers = {
4873 - 'Referrer-Policy': 'no-referrer',
4874 - 'X-XSS-Protection': '1; mode=block',
4875 - 'X-Content-Type-Options': 'nosniff',
4876 - 'Content-Security-Policy': "default-src 'none'; font-src 'self'; script-src 'self' 'unsafe-inline'" + extraScriptSrc + "; connect-src 'self'" + geourl + selfurl + "; img-src 'self'" + geourl + " data:; style-src 'self' 'unsafe-inline'; frame-src 'self' https://*.youtube.com mcrouter:; media-src 'self'; form-action 'self'"
4877 - };
4878 - if ((parent.config.settings.allowframing !== true) && (typeof parent.config.settings.allowframing !== 'string')) { headers['X-Frame-Options'] = 'sameorigin'; }
4879 - res.set(headers);
4880 - }
4881 -
4882 - // Check the session if bound to the external IP address
4883 - if ((req.session.ip != null) && (req.clientIp != null) && (req.session.ip != req.clientIp)) { req.session = {}; }
4884 -
4885 - // Extend the session time by forcing a change to the session every minute.
4886 - if (req.session.userid != null) { req.session.nowInMinutes = Math.floor(Date.now() / 60e3); } else { delete req.session.nowInMinutes; }
4887 -
4888 - // Continue processing the request
4889 - return next();
4890 - });
4891 -
4892 - if (obj.agentapp) {
4893 - // Add HTTP security headers to all responses
4894 - obj.agentapp.use(function (req, res, next) {
4895 - // Set the real IP address of the request
4896 - // If a trusted reverse-proxy is sending us the remote IP address, use it.
4897 - var ipex = '0.0.0.0';
4898 - if (typeof req.connection.remoteAddress == 'string') { ipex = (req.connection.remoteAddress.startsWith('::ffff:')) ? req.connection.remoteAddress.substring(7) : req.connection.remoteAddress; }
4899 - if (
4900 - (obj.args.trustedproxy === true) ||
4901 - ((typeof obj.args.trustedproxy == 'object') && (obj.args.trustedproxy.indexOf(ipex) >= 0)) ||
4902 - ((typeof obj.args.tlsoffload == 'object') && (obj.args.tlsoffload.indexOf(ipex) >= 0))
4903 - ) {
4904 - if (req.headers['cf-connecting-ip']) { // Use CloudFlare IP address if present
4905 - req.clientIp = req.headers['cf-connecting-ip'].split(',')[0].trim();
4906 - } else if (req.headers['x-forwarded-for']) {
4907 - req.clientIp = req.headers['x-forwarded-for'].split(',')[0].trim();
4908 - } else if (req.headers['x-real-ip']) {
4909 - req.clientIp = req.headers['x-real-ip'].split(',')[0].trim();
4910 - } else {
4911 - req.clientIp = ipex;
4912 - }
4913 - } else {
4914 - req.clientIp = ipex;
4915 - }
4916 -
4917 - // Get the domain for this request
4918 - const domain = req.xdomain = getDomain(req);
4919 - parent.debug('webrequest', '(' + req.clientIp + ') AgentPort: ' + req.url);
4920 - res.removeHeader('X-Powered-By');
4921 - return next();
4922 - });
4923 - }
4924 -
4925 - // Setup all sharing domains
4926 - for (var i in parent.config.domains) {
4927 - if ((parent.config.domains[i].dns == null) && (parent.config.domains[i].share != null)) { obj.app.use(parent.config.domains[i].url, obj.express.static(parent.config.domains[i].share)); }
4928 - }
4929 -
4930 - // Setup all HTTP handlers
4931 - if (parent.multiServer != null) { obj.app.ws('/meshserver.ashx', function (ws, req) { parent.multiServer.CreatePeerInServer(parent.multiServer, ws, req, obj.args.tlsoffload == null); }); }
4932 - for (var i in parent.config.domains) {
4933 - if ((parent.config.domains[i].dns != null) || (parent.config.domains[i].share != null)) { continue; } // This is a subdomain with a DNS name, no added HTTP bindings needed.
4934 - var domain = parent.config.domains[i];
4935 - var url = domain.url;
4936 - if (domain.rootredirect == null) {
4937 - // Present the login page as the root page
4938 - obj.app.get(url, handleRootRequest);
4939 - obj.app.post(url, handleRootPostRequest);
4940 - } else {
4941 - // Root page redirects the user to a different URL
4942 - obj.app.get(url, handleRootRedirect);
4943 - }
4944 - obj.app.get(url + 'refresh.ashx', function (req, res) { res.sendStatus(200); });
4945 - if ((domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.backup === true))) { obj.app.get(url + 'backup.zip', handleBackupRequest); }
4946 - if ((domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.restore === true))) { obj.app.post(url + 'restoreserver.ashx', handleRestoreRequest); }
4947 - obj.app.get(url + 'terms', handleTermsRequest);
4948 - obj.app.get(url + 'xterm', handleXTermRequest);
4949 - obj.app.get(url + 'login', handleRootRequest);
4950 - obj.app.post(url + 'login', handleRootPostRequest);
4951 - obj.app.post(url + 'tokenlogin', handleLoginRequest);
4952 - obj.app.get(url + 'logout', handleLogoutRequest);
4953 - obj.app.get(url + 'MeshServerRootCert.cer', handleRootCertRequest);
4954 - obj.app.post(url + 'changepassword', handlePasswordChangeRequest);
4955 - obj.app.post(url + 'deleteaccount', handleDeleteAccountRequest);
4956 - obj.app.post(url + 'createaccount', handleCreateAccountRequest);
4957 - obj.app.post(url + 'resetpassword', handleResetPasswordRequest);
4958 - obj.app.post(url + 'resetaccount', handleResetAccountRequest);
4959 - obj.app.get(url + 'checkmail', handleCheckMailRequest);
4960 - obj.app.get(url + 'agentinvite', handleAgentInviteRequest);
4961 - obj.app.post(url + 'amtevents.ashx', obj.handleAmtEventRequest);
4962 - obj.app.get(url + 'meshagents', obj.handleMeshAgentRequest);
4963 - obj.app.get(url + 'messenger', handleMessengerRequest);
4964 - obj.app.get(url + 'meshosxagent', obj.handleMeshOsxAgentRequest);
4965 - obj.app.get(url + 'meshsettings', obj.handleMeshSettingsRequest);
4966 - obj.app.get(url + 'devicepowerevents.ashx', obj.handleDevicePowerEvents);
4967 - obj.app.get(url + 'downloadfile.ashx', handleDownloadFile);
4968 - obj.app.post(url + 'uploadfile.ashx', handleUploadFile);
4969 - obj.app.post(url + 'uploadfilebatch.ashx', handleUploadFileBatch);
4970 - obj.app.post(url + 'uploadmeshcorefile.ashx', handleUploadMeshCoreFile);
4971 - obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
4972 - obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
4973 - obj.app.ws(url + 'apf.ashx', function (ws, req) { obj.parent.mpsserver.onWebSocketConnection(ws, req); })
4974 - obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
4975 - obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking.
4976 - obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
4977 - obj.app.ws(url + 'webider.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie) { obj.meshIderHandler.CreateAmtIderSession(obj, obj.db, ws1, req1, obj.args, domain, user); }); });
4978 - obj.app.ws(url + 'control.ashx', function (ws, req) {
4979 - const domain = getDomain(req);
4980 - if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { ws.close(); return; } // Check 3FA URL key
4981 - PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user); });
4982 - });
4983 - obj.app.ws(url + 'devicefile.ashx', function (ws, req) { obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, ws, null, req, domain); });
4984 - obj.app.get(url + 'devicefile.ashx', handleDeviceFile);
4985 - obj.app.get(url + 'agentdownload.ashx', handleAgentDownloadFile);
4986 - obj.app.get(url + 'logo.png', handleLogoRequest);
4987 - obj.app.get(url + 'loginlogo.png', handleLoginLogoRequest);
4988 - obj.app.post(url + 'translations', handleTranslationsRequest);
4989 - obj.app.get(url + 'welcome.jpg', handleWelcomeImageRequest);
4990 - obj.app.get(url + 'welcome.png', handleWelcomeImageRequest);
4991 - obj.app.get(url + 'recordings.ashx', handleGetRecordings);
4992 - obj.app.get(url + 'player.htm', handlePlayerRequest);
4993 - obj.app.get(url + 'player', handlePlayerRequest);
4994 - obj.app.get(url + 'desktop', handleDesktopRequest);
4995 - obj.app.get(url + 'terminal', handleTerminalRequest);
4996 - obj.app.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
4997 - obj.app.ws(url + 'meshrelay.ashx', function (ws, req) {
4998 - PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) {
4999 - if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {

This file is too large to show in full.

webserver.js
+1 -1
@@ -2759,7 +2759,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2759 if (idSplit.length == 7) {
2760 const user = obj.users[idSplit[4] + '/' + idSplit[5] + '/' + idSplit[6]];
2761 if (user != null) {
2762 - if (domain.meshmessengertitle.indexOf('{0}') >= 0) { options.username = encodeURIComponent(user.name ? user.name : user._id.split('/')[2]).replace(/'/g, '%27'); }
2762 + if (domain.meshmessengertitle.indexOf('{0}') >= 0) { options.username = encodeURIComponent(user.realname ? user.realname : user._id.split('/')[2]).replace(/'/g, '%27'); }
2763 if (domain.meshmessengertitle.indexOf('{1}') >= 0) { options.userid = encodeURIComponent(user._id.split('/')[2]).replace(/'/g, '%27'); }
2764 }
2765 }