mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-03-09 15:40:18 +00:00
Merge branch 'Ylianst:master' into master
This commit is contained in:
commit
f839825e29
100 changed files with 15399 additions and 9376 deletions
2
LICENSE
2
LICENSE
|
@ -186,7 +186,7 @@
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2017-2021 Intel Corporation
|
Copyright 2017-2025 Intel Corporation
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -594,12 +594,14 @@
|
||||||
<Content Include="readme.md" />
|
<Content Include="readme.md" />
|
||||||
<Content Include="sample-config-advanced.json" />
|
<Content Include="sample-config-advanced.json" />
|
||||||
<Content Include="sample-config.json" />
|
<Content Include="sample-config.json" />
|
||||||
|
<Content Include="SECURITY.md" />
|
||||||
<Content Include="SourceFileList.txt" />
|
<Content Include="SourceFileList.txt" />
|
||||||
<Content Include="translate\readme.txt" />
|
<Content Include="translate\readme.txt" />
|
||||||
<Content Include="translate\translate.json" />
|
<Content Include="translate\translate.json" />
|
||||||
<Content Include="views\agentinvite.handlebars" />
|
<Content Include="views\agentinvite.handlebars" />
|
||||||
<Content Include="views\default-mobile.handlebars" />
|
<Content Include="views\default-mobile.handlebars" />
|
||||||
<Content Include="views\default.handlebars" />
|
<Content Include="views\default.handlebars" />
|
||||||
|
<Content Include="views\default3.handlebars" />
|
||||||
<Content Include="views\download.handlebars" />
|
<Content Include="views\download.handlebars" />
|
||||||
<Content Include="views\download2.handlebars" />
|
<Content Include="views\download2.handlebars" />
|
||||||
<Content Include="views\error404-mobile.handlebars" />
|
<Content Include="views\error404-mobile.handlebars" />
|
||||||
|
|
|
@ -588,7 +588,7 @@ function run(argv) {
|
||||||
}
|
}
|
||||||
amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } });
|
amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } });
|
||||||
amtMei.getProvisioningMode(function (result) { if (result) { mestate.ProvisioningMode = result; } });
|
amtMei.getProvisioningMode(function (result) { if (result) { mestate.ProvisioningMode = result; } });
|
||||||
amtMei.getEHBCState(function (result) { mestate.ehbc = ((result === true) || (typeof result == 'object') && (result.EHBC === true)); });
|
amtMei.getEHBCState(function (result) { if (result) { mestate.ehbc = ((result === true) || (typeof result == 'object') && (result.EHBC === true)); } });
|
||||||
amtMei.getControlMode(function (result) { if (result) { mestate.controlmode = result; } });
|
amtMei.getControlMode(function (result) { if (result) { mestate.controlmode = result; } });
|
||||||
amtMei.getMACAddresses(function (result) { if (result) { mestate.mac = result; } });
|
amtMei.getMACAddresses(function (result) { if (result) { mestate.mac = result; } });
|
||||||
amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } });
|
amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } });
|
||||||
|
|
|
@ -249,7 +249,7 @@ function lockDesktop(uid) {
|
||||||
case 'win32':
|
case 'win32':
|
||||||
{
|
{
|
||||||
var options = { type: 1, uid: uid };
|
var options = { type: 1, uid: uid };
|
||||||
var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd', '/c', 'RunDll32.exe user32.dll,LockWorkStation'], options);
|
var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], options);
|
||||||
child.waitExit();
|
child.waitExit();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -655,33 +655,39 @@ var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ?
|
||||||
try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { }
|
try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { }
|
||||||
|
|
||||||
// Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
|
// Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
|
||||||
|
function onUserSessionChanged(user, locked) {
|
||||||
|
userSession.enumerateUsers().then(function (users) {
|
||||||
|
if (process.platform == 'linux') {
|
||||||
|
if (userSession._startTime == null) {
|
||||||
|
userSession._startTime = Date.now();
|
||||||
|
userSession._count = users.length;
|
||||||
|
}
|
||||||
|
else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
|
||||||
|
userSession.removeAllListeners('changed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var u = [], a = users.Active;
|
||||||
|
if(meshCoreObj.lusers == null) { meshCoreObj.lusers = []; }
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
|
||||||
|
if (user && locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { if (meshCoreObj.lusers.indexOf(un) == -1) { meshCoreObj.lusers.push(un); } }
|
||||||
|
else if (user && !locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { meshCoreObj.lusers.splice(meshCoreObj.lusers.indexOf(un), 1); }
|
||||||
|
if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
|
||||||
|
}
|
||||||
|
meshCoreObj.lusers = meshCoreObj.lusers;
|
||||||
|
meshCoreObj.users = u;
|
||||||
|
meshCoreObjChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var userSession = require('user-sessions');
|
var userSession = require('user-sessions');
|
||||||
userSession.on('changed', function onUserSessionChanged() {
|
userSession.on('changed', function () { onUserSessionChanged(null, false); });
|
||||||
userSession.enumerateUsers().then(function (users) {
|
|
||||||
if (process.platform == 'linux') {
|
|
||||||
if (userSession._startTime == null) {
|
|
||||||
userSession._startTime = Date.now();
|
|
||||||
userSession._count = users.length;
|
|
||||||
}
|
|
||||||
else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
|
|
||||||
userSession.removeAllListeners('changed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var u = [], a = users.Active;
|
|
||||||
for (var i = 0; i < a.length; i++) {
|
|
||||||
var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
|
|
||||||
if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
|
|
||||||
}
|
|
||||||
meshCoreObj.users = u;
|
|
||||||
meshCoreObjChanged();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
userSession.emit('changed');
|
userSession.emit('changed');
|
||||||
//userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); });
|
userSession.on('locked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, true); } });
|
||||||
//userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); });
|
userSession.on('unlocked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, false); } });
|
||||||
} catch (ex) { }
|
} catch (ex) { }
|
||||||
|
|
||||||
var meshServerConnectionState = 0;
|
var meshServerConnectionState = 0;
|
||||||
|
@ -1158,6 +1164,7 @@ function handleServerCommand(data) {
|
||||||
tunnel.soptions = data.soptions;
|
tunnel.soptions = data.soptions;
|
||||||
tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30;
|
tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30;
|
||||||
tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true));
|
tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true));
|
||||||
|
tunnel.consentAutoAcceptIfNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfNoUser === true));
|
||||||
tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false;
|
tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false;
|
||||||
tunnel.tcpaddr = data.tcpaddr;
|
tunnel.tcpaddr = data.tcpaddr;
|
||||||
tunnel.tcpport = data.tcpport;
|
tunnel.tcpport = data.tcpport;
|
||||||
|
@ -1572,7 +1579,7 @@ function handleServerCommand(data) {
|
||||||
mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
|
mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
|
||||||
mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
|
mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
|
||||||
mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); });
|
mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); });
|
||||||
mesh.cmdchild.stderr.on('data', function (c) { replydata + c.toString(); });
|
mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); });
|
||||||
mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
|
mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
|
||||||
mesh.cmdchild.on('exit', function () {
|
mesh.cmdchild.on('exit', function () {
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
|
@ -1936,9 +1943,9 @@ function getSystemInformation(func) {
|
||||||
if (process.platform == 'win32')
|
if (process.platform == 'win32')
|
||||||
{
|
{
|
||||||
results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot
|
results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot
|
||||||
if (require('computer-identifiers').volumes_promise != null)
|
if (require('win-volumes').volumes_promise != null)
|
||||||
{
|
{
|
||||||
var p = require('computer-identifiers').volumes_promise();
|
var p = require('win-volumes').volumes_promise();
|
||||||
p.then(function (res)
|
p.then(function (res)
|
||||||
{
|
{
|
||||||
results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(res);
|
results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(res);
|
||||||
|
@ -1946,12 +1953,6 @@ function getSystemInformation(func) {
|
||||||
func(results);
|
func(results);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (require('computer-identifiers').volumes != null)
|
|
||||||
{
|
|
||||||
results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(require('computer-identifiers').volumes());
|
|
||||||
results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
|
|
||||||
func(results);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
|
results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
|
||||||
|
@ -2303,6 +2304,59 @@ function terminal_end()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function terminal_consent_ask(ws) {
|
||||||
|
ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
|
||||||
|
var consentMessage = currentTranslation['terminalConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username);
|
||||||
|
var consentTitle = 'MeshCentral';
|
||||||
|
if (ws.httprequest.soptions != null) {
|
||||||
|
if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
|
||||||
|
if (ws.httprequest.soptions.consentMsgTerminal != null) { consentMessage = ws.httprequest.soptions.consentMsgTerminal.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
|
||||||
|
}
|
||||||
|
if (process.platform == 'win32') {
|
||||||
|
var enhanced = false;
|
||||||
|
if (ws.httprequest.oldStyle === false) {
|
||||||
|
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
|
||||||
|
}
|
||||||
|
if (enhanced) {
|
||||||
|
var ipr = server_getUserImage(ws.httprequest.userid);
|
||||||
|
ipr.consentTitle = consentTitle;
|
||||||
|
ipr.consentMessage = consentMessage;
|
||||||
|
ipr.consentTimeout = ws.httprequest.consentTimeout;
|
||||||
|
ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
|
||||||
|
ipr.username = ws.httprequest.realname;
|
||||||
|
ipr.tsid = ws.tsid;
|
||||||
|
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
|
||||||
|
ws.httprequest.tpromise._consent = ipr.then(function (img) {
|
||||||
|
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
|
||||||
|
this.__childPromise.close = this.consent.close.bind(this.consent);
|
||||||
|
return (this.consent);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
|
||||||
|
}
|
||||||
|
ws.httprequest.tpromise._consent.retPromise = ws.httprequest.tpromise;
|
||||||
|
ws.httprequest.tpromise._consent.then(function (always) {
|
||||||
|
if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
|
||||||
|
// Success
|
||||||
|
MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
|
||||||
|
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
|
||||||
|
this.retPromise._consent = null;
|
||||||
|
this.retPromise._res();
|
||||||
|
}, function (e) {
|
||||||
|
if (this.retPromise.that) {
|
||||||
|
if(this.retPromise.that.httprequest){ // User Consent Denied
|
||||||
|
MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
|
||||||
|
} else { } // Connection was closed server side, maybe log some messages somewhere?
|
||||||
|
this.retPromise._consent = null;
|
||||||
|
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
|
||||||
|
} else { } // no websocket, maybe log some messages somewhere?
|
||||||
|
this.retPromise._rej(e.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function terminal_promise_connection_rejected(e)
|
function terminal_promise_connection_rejected(e)
|
||||||
{
|
{
|
||||||
// FAILED to connect terminal
|
// FAILED to connect terminal
|
||||||
|
@ -2615,6 +2669,101 @@ function kvm_tunnel_consentpromise_closehandler()
|
||||||
if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
|
if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function kvm_consent_ok(ws) {
|
||||||
|
// User Consent Prompt is not required because no user is present
|
||||||
|
if (ws.httprequest.consent && (ws.httprequest.consent & 1)){
|
||||||
|
// User Notifications is required
|
||||||
|
MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
|
||||||
|
var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', ws.httprequest.realname);
|
||||||
|
var notifyTitle = "MeshCentral";
|
||||||
|
if (ws.httprequest.soptions != null) {
|
||||||
|
if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
|
||||||
|
if (ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
|
||||||
|
}
|
||||||
|
try { require('toaster').Toast(notifyTitle, notifyMessage, ws.tsid); } catch (ex) { }
|
||||||
|
} else {
|
||||||
|
MeshServerLogEx(36, null, "Started remote desktop without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
|
||||||
|
}
|
||||||
|
if (ws.httprequest.consent && (ws.httprequest.consent & 0x40)) {
|
||||||
|
// Connection Bar is required
|
||||||
|
if (ws.httprequest.desktop.kvm.connectionBar) {
|
||||||
|
ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
|
||||||
|
ws.httprequest.desktop.kvm.connectionBar.close();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(ws.httprequest.privacybartext.replace('{0}', ws.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', ws.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
|
||||||
|
MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
|
||||||
|
} catch (ex) {
|
||||||
|
MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
|
||||||
|
}
|
||||||
|
if (ws.httprequest.desktop.kvm.connectionBar) {
|
||||||
|
ws.httprequest.desktop.kvm.connectionBar.state = {
|
||||||
|
userid: ws.httprequest.userid,
|
||||||
|
xuserid: ws.httprequest.xuserid,
|
||||||
|
username: ws.httprequest.username,
|
||||||
|
sessionid: ws.httprequest.sessionid,
|
||||||
|
remoteaddr: ws.httprequest.remoteaddr,
|
||||||
|
guestname: ws.httprequest.guestname,
|
||||||
|
desktop: ws.httprequest.desktop
|
||||||
|
};
|
||||||
|
ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
|
||||||
|
console.info1('Connection Bar Forcefully closed');
|
||||||
|
MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
|
||||||
|
for (var i in this.state.desktop.kvm._pipedStreams) {
|
||||||
|
this.state.desktop.kvm._pipedStreams[i].end();
|
||||||
|
}
|
||||||
|
this.state.desktop.kvm.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws.httprequest.desktop.kvm.pipe(ws, { dataTypeSkip: 1 });
|
||||||
|
if (ws.httprequest.autolock) {
|
||||||
|
destopLockHelper_pipe(ws.httprequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function kvm_consent_ask(ws){
|
||||||
|
// Send a console message back using the console channel, "\n" is supported.
|
||||||
|
ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
|
||||||
|
var consentMessage = currentTranslation['desktopConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username);
|
||||||
|
var consentTitle = 'MeshCentral';
|
||||||
|
if (ws.httprequest.soptions != null) {
|
||||||
|
if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
|
||||||
|
if (ws.httprequest.soptions.consentMsgDesktop != null) { consentMessage = ws.httprequest.soptions.consentMsgDesktop.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
|
||||||
|
}
|
||||||
|
var pr;
|
||||||
|
if (process.platform == 'win32') {
|
||||||
|
var enhanced = false;
|
||||||
|
if (ws.httprequest.oldStyle === false) {
|
||||||
|
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
|
||||||
|
}
|
||||||
|
if (enhanced) {
|
||||||
|
var ipr = server_getUserImage(ws.httprequest.userid);
|
||||||
|
ipr.consentTitle = consentTitle;
|
||||||
|
ipr.consentMessage = consentMessage;
|
||||||
|
ipr.consentTimeout = ws.httprequest.consentTimeout;
|
||||||
|
ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
|
||||||
|
ipr.tsid = ws.tsid;
|
||||||
|
ipr.username = ws.httprequest.realname;
|
||||||
|
ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
|
||||||
|
pr = ipr.then(function (img) {
|
||||||
|
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground });
|
||||||
|
this.__childPromise.close = this.consent.close.bind(this.consent);
|
||||||
|
return (this.consent);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
|
||||||
|
}
|
||||||
|
pr.ws = ws;
|
||||||
|
ws.pause();
|
||||||
|
ws._consentpromise = pr;
|
||||||
|
ws.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
|
||||||
|
pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
|
||||||
|
}
|
||||||
|
|
||||||
function kvm_consentpromise_rejected(e)
|
function kvm_consentpromise_rejected(e)
|
||||||
{
|
{
|
||||||
if (this.ws) {
|
if (this.ws) {
|
||||||
|
@ -2694,6 +2843,67 @@ function kvm_consentpromise_resolved(always)
|
||||||
this.ws = null;
|
this.ws = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function files_consent_ok(ws){
|
||||||
|
// User Consent Prompt is not required
|
||||||
|
if (ws.httprequest.consent && (ws.httprequest.consent & 4)) {
|
||||||
|
// User Notifications is required
|
||||||
|
MeshServerLogEx(42, null, "Started remote files with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
|
||||||
|
var notifyMessage = currentTranslation['fileNotify'].replace('{0}', ws.httprequest.realname);
|
||||||
|
var notifyTitle = "MeshCentral";
|
||||||
|
if (ws.httprequest.soptions != null) {
|
||||||
|
if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
|
||||||
|
if (ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = ws.httprequest.soptions.notifyMsgFiles.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
|
||||||
|
}
|
||||||
|
try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
|
||||||
|
} else {
|
||||||
|
MeshServerLogEx(43, null, "Started remote files without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
|
||||||
|
}
|
||||||
|
ws.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
function files_consent_ask(ws){
|
||||||
|
// Send a console message back using the console channel, "\n" is supported.
|
||||||
|
ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
|
||||||
|
var consentMessage = currentTranslation['fileConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username);
|
||||||
|
var consentTitle = 'MeshCentral';
|
||||||
|
|
||||||
|
if (ws.httprequest.soptions != null) {
|
||||||
|
if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
|
||||||
|
if (ws.httprequest.soptions.consentMsgFiles != null) { consentMessage = ws.httprequest.soptions.consentMsgFiles.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
|
||||||
|
}
|
||||||
|
var pr;
|
||||||
|
if (process.platform == 'win32') {
|
||||||
|
var enhanced = false;
|
||||||
|
if (ws.httprequest.oldStyle === false) {
|
||||||
|
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
|
||||||
|
}
|
||||||
|
if (enhanced) {
|
||||||
|
var ipr = server_getUserImage(ws.httprequest.userid);
|
||||||
|
ipr.consentTitle = consentTitle;
|
||||||
|
ipr.consentMessage = consentMessage;
|
||||||
|
ipr.consentTimeout = ws.httprequest.consentTimeout;
|
||||||
|
ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
|
||||||
|
ipr.username = ws.httprequest.realname;
|
||||||
|
ipr.tsid = ws.tsid;
|
||||||
|
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
|
||||||
|
pr = ipr.then(function (img) {
|
||||||
|
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
|
||||||
|
this.__childPromise.close = this.consent.close.bind(this.consent);
|
||||||
|
return (this.consent);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
|
||||||
|
}
|
||||||
|
pr.ws = ws;
|
||||||
|
ws.pause();
|
||||||
|
ws._consentpromise = pr;
|
||||||
|
ws.prependOnceListener('end', files_tunnel_endhandler);
|
||||||
|
pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
|
||||||
|
}
|
||||||
|
|
||||||
function files_consentpromise_resolved(always)
|
function files_consentpromise_resolved(always)
|
||||||
{
|
{
|
||||||
if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); }
|
if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); }
|
||||||
|
@ -2807,6 +3017,12 @@ function onTunnelData(data)
|
||||||
|
|
||||||
this.descriptorMetadata = "Remote Terminal";
|
this.descriptorMetadata = "Remote Terminal";
|
||||||
|
|
||||||
|
// Look for a TSID
|
||||||
|
var tsid = null;
|
||||||
|
if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
|
||||||
|
require('MeshAgent')._tsid = tsid;
|
||||||
|
this.tsid = tsid;
|
||||||
|
|
||||||
if (process.platform == 'win32')
|
if (process.platform == 'win32')
|
||||||
{
|
{
|
||||||
if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
|
if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
|
||||||
|
@ -2823,76 +3039,31 @@ function onTunnelData(data)
|
||||||
this.end = terminal_end;
|
this.end = terminal_end;
|
||||||
|
|
||||||
// Perform User-Consent if needed.
|
// Perform User-Consent if needed.
|
||||||
if (this.httprequest.consent && (this.httprequest.consent & 16))
|
if (this.httprequest.consent && (this.httprequest.consent & 16)) {
|
||||||
{
|
// User asked for consent so now we check if we can auto accept if no user is present/loggedin
|
||||||
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
|
if (this.httprequest.consentAutoAcceptIfNoUser) {
|
||||||
var consentMessage = currentTranslation['terminalConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username);
|
var p = require('user-sessions').enumerateUsers();
|
||||||
var consentTitle = 'MeshCentral';
|
p.sessionid = this.httprequest.sessionid;
|
||||||
|
p.ws = this;
|
||||||
if (this.httprequest.soptions != null)
|
p.then(function (u) {
|
||||||
{
|
var v = [];
|
||||||
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
|
for (var i in u) {
|
||||||
if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
|
if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
|
||||||
}
|
}
|
||||||
if (process.platform == 'win32')
|
if (v.length == 0) { // No user is present, auto accept
|
||||||
{
|
this.ws.httprequest.tpromise._res();
|
||||||
var enhanced = false;
|
} else {
|
||||||
if (this.httprequest.oldStyle === false) {
|
// User is present so we still need consent
|
||||||
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
|
terminal_consent_ask(this.ws);
|
||||||
}
|
}
|
||||||
if (enhanced)
|
|
||||||
{
|
|
||||||
var ipr = server_getUserImage(this.httprequest.userid);
|
|
||||||
ipr.consentTitle = consentTitle;
|
|
||||||
ipr.consentMessage = consentMessage;
|
|
||||||
ipr.consentTimeout = this.httprequest.consentTimeout;
|
|
||||||
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
|
|
||||||
ipr.username = this.httprequest.realname;
|
|
||||||
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
|
|
||||||
this.httprequest.tpromise._consent = ipr.then(function (img)
|
|
||||||
{
|
|
||||||
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
|
|
||||||
this.__childPromise.close = this.consent.close.bind(this.consent);
|
|
||||||
return (this.consent);
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout);
|
|
||||||
}
|
|
||||||
this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise;
|
|
||||||
this.httprequest.tpromise._consent.then(
|
|
||||||
function (always)
|
|
||||||
{
|
|
||||||
if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
|
|
||||||
|
|
||||||
// Success
|
|
||||||
MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
|
|
||||||
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
|
|
||||||
this.retPromise._consent = null;
|
|
||||||
this.retPromise._res();
|
|
||||||
},
|
|
||||||
function (e) {
|
|
||||||
if (this.retPromise.that) {
|
|
||||||
if(this.retPromise.that.httprequest){ // User Consent Denied
|
|
||||||
MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
|
|
||||||
} else { } // Connection was closed server side, maybe log some messages somewhere?
|
|
||||||
this.retPromise._consent = null;
|
|
||||||
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
|
|
||||||
} else { } // no websocket, maybe log some messages somewhere?
|
|
||||||
this.retPromise._rej(e.toString());
|
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else
|
terminal_consent_ask(this);
|
||||||
{
|
}
|
||||||
|
} else {
|
||||||
// User-Consent is not required, so just resolve this promise
|
// User-Consent is not required, so just resolve this promise
|
||||||
this.httprequest.tpromise._res();
|
this.httprequest.tpromise._res();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected);
|
this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected);
|
||||||
}
|
}
|
||||||
else if (this.httprequest.protocol == 2)
|
else if (this.httprequest.protocol == 2)
|
||||||
|
@ -2916,6 +3087,7 @@ function onTunnelData(data)
|
||||||
var tsid = null;
|
var tsid = null;
|
||||||
if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
|
if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
|
||||||
require('MeshAgent')._tsid = tsid;
|
require('MeshAgent')._tsid = tsid;
|
||||||
|
this.tsid = tsid;
|
||||||
|
|
||||||
// If MacOS, Wake up device with caffeinate
|
// If MacOS, Wake up device with caffeinate
|
||||||
if(process.platform == 'darwin'){
|
if(process.platform == 'darwin'){
|
||||||
|
@ -2987,119 +3159,33 @@ function onTunnelData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform notification if needed. Toast messages may not be supported on all platforms.
|
// Perform notification if needed. Toast messages may not be supported on all platforms.
|
||||||
if (this.httprequest.consent && (this.httprequest.consent & 8))
|
if (this.httprequest.consent && (this.httprequest.consent & 8)) {
|
||||||
{
|
|
||||||
// User Consent Prompt is required
|
// User asked for consent but now we check if can auto accept if no user is present
|
||||||
// Send a console message back using the console channel, "\n" is supported.
|
if (this.httprequest.consentAutoAcceptIfNoUser) {
|
||||||
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
|
// Get list of users to check if we any actual users logged in, and if users logged in, we still need consent
|
||||||
var consentMessage = currentTranslation['desktopConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username);
|
var p = require('user-sessions').enumerateUsers();
|
||||||
var consentTitle = 'MeshCentral';
|
p.sessionid = this.httprequest.sessionid;
|
||||||
if (this.httprequest.soptions != null)
|
p.ws = this;
|
||||||
{
|
p.then(function (u) {
|
||||||
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
|
var v = [];
|
||||||
if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
|
for (var i in u) {
|
||||||
|
if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
|
||||||
|
}
|
||||||
|
if (v.length == 0) { // No user is present, auto accept
|
||||||
|
kvm_consent_ok(this.ws);
|
||||||
|
} else {
|
||||||
|
// User is present so we still need consent
|
||||||
|
kvm_consent_ask(this.ws);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// User Consent Prompt is required
|
||||||
|
kvm_consent_ask(this);
|
||||||
}
|
}
|
||||||
var pr;
|
} else {
|
||||||
if (process.platform == 'win32')
|
|
||||||
{
|
|
||||||
var enhanced = false;
|
|
||||||
if (this.httprequest.oldStyle === false) {
|
|
||||||
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
|
|
||||||
}
|
|
||||||
if (enhanced)
|
|
||||||
{
|
|
||||||
var ipr = server_getUserImage(this.httprequest.userid);
|
|
||||||
ipr.consentTitle = consentTitle;
|
|
||||||
ipr.consentMessage = consentMessage;
|
|
||||||
ipr.consentTimeout = this.httprequest.consentTimeout;
|
|
||||||
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
|
|
||||||
ipr.tsid = tsid;
|
|
||||||
ipr.username = this.httprequest.realname;
|
|
||||||
ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
|
|
||||||
pr = ipr.then(function (img)
|
|
||||||
{
|
|
||||||
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground });
|
|
||||||
this.__childPromise.close = this.consent.close.bind(this.consent);
|
|
||||||
return (this.consent);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid);
|
|
||||||
}
|
|
||||||
pr.ws = this;
|
|
||||||
this.pause();
|
|
||||||
this._consentpromise = pr;
|
|
||||||
this.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
|
|
||||||
pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// User Consent Prompt is not required
|
// User Consent Prompt is not required
|
||||||
if (this.httprequest.consent && (this.httprequest.consent & 1))
|
kvm_consent_ok(this);
|
||||||
{
|
|
||||||
// User Notifications is required
|
|
||||||
MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
|
|
||||||
var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.httprequest.realname);
|
|
||||||
var notifyTitle = "MeshCentral";
|
|
||||||
if (this.httprequest.soptions != null) {
|
|
||||||
if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
|
|
||||||
if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
|
|
||||||
}
|
|
||||||
try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
|
|
||||||
}
|
|
||||||
if (this.httprequest.consent && (this.httprequest.consent & 0x40))
|
|
||||||
{
|
|
||||||
// Connection Bar is required
|
|
||||||
if (this.httprequest.desktop.kvm.connectionBar)
|
|
||||||
{
|
|
||||||
this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
|
|
||||||
this.httprequest.desktop.kvm.connectionBar.close();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
|
|
||||||
MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest);
|
|
||||||
} catch (ex) {
|
|
||||||
MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest);
|
|
||||||
}
|
|
||||||
if (this.httprequest.desktop.kvm.connectionBar)
|
|
||||||
{
|
|
||||||
this.httprequest.desktop.kvm.connectionBar.state =
|
|
||||||
{
|
|
||||||
userid: this.httprequest.userid,
|
|
||||||
xuserid: this.httprequest.xuserid,
|
|
||||||
username: this.httprequest.username,
|
|
||||||
sessionid: this.httprequest.sessionid,
|
|
||||||
remoteaddr: this.httprequest.remoteaddr,
|
|
||||||
guestname: this.httprequest.guestname,
|
|
||||||
desktop: this.httprequest.desktop
|
|
||||||
};
|
|
||||||
this.httprequest.desktop.kvm.connectionBar.on('close', function ()
|
|
||||||
{
|
|
||||||
console.info1('Connection Bar Forcefully closed');
|
|
||||||
MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
|
|
||||||
for (var i in this.state.desktop.kvm._pipedStreams)
|
|
||||||
{
|
|
||||||
this.state.desktop.kvm._pipedStreams[i].end();
|
|
||||||
}
|
|
||||||
this.state.desktop.kvm.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 });
|
|
||||||
if (this.httprequest.autolock)
|
|
||||||
{
|
|
||||||
destopLockHelper_pipe(this.httprequest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.removeAllListeners('data');
|
this.removeAllListeners('data');
|
||||||
|
@ -3121,6 +3207,12 @@ function onTunnelData(data)
|
||||||
|
|
||||||
this.descriptorMetadata = "Remote Files";
|
this.descriptorMetadata = "Remote Files";
|
||||||
|
|
||||||
|
// Look for a TSID
|
||||||
|
var tsid = null;
|
||||||
|
if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
|
||||||
|
require('MeshAgent')._tsid = tsid;
|
||||||
|
this.tsid = tsid;
|
||||||
|
|
||||||
// Add the files session to the count to update the server
|
// Add the files session to the count to update the server
|
||||||
if (this.httprequest.userid != null) {
|
if (this.httprequest.userid != null) {
|
||||||
var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
|
var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
|
||||||
|
@ -3143,71 +3235,31 @@ function onTunnelData(data)
|
||||||
// Perform notification if needed. Toast messages may not be supported on all platforms.
|
// Perform notification if needed. Toast messages may not be supported on all platforms.
|
||||||
if (this.httprequest.consent && (this.httprequest.consent & 32))
|
if (this.httprequest.consent && (this.httprequest.consent & 32))
|
||||||
{
|
{
|
||||||
// User Consent Prompt is required
|
// User asked for consent so now we check if we can auto accept if no user is present/loggedin
|
||||||
// Send a console message back using the console channel, "\n" is supported.
|
if (this.httprequest.consentAutoAcceptIfNoUser) {
|
||||||
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
|
var p = require('user-sessions').enumerateUsers();
|
||||||
var consentMessage = currentTranslation['fileConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username);
|
p.sessionid = this.httprequest.sessionid;
|
||||||
var consentTitle = 'MeshCentral';
|
p.ws = this;
|
||||||
|
p.then(function (u) {
|
||||||
if (this.httprequest.soptions != null)
|
var v = [];
|
||||||
{
|
for (var i in u) {
|
||||||
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
|
if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
|
||||||
if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
|
}
|
||||||
}
|
if (v.length == 0) { // No user is present, auto accept
|
||||||
var pr;
|
// User Consent Prompt is not required
|
||||||
if (process.platform == 'win32')
|
files_consent_ok(this.ws);
|
||||||
{
|
} else {
|
||||||
var enhanced = false;
|
// User is present so we still need consent
|
||||||
if (this.httprequest.oldStyle === false) {
|
files_consent_ask(this.ws);
|
||||||
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
|
}
|
||||||
}
|
});
|
||||||
if (enhanced)
|
|
||||||
{
|
|
||||||
var ipr = server_getUserImage(this.httprequest.userid);
|
|
||||||
ipr.consentTitle = consentTitle;
|
|
||||||
ipr.consentMessage = consentMessage;
|
|
||||||
ipr.consentTimeout = this.httprequest.consentTimeout;
|
|
||||||
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
|
|
||||||
ipr.username = this.httprequest.realname;
|
|
||||||
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
|
|
||||||
pr = ipr.then(function (img)
|
|
||||||
{
|
|
||||||
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
|
|
||||||
this.__childPromise.close = this.consent.close.bind(this.consent);
|
|
||||||
return (this.consent);
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null);
|
|
||||||
}
|
|
||||||
pr.ws = this;
|
|
||||||
this.pause();
|
|
||||||
this._consentpromise = pr;
|
|
||||||
this.prependOnceListener('end', files_tunnel_endhandler);
|
|
||||||
pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// User Consent Prompt is not required
|
|
||||||
if (this.httprequest.consent && (this.httprequest.consent & 4)) {
|
|
||||||
// User Notifications is required
|
|
||||||
MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
|
|
||||||
var notifyMessage = currentTranslation['fileNotify'].replace('{0}', this.httprequest.realname);
|
|
||||||
var notifyTitle = "MeshCentral";
|
|
||||||
if (this.httprequest.soptions != null) {
|
|
||||||
if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
|
|
||||||
if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
|
|
||||||
}
|
|
||||||
try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
|
|
||||||
} else {
|
} else {
|
||||||
MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
|
// User Consent Prompt is required
|
||||||
|
files_consent_ask(this);
|
||||||
}
|
}
|
||||||
this.resume();
|
} else {
|
||||||
|
// User Consent Prompt is not required
|
||||||
|
files_consent_ok(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup files
|
// Setup files
|
||||||
|
@ -3695,7 +3747,14 @@ function onTunnelControlData(data, ws) {
|
||||||
{ // Desktop
|
{ // Desktop
|
||||||
// Switch the user input from websocket to webrtc at this point.
|
// Switch the user input from websocket to webrtc at this point.
|
||||||
ws.unpipe(ws.httprequest.desktop.kvm);
|
ws.unpipe(ws.httprequest.desktop.kvm);
|
||||||
try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
|
if ((ws.httprequest.desktopviewonly != true) && ((ws.httprequest.rights == 0xFFFFFFFF) || (((ws.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((ws.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) {
|
||||||
|
// If we have remote control rights, pipe the KVM input
|
||||||
|
try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
|
||||||
|
} else {
|
||||||
|
// We need to only pipe non-mouse & non-keyboard inputs.
|
||||||
|
// sendConsoleText('Warning: No Remote Desktop Input Rights.');
|
||||||
|
// TODO!!!
|
||||||
|
}
|
||||||
ws.resume(); // Resume the websocket to keep receiving control data
|
ws.resume(); // Resume the websocket to keep receiving control data
|
||||||
}
|
}
|
||||||
ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
|
ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
|
||||||
|
@ -3881,7 +3940,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
|
||||||
if (require('os').dns != null) { availcommands += ',dnsinfo'; }
|
if (require('os').dns != null) { availcommands += ',dnsinfo'; }
|
||||||
try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { }
|
try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { }
|
||||||
if (process.platform == 'win32') {
|
if (process.platform == 'win32') {
|
||||||
availcommands += ',bitlocker,cs,wpfhwacceleration,uac,volumes,rdpport';
|
availcommands += ',bitlocker,cs,wpfhwacceleration,uac,volumes,rdpport,deskbackground';
|
||||||
if (bcdOK()) { availcommands += ',safemode'; }
|
if (bcdOK()) { availcommands += ',safemode'; }
|
||||||
if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; }
|
if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; }
|
||||||
try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { }
|
try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { }
|
||||||
|
@ -4044,12 +4103,9 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
|
||||||
break;
|
break;
|
||||||
case 'bitlocker':
|
case 'bitlocker':
|
||||||
if (process.platform == 'win32') {
|
if (process.platform == 'win32') {
|
||||||
if (require('computer-identifiers').volumes_promise != null) {
|
if (require('win-volumes').volumes_promise != null) {
|
||||||
var p = require('computer-identifiers').volumes_promise();
|
var p = require('win-volumes').volumes_promise();
|
||||||
p.then(function (res) { sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(res), null, 1), this.session); });
|
p.then(function (res) { sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(res), null, 1), this.session); });
|
||||||
response = "Please wait...";
|
|
||||||
} else if (require('computer-identifiers').volumes != null) {
|
|
||||||
sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(require('computer-identifiers').volumes()), null, 1), this.session);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -4299,7 +4355,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
|
||||||
}
|
}
|
||||||
case 'agentmsg': {
|
case 'agentmsg': {
|
||||||
if (args['_'].length == 0) {
|
if (args['_'].length == 0) {
|
||||||
response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage
|
response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [id]\r\n agentmsg list"; // Display usage
|
||||||
} else {
|
} else {
|
||||||
if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
|
if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
|
||||||
var msgID, iconIndex = 0;
|
var msgID, iconIndex = 0;
|
||||||
|
|
|
@ -422,106 +422,6 @@ function windows_wmic_results(str)
|
||||||
return (result);
|
return (result);
|
||||||
}
|
}
|
||||||
|
|
||||||
function windows_volumes()
|
|
||||||
{
|
|
||||||
var promise = require('promise');
|
|
||||||
var p1 = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
|
||||||
var p2 = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
|
||||||
|
|
||||||
p1._p2 = p2;
|
|
||||||
p2._p1 = p1;
|
|
||||||
|
|
||||||
var cmd = '"Get-Volume | Select-Object -Property DriveLetter,FileSystemLabel,FileSystemType,Size,SizeRemaining,DriveType | ConvertTo-Csv -NoTypeInformation"';
|
|
||||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', cmd]);
|
|
||||||
p1.child = child;
|
|
||||||
child.promise = p1;
|
|
||||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
|
||||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
|
||||||
child.on('exit', function (c)
|
|
||||||
{
|
|
||||||
var a, i, tokens, key;
|
|
||||||
var ret = {};
|
|
||||||
|
|
||||||
a = this.stdout.str.trim().split('\r\n');
|
|
||||||
for (i = 1; i < a.length; ++i)
|
|
||||||
{
|
|
||||||
tokens = a[i].split(',');
|
|
||||||
if (tokens[0] != '' && tokens[1] != undefined)
|
|
||||||
{
|
|
||||||
ret[tokens[0].split('"')[1]] =
|
|
||||||
{
|
|
||||||
name: tokens[1].split('"')[1],
|
|
||||||
type: tokens[2].split('"')[1],
|
|
||||||
size: tokens[3].split('"')[1],
|
|
||||||
sizeremaining: tokens[4].split('"')[1],
|
|
||||||
removable: tokens[5].split('"')[1] == 'Removable',
|
|
||||||
cdrom: tokens[5].split('"')[1] == 'CD-ROM'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.promise._res({ r: ret, t: tokens });
|
|
||||||
});
|
|
||||||
|
|
||||||
p1.then(function (j)
|
|
||||||
{
|
|
||||||
var ret = j.r;
|
|
||||||
var tokens = j.t;
|
|
||||||
|
|
||||||
var cmd = '"Get-BitLockerVolume | Select-Object -Property MountPoint,VolumeStatus,ProtectionStatus | ConvertTo-Csv -NoTypeInformation"';
|
|
||||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', cmd]);
|
|
||||||
p2.child = child;
|
|
||||||
child.promise = p2;
|
|
||||||
child.tokens = tokens;
|
|
||||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
|
||||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
|
||||||
child.on('exit', function ()
|
|
||||||
{
|
|
||||||
var i;
|
|
||||||
var a = this.stdout.str.trim().split('\r\n');
|
|
||||||
for (i = 1; i < a.length; ++i)
|
|
||||||
{
|
|
||||||
tokens = a[i].split(',');
|
|
||||||
key = tokens[0].split(':').shift().split('"').pop();
|
|
||||||
if (ret[key] != null)
|
|
||||||
{
|
|
||||||
ret[key].volumeStatus = tokens[1].split('"')[1];
|
|
||||||
ret[key].protectionStatus = tokens[2].split('"')[1];
|
|
||||||
try {
|
|
||||||
var foundIDMarkedLine = false, foundMarkedLine = false, identifier = '', password = '';
|
|
||||||
var keychild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd', '/c', 'manage-bde -protectors -get ', tokens[0].split('"')[1], ' -Type recoverypassword'], {});
|
|
||||||
keychild.stdout.str = ''; keychild.stdout.on('data', function (c) { this.str += c.toString(); });
|
|
||||||
keychild.waitExit();
|
|
||||||
var lines = keychild.stdout.str.trim().split('\r\n');
|
|
||||||
for (var x = 0; x < lines.length; x++) { // Loop each line
|
|
||||||
var abc = lines[x].trim();
|
|
||||||
var englishidpass = (abc !== '' && abc.includes('Numerical Password:')); // English ID
|
|
||||||
var germanidpass = (abc !== '' && abc.includes('Numerisches Kennwort:')); // German ID
|
|
||||||
var frenchidpass = (abc !== '' && abc.includes('Mot de passe num')); // French ID
|
|
||||||
var englishpass = (abc !== '' && abc.includes('Password:') && !abc.includes('Numerical Password:')); // English Password
|
|
||||||
var germanpass = (abc !== '' && abc.includes('Kennwort:') && !abc.includes('Numerisches Kennwort:')); // German Password
|
|
||||||
var frenchpass = (abc !== '' && abc.includes('Mot de passe :') && !abc.includes('Mot de passe num')); // French Password
|
|
||||||
if (englishidpass || germanidpass || frenchidpass|| englishpass || germanpass || frenchpass) {
|
|
||||||
var nextline = lines[x + 1].trim();
|
|
||||||
if (x + 1 < lines.length && (nextline !== '' && (nextline.startsWith('ID:') || nextline.startsWith('ID :')) )) {
|
|
||||||
identifier = nextline.replace('ID:','').replace('ID :', '').trim();
|
|
||||||
foundIDMarkedLine = true;
|
|
||||||
}else if (x + 1 < lines.length && nextline !== '') {
|
|
||||||
password = nextline;
|
|
||||||
foundMarkedLine = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret[key].identifier = (foundIDMarkedLine ? identifier : ''); // Set Bitlocker Identifier
|
|
||||||
ret[key].recoveryPassword = (foundMarkedLine ? password : ''); // Set Bitlocker Password
|
|
||||||
} catch(ex) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.promise._res(ret);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return (p2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function windows_identifiers()
|
function windows_identifiers()
|
||||||
{
|
{
|
||||||
var ret = { windows: {} };
|
var ret = { windows: {} };
|
||||||
|
@ -803,12 +703,13 @@ function hexToAscii(hexString) {
|
||||||
function win_chassisType()
|
function win_chassisType()
|
||||||
{
|
{
|
||||||
// needs to be replaced with win-wmi but due to bug in win-wmi it doesnt handle arrays correctly
|
// needs to be replaced with win-wmi but due to bug in win-wmi it doesnt handle arrays correctly
|
||||||
var cmd = '"Get-CimInstance Win32_SystemEnclosure | Select-Object -ExpandProperty ChassisTypes"';
|
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
|
||||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', cmd], {});
|
|
||||||
if (child == null) { return ([]); }
|
if (child == null) { return ([]); }
|
||||||
child.descriptorMetadata = 'process-manager';
|
child.descriptorMetadata = 'process-manager';
|
||||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||||
|
child.stdin.write('Get-WmiObject Win32_SystemEnclosure | Select-Object -ExpandProperty ChassisTypes\r\n');
|
||||||
|
child.stdin.write('exit\r\n');
|
||||||
child.waitExit();
|
child.waitExit();
|
||||||
try {
|
try {
|
||||||
return (parseInt(child.stdout.str));
|
return (parseInt(child.stdout.str));
|
||||||
|
@ -992,11 +893,6 @@ module.exports.isVM = function isVM()
|
||||||
return (ret);
|
return (ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.platform == 'win32')
|
|
||||||
{
|
|
||||||
module.exports.volumes_promise = windows_volumes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bios_date = BIOS->ReleaseDate
|
// bios_date = BIOS->ReleaseDate
|
||||||
// bios_vendor = BIOS->Manufacturer
|
// bios_vendor = BIOS->Manufacturer
|
||||||
// bios_version = BIOS->SMBIOSBIOSVersion
|
// bios_version = BIOS->SMBIOSBIOSVersion
|
||||||
|
|
|
@ -243,15 +243,20 @@ function installedApps()
|
||||||
function defender(){
|
function defender(){
|
||||||
var promise = require('promise');
|
var promise = require('promise');
|
||||||
var ret = new promise(function (a, r) { this._resolve = a; this._reject = r; });
|
var ret = new promise(function (a, r) { this._resolve = a; this._reject = r; });
|
||||||
var cmd = '"Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled,IsTamperProtected | ConvertTo-JSON"';
|
ret.child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
|
||||||
ret.child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', cmd], {});
|
|
||||||
ret.child.promise = ret;
|
ret.child.promise = ret;
|
||||||
ret.child.stdout.str = ''; ret.child.stdout.on('data', function (c) { this.str += c.toString(); });
|
ret.child.stdout.str = ''; ret.child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||||
ret.child.stderr.str = ''; ret.child.stderr.on('data', function (c) { this.str += c.toString(); });
|
ret.child.stderr.str = ''; ret.child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||||
|
ret.child.stdin.write('Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled,IsTamperProtected | ConvertTo-JSON\r\n');
|
||||||
|
ret.child.stdin.write('exit\r\n');
|
||||||
ret.child.on('exit', function (c) {
|
ret.child.on('exit', function (c) {
|
||||||
if (this.stdout.str == '') { this.promise._resolve({}); return; }
|
if (this.stdout.str == '') { this.promise._resolve({}); return; }
|
||||||
var abc = JSON.parse(this.stdout.str.trim())
|
try {
|
||||||
this.promise._resolve({ RealTimeProtection: abc.RealTimeProtectionEnabled, TamperProtected: abc.IsTamperProtected });
|
var abc = JSON.parse(this.stdout.str.trim());
|
||||||
|
this.promise._resolve({ RealTimeProtection: abc.RealTimeProtectionEnabled, TamperProtected: abc.IsTamperProtected });
|
||||||
|
} catch (ex) {
|
||||||
|
this.promise._resolve({}); return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return (ret);
|
return (ret);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,17 +39,90 @@ function getVolumes()
|
||||||
{
|
{
|
||||||
ret[v[i].DeviceID] = trimObject(v[i]);
|
ret[v[i].DeviceID] = trimObject(v[i]);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume');
|
v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume');
|
||||||
for (i in v)
|
for (i in v)
|
||||||
{
|
|
||||||
var tmp = trimObject(v[i]);
|
|
||||||
for (var k in tmp)
|
|
||||||
{
|
{
|
||||||
ret[tmp.DeviceID][k] = tmp[k];
|
var tmp = trimObject(v[i]);
|
||||||
|
for (var k in tmp)
|
||||||
|
{
|
||||||
|
ret[tmp.DeviceID][k] = tmp[k];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (ex) { }
|
||||||
return (ret);
|
return (ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getVolumes: function () { try { return (getVolumes()); } catch (x) { return ({}); } } };
|
function windows_volumes()
|
||||||
|
{
|
||||||
|
var promise = require('promise');
|
||||||
|
var p1 = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
||||||
|
var ret = {};
|
||||||
|
var values = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_LogicalDisk', ['DeviceID', 'VolumeName', 'FileSystem', 'Size', 'FreeSpace', 'DriveType']);
|
||||||
|
if(values[0]){
|
||||||
|
for (var i = 0; i < values.length; ++i) {
|
||||||
|
var drive = values[i]['DeviceID'].slice(0,-1);
|
||||||
|
ret[drive] = {
|
||||||
|
name: (values[i]['VolumeName'] ? values[i]['VolumeName'] : ""),
|
||||||
|
type: (values[i]['FileSystem'] ? values[i]['FileSystem'] : "Unknown"),
|
||||||
|
size: (values[i]['Size'] ? values[i]['Size'] : 0),
|
||||||
|
sizeremaining: (values[i]['FreeSpace'] ? values[i]['FreeSpace'] : 0),
|
||||||
|
removable: (values[i]['DriveType'] == 2),
|
||||||
|
cdrom: (values[i]['DriveType'] == 5)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
values = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume', ['DriveLetter','ConversionStatus','ProtectionStatus']);
|
||||||
|
if(values[0]){
|
||||||
|
for (var i = 0; i < values.length; ++i) {
|
||||||
|
var drive = values[i]['DriveLetter'].slice(0,-1);
|
||||||
|
var statuses = {
|
||||||
|
0: 'FullyDecrypted',
|
||||||
|
1: 'FullyEncrypted',
|
||||||
|
2: 'EncryptionInProgress',
|
||||||
|
3: 'DecryptionInProgress',
|
||||||
|
4: 'EncryptionPaused',
|
||||||
|
5: 'DecryptionPaused'
|
||||||
|
};
|
||||||
|
ret[drive].volumeStatus = statuses.hasOwnProperty(values[i].ConversionStatus) ? statuses[values[i].ConversionStatus] : 'FullyDecrypted';
|
||||||
|
ret[drive].protectionStatus = (values[i].ProtectionStatus == 0 ? 'Off' : (values[i].ProtectionStatus == 1 ? 'On' : 'Unknown'));
|
||||||
|
try {
|
||||||
|
var foundIDMarkedLine = false, foundMarkedLine = false, identifier = '', password = '';
|
||||||
|
var keychild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'manage-bde -protectors -get ' + drive + ': -Type recoverypassword'], {});
|
||||||
|
keychild.stdout.str = ''; keychild.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||||
|
keychild.waitExit();
|
||||||
|
var lines = keychild.stdout.str.trim().split('\r\n');
|
||||||
|
for (var x = 0; x < lines.length; x++) { // Loop each line
|
||||||
|
var abc = lines[x].trim();
|
||||||
|
var englishidpass = (abc !== '' && abc.includes('Numerical Password:')); // English ID
|
||||||
|
var germanidpass = (abc !== '' && abc.includes('Numerisches Kennwort:')); // German ID
|
||||||
|
var frenchidpass = (abc !== '' && abc.includes('Mot de passe num')); // French ID
|
||||||
|
var englishpass = (abc !== '' && abc.includes('Password:') && !abc.includes('Numerical Password:')); // English Password
|
||||||
|
var germanpass = (abc !== '' && abc.includes('Kennwort:') && !abc.includes('Numerisches Kennwort:')); // German Password
|
||||||
|
var frenchpass = (abc !== '' && abc.includes('Mot de passe :') && !abc.includes('Mot de passe num')); // French Password
|
||||||
|
if (englishidpass || germanidpass || frenchidpass|| englishpass || germanpass || frenchpass) {
|
||||||
|
var nextline = lines[x + 1].trim();
|
||||||
|
if (x + 1 < lines.length && (nextline !== '' && (nextline.startsWith('ID:') || nextline.startsWith('ID :')) )) {
|
||||||
|
identifier = nextline.replace('ID:','').replace('ID :', '').trim();
|
||||||
|
foundIDMarkedLine = true;
|
||||||
|
}else if (x + 1 < lines.length && nextline !== '') {
|
||||||
|
password = nextline;
|
||||||
|
foundMarkedLine = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret[drive].identifier = (foundIDMarkedLine ? identifier : ''); // Set Bitlocker Identifier
|
||||||
|
ret[drive].recoveryPassword = (foundMarkedLine ? password : ''); // Set Bitlocker Password
|
||||||
|
} catch(ex) { } // just carry on as we cant get bitlocker key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p1._res(ret);
|
||||||
|
} catch (ex) { p1._res(ret); } // just return volumes as cant get encryption/bitlocker
|
||||||
|
return (p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getVolumes: function () { try { return (getVolumes()); } catch (x) { return ({}); } },
|
||||||
|
volumes_promise: windows_volumes
|
||||||
|
};
|
|
@ -870,7 +870,7 @@ function onTunnelControlData(data, ws) {
|
||||||
if (process.platform == 'win32') {
|
if (process.platform == 'win32') {
|
||||||
MeshServerLog("Locking remote user out of desktop", ws.httprequest);
|
MeshServerLog("Locking remote user out of desktop", ws.httprequest);
|
||||||
var child = require('child_process');
|
var child = require('child_process');
|
||||||
child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd', '/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
|
child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
break;
|
break;
|
||||||
|
|
BIN
agents/test_agents/MeshCmd.exe
Normal file
BIN
agents/test_agents/MeshCmd.exe
Normal file
Binary file not shown.
BIN
agents/test_agents/MeshCmd64.exe
Normal file
BIN
agents/test_agents/MeshCmd64.exe
Normal file
Binary file not shown.
BIN
agents/test_agents/MeshCmdARM64.exe
Normal file
BIN
agents/test_agents/MeshCmdARM64.exe
Normal file
Binary file not shown.
BIN
agents/test_agents/MeshService.exe
Normal file
BIN
agents/test_agents/MeshService.exe
Normal file
Binary file not shown.
BIN
agents/test_agents/MeshService64.exe
Normal file
BIN
agents/test_agents/MeshService64.exe
Normal file
Binary file not shown.
BIN
agents/test_agents/MeshServiceARM64.exe
Normal file
BIN
agents/test_agents/MeshServiceARM64.exe
Normal file
Binary file not shown.
|
@ -719,7 +719,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain, mtype) {
|
||||||
}
|
}
|
||||||
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i.trim(), header[i]); } // Set the headers if not blocked
|
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i.trim(), header[i]); } // Set the headers if not blocked
|
||||||
}
|
}
|
||||||
obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future
|
// Dont set any Content-Security-Policy at all because some applications like Node-Red, access external websites from there javascript which would be forbidden by the below CSP
|
||||||
|
//obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||||
//obj.res.set('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';"); // Set an "allow all" policy, see if the can restrict this in the future
|
//obj.res.set('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||||
obj.res.set('Cache-Control', 'no-store'); // Tell the browser not to cache the responses since since the relay port can be used for many relays
|
obj.res.set('Cache-Control', 'no-store'); // Tell the browser not to cache the responses since since the relay port can be used for many relays
|
||||||
}
|
}
|
||||||
|
|
|
@ -1049,6 +1049,7 @@ module.exports.CertificateOperations = function (parent) {
|
||||||
config.domains[i].certs = r.dns[i];
|
config.domains[i].certs = r.dns[i];
|
||||||
} else {
|
} else {
|
||||||
console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly.");
|
console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly.");
|
||||||
|
rcountmax++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the web certificate already exist, load it. Load both certificate and private key
|
// If the web certificate already exist, load it. Load both certificate and private key
|
||||||
|
|
|
@ -155,12 +155,12 @@ module.exports.objKeysToLower = function (obj, exceptions, parent) {
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Escape and unescape field names so there are no invalid characters for MongoDB
|
// Escape and unescape field names so there are no invalid characters for MongoDB/NeDB ("$", ",", ".", see https://github.com/seald/nedb/tree/master?tab=readme-ov-file#inserting-documents)
|
||||||
module.exports.escapeFieldName = function (name) { if ((name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24'); };
|
module.exports.escapeFieldName = function (name) { if ((name.indexOf(',') == -1) && (name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24').split(',').join('%2C'); };
|
||||||
module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2E').join('.').split('%24').join('$').split('%25').join('%'); };
|
module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2C').join(',').split('%2E').join('.').split('%24').join('$').split('%25').join('%'); };
|
||||||
|
|
||||||
// Escape all links, SSH and RDP usernames
|
// Escape all links, SSH and RDP usernames
|
||||||
// This is required for databases like NeDB that don't accept "." as part of a field name.
|
// This is required for databases like NeDB that don't accept "." or "," as part of a field name.
|
||||||
module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); };
|
module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); };
|
||||||
module.exports.escapeLinksFieldName = function (docx) {
|
module.exports.escapeLinksFieldName = function (docx) {
|
||||||
var doc = Object.assign({}, docx);
|
var doc = Object.assign({}, docx);
|
||||||
|
|
334
db.js
334
db.js
|
@ -30,7 +30,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
var Datastore = null;
|
var Datastore = null;
|
||||||
var expireEventsSeconds = (60 * 60 * 24 * 20); // By default, expire events after 20 days (1728000). (Seconds * Minutes * Hours * Days)
|
var expireEventsSeconds = (60 * 60 * 24 * 20); // By default, expire events after 20 days (1728000). (Seconds * Minutes * Hours * Days)
|
||||||
var expirePowerEventsSeconds = (60 * 60 * 24 * 10); // By default, expire power events after 10 days (864000). (Seconds * Minutes * Hours * Days)
|
var expirePowerEventsSeconds = (60 * 60 * 24 * 10); // By default, expire power events after 10 days (864000). (Seconds * Minutes * Hours * Days)
|
||||||
var expireServerStatsSeconds = (60 * 60 * 24 * 30); // By default, expire power events after 30 days (2592000). (Seconds * Minutes * Hours * Days)
|
var expireServerStatsSeconds = (60 * 60 * 24 * 30); // By default, expire server stats after 30 days (2592000). (Seconds * Minutes * Hours * Days)
|
||||||
const common = require('./common.js');
|
const common = require('./common.js');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -611,7 +611,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
obj.GetAllType('mesh', function (err, docs) {
|
obj.GetAllType('mesh', function (err, docs) {
|
||||||
if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
|
if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
|
||||||
if (obj.databaseType == DB_NEDB) { // If we are using NeDB, compact the database.
|
if (obj.databaseType == DB_NEDB) { // If we are using NeDB, compact the database.
|
||||||
obj.file.persistence.compactDatafile();
|
obj.file.compactDatafile();
|
||||||
obj.file.on('compaction.done', function () { func(count); }); // It's important to wait for compaction to finish before exit, otherwise NeDB may corrupt.
|
obj.file.on('compaction.done', function () { func(count); }); // It's important to wait for compaction to finish before exit, otherwise NeDB may corrupt.
|
||||||
} else {
|
} else {
|
||||||
func(count); // For all other databases, normal exit.
|
func(count); // For all other databases, normal exit.
|
||||||
|
@ -781,10 +781,10 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
parent.debug('db', 'SQlite config options: ' + JSON.stringify(obj.sqliteConfig, null, 4));
|
parent.debug('db', 'SQlite config options: ' + JSON.stringify(obj.sqliteConfig, null, 4));
|
||||||
if (obj.sqliteConfig.journalMode == 'memory') { console.log('[WARNING] journal_mode=memory: this can lead to database corruption if there is a crash during a transaction. See https://www.sqlite.org/pragma.html#pragma_journal_mode') };
|
if (obj.sqliteConfig.journalMode == 'memory') { console.log('[WARNING] journal_mode=memory: this can lead to database corruption if there is a crash during a transaction. See https://www.sqlite.org/pragma.html#pragma_journal_mode') };
|
||||||
//.cached not usefull
|
//.cached not usefull
|
||||||
obj.file = new sqlite3.Database(parent.path.join(parent.datapath, databaseName + '.sqlite'), sqlite3.OPEN_READWRITE, function (err) {
|
obj.file = new sqlite3.Database(path.join(parent.datapath, databaseName + '.sqlite'), sqlite3.OPEN_READWRITE, function (err) {
|
||||||
if (err && (err.code == 'SQLITE_CANTOPEN')) {
|
if (err && (err.code == 'SQLITE_CANTOPEN')) {
|
||||||
// Database needs to be created
|
// Database needs to be created
|
||||||
obj.file = new sqlite3.Database(parent.path.join(parent.datapath, databaseName + '.sqlite'), function (err) {
|
obj.file = new sqlite3.Database(path.join(parent.datapath, databaseName + '.sqlite'), function (err) {
|
||||||
if (err) { console.log("SQLite Error: " + err); process.exit(1); }
|
if (err) { console.log("SQLite Error: " + err); process.exit(1); }
|
||||||
obj.file.exec(`
|
obj.file.exec(`
|
||||||
CREATE TABLE main (id VARCHAR(256) PRIMARY KEY NOT NULL, type CHAR(32), domain CHAR(64), extra CHAR(255), extraex CHAR(255), doc JSON);
|
CREATE TABLE main (id VARCHAR(256) PRIMARY KEY NOT NULL, type CHAR(32), domain CHAR(64), extra CHAR(255), extraex CHAR(255), doc JSON);
|
||||||
|
@ -909,7 +909,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
} else if (parent.args.postgres) {
|
} else if (parent.args.postgres) {
|
||||||
// Postgres SQL
|
// Postgres SQL
|
||||||
let connectinArgs = parent.args.postgres;
|
let connectinArgs = parent.args.postgres;
|
||||||
connectinArgs.Database = (databaseName = (connectinArgs.database != null) ? connectinArgs.database : 'meshcentral');
|
connectinArgs.database = (databaseName = (connectinArgs.database != null) ? connectinArgs.database : 'meshcentral');
|
||||||
|
|
||||||
let DatastoreTest;
|
let DatastoreTest;
|
||||||
obj.databaseType = DB_POSTGRESQL;
|
obj.databaseType = DB_POSTGRESQL;
|
||||||
|
@ -975,7 +975,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
} else {
|
} else {
|
||||||
if ((info.versionArray[0] < 3) || ((info.versionArray[0] == 3) && (info.versionArray[1] < 6))) {
|
if ((info.versionArray[0] < 3) || ((info.versionArray[0] == 3) && (info.versionArray[1] < 6))) {
|
||||||
// We are running with mongoDB older than 3.6, this is not good.
|
// We are running with mongoDB older than 3.6, this is not good.
|
||||||
parent.addServerWarning("Current version of MongoDB (" + info.version + ") is too old, please upgrade to MongoDB 3.6 or better.");
|
parent.addServerWarning("Current version of MongoDB (" + info.version + ") is too old, please upgrade to MongoDB 3.6 or better.", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1239,8 +1239,11 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
} else {
|
} else {
|
||||||
// Use NeDB (The default)
|
// Use NeDB (The default)
|
||||||
obj.databaseType = DB_NEDB;
|
obj.databaseType = DB_NEDB;
|
||||||
try { Datastore = require('@yetzt/nedb'); } catch (ex) { } // This is the NeDB with fixed security dependencies.
|
try { Datastore = require('@seald-io/nedb'); } catch (ex) { } // This is the NeDB with Node 23 support.
|
||||||
if (Datastore == null) { Datastore = require('nedb'); } // So not to break any existing installations, if the old NeDB is present, use it.
|
if (Datastore == null) {
|
||||||
|
try { Datastore = require('@yetzt/nedb'); } catch (ex) { } // This is the NeDB with fixed security dependencies.
|
||||||
|
if (Datastore == null) { Datastore = require('nedb'); } // So not to break any existing installations, if the old NeDB is present, use it.
|
||||||
|
}
|
||||||
var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
|
var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
|
||||||
|
|
||||||
// If a DB encryption key is provided, perform database encryption
|
// If a DB encryption key is provided, perform database encryption
|
||||||
|
@ -1267,7 +1270,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
// Start NeDB main collection and setup indexes
|
// Start NeDB main collection and setup indexes
|
||||||
obj.file = new Datastore(datastoreOptions);
|
obj.file = new Datastore(datastoreOptions);
|
||||||
obj.file.persistence.setAutocompactionInterval(86400000); // Compact once a day
|
obj.file.setAutocompactionInterval(86400000); // Compact once a day
|
||||||
obj.file.ensureIndex({ fieldName: 'type' });
|
obj.file.ensureIndex({ fieldName: 'type' });
|
||||||
obj.file.ensureIndex({ fieldName: 'domain' });
|
obj.file.ensureIndex({ fieldName: 'domain' });
|
||||||
obj.file.ensureIndex({ fieldName: 'meshid', sparse: true });
|
obj.file.ensureIndex({ fieldName: 'meshid', sparse: true });
|
||||||
|
@ -1276,7 +1279,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
// Setup the events collection and setup indexes
|
// Setup the events collection and setup indexes
|
||||||
obj.eventsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-events.db'), autoload: true, corruptAlertThreshold: 1 });
|
obj.eventsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-events.db'), autoload: true, corruptAlertThreshold: 1 });
|
||||||
obj.eventsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
|
obj.eventsfile.setAutocompactionInterval(86400000); // Compact once a day
|
||||||
obj.eventsfile.ensureIndex({ fieldName: 'ids' }); // TODO: Not sure if this is a good index, this is a array field.
|
obj.eventsfile.ensureIndex({ fieldName: 'ids' }); // TODO: Not sure if this is a good index, this is a array field.
|
||||||
obj.eventsfile.ensureIndex({ fieldName: 'nodeid', sparse: true });
|
obj.eventsfile.ensureIndex({ fieldName: 'nodeid', sparse: true });
|
||||||
obj.eventsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireEventsSeconds });
|
obj.eventsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireEventsSeconds });
|
||||||
|
@ -1284,18 +1287,18 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
// Setup the power collection and setup indexes
|
// Setup the power collection and setup indexes
|
||||||
obj.powerfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-power.db'), autoload: true, corruptAlertThreshold: 1 });
|
obj.powerfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-power.db'), autoload: true, corruptAlertThreshold: 1 });
|
||||||
obj.powerfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
|
obj.powerfile.setAutocompactionInterval(86400000); // Compact once a day
|
||||||
obj.powerfile.ensureIndex({ fieldName: 'nodeid' });
|
obj.powerfile.ensureIndex({ fieldName: 'nodeid' });
|
||||||
obj.powerfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expirePowerEventsSeconds });
|
obj.powerfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expirePowerEventsSeconds });
|
||||||
obj.powerfile.remove({ time: { '$lt': new Date(Date.now() - (expirePowerEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
|
obj.powerfile.remove({ time: { '$lt': new Date(Date.now() - (expirePowerEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
|
||||||
|
|
||||||
// Setup the SMBIOS collection, for NeDB we don't setup SMBIOS since NeDB will corrupt the database. Remove any existing ones.
|
// Setup the SMBIOS collection, for NeDB we don't setup SMBIOS since NeDB will corrupt the database. Remove any existing ones.
|
||||||
//obj.smbiosfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-smbios.db'), autoload: true, corruptAlertThreshold: 1 });
|
//obj.smbiosfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-smbios.db'), autoload: true, corruptAlertThreshold: 1 });
|
||||||
parent.fs.unlink(parent.getConfigFilePath('meshcentral-smbios.db'), function () { });
|
fs.unlink(parent.getConfigFilePath('meshcentral-smbios.db'), function () { });
|
||||||
|
|
||||||
// Setup the server stats collection and setup indexes
|
// Setup the server stats collection and setup indexes
|
||||||
obj.serverstatsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-stats.db'), autoload: true, corruptAlertThreshold: 1 });
|
obj.serverstatsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-stats.db'), autoload: true, corruptAlertThreshold: 1 });
|
||||||
obj.serverstatsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
|
obj.serverstatsfile.setAutocompactionInterval(86400000); // Compact once a day
|
||||||
obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireServerStatsSeconds });
|
obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireServerStatsSeconds });
|
||||||
obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
|
obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
|
||||||
obj.serverstatsfile.remove({ time: { '$lt': new Date(Date.now() - (expireServerStatsSeconds * 1000)) } }, { multi: true }); // Force delete older events
|
obj.serverstatsfile.remove({ time: { '$lt': new Date(Date.now() - (expireServerStatsSeconds * 1000)) } }, { multi: true }); // Force delete older events
|
||||||
|
@ -1303,7 +1306,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
// Setup plugin info collection
|
// Setup plugin info collection
|
||||||
if (obj.pluginsActive) {
|
if (obj.pluginsActive) {
|
||||||
obj.pluginsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-plugins.db'), autoload: true });
|
obj.pluginsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-plugins.db'), autoload: true });
|
||||||
obj.pluginsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
|
obj.pluginsfile.setAutocompactionInterval(86400000); // Compact once a day
|
||||||
}
|
}
|
||||||
|
|
||||||
setupFunctions(func); // Completed setup of NeDB
|
setupFunctions(func); // Completed setup of NeDB
|
||||||
|
@ -1778,7 +1781,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
var query = "SELECT doc FROM events WHERE (nodeid = $1 AND domain = $2";
|
var query = "SELECT doc FROM events WHERE (nodeid = $1 AND domain = $2";
|
||||||
var dataarray = [nodeid, domain];
|
var dataarray = [nodeid, domain];
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
query = query + "AND action = $3) ORDER BY time DESC LIMIT $4";
|
query = query + " AND action = $3) ORDER BY time DESC LIMIT $4";
|
||||||
dataarray.push(filter);
|
dataarray.push(filter);
|
||||||
} else {
|
} else {
|
||||||
query = query + ") ORDER BY time DESC LIMIT $3";
|
query = query + ") ORDER BY time DESC LIMIT $3";
|
||||||
|
@ -3184,7 +3187,6 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
// Return a human readable string with current backup configuration
|
// Return a human readable string with current backup configuration
|
||||||
obj.getBackupConfig = function () {
|
obj.getBackupConfig = function () {
|
||||||
var r = '', backupPath = parent.backuppath;
|
var r = '', backupPath = parent.backuppath;
|
||||||
if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
|
|
||||||
|
|
||||||
let dbname = 'meshcentral';
|
let dbname = 'meshcentral';
|
||||||
if (parent.args.mongodbname) { dbname = parent.args.mongodbname; }
|
if (parent.args.mongodbname) { dbname = parent.args.mongodbname; }
|
||||||
|
@ -3194,7 +3196,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
|
const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
|
||||||
obj.newAutoBackupFile = ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix;
|
obj.newAutoBackupFile = parent.config.settings.autobackup.backupname + fileSuffix;
|
||||||
|
|
||||||
r += 'DB Name: ' + dbname + '\r\n';
|
r += 'DB Name: ' + dbname + '\r\n';
|
||||||
r += 'DB Type: ' + DB_LIST[obj.databaseType] + '\r\n';
|
r += 'DB Type: ' + DB_LIST[obj.databaseType] + '\r\n';
|
||||||
|
@ -3204,15 +3206,14 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
if (parent.config.settings.autobackup == null) {
|
if (parent.config.settings.autobackup == null) {
|
||||||
r += 'No Settings/AutoBackup\r\n';
|
r += 'No Settings/AutoBackup\r\n';
|
||||||
} else {
|
} else {
|
||||||
|
if (parent.config.settings.autobackup.backuphour != null && parent.config.settings.autobackup.backuphour != -1) {
|
||||||
|
r += 'Backup between: ' + parent.config.settings.autobackup.backuphour + 'H-' + (parent.config.settings.autobackup.backuphour + 1) + 'H\r\n';
|
||||||
|
}
|
||||||
if (parent.config.settings.autobackup.backupintervalhours != null) {
|
if (parent.config.settings.autobackup.backupintervalhours != null) {
|
||||||
r += 'Backup Interval (Hours): ';
|
r += 'Backup Interval (Hours): ' + parent.config.settings.autobackup.backupintervalhours + '\r\n';
|
||||||
if (typeof parent.config.settings.autobackup.backupintervalhours != 'number') { r += 'Bad backupintervalhours type\r\n'; }
|
|
||||||
else { r += parent.config.settings.autobackup.backupintervalhours + '\r\n'; }
|
|
||||||
}
|
}
|
||||||
if (parent.config.settings.autobackup.keeplastdaysbackup != null) {
|
if (parent.config.settings.autobackup.keeplastdaysbackup != null) {
|
||||||
r += 'Keep Last Backups (Days): ';
|
r += 'Keep Last Backups (Days): ' + parent.config.settings.autobackup.keeplastdaysbackup + '\r\n';
|
||||||
if (typeof parent.config.settings.autobackup.keeplastdaysbackup != 'number') { r += 'Bad keeplastdaysbackup type\r\n'; }
|
|
||||||
else { r += parent.config.settings.autobackup.keeplastdaysbackup + '\r\n'; }
|
|
||||||
}
|
}
|
||||||
if (parent.config.settings.autobackup.zippassword != null) {
|
if (parent.config.settings.autobackup.zippassword != null) {
|
||||||
r += 'ZIP Password: ';
|
r += 'ZIP Password: ';
|
||||||
|
@ -3290,7 +3291,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
if (props.ssl) {
|
if (props.ssl) {
|
||||||
sslOptions = ' --ssl';
|
sslOptions = ' --ssl';
|
||||||
if (props.ssl.cacertpath) sslOptions = ' --ssl-ca=' + props.ssl.cacertpath;
|
if (props.ssl.cacertpath) sslOptions = ' --ssl-ca=' + props.ssl.cacertpath;
|
||||||
if (props.ssl.dontcheckserveridentity != true) sslOptions += ' --ssl-verify-server-cert';
|
if (props.ssl.dontcheckserveridentity != true) {sslOptions += ' --ssl-verify-server-cert'} else {sslOptions += ' --ssl-verify-server-cert=false'};
|
||||||
if (props.ssl.clientcertpath) sslOptions += ' --ssl-cert=' + props.ssl.clientcertpath;
|
if (props.ssl.clientcertpath) sslOptions += ' --ssl-cert=' + props.ssl.clientcertpath;
|
||||||
if (props.ssl.clientkeypath) sslOptions += ' --ssl-key=' + props.ssl.clientkeypath;
|
if (props.ssl.clientkeypath) sslOptions += ' --ssl-key=' + props.ssl.clientkeypath;
|
||||||
}
|
}
|
||||||
|
@ -3327,48 +3328,70 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the server is capable of performing a backup
|
// Check that the server is capable of performing a backup
|
||||||
|
// Tries configured custom location with fallback to default location
|
||||||
|
// Now runs after autobackup config init in meshcentral.js so config options are checked
|
||||||
obj.checkBackupCapability = function (func) {
|
obj.checkBackupCapability = function (func) {
|
||||||
if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { func(); return; };
|
if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { return; };
|
||||||
|
//block backup until validated. Gets put back if all checks are ok.
|
||||||
|
let backupInterval = parent.config.settings.autobackup.backupintervalhours;
|
||||||
|
parent.config.settings.autobackup.backupintervalhours = -1;
|
||||||
let backupPath = parent.backuppath;
|
let backupPath = parent.backuppath;
|
||||||
if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
|
|
||||||
try { parent.fs.mkdirSync(backupPath); } catch (e) { }
|
|
||||||
if (parent.fs.existsSync(backupPath) == false) { func(1, "Backup folder \"" + backupPath + "\" does not exist, auto-backup will not be performed."); return; }
|
|
||||||
|
|
||||||
|
if (backupPath.startsWith(parent.datapath)) {
|
||||||
|
func(1, "Backup path can't be set within meshcentral-data folder. No backups will be made.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check create/write backupdir
|
||||||
|
try { fs.mkdirSync(backupPath); }
|
||||||
|
catch (e) {
|
||||||
|
// EEXIST error = dir already exists
|
||||||
|
if (e.code != 'EEXIST' ) {
|
||||||
|
//Unable to create backuppath
|
||||||
|
console.error(e.message);
|
||||||
|
func(1, 'Unable to create ' + backupPath + '. No backups will be made. Error: ' + e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const testFile = path.join(backupPath, (parent.config.settings.autobackup.backupname + ".test"));
|
||||||
|
|
||||||
|
try { fs.writeFileSync( testFile, "DeleteMe"); }
|
||||||
|
catch (e) {
|
||||||
|
//Unable to create file
|
||||||
|
console.error (e.message);
|
||||||
|
func(1, "Backuppath (" + backupPath + ") can't be written to. No backups will be made. Error: " + e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try { fs.unlinkSync(testFile); parent.debug('backup', 'Backuppath ' + backupPath + ' accesscheck successful');}
|
||||||
|
catch (e) {
|
||||||
|
console.error (e.message);
|
||||||
|
func(1, "Backuppathtestfile (" + testFile + ") can't be deleted, check filerights. Error: " + e.message);
|
||||||
|
// Assume write rights, no delete rights. Continue with warning.
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check database dumptools
|
||||||
if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) {
|
if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) {
|
||||||
// Check that we have access to MongoDump
|
// Check that we have access to MongoDump
|
||||||
var cmd = buildMongoDumpCommand();
|
var cmd = buildMongoDumpCommand();
|
||||||
cmd += (parent.platform == 'win32') ? ' --archive=\"nul\"' : ' --archive=\"/dev/null\"';
|
cmd += (parent.platform == 'win32') ? ' --archive=\"nul\"' : ' --archive=\"/dev/null\"';
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
child_process.exec(cmd, { cwd: backupPath }, function (error, stdout, stderr) {
|
child_process.exec(cmd, { cwd: backupPath }, function (error, stdout, stderr) {
|
||||||
try {
|
if ((error != null) && (error != '')) {
|
||||||
if ((error != null) && (error != '')) {
|
func(1, "Unable to find mongodump tool, backup will not be performed. Command tried: " + cmd);
|
||||||
if (parent.platform == 'win32') {
|
return;
|
||||||
func(1, "Unable to find mongodump.exe, MongoDB database auto-backup will not be performed.");
|
} else {parent.config.settings.autobackup.backupintervalhours = backupInterval;}
|
||||||
} else {
|
|
||||||
func(1, "Unable to find mongodump, MongoDB database auto-backup will not be performed.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
} catch (ex) { console.log(ex); }
|
|
||||||
});
|
});
|
||||||
} else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) {
|
} else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) {
|
||||||
// Check that we have access to mysqldump
|
// Check that we have access to mysqldump
|
||||||
var cmd = buildSqlDumpCommand();
|
var cmd = buildSqlDumpCommand();
|
||||||
cmd += ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"');
|
cmd += ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"');
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
child_process.exec(cmd, { cwd: backupPath }, function(error, stdout, stdin) {
|
child_process.exec(cmd, { cwd: backupPath, timeout: 1000*30 }, function(error, stdout, stdin) {
|
||||||
try {
|
if ((error != null) && (error != '')) {
|
||||||
if ((error != null) && (error != '')) {
|
func(1, "Unable to find mysqldump tool, backup will not be performed. Command tried: " + cmd);
|
||||||
if (parent.platform == 'win32') {
|
return;
|
||||||
func(1, "Unable to find mysqldump.exe, MySQL/MariaDB database auto-backup will not be performed.");
|
} else {parent.config.settings.autobackup.backupintervalhours = backupInterval;}
|
||||||
} else {
|
|
||||||
func(1, "Unable to find mysqldump, MySQL/MariaDB database auto-backup will not be performed.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
} catch (ex) { console.log(ex); }
|
|
||||||
});
|
});
|
||||||
} else if (obj.databaseType == DB_POSTGRESQL) {
|
} else if (obj.databaseType == DB_POSTGRESQL) {
|
||||||
// Check that we have access to pg_dump
|
// Check that we have access to pg_dump
|
||||||
|
@ -3379,17 +3402,14 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
+ ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"');
|
+ ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"');
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
child_process.exec(cmd, { cwd: backupPath }, function(error, stdout, stdin) {
|
child_process.exec(cmd, { cwd: backupPath }, function(error, stdout, stdin) {
|
||||||
try {
|
if ((error != null) && (error != '')) {
|
||||||
if ((error != null) && (error != '')) {
|
func(1, "Unable to find pg_dump tool, backup will not be performed. Command tried: " + cmd);
|
||||||
func(1, "Unable to find pg_dump, PostgreSQL database auto-backup will not be performed.");
|
return;
|
||||||
} else {
|
} else {parent.config.settings.autobackup.backupintervalhours = backupInterval;}
|
||||||
func();
|
|
||||||
}
|
|
||||||
} catch (ex) { console.log(ex); }
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
func();
|
//all ok, enable backup
|
||||||
}
|
parent.config.settings.autobackup.backupintervalhours = backupInterval;}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MongoDB pending bulk read operation, perform fast bulk document reads.
|
// MongoDB pending bulk read operation, perform fast bulk document reads.
|
||||||
|
@ -3503,19 +3523,18 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
// Perform a server backup
|
// Perform a server backup
|
||||||
obj.performBackup = function (func) {
|
obj.performBackup = function (func) {
|
||||||
parent.debug('db','Entering performBackup');
|
parent.debug('backup','Entering performBackup');
|
||||||
try {
|
try {
|
||||||
if (obj.performingBackup) return 'Backup alreay in progress.';
|
if (obj.performingBackup) return 'Backup alreay in progress.';
|
||||||
if (parent.config.settings.autobackup.backupintervalhours == -1) { if (func) { func('Unable to create backup if backuppath is set to the data folder.'); return 'Backup aborted.' }};
|
if (parent.config.settings.autobackup.backupintervalhours == -1) { if (func) { func('Backup disabled.'); return 'Backup disabled.' }};
|
||||||
obj.performingBackup = true;
|
obj.performingBackup = true;
|
||||||
let backupPath = parent.backuppath;
|
let backupPath = parent.backuppath;
|
||||||
let dataPath = parent.datapath;
|
let dataPath = parent.datapath;
|
||||||
|
|
||||||
if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
|
|
||||||
try { parent.fs.mkdirSync(backupPath); } catch (e) { }
|
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
|
const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
|
||||||
obj.newAutoBackupFile = path.join(backupPath, ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix + '.zip');
|
obj.newAutoBackupFile = path.join(backupPath, parent.config.settings.autobackup.backupname + fileSuffix + '.zip');
|
||||||
|
parent.debug('backup','newAutoBackupFile=' + obj.newAutoBackupFile);
|
||||||
|
|
||||||
if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) {
|
if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) {
|
||||||
// Perform a MongoDump
|
// Perform a MongoDump
|
||||||
|
@ -3527,13 +3546,14 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
var cmd = buildMongoDumpCommand();
|
var cmd = buildMongoDumpCommand();
|
||||||
cmd += (dburl) ? ' --archive=\"' + obj.newDBDumpFile + '\"' :
|
cmd += (dburl) ? ' --archive=\"' + obj.newDBDumpFile + '\"' :
|
||||||
' --db=\"' + dbname + '\" --archive=\"' + obj.newDBDumpFile + '\"';
|
' --db=\"' + dbname + '\" --archive=\"' + obj.newDBDumpFile + '\"';
|
||||||
|
parent.debug('backup','Mongodump cmd: ' + cmd);
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
const dumpProcess = child_process.exec(
|
const dumpProcess = child_process.exec(
|
||||||
cmd,
|
cmd,
|
||||||
{ cwd: parent.parentpath },
|
{ cwd: parent.parentpath },
|
||||||
(error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); obj.createBackupfile(func);}}
|
(error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.error('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); obj.createBackupfile(func);}}
|
||||||
);
|
);
|
||||||
|
|
||||||
dumpProcess.on('exit', (code) => {
|
dumpProcess.on('exit', (code) => {
|
||||||
if (code != 0) {console.log(`Mongodump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;}
|
if (code != 0) {console.log(`Mongodump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;}
|
||||||
obj.createBackupfile(func);
|
obj.createBackupfile(func);
|
||||||
|
@ -3546,15 +3566,16 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
var cmd = buildSqlDumpCommand();
|
var cmd = buildSqlDumpCommand();
|
||||||
cmd += ' --result-file=\"' + obj.newDBDumpFile + '\"';
|
cmd += ' --result-file=\"' + obj.newDBDumpFile + '\"';
|
||||||
|
parent.debug('backup','Maria/MySQLdump cmd: ' + cmd);
|
||||||
|
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
const dumpProcess = child_process.exec(
|
const dumpProcess = child_process.exec(
|
||||||
cmd,
|
cmd,
|
||||||
{ cwd: parent.parentpath },
|
{ cwd: parent.parentpath },
|
||||||
(error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform MySQL backup: ' + error + '\r\n'); obj.createBackupfile(func);}}
|
(error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.error('ERROR: Unable to perform MySQL backup: ' + error + '\r\n'); obj.createBackupfile(func);}}
|
||||||
);
|
);
|
||||||
dumpProcess.on('exit', (code) => {
|
dumpProcess.on('exit', (code) => {
|
||||||
if (code != 0) {console.log(`MySQLdump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;}
|
if (code != 0) {console.error(`MySQLdump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;}
|
||||||
obj.createBackupfile(func);
|
obj.createBackupfile(func);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3562,8 +3583,9 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
//.db3 suffix to escape escape backupfile glob to exclude the sqlite db files
|
//.db3 suffix to escape escape backupfile glob to exclude the sqlite db files
|
||||||
obj.newDBDumpFile = path.join(backupPath, databaseName + '-sqlitedump-' + fileSuffix + '.db3');
|
obj.newDBDumpFile = path.join(backupPath, databaseName + '-sqlitedump-' + fileSuffix + '.db3');
|
||||||
// do a VACUUM INTO in favor of the backup API to compress the export, see https://www.sqlite.org/backup.html
|
// do a VACUUM INTO in favor of the backup API to compress the export, see https://www.sqlite.org/backup.html
|
||||||
|
parent.debug('backup','SQLitedump: VACUUM INTO ' + obj.newDBDumpFile);
|
||||||
obj.file.exec('VACUUM INTO \'' + obj.newDBDumpFile + '\'', function (err) {
|
obj.file.exec('VACUUM INTO \'' + obj.newDBDumpFile + '\'', function (err) {
|
||||||
if (err) { console.log('SQLite start-backup error: ' + err); obj.backupStatus |=BACKUPFAIL_DBDUMP;};
|
if (err) { console.error('SQLite backup error: ' + err); obj.backupStatus |=BACKUPFAIL_DBDUMP;};
|
||||||
//always finish/clean up
|
//always finish/clean up
|
||||||
obj.createBackupfile(func);
|
obj.createBackupfile(func);
|
||||||
});
|
});
|
||||||
|
@ -3575,6 +3597,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
+ ' --dbname=postgresql://' + parent.config.settings.postgres.user + ":" +parent.config.settings.postgres.password
|
+ ' --dbname=postgresql://' + parent.config.settings.postgres.user + ":" +parent.config.settings.postgres.password
|
||||||
+ "@" + parent.config.settings.postgres.host + ":" + parent.config.settings.postgres.port + "/" + databaseName
|
+ "@" + parent.config.settings.postgres.host + ":" + parent.config.settings.postgres.port + "/" + databaseName
|
||||||
+ " --file=" + obj.newDBDumpFile;
|
+ " --file=" + obj.newDBDumpFile;
|
||||||
|
parent.debug('backup','Postgresqldump cmd: ' + cmd);
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
const dumpProcess = child_process.exec(
|
const dumpProcess = child_process.exec(
|
||||||
cmd,
|
cmd,
|
||||||
|
@ -3586,15 +3609,15 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
obj.createBackupfile(func);
|
obj.createBackupfile(func);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//NeDB backup, no db dump needed, just make a file backup
|
// NeDB/Acebase backup, no db dump needed, just make a file backup
|
||||||
obj.createBackupfile(func);
|
obj.createBackupfile(func);
|
||||||
}
|
}
|
||||||
} catch (ex) { console.log(ex); };
|
} catch (ex) { console.error(ex); parent.addServerWarning( 'Something went wrong during performBackup, check errorlog: ' +ex.message, true); };
|
||||||
return 'Starting auto-backup...';
|
return 'Starting auto-backup...';
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.createBackupfile = function(func) {
|
obj.createBackupfile = function(func) {
|
||||||
parent.debug('db', 'Entering createFileBackup');
|
parent.debug('backup', 'Entering createBackupfile');
|
||||||
let archiver = require('archiver');
|
let archiver = require('archiver');
|
||||||
let archive = null;
|
let archive = null;
|
||||||
let zipLevel = Math.min(Math.max(Number(parent.config.settings.autobackup.zipcompression ? parent.config.settings.autobackup.zipcompression : 5),1),9);
|
let zipLevel = Math.min(Math.max(Number(parent.config.settings.autobackup.zipcompression ? parent.config.settings.autobackup.zipcompression : 5),1),9);
|
||||||
|
@ -3608,8 +3631,8 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
if (func) { func('Creating encrypted ZIP'); }
|
if (func) { func('Creating encrypted ZIP'); }
|
||||||
} catch (ex) { // registering encryption failed, do not fall back to non-encrypted, fail backup and skip old backup removal as a precaution to not lose any backups
|
} catch (ex) { // registering encryption failed, do not fall back to non-encrypted, fail backup and skip old backup removal as a precaution to not lose any backups
|
||||||
obj.backupStatus |= BACKUPFAIL_ZIPMODULE;
|
obj.backupStatus |= BACKUPFAIL_ZIPMODULE;
|
||||||
if (func) { func('Zipencryptionmodule failed, aborting'); }
|
if (func) { func('Zipencryptionmodule failed, aborting');}
|
||||||
console.log('Zipencryptionmodule failed, aborting');
|
console.error('Zipencryptionmodule failed, aborting');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (func) { func('Creating a NON-ENCRYPTED ZIP'); }
|
if (func) { func('Creating a NON-ENCRYPTED ZIP'); }
|
||||||
|
@ -3619,51 +3642,36 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
//original behavior, just a filebackup if dbdump fails : (obj.backupStatus == 0 || obj.backupStatus == BACKUPFAIL_DBDUMP)
|
//original behavior, just a filebackup if dbdump fails : (obj.backupStatus == 0 || obj.backupStatus == BACKUPFAIL_DBDUMP)
|
||||||
if (obj.backupStatus == 0) {
|
if (obj.backupStatus == 0) {
|
||||||
// Zip the data directory with the dbdump|NeDB files
|
// Zip the data directory with the dbdump|NeDB files
|
||||||
let output = parent.fs.createWriteStream(obj.newAutoBackupFile);
|
let output = fs.createWriteStream(obj.newAutoBackupFile);
|
||||||
|
|
||||||
|
// Archive finalized and closed
|
||||||
output.on('close', function () {
|
output.on('close', function () {
|
||||||
if (obj.backupStatus == 0) {
|
if (obj.backupStatus == 0) {
|
||||||
//remove dump archive file, because zipped and otherwise fills up
|
let mesg = 'Auto-backup completed: ' + obj.newAutoBackupFile + ', backup-size: ' + ((archive.pointer() / 1048576).toFixed(2)) + "Mb";
|
||||||
if (obj.databaseType != DB_NEDB) {
|
console.log(mesg);
|
||||||
try { parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) {console.log('Failed to clean up dbdump file')};
|
if (func) { func(mesg); };
|
||||||
};
|
|
||||||
obj.performCloudBackup(obj.newAutoBackupFile, func);
|
obj.performCloudBackup(obj.newAutoBackupFile, func);
|
||||||
// Remove old backups
|
obj.removeExpiredBackupfiles(func);
|
||||||
if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) {
|
|
||||||
let cutoffDate = new Date();
|
|
||||||
cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup);
|
|
||||||
parent.fs.readdir(parent.backuppath, function (err, dir) {
|
|
||||||
try {
|
|
||||||
if ((err == null) && (dir.length > 0)) {
|
|
||||||
let fileName = (typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-';
|
|
||||||
for (var i in dir) {
|
|
||||||
var name = dir[i];
|
|
||||||
if (name.startsWith(fileName) && name.endsWith('.zip')) {
|
|
||||||
var timex = name.substring(23, name.length - 4).split('-');
|
|
||||||
if (timex.length == 5) {
|
|
||||||
var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4]));
|
|
||||||
if (fileDate && (cutoffDate > fileDate)) { try { parent.fs.unlink(parent.path.join(parent.backuppath, name), function () { }); } catch (ex) { } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ex) { console.log(ex); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log('Auto-backup completed.');
|
|
||||||
if (func) { func('Auto-backup completed.'); };
|
|
||||||
} else {
|
} else {
|
||||||
console.log('Zipbackup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + '), deleting incomplete backup: ' + obj.newAutoBackupFile );
|
let mesg = 'Zipbackup failed (' + obj.backupStatus.toString(2).slice(-8) + '), deleting incomplete backup: ' + obj.newAutoBackupFile;
|
||||||
if (func) { func('Zipbackup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + '), deleting incomplete backup: ' + obj.newAutoBackupFile) };
|
if (func) { func(mesg) }
|
||||||
try { parent.fs.unlink(obj.newAutoBackupFile, function () { }); parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) {console.log('Failed to delete incomplete backup files')};
|
else { parent.addServerWarning(mesg, true ) };
|
||||||
|
if (fs.existsSync(obj.newAutoBackupFile)) { fs.unlink(obj.newAutoBackupFile, function (err) { console.error('Failed to clean up backupfile: ' + err.message) }) };
|
||||||
|
};
|
||||||
|
if (obj.databaseType != DB_NEDB) {
|
||||||
|
//remove dump archive file, because zipped and otherwise fills up
|
||||||
|
if (fs.existsSync(obj.newDBDumpFile)) { fs.unlink(obj.newDBDumpFile, function (err) { if (err) {console.error('Failed to clean up dbdump file: ' + err.message) } }) };
|
||||||
};
|
};
|
||||||
obj.performingBackup = false;
|
obj.performingBackup = false;
|
||||||
obj.backupStatus = 0x0;
|
obj.backupStatus = 0x0;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
output.on('end', function () { });
|
output.on('end', function () { });
|
||||||
output.on('error', function (err) {
|
output.on('error', function (err) {
|
||||||
if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
|
if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
|
||||||
console.log('Output error: ' + err);
|
console.error('Output error: ' + err.message);
|
||||||
if (func) { func('Output error: ' + err); };
|
if (func) { func('Output error: ' + err.message); };
|
||||||
obj.backupStatus |= BACKUPFAIL_ZIPCREATE;
|
obj.backupStatus |= BACKUPFAIL_ZIPCREATE;
|
||||||
archive.abort();
|
archive.abort();
|
||||||
};
|
};
|
||||||
|
@ -3673,16 +3681,16 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
//an ENOENT warning is given, but the archiver module has no option to/does not skip/resume
|
//an ENOENT warning is given, but the archiver module has no option to/does not skip/resume
|
||||||
//so the backup needs te be aborted as it otherwise leaves an incomplete zip and never 'ends'
|
//so the backup needs te be aborted as it otherwise leaves an incomplete zip and never 'ends'
|
||||||
if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
|
if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
|
||||||
console.log('Zip warning: ' + err);
|
console.log('Zip warning: ' + err.message);
|
||||||
if (func) { func('Zip warning: ' + err); };
|
if (func) { func('Zip warning: ' + err.message); };
|
||||||
obj.backupStatus |= BACKUPFAIL_ZIPCREATE;
|
obj.backupStatus |= BACKUPFAIL_ZIPCREATE;
|
||||||
archive.abort();
|
archive.abort();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
archive.on('error', function (err) {
|
archive.on('error', function (err) {
|
||||||
if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
|
if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
|
||||||
console.log('Zip error: ' + err);
|
console.error('Zip error: ' + err.message);
|
||||||
if (func) { func('Zip error: ' + err); };
|
if (func) { func('Zip error: ' + err.message); };
|
||||||
obj.backupStatus |= BACKUPFAIL_ZIPCREATE;
|
obj.backupStatus |= BACKUPFAIL_ZIPCREATE;
|
||||||
archive.abort();
|
archive.abort();
|
||||||
}
|
}
|
||||||
|
@ -3715,22 +3723,67 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
archive.finalize();
|
archive.finalize();
|
||||||
} else {
|
} else {
|
||||||
//failed somewhere before zipping
|
//failed somewhere before zipping
|
||||||
console.log('Backup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + ')');
|
console.error('Backup failed ('+ obj.backupStatus.toString(2).slice(-8) + ')');
|
||||||
if (func) { func('Backup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + ')') };
|
if (func) { func('Backup failed ('+ obj.backupStatus.toString(2).slice(-8) + ')') }
|
||||||
|
else {
|
||||||
|
parent.addServerWarning('Backup failed ('+ obj.backupStatus.toString(2).slice(-8) + ')', true);
|
||||||
|
}
|
||||||
//Just in case something's there
|
//Just in case something's there
|
||||||
try { parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) { };
|
if (fs.existsSync(obj.newDBDumpFile)) { fs.unlink(obj.newDBDumpFile, function (err) { if (err) {console.error('Failed to clean up dbdump file: ' + err.message) } }); };
|
||||||
obj.backupStatus = 0x0;
|
obj.backupStatus = 0x0;
|
||||||
obj.performingBackup = false;
|
obj.performingBackup = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remove expired backupfiles by filenamedate
|
||||||
|
obj.removeExpiredBackupfiles = function (func) {
|
||||||
|
if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) {
|
||||||
|
let cutoffDate = new Date();
|
||||||
|
cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup);
|
||||||
|
fs.readdir(parent.backuppath, function (err, dir) {
|
||||||
|
try {
|
||||||
|
if (err == null) {
|
||||||
|
if (dir.length > 0) {
|
||||||
|
let fileName = parent.config.settings.autobackup.backupname;
|
||||||
|
let checked = 0;
|
||||||
|
let removed = 0;
|
||||||
|
for (var i in dir) {
|
||||||
|
var name = dir[i];
|
||||||
|
parent.debug('backup', "checking file: ", path.join(parent.backuppath, name));
|
||||||
|
if (name.startsWith(fileName) && name.endsWith('.zip')) {
|
||||||
|
var timex = name.substring(fileName.length, name.length - 4).split('-');
|
||||||
|
if (timex.length == 5) {
|
||||||
|
checked++;
|
||||||
|
var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4]));
|
||||||
|
if (fileDate && (cutoffDate > fileDate)) {
|
||||||
|
console.log("Removing expired backup file: ", path.join(parent.backuppath, name));
|
||||||
|
fs.unlink(path.join(parent.backuppath, name), function (err) { if (err) { console.error(err.message); if (func) {func('Error removing: ' + err.message); } } });
|
||||||
|
removed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { parent.debug('backup', "file: " + name + " timestamp failure: ", timex); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mesg= 'Checked ' + checked + ' candidates in ' + parent.backuppath + '. Removed ' + removed + ' expired backupfiles using cutoffDate: '+ cutoffDate.toLocaleString('default', { dateStyle: 'short', timeStyle: 'short' });
|
||||||
|
parent.debug (mesg);
|
||||||
|
if (func) { func(mesg); }
|
||||||
|
} else { console.error('No files found in ' + parent.backuppath + '. There should be at least one.')}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ console.error(err); parent.addServerWarning( 'Reading files in backup directory ' + parent.backuppath + ' failed, check errorlog: ' + err.message, true); }
|
||||||
|
} catch (ex) { console.error(ex); parent.addServerWarning( 'Something went wrong during removeExpiredBackupfiles, check errorlog: ' +ex.message, true); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Perform cloud backup
|
// Perform cloud backup
|
||||||
obj.performCloudBackup = function (filename, func) {
|
obj.performCloudBackup = function (filename, func) {
|
||||||
|
|
||||||
// WebDAV Backup
|
// WebDAV Backup
|
||||||
if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) {
|
if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) {
|
||||||
const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
|
parent.debug( 'backup', 'Entering WebDAV backup');
|
||||||
|
if (func) { func('Entering WebDAV backup.'); }
|
||||||
|
|
||||||
|
const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
|
||||||
// Fetch the folder name
|
// Fetch the folder name
|
||||||
var webdavfolderName = 'MeshCentral-Backups';
|
var webdavfolderName = 'MeshCentral-Backups';
|
||||||
if (typeof parent.config.settings.autobackup.webdav.foldername == 'string') { webdavfolderName = parent.config.settings.autobackup.webdav.foldername; }
|
if (typeof parent.config.settings.autobackup.webdav.foldername == 'string') { webdavfolderName = parent.config.settings.autobackup.webdav.foldername; }
|
||||||
|
@ -3738,23 +3791,28 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
// Clean up our WebDAV folder
|
// Clean up our WebDAV folder
|
||||||
function performWebDavCleanup(client) {
|
function performWebDavCleanup(client) {
|
||||||
if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) {
|
if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) {
|
||||||
let fileName = (typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-';
|
let fileName = parent.config.settings.autobackup.backupname;
|
||||||
//only files matching our backupfilename
|
//only files matching our backupfilename
|
||||||
let directoryItems = client.getDirectoryContents(webdavfolderName, { deep: false, glob: "/**/" + fileName + "*.zip" });
|
let directoryItems = client.getDirectoryContents(webdavfolderName, { deep: false, glob: "/**/" + fileName + "*.zip" });
|
||||||
directoryItems.then(
|
directoryItems.then(
|
||||||
function (files) {
|
function (files) {
|
||||||
for (var i in files) { files[i].xdate = new Date(files[i].lastmod); }
|
for (var i in files) { files[i].xdate = new Date(files[i].lastmod); }
|
||||||
files.sort(xdateTimeSort);
|
files.sort(xdateTimeSort);
|
||||||
|
parent.debug('backup','WebDAV filtered directory contents: ' + JSON.stringify(files, null, 4));
|
||||||
while (files.length >= parent.config.settings.autobackup.webdav.maxfiles) {
|
while (files.length >= parent.config.settings.autobackup.webdav.maxfiles) {
|
||||||
client.deleteFile(files.shift().filename).then(function (state) {
|
let delFile = files.shift().filename;
|
||||||
if (func) { func('WebDAV file deleted.'); }
|
client.deleteFile(delFile).then(function (state) {
|
||||||
|
parent.debug('backup','WebDAV file deleted: ' + delFile);
|
||||||
|
if (func) { func('WebDAV file deleted: ' + delFile); }
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (func) { func('WebDAV (deleteFile) error: ' + err); }
|
console.error(err);
|
||||||
|
if (func) { func('WebDAV (deleteFile) error: ' + err.message); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).catch(function (err) {
|
).catch(function (err) {
|
||||||
if (func) { func('WebDAV (getDirectoryContents) error: ' + err); }
|
console.error(err);
|
||||||
|
if (func) { func('WebDAV (getDirectoryContents) error: ' + err.message); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3763,14 +3821,14 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
function performWebDavUpload(client, filepath) {
|
function performWebDavUpload(client, filepath) {
|
||||||
require('fs').stat(filepath, function(err,stat){
|
require('fs').stat(filepath, function(err,stat){
|
||||||
var fileStream = require('fs').createReadStream(filepath);
|
var fileStream = require('fs').createReadStream(filepath);
|
||||||
fileStream.on('close', function () { if (func) { func('WebDAV upload completed'); } })
|
fileStream.on('close', function () { console.log('WebDAV upload completed: ' + webdavfolderName + '/' + require('path').basename(filepath)); if (func) { func('WebDAV upload completed: ' + webdavfolderName + '/' + require('path').basename(filepath)); } })
|
||||||
fileStream.on('error', function (err) { if (func) { func('WebDAV (fileUpload) error: ' + err); } })
|
fileStream.on('error', function (err) { console.error(err); if (func) { func('WebDAV (fileUpload) error: ' + err.message); } })
|
||||||
fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath), { headers: { "Content-Length": stat.size } }));
|
fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath), { headers: { "Content-Length": stat.size } }));
|
||||||
if (func) { func('Uploading using WebDAV...'); }
|
parent.debug('backup', 'Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url);
|
||||||
|
if (func) { func('Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (func) { func('Attempting WebDAV upload...'); }
|
|
||||||
const { createClient } = require('webdav');
|
const { createClient } = require('webdav');
|
||||||
const client = createClient(parent.config.settings.autobackup.webdav.url, {
|
const client = createClient(parent.config.settings.autobackup.webdav.url, {
|
||||||
username: parent.config.settings.autobackup.webdav.username,
|
username: parent.config.settings.autobackup.webdav.username,
|
||||||
|
@ -3784,19 +3842,23 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
performWebDavUpload(client, filename);
|
performWebDavUpload(client, filename);
|
||||||
}else{
|
}else{
|
||||||
client.createDirectory(webdavfolderName, {recursive: true}).then(function (a) {
|
client.createDirectory(webdavfolderName, {recursive: true}).then(function (a) {
|
||||||
if (func) { func('WebDAV folder created'); }
|
console.log('backup','WebDAV folder created: ' + webdavfolderName);
|
||||||
|
if (func) { func('WebDAV folder created: ' + webdavfolderName); }
|
||||||
performWebDavUpload(client, filename);
|
performWebDavUpload(client, filename);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (func) { func('WebDAV (createDirectory) error: ' + err); }
|
console.error(err);
|
||||||
|
if (func) { func('WebDAV (createDirectory) error: ' + err.message); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (func) { func('WebDAV (exists) error: ' + err); }
|
console.error(err);
|
||||||
|
if (func) { func('WebDAV (exists) error: ' + err.message); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Google Drive Backup
|
// Google Drive Backup
|
||||||
if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.googledrive == 'object')) {
|
if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.googledrive == 'object')) {
|
||||||
|
parent.debug( 'backup', 'Entering Google Drive backup');
|
||||||
obj.Get('GoogleDriveBackup', function (err, docs) {
|
obj.Get('GoogleDriveBackup', function (err, docs) {
|
||||||
if ((err != null) || (docs.length != 1) || (docs[0].state != 3)) return;
|
if ((err != null) || (docs.length != 1) || (docs[0].state != 3)) return;
|
||||||
if (func) { func('Attempting Google Drive upload...'); }
|
if (func) { func('Attempting Google Drive upload...'); }
|
||||||
|
@ -3875,6 +3937,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
// S3 Backup
|
// S3 Backup
|
||||||
if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.s3 == 'object')) {
|
if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.s3 == 'object')) {
|
||||||
|
parent.debug( 'backup', 'Entering S3 backup');
|
||||||
var s3folderName = 'MeshCentral-Backups';
|
var s3folderName = 'MeshCentral-Backups';
|
||||||
if (typeof parent.config.settings.autobackup.s3.foldername == 'string') { s3folderName = parent.config.settings.autobackup.s3.foldername; }
|
if (typeof parent.config.settings.autobackup.s3.foldername == 'string') { s3folderName = parent.config.settings.autobackup.s3.foldername; }
|
||||||
// Construct the config object
|
// Construct the config object
|
||||||
|
@ -3970,8 +4033,11 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
// Transfer NeDB data into the current database
|
// Transfer NeDB data into the current database
|
||||||
obj.nedbtodb = function (func) {
|
obj.nedbtodb = function (func) {
|
||||||
var nedbDatastore = null;
|
var nedbDatastore = null;
|
||||||
try { nedbDatastore = require('@yetzt/nedb'); } catch (ex) { } // This is the NeDB with fixed security dependencies.
|
try { nedbDatastore = require('@seald-io/nedb'); } catch (ex) { } // This is the NeDB with Node 23 support.
|
||||||
if (nedbDatastore == null) { nedbDatastore = require('nedb'); } // So not to break any existing installations, if the old NeDB is present, use it.
|
if (nedbDatastore == null) {
|
||||||
|
try { nedbDatastore = require('@yetzt/nedb'); } catch (ex) { } // This is the NeDB with fixed security dependencies.
|
||||||
|
if (nedbDatastore == null) { nedbDatastore = require('nedb'); } // So not to break any existing installations, if the old NeDB is present, use it.
|
||||||
|
}
|
||||||
|
|
||||||
var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
|
var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
|
||||||
|
|
||||||
|
@ -4150,7 +4216,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||||
|
|
||||||
function dbMergeSqlArray(arr) {
|
function dbMergeSqlArray(arr) {
|
||||||
var x = '';
|
var x = '';
|
||||||
for (var i in arr) { if (x != '') { x += ','; } x += '"' + arr[i] + '"'; }
|
for (var i in arr) { if (x != '') { x += ','; } x += '\'' + arr[i] + '\''; }
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
"@yetzt/nedb": "1.8.0",
|
"@seald-io/nedb": "4.0.4",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"cbor": "5.2.0",
|
"cbor": "5.2.0",
|
||||||
"compression": "1.7.4",
|
"compression": "1.7.5",
|
||||||
"cookie-session": "2.1.0",
|
"cookie-session": "2.1.0",
|
||||||
"express": "4.21.1",
|
"express": "4.21.2",
|
||||||
"express-handlebars": "7.1.3",
|
"express-handlebars": "7.1.3",
|
||||||
"express-ws": "5.0.2",
|
"express-ws": "5.0.2",
|
||||||
"ipcheck": "0.1.0",
|
"ipcheck": "0.1.0",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM --platform=$BUILDPLATFORM node:20-alpine AS builder
|
FROM --platform=$BUILDPLATFORM node:22-alpine AS builder
|
||||||
|
|
||||||
RUN mkdir -p /opt/meshcentral/meshcentral
|
RUN mkdir -p /opt/meshcentral/meshcentral
|
||||||
COPY ./ /opt/meshcentral/meshcentral/
|
COPY ./ /opt/meshcentral/meshcentral/
|
||||||
|
@ -34,7 +34,7 @@ RUN rm -rf /opt/meshcentral/meshcentral/docker
|
||||||
RUN rm -rf /opt/meshcentral/meshcentral/node_modules
|
RUN rm -rf /opt/meshcentral/meshcentral/node_modules
|
||||||
|
|
||||||
|
|
||||||
FROM --platform=$TARGETPLATFORM alpine:3.19
|
FROM --platform=$TARGETPLATFORM alpine:3.21
|
||||||
|
|
||||||
#Add non-root user, add installation directories and assign proper permissions
|
#Add non-root user, add installation directories and assign proper permissions
|
||||||
RUN mkdir -p /opt/meshcentral/meshcentral
|
RUN mkdir -p /opt/meshcentral/meshcentral
|
||||||
|
@ -62,8 +62,8 @@ ENV MONGO_URL=""
|
||||||
ENV HOSTNAME="localhost"
|
ENV HOSTNAME="localhost"
|
||||||
ENV ALLOW_NEW_ACCOUNTS="true"
|
ENV ALLOW_NEW_ACCOUNTS="true"
|
||||||
ENV ALLOWPLUGINS="false"
|
ENV ALLOWPLUGINS="false"
|
||||||
ENV LOCALSESSIONRECORDING="false"
|
ENV LOCALSESSIONRECORDING="true"
|
||||||
ENV MINIFY="true"
|
ENV MINIFY="false"
|
||||||
ENV WEBRTC="false"
|
ENV WEBRTC="false"
|
||||||
ENV IFRAME="false"
|
ENV IFRAME="false"
|
||||||
ENV SESSION_KEY=""
|
ENV SESSION_KEY=""
|
||||||
|
@ -83,8 +83,8 @@ COPY --from=builder /opt/meshcentral/meshcentral /opt/meshcentral/meshcentral
|
||||||
COPY ./docker/startup.sh ./startup.sh
|
COPY ./docker/startup.sh ./startup.sh
|
||||||
COPY ./docker/config.json.template /opt/meshcentral/config.json.template
|
COPY ./docker/config.json.template /opt/meshcentral/config.json.template
|
||||||
|
|
||||||
# install dependencies from package.json and nedb
|
# install dependencies from package.json
|
||||||
RUN cd meshcentral && npm install && npm install nedb
|
RUN cd meshcentral && npm install
|
||||||
|
|
||||||
# NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN meshcentral.js mainStart()
|
# NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN meshcentral.js mainStart()
|
||||||
RUN if ! [ -z "$INCLUDE_MONGODBTOOLS" ]; then cd meshcentral && npm install mongodb@4.13.0 saslprep@1.0.3; fi
|
RUN if ! [ -z "$INCLUDE_MONGODBTOOLS" ]; then cd meshcentral && npm install mongodb@4.13.0 saslprep@1.0.3; fi
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
"": {
|
"": {
|
||||||
"_title": "MyServer",
|
"_title": "MyServer",
|
||||||
"_title2": "Servername",
|
"_title2": "Servername",
|
||||||
"minify": true,
|
"minify": false,
|
||||||
"NewAccounts": true,
|
"NewAccounts": true,
|
||||||
"localSessionRecording": false,
|
"localSessionRecording": true,
|
||||||
"_userNameIsEmail": true,
|
"_userNameIsEmail": true,
|
||||||
"_certUrl": "my.reverse.proxy"
|
"_certUrl": "my.reverse.proxy"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ else
|
||||||
sed -i "s/\"NewAccounts\": true/\"NewAccounts\": $ALLOW_NEW_ACCOUNTS/" meshcentral-data/"${CONFIG_FILE}"
|
sed -i "s/\"NewAccounts\": true/\"NewAccounts\": $ALLOW_NEW_ACCOUNTS/" meshcentral-data/"${CONFIG_FILE}"
|
||||||
sed -i "s/\"enabled\": false/\"enabled\": $ALLOWPLUGINS/" meshcentral-data/"${CONFIG_FILE}"
|
sed -i "s/\"enabled\": false/\"enabled\": $ALLOWPLUGINS/" meshcentral-data/"${CONFIG_FILE}"
|
||||||
sed -i "s/\"localSessionRecording\": false/\"localSessionRecording\": $LOCALSESSIONRECORDING/" meshcentral-data/"${CONFIG_FILE}"
|
sed -i "s/\"localSessionRecording\": false/\"localSessionRecording\": $LOCALSESSIONRECORDING/" meshcentral-data/"${CONFIG_FILE}"
|
||||||
sed -i "s/\"minify\": true/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}"
|
sed -i "s/\"minify\": false/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}"
|
||||||
sed -i "s/\"WebRTC\": false/\"WebRTC\": $WEBRTC/" meshcentral-data/"${CONFIG_FILE}"
|
sed -i "s/\"WebRTC\": false/\"WebRTC\": $WEBRTC/" meshcentral-data/"${CONFIG_FILE}"
|
||||||
sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/"${CONFIG_FILE}"
|
sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/"${CONFIG_FILE}"
|
||||||
if [ -z "$SESSION_KEY" ]; then
|
if [ -z "$SESSION_KEY" ]; then
|
||||||
|
|
|
@ -12,7 +12,7 @@ For more information, [visit MeshCentral.com](https://www.meshcentral.com/).
|
||||||
|
|
||||||
[Reddit](https://www.reddit.com/r/MeshCentral/)
|
[Reddit](https://www.reddit.com/r/MeshCentral/)
|
||||||
|
|
||||||
[Twitter](https://twitter.com/MeshCentral)
|
[BlueSky](https://bsky.app/profile/meshcentral.bsky.social)
|
||||||
|
|
||||||
[BlogSpot](https://meshcentral2.blogspot.com/)
|
[BlogSpot](https://meshcentral2.blogspot.com/)
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,12 @@ chmod 755 mc-azure-ubuntu1804.sh
|
||||||
|
|
||||||
In this situation, port 3389 will be used to receive Intel AMT CIRA connections instead of port 4433. After these scripts are run, try accessing the server using a browser. MeshCentral will take a minute or two to create certificates after that, the server will be up. The first account to be created will be the site administrator – so don’t delay and create an account right away. Once running, move on to the MeshCentral’s user’s guide to configure your new server.
|
In this situation, port 3389 will be used to receive Intel AMT CIRA connections instead of port 4433. After these scripts are run, try accessing the server using a browser. MeshCentral will take a minute or two to create certificates after that, the server will be up. The first account to be created will be the site administrator – so don’t delay and create an account right away. Once running, move on to the MeshCentral’s user’s guide to configure your new server.
|
||||||
|
|
||||||
|
### Elestio
|
||||||
|
|
||||||
|
You can deploy MeshCentral on Elestio using one-click deployment. Elestio handles version updates, maintenance, securtiy, backups, etc. Additionally, Elestio supports MeshCentral by providing revenue share so go ahead and click below to deploy and start using.
|
||||||
|
|
||||||
|
[](https://elest.io/open-source/meshcentral)
|
||||||
|
|
||||||
## Server Security - Adding Crowdsec
|
## Server Security - Adding Crowdsec
|
||||||
|
|
||||||
MeshCentral has built-in support for a CrowdSec bouncer. This allows MeshCentral to get threat signals from the community and block or CAPTCHA requests coming from known bad IP addresses.
|
MeshCentral has built-in support for a CrowdSec bouncer. This allows MeshCentral to get threat signals from the community and block or CAPTCHA requests coming from known bad IP addresses.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Meshcentral2 Guide
|
# MeshCentral Guide
|
||||||
|
|
||||||
[MeshCentral2 Guide](https://meshcentral.com/docs/MeshCentral2UserGuide.pdf)
|
[MeshCentral Guide](https://meshcentral.com/docs/MeshCentral2UserGuide.pdf)
|
||||||
|
|
||||||
MeshCmd Guide [as .pdf](https://meshcentral.com/docs/MeshCmdUserGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCentral User's Guide v0.2.9.odt?raw=true)
|
MeshCmd Guide [as .pdf](https://meshcentral.com/docs/MeshCmdUserGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCentral User's Guide v0.2.9.odt?raw=true)
|
||||||
|
|
||||||
|
@ -1278,6 +1278,8 @@ And taking authentication to the next step is removing the login page entirely.
|
||||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/-WKY8Wy0Huk" frameborder="0" allowfullscreen></iframe>
|
<iframe width="320" height="180" src="https://www.youtube.com/embed/-WKY8Wy0Huk" frameborder="0" allowfullscreen></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
You can also setup [Duo 2FA](https://github.com/Ylianst/MeshCentral/blob/master/docs/docs/meshcentral/security.md#duo-2fa-setup) which is a commertial offering.
|
||||||
|
|
||||||
## Server Backup & Restore
|
## Server Backup & Restore
|
||||||
|
|
||||||
It’s very important that the server be backed up regularly and that a backup be kept offsite. Luckily, performing a full backup of the MeshCentral server is generally easy to do. For all installations make sure to back up the following two folders and all sub-folders.
|
It’s very important that the server be backed up regularly and that a backup be kept offsite. Luckily, performing a full backup of the MeshCentral server is generally easy to do. For all installations make sure to back up the following two folders and all sub-folders.
|
||||||
|
|
|
@ -27,3 +27,29 @@ Adjust these items in your `config.json`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Duo 2FA setup
|
||||||
|
|
||||||
|
MeshCentral supports Duo as a way for users to add two-factor authentication and Duo offers free accounts for user 10 users. To get started, go to [Duo.com](https://duo.com/) and create a free account. Once logged into Duo, select "Applications" and "Protect an Application" on the left side. Search for "Web SDK" and hit the "Protect" button. You will see a screen with the following information:
|
||||||
|
|
||||||
|
- Client ID
|
||||||
|
- Client secret
|
||||||
|
- API hostname
|
||||||
|
|
||||||
|
Copy these three values in a safe place and do not share these values with anyone. Then, in your MeshCentral config.json file, add the following in the domains section:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"domains": {
|
||||||
|
"": {
|
||||||
|
"duo2factor": {
|
||||||
|
"integrationkey": "ClientId",
|
||||||
|
"secretkey": "ClientSecret",
|
||||||
|
"apihostname": "api-xxxxxxxxxxx.duosecurity.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart MeshCentral and your server should now be Duo capable. Users will see an option to enable it in the "My Account" tab. When enabling it, users will be walked thru the process of downloading the mobile application and going thru a trial run on 2FA. Users that get setup will be added to your Duo account under the "Users" / "Users" screen in Duo. Note that the "admin" user is not valid in Duo, so, if you have a user with the name "Admin" in MeshCentral, they will get an error trying to setup Duo.
|
||||||
|
|
246
firebase.js
246
firebase.js
|
@ -1,7 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* @description MeshCentral Firebase communication module
|
* @description MeshCentral Firebase communication module
|
||||||
* @author Ylian Saint-Hilaire
|
* @author Ylian Saint-Hilaire
|
||||||
* @copyright Intel Corporation 2018-2022
|
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
* @version v0.0.1
|
* @version v0.0.1
|
||||||
*/
|
*/
|
||||||
|
@ -14,27 +13,31 @@
|
||||||
/*jshint esversion: 6 */
|
/*jshint esversion: 6 */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Construct the Firebase object
|
// Initialize the Firebase Admin SDK
|
||||||
module.exports.CreateFirebase = function (parent, senderid, serverkey) {
|
module.exports.CreateFirebase = function (parent, serviceAccount) {
|
||||||
var obj = {};
|
|
||||||
|
// Import the Firebase Admin SDK
|
||||||
|
const admin = require('firebase-admin');
|
||||||
|
|
||||||
|
const obj = {};
|
||||||
obj.messageId = 0;
|
obj.messageId = 0;
|
||||||
obj.relays = {};
|
obj.relays = {};
|
||||||
obj.stats = {
|
obj.stats = {
|
||||||
mode: "Real",
|
mode: 'Real',
|
||||||
sent: 0,
|
sent: 0,
|
||||||
sendError: 0,
|
sendError: 0,
|
||||||
received: 0,
|
received: 0,
|
||||||
receivedNoRoute: 0,
|
receivedNoRoute: 0,
|
||||||
receivedBadArgs: 0
|
receivedBadArgs: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const tokenToNodeMap = {}; // Token --> { nid: nodeid, mid: meshid }
|
||||||
|
|
||||||
|
// Initialize Firebase Admin with server key and project ID
|
||||||
|
if (!admin.apps.length) {
|
||||||
|
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sender = require('node-xcs').Sender;
|
|
||||||
const Message = require('node-xcs').Message;
|
|
||||||
const Notification = require('node-xcs').Notification;
|
|
||||||
const xcs = new Sender(senderid, serverkey);
|
|
||||||
|
|
||||||
var tokenToNodeMap = {} // Token --> { nid: nodeid, mid: meshid }
|
|
||||||
|
|
||||||
// Setup logging
|
// Setup logging
|
||||||
if (parent.config.firebase && (parent.config.firebase.log === true)) {
|
if (parent.config.firebase && (parent.config.firebase.log === true)) {
|
||||||
obj.logpath = parent.path.join(parent.datapath, 'firebase.txt');
|
obj.logpath = parent.path.join(parent.datapath, 'firebase.txt');
|
||||||
|
@ -42,155 +45,108 @@ module.exports.CreateFirebase = function (parent, senderid, serverkey) {
|
||||||
} else {
|
} else {
|
||||||
obj.log = function () { }
|
obj.log = function () { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages received from client (excluding receipts)
|
// Function to send notifications
|
||||||
xcs.on('message', function (messageId, from, data, category) {
|
|
||||||
const jsonData = JSON.stringify(data);
|
|
||||||
obj.log('Firebase-Message: ' + jsonData);
|
|
||||||
parent.debug('email', 'Firebase-Message: ' + jsonData);
|
|
||||||
|
|
||||||
if (typeof data.r == 'string') {
|
|
||||||
// Lookup push relay server
|
|
||||||
parent.debug('email', 'Firebase-RelayRoute: ' + data.r);
|
|
||||||
const wsrelay = obj.relays[data.r];
|
|
||||||
if (wsrelay != null) {
|
|
||||||
delete data.r;
|
|
||||||
try { wsrelay.send(JSON.stringify({ from: from, data: data, category: category })); } catch (ex) { }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Lookup node information from the cache
|
|
||||||
var ninfo = tokenToNodeMap[from];
|
|
||||||
if (ninfo == null) { obj.stats.receivedNoRoute++; return; }
|
|
||||||
|
|
||||||
if ((data != null) && (data.con != null) && (data.s != null)) { // Console command
|
|
||||||
obj.stats.received++;
|
|
||||||
parent.webserver.routeAgentCommand({ action: 'msg', type: 'console', value: data.con, sessionid: data.s }, ninfo.did, ninfo.nid, ninfo.mid);
|
|
||||||
} else {
|
|
||||||
obj.stats.receivedBadArgs++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only fired for messages where options.delivery_receipt_requested = true
|
|
||||||
/*
|
|
||||||
xcs.on('receipt', function (messageId, from, data, category) { console.log('Firebase-Receipt', messageId, from, data, category); });
|
|
||||||
xcs.on('connected', function () { console.log('Connected'); });
|
|
||||||
xcs.on('disconnected', function () { console.log('disconnected'); });
|
|
||||||
xcs.on('online', function () { console.log('online'); });
|
|
||||||
xcs.on('error', function (e) { console.log('error', e); });
|
|
||||||
xcs.on('message-error', function (e) { console.log('message-error', e); });
|
|
||||||
*/
|
|
||||||
|
|
||||||
xcs.start();
|
|
||||||
|
|
||||||
obj.log('CreateFirebase-Setup');
|
|
||||||
parent.debug('email', 'CreateFirebase-Setup');
|
|
||||||
|
|
||||||
// EXAMPLE
|
|
||||||
//var payload = { notification: { title: command.title, body: command.msg }, data: { url: obj.msgurl } };
|
|
||||||
//var options = { priority: 'High', timeToLive: 5 * 60 }; // TTL: 5 minutes, priority 'Normal' or 'High'
|
|
||||||
|
|
||||||
obj.sendToDevice = function (node, payload, options, func) {
|
obj.sendToDevice = function (node, payload, options, func) {
|
||||||
if (typeof node == 'string') {
|
if (typeof node === 'string') {
|
||||||
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
parent.db.Get(node, function (err, docs) {
|
||||||
|
if (!err && docs && docs.length === 1) {
|
||||||
|
obj.sendToDeviceEx(docs[0], payload, options, func);
|
||||||
|
} else {
|
||||||
|
func(0, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
obj.sendToDeviceEx(node, payload, options, func);
|
obj.sendToDeviceEx(node, payload, options, func);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Send an outbound push notification
|
// Send an outbound push notification
|
||||||
obj.sendToDeviceEx = function (node, payload, options, func) {
|
obj.sendToDeviceEx = function (node, payload, options, func) {
|
||||||
parent.debug('email', 'Firebase-sendToDevice');
|
if (!node || typeof node.pmt !== 'string') {
|
||||||
if ((node == null) || (typeof node.pmt != 'string')) return;
|
func(0, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
||||||
|
|
||||||
// Fill in our lookup table
|
// Fill in our lookup table
|
||||||
if (node._id != null) { tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } }
|
if (node._id) {
|
||||||
|
tokenToNodeMap[node.pmt] = {
|
||||||
// Built the on-screen notification
|
nid: node._id,
|
||||||
var notification = null;
|
mid: node.meshid,
|
||||||
if (payload.notification) {
|
did: node.domain
|
||||||
var notification = new Notification('ic_message')
|
};
|
||||||
.title(payload.notification.title)
|
|
||||||
.body(payload.notification.body)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the message
|
const message = {
|
||||||
var message = new Message('msg_' + (++obj.messageId));
|
token: node.pmt,
|
||||||
if (options.priority) { message.priority(options.priority); }
|
notification: payload.notification,
|
||||||
if (payload.data) { for (var i in payload.data) { message.addData(i, payload.data[i]); } }
|
data: payload.data,
|
||||||
if ((payload.data == null) || (payload.data.shash == null)) { message.addData('shash', parent.webserver.agentCertificateHashBase64); } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
android: {
|
||||||
if (notification) { message.notification(notification) }
|
priority: options.priority || 'high',
|
||||||
message.build();
|
ttl: options.timeToLive ? options.timeToLive * 1000 : undefined
|
||||||
|
}
|
||||||
// Send the message
|
};
|
||||||
function callback(result) {
|
|
||||||
if (result.getError() == null) { obj.stats.sent++; obj.log('Success'); } else { obj.stats.sendError++; obj.log('Fail'); }
|
admin.messaging().send(message).then(function (response) {
|
||||||
callback.func(result.getMessageId(), result.getError(), result.getErrorDescription())
|
obj.stats.sent++;
|
||||||
}
|
obj.log('Success');
|
||||||
callback.func = func;
|
func(response);
|
||||||
parent.debug('email', 'Firebase-sending');
|
}).catch(function (error) {
|
||||||
xcs.sendNoRetry(message, node.pmt, callback);
|
obj.stats.sendError++;
|
||||||
}
|
obj.log('Fail: ' + error);
|
||||||
|
func(0, error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Setup a two way relay
|
// Setup a two way relay
|
||||||
obj.setupRelay = function (ws) {
|
obj.setupRelay = function (ws) {
|
||||||
// Select and set a relay identifier
|
|
||||||
ws.relayId = getRandomPassword();
|
ws.relayId = getRandomPassword();
|
||||||
while (obj.relays[ws.relayId] != null) { ws.relayId = getRandomPassword(); }
|
while (obj.relays[ws.relayId]) { ws.relayId = getRandomPassword(); }
|
||||||
obj.relays[ws.relayId] = ws;
|
obj.relays[ws.relayId] = ws;
|
||||||
|
|
||||||
// On message, parse it
|
|
||||||
ws.on('message', function (msg) {
|
ws.on('message', function (msg) {
|
||||||
parent.debug('email', 'FBWS-Data(' + this.relayId + '): ' + msg);
|
parent.debug('email', 'FBWS-Data(' + this.relayId + '): ' + msg);
|
||||||
if (typeof msg == 'string') {
|
if (typeof msg === 'string') {
|
||||||
obj.log('Relay: ' + msg);
|
obj.log('Relay: ' + msg);
|
||||||
|
|
||||||
// Parse the incoming push request
|
let data;
|
||||||
var data = null;
|
try { data = JSON.parse(msg); } catch (ex) { return; }
|
||||||
try { data = JSON.parse(msg) } catch (ex) { return; }
|
if (typeof data !== 'object') return;
|
||||||
if (typeof data != 'object') return;
|
if (!parent.common.validateObjectForMongo(data, 4096)) return;
|
||||||
if (parent.common.validateObjectForMongo(data, 4096) == false) return; // Perform sanity checking on this object.
|
if (typeof data.pmt !== 'string' || typeof data.payload !== 'object') return;
|
||||||
if (typeof data.pmt != 'string') return;
|
|
||||||
if (typeof data.payload != 'object') return;
|
data.payload.data = data.payload.data || {};
|
||||||
if (typeof data.payload.notification == 'object') {
|
data.payload.data.r = ws.relayId;
|
||||||
if (typeof data.payload.notification.title != 'string') return;
|
|
||||||
if (typeof data.payload.notification.body != 'string') return;
|
obj.sendToDevice({ pmt: data.pmt }, data.payload, data.options, function (id, err) {
|
||||||
}
|
if (!err) {
|
||||||
if (typeof data.options != 'object') return;
|
try { ws.send(JSON.stringify({ sent: true })); } catch (ex) { }
|
||||||
if ((data.options.priority != 'Normal') && (data.options.priority != 'High')) return;
|
|
||||||
if ((typeof data.options.timeToLive != 'number') || (data.options.timeToLive < 1)) return;
|
|
||||||
if (typeof data.payload.data != 'object') { data.payload.data = {}; }
|
|
||||||
data.payload.data.r = ws.relayId; // Set the relay id.
|
|
||||||
|
|
||||||
// Send the push notification
|
|
||||||
obj.sendToDevice({ pmt: data.pmt }, data.payload, data.options, function (id, err, errdesc) {
|
|
||||||
if (err == null) {
|
|
||||||
try { wsrelay.send(JSON.stringify({ sent: true })); } catch (ex) { }
|
|
||||||
} else {
|
} else {
|
||||||
try { wsrelay.send(JSON.stringify({ sent: false })); } catch (ex) { }
|
try { ws.send(JSON.stringify({ sent: false })); } catch (ex) { }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If error, close the relay
|
// If error, close the relay
|
||||||
ws.on('error', function (err) {
|
ws.on('error', function (err) {
|
||||||
parent.debug('email', 'FBWS-Error(' + this.relayId + '): ' + err);
|
parent.debug('email', 'FBWS-Error(' + this.relayId + '): ' + err);
|
||||||
delete obj.relays[this.relayId];
|
delete obj.relays[this.relayId];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close the relay
|
// Close the relay
|
||||||
ws.on('close', function () {
|
ws.on('close', function () {
|
||||||
parent.debug('email', 'FBWS-Close(' + this.relayId + ')');
|
parent.debug('email', 'FBWS-Close(' + this.relayId + ')');
|
||||||
delete obj.relays[this.relayId];
|
delete obj.relays[this.relayId];
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRandomPassword() {
|
||||||
|
return Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').replace(/\//g, '@');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomPassword() { return Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
|
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -212,7 +168,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
const querystring = require('querystring');
|
const querystring = require('querystring');
|
||||||
const relayUrl = require('url').parse(url);
|
const relayUrl = require('url').parse(url);
|
||||||
parent.debug('email', 'CreateFirebaseRelay-Setup');
|
parent.debug('email', 'CreateFirebaseRelay-Setup');
|
||||||
|
|
||||||
// Setup logging
|
// Setup logging
|
||||||
if (parent.config.firebaserelay && (parent.config.firebaserelay.log === true)) {
|
if (parent.config.firebaserelay && (parent.config.firebaserelay.log === true)) {
|
||||||
obj.logpath = parent.path.join(parent.datapath, 'firebaserelay.txt');
|
obj.logpath = parent.path.join(parent.datapath, 'firebaserelay.txt');
|
||||||
|
@ -220,7 +176,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
} else {
|
} else {
|
||||||
obj.log = function () { }
|
obj.log = function () { }
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.log('Starting relay to: ' + relayUrl.href);
|
obj.log('Starting relay to: ' + relayUrl.href);
|
||||||
if (relayUrl.protocol == 'wss:') {
|
if (relayUrl.protocol == 'wss:') {
|
||||||
// Setup two-way push notification channel
|
// Setup two-way push notification channel
|
||||||
|
@ -252,7 +208,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
parent.debug('email', 'FBWS-Disconnected');
|
parent.debug('email', 'FBWS-Disconnected');
|
||||||
obj.wsclient = null;
|
obj.wsclient = null;
|
||||||
obj.wsopen = false;
|
obj.wsopen = false;
|
||||||
|
|
||||||
// Compute the backoff timer
|
// Compute the backoff timer
|
||||||
if (obj.reconnectTimer == null) {
|
if (obj.reconnectTimer == null) {
|
||||||
if ((obj.lastConnect != null) && ((Date.now() - obj.lastConnect) > 10000)) { obj.backoffTimer = 0; }
|
if ((obj.lastConnect != null) && ((Date.now() - obj.lastConnect) > 10000)) { obj.backoffTimer = 0; }
|
||||||
|
@ -263,12 +219,12 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function processMessage(messageId, from, data, category) {
|
function processMessage(messageId, from, data, category) {
|
||||||
// Lookup node information from the cache
|
// Lookup node information from the cache
|
||||||
var ninfo = obj.tokenToNodeMap[from];
|
var ninfo = obj.tokenToNodeMap[from];
|
||||||
if (ninfo == null) { obj.stats.receivedNoRoute++; return; }
|
if (ninfo == null) { obj.stats.receivedNoRoute++; return; }
|
||||||
|
|
||||||
if ((data != null) && (data.con != null) && (data.s != null)) { // Console command
|
if ((data != null) && (data.con != null) && (data.s != null)) { // Console command
|
||||||
obj.stats.received++;
|
obj.stats.received++;
|
||||||
parent.webserver.routeAgentCommand({ action: 'msg', type: 'console', value: data.con, sessionid: data.s }, ninfo.did, ninfo.nid, ninfo.mid);
|
parent.webserver.routeAgentCommand({ action: 'msg', type: 'console', value: data.con, sessionid: data.s }, ninfo.did, ninfo.nid, ninfo.mid);
|
||||||
|
@ -276,7 +232,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
obj.stats.receivedBadArgs++;
|
obj.stats.receivedBadArgs++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.sendToDevice = function (node, payload, options, func) {
|
obj.sendToDevice = function (node, payload, options, func) {
|
||||||
if (typeof node == 'string') {
|
if (typeof node == 'string') {
|
||||||
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
||||||
|
@ -284,19 +240,19 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
obj.sendToDeviceEx(node, payload, options, func);
|
obj.sendToDeviceEx(node, payload, options, func);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.sendToDeviceEx = function (node, payload, options, func) {
|
obj.sendToDeviceEx = function (node, payload, options, func) {
|
||||||
parent.debug('email', 'Firebase-sendToDevice-webSocket');
|
parent.debug('email', 'Firebase-sendToDevice-webSocket');
|
||||||
if ((node == null) || (typeof node.pmt != 'string')) { func(0, 'error'); return; }
|
if ((node == null) || (typeof node.pmt != 'string')) { func(0, 'error'); return; }
|
||||||
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
||||||
|
|
||||||
// Fill in our lookup table
|
// Fill in our lookup table
|
||||||
if (node._id != null) { obj.tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } }
|
if (node._id != null) { obj.tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } }
|
||||||
|
|
||||||
// Fill in the server agent cert hash
|
// Fill in the server agent cert hash
|
||||||
if (payload.data == null) { payload.data = {}; }
|
if (payload.data == null) { payload.data = {}; }
|
||||||
if (payload.data.shash == null) { payload.data.shash = parent.webserver.agentCertificateHashBase64; } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
if (payload.data.shash == null) { payload.data.shash = parent.webserver.agentCertificateHashBase64; } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
||||||
|
|
||||||
// If the web socket is open, send now
|
// If the web socket is open, send now
|
||||||
if (obj.wsopen == true) {
|
if (obj.wsopen == true) {
|
||||||
try { obj.wsclient.send(JSON.stringify({ pmt: node.pmt, payload: payload, options: options })); } catch (ex) { func(0, 'error'); obj.stats.sendError++; return; }
|
try { obj.wsclient.send(JSON.stringify({ pmt: node.pmt, payload: payload, options: options })); } catch (ex) { func(0, 'error'); obj.stats.sendError++; return; }
|
||||||
|
@ -314,7 +270,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
} else if (relayUrl.protocol == 'https:') {
|
} else if (relayUrl.protocol == 'https:') {
|
||||||
// Send an outbound push notification using an HTTPS POST
|
// Send an outbound push notification using an HTTPS POST
|
||||||
obj.pushOnly = true;
|
obj.pushOnly = true;
|
||||||
|
|
||||||
obj.sendToDevice = function (node, payload, options, func) {
|
obj.sendToDevice = function (node, payload, options, func) {
|
||||||
if (typeof node == 'string') {
|
if (typeof node == 'string') {
|
||||||
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
||||||
|
@ -322,18 +278,18 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
obj.sendToDeviceEx(node, payload, options, func);
|
obj.sendToDeviceEx(node, payload, options, func);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.sendToDeviceEx = function (node, payload, options, func) {
|
obj.sendToDeviceEx = function (node, payload, options, func) {
|
||||||
parent.debug('email', 'Firebase-sendToDevice-httpPost');
|
parent.debug('email', 'Firebase-sendToDevice-httpPost');
|
||||||
if ((node == null) || (typeof node.pmt != 'string')) return;
|
if ((node == null) || (typeof node.pmt != 'string')) return;
|
||||||
|
|
||||||
// Fill in the server agent cert hash
|
// Fill in the server agent cert hash
|
||||||
if (payload.data == null) { payload.data = {}; }
|
if (payload.data == null) { payload.data = {}; }
|
||||||
if (payload.data.shash == null) { payload.data.shash = parent.webserver.agentCertificateHashBase64; } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
if (payload.data.shash == null) { payload.data.shash = parent.webserver.agentCertificateHashBase64; } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
||||||
|
|
||||||
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
||||||
const querydata = querystring.stringify({ 'msg': JSON.stringify({ pmt: node.pmt, payload: payload, options: options }) });
|
const querydata = querystring.stringify({ 'msg': JSON.stringify({ pmt: node.pmt, payload: payload, options: options }) });
|
||||||
|
|
||||||
// Send the message to the relay
|
// Send the message to the relay
|
||||||
const httpOptions = {
|
const httpOptions = {
|
||||||
hostname: relayUrl.hostname,
|
hostname: relayUrl.hostname,
|
||||||
|
@ -357,6 +313,6 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
||||||
req.end();
|
req.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
|
@ -1936,8 +1936,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||||
change = 1; // Don't save this change as an event to the db, so no log=1.
|
change = 1; // Don't save this change as an event to the db, so no log=1.
|
||||||
parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included.
|
parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included.
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((command.users != null) && (Array.isArray(command.users)) && (device.users != command.users)) { device.users = command.users; change = 1; } // Don't save this to the db.
|
if ((command.users != null) && (Array.isArray(command.users)) && (device.users != command.users)) { device.users = command.users; change = 1; } // Don't save this to the db.
|
||||||
|
if ((command.lusers != null) && (Array.isArray(command.lusers)) && (device.lusers != command.lusers)) { device.lusers = command.lusers; change = 1; } // Don't save this to the db.
|
||||||
if ((mesh.mtype == 2) && (!args.wanonly)) {
|
if ((mesh.mtype == 2) && (!args.wanonly)) {
|
||||||
// In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
|
// In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
|
||||||
if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
|
if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
|
||||||
|
|
|
@ -767,6 +767,14 @@
|
||||||
"default": false,
|
"default": false,
|
||||||
"description": "When set to true, the MPS server will only accept TLS 1.2 and 1.3 connections. Older Intel AMT devices will not be able to connect."
|
"description": "When set to true, the MPS server will only accept TLS 1.2 and 1.3 connections. Older Intel AMT devices will not be able to connect."
|
||||||
},
|
},
|
||||||
|
"prometheus": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"number"
|
||||||
|
],
|
||||||
|
"default": false,
|
||||||
|
"description": "When set to true, a prometheus metrics endpoint will be available \"0.0.0.0:9464/metrics\". If you specify a number instead, the prometheus metrics will listen on this port instead of the default 9464."
|
||||||
|
},
|
||||||
"no2FactorAuth": {
|
"no2FactorAuth": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false
|
||||||
|
@ -856,7 +864,7 @@
|
||||||
"boolean",
|
"boolean",
|
||||||
"object"
|
"object"
|
||||||
],
|
],
|
||||||
"description": "If set to \"true\", automatic backups of your MeshCentral data will be enabled. Alternatively, you can provide an object with additional values such as \"webdav\", \"backupPath\", \"backupIntervalHours\", and more.",
|
"description": "Enabled by default or if set to true. Disabled if set to false. An object can be provided with additional properties to set autobackup options.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"mongoDumpPath": {
|
"mongoDumpPath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -878,6 +886,11 @@
|
||||||
"default": 24,
|
"default": 24,
|
||||||
"description": "How often should the autobackup run in hours from the second meshcentral starts up? Default is every 24 hours"
|
"description": "How often should the autobackup run in hours from the second meshcentral starts up? Default is every 24 hours"
|
||||||
},
|
},
|
||||||
|
"backupHour": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0,
|
||||||
|
"description": "At which hour the autobackup should run. This forces a daily backup, overrules a custom 'backupIntervalHours'."
|
||||||
|
},
|
||||||
"keepLastDaysBackup": {
|
"keepLastDaysBackup": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"default": 10,
|
"default": 10,
|
||||||
|
@ -1160,6 +1173,11 @@
|
||||||
"default": 2,
|
"default": 2,
|
||||||
"description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages."
|
"description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages."
|
||||||
},
|
},
|
||||||
|
"showModernUIToggle": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When set to true, the user will be able to toggle between the modern and classic UI."
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "MeshCentral",
|
"default": "MeshCentral",
|
||||||
|
@ -1664,6 +1682,11 @@
|
||||||
"default": true,
|
"default": true,
|
||||||
"description": "Set to false to disable SMS 2FA."
|
"description": "Set to false to disable SMS 2FA."
|
||||||
},
|
},
|
||||||
|
"duo2factor": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Set to false to disable Duo 2FA."
|
||||||
|
},
|
||||||
"push2factor": {
|
"push2factor": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
@ -1949,6 +1972,11 @@
|
||||||
"default": false,
|
"default": false,
|
||||||
"description": "If true, user consent is accepted after the timeout."
|
"description": "If true, user consent is accepted after the timeout."
|
||||||
},
|
},
|
||||||
|
"autoAcceptIfNoUser": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "If true, user consent is accepted if no user is logged in."
|
||||||
|
},
|
||||||
"oldStyle": {
|
"oldStyle": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
|
@ -2152,6 +2180,11 @@
|
||||||
"default": null,
|
"default": null,
|
||||||
"description": "When set, idle users will be disconnected after a set amounts of minutes."
|
"description": "When set, idle users will be disconnected after a set amounts of minutes."
|
||||||
},
|
},
|
||||||
|
"logoutOnIdleSessionTimeout": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Determines whether MeshCentral should logout after the session idle timeout elapsed or should just disconnect remote desktop, terminal and files."
|
||||||
|
},
|
||||||
"userConsentFlags": {
|
"userConsentFlags": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Use this section to require user consent for this domain.",
|
"description": "Use this section to require user consent for this domain.",
|
||||||
|
@ -2684,6 +2717,26 @@
|
||||||
},
|
},
|
||||||
"description": "This is used to create HTTP redirections. For example setting \"redirects\": { \"example\":\"https://example.com\" } will make it so that anyone accessing /example on the server will get redirected to the specified URL."
|
"description": "This is used to create HTTP redirections. For example setting \"redirects\": { \"example\":\"https://example.com\" } will make it so that anyone accessing /example on the server will get redirected to the specified URL."
|
||||||
},
|
},
|
||||||
|
"duo2factor": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"integrationkey": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Integration key from Duo"
|
||||||
|
},
|
||||||
|
"secretkey": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Secret key from Duo"
|
||||||
|
},
|
||||||
|
"apihostname": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "API Hostname from Duo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"yubikey": {
|
"yubikey": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2862,12 +2915,10 @@
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "string",
|
|
||||||
"description": "SMTP username."
|
"description": "SMTP username."
|
||||||
},
|
},
|
||||||
"pass": {
|
"pass": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "string",
|
|
||||||
"description": "SMTP password."
|
"description": "SMTP password."
|
||||||
},
|
},
|
||||||
"tls": {
|
"tls": {
|
||||||
|
@ -3220,7 +3271,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"newAccounts": {
|
"newAccounts": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
@ -3428,8 +3478,7 @@
|
||||||
"required": [
|
"required": [
|
||||||
"client_id",
|
"client_id",
|
||||||
"client_secret"
|
"client_secret"
|
||||||
],
|
]
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"issuer": {
|
"issuer": {
|
||||||
"type": [
|
"type": [
|
||||||
|
@ -3519,8 +3568,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"custom": {
|
"custom": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -3571,8 +3619,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "REQUIRED IF USING GROUPS: Customer ID from Google Workspace Admin Console (https://admin.google.com/ac/accountsettings/profile)"
|
"description": "REQUIRED IF USING GROUPS: Customer ID from Google Workspace Admin Console (https://admin.google.com/ac/accountsettings/profile)"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"groups": {
|
"groups": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -3624,8 +3671,7 @@
|
||||||
"default": "groups",
|
"default": "groups",
|
||||||
"description": "Custom claim to use."
|
"description": "Custom claim to use."
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3690,8 +3736,7 @@
|
||||||
"description": "EAB HMAC KEY",
|
"description": "EAB HMAC KEY",
|
||||||
"default": ""
|
"default": ""
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -3786,12 +3831,10 @@
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "string",
|
|
||||||
"description": "SMTP username."
|
"description": "SMTP username."
|
||||||
},
|
},
|
||||||
"pass": {
|
"pass": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "string",
|
|
||||||
"description": "SMTP password."
|
"description": "SMTP password."
|
||||||
},
|
},
|
||||||
"tls": {
|
"tls": {
|
||||||
|
|
|
@ -585,6 +585,7 @@ function CreateMeshCentralServer(config, args) {
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
try { if (process.traceDeprecation === true) { startArgs.unshift('--trace-deprecation'); } } catch (ex) { }
|
try { if (process.traceDeprecation === true) { startArgs.unshift('--trace-deprecation'); } } catch (ex) { }
|
||||||
try { if (process.traceProcessWarnings === true) { startArgs.unshift('--trace-warnings'); } } catch (ex) { }
|
try { if (process.traceProcessWarnings === true) { startArgs.unshift('--trace-warnings'); } } catch (ex) { }
|
||||||
|
if (startArgs[0] != "--disable-proto=delete") startArgs.unshift("--disable-proto=delete")
|
||||||
childProcess = child_process.execFile(process.argv[0], startArgs, { maxBuffer: Infinity, cwd: obj.parentpath }, function (error, stdout, stderr) {
|
childProcess = child_process.execFile(process.argv[0], startArgs, { maxBuffer: Infinity, cwd: obj.parentpath }, function (error, stdout, stderr) {
|
||||||
if (childProcess.xrestart == 1) {
|
if (childProcess.xrestart == 1) {
|
||||||
setTimeout(function () { obj.launchChildServer(startArgs); }, 500); // This is an expected restart.
|
setTimeout(function () { obj.launchChildServer(startArgs); }, 500); // This is an expected restart.
|
||||||
|
@ -1348,7 +1349,7 @@ function CreateMeshCentralServer(config, args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the database is capable of performing a backup
|
// Check if the database is capable of performing a backup
|
||||||
obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
|
// Moved behind autobackup config init in startex4: obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
|
||||||
|
|
||||||
// Load configuration for database if needed
|
// Load configuration for database if needed
|
||||||
if (obj.args.loadconfigfromdb) {
|
if (obj.args.loadconfigfromdb) {
|
||||||
|
@ -1656,7 +1657,7 @@ function CreateMeshCentralServer(config, args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup agent error log
|
// Setup agent error log
|
||||||
if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump != null)) {
|
if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump)) {
|
||||||
obj.fs.open(obj.path.join(obj.datapath, 'agenterrorlogs.txt'), 'a', function (err, fd) { obj.agentErrorLog = fd; })
|
obj.fs.open(obj.path.join(obj.datapath, 'agenterrorlogs.txt'), 'a', function (err, fd) { obj.agentErrorLog = fd; })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1992,9 +1993,17 @@ function CreateMeshCentralServer(config, args) {
|
||||||
if (typeof config.settings.webpush.gcmapi == 'string') { webpush.setGCMAPIKey(config.settings.webpush.gcmapi); }
|
if (typeof config.settings.webpush.gcmapi == 'string') { webpush.setGCMAPIKey(config.settings.webpush.gcmapi); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the current node version
|
||||||
|
const verSplit = process.version.substring(1).split('.');
|
||||||
|
var nodeVersion = parseInt(verSplit[0]) + (parseInt(verSplit[1]) / 100);
|
||||||
|
|
||||||
// Setup Firebase
|
// Setup Firebase
|
||||||
if ((config.firebase != null) && (typeof config.firebase.senderid == 'string') && (typeof config.firebase.serverkey == 'string')) {
|
if ((config.firebase != null) && (typeof config.firebase.senderid == 'string') && (typeof config.firebase.serverkey == 'string')) {
|
||||||
obj.firebase = require('./firebase').CreateFirebase(obj, config.firebase.senderid, config.firebase.serverkey);
|
addServerWarning('Firebase now requires a service account JSON file, Firebase disabled.', 27);
|
||||||
|
} else if ((config.firebase != null) && (typeof config.firebase.serviceaccountfile == 'string')) {
|
||||||
|
var serviceAccount;
|
||||||
|
try { serviceAccount = JSON.parse(obj.fs.readFileSync(obj.path.join(obj.datapath, config.firebase.serviceaccountfile)).toString()); } catch (ex) { console.log(ex); }
|
||||||
|
if (serviceAccount != null) { obj.firebase = require('./firebase').CreateFirebase(obj, serviceAccount); }
|
||||||
} else if ((typeof config.firebaserelay == 'object') && (typeof config.firebaserelay.url == 'string')) {
|
} else if ((typeof config.firebaserelay == 'object') && (typeof config.firebaserelay.url == 'string')) {
|
||||||
// Setup the push messaging relay
|
// Setup the push messaging relay
|
||||||
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, config.firebaserelay.url, config.firebaserelay.key);
|
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, config.firebaserelay.url, config.firebaserelay.key);
|
||||||
|
@ -2003,8 +2012,12 @@ function CreateMeshCentralServer(config, args) {
|
||||||
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, 'https://alt.meshcentral.com/firebaserelay.aspx');
|
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, 'https://alt.meshcentral.com/firebaserelay.aspx');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup monitoring
|
||||||
|
obj.monitoring = require('./monitoring.js').CreateMonitoring(obj, obj.args);
|
||||||
|
|
||||||
// Start periodic maintenance
|
// Start periodic maintenance
|
||||||
obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
|
obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
|
||||||
|
//obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 10 * 1); // DEBUG: Run this more often
|
||||||
|
|
||||||
// Dispatch an event that the server is now running
|
// Dispatch an event that the server is now running
|
||||||
obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' });
|
obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' });
|
||||||
|
@ -2089,23 +2102,24 @@ function CreateMeshCentralServer(config, args) {
|
||||||
obj.updateServerState('state', "running");
|
obj.updateServerState('state', "running");
|
||||||
|
|
||||||
// Setup auto-backup defaults
|
// Setup auto-backup defaults
|
||||||
if (obj.config.settings.autobackup == null || obj.config.settings.autobackup == false || obj.config.settings.autobackup == 'false') { obj.config.settings.autobackup = {backupintervalhours: 0}; } //no schedule, but able to console autobackup
|
if (obj.config.settings.autobackup == false || obj.config.settings.autobackup == 'false') { obj.config.settings.autobackup = {backupintervalhours: 0}; } //no schedule, but able to console autobackup
|
||||||
else {
|
else {
|
||||||
if (obj.config.settings.autobackup === true) {obj.config.settings.autobackup = {backupintervalhours: 24, keeplastdaysbackup: 10}; };
|
if (obj.config.settings.autobackup == null || obj.config.settings.autobackup === true) { obj.config.settings.autobackup = {backupintervalhours: 24, keeplastdaysbackup: 10}; };
|
||||||
if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; };
|
if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; };
|
||||||
if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; };
|
if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; };
|
||||||
|
if (obj.config.settings.autobackup.backuphour != null ) { obj.config.settings.autobackup.backupintervalhours = 24; if ((typeof obj.config.settings.autobackup.backuphour != 'number') || (obj.config.settings.autobackup.backuphour > 23 || obj.config.settings.autobackup.backuphour < 0 )) { obj.config.settings.autobackup.backuphour = 0; }}
|
||||||
|
else {obj.config.settings.autobackup.backuphour = -1 };
|
||||||
//arrayfi in case of string and remove possible ', ' space. !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !!
|
//arrayfi in case of string and remove possible ', ' space. !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !!
|
||||||
if (!obj.config.settings.autobackup.backupignorefilesglob) {obj.config.settings.autobackup.backupignorefilesglob = []}
|
if (!obj.config.settings.autobackup.backupignorefilesglob) {obj.config.settings.autobackup.backupignorefilesglob = []}
|
||||||
else if (typeof obj.config.settings.autobackup.backupignorefilesglob == 'string') { obj.config.settings.autobackup.backupignorefilesglob = obj.config.settings.autobackup.backupignorefilesglob.replaceAll(', ', ',').split(','); };
|
else if (typeof obj.config.settings.autobackup.backupignorefilesglob == 'string') { obj.config.settings.autobackup.backupignorefilesglob = obj.config.settings.autobackup.backupignorefilesglob.replaceAll(', ', ',').split(','); };
|
||||||
if (!obj.config.settings.autobackup.backupskipfoldersglob) {obj.config.settings.autobackup.backupskipfoldersglob = []}
|
if (!obj.config.settings.autobackup.backupskipfoldersglob) {obj.config.settings.autobackup.backupskipfoldersglob = []}
|
||||||
else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); };
|
else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); };
|
||||||
|
if (typeof obj.config.settings.autobackup.backuppath == 'string') { obj.backuppath = (obj.config.settings.autobackup.backuppath = (obj.path.resolve(obj.config.settings.autobackup.backuppath))) } else { obj.config.settings.autobackup.backuppath = obj.backuppath };
|
||||||
|
if (typeof obj.config.settings.autobackup.backupname != 'string') { obj.config.settings.autobackup.backupname = 'meshcentral-autobackup-'};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that autobackup path is not within the "meshcentral-data" folder.
|
// Check if the database is capable of performing a backup
|
||||||
if ((typeof obj.config.settings.autobackup == 'object') && (typeof obj.config.settings.autobackup.backuppath == 'string') && (obj.path.normalize(obj.config.settings.autobackup.backuppath).startsWith(obj.path.normalize(obj.datapath)))) {
|
obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
|
||||||
addServerWarning("Backup path can't be set within meshcentral-data folder, backup settings ignored.", 21);
|
|
||||||
obj.config.settings.autobackup = {backupintervalhours: -1}; //block console autobackup
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load Intel AMT passwords from the "amtactivation.log" file
|
// Load Intel AMT passwords from the "amtactivation.log" file
|
||||||
obj.loadAmtActivationLogPasswords(function (amtPasswords) {
|
obj.loadAmtActivationLogPasswords(function (amtPasswords) {
|
||||||
|
@ -2267,14 +2281,19 @@ function CreateMeshCentralServer(config, args) {
|
||||||
|
|
||||||
// Check if we need to perform an automatic backup
|
// Check if we need to perform an automatic backup
|
||||||
function checkAutobackup() {
|
function checkAutobackup() {
|
||||||
if (obj.config.settings.autobackup.backupintervalhours >= 1) {
|
if (obj.config.settings.autobackup.backupintervalhours >= 1 ) {
|
||||||
obj.db.Get('LastAutoBackupTime', function (err, docs) {
|
obj.db.Get('LastAutoBackupTime', function (err, docs) {
|
||||||
if (err != null) return;
|
if (err != null) { console.error("checkAutobackup: Error getting LastBackupTime from DB"); return}
|
||||||
var lastBackup = 0;
|
var lastBackup = 0;
|
||||||
const now = new Date().getTime();
|
const currentdate = new Date();
|
||||||
|
let currentHour = currentdate.getHours();
|
||||||
|
let now = currentdate.getTime();
|
||||||
if (docs.length == 1) { lastBackup = docs[0].value; }
|
if (docs.length == 1) { lastBackup = docs[0].value; }
|
||||||
const delta = now - lastBackup;
|
const delta = now - lastBackup;
|
||||||
if (delta > (obj.config.settings.autobackup.backupintervalhours * 60 * 60 * 1000)) {
|
//const delta = 9999999999; // DEBUG: backup always
|
||||||
|
obj.debug ('backup', 'Entering checkAutobackup, lastAutoBackupTime: ' + new Date(lastBackup).toLocaleString('default', { dateStyle: 'medium', timeStyle: 'short' }) + ', delta: ' + (delta/(1000*60*60)).toFixed(2) + ' hours');
|
||||||
|
//start autobackup if interval has passed or at configured hour, whichever comes first. When an hour schedule is missed, it will make a backup immediately.
|
||||||
|
if ((delta > (obj.config.settings.autobackup.backupintervalhours * 60 * 60 * 1000)) || ((currentHour == obj.config.settings.autobackup.backuphour) && (delta >= 2 * 60 * 60 * 1000))) {
|
||||||
// A new auto-backup is required.
|
// A new auto-backup is required.
|
||||||
obj.db.Set({ _id: 'LastAutoBackupTime', value: now }); // Save the current time in the database
|
obj.db.Set({ _id: 'LastAutoBackupTime', value: now }); // Save the current time in the database
|
||||||
obj.db.performBackup(); // Perform the backup
|
obj.db.performBackup(); // Perform the backup
|
||||||
|
@ -2396,6 +2415,10 @@ function CreateMeshCentralServer(config, args) {
|
||||||
storeEvent.links = Object.assign({}, storeEvent.links);
|
storeEvent.links = Object.assign({}, storeEvent.links);
|
||||||
for (var i in storeEvent.links) { var ue = obj.common.escapeFieldName(i); if (ue !== i) { storeEvent.links[ue] = storeEvent.links[i]; delete storeEvent.links[i]; } }
|
for (var i in storeEvent.links) { var ue = obj.common.escapeFieldName(i); if (ue !== i) { storeEvent.links[ue] = storeEvent.links[i]; delete storeEvent.links[i]; } }
|
||||||
}
|
}
|
||||||
|
if (storeEvent.mesh) {
|
||||||
|
// Escape "mesh" names that may have "." and/or "$"
|
||||||
|
storeEvent.mesh = obj.common.escapeLinksFieldNameEx(storeEvent.mesh);
|
||||||
|
}
|
||||||
storeEvent.ids = ids;
|
storeEvent.ids = ids;
|
||||||
obj.db.StoreEvent(storeEvent);
|
obj.db.StoreEvent(storeEvent);
|
||||||
}
|
}
|
||||||
|
@ -3921,6 +3944,7 @@ function CreateMeshCentralServer(config, args) {
|
||||||
function logWarnEvent(msg) { if (obj.servicelog != null) { obj.servicelog.warn(msg); } console.log(msg); }
|
function logWarnEvent(msg) { if (obj.servicelog != null) { obj.servicelog.warn(msg); } console.log(msg); }
|
||||||
function logErrorEvent(msg) { if (obj.servicelog != null) { obj.servicelog.error(msg); } console.error(msg); }
|
function logErrorEvent(msg) { if (obj.servicelog != null) { obj.servicelog.error(msg); } console.error(msg); }
|
||||||
obj.getServerWarnings = function () { return serverWarnings; }
|
obj.getServerWarnings = function () { return serverWarnings; }
|
||||||
|
// TODO: migrate from other addServerWarning function and add timestamp
|
||||||
obj.addServerWarning = function (msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
|
obj.addServerWarning = function (msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
|
||||||
|
|
||||||
// auth.log functions
|
// auth.log functions
|
||||||
|
@ -4033,7 +4057,16 @@ function InstallModules(modules, args, func) {
|
||||||
try {
|
try {
|
||||||
// Does the module need a specific version?
|
// Does the module need a specific version?
|
||||||
if (moduleVersion) {
|
if (moduleVersion) {
|
||||||
if (require(`${moduleName}/package.json`).version != moduleVersion) { throw new Error(); }
|
var versionMatch = false;
|
||||||
|
var modulePath = null;
|
||||||
|
// This is the first way to test if a module is already installed.
|
||||||
|
try { versionMatch = (require(`${moduleName}/package.json`).version == moduleVersion) } catch (ex) {
|
||||||
|
if (ex.code == "ERR_PACKAGE_PATH_NOT_EXPORTED") { modulePath = ("" + ex).split(' ').at(-1); } else { throw new Error(); }
|
||||||
|
}
|
||||||
|
// If the module is not installed, but we get the ERR_PACKAGE_PATH_NOT_EXPORTED error, try a second way.
|
||||||
|
if ((versionMatch == false) && (modulePath != null)) {
|
||||||
|
if (JSON.parse(require('fs').readFileSync(modulePath, 'utf8')).version != moduleVersion) { throw new Error(); }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For all other modules, do the check here.
|
// For all other modules, do the check here.
|
||||||
// Is the module in package.json? Install exact version.
|
// Is the module in package.json? Install exact version.
|
||||||
|
@ -4082,6 +4115,7 @@ function InstallModuleEx(modulenames, args, func) {
|
||||||
process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); meshserver = null; } console.log('Server Ctrl-C exit...'); process.exit(); });
|
process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); meshserver = null; } console.log('Server Ctrl-C exit...'); process.exit(); });
|
||||||
|
|
||||||
// Add a server warning, warnings will be shown to the administrator on the web application
|
// Add a server warning, warnings will be shown to the administrator on the web application
|
||||||
|
// TODO: migrate to obj.addServerWarning?
|
||||||
const serverWarnings = [];
|
const serverWarnings = [];
|
||||||
function addServerWarning(msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
|
function addServerWarning(msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
|
||||||
|
|
||||||
|
@ -4112,7 +4146,8 @@ var ServerWarnings = {
|
||||||
23: "Unable to load agent icon file: {0}.",
|
23: "Unable to load agent icon file: {0}.",
|
||||||
24: "Unable to load agent logo file: {0}.",
|
24: "Unable to load agent logo file: {0}.",
|
||||||
25: "This NodeJS version does not support OpenID.",
|
25: "This NodeJS version does not support OpenID.",
|
||||||
26: "This NodeJS version does not support Discord.js."
|
26: "This NodeJS version does not support Discord.js.",
|
||||||
|
27: "Firebase now requires a service account JSON file, Firebase disabled."
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -4166,7 +4201,7 @@ function mainStart() {
|
||||||
// Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used
|
// Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used
|
||||||
var sspi = false;
|
var sspi = false;
|
||||||
var ldap = false;
|
var ldap = false;
|
||||||
var passport = null;
|
var passport = [];
|
||||||
var allsspi = true;
|
var allsspi = true;
|
||||||
var yubikey = false;
|
var yubikey = false;
|
||||||
var ssh = false;
|
var ssh = false;
|
||||||
|
@ -4187,17 +4222,17 @@ function mainStart() {
|
||||||
if (mstsc == false) { config.domains[i].mstsc = false; }
|
if (mstsc == false) { config.domains[i].mstsc = false; }
|
||||||
if (config.domains[i].ssh == true) { ssh = true; }
|
if (config.domains[i].ssh == true) { ssh = true; }
|
||||||
if ((typeof config.domains[i].authstrategies == 'object')) {
|
if ((typeof config.domains[i].authstrategies == 'object')) {
|
||||||
if (passport == null) { passport = ['passport','connect-flash']; } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors
|
if (passport.indexOf('passport') == -1) { passport.push('passport','connect-flash'); } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors
|
||||||
if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); }
|
if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); }
|
||||||
if ((typeof config.domains[i].authstrategies.google == 'object') && (typeof config.domains[i].authstrategies.google.clientid == 'string') && (typeof config.domains[i].authstrategies.google.clientsecret == 'string') && (passport.indexOf('passport-google-oauth20') == -1)) { passport.push('passport-google-oauth20'); }
|
if ((typeof config.domains[i].authstrategies.google == 'object') && (typeof config.domains[i].authstrategies.google.clientid == 'string') && (typeof config.domains[i].authstrategies.google.clientsecret == 'string') && (passport.indexOf('passport-google-oauth20') == -1)) { passport.push('passport-google-oauth20'); }
|
||||||
if ((typeof config.domains[i].authstrategies.github == 'object') && (typeof config.domains[i].authstrategies.github.clientid == 'string') && (typeof config.domains[i].authstrategies.github.clientsecret == 'string') && (passport.indexOf('passport-github2') == -1)) { passport.push('passport-github2'); }
|
if ((typeof config.domains[i].authstrategies.github == 'object') && (typeof config.domains[i].authstrategies.github.clientid == 'string') && (typeof config.domains[i].authstrategies.github.clientsecret == 'string') && (passport.indexOf('passport-github2') == -1)) { passport.push('passport-github2'); }
|
||||||
if ((typeof config.domains[i].authstrategies.azure == 'object') && (typeof config.domains[i].authstrategies.azure.clientid == 'string') && (typeof config.domains[i].authstrategies.azure.clientsecret == 'string') && (typeof config.domains[i].authstrategies.azure.tenantid == 'string') && (passport.indexOf('passport-azure-oauth2') == -1)) { passport.push('passport-azure-oauth2'); passport.push('jwt-simple'); }
|
if ((typeof config.domains[i].authstrategies.azure == 'object') && (typeof config.domains[i].authstrategies.azure.clientid == 'string') && (typeof config.domains[i].authstrategies.azure.clientsecret == 'string') && (typeof config.domains[i].authstrategies.azure.tenantid == 'string') && (passport.indexOf('passport-azure-oauth2') == -1)) { passport.push('passport-azure-oauth2'); passport.push('jwt-simple'); }
|
||||||
if ((typeof config.domains[i].authstrategies.oidc == 'object') && (passport.indexOf('openid-client') == -1)) {
|
if ((typeof config.domains[i].authstrategies.oidc == 'object') && (passport.indexOf('openid-client@5.7.1') == -1)) {
|
||||||
if ((nodeVersion >= 17)
|
if ((nodeVersion >= 17)
|
||||||
|| ((Math.floor(nodeVersion) == 16) && (nodeVersion >= 16.13))
|
|| ((Math.floor(nodeVersion) == 16) && (nodeVersion >= 16.13))
|
||||||
|| ((Math.floor(nodeVersion) == 14) && (nodeVersion >= 14.15))
|
|| ((Math.floor(nodeVersion) == 14) && (nodeVersion >= 14.15))
|
||||||
|| ((Math.floor(nodeVersion) == 12) && (nodeVersion >= 12.19))) {
|
|| ((Math.floor(nodeVersion) == 12) && (nodeVersion >= 12.19))) {
|
||||||
passport.push('openid-client@5.7.0');
|
passport.push('openid-client@5.7.1');
|
||||||
} else {
|
} else {
|
||||||
addServerWarning('This NodeJS version does not support OpenID Connect on MeshCentral.', 25);
|
addServerWarning('This NodeJS version does not support OpenID Connect on MeshCentral.', 25);
|
||||||
delete config.domains[i].authstrategies.oidc;
|
delete config.domains[i].authstrategies.oidc;
|
||||||
|
@ -4208,11 +4243,12 @@ function mainStart() {
|
||||||
if (config.domains[i].sessionrecording != null) { sessionRecording = true; }
|
if (config.domains[i].sessionrecording != null) { sessionRecording = true; }
|
||||||
if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
|
if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
|
||||||
if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; }
|
if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; }
|
||||||
|
if ((typeof config.domains[i].duo2factor == 'object') && (passport.indexOf('@duosecurity/duo_universal') == -1)) { passport.push('@duosecurity/duo_universal'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the list of required modules
|
// Build the list of required modules
|
||||||
// NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN Dockerfile
|
// NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN Dockerfile
|
||||||
var modules = ['archiver@7.0.1', 'body-parser@1.20.3', 'cbor@5.2.0', 'compression@1.7.4', 'cookie-session@2.1.0', 'express@4.21.1', 'express-handlebars@7.1.3', 'express-ws@5.0.2', 'ipcheck@0.1.0', 'minimist@1.2.8', 'multiparty@4.2.3', '@yetzt/nedb', 'node-forge@1.3.1', 'ua-parser-js@1.0.39', 'ws@8.18.0', 'yauzl@2.10.0'];
|
var modules = ['archiver@7.0.1', 'body-parser@1.20.3', 'cbor@5.2.0', 'compression@1.7.5', 'cookie-session@2.1.0', 'express@4.21.2', 'express-handlebars@7.1.3', 'express-ws@5.0.2', 'ipcheck@0.1.0', 'minimist@1.2.8', 'multiparty@4.2.3', '@seald-io/nedb', 'node-forge@1.3.1', 'ua-parser-js@1.0.39', 'ws@8.18.0', 'yauzl@2.10.0'];
|
||||||
if (require('os').platform() == 'win32') { modules.push('node-windows@0.1.14'); modules.push('loadavg-windows@1.1.1'); if (sspi == true) { modules.push('node-sspi@0.2.10'); } } // Add Windows modules
|
if (require('os').platform() == 'win32') { modules.push('node-windows@0.1.14'); modules.push('loadavg-windows@1.1.1'); if (sspi == true) { modules.push('node-sspi@0.2.10'); } } // Add Windows modules
|
||||||
if (ldap == true) { modules.push('ldapauth-fork@5.0.5'); }
|
if (ldap == true) { modules.push('ldapauth-fork@5.0.5'); }
|
||||||
if (ssh == true) { modules.push('ssh2@1.16.0'); }
|
if (ssh == true) { modules.push('ssh2@1.16.0'); }
|
||||||
|
@ -4222,21 +4258,22 @@ function mainStart() {
|
||||||
if (sessionRecording == true) { modules.push('image-size@1.1.1'); } // Need to get the remote desktop JPEG sizes to index the recodring file.
|
if (sessionRecording == true) { modules.push('image-size@1.1.1'); } // Need to get the remote desktop JPEG sizes to index the recodring file.
|
||||||
if (config.letsencrypt != null) { modules.push('acme-client@4.2.5'); } // Add acme-client module. We need to force v4.2.4 or higher since olver versions using SHA-1 which is no longer supported by Let's Encrypt.
|
if (config.letsencrypt != null) { modules.push('acme-client@4.2.5'); } // Add acme-client module. We need to force v4.2.4 or higher since olver versions using SHA-1 which is no longer supported by Let's Encrypt.
|
||||||
if (config.settings.mqtt != null) { modules.push('aedes@0.39.0'); } // Add MQTT Modules
|
if (config.settings.mqtt != null) { modules.push('aedes@0.39.0'); } // Add MQTT Modules
|
||||||
if (config.settings.mysql != null) { modules.push('mysql2@3.6.2'); } // Add MySQL.
|
if (config.settings.mysql != null) { modules.push('mysql2@3.11.4'); } // Add MySQL.
|
||||||
//if (config.settings.mysql != null) { modules.push('@mysql/xdevapi@8.0.33'); } // Add MySQL, official driver (https://dev.mysql.com/doc/dev/connector-nodejs/8.0/)
|
//if (config.settings.mysql != null) { modules.push('@mysql/xdevapi@8.0.33'); } // Add MySQL, official driver (https://dev.mysql.com/doc/dev/connector-nodejs/8.0/)
|
||||||
if (config.settings.mongodb != null) { modules.push('mongodb@4.13.0'); modules.push('saslprep@1.0.3'); } // Add MongoDB, official driver.
|
if (config.settings.mongodb != null) { modules.push('mongodb@4.13.0'); modules.push('saslprep@1.0.3'); } // Add MongoDB, official driver.
|
||||||
if (config.settings.postgres != null) { modules.push('pg@8.13.1') } // Add Postgres, official driver.
|
if (config.settings.postgres != null) { modules.push('pg@8.13.1') } // Add Postgres, official driver.
|
||||||
if (config.settings.mariadb != null) { modules.push('mariadb@3.2.2'); } // Add MariaDB, official driver.
|
if (config.settings.mariadb != null) { modules.push('mariadb@3.4.0'); } // Add MariaDB, official driver.
|
||||||
if (config.settings.acebase != null) { modules.push('acebase@1.29.5'); } // Add AceBase, official driver.
|
if (config.settings.acebase != null) { modules.push('acebase@1.29.5'); } // Add AceBase, official driver.
|
||||||
if (config.settings.sqlite3 != null) { modules.push('sqlite3@5.1.7'); } // Add sqlite3, official driver.
|
if (config.settings.sqlite3 != null) { modules.push('sqlite3@5.1.7'); } // Add sqlite3, official driver.
|
||||||
if (config.settings.vault != null) { modules.push('node-vault@0.10.2'); } // Add official HashiCorp's Vault module.
|
if (config.settings.vault != null) { modules.push('node-vault@0.10.2'); } // Add official HashiCorp's Vault module.
|
||||||
if (config.settings.plugins != null) { modules.push('semver@7.5.4'); } // Required for version compat testing and update checks
|
if (config.settings.plugins != null) { modules.push('semver@7.5.4'); } // Required for version compat testing and update checks
|
||||||
if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent@7.0.2'); } // Required for HTTP/HTTPS proxy support
|
if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent@7.0.2'); } // Required for HTTP/HTTPS proxy support
|
||||||
else if (config.settings.xmongodb != null) { modules.push('mongojs@3.1.0'); } // Add MongoJS, old driver.
|
else if (config.settings.xmongodb != null) { modules.push('mongojs@3.1.0'); } // Add MongoJS, old driver.
|
||||||
if (nodemailer || ((config.smtp != null) && (config.smtp.name != 'console')) || (config.sendmail != null)) { modules.push('nodemailer@6.9.15'); } // Add SMTP support
|
if (nodemailer || ((config.smtp != null) && (config.smtp.name != 'console')) || (config.sendmail != null)) { modules.push('nodemailer@6.9.16'); } // Add SMTP support
|
||||||
if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support
|
if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support
|
||||||
if ((args.translate || args.dev) && (Number(process.version.match(/^v(\d+\.\d+)/)[1]) >= 16)) { modules.push('jsdom@22.1.0'); modules.push('esprima@4.0.1'); modules.push('html-minifier@4.0.0'); } // Translation support
|
if ((args.translate || args.dev) && (Number(process.version.match(/^v(\d+\.\d+)/)[1]) >= 16)) { modules.push('jsdom@22.1.0'); modules.push('esprima@4.0.1'); modules.push('html-minifier@4.0.0'); } // Translation support
|
||||||
if (typeof config.settings.crowdsec == 'object') { modules.push('@crowdsec/express-bouncer@0.1.0'); } // Add CrowdSec bounser module (https://www.npmjs.com/package/@crowdsec/express-bouncer)
|
if (typeof config.settings.crowdsec == 'object') { modules.push('@crowdsec/express-bouncer@0.1.0'); } // Add CrowdSec bounser module (https://www.npmjs.com/package/@crowdsec/express-bouncer)
|
||||||
|
if (config.settings.prometheus != null) { modules.push('prom-client'); } // Add Prometheus Metrics support
|
||||||
|
|
||||||
if (typeof config.settings.autobackup == 'object') {
|
if (typeof config.settings.autobackup == 'object') {
|
||||||
// Setup encrypted zip support if needed
|
// Setup encrypted zip support if needed
|
||||||
|
@ -4248,7 +4285,7 @@ function mainStart() {
|
||||||
if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.4'); }
|
if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.4'); }
|
||||||
}
|
}
|
||||||
// Enable S3 Support
|
// Enable S3 Support
|
||||||
if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.1'); }
|
if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.2'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup common password blocking
|
// Setup common password blocking
|
||||||
|
@ -4284,8 +4321,7 @@ function mainStart() {
|
||||||
if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('web-push@3.6.6'); }
|
if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('web-push@3.6.6'); }
|
||||||
|
|
||||||
// Firebase Support
|
// Firebase Support
|
||||||
// Avoid 0.1.8 due to bugs: https://github.com/guness/node-xcs/issues/43
|
if ((config.firebase != null) && (typeof config.firebase.serviceaccountfile == 'string')) { modules.push('firebase-admin@12.7.0'); }
|
||||||
if (config.firebase != null) { modules.push('node-xcs@0.1.7'); }
|
|
||||||
|
|
||||||
// Syslog support
|
// Syslog support
|
||||||
if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('modern-syslog@1.2.0'); }
|
if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('modern-syslog@1.2.0'); }
|
||||||
|
|
53
meshctrl.js
53
meshctrl.js
|
@ -16,7 +16,7 @@ var settings = {};
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const args = require('minimist')(process.argv.slice(2));
|
const args = require('minimist')(process.argv.slice(2));
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addlocaldevice', 'addamtdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report', 'grouptoast', 'groupmessage'];
|
const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addlocaldevice', 'addamtdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report', 'grouptoast', 'groupmessage', 'webrelay'];
|
||||||
if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
|
if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
|
||||||
|
|
||||||
if (args['_'].length == 0) {
|
if (args['_'].length == 0) {
|
||||||
|
@ -65,6 +65,7 @@ if (args['_'].length == 0) {
|
||||||
console.log(" Shell - Access command shell of a remote device.");
|
console.log(" Shell - Access command shell of a remote device.");
|
||||||
console.log(" Upload - Upload a file to a remote device.");
|
console.log(" Upload - Upload a file to a remote device.");
|
||||||
console.log(" Download - Download a file from a remote device.");
|
console.log(" Download - Download a file from a remote device.");
|
||||||
|
console.log(" WebRelay - Creates a HTTP/HTTPS webrelay link for a remote device.");
|
||||||
console.log(" DeviceOpenUrl - Open a URL on a remote device.");
|
console.log(" DeviceOpenUrl - Open a URL on a remote device.");
|
||||||
console.log(" DeviceMessage - Open a message box on a remote device.");
|
console.log(" DeviceMessage - Open a message box on a remote device.");
|
||||||
console.log(" DeviceToast - Display a toast notification on a remote device.");
|
console.log(" DeviceToast - Display a toast notification on a remote device.");
|
||||||
|
@ -277,6 +278,12 @@ if (args['_'].length == 0) {
|
||||||
else { ok = true; }
|
else { ok = true; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'webrelay': {
|
||||||
|
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
|
||||||
|
else if (args.type == null) { console.log(winRemoveSingleQuotes("Missing protocol type, use --type [http,https]")); }
|
||||||
|
else { ok = true; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'deviceopenurl': {
|
case 'deviceopenurl': {
|
||||||
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
|
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
|
||||||
else if (args.openurl == null) { console.log("Remote URL, use --openurl [url] specify the link to open."); }
|
else if (args.openurl == null) { console.log("Remote URL, use --openurl [url] specify the link to open."); }
|
||||||
|
@ -1015,6 +1022,21 @@ if (args['_'].length == 0) {
|
||||||
console.log(" --target [localpath] - The local path to download the file to.");
|
console.log(" --target [localpath] - The local path to download the file to.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'webrelay': {
|
||||||
|
console.log("Generate a webrelay URL to access a HTTP/HTTPS service on a remote device, Example usages:\r\n");
|
||||||
|
console.log(winRemoveSingleQuotes(" MeshCtrl WebRelay --id 'deviceid' --type http --port 80"));
|
||||||
|
console.log(winRemoveSingleQuotes(" MeshCtrl WebRelay --id 'deviceid' --type https --port 443"));
|
||||||
|
console.log("\r\nRequired arguments:\r\n");
|
||||||
|
if (process.platform == 'win32') {
|
||||||
|
console.log(" --id [deviceid] - The device identifier.");
|
||||||
|
} else {
|
||||||
|
console.log(" --id '[deviceid]' - The device identifier.");
|
||||||
|
}
|
||||||
|
console.log(" --type [http,https] - Type of relay from remote device, http or https.");
|
||||||
|
console.log("\r\nOptional arguments:\r\n");
|
||||||
|
console.log(" --port [portnumber] - Set alternative port for http or https, default is 80 for http and 443 for https.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'deviceopenurl': {
|
case 'deviceopenurl': {
|
||||||
console.log("Open a web page on a remote device, Example usages:\r\n");
|
console.log("Open a web page on a remote device, Example usages:\r\n");
|
||||||
console.log(winRemoveSingleQuotes(" MeshCtrl DeviceOpenUrl --id 'deviceid' --openurl http://meshcentral.com"));
|
console.log(winRemoveSingleQuotes(" MeshCtrl DeviceOpenUrl --id 'deviceid' --openurl http://meshcentral.com"));
|
||||||
|
@ -1804,6 +1826,29 @@ function serverConnect() {
|
||||||
req.end()
|
req.end()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'webrelay': {
|
||||||
|
var protocol = null;
|
||||||
|
if (args.type != null) {
|
||||||
|
if (args.type == 'http') {
|
||||||
|
protocol = 1;
|
||||||
|
} else if (args.type == 'https') {
|
||||||
|
protocol = 2;
|
||||||
|
} else {
|
||||||
|
console.log("Unknown protocol type: " + args.type); process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var port = null;
|
||||||
|
if (typeof args.port == 'number') {
|
||||||
|
if ((args.port < 1) || (args.port > 65535)) { console.log("Port number must be between 1 and 65535."); process.exit(1); }
|
||||||
|
port = args.port;
|
||||||
|
} else if (protocol == 1) {
|
||||||
|
port = 80;
|
||||||
|
} else if (protocol == 2) {
|
||||||
|
port = 443;
|
||||||
|
}
|
||||||
|
ws.send(JSON.stringify({ action: 'webrelay', nodeid: args.id, port: port, appid: protocol, responseid: 'meshctrl' }));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'devicesharing': {
|
case 'devicesharing': {
|
||||||
if (args.add) {
|
if (args.add) {
|
||||||
if (args.add.length == 0) { console.log("Invalid guest name."); process.exit(1); }
|
if (args.add.length == 0) { console.log("Invalid guest name."); process.exit(1); }
|
||||||
|
@ -2198,6 +2243,7 @@ function serverConnect() {
|
||||||
case 'removeDeviceShare':
|
case 'removeDeviceShare':
|
||||||
case 'userbroadcast': { // BROADCAST
|
case 'userbroadcast': { // BROADCAST
|
||||||
if ((settings.cmd == 'shell') || (settings.cmd == 'upload') || (settings.cmd == 'download')) return;
|
if ((settings.cmd == 'shell') || (settings.cmd == 'upload') || (settings.cmd == 'download')) return;
|
||||||
|
if ((data.type == 'runcommands') && (settings.cmd != 'runcommand')) return;
|
||||||
if ((settings.multiresponse != null) && (settings.multiresponse > 1)) { settings.multiresponse--; break; }
|
if ((settings.multiresponse != null) && (settings.multiresponse > 1)) { settings.multiresponse--; break; }
|
||||||
if (data.responseid == 'meshctrl') {
|
if (data.responseid == 'meshctrl') {
|
||||||
if (data.meshid) { console.log(data.result, data.meshid); }
|
if (data.meshid) { console.log(data.result, data.meshid); }
|
||||||
|
@ -2208,6 +2254,7 @@ function serverConnect() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'createDeviceShareLink':
|
case 'createDeviceShareLink':
|
||||||
|
case 'webrelay':
|
||||||
if (data.result == 'OK') {
|
if (data.result == 'OK') {
|
||||||
if (data.publicid) { console.log('ID: ' + data.publicid); }
|
if (data.publicid) { console.log('ID: ' + data.publicid); }
|
||||||
console.log('URL: ' + data.url);
|
console.log('URL: ' + data.url);
|
||||||
|
@ -2619,8 +2666,8 @@ function getDevicesThatMatchFilter(nodes, x) {
|
||||||
} else if (tagSearch != null) {
|
} else if (tagSearch != null) {
|
||||||
// Tag filter
|
// Tag filter
|
||||||
for (var d in nodes) {
|
for (var d in nodes) {
|
||||||
if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(d); }
|
if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(nodes[d]); }
|
||||||
else if (nodes[d].tags != null) { for (var j in nodes[d].tags) { if (nodes[d].tags[j].toLowerCase() == tagSearch) { r.push(d); break; } } }
|
else if (nodes[d].tags != null) { for (var j in nodes[d].tags) { if (nodes[d].tags[j].toLowerCase() == tagSearch) { r.push(nodes[d]); break; } } }
|
||||||
}
|
}
|
||||||
} else if (agentTagSearch != null) {
|
} else if (agentTagSearch != null) {
|
||||||
// Agent Tag filter
|
// Agent Tag filter
|
||||||
|
|
|
@ -1347,6 +1347,7 @@ function CreateMeshRelayEx2(parent, ws, req, domain, user, cookie) {
|
||||||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||||
|
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||||
}
|
}
|
||||||
if (typeof domain.notificationmessages == 'object') {
|
if (typeof domain.notificationmessages == 'object') {
|
||||||
|
|
18
meshrelay.js
18
meshrelay.js
|
@ -119,6 +119,9 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
try { sr = parseInt(req.query.slowrelay); } catch (ex) { }
|
try { sr = parseInt(req.query.slowrelay); } catch (ex) { }
|
||||||
if ((typeof sr == 'number') && (sr > 0) && (sr < 1000)) { obj.ws.slowRelay = sr; }
|
if ((typeof sr == 'number') && (sr > 0) && (sr < 1000)) { obj.ws.slowRelay = sr; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if protocol is set in the cookie and if so replace req.query.p but only if its not already set or blank
|
||||||
|
if ((cookie != null) && (typeof cookie.p == 'number') && (obj.req.query.p === undefined || obj.req.query.p === "")) { obj.req.query.p = cookie.p; }
|
||||||
|
|
||||||
// Mesh Rights
|
// Mesh Rights
|
||||||
const MESHRIGHT_EDITMESH = 1;
|
const MESHRIGHT_EDITMESH = 1;
|
||||||
|
@ -509,7 +512,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
if (obj.req.query.p == 1) { msg = 'Started terminal session'; msgid = 14; }
|
if (obj.req.query.p == 1) { msg = 'Started terminal session'; msgid = 14; }
|
||||||
else if (obj.req.query.p == 2) { msg = 'Started desktop session'; msgid = 15; }
|
else if (obj.req.query.p == 2) { msg = 'Started desktop session'; msgid = 15; }
|
||||||
else if (obj.req.query.p == 5) { msg = 'Started file management session'; msgid = 16; }
|
else if (obj.req.query.p == 5) { msg = 'Started file management session'; msgid = 16; }
|
||||||
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: sessionUser._id, username: sessionUser.name, msgid: msgid, msgArgs: [obj.id, obj.peer.req.clientIp, req.clientIp], msg: msg + ' \"' + obj.id + '\" from ' + obj.peer.req.clientIp + ' to ' + req.clientIp, protocol: req.query.p, nodeid: req.query.nodeid };
|
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: sessionUser._id, username: sessionUser.name, msgid: msgid, msgArgs: [obj.id, req.clientIp, obj.peer.req.clientIp], msg: msg + ' \"' + obj.id + '\" from ' + req.clientIp + ' to ' + obj.peer.req.clientIp, protocol: req.query.p, nodeid: req.query.nodeid };
|
||||||
if (obj.guestname) { event.guestname = obj.guestname; } else if (relayinfo.peer1.guestname) { event.guestname = relayinfo.peer1.guestname; } // If this is a sharing session, set the guest name here.
|
if (obj.guestname) { event.guestname = obj.guestname; } else if (relayinfo.peer1.guestname) { event.guestname = relayinfo.peer1.guestname; } // If this is a sharing session, set the guest name here.
|
||||||
parent.parent.DispatchEvent(['*', sessionUser._id], obj, event);
|
parent.parent.DispatchEvent(['*', sessionUser._id], obj, event);
|
||||||
|
|
||||||
|
@ -884,7 +887,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
if (user != null) { rcookieData.ruserid = user._id; } else if (obj.nouser === true) { rcookieData.nouser = 1; }
|
if (user != null) { rcookieData.ruserid = user._id; } else if (obj.nouser === true) { rcookieData.nouser = 1; }
|
||||||
const rcookie = parent.parent.encodeCookie(rcookieData, parent.parent.loginCookieEncryptionKey);
|
const rcookie = parent.parent.encodeCookie(rcookieData, parent.parent.loginCookieEncryptionKey);
|
||||||
if (obj.id == null) { obj.id = parent.crypto.randomBytes(9).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } // If there is no connection id, generate one.
|
if (obj.id == null) { obj.id = parent.crypto.randomBytes(9).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } // If there is no connection id, generate one.
|
||||||
const command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/' + xdomain + 'meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr, soptions: {} };
|
const command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/' + xdomain + 'meshrelay.ashx?' + (obj.req.query.p != null ? ('p=' + obj.req.query.p + '&') : '') + 'id=' + obj.id + '&rauth=' + rcookie, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr, soptions: {} };
|
||||||
if (user) { command.userid = user._id; }
|
if (user) { command.userid = user._id; }
|
||||||
if (typeof domain.consentmessages == 'object') {
|
if (typeof domain.consentmessages == 'object') {
|
||||||
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
||||||
|
@ -893,6 +896,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||||
|
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||||
}
|
}
|
||||||
if (typeof domain.notificationmessages == 'object') {
|
if (typeof domain.notificationmessages == 'object') {
|
||||||
|
@ -923,7 +927,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
if (obj.id == null) { obj.id = parent.crypto.randomBytes(9).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } // If there is no connection id, generate one.
|
if (obj.id == null) { obj.id = parent.crypto.randomBytes(9).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } // If there is no connection id, generate one.
|
||||||
const rcookie = parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey);
|
const rcookie = parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey);
|
||||||
if (obj.req.query.tcpport != null) {
|
if (obj.req.query.tcpport != null) {
|
||||||
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: obj.req.query.tcpport, tcpaddr: ((obj.req.query.tcpaddr == null) ? '127.0.0.1' : obj.req.query.tcpaddr), soptions: {} };
|
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?' + (obj.req.query.p != null ? ('p=' + obj.req.query.p + '&') : '') + 'id=' + obj.id + '&rauth=' + rcookie, tcpport: obj.req.query.tcpport, tcpaddr: ((obj.req.query.tcpaddr == null) ? '127.0.0.1' : obj.req.query.tcpaddr), soptions: {} };
|
||||||
if (typeof domain.consentmessages == 'object') {
|
if (typeof domain.consentmessages == 'object') {
|
||||||
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
||||||
if (typeof domain.consentmessages.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
|
if (typeof domain.consentmessages.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
|
||||||
|
@ -931,6 +935,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||||
|
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||||
}
|
}
|
||||||
if (typeof domain.notificationmessages == 'object') {
|
if (typeof domain.notificationmessages == 'object') {
|
||||||
|
@ -942,14 +947,14 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
parent.parent.debug('relay', 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command));
|
parent.parent.debug('relay', 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command));
|
||||||
if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); }
|
if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); }
|
||||||
} else if (obj.req.query.udpport != null) {
|
} else if (obj.req.query.udpport != null) {
|
||||||
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, udpport: obj.req.query.udpport, udpaddr: ((obj.req.query.udpaddr == null) ? '127.0.0.1' : obj.req.query.udpaddr), soptions: {} };
|
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?' + (obj.req.query.p != null ? ('p=' + obj.req.query.p + '&') : '') + 'id=' + obj.id + '&rauth=' + rcookie, udpport: obj.req.query.udpport, udpaddr: ((obj.req.query.udpaddr == null) ? '127.0.0.1' : obj.req.query.udpaddr), soptions: {} }; if (typeof domain.consentmessages == 'object') {
|
||||||
if (typeof domain.consentmessages == 'object') {
|
|
||||||
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
||||||
if (typeof domain.consentmessages.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
|
if (typeof domain.consentmessages.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
|
||||||
if (typeof domain.consentmessages.terminal == 'string') { command.soptions.consentMsgTerminal = domain.consentmessages.terminal; }
|
if (typeof domain.consentmessages.terminal == 'string') { command.soptions.consentMsgTerminal = domain.consentmessages.terminal; }
|
||||||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||||
|
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||||
}
|
}
|
||||||
if (typeof domain.notificationmessages == 'object') {
|
if (typeof domain.notificationmessages == 'object') {
|
||||||
|
@ -1002,6 +1007,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||||
|
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||||
}
|
}
|
||||||
if (typeof domain.notificationmessages == 'object') {
|
if (typeof domain.notificationmessages == 'object') {
|
||||||
|
@ -1231,6 +1237,7 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
else if (req.query.p == 11) { protocolStr = 'SSH-TERM'; }
|
else if (req.query.p == 11) { protocolStr = 'SSH-TERM'; }
|
||||||
else if (req.query.p == 12) { protocolStr = 'VNC'; }
|
else if (req.query.p == 12) { protocolStr = 'VNC'; }
|
||||||
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; }
|
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; }
|
||||||
|
else if (req.query.p == 14) { protocolStr = 'Web-TCP'; }
|
||||||
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: obj.user._id, username: obj.user.name, msgid: 121, msgArgs: [obj.id, protocolStr, obj.host, Math.floor((Date.now() - obj.time) / 1000)], msg: 'Ended local relay session \"' + obj.id + '\", protocol ' + protocolStr + ' to ' + obj.host + ', ' + Math.floor((Date.now() - obj.time) / 1000) + ' second(s)', nodeid: obj.req.query.nodeid, protocol: req.query.p, in: inTraffc, out: outTraffc };
|
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: obj.user._id, username: obj.user.name, msgid: 121, msgArgs: [obj.id, protocolStr, obj.host, Math.floor((Date.now() - obj.time) / 1000)], msg: 'Ended local relay session \"' + obj.id + '\", protocol ' + protocolStr + ' to ' + obj.host + ', ' + Math.floor((Date.now() - obj.time) / 1000) + ' second(s)', nodeid: obj.req.query.nodeid, protocol: req.query.p, in: inTraffc, out: outTraffc };
|
||||||
if (obj.guestname) { event.guestname = obj.guestname; } // If this is a sharing session, set the guest name here.
|
if (obj.guestname) { event.guestname = obj.guestname; } // If this is a sharing session, set the guest name here.
|
||||||
parent.parent.DispatchEvent(['*', user._id], obj, event);
|
parent.parent.DispatchEvent(['*', user._id], obj, event);
|
||||||
|
@ -1285,6 +1292,7 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) {
|
||||||
else if (req.query.p == 11) { protocolStr = 'SSH-TERM'; }
|
else if (req.query.p == 11) { protocolStr = 'SSH-TERM'; }
|
||||||
else if (req.query.p == 12) { protocolStr = 'VNC'; }
|
else if (req.query.p == 12) { protocolStr = 'VNC'; }
|
||||||
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; }
|
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; }
|
||||||
|
else if (req.query.p == 14) { protocolStr = 'Web-TCP'; }
|
||||||
obj.time = Date.now();
|
obj.time = Date.now();
|
||||||
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: obj.user._id, username: obj.user.name, msgid: 120, msgArgs: [obj.id, protocolStr, obj.host], msg: 'Started local relay session \"' + obj.id + '\", protocol ' + protocolStr + ' to ' + obj.host, nodeid: req.query.nodeid, protocol: req.query.p };
|
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: obj.user._id, username: obj.user.name, msgid: 120, msgArgs: [obj.id, protocolStr, obj.host], msg: 'Started local relay session \"' + obj.id + '\", protocol ' + protocolStr + ' to ' + obj.host, nodeid: req.query.nodeid, protocol: req.query.p };
|
||||||
if (obj.guestname) { event.guestname = obj.guestname; } // If this is a sharing session, set the guest name here.
|
if (obj.guestname) { event.guestname = obj.guestname; } // If this is a sharing session, set the guest name here.
|
||||||
|
|
240
meshuser.js
240
meshuser.js
|
@ -600,7 +600,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof domain.userconsentflags == 'number') { serverinfo.consent = domain.userconsentflags; }
|
if (typeof domain.userconsentflags == 'number') { serverinfo.consent = domain.userconsentflags; }
|
||||||
if ((typeof domain.usersessionidletimeout == 'number') && (domain.usersessionidletimeout > 0)) { serverinfo.timeout = (domain.usersessionidletimeout * 60 * 1000); }
|
if ((typeof domain.usersessionidletimeout == 'number') && (domain.usersessionidletimeout > 0)) {serverinfo.timeout = (domain.usersessionidletimeout * 60 * 1000); }
|
||||||
|
if (typeof domain.logoutOnIdleSessionTimeout == 'boolean') {
|
||||||
|
serverinfo.logoutOnIdleSessionTimeout = domain.logoutOnIdleSessionTimeout;
|
||||||
|
} else {
|
||||||
|
// Default
|
||||||
|
serverinfo.logoutOnIdleSessionTimeout = true;
|
||||||
|
}
|
||||||
if (user.siteadmin === SITERIGHT_ADMIN) {
|
if (user.siteadmin === SITERIGHT_ADMIN) {
|
||||||
if (parent.parent.config.settings.managealldevicegroups.indexOf(user._id) >= 0) { serverinfo.manageAllDeviceGroups = true; }
|
if (parent.parent.config.settings.managealldevicegroups.indexOf(user._id) >= 0) { serverinfo.manageAllDeviceGroups = true; }
|
||||||
if (obj.crossDomain === true) { serverinfo.crossDomain = []; for (var i in parent.parent.config.domains) { serverinfo.crossDomain.push(i); } }
|
if (obj.crossDomain === true) { serverinfo.crossDomain = []; for (var i in parent.parent.config.domains) { serverinfo.crossDomain.push(i); } }
|
||||||
|
@ -922,7 +928,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
// Get a short file and send it back on the web socket
|
// Get a short file and send it back on the web socket
|
||||||
if (common.validateString(command.file, 1, 4096) == false) return;
|
if (common.validateString(command.file, 1, 4096) == false) return;
|
||||||
const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
|
const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
|
||||||
if (scpath == null) break;
|
if ((scpath == null) || (command.file !== parent.path.basename(command.file))) break;
|
||||||
const filePath = parent.path.join(scpath, command.file);
|
const filePath = parent.path.join(scpath, command.file);
|
||||||
fs.stat(filePath, function (err, stat) {
|
fs.stat(filePath, function (err, stat) {
|
||||||
if ((err != null) || (stat == null) || (stat.size >= 204800)) return;
|
if ((err != null) || (stat == null) || (stat.size >= 204800)) return;
|
||||||
|
@ -937,7 +943,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
if (common.validateString(command.file, 1, 4096) == false) return;
|
if (common.validateString(command.file, 1, 4096) == false) return;
|
||||||
if (typeof command.data != 'string') return;
|
if (typeof command.data != 'string') return;
|
||||||
const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
|
const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
|
||||||
if (scpath == null) break;
|
if ((scpath == null) || (command.file !== parent.path.basename(command.file))) break;
|
||||||
const filePath = parent.path.join(scpath, command.file);
|
const filePath = parent.path.join(scpath, command.file);
|
||||||
var data = null;
|
var data = null;
|
||||||
try { data = Buffer.from(command.data, 'base64'); } catch (ex) { return; }
|
try { data = Buffer.from(command.data, 'base64'); } catch (ex) { return; }
|
||||||
|
@ -997,6 +1003,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||||
|
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||||
}
|
}
|
||||||
if (typeof domain.notificationmessages == 'object') {
|
if (typeof domain.notificationmessages == 'object') {
|
||||||
|
@ -2948,6 +2955,48 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'webrelay':
|
||||||
|
{
|
||||||
|
if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
|
||||||
|
else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
|
||||||
|
else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
|
||||||
|
else if ((command.port != null) && (common.validateInt(command.port, 1, 65535) == false)) { err = 'Invalid port value'; } // Check the port if present
|
||||||
|
else {
|
||||||
|
if (command.nodeid.split('/').length == 1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
|
||||||
|
var snode = command.nodeid.split('/');
|
||||||
|
if ((snode.length != 3) || (snode[0] != 'node') || (snode[1] != domain.id)) { err = 'Invalid node id'; }
|
||||||
|
}
|
||||||
|
// Handle any errors
|
||||||
|
if (err != null) {
|
||||||
|
if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: err })); } catch (ex) { } }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Get the device rights
|
||||||
|
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
|
||||||
|
// If node not found or we don't have remote control, reject.
|
||||||
|
if (node == null) {
|
||||||
|
if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: 'Invalid node id' })); } catch (ex) { } }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var relayid = null;
|
||||||
|
var addr = null;
|
||||||
|
if (node.mtype == 3) { // Setup device relay if needed
|
||||||
|
var mesh = parent.meshes[node.meshid];
|
||||||
|
if (mesh && mesh.relayid) { relayid = mesh.relayid; addr = node.host; }
|
||||||
|
}
|
||||||
|
var webRelayDns = (args.relaydns != null) ? args.relaydns[0] : obj.getWebServerName(domain, req);
|
||||||
|
var webRelayPort = ((args.relaydns != null) ? ((typeof args.aliasport == 'number') ? args.aliasport : args.port) : ((parent.webrelayserver != null) ? ((typeof args.relayaliasport == 'number') ? args.relayaliasport : parent.webrelayserver.port) : 0));
|
||||||
|
if (webRelayPort == 0) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: 'WebRelay Disabled' })); return; } catch (ex) { } }
|
||||||
|
const authRelayCookie = parent.parent.encodeCookie({ ruserid: user._id, x: req.session.x }, parent.parent.loginCookieEncryptionKey);
|
||||||
|
var url = 'https://' + webRelayDns + ':' + webRelayPort + '/control-redirect.ashx?n=' + command.nodeid + '&p=' + command.port + '&appid=' + command.appid + '&c=' + authRelayCookie;
|
||||||
|
if (addr != null) { url += '&addr=' + addr; }
|
||||||
|
if (relayid != null) { url += '&relayid=' + relayid }
|
||||||
|
command.url = url;
|
||||||
|
if (command.responseid != null) { command.result = 'OK'; }
|
||||||
|
try { ws.send(JSON.stringify(command)); } catch (ex) { }
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'runcommands':
|
case 'runcommands':
|
||||||
{
|
{
|
||||||
if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
|
if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
|
||||||
|
@ -3586,6 +3635,41 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
parent.parent.DispatchEvent(targets, obj, event);
|
parent.parent.DispatchEvent(targets, obj, event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'otpduo':
|
||||||
|
{
|
||||||
|
// Do not allow this command if 2FA's are locked
|
||||||
|
if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
|
||||||
|
|
||||||
|
// Do not allow if Duo is not supported
|
||||||
|
if ((typeof domain.duo2factor != 'object') || (typeof domain.duo2factor.integrationkey != 'string') || (typeof domain.duo2factor.secretkey != 'string') || (typeof domain.duo2factor.apihostname != 'string')) return;
|
||||||
|
|
||||||
|
// Do not allow if Duo is disabled
|
||||||
|
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor == false)) return;
|
||||||
|
|
||||||
|
// Do not allow this command when logged in using a login token
|
||||||
|
if (req.session.loginToken != null) break;
|
||||||
|
|
||||||
|
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
|
||||||
|
|
||||||
|
// Check input
|
||||||
|
if ((typeof command.enabled != 'boolean') || (command.enabled != false)) return;
|
||||||
|
|
||||||
|
// See if we really need to change the state
|
||||||
|
if ((command.enabled === false) && (user.otpduo == null)) return;
|
||||||
|
|
||||||
|
// Change the duo 2FA of this user
|
||||||
|
delete user.otpduo;
|
||||||
|
parent.db.SetUser(user);
|
||||||
|
ws.send(JSON.stringify({ action: 'otpduo', success: true, enabled: command.enabled })); // Report success
|
||||||
|
|
||||||
|
// Notify change
|
||||||
|
var targets = ['*', 'server-users', user._id];
|
||||||
|
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
|
||||||
|
var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: command.enabled ? 160 : 161, msg: command.enabled ? "Enabled duo two-factor authentication." : "Disabled duo two-factor authentication.", domain: domain.id };
|
||||||
|
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.
|
||||||
|
parent.parent.DispatchEvent(targets, obj, event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'otpauth-request':
|
case 'otpauth-request':
|
||||||
{
|
{
|
||||||
// Do not allow this command if 2FA's are locked
|
// Do not allow this command if 2FA's are locked
|
||||||
|
@ -5012,14 +5096,36 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
|
|
||||||
// Merge any last connection information
|
// Merge any last connection information
|
||||||
const lc = lastConnects['lc' + results[i].node._id];
|
const lc = lastConnects['lc' + results[i].node._id];
|
||||||
if (lc != null) { delete lc._id; delete lc.type;; delete lc.meshid; delete lc.domain; results[i].lastConnect = lc; }
|
if (lc != null) { delete lc._id; delete lc.type; delete lc.meshid; delete lc.domain; results[i].lastConnect = lc; }
|
||||||
|
|
||||||
|
// Remove any connectivity and power state information, that should not be in the database anyway.
|
||||||
|
// TODO: Find why these are sometimes saved in the db.
|
||||||
|
if (results[i].node.conn != null) { delete results[i].node.conn; }
|
||||||
|
if (results[i].node.pwr != null) { delete results[i].node.pwr; }
|
||||||
|
if (results[i].node.agct != null) { delete results[i].node.agct; }
|
||||||
|
if (results[i].node.cict != null) { delete results[i].node.cict; }
|
||||||
|
|
||||||
|
// Add the connection state
|
||||||
|
var state = parent.parent.GetConnectivityState(results[i].node._id);
|
||||||
|
if (state) {
|
||||||
|
results[i].node.conn = state.connectivity;
|
||||||
|
results[i].node.pwr = state.powerState;
|
||||||
|
if ((state.connectivity & 1) != 0) { var agent = parent.wsagents[results[i].node._id]; if (agent != null) { results[i].node.agct = agent.connectTime; } }
|
||||||
|
|
||||||
|
// Use the connection time of the CIRA/Relay connection
|
||||||
|
if ((state.connectivity & 2) != 0) {
|
||||||
|
var ciraConnection = parent.parent.mpsserver.GetConnectionToNode(results[i].node._id, null, true);
|
||||||
|
if ((ciraConnection != null) && (ciraConnection.tag != null)) { results[i].node.cict = ciraConnection.tag.connectTime; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var output = null;
|
var output = null;
|
||||||
if (type == 'csv') {
|
if (type == 'csv') {
|
||||||
try {
|
try {
|
||||||
// Create the CSV file
|
// Create the CSV file
|
||||||
output = 'id,name,rname,host,icon,ip,osdesc,groupname,av,update,firewall,bitlocker,avdetails,tags,cpu,osbuild,biosDate,biosVendor,biosVersion,biosSerial,biosMode,boardName,boardVendor,boardVersion,productUuid,tpmversion,tpmmanufacturer,tpmmanufacturerversion,tpmisactivated,tpmisenabled,tpmisowned,totalMemory,agentOpenSSL,agentCommitDate,agentCommitHash,agentCompileTime,netIfCount,macs,addresses,lastConnectTime,lastConnectAddr\r\n';
|
output = 'id,name,rname,host,icon,ip,osdesc,groupname,av,update,firewall,bitlocker,avdetails,tags,lastbootuptime,cpu,osbuild,biosDate,biosVendor,biosVersion,biosSerial,biosMode,boardName,boardVendor,boardVersion,productUuid,tpmversion,tpmmanufacturer,tpmmanufacturerversion,tpmisactivated,tpmisenabled,tpmisowned,totalMemory,agentOpenSSL,agentCommitDate,agentCommitHash,agentCompileTime,netIfCount,macs,addresses,lastConnectTime,lastConnectAddr\r\n';
|
||||||
for (var i = 0; i < results.length; i++) {
|
for (var i = 0; i < results.length; i++) {
|
||||||
const nodeinfo = results[i];
|
const nodeinfo = results[i];
|
||||||
|
|
||||||
|
@ -5051,8 +5157,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
} else {
|
} else {
|
||||||
output += ',';
|
output += ',';
|
||||||
}
|
}
|
||||||
|
if (typeof n.lastbootuptime == 'number') { output += ',' + n.lastbootuptime; } else { output += ','; }
|
||||||
} else {
|
} else {
|
||||||
output += ',,,,,,,,,,,,,,,,,,,';
|
output += ',,,,,,,,,,,,,,,,,,,,';
|
||||||
}
|
}
|
||||||
|
|
||||||
// System infomation
|
// System infomation
|
||||||
|
@ -5500,7 +5607,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
'heapdump': [serverUserCommandHeapDump, ""],
|
'heapdump': [serverUserCommandHeapDump, ""],
|
||||||
'heapdump2': [serverUserCommandHeapDump2, ""],
|
'heapdump2': [serverUserCommandHeapDump2, ""],
|
||||||
'help': [serverUserCommandHelp, ""],
|
'help': [serverUserCommandHelp, ""],
|
||||||
'info': [serverUserCommandInfo, "Returns the most immidiatly useful information about this server, including MeshCentral and NodeJS versions. This is often information required to file a bug."],
|
'info': [serverUserCommandInfo, "Returns the most immidiatly useful information about this server, including MeshCentral and NodeJS versions. This is often information required to file a bug. Optionally use info h for human readable form."],
|
||||||
'le': [serverUserCommandLe, ""],
|
'le': [serverUserCommandLe, ""],
|
||||||
'lecheck': [serverUserCommandLeCheck, ""],
|
'lecheck': [serverUserCommandLeCheck, ""],
|
||||||
'leevents': [serverUserCommandLeEvents, ""],
|
'leevents': [serverUserCommandLeEvents, ""],
|
||||||
|
@ -5537,7 +5644,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
if (common.validateString(command.nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check the nodeid
|
if (common.validateString(command.nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check the nodeid
|
||||||
else if (common.validateInt(command.rights) == false) { err = 'Invalid rights'; } // Device rights must be an integer
|
else if (common.validateInt(command.rights) == false) { err = 'Invalid rights'; } // Device rights must be an integer
|
||||||
else if ((command.rights & 7) != 0) { err = 'Invalid rights'; } // EDITMESH, MANAGEUSERS or MANAGECOMPUTERS rights can't be assigned to a user to device link
|
else if ((command.rights & 7) != 0) { err = 'Invalid rights'; } // EDITMESH, MANAGEUSERS or MANAGECOMPUTERS rights can't be assigned to a user to device link
|
||||||
else if ((common.validateStrArray(command.usernames, 1, 64) == false) && (common.validateStrArray(command.userids, 1, 128) == false)) { err = 'Invalid usernames'; } // Username is between 1 and 64 characters
|
else if ((common.validateStrArray(command.usernames, 1, 128) == false) && (common.validateStrArray(command.userids, 1, 128) == false)) { err = 'Invalid usernames'; } // Username is between 1 and 128 characters
|
||||||
else {
|
else {
|
||||||
if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
|
if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
|
||||||
else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
|
else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
|
||||||
|
@ -5684,7 +5791,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
try {
|
try {
|
||||||
if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid groupid'; } // Check the meshid
|
if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid groupid'; } // Check the meshid
|
||||||
else if (common.validateInt(command.meshadmin) == false) { err = 'Invalid group rights'; } // Mesh rights must be an integer
|
else if (common.validateInt(command.meshadmin) == false) { err = 'Invalid group rights'; } // Mesh rights must be an integer
|
||||||
else if ((common.validateStrArray(command.usernames, 1, 64) == false) && (common.validateStrArray(command.userids, 1, 128) == false)) { err = 'Invalid usernames'; } // Username is between 1 and 64 characters
|
else if ((common.validateStrArray(command.usernames, 1, 128) == false) && (common.validateStrArray(command.userids, 1, 128) == false)) { err = 'Invalid usernames'; } // Username is between 1 and 128 characters
|
||||||
else {
|
else {
|
||||||
if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
|
if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
|
||||||
mesh = parent.meshes[command.meshid];
|
mesh = parent.meshes[command.meshid];
|
||||||
|
@ -6015,7 +6122,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
try {
|
try {
|
||||||
if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { err = 'Permission denied'; }
|
if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { err = 'Permission denied'; }
|
||||||
else if (common.validateString(command.ugrpid, 1, 1024) == false) { err = 'Invalid groupid'; } // Check the meshid
|
else if (common.validateString(command.ugrpid, 1, 1024) == false) { err = 'Invalid groupid'; } // Check the meshid
|
||||||
else if (common.validateStrArray(command.usernames, 1, 64) == false) { err = 'Invalid usernames'; } // Username is between 1 and 64 characters
|
else if (common.validateStrArray(command.usernames, 1, 128) == false) { err = 'Invalid usernames'; } // Username is between 1 and 128 characters
|
||||||
else {
|
else {
|
||||||
var ugroupidsplit = command.ugrpid.split('/');
|
var ugroupidsplit = command.ugrpid.split('/');
|
||||||
if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || ((obj.crossDomain !== true) && (ugroupidsplit[1] != domain.id))) { err = 'Invalid groupid'; }
|
if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || ((obj.crossDomain !== true) && (ugroupidsplit[1] != domain.id))) { err = 'Invalid groupid'; }
|
||||||
|
@ -6303,6 +6410,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
if (command.nodeid) { cookieContent.nodeid = command.nodeid; }
|
if (command.nodeid) { cookieContent.nodeid = command.nodeid; }
|
||||||
if (command.tcpaddr) { cookieContent.tcpaddr = command.tcpaddr; } // Indicates the browser want the agent to TCP connect to a remote address
|
if (command.tcpaddr) { cookieContent.tcpaddr = command.tcpaddr; } // Indicates the browser want the agent to TCP connect to a remote address
|
||||||
if (command.tcpport) { cookieContent.tcpport = command.tcpport; } // Indicates the browser want the agent to TCP connect to a remote port
|
if (command.tcpport) { cookieContent.tcpport = command.tcpport; } // Indicates the browser want the agent to TCP connect to a remote port
|
||||||
|
if (command.tag == 'novnc') { cookieContent.p = 12; } // If tag is novnc we must encode a protocol for meshrelay logging
|
||||||
if (node.mtype == 3) { cookieContent.lc = 1; command.localRelay = true; } // Indicate this is for a local connection
|
if (node.mtype == 3) { cookieContent.lc = 1; command.localRelay = true; } // Indicate this is for a local connection
|
||||||
command.cookie = parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey);
|
command.cookie = parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey);
|
||||||
command.trustedCert = parent.isTrustedCert(domain);
|
command.trustedCert = parent.isTrustedCert(domain);
|
||||||
|
@ -7445,7 +7553,26 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
}
|
}
|
||||||
|
|
||||||
function serverUserCommandInfo(cmdData) {
|
function serverUserCommandInfo(cmdData) {
|
||||||
var info = {};
|
function convertSeconds (s, form) {
|
||||||
|
if (!['long', 'shortprecise'].includes(form)) {
|
||||||
|
form = 'shortprecise';
|
||||||
|
}
|
||||||
|
let t = {}, r = '';
|
||||||
|
t.d = Math.floor(s / (24 * 3600));
|
||||||
|
s %= 24 * 3600;
|
||||||
|
t.h= Math.floor(s / 3600);
|
||||||
|
s %= 3600;
|
||||||
|
t.m = Math.floor(s / 60);
|
||||||
|
t.s =(s%60).toFixed(0);
|
||||||
|
if ( form == 'long') {
|
||||||
|
r = t.d + ((t.d == 1) ? ' day, ' : ' days, ') + t.h + ((t.h == 1) ? ' hour, ' : ' hours, ') + t.m + ((t.m == 1) ? ' minute, ' : ' minutes, ') + t.s+ ((t.s == 1) ? ' second' : ' seconds');
|
||||||
|
} else if (form == 'shortprecise') {
|
||||||
|
r = String(t.d).padStart(2, '0') + ':' + String(t.h).padStart(2, '0') + ':' + String(t.m).padStart(2, '0') + ':' + String((s%60).toFixed(2)).padStart(5, '0') + 's';
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
var info = {}, arg = null, t = {}, r = '';
|
||||||
|
if ((cmdData.cmdargs['_'] != null) && (cmdData.cmdargs['_'][0] != null)) { arg = cmdData.cmdargs['_'][0].toLowerCase(); }
|
||||||
try { info.meshVersion = 'v' + parent.parent.currentVer; } catch (ex) { }
|
try { info.meshVersion = 'v' + parent.parent.currentVer; } catch (ex) { }
|
||||||
try { info.nodeVersion = process.version; } catch (ex) { }
|
try { info.nodeVersion = process.version; } catch (ex) { }
|
||||||
try { info.runMode = (["Hybrid (LAN + WAN) mode", "WAN mode", "LAN mode"][(args.lanonly ? 2 : (args.wanonly ? 1 : 0))]); } catch (ex) { }
|
try { info.runMode = (["Hybrid (LAN + WAN) mode", "WAN mode", "LAN mode"][(args.lanonly ? 2 : (args.wanonly ? 1 : 0))]); } catch (ex) { }
|
||||||
|
@ -7457,9 +7584,24 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
try { info.platform = process.platform; } catch (ex) { }
|
try { info.platform = process.platform; } catch (ex) { }
|
||||||
try { info.arch = process.arch; } catch (ex) { }
|
try { info.arch = process.arch; } catch (ex) { }
|
||||||
try { info.pid = process.pid; } catch (ex) { }
|
try { info.pid = process.pid; } catch (ex) { }
|
||||||
try { info.uptime = process.uptime(); } catch (ex) { }
|
if (arg == 'h') {
|
||||||
try { info.cpuUsage = process.cpuUsage(); } catch (ex) { }
|
try {
|
||||||
try { info.memoryUsage = process.memoryUsage(); } catch (ex) { }
|
info.uptime = convertSeconds(process.uptime(), 'long');
|
||||||
|
info.cpuUsage = {
|
||||||
|
system: (convertSeconds(process.cpuUsage().system /1000000)),
|
||||||
|
user: (convertSeconds(process.cpuUsage().user /1000000))
|
||||||
|
}
|
||||||
|
info.memoryUsage = {};
|
||||||
|
for (const [key,value] of Object.entries(process.memoryUsage())){
|
||||||
|
info.memoryUsage[key] = ([value]/1048576).toFixed(2) + 'Mb';
|
||||||
|
}
|
||||||
|
} catch (ex) { }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try { info.uptime = process.uptime(); } catch (ex) { }
|
||||||
|
try { info.cpuUsage = process.cpuUsage(); } catch (ex) { }
|
||||||
|
try { info.memoryUsage = process.memoryUsage(); } catch (ex) { }
|
||||||
|
}
|
||||||
try { info.warnings = parent.parent.getServerWarnings(); } catch (ex) { console.log(ex); }
|
try { info.warnings = parent.parent.getServerWarnings(); } catch (ex) { console.log(ex); }
|
||||||
try { info.allDevGroupManagers = parent.parent.config.settings.managealldevicegroups; } catch (ex) { }
|
try { info.allDevGroupManagers = parent.parent.config.settings.managealldevicegroups; } catch (ex) { }
|
||||||
try { if (process.traceDeprecation == true) { info.traceDeprecation = true; } } catch (ex) { }
|
try { if (process.traceDeprecation == true) { info.traceDeprecation = true; } } catch (ex) { }
|
||||||
|
@ -7949,42 +8091,46 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
parent.common.unEscapeAllLinksFieldName(docs);
|
parent.common.unEscapeAllLinksFieldName(docs);
|
||||||
|
|
||||||
var results = [], resultPendingCount = 0;
|
var results = [], resultPendingCount = 0;
|
||||||
for (i in docs) {
|
if (docs.length == 0) { // no results return blank array
|
||||||
// Check device links, if a link points to an unknown user, remove it.
|
func(docs, type);
|
||||||
parent.cleanDevice(docs[i]);
|
} else {
|
||||||
|
for (i in docs) {
|
||||||
|
// Check device links, if a link points to an unknown user, remove it.
|
||||||
|
parent.cleanDevice(docs[i]);
|
||||||
|
|
||||||
// Fetch the node from the database
|
// Fetch the node from the database
|
||||||
resultPendingCount++;
|
resultPendingCount++;
|
||||||
const getNodeFunc = function (node, rights, visible) {
|
const getNodeFunc = function (node, rights, visible) {
|
||||||
if ((node != null) && (visible == true)) {
|
if ((node != null) && (visible == true)) {
|
||||||
const getNodeSysInfoFunc = function (err, docs) {
|
const getNodeSysInfoFunc = function (err, docs) {
|
||||||
const getNodeNetInfoFunc = function (err, docs) {
|
const getNodeNetInfoFunc = function (err, docs) {
|
||||||
var netinfo = null;
|
var netinfo = null;
|
||||||
if ((err == null) && (docs != null) && (docs.length == 1)) { netinfo = docs[0]; }
|
if ((err == null) && (docs != null) && (docs.length == 1)) { netinfo = docs[0]; }
|
||||||
resultPendingCount--;
|
resultPendingCount--;
|
||||||
getNodeNetInfoFunc.results.push({ node: parent.CloneSafeNode(getNodeNetInfoFunc.node), sys: getNodeNetInfoFunc.sysinfo, net: netinfo });
|
getNodeNetInfoFunc.results.push({ node: parent.CloneSafeNode(getNodeNetInfoFunc.node), sys: getNodeNetInfoFunc.sysinfo, net: netinfo });
|
||||||
if (resultPendingCount == 0) { func(getNodeFunc.results, type); }
|
if (resultPendingCount == 0) { func(getNodeFunc.results, type); }
|
||||||
|
}
|
||||||
|
getNodeNetInfoFunc.results = getNodeSysInfoFunc.results;
|
||||||
|
getNodeNetInfoFunc.nodeid = getNodeSysInfoFunc.nodeid;
|
||||||
|
getNodeNetInfoFunc.node = getNodeSysInfoFunc.node;
|
||||||
|
if ((err == null) && (docs != null) && (docs.length == 1)) { getNodeNetInfoFunc.sysinfo = docs[0]; }
|
||||||
|
|
||||||
|
// Query the database for network information
|
||||||
|
db.Get('if' + getNodeSysInfoFunc.nodeid, getNodeNetInfoFunc);
|
||||||
}
|
}
|
||||||
getNodeNetInfoFunc.results = getNodeSysInfoFunc.results;
|
getNodeSysInfoFunc.results = getNodeFunc.results;
|
||||||
getNodeNetInfoFunc.nodeid = getNodeSysInfoFunc.nodeid;
|
getNodeSysInfoFunc.nodeid = getNodeFunc.nodeid;
|
||||||
getNodeNetInfoFunc.node = getNodeSysInfoFunc.node;
|
getNodeSysInfoFunc.node = node;
|
||||||
if ((err == null) && (docs != null) && (docs.length == 1)) { getNodeNetInfoFunc.sysinfo = docs[0]; }
|
|
||||||
|
|
||||||
// Query the database for network information
|
// Query the database for system information
|
||||||
db.Get('if' + getNodeSysInfoFunc.nodeid, getNodeNetInfoFunc);
|
db.Get('si' + getNodeFunc.nodeid, getNodeSysInfoFunc);
|
||||||
}
|
} else { resultPendingCount--; }
|
||||||
getNodeSysInfoFunc.results = getNodeFunc.results;
|
if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
|
||||||
getNodeSysInfoFunc.nodeid = getNodeFunc.nodeid;
|
}
|
||||||
getNodeSysInfoFunc.node = node;
|
getNodeFunc.results = results;
|
||||||
|
getNodeFunc.nodeid = docs[i]._id;
|
||||||
// Query the database for system information
|
parent.GetNodeWithRights(domain, user, docs[i]._id, getNodeFunc);
|
||||||
db.Get('si' + getNodeFunc.nodeid, getNodeSysInfoFunc);
|
|
||||||
} else { resultPendingCount--; }
|
|
||||||
if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
|
|
||||||
}
|
}
|
||||||
getNodeFunc.results = results;
|
|
||||||
getNodeFunc.nodeid = docs[i]._id;
|
|
||||||
parent.GetNodeWithRights(domain, user, docs[i]._id, getNodeFunc);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -8182,11 +8328,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null));
|
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null));
|
||||||
var sms2fa = ((parent.parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)));
|
var sms2fa = ((parent.parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)));
|
||||||
var msg2fa = ((parent.parent.msgserver != null) && (parent.parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)));
|
var msg2fa = ((parent.parent.msgserver != null) && (parent.parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)));
|
||||||
|
var duo2fa = ((typeof domain.passwordrequirements != 'object') || (typeof domain.passwordrequirements.duo2factor == 'object'));
|
||||||
var authFactorCount = 0;
|
var authFactorCount = 0;
|
||||||
if (typeof user.otpsecret == 'string') { authFactorCount++; } // Authenticator time factor
|
if (typeof user.otpsecret == 'string') { authFactorCount++; } // Authenticator time factor
|
||||||
if (email2fa && (user.otpekey != null)) { authFactorCount++; } // EMail factor
|
if (email2fa && (user.otpekey != null)) { authFactorCount++; } // EMail factor
|
||||||
if (sms2fa && (user.phone != null)) { authFactorCount++; } // SMS factor
|
if (sms2fa && (user.phone != null)) { authFactorCount++; } // SMS factor
|
||||||
if (msg2fa && (user.msghandle != null)) { authFactorCount++; } // Messaging factor
|
if (msg2fa && (user.msghandle != null)) { authFactorCount++; } // Messaging factor
|
||||||
|
if (duo2fa && (user.otpduo != null)) { authFactorCount++; } // Duo authentication factor
|
||||||
if (user.otphkeys != null) { authFactorCount += user.otphkeys.length; } // FIDO hardware factor
|
if (user.otphkeys != null) { authFactorCount += user.otphkeys.length; } // FIDO hardware factor
|
||||||
if ((authFactorCount > 0) && (user.otpkeys != null)) { authFactorCount++; } // Backup keys
|
if ((authFactorCount > 0) && (user.otpkeys != null)) { authFactorCount++; } // Backup keys
|
||||||
return authFactorCount;
|
return authFactorCount;
|
||||||
|
|
117
monitoring.js
Normal file
117
monitoring.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* @description MeshCentral monitoring module
|
||||||
|
* @author Simon Smith
|
||||||
|
* @license Apache-2.0
|
||||||
|
* @version v0.0.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports.CreateMonitoring = function (parent, args) {
|
||||||
|
var obj = {};
|
||||||
|
obj.args = args;
|
||||||
|
obj.parent = parent;
|
||||||
|
obj.express = require('express');
|
||||||
|
obj.app = obj.express();
|
||||||
|
obj.prometheus = null;
|
||||||
|
if (args.compression !== false) { obj.app.use(require('compression')()); }
|
||||||
|
obj.app.disable('x-powered-by');
|
||||||
|
obj.counterMetrics = { // Counter Metrics always start at 0 and increase but never decrease
|
||||||
|
RelayErrors: { description: "Relay Errors" }, // parent.webserver.relaySessionErrorCount
|
||||||
|
UnknownGroup: { description: "Unknown Group" }, // meshDoesNotExistCount
|
||||||
|
InvalidPKCSsignature: { description: "Invalid PKCS signature" }, // invalidPkcsSignatureCount
|
||||||
|
InvalidRSAsignature: { description: "Invalid RSA signature" }, // invalidRsaSignatureCount
|
||||||
|
InvalidJSON: { description: "Invalid JSON" }, // invalidJsonCount
|
||||||
|
UnknownAction: { description: "Unknown Action" }, // unknownAgentActionCount
|
||||||
|
BadWebCertificate: { description: "Bad Web Certificate" }, // agentBadWebCertHashCount
|
||||||
|
BadSignature: { description: "Bad Signature" }, // (agentBadSignature1Count + agentBadSignature2Count)
|
||||||
|
MaxSessionsReached: { description: "Max Sessions Reached" }, // agentMaxSessionHoldCount
|
||||||
|
UnknownDeviceGroup: { description: "Unknown Device Group" }, // (invalidDomainMeshCount + invalidDomainMesh2Count)
|
||||||
|
InvalidDeviceGroupType: { description: "Invalid Device Group Type" }, // invalidMeshTypeCount
|
||||||
|
DuplicateAgent: { description: "Duplicate Agent" }, // duplicateAgentCount
|
||||||
|
blockedUsers: { description: "Blocked Users" }, // blockedUsers
|
||||||
|
blockedAgents: { description: "Blocked Agents" }, // blockedAgents
|
||||||
|
};
|
||||||
|
obj.gaugeMetrics = { // Gauge Metrics always start at 0 and can increase and decrease
|
||||||
|
ConnectedIntelAMT: { description: "Connected Intel AMT" }, // parent.mpsserver.ciraConnections[i].length
|
||||||
|
UserAccounts: { description: "User Accounts" }, // Object.keys(parent.webserver.users).length
|
||||||
|
DeviceGroups: { description: "Device Groups" }, // parent.webserver.meshes (ONLY WHERE deleted=null)
|
||||||
|
AgentSessions: { description: "Agent Sessions" }, // Object.keys(parent.webserver.wsagents).length
|
||||||
|
ConnectedUsers: { description: "Connected Users" }, // Object.keys(parent.webserver.wssessions).length
|
||||||
|
UsersSessions: { description: "Users Sessions" }, // Object.keys(parent.webserver.wssessions2).length
|
||||||
|
RelaySessions: { description: "Relay Sessions" }, // parent.webserver.relaySessionCount
|
||||||
|
RelayCount: { description: "Relay Count" } // Object.keys(parent.webserver.wsrelays).length30bb4fb74dfb758d36be52a7
|
||||||
|
}
|
||||||
|
obj.collectors = [];
|
||||||
|
if (parent.config.settings.prometheus != null) { // Create Prometheus Monitoring Endpoint
|
||||||
|
if ((typeof parent.config.settings.prometheus == 'number') && ((parent.config.settings.prometheus < 1) || (parent.config.settings.prometheus > 65535))) {
|
||||||
|
console.log('Promethus port number is invalid, Prometheus metrics endpoint has be disabled');
|
||||||
|
delete parent.config.settings.prometheus;
|
||||||
|
} else {
|
||||||
|
const port = ((typeof parent.config.settings.prometheus == 'number') ? parent.config.settings.prometheus : 9464);
|
||||||
|
obj.prometheus = require('prom-client');
|
||||||
|
const collectDefaultMetrics = obj.prometheus.collectDefaultMetrics;
|
||||||
|
collectDefaultMetrics();
|
||||||
|
for (const key in obj.gaugeMetrics) {
|
||||||
|
obj.gaugeMetrics[key].prometheus = new obj.prometheus.Gauge({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.gaugeMetrics[key].description });
|
||||||
|
}
|
||||||
|
for (const key in obj.counterMetrics) {
|
||||||
|
obj.counterMetrics[key].prometheus = new obj.prometheus.Counter({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.counterMetrics[key].description });
|
||||||
|
}
|
||||||
|
obj.app.get('/', function (req, res) { res.send('MeshCentral Prometheus server.'); });
|
||||||
|
obj.app.listen(port, function () {
|
||||||
|
console.log('MeshCentral Prometheus server running on port ' + port + '.');
|
||||||
|
obj.parent.updateServerState('prometheus-port', port);
|
||||||
|
});
|
||||||
|
obj.app.get('/metrics', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Count the number of device groups that are not deleted
|
||||||
|
var activeDeviceGroups = 0;
|
||||||
|
for (var i in parent.webserver.meshes) { if (parent.webserver.meshes[i].deleted == null) { activeDeviceGroups++; } } // This is not ideal for performance, we want to dome something better.
|
||||||
|
var gauges = {
|
||||||
|
UserAccounts: Object.keys(parent.webserver.users).length,
|
||||||
|
DeviceGroups: activeDeviceGroups,
|
||||||
|
AgentSessions: Object.keys(parent.webserver.wsagents).length,
|
||||||
|
ConnectedUsers: Object.keys(parent.webserver.wssessions).length,
|
||||||
|
UsersSessions: Object.keys(parent.webserver.wssessions2).length,
|
||||||
|
RelaySessions: parent.webserver.relaySessionCount,
|
||||||
|
RelayCount: Object.keys(parent.webserver.wsrelays).length,
|
||||||
|
ConnectedIntelAMT: 0
|
||||||
|
};
|
||||||
|
if (parent.mpsserver != null) {
|
||||||
|
for (var i in parent.mpsserver.ciraConnections) {
|
||||||
|
gauges.ConnectedIntelAMT += parent.mpsserver.ciraConnections[i].length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key in gauges) { obj.gaugeMetrics[key].prometheus.set(gauges[key]); }
|
||||||
|
// Take a look at agent errors
|
||||||
|
var agentstats = parent.webserver.getAgentStats();
|
||||||
|
const counters = {
|
||||||
|
RelayErrors: parent.webserver.relaySessionErrorCount,
|
||||||
|
UnknownGroup: agentstats.meshDoesNotExistCount,
|
||||||
|
InvalidPKCSsignature: agentstats.invalidPkcsSignatureCount,
|
||||||
|
InvalidRSAsignature: agentstats.invalidRsaSignatureCount,
|
||||||
|
InvalidJSON: agentstats.invalidJsonCount,
|
||||||
|
UnknownAction: agentstats.unknownAgentActionCount,
|
||||||
|
BadWebCertificate: agentstats.agentBadWebCertHashCount,
|
||||||
|
BadSignature: (agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count),
|
||||||
|
MaxSessionsReached: agentstats.agentMaxSessionHoldCount,
|
||||||
|
UnknownDeviceGroup: (agentstats.invalidDomainMeshCount + agentstats.invalidDomainMesh2Count),
|
||||||
|
InvalidDeviceGroupType: (agentstats.invalidMeshTypeCount + agentstats.invalidMeshType2Count),
|
||||||
|
DuplicateAgent: agentstats.duplicateAgentCount,
|
||||||
|
blockedUsers: parent.webserver.blockedUsers,
|
||||||
|
blockedAgents: parent.webserver.blockedAgents
|
||||||
|
};
|
||||||
|
for (const key in counters) { obj.counterMetrics[key].prometheus.reset(); obj.counterMetrics[key].prometheus.inc(counters[key]); }
|
||||||
|
res.set('Content-Type', obj.prometheus.register.contentType);
|
||||||
|
await Promise.all(obj.collectors.map((collector) => (collector(req, res))));
|
||||||
|
res.end(await obj.prometheus.register.metrics());
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
1547
package-lock.json
generated
1547
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "meshcentral",
|
"name": "meshcentral",
|
||||||
"version": "1.1.33",
|
"version": "1.1.39",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Remote Device Management",
|
"Remote Device Management",
|
||||||
"Remote Device Monitoring",
|
"Remote Device Monitoring",
|
||||||
|
@ -37,13 +37,13 @@
|
||||||
"sample-config-advanced.json"
|
"sample-config-advanced.json"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@yetzt/nedb": "1.8.0",
|
"@seald-io/nedb": "4.0.4",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"cbor": "5.2.0",
|
"cbor": "5.2.0",
|
||||||
"compression": "1.7.4",
|
"compression": "1.7.5",
|
||||||
"cookie-session": "2.1.0",
|
"cookie-session": "2.1.0",
|
||||||
"express": "4.21.1",
|
"express": "4.21.2",
|
||||||
"express-handlebars": "7.1.3",
|
"express-handlebars": "7.1.3",
|
||||||
"express-ws": "5.0.2",
|
"express-ws": "5.0.2",
|
||||||
"ipcheck": "0.1.0",
|
"ipcheck": "0.1.0",
|
||||||
|
|
|
@ -139,7 +139,7 @@ module.exports.pluginHandler = function (parent) {
|
||||||
try {
|
try {
|
||||||
obj.plugins[p][hookName](...args);
|
obj.plugins[p][hookName](...args);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error ocurred while running plugin hook" + p + ':' + hookName + ' (' + e + ')');
|
console.log("Error occurred while running plugin hook" + p + ':' + hookName + ' (' + e + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ module.exports.pluginHandler = function (parent) {
|
||||||
panel[p].header = obj.plugins[p].on_device_header();
|
panel[p].header = obj.plugins[p].on_device_header();
|
||||||
panel[p].content = obj.plugins[p].on_device_page();
|
panel[p].content = obj.plugins[p].on_device_page();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error ocurred while getting plugin views " + p + ':' + ' (' + e + ')');
|
console.log("Error occurred while getting plugin views " + p + ':' + ' (' + e + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
public/images/duo-2fa-250-disable.png
Normal file
BIN
public/images/duo-2fa-250-disable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
public/images/duo-2fa-250.png
Normal file
BIN
public/images/duo-2fa-250.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
public/images/login/2fa-duo-48.png
Normal file
BIN
public/images/login/2fa-duo-48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/login/2fa-duo-96.png
Normal file
BIN
public/images/login/2fa-duo-96.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
|
@ -180,6 +180,7 @@
|
||||||
self.prevClipboardText = null;
|
self.prevClipboardText = null;
|
||||||
self.clipboardReadTimer = setInterval(function(){
|
self.clipboardReadTimer = setInterval(function(){
|
||||||
if(navigator.clipboard.readText != null){
|
if(navigator.clipboard.readText != null){
|
||||||
|
if (Mstsc.browser() == 'firefox') return; // this is needed because firefox pops up a PASTE option every second which is annoying
|
||||||
navigator.clipboard.readText()
|
navigator.clipboard.readText()
|
||||||
.then(function(data){
|
.then(function(data){
|
||||||
if(data != self.prevClipboard){
|
if(data != self.prevClipboard){
|
||||||
|
|
963
public/scripts/agent-desktop-0.0.2-min.js
vendored
963
public/scripts/agent-desktop-0.0.2-min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -155,7 +155,6 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
|
||||||
if (Msg[1] == 1) { obj.ProcessCopyRectMsg(Msg[2]); }
|
if (Msg[1] == 1) { obj.ProcessCopyRectMsg(Msg[2]); }
|
||||||
else if (Msg[1] == 2) { obj.Canvas.drawImage(Msg[2], obj.rotX(Msg[3], Msg[4]), obj.rotY(Msg[3], Msg[4])); delete Msg[2]; }
|
else if (Msg[1] == 2) { obj.Canvas.drawImage(Msg[2], obj.rotX(Msg[3], Msg[4]), obj.rotY(Msg[3], Msg[4])); delete Msg[2]; }
|
||||||
obj.PendingOperations.splice(i, 1);
|
obj.PendingOperations.splice(i, 1);
|
||||||
delete Msg;
|
|
||||||
obj.TilesDrawn++;
|
obj.TilesDrawn++;
|
||||||
if ((obj.TilesDrawn == obj.tilesReceived) && (obj.KillDraw < obj.TilesDrawn)) { obj.KillDraw = obj.TilesDrawn = obj.tilesReceived = 0; }
|
if ((obj.TilesDrawn == obj.tilesReceived) && (obj.KillDraw < obj.TilesDrawn)) { obj.KillDraw = obj.TilesDrawn = obj.tilesReceived = 0; }
|
||||||
return true;
|
return true;
|
||||||
|
@ -221,12 +220,16 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
|
||||||
if ((cmd == 3) || (cmd == 4) || (cmd == 7)) { X = (view[4] << 8) + view[5]; Y = (view[6] << 8) + view[7]; }
|
if ((cmd == 3) || (cmd == 4) || (cmd == 7)) { X = (view[4] << 8) + view[5]; Y = (view[6] << 8) + view[7]; }
|
||||||
if (obj.debugmode > 2) { console.log('CMD', cmd, cmdsize, X, Y); }
|
if (obj.debugmode > 2) { console.log('CMD', cmd, cmdsize, X, Y); }
|
||||||
|
|
||||||
|
// Fix for view being too large for String.fromCharCode.apply()
|
||||||
|
var chunkSize = 10000;
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < view.length; i += chunkSize) { result += String.fromCharCode.apply(null, view.slice(i, i + chunkSize)); }
|
||||||
// Record the command if needed
|
// Record the command if needed
|
||||||
if (obj.recordedData != null) {
|
if (obj.recordedData != null) {
|
||||||
if (cmdsize > 65000) {
|
if (cmdsize > 65000) {
|
||||||
obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(27) + obj.shortToStr(8) + obj.intToStr(cmdsize) + obj.shortToStr(cmd) + obj.shortToStr(0) + obj.shortToStr(0) + obj.shortToStr(0) + String.fromCharCode.apply(null, view)));
|
obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(27) + obj.shortToStr(8) + obj.intToStr(cmdsize) + obj.shortToStr(cmd) + obj.shortToStr(0) + obj.shortToStr(0) + obj.shortToStr(0) + result));
|
||||||
} else {
|
} else {
|
||||||
obj.recordedData.push(recordingEntry(2, 1, String.fromCharCode.apply(null, view)));
|
obj.recordedData.push(recordingEntry(2, 1, result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
public/scripts/agent-rdp-0.0.1-min.js
vendored
2
public/scripts/agent-rdp-0.0.1-min.js
vendored
File diff suppressed because one or more lines are too long
2
public/scripts/amt-desktop-0.0.2-min.js
vendored
2
public/scripts/amt-desktop-0.0.2-min.js
vendored
File diff suppressed because one or more lines are too long
2
public/scripts/amt-ider-ws-0.0.1-min.js
vendored
2
public/scripts/amt-ider-ws-0.0.1-min.js
vendored
File diff suppressed because one or more lines are too long
2
public/scripts/amt-redir-ws-0.1.0-min.js
vendored
2
public/scripts/amt-redir-ws-0.1.0-min.js
vendored
File diff suppressed because one or more lines are too long
2
public/scripts/amt-terminal-0.0.2-min.js
vendored
2
public/scripts/amt-terminal-0.0.2-min.js
vendored
File diff suppressed because one or more lines are too long
2
public/scripts/amt-wsman-0.2.0-min.js
vendored
2
public/scripts/amt-wsman-0.2.0-min.js
vendored
File diff suppressed because one or more lines are too long
2
public/scripts/bootstrap-min.js
vendored
2
public/scripts/bootstrap-min.js
vendored
File diff suppressed because one or more lines are too long
1894
public/scripts/bootstrap.js
vendored
1894
public/scripts/bootstrap.js
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/scripts/common-0.0.1-min.js
vendored
2
public/scripts/common-0.0.1-min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -17,6 +17,7 @@ function QV(x, y) { try { QS(x).display = (y ? '' : 'none'); } catch (x) { } }
|
||||||
function QA(x, y) { Q(x).innerHTML += y; } // "Q" append
|
function QA(x, y) { Q(x).innerHTML += y; } // "Q" append
|
||||||
function QH(x, y) { Q(x).innerHTML = y; } // "Q" html
|
function QH(x, y) { Q(x).innerHTML = y; } // "Q" html
|
||||||
function QC(x) { try { return Q(x).classList; } catch (x) { } } // "Q" class
|
function QC(x) { try { return Q(x).classList; } catch (x) { } } // "Q" class
|
||||||
|
function QVH(x, y) { try { y ? Q(x).classList.remove('visually-hidden') : Q(x).classList.add('visually-hidden'); } catch (x) { } } // "Q" visibility
|
||||||
|
|
||||||
// Move cursor to end of input box
|
// Move cursor to end of input box
|
||||||
function inputBoxFocus(x) { Q(x).focus(); var v = Q(x).value; Q(x).value = ''; Q(x).value = v; }
|
function inputBoxFocus(x) { Q(x).focus(); var v = Q(x).value; Q(x).value = ''; Q(x).value = v; }
|
||||||
|
|
2
public/scripts/ol3-contextmenu-min.js
vendored
2
public/scripts/ol3-contextmenu-min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,29 +1,21 @@
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const themeStylesheet = document.getElementById("theme-stylesheet");
|
||||||
const themeSwitcher = document.getElementById('theme-switcher');
|
|
||||||
const themeStylesheet = document.getElementById('theme-stylesheet');
|
|
||||||
|
|
||||||
// Load saved theme from local storage
|
// Load saved theme from local storage
|
||||||
const savedTheme = localStorage.getItem('theme');
|
const savedTheme = localStorage.getItem("theme");
|
||||||
if (savedTheme) {
|
if (savedTheme) {
|
||||||
const safeTheme = encodeURIComponent(savedTheme);
|
const safeTheme = ((savedTheme != 'default') ? encodeURIComponent(savedTheme) : encodeURIComponent('..'));
|
||||||
themeStylesheet.href = `styles/themes/${safeTheme}/bootstrap.min.css`;
|
themeStylesheet.href = `styles/themes/${safeTheme}/bootstrap-min.css`;
|
||||||
themeSwitcher.value = savedTheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change theme on selection
|
|
||||||
themeSwitcher.addEventListener('change', function () {
|
|
||||||
const selectedTheme = themeSwitcher.value;
|
|
||||||
const safeTheme = encodeURIComponent(selectedTheme);
|
|
||||||
themeStylesheet.href = `styles/themes/${safeTheme}/bootstrap.min.css`;
|
|
||||||
// Save selected theme to local storage
|
|
||||||
localStorage.setItem('theme', selectedTheme);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize Select2 on all select elements with the 'select2' class
|
// Initialize Select2 on all select elements with the 'select2' class
|
||||||
$('.select2').select2({
|
$(".select2").select2({
|
||||||
theme: 'bootstrap-5',
|
theme: "bootstrap-5",
|
||||||
width: $( this ).data( 'width' ) ? $( this ).data( 'width' ) : $( this ).hasClass( 'w-100' ) ? '100%' : 'style',
|
width: $(this).data("width")
|
||||||
placeholder: $( this ).data( 'placeholder' ),
|
? $(this).data("width")
|
||||||
|
: $(this).hasClass("w-100")
|
||||||
|
? "100%"
|
||||||
|
: "style",
|
||||||
|
placeholder: $(this).data("placeholder"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
2
public/scripts/xterm-addon-fit-min.js
vendored
2
public/scripts/xterm-addon-fit-min.js
vendored
|
@ -1 +1 @@
|
||||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(window,function(){return r=[function(e,t,r){function n(){}Object.defineProperty(t,"__esModule",{value:!0}),n.prototype.activate=function(e){this._terminal=e},n.prototype.dispose=function(){},n.prototype.fit=function(){var e,t=this.proposeDimensions();t&&this._terminal&&(e=this._terminal._core,this._terminal.rows===t.rows&&this._terminal.cols===t.cols||(e._renderService.clear(),this._terminal.resize(t.cols,t.rows)))},n.prototype.proposeDimensions=function(){var e,t,r,n;if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement)return e=this._terminal._core,n=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(n.getPropertyValue("height")),n=Math.max(0,parseInt(n.getPropertyValue("width"))),t=window.getComputedStyle(this._terminal.element),r=r-(parseInt(t.getPropertyValue("padding-top"))+parseInt(t.getPropertyValue("padding-bottom"))),n=n-(parseInt(t.getPropertyValue("padding-right"))+parseInt(t.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth,{cols:Math.max(2,Math.floor(n/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(r/e._renderService.dimensions.actualCellHeight))}},t.FitAddon=n}],n={},o.m=r,o.c=n,o.d=function(e,t,r){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(o.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)o.d(r,n,function(e){return t[e]}.bind(null,n));return r},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=0);function o(e){var t;return(n[e]||(t=n[e]={i:e,l:!1,exports:{}},r[e].call(t.exports,t,t.exports,o),t.l=!0,t)).exports}var r,n})
|
((e,t)=>{"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()})(window,function(){return r=[function(e,t,r){function n(){}Object.defineProperty(t,"__esModule",{value:!0}),n.prototype.activate=function(e){this._terminal=e},n.prototype.dispose=function(){},n.prototype.fit=function(){var e,t=this.proposeDimensions();t&&this._terminal&&(e=this._terminal._core,this._terminal.rows===t.rows&&this._terminal.cols===t.cols||(e._renderService.clear(),this._terminal.resize(t.cols,t.rows)))},n.prototype.proposeDimensions=function(){var e,t,r,n;if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement)return e=this._terminal._core,n=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(n.getPropertyValue("height")),n=Math.max(0,parseInt(n.getPropertyValue("width"))),t=window.getComputedStyle(this._terminal.element),r=r-(parseInt(t.getPropertyValue("padding-top"))+parseInt(t.getPropertyValue("padding-bottom"))),n=n-(parseInt(t.getPropertyValue("padding-right"))+parseInt(t.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth,{cols:Math.max(2,Math.floor(n/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(r/e._renderService.dimensions.actualCellHeight))}},t.FitAddon=n}],n={},o.m=r,o.c=n,o.d=function(e,t,r){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(o.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)o.d(r,n,function(e){return t[e]}.bind(null,n));return r},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=0);function o(e){var t;return(n[e]||(t=n[e]={i:e,l:!1,exports:{}},r[e].call(t.exports,t,t.exports,o),t.l=!0,t)).exports}var r,n})
|
2
public/scripts/xterm-min.js
vendored
2
public/scripts/xterm-min.js
vendored
File diff suppressed because one or more lines are too long
2
public/scripts/zlib-adler32-min.js
vendored
2
public/scripts/zlib-adler32-min.js
vendored
|
@ -1 +1 @@
|
||||||
"undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-adler32.js"),function(){var b=65521,v=5552;ZLIB.adler32=function(r,e,o,t){if("string"==typeof e){var a,d=r,c=e,C=o,h=t,A=d>>>16&65535;if(d&=65535,1==h)d+=255&c.charCodeAt(C),b<=d&&(d-=b),b<=(A+=d)&&(A-=b);else{if(null===c)return 1;if(h<16){for(;h--;)A+=d+=255&c.charCodeAt(C++);return b<=d&&(d-=b),d|(A%=b)<<16}for(;v<=h;){for(h-=v,a=347;A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)),--a;);d%=b,A%=b}if(h){for(;16<=h;)h-=16,A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++));for(;h--;)A+=d+=255&c.charCodeAt(C++);d%=b,A%=b}}return d|A<<16}var f,n=r,i=e,u=o,l=t,s=n>>>16&65535;if(n&=65535,1==l)n+=i[u],b<=n&&(n-=b),b<=(s+=n)&&(s-=b);else{if(null===i)return 1;if(l<16){for(;l--;)s+=n+=i[u++];return b<=n&&(n-=b),n|(s%=b)<<16}for(;v<=l;){for(l-=v,f=347;s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[u++])+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]),--f;);n%=b,s%=b}if(l){for(;16<=l;)l-=16,s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[u++])+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]);for(;l--;)s+=n+=i[u++];n%=b,s%=b}}return n|s<<16},ZLIB.adler32_combine=function(r,e,o){var t,a;return o<0?4294967295:(a=(o%=b)*(t=65535&r),b<=(t+=(65535&e)+b-1)&&(t-=b),b<=t&&(t-=b),b<<1<=(a=a%b+((r>>16&65535)+(e>>16&65535)+b-o))&&(a-=b<<1),b<=a&&(a-=b),t|a<<16)}}()
|
"undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-adler32.js"),(()=>{var b=65521,v=5552;ZLIB.adler32=function(r,e,o,t){if("string"==typeof e){var a,d=r,c=e,C=o,h=t,A=d>>>16&65535;if(d&=65535,1==h)d+=255&c.charCodeAt(C),b<=d&&(d-=b),b<=(A+=d)&&(A-=b);else{if(null===c)return 1;if(h<16){for(;h--;)A+=d+=255&c.charCodeAt(C++);return b<=d&&(d-=b),d|(A%=b)<<16}for(;v<=h;){for(h-=v,a=347;A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)),--a;);d%=b,A%=b}if(h){for(;16<=h;)h-=16,A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++));for(;h--;)A+=d+=255&c.charCodeAt(C++);d%=b,A%=b}}return d|A<<16}var f,n=r,i=e,l=o,u=t,s=n>>>16&65535;if(n&=65535,1==u)n+=i[l],b<=n&&(n-=b),b<=(s+=n)&&(s-=b);else{if(null===i)return 1;if(u<16){for(;u--;)s+=n+=i[l++];return b<=n&&(n-=b),n|(s%=b)<<16}for(;v<=u;){for(u-=v,f=347;s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[l++])+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]),--f;);n%=b,s%=b}if(u){for(;16<=u;)u-=16,s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[l++])+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]);for(;u--;)s+=n+=i[l++];n%=b,s%=b}}return n|s<<16},ZLIB.adler32_combine=function(r,e,o){var t,a;return o<0?4294967295:(a=(o%=b)*(t=65535&r),b<=(t+=(65535&e)+b-1)&&(t-=b),b<=t&&(t-=b),b<<1<=(a=a%b+((r>>16&65535)+(e>>16&65535)+b-o))&&(a-=b<<1),b<=a&&(a-=b),t|a<<16)}})()
|
2
public/scripts/zlib-crc32-min.js
vendored
2
public/scripts/zlib-crc32-min.js
vendored
|
@ -1 +1 @@
|
||||||
"undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-crc32.js"),function(){var C=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117];ZLIB.crc32=function(r,e,o,n){if("string"==typeof e){var t=r,f=e,c=o,a=n;if(null==f)return 0;for(t^=4294967295;8<=a;)t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,a-=8;if(a)for(;t=C[255&(t^f.charCodeAt(c++))]^t>>>8,--a;);return 4294967295^t}var i=r,u=e,d=o,A=n;if(null==u)return 0;for(i^=4294967295;8<=A;)i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,A-=8;if(A)for(;i=C[255&(i^u[d++])]^i>>>8,--A;);return 4294967295^i};function a(r,e){for(var o=0,n=0;e;)1&e&&(n^=r[o]),e>>=1,o++;return n}function i(r,e){for(var o=0;o<32;o++)r[o]=a(e,e[o])}ZLIB.crc32_combine=function(r,e,o){var n,t,f,c;if(!(o<=0)){for(f=new Array(32),(c=new Array(32))[0]=3988292384,n=t=1;n<32;n++)c[n]=t,t<<=1;for(i(f,c),i(c,f);i(f,c),1&o&&(r=a(f,r)),0!=(o>>=1)&&(i(c,f),1&o&&(r=a(c,r)),0!=(o>>=1)););r^=e}return r}}()
|
"undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-crc32.js"),(()=>{var C=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117];function a(r,e){for(var o=0,n=0;e;)1&e&&(n^=r[o]),e>>=1,o++;return n}function i(r,e){for(var o=0;o<32;o++)r[o]=a(e,e[o])}ZLIB.crc32=function(r,e,o,n){if("string"==typeof e){var t=r,f=e,c=o,a=n;if(null==f)return 0;for(t^=4294967295;8<=a;)t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,a-=8;if(a)for(;t=C[255&(t^f.charCodeAt(c++))]^t>>>8,--a;);return 4294967295^t}var i=r,d=e,u=o,A=n;if(null==d)return 0;for(i^=4294967295;8<=A;)i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,A-=8;if(A)for(;i=C[255&(i^d[u++])]^i>>>8,--A;);return 4294967295^i},ZLIB.crc32_combine=function(r,e,o){var n,t,f,c;if(!(o<=0)){for(f=new Array(32),(c=new Array(32))[0]=3988292384,n=t=1;n<32;n++)c[n]=t,t<<=1;for(i(f,c),i(c,f);i(f,c),1&o&&(r=a(f,r)),0!=(o>>=1)&&(i(c,f),1&o&&(r=a(c,r)),0!=(o>>=1)););r^=e}return r}})()
|
2
public/scripts/zlib-inflate-min.js
vendored
2
public/scripts/zlib-inflate-min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -168,7 +168,6 @@ body {
|
||||||
height: 66px;
|
height: 66px;
|
||||||
color: #c8c8c8;
|
color: #c8c8c8;
|
||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
padding-top: 7px;
|
|
||||||
font-size: 46px;
|
font-size: 46px;
|
||||||
font-family: Arial,Helvetica,sans-serif;
|
font-family: Arial,Helvetica,sans-serif;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -180,7 +179,7 @@ body {
|
||||||
height: 66px;
|
height: 66px;
|
||||||
color: #c8c8c8;
|
color: #c8c8c8;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-top: 14px;
|
padding-top: 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: Arial,Helvetica,sans-serif;
|
font-family: Arial,Helvetica,sans-serif;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -248,7 +247,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.nonenglish .topbar_td {
|
.nonenglish .topbar_td {
|
||||||
width: 10px;
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-left:16px;
|
padding-left:16px;
|
||||||
|
@ -268,6 +266,16 @@ body {
|
||||||
right: 3px;
|
right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textnewui {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-top: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.LogoffLinkColor {
|
.LogoffLinkColor {
|
||||||
color:white;
|
color:white;
|
||||||
}
|
}
|
||||||
|
@ -295,8 +303,8 @@ body {
|
||||||
#MainSubMenuSpan, #MeshSubMenuSpan, #EventsSubMenuSpan, #UserSubMenuSpan, #UsersSubMenuSpan, #ServerSubMenuSpan, #MainMenuSpan, #MainSubMenu, #MeshSubMenu, #UserSubMenu, #ServerSubMenu, #UserDummyMenu, #PluginSubMenu {
|
#MainSubMenuSpan, #MeshSubMenuSpan, #EventsSubMenuSpan, #UserSubMenuSpan, #UsersSubMenuSpan, #ServerSubMenuSpan, #MainMenuSpan, #MainSubMenu, #MeshSubMenu, #UserSubMenu, #ServerSubMenu, #UserDummyMenu, #PluginSubMenu {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
color: var(--sub-menu-color);
|
color: var(--bs-secondary-color);
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu_stack #UserDummyMenu {
|
.menu_stack #UserDummyMenu {
|
||||||
|
@ -636,7 +644,6 @@ body {
|
||||||
width: 230px;
|
width: 230px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
background-color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#idx_dlgButtonBar {
|
#idx_dlgButtonBar {
|
||||||
|
@ -731,25 +738,21 @@ body {
|
||||||
|
|
||||||
#devListToolbarSpan {
|
#devListToolbarSpan {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.night #devListToolbarSpan {
|
#SearchInput, #KvmSearchInput {
|
||||||
color: black;
|
|
||||||
background-color: #d3d9d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
#SearchInput {
|
|
||||||
width: 120px;
|
width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#SearchInput.search {
|
#SearchInput.search, #KvmSearchInput.search {
|
||||||
background-color: #FDFFBE;
|
background-color: #FDFFBE;
|
||||||
}
|
}
|
||||||
|
|
||||||
.night #SearchInput.search {
|
.night #SearchInput.search, .night #KvmSearchInput.search {
|
||||||
background-color: grey;
|
background-color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1079,11 +1082,6 @@ NoMeshesPanel img {
|
||||||
|
|
||||||
.pTable {
|
.pTable {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 24px;
|
|
||||||
background: var(--sub-menu-bg) !important;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-spacing: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#p50groups {
|
#p50groups {
|
||||||
|
@ -1127,24 +1125,15 @@ NoMeshesPanel img {
|
||||||
|
|
||||||
#p5filehead {
|
#p5filehead {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
text-align: left;
|
padding: 4px 0px;
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.night #p5filehead {
|
|
||||||
background: var(--sub-menu-bg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#p5filesubhead {
|
#p5filesubhead {
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.night #p5filesubhead {
|
|
||||||
background-color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
#p5rightOfButtons {
|
#p5rightOfButtons {
|
||||||
float: right;
|
float: right;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
|
@ -1265,7 +1254,7 @@ NoMeshesPanel img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.pwsBlack {
|
.pwsBlack {
|
||||||
background-color: black;
|
background-color: var(--bs-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pwsBlue {
|
.pwsBlue {
|
||||||
|
@ -1745,14 +1734,14 @@ nav .lbbuttonsel2 {
|
||||||
.style3 {
|
.style3 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
color: white;
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.style3x {
|
.style3x {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--sub-menu-color);
|
color: var(--bs-secondary-color);
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1833,34 +1822,21 @@ nav .lbbuttonsel2 {
|
||||||
.style14 {
|
.style14 {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
/* background-color: #D3D9D6; */
|
background: var(--bs-secondary-bg);
|
||||||
background: var(--sub-menu-bg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.night .style14 {
|
.night .style14 {
|
||||||
background-color: #333;
|
|
||||||
color: #CCC;
|
color: #CCC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auto-style1 {
|
.auto-style1 {
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pTable .auto-style1 {
|
|
||||||
height: 100%;
|
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.night .auto-style1 {
|
.night .auto-style1 {
|
||||||
background: var(--sub-menu-bg);
|
|
||||||
color: #CCC;
|
color: #CCC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.night #pTable {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon2 {
|
.icon2 {
|
||||||
float: left;
|
float: left;
|
||||||
margin: 7px;
|
margin: 7px;
|
||||||
|
@ -2103,7 +2079,11 @@ nav .lbbuttonsel2 {
|
||||||
#d2devNotes,
|
#d2devNotes,
|
||||||
#d2devEvent,
|
#d2devEvent,
|
||||||
#d2runcmd,
|
#d2runcmd,
|
||||||
#d2devMessage {
|
#d2devMessage,
|
||||||
|
#d2smsText,
|
||||||
|
#d2emailSubject,
|
||||||
|
#d2emailText,
|
||||||
|
#broadcastMessage {
|
||||||
background-color: #FFF9D3;
|
background-color: #FFF9D3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2113,7 +2093,11 @@ nav .lbbuttonsel2 {
|
||||||
.night #d2devNotes,
|
.night #d2devNotes,
|
||||||
.night #d2devEvent,
|
.night #d2devEvent,
|
||||||
.night #d2runcmd,
|
.night #d2runcmd,
|
||||||
.night #d2devMessage {
|
.night #d2devMessage,
|
||||||
|
.night #d2smsText,
|
||||||
|
.night #d2emailSubject,
|
||||||
|
.night #d2emailText,
|
||||||
|
.night #broadcastMessage {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2463,7 +2447,6 @@ nav .lbbuttonsel2 {
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
background: var(--sub-menu-bg);
|
background: var(--sub-menu-bg);
|
||||||
height: 22px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.night .areaFoot {
|
.night .areaFoot {
|
||||||
|
@ -2691,29 +2674,14 @@ nav .lbbuttonsel2 {
|
||||||
-ms-grid-row: 4;
|
-ms-grid-row: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#DeskRunButton, #DeskChatButton, #DeskNotifyButton, #DeskOpenWebButton, #DeskBackgroundButton, #DeskSaveImageButton, #DeskRecordButton, #DeskClipboardInButton, #DeskClipboardOutButton, #DeskRefreshButton, #DeskLockButton, #DeskInputLockedButton, #DeskInputUnLockedButton, #DeskGuestShareButton, #DeskMonitorSelectionSpan {
|
|
||||||
float: right;
|
|
||||||
margin-top: 1px;
|
|
||||||
margin-right: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#DeskClip, #DeskControlSpan, #specialkeylist {
|
|
||||||
padding-left: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.serverStateTableCell {
|
.serverStateTableCell {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
}
|
color: var(--bs-secondary-color);
|
||||||
|
|
||||||
.night .serverStateTableCell {
|
|
||||||
background-color: #333;
|
|
||||||
color: #CCC;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.userTableHeader {
|
.userTableHeader {
|
||||||
|
@ -2722,10 +2690,6 @@ nav .lbbuttonsel2 {
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#deskkeys {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#p12BackButton {
|
#p12BackButton {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -2767,23 +2731,16 @@ nav .lbbuttonsel2 {
|
||||||
|
|
||||||
#p13toolbar .areaHead2 {
|
#p13toolbar .areaHead2 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
text-align: left;
|
padding: 4px 0px;
|
||||||
padding: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.night #p13toolbar .areaHead2 {
|
|
||||||
background: var(--sub-menu-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#p13toolbar .areaHead3 {
|
#p13toolbar .areaHead3 {
|
||||||
background: var(--sub-menu-bg);
|
background: var(--bs-secondary-bg);
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.night #p13toolbar .areaHead3 {
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
#p13filetable {
|
#p13filetable {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -2833,11 +2790,6 @@ nav .lbbuttonsel2 {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#p15statetext {
|
|
||||||
padding: 4px;
|
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#p15agentConsole {
|
#p15agentConsole {
|
||||||
background: black;
|
background: black;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -2854,11 +2806,6 @@ nav .lbbuttonsel2 {
|
||||||
max-height: calc(100vh - 305px);
|
max-height: calc(100vh - 305px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#p15coreName {
|
|
||||||
padding: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#p15agentConsoleText {
|
#p15agentConsoleText {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -2940,7 +2887,7 @@ nav .lbbuttonsel2 {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewSelector3 {
|
.viewSelector3, .uiSelector7 {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
background: url(../images/views.png) -56px 0px;
|
background: url(../images/views.png) -56px 0px;
|
||||||
|
@ -3040,6 +2987,13 @@ nav .lbbuttonsel2 {
|
||||||
background-color: #AAA;
|
background-color: #AAA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uiSelector_end {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
float: left;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.uiSelectorSel {
|
.uiSelectorSel {
|
||||||
background-color: #BBB;
|
background-color: #BBB;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
@ -3289,7 +3243,6 @@ nav .lbbuttonsel2 {
|
||||||
.dtab {
|
.dtab {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dtab button {
|
.dtab button {
|
||||||
|
|
|
@ -263,6 +263,16 @@ body {
|
||||||
right: 3px;
|
right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textnewui {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-top: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.LogoffLinkColor {
|
.LogoffLinkColor {
|
||||||
color:white;
|
color:white;
|
||||||
}
|
}
|
||||||
|
@ -2896,7 +2906,7 @@ a {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewSelector3 {
|
.viewSelector3, .uiSelector7 {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
background: url(../images/views.png) -56px 0px;
|
background: url(../images/views.png) -56px 0px;
|
||||||
|
@ -2996,6 +3006,13 @@ a {
|
||||||
background-color: #AAA;
|
background-color: #AAA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uiSelector_end {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
float: left;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.uiSelectorSel {
|
.uiSelectorSel {
|
||||||
background-color: #BBB;
|
background-color: #BBB;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
12
public/styles/themes/litera/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/litera/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/lumen/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/lumen/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/lux/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/lux/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/materia/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/materia/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/minty/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/minty/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/morph/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/morph/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/pulse/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/pulse/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/sandstone/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/sandstone/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/simplex/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/simplex/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/sketchy/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/sketchy/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/solar/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/solar/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/spacelab/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/spacelab/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/united/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/united/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/vapor/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/vapor/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/yeti/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/yeti/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/styles/themes/zephyr/bootstrap-min.css
vendored
Normal file
12
public/styles/themes/zephyr/bootstrap-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -186,6 +186,7 @@
|
||||||
"domains": {
|
"domains": {
|
||||||
"": {
|
"": {
|
||||||
"_siteStyle": 2,
|
"_siteStyle": 2,
|
||||||
|
"_showModernUIToggle": true,
|
||||||
"title": "MyServer",
|
"title": "MyServer",
|
||||||
"title2": "Servername",
|
"title2": "Servername",
|
||||||
"_titlePicture": "title-sample.png",
|
"_titlePicture": "title-sample.png",
|
||||||
|
@ -322,7 +323,8 @@
|
||||||
"skip2factor": "127.0.0.1,192.168.2.0/24",
|
"skip2factor": "127.0.0.1,192.168.2.0/24",
|
||||||
"oldPasswordBan": 5,
|
"oldPasswordBan": 5,
|
||||||
"banCommonPasswords": false,
|
"banCommonPasswords": false,
|
||||||
"twoFactorTimeout": 300
|
"twoFactorTimeout": 300,
|
||||||
|
"duo2factor": true
|
||||||
},
|
},
|
||||||
"_twoFactorCookieDurationDays": 30,
|
"_twoFactorCookieDurationDays": 30,
|
||||||
"_agentInviteCodes": true,
|
"_agentInviteCodes": true,
|
||||||
|
@ -341,6 +343,7 @@
|
||||||
"files": "{0} requesting remote files access. Grant access?",
|
"files": "{0} requesting remote files access. Grant access?",
|
||||||
"consentTimeout": 30,
|
"consentTimeout": 30,
|
||||||
"autoAcceptOnTimeout": false,
|
"autoAcceptOnTimeout": false,
|
||||||
|
"autoAcceptIfNoUser": false,
|
||||||
"oldStyle": true
|
"oldStyle": true
|
||||||
},
|
},
|
||||||
"_notificationMessages": {
|
"_notificationMessages": {
|
||||||
|
@ -383,6 +386,8 @@
|
||||||
"_agentBlockedIP": "127.0.0.1,::1",
|
"_agentBlockedIP": "127.0.0.1,::1",
|
||||||
"___userSessionIdleTimeout__": "Number of user idle minutes before auto-disconnect",
|
"___userSessionIdleTimeout__": "Number of user idle minutes before auto-disconnect",
|
||||||
"_userSessionIdleTimeout": 30,
|
"_userSessionIdleTimeout": 30,
|
||||||
|
"___logoutOnIdleSessionTimeout": "Determines whether MeshCentral should logout after the session idle timeout elapsed or should just disconnect remote desktop, terminal and files.",
|
||||||
|
"_logoutOnIdleSessionTimeout": false,
|
||||||
"userConsentFlags": {
|
"userConsentFlags": {
|
||||||
"desktopnotify": true,
|
"desktopnotify": true,
|
||||||
"terminalnotify": true,
|
"terminalnotify": true,
|
||||||
|
@ -454,6 +459,11 @@
|
||||||
"_redirects": {
|
"_redirects": {
|
||||||
"meshcommander": "https://www.meshcommander.com/"
|
"meshcommander": "https://www.meshcommander.com/"
|
||||||
},
|
},
|
||||||
|
"_duo2factor": {
|
||||||
|
"integrationkey": "mykey",
|
||||||
|
"secretkey": "mysecret",
|
||||||
|
"apihostname": "api-xxxxxxxxxxx.duosecurity.com"
|
||||||
|
},
|
||||||
"_yubikey": {
|
"_yubikey": {
|
||||||
"id": "0000",
|
"id": "0000",
|
||||||
"secret": "xxxxxxxxxxxxxxxxxxxxx",
|
"secret": "xxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
|
|
@ -22,6 +22,7 @@ var meshCentralSourceFiles = [
|
||||||
"../views/agentinvite.handlebars",
|
"../views/agentinvite.handlebars",
|
||||||
"../views/invite.handlebars",
|
"../views/invite.handlebars",
|
||||||
"../views/default.handlebars",
|
"../views/default.handlebars",
|
||||||
|
"../views/default3.handlebars",
|
||||||
"../views/default-mobile.handlebars",
|
"../views/default-mobile.handlebars",
|
||||||
"../views/download.handlebars",
|
"../views/download.handlebars",
|
||||||
"../views/download2.handlebars",
|
"../views/download2.handlebars",
|
||||||
|
@ -63,6 +64,7 @@ var minifyMeshCentralSourceFiles = [
|
||||||
"../views/agentinvite.handlebars",
|
"../views/agentinvite.handlebars",
|
||||||
"../views/invite.handlebars",
|
"../views/invite.handlebars",
|
||||||
"../views/default.handlebars",
|
"../views/default.handlebars",
|
||||||
|
"../views/default3.handlebars",
|
||||||
"../views/default-mobile.handlebars",
|
"../views/default-mobile.handlebars",
|
||||||
"../views/download.handlebars",
|
"../views/download.handlebars",
|
||||||
"../views/download2.handlebars",
|
"../views/download2.handlebars",
|
||||||
|
@ -451,7 +453,8 @@ function startEx(argv) {
|
||||||
removeScriptTypeAttributes: true,
|
removeScriptTypeAttributes: true,
|
||||||
removeTagWhitespace: true,
|
removeTagWhitespace: true,
|
||||||
preserveLineBreaks: false,
|
preserveLineBreaks: false,
|
||||||
useShortDoctype: true
|
useShortDoctype: true,
|
||||||
|
log: function(a) { if (typeof a !== 'string') { console.log(a); } } // Log errors from UglifyJS to console output
|
||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.log(ex);
|
console.log(ex);
|
||||||
|
@ -782,10 +785,12 @@ function getStringsHtml(name, node) {
|
||||||
|
|
||||||
// Check if the "value" attribute exists and needs to be translated
|
// Check if the "value" attribute exists and needs to be translated
|
||||||
var subnodeignore = false;
|
var subnodeignore = false;
|
||||||
|
var subnodevalueignore = false;
|
||||||
if ((subnode.attributes != null) && (subnode.attributes.length > 0)) {
|
if ((subnode.attributes != null) && (subnode.attributes.length > 0)) {
|
||||||
var subnodevalue = null, subnodeplaceholder = null, subnodetitle = null;
|
var subnodevalue = null, subnodeplaceholder = null, subnodetitle = null;
|
||||||
for (var j in subnode.attributes) {
|
for (var j in subnode.attributes) {
|
||||||
if ((subnode.attributes[j].name == 'notrans') && (subnode.attributes[j].value == '1')) { subnodeignore = true; }
|
if ((subnode.attributes[j].name == 'notrans') && (subnode.attributes[j].value == '1')) { subnodeignore = true; }
|
||||||
|
if ((subnode.attributes[j].name == 'notransval') && (subnode.attributes[j].value == '1')) { subnodevalueignore = true; }
|
||||||
if ((subnode.attributes[j].name == 'type') && (subnode.attributes[j].value == 'hidden')) { subnodeignore = true; }
|
if ((subnode.attributes[j].name == 'type') && (subnode.attributes[j].value == 'hidden')) { subnodeignore = true; }
|
||||||
if (subnode.attributes[j].name == 'value') { subnodevalue = subnode.attributes[j].value; }
|
if (subnode.attributes[j].name == 'value') { subnodevalue = subnode.attributes[j].value; }
|
||||||
if (subnode.attributes[j].name == 'placeholder') { subnodeplaceholder = subnode.attributes[j].value; }
|
if (subnode.attributes[j].name == 'placeholder') { subnodeplaceholder = subnode.attributes[j].value; }
|
||||||
|
@ -794,7 +799,7 @@ function getStringsHtml(name, node) {
|
||||||
if ((subnodevalue != null) && isNumber(subnodevalue) == true) { subnodevalue = null; }
|
if ((subnodevalue != null) && isNumber(subnodevalue) == true) { subnodevalue = null; }
|
||||||
if ((subnodeplaceholder != null) && isNumber(subnodeplaceholder) == true) { subnodeplaceholder = null; }
|
if ((subnodeplaceholder != null) && isNumber(subnodeplaceholder) == true) { subnodeplaceholder = null; }
|
||||||
if ((subnodetitle != null) && isNumber(subnodetitle) == true) { subnodetitle = null; }
|
if ((subnodetitle != null) && isNumber(subnodetitle) == true) { subnodetitle = null; }
|
||||||
if ((subnodeignore == false) && (subnodevalue != null)) {
|
if ((subnodeignore == false) && (subnodevalueignore == false) && (subnodevalue != null)) {
|
||||||
// Add a new string to the list (value)
|
// Add a new string to the list (value)
|
||||||
if (sourceStrings[subnodevalue] == null) { sourceStrings[subnodevalue] = { en: subnodevalue, xloc: [name] }; } else { if (sourceStrings[subnodevalue].xloc == null) { sourceStrings[subnodevalue].xloc = []; } sourceStrings[subnodevalue].xloc.push(name); }
|
if (sourceStrings[subnodevalue] == null) { sourceStrings[subnodevalue] = { en: subnodevalue, xloc: [name] }; } else { if (sourceStrings[subnodevalue].xloc == null) { sourceStrings[subnodevalue].xloc = []; } sourceStrings[subnodevalue].xloc.push(name); }
|
||||||
}
|
}
|
||||||
|
|
14682
translate/translate.json
14682
translate/translate.json
File diff suppressed because it is too large
Load diff
|
@ -1304,6 +1304,7 @@
|
||||||
delete urlargs.viewmode;
|
delete urlargs.viewmode;
|
||||||
delete urlargs.gotonode;
|
delete urlargs.gotonode;
|
||||||
delete urlargs.gotodevicename;
|
delete urlargs.gotodevicename;
|
||||||
|
delete urlargs.gotodeviceip;
|
||||||
delete urlargs.gotomesh;
|
delete urlargs.gotomesh;
|
||||||
delete urlargs.panel;
|
delete urlargs.panel;
|
||||||
|
|
||||||
|
@ -1324,7 +1325,7 @@
|
||||||
var logoutControls = JSON.parse(decodeURIComponent('{{{logoutControls}}}'));
|
var logoutControls = JSON.parse(decodeURIComponent('{{{logoutControls}}}'));
|
||||||
var authCookieRenewTimer = null;
|
var authCookieRenewTimer = null;
|
||||||
var webRelayPort = parseInt('{{{webRelayPort}}}');
|
var webRelayPort = parseInt('{{{webRelayPort}}}');
|
||||||
var hidePowerTimeline = {{{hidePowerTimeline}}};
|
var hidePowerTimeline = '{{{hidePowerTimeline}}}';
|
||||||
var webRelayDns = '{{{webRelayDns}}}';
|
var webRelayDns = '{{{webRelayDns}}}';
|
||||||
var meshserver = null;
|
var meshserver = null;
|
||||||
var xdr = null;
|
var xdr = null;
|
||||||
|
@ -1521,7 +1522,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSessionActivity() { sessionActivity = Date.now(); }
|
function setSessionActivity() { sessionActivity = Date.now(); }
|
||||||
function checkIdleSessionTimeout() { var delta = (Date.now() - sessionActivity); if (delta > serverinfo.timeout) { window.location.href = 'logout'; } }
|
function checkIdleSessionTimeout() {
|
||||||
|
var delta = (Date.now() - sessionActivity);
|
||||||
|
if (delta > serverinfo.timeout && serverinfo.logoutOnIdleSessionTimeout) {
|
||||||
|
window.location.href = 'logout';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onMessage(server, message) {
|
function onMessage(server, message) {
|
||||||
switch (message.action) {
|
switch (message.action) {
|
||||||
|
@ -1743,7 +1749,7 @@
|
||||||
var secret = message.secret;
|
var secret = message.secret;
|
||||||
if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
|
if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
|
||||||
else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
|
else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
|
||||||
QH('d2optinfo', format("Install <a href=\"https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2\" rel=\"noreferrer noopener\" target=_blank>Google Authenticator</a> or a compatible application, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank> this link</a> or enter the secret below. Then, enter the current 6 digit token to activate 2-Step login.", message.url) + '<br /><br /><div style=width:100%;text-align:center><tt id=d2optsecret secret="' + message.secret + '" style=font-size:15px>' + secret + '</tt><br /><br />Token: <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress=\"return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)\" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></div>');
|
QH('d2optinfo', format("Install" + ' <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" rel="noreferrer noopener" target=_blank>' + "Google Authenticator" + '</a> ' + "or a compatible application, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank> this link</a> or enter the secret below. Then, enter the current 6 digit token to activate 2-Step login.", message.url) + '<br /><br /><div style=width:100%;text-align:center><tt id=d2optsecret secret="' + message.secret + '" style=font-size:15px>' + secret + '</tt><br /><br />Token: <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress=\"return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)\" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></div>');
|
||||||
QV('idx_dlgOkButton', true);
|
QV('idx_dlgOkButton', true);
|
||||||
QE('idx_dlgOkButton', false);
|
QE('idx_dlgOkButton', false);
|
||||||
Q('d2otpauthinput').focus();
|
Q('d2otpauthinput').focus();
|
||||||
|
@ -1753,12 +1759,12 @@
|
||||||
}
|
}
|
||||||
case 'otpauth-setup': {
|
case 'otpauth-setup': {
|
||||||
if (xxdialogMode) return;
|
if (xxdialogMode) return;
|
||||||
setDialogMode(2, "Authenticator App", 1, null, message.success ? "<b style=color:green>2-step login activation successful</b>. You will now need a valid token to login again." : "<b style=color:red>2-step login activation failed</b>. Clear the secret from the application and try again. You only have a few minutes to enter the proper code.");
|
setDialogMode(2, "Authenticator App", 1, null, message.success ? ('<b style=color:green>' + "Authenticator app activation successful." + '</b> ' + "You will now need a valid token to login again.") : ('<b style=color:red>' + "2-step login activation failed." + '</b> ' + "Clear the secret from the application and try again. You only have a few minutes to enter the proper code."));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'otpauth-clear': {
|
case 'otpauth-clear': {
|
||||||
if (xxdialogMode) return;
|
if (xxdialogMode) return;
|
||||||
setDialogMode(2, "Authenticator App", 1, null, message.success ? "<b style=color:green>2-step login activation removed</b>. You can reactivate this feature at any time." : "<b style=color:red>2-step login activation removal failed</b>. Try again.");
|
setDialogMode(2, "Authenticator App", 1, null, message.success ? ('<b>' + "Authenticator application removed." + '</b> ' + "You can reactivate this feature at any time.") : ('<b style=color:red>' + "2-step login activation removal failed." + '</b> ' + "Try again."));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'otpauth-getpasswords': {
|
case 'otpauth-getpasswords': {
|
||||||
|
@ -2025,6 +2031,8 @@
|
||||||
// Change the node
|
// Change the node
|
||||||
node.name = message.event.node.name;
|
node.name = message.event.node.name;
|
||||||
node.rname = message.event.node.rname;
|
node.rname = message.event.node.rname;
|
||||||
|
node.lusers = message.event.node.lusers;
|
||||||
|
node.users = message.event.node.users;
|
||||||
node.host = message.event.node.host;
|
node.host = message.event.node.host;
|
||||||
node.desc = message.event.node.desc;
|
node.desc = message.event.node.desc;
|
||||||
node.publicip = message.event.node.publicip;
|
node.publicip = message.event.node.publicip;
|
||||||
|
@ -2229,6 +2237,10 @@
|
||||||
var foundNode = null;
|
var foundNode = null;
|
||||||
if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
|
if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
|
||||||
if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
|
if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
|
||||||
|
} else if (args.gotodeviceip != null) {
|
||||||
|
var foundNode = null;
|
||||||
|
if (nodes != null) { for (var i in nodes) { if (nodes[i].ip == args.gotodeviceip) { foundNode = nodes[i]._id; } } }
|
||||||
|
if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
|
||||||
} else if (args.gotomesh != null) {
|
} else if (args.gotomesh != null) {
|
||||||
if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
|
if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
|
||||||
gotoMesh('mesh/' + domain + '/' + args.gotomesh);
|
gotoMesh('mesh/' + domain + '/' + args.gotomesh);
|
||||||
|
@ -4255,7 +4267,7 @@
|
||||||
|
|
||||||
// Draw device power bars. The bars are 766px wide.
|
// Draw device power bars. The bars are 766px wide.
|
||||||
function drawDeviceTimeline() {
|
function drawDeviceTimeline() {
|
||||||
if (currentNode.mtype == 3 || hidePowerTimeline) { QH('p10html2', '<br />'); return; }
|
if (currentNode.mtype == 3 || hidePowerTimeline === 'true') { QH('p10html2', '<br />'); return; }
|
||||||
var timeline = null, now = Date.now();
|
var timeline = null, now = Date.now();
|
||||||
if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
|
if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
|
||||||
|
|
||||||
|
@ -5895,7 +5907,7 @@
|
||||||
downloadFile = { path: decodeURIComponent(x), file: decodeURIComponent(y), size: z, tsize: 0, data: '', state: 0, id: Math.random() }
|
downloadFile = { path: decodeURIComponent(x), file: decodeURIComponent(y), size: z, tsize: 0, data: '', state: 0, id: Math.random() }
|
||||||
//console.log('p13downloadFileCancel', downloadFile);
|
//console.log('p13downloadFileCancel', downloadFile);
|
||||||
files.sendText({ action: 'download', sub: 'start', id: downloadFile.id, path: downloadFile.path });
|
files.sendText({ action: 'download', sub: 'start', id: downloadFile.id, path: downloadFile.path });
|
||||||
setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + HtmlEscape(downloadFile.file) + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
|
setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + EscapeHtml(downloadFile.file) + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the html page to cancel the download
|
// Called by the html page to cancel the download
|
||||||
|
@ -6043,7 +6055,7 @@
|
||||||
if (uploadFile.xfiles.length > uploadFile.xfilePtr) {
|
if (uploadFile.xfiles.length > uploadFile.xfilePtr) {
|
||||||
uploadFile.xptr = 0;
|
uploadFile.xptr = 0;
|
||||||
var file = uploadFile.xfiles[uploadFile.xfilePtr];
|
var file = uploadFile.xfiles[uploadFile.xfilePtr];
|
||||||
QH('p13dfileName', HtmlEscape(file.name));
|
QH('p13dfileName', EscapeHtml(file.name));
|
||||||
Q('d2progressBar').max = file.size;
|
Q('d2progressBar').max = file.size;
|
||||||
Q('d2progressBar').value = 0;
|
Q('d2progressBar').value = 0;
|
||||||
if (file.xdata == null) {
|
if (file.xdata == null) {
|
||||||
|
@ -6180,6 +6192,7 @@
|
||||||
// Operating System
|
// Operating System
|
||||||
var x = '';
|
var x = '';
|
||||||
if (node.rname) { x += addDetailItem("Name", EscapeHtml(node.rname), s); }
|
if (node.rname) { x += addDetailItem("Name", EscapeHtml(node.rname), s); }
|
||||||
|
if (hardware.windows && hardware.windows.osinfo && hardware.windows.osinfo.Description) { x += addDetailItem("Description", EscapeHtml(hardware.windows.osinfo.Description), s); }
|
||||||
if (node.osdesc) { x += addDetailItem("Version", EscapeHtml(node.osdesc), s); }
|
if (node.osdesc) { x += addDetailItem("Version", EscapeHtml(node.osdesc), s); }
|
||||||
if (hardware.windows && hardware.windows.osinfo) {
|
if (hardware.windows && hardware.windows.osinfo) {
|
||||||
var m = hardware.windows.osinfo;
|
var m = hardware.windows.osinfo;
|
||||||
|
@ -6238,11 +6251,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defender for Windows Server
|
// Defender for Windows Server
|
||||||
if(node.defender && !node.wsc) {
|
if(node.defender) {
|
||||||
var y = [];
|
var y = [];
|
||||||
if (node.defender.RealTimeProtection != null) { if (node.defender.RealTimeProtection == true) { y.push("RealTimeProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("RealTimeProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
if (node.defender.RealTimeProtection != null) { if (node.defender.RealTimeProtection == true) { y.push("RealTimeProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("RealTimeProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
||||||
if (node.defender.TamperProtected != null) { if (node.defender.TamperProtected == true) { y.push("TamperProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("TamperProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
if (node.defender.TamperProtected != null) { if (node.defender.TamperProtected == true) { y.push("TamperProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("TamperProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
||||||
x += addDetailItem("Windows Defender", y.join(', '));
|
if (y.length > 0) x += addDetailItem("Windows Defender", y.join(', '));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Antivirus
|
// Antivirus
|
||||||
|
@ -6261,7 +6274,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Active Users
|
// Active Users
|
||||||
if (node.users && (node.users.length > 0)) { x += addDetailItem(((node.users.length > 1)?"Active Users":"Active User"), EscapeHtml(node.users.join(', '))); }
|
if (node.users && node.users.length > 0) {
|
||||||
|
var u = node.users.map(function(user) {
|
||||||
|
return addKeyLinkConditional(EscapeHtml(user), "Locked", (node.lusers && node.lusers.indexOf(user) >= 0));
|
||||||
|
}).join(', ');
|
||||||
|
x += addDetailItem((node.users.length > 1 ? "Active Users" : "Active User"), u);
|
||||||
|
}
|
||||||
|
|
||||||
if (x != '') { sections.push({ name: "Operating System", html: x, img: 'software' }); }
|
if (x != '') { sections.push({ name: "Operating System", html: x, img: 'software' }); }
|
||||||
|
|
||||||
|
@ -6350,7 +6368,7 @@
|
||||||
}
|
}
|
||||||
if (hardware.network && hardware.network.dns) {
|
if (hardware.network && hardware.network.dns) {
|
||||||
x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
|
x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
|
||||||
x += addDetailItem("<b>DNS Servers</b>", hardware.network.dns.join(", "));
|
x += addDetailItem('<b>' + "DNS Servers" + '</b>', hardware.network.dns.join(", "));
|
||||||
x += '</div></td></tr>';
|
x += '</div></td></tr>';
|
||||||
}
|
}
|
||||||
x += '</table>';
|
x += '</table>';
|
||||||
|
@ -7573,7 +7591,7 @@
|
||||||
function addLink(x, f) { return '<a style=cursor:pointer;text-decoration:none onclick=\'' + f + '\'>♦ ' + x + '</a>'; }
|
function addLink(x, f) { return '<a style=cursor:pointer;text-decoration:none onclick=\'' + f + '\'>♦ ' + x + '</a>'; }
|
||||||
function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
|
function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
|
||||||
function addKeyLink(x, f) { return '<span tabindex=0 style=cursor:pointer;text-decoration:none onclick=' + f + ' onkeypress="if (event.key==\'Enter\') { ' + f + ' } ">' + x + ' <img class=hoverButton src=images/key16.png></span>'; }
|
function addKeyLink(x, f) { return '<span tabindex=0 style=cursor:pointer;text-decoration:none onclick=' + f + ' onkeypress="if (event.key==\'Enter\') { ' + f + ' } ">' + x + ' <img class=hoverButton src=images/key16.png></span>'; }
|
||||||
function addKeyLinkConditional(x, f, c) { if (c) return addKeyLink(x, f); return x; }
|
function addKeyLinkConditional(x, t, c) { if (c) return '<span title=\'' + t + '\'>' + x + ' <img class=hoverButton src=images/key16.png></span>'; return x }
|
||||||
function passwordcheck(p) { var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()]).{8,}/; return re.test(p); }
|
function passwordcheck(p) { var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()]).{8,}/; return re.test(p); }
|
||||||
function getFileSizeStr(size) { if (typeof size != 'number') { size = 0; } if (size == 1) return "1 byte"; return format('{0} bytes', size); }
|
function getFileSizeStr(size) { if (typeof size != 'number') { size = 0; } if (size == 1) return "1 byte"; return format('{0} bytes', size); }
|
||||||
function joinPaths() { var x = []; for (var i in arguments) { var w = arguments[i]; if ((w != null) && (w != '')) { while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } x.push(w); } } return x.join('/'); }
|
function joinPaths() { var x = []; for (var i in arguments) { var w = arguments[i]; if ((w != null) && (w != '')) { while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } x.push(w); } } return x.join('/'); }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
|
<html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
@ -164,6 +164,9 @@
|
||||||
<div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="display: none;" title="Click to view current notifications">0</div>
|
<div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="display: none;" title="Click to view current notifications">0</div>
|
||||||
</div>
|
</div>
|
||||||
<p id="logoutControl"><span id=logoutControlSpan class="logoncontrolspan"></span><span id=idleTimeoutNotify style="color:yellow"></span></p>
|
<p id="logoutControl"><span id=logoutControlSpan class="logoncontrolspan"></span><span id=idleTimeoutNotify style="color:yellow"></span></p>
|
||||||
|
<div class=textnewui id=textnewui onmouseup=toggleBootstrapUIMode() onkeypress="if (event.key=='Enter') { toggleBootstrapUIMode(); }">
|
||||||
|
<b>Try the new MeshCentral UI</b>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="page_leftbar">
|
<div id="page_leftbar">
|
||||||
<div style="height:16px"></div>
|
<div style="height:16px"></div>
|
||||||
|
@ -199,11 +202,13 @@
|
||||||
<div tabindex=0 id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(1)"><div class="uiSelector1"></div></div>
|
<div tabindex=0 id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(1)"><div class="uiSelector1"></div></div>
|
||||||
<div tabindex=0 id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(2)"><div class="uiSelector2"></div></div>
|
<div tabindex=0 id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(2)"><div class="uiSelector2"></div></div>
|
||||||
<div tabindex=0 id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(3)"><div class="uiSelector3"></div></div>
|
<div tabindex=0 id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(3)"><div class="uiSelector3"></div></div>
|
||||||
|
<div tabindex=0 id=uiViewButton7 class=uiSelector onclick=toggleBootstrapUIMode() title="Toggle Modern UI" onkeypress="if (event.key == 'Enter') toggleBootstrapUIMode()"><div class="uiSelector7"></div></div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div tabindex=0 id=uiViewButton6 class=uiSelector onclick="showNotes(false)" title="Personal Notes" onkeypress="if (event.key == 'Enter') showNotes(false)"><div class="uiSelector6"></div></div>
|
<div tabindex=0 id=uiViewButton6 class=uiSelector onclick="showNotes(false)" title="Personal Notes" onkeypress="if (event.key == 'Enter') showNotes(false)"><div class="uiSelector6"></div></div>
|
||||||
<div tabindex=0 id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode" onkeypress="if (event.key == 'Enter') toggleNightMode()"><div class="uiSelector4"></div></div>
|
<div tabindex=0 id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode" onkeypress="if (event.key == 'Enter') toggleNightMode()"><div class="uiSelector4"></div></div>
|
||||||
<div tabindex=0 id=uiViewButton5 class=uiSelector onclick=toggleFooterBarMode() title="Toggle footer bar" onkeypress="if (event.key == 'Enter') toggleFooterBarMode()"><div class="uiSelector5"></div></div>
|
<div tabindex=0 id=uiViewButton5 class=uiSelector onclick=toggleFooterBarMode() title="Toggle footer bar" onkeypress="if (event.key == 'Enter') toggleFooterBarMode()"><div class="uiSelector5"></div></div>
|
||||||
|
<div class=uiSelector_end> </div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -430,6 +435,7 @@
|
||||||
<div id="managePhoneNumber1"><div class="p2AccountActions"><span id="authPhoneNumberCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_managePhone()">Manage phone number</a><br /></span></div>
|
<div id="managePhoneNumber1"><div class="p2AccountActions"><span id="authPhoneNumberCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_managePhone()">Manage phone number</a><br /></span></div>
|
||||||
<div id="manageEmail2FA"><div class="p2AccountActions"><span id="authEmailSetupCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageAuthEmail()">Manage email authentication</a><br /></span></div>
|
<div id="manageEmail2FA"><div class="p2AccountActions"><span id="authEmailSetupCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageAuthEmail()">Manage email authentication</a><br /></span></div>
|
||||||
<div id="manageAuthApp"><div class="p2AccountActions"><span id="authAppSetupCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageAuthApp()">Manage authenticator app</a><br /></span></div>
|
<div id="manageAuthApp"><div class="p2AccountActions"><span id="authAppSetupCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageAuthApp()">Manage authenticator app</a><br /></span></div>
|
||||||
|
<div id="manageDuoApp"><div class="p2AccountActions"><span id="authDuoSetupCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageAuthDuo()">Manage Duo authentication</a><br /></span></div>
|
||||||
<div id="manageHardwareOtp"><div class="p2AccountActions"><span id="authKeySetupCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageHardwareOtp(0)">Manage security keys</a><br /></span></div>
|
<div id="manageHardwareOtp"><div class="p2AccountActions"><span id="authKeySetupCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageHardwareOtp(0)">Manage security keys</a><br /></span></div>
|
||||||
<div id="managePushAuthDev"><div class="p2AccountActions"><span id="authPushAuthDevCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_managePushAuthDev()">Manage push authentication</a><br /></span></div>
|
<div id="managePushAuthDev"><div class="p2AccountActions"><span id="authPushAuthDevCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_managePushAuthDev()">Manage push authentication</a><br /></span></div>
|
||||||
<div id="manageMessaging1"><div class="p2AccountActions"><span id="authMessagingCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageMessaging()">Manage messaging</a><br /></span></div>
|
<div id="manageMessaging1"><div class="p2AccountActions"><span id="authMessagingCheck"><strong>✓</strong></span></div><span><a href=# onclick="return account_manageMessaging()">Manage messaging</a><br /></span></div>
|
||||||
|
@ -475,23 +481,23 @@
|
||||||
<td class="auto-style1">
|
<td class="auto-style1">
|
||||||
Filter
|
Filter
|
||||||
<select id=p3filterevents onchange=refreshEvents()>
|
<select id=p3filterevents onchange=refreshEvents()>
|
||||||
<option value="">All Logs</option>
|
<option notransval=1 value="">All Logs</option>
|
||||||
<option value=agentlog>Agent Logs</option>
|
<option notransval=1 value=agentlog>Agent Logs</option>
|
||||||
<option value=relaylog>Relay Logs</option>
|
<option notransval=1 value=relaylog>Relay Logs</option>
|
||||||
<option value=manual>Manual Logs</option>
|
<option notransval=1 value=manual>Manual Logs</option>
|
||||||
<option value=runcommands>Run Command Logs</option>
|
<option notransval=1 value=runcommands>Run Command Logs</option>
|
||||||
<option value=batchupload>Batch Upload Logs</option>
|
<option notransval=1 value=batchupload>Batch Upload Logs</option>
|
||||||
<option value=changenode>Change Node Logs</option>
|
<option notransval=1 value=changenode>Change Node Logs</option>
|
||||||
<option value=removenode>Remove Node Logs</option>
|
<option notransval=1 value=removenode>Remove Node Logs</option>
|
||||||
</select>
|
</select>
|
||||||
Show
|
Show
|
||||||
<select id=p3limitdropdown onchange=refreshEvents()>
|
<select id=p3limitdropdown onchange=refreshEvents()>
|
||||||
<option value=60>Last 60</option>
|
<option notransval=1 value=60>Last 60</option>
|
||||||
<option value=120>Last 120</option>
|
<option notransval=1 value=120>Last 120</option>
|
||||||
<option value=250>Last 250</option>
|
<option notransval=1 value=250>Last 250</option>
|
||||||
<option value=500>Last 500</option>
|
<option notransval=1 value=500>Last 500</option>
|
||||||
<option value=1000>Last 1000</option>
|
<option notransval=1 value=1000>Last 1000</option>
|
||||||
<option value="">No limit</option>
|
<option notransval=1 value="">No limit</option>
|
||||||
</select>
|
</select>
|
||||||
<a href=# onclick=p3showDownloadEventsDialog(2)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>
|
<a href=# onclick=p3showDownloadEventsDialog(2)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -992,22 +998,22 @@
|
||||||
<td class="auto-style1">
|
<td class="auto-style1">
|
||||||
Filter
|
Filter
|
||||||
<select id=p16filterevents onchange=refreshDeviceEvents()>
|
<select id=p16filterevents onchange=refreshDeviceEvents()>
|
||||||
<option value="">All Logs</option>
|
<option notransval=1 value="">All Logs</option>
|
||||||
<option value=agentlog>Agent Logs</option>
|
<option notransval=1 value=agentlog>Agent Logs</option>
|
||||||
<option value=relaylog>Relay Logs</option>
|
<option notransval=1 value=relaylog>Relay Logs</option>
|
||||||
<option value=manual>Manual Logs</option>
|
<option notransval=1 value=manual>Manual Logs</option>
|
||||||
<option value=runcommands>Run Command Logs</option>
|
<option notransval=1 value=runcommands>Run Command Logs</option>
|
||||||
<option value=batchupload>Batch Upload Logs</option>
|
<option notransval=1 value=batchupload>Batch Upload Logs</option>
|
||||||
<option value=changenode>Change Node Logs</option>
|
<option notransval=1 value=changenode>Change Node Logs</option>
|
||||||
<option value=removenode>Remove Node Logs</option>
|
<option notransval=1 value=removenode>Remove Node Logs</option>
|
||||||
</select>
|
</select>
|
||||||
Show
|
Show
|
||||||
<select id=p16limitdropdown onchange=refreshDeviceEvents()>
|
<select id=p16limitdropdown onchange=refreshDeviceEvents()>
|
||||||
<option value=60>Last 60</option>
|
<option notransval=1 value=60>Last 60</option>
|
||||||
<option value=120>Last 120</option>
|
<option notransval=1 value=120>Last 120</option>
|
||||||
<option value=250>Last 250</option>
|
<option notransval=1 value=250>Last 250</option>
|
||||||
<option value=500>Last 500</option>
|
<option notransval=1 value=500>Last 500</option>
|
||||||
<option value=1000>Last 1000</option>
|
<option notransval=1 value=1000>Last 1000</option>
|
||||||
</select>
|
</select>
|
||||||
<a href=# onclick=p3showDownloadEventsDialog(1)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>
|
<a href=# onclick=p3showDownloadEventsDialog(1)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -1132,23 +1138,23 @@
|
||||||
<td class="auto-style1">
|
<td class="auto-style1">
|
||||||
Filter
|
Filter
|
||||||
<select id=p31filterevents onchange=refreshUsersEvents()>
|
<select id=p31filterevents onchange=refreshUsersEvents()>
|
||||||
<option value="">All Logs</option>
|
<option notransval=1 value="">All Logs</option>
|
||||||
<option value=agentlog>Agent Logs</option>
|
<option notransval=1 value=agentlog>Agent Logs</option>
|
||||||
<option value=relaylog>Relay Logs</option>
|
<option notransval=1 value=relaylog>Relay Logs</option>
|
||||||
<option value=manual>Manual Logs</option>
|
<option notransval=1 value=manual>Manual Logs</option>
|
||||||
<option value=runcommands>Run Command Logs</option>
|
<option notransval=1 value=runcommands>Run Command Logs</option>
|
||||||
<option value=batchupload>Batch Upload Logs</option>
|
<option notransval=1 value=batchupload>Batch Upload Logs</option>
|
||||||
<option value=changenode>Change Node Logs</option>
|
<option notransval=1 value=changenode>Change Node Logs</option>
|
||||||
<option value=removenode>Remove Node Logs</option>
|
<option notransval=1 value=removenode>Remove Node Logs</option>
|
||||||
</select>
|
</select>
|
||||||
Show
|
Show
|
||||||
<select id=p31limitdropdown onchange=refreshUsersEvents()>
|
<select id=p31limitdropdown onchange=refreshUsersEvents()>
|
||||||
<option value=60>Last 60</option>
|
<option notransval=1 value=60>Last 60</option>
|
||||||
<option value=120>Last 120</option>
|
<option notransval=1 value=120>Last 120</option>
|
||||||
<option value=250>Last 250</option>
|
<option notransval=1 value=250>Last 250</option>
|
||||||
<option value=500>Last 500</option>
|
<option notransval=1 value=500>Last 500</option>
|
||||||
<option value=1000>Last 1000</option>
|
<option notransval=1 value=1000>Last 1000</option>
|
||||||
<option value="">No limit</option>
|
<option notransval=1 value="">No limit</option>
|
||||||
</select>
|
</select>
|
||||||
<a href=# onclick=p3showDownloadEventsDialog(3)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>
|
<a href=# onclick=p3showDownloadEventsDialog(3)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -1452,16 +1458,16 @@
|
||||||
<div style="margin-top:8px">
|
<div style="margin-top:8px">
|
||||||
<div>Display Size</div>
|
<div>Display Size</div>
|
||||||
<select id="d7rdpsize">
|
<select id="d7rdpsize">
|
||||||
<option value="canvas">Canvas Size</option>
|
<option notransval=1 value="canvas">Canvas Size</option>
|
||||||
<option value="browser">Browser Size</option>
|
<option notransval=1 value="browser">Browser Size</option>
|
||||||
<option value="screen">Screen Size</option>
|
<option notransval=1 value="screen">Screen Size</option>
|
||||||
<option value="640x480">640x480</option>
|
<option notransval=1 value="640x480">640x480</option>
|
||||||
<option value="1024x768">1024x768</option>
|
<option notransval=1 value="1024x768">1024x768</option>
|
||||||
<option value="1280x800">1280x800</option>
|
<option notransval=1 value="1280x800">1280x800</option>
|
||||||
<option value="1440x900">1440x900</option>
|
<option notransval=1 value="1440x900">1440x900</option>
|
||||||
<option value="1600x900">1600x900</option>
|
<option notransval=1 value="1600x900">1600x900</option>
|
||||||
<option value="1680x1050">1680x1050</option>
|
<option notransval=1 value="1680x1050">1680x1050</option>
|
||||||
<option value="1920x1080">1920x1080</option>
|
<option notransval=1 value="1920x1080">1920x1080</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -1473,7 +1479,7 @@
|
||||||
<label style="display:block"><input type="checkbox" id="d7rdp4" />Disable Theming</label>
|
<label style="display:block"><input type="checkbox" id="d7rdp4" />Disable Theming</label>
|
||||||
<label style="display:block"><input type="checkbox" id="d7rdp6" />Disable Cursor Shadow</label>
|
<label style="display:block"><input type="checkbox" id="d7rdp6" />Disable Cursor Shadow</label>
|
||||||
<label style="display:block"><input type="checkbox" id="d7rdp7" />Disable Cursor Settings</label>
|
<label style="display:block"><input type="checkbox" id="d7rdp7" />Disable Cursor Settings</label>
|
||||||
<label style="display:block"><input type="checkbox" id="d7rdp8" />Enable Font Smooting</label>
|
<label style="display:block"><input type="checkbox" id="d7rdp8" />Enable Font Smoothing</label>
|
||||||
<label style="display:block"><input type="checkbox" id="d7rdp9" />Enable Desktop Composision</label>
|
<label style="display:block"><input type="checkbox" id="d7rdp9" />Enable Desktop Composision</label>
|
||||||
<label style="display:block"><input type="checkbox" id="d7rdpclip" />Automatic Clipboard</label>
|
<label style="display:block"><input type="checkbox" id="d7rdpclip" />Automatic Clipboard</label>
|
||||||
<label style="display:block"><input type="checkbox" id="d7rdpsmb" />Swap Mouse Buttons</label>
|
<label style="display:block"><input type="checkbox" id="d7rdpsmb" />Swap Mouse Buttons</label>
|
||||||
|
@ -1547,8 +1553,8 @@
|
||||||
var sessionTime = parseInt('{{{sessiontime}}}');
|
var sessionTime = parseInt('{{{sessiontime}}}');
|
||||||
var webRelayPort = parseInt('{{{webRelayPort}}}');
|
var webRelayPort = parseInt('{{{webRelayPort}}}');
|
||||||
var webRelayDns = '{{{webRelayDns}}}';
|
var webRelayDns = '{{{webRelayDns}}}';
|
||||||
var hidePowerTimeline = {{{hidePowerTimeline}}};
|
var hidePowerTimeline = '{{{hidePowerTimeline}}}';
|
||||||
var showNotesPanel = {{{showNotesPanel}}};
|
var showNotesPanel = '{{{showNotesPanel}}}';
|
||||||
var sessionRefreshTimer = null;
|
var sessionRefreshTimer = null;
|
||||||
var domain = '{{{domain}}}';
|
var domain = '{{{domain}}}';
|
||||||
var domainUrl = '{{{domainurl}}}';
|
var domainUrl = '{{{domainurl}}}';
|
||||||
|
@ -1641,6 +1647,7 @@
|
||||||
delete urlargs.viewmode;
|
delete urlargs.viewmode;
|
||||||
delete urlargs.gotonode;
|
delete urlargs.gotonode;
|
||||||
delete urlargs.gotodevicename;
|
delete urlargs.gotodevicename;
|
||||||
|
delete urlargs.gotodeviceip;
|
||||||
delete urlargs.gotomesh;
|
delete urlargs.gotomesh;
|
||||||
delete urlargs.gotouser;
|
delete urlargs.gotouser;
|
||||||
delete urlargs.gotougrp;
|
delete urlargs.gotougrp;
|
||||||
|
@ -1721,6 +1728,9 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show the modern ui switcher
|
||||||
|
QV('textnewui', ((features2 & 0x40000000) == 0) ? false : true);
|
||||||
|
|
||||||
// Connect to the mesh server
|
// Connect to the mesh server
|
||||||
meshserver = MeshServerCreateControl(domainUrl);
|
meshserver = MeshServerCreateControl(domainUrl);
|
||||||
meshserver.onStateChanged = onStateChanged;
|
meshserver.onStateChanged = onStateChanged;
|
||||||
|
@ -2181,10 +2191,29 @@
|
||||||
QV('body', true);
|
QV('body', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveUserInterfaceMode() {
|
||||||
|
var nUiViewMode = 2;
|
||||||
|
if (Q('ui1').checked) { nUiViewMode = 3; }
|
||||||
|
if (getstore('uiViewMode', 2) != nUiViewMode) {
|
||||||
|
putstore('uiViewMode', nUiViewMode);
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBootstrapUIMode() {
|
||||||
|
if (xxdialogMode) return;
|
||||||
|
var uiViewMode = getstore('uiViewMode', 2);
|
||||||
|
var x = '<input type=radio id=ui0 name=uiradio value=2 ' + ((uiViewMode == 2)?'checked':'') + '><label for=ui0>' + "Classic" + '</label><br>';
|
||||||
|
x += '<input type=radio id=ui1 name=uiradio value=3 ' + ((uiViewMode == 3)?'checked':'') + '><label for=ui1>' + "Modern" + '</label><br>';
|
||||||
|
setDialogMode(2, "User Interface", 3, saveUserInterfaceMode, x);
|
||||||
|
QV('uiMenu', false);
|
||||||
|
}
|
||||||
|
|
||||||
function getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; }
|
function getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; }
|
||||||
function reload() {
|
function reload() {
|
||||||
var x = window.location.href;
|
var x = window.location.href;
|
||||||
if (x.endsWith('/#')) { x = x.substring(0, x.length - 2); }
|
if (x.endsWith('/#')) { x = x.substring(0, x.length - 2); }
|
||||||
|
if (x.endsWith('#')) { x = x.substring(0, x.length - 1); }
|
||||||
window.location.href = x;
|
window.location.href = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2357,8 +2386,10 @@
|
||||||
QV('authPhoneNumberCheck', (userinfo.phone != null));
|
QV('authPhoneNumberCheck', (userinfo.phone != null));
|
||||||
QV('authMessagingCheck', (userinfo.msghandle != null));
|
QV('authMessagingCheck', (userinfo.msghandle != null));
|
||||||
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
|
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
|
||||||
|
QV('authDuoSetupCheck', (userinfo.otpduo == 1) && ((features2 & 0x20000000) != 0));
|
||||||
QV('authAppSetupCheck', userinfo.otpsecret == 1);
|
QV('authAppSetupCheck', userinfo.otpsecret == 1);
|
||||||
QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
|
QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
|
||||||
|
QV('manageDuoApp', (serverinfo.lock2factor != true) && ((features2 & 0x20000000) != 0));
|
||||||
QV('authKeySetupCheck', userinfo.otphkeys > 0);
|
QV('authKeySetupCheck', userinfo.otphkeys > 0);
|
||||||
QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0));
|
QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0));
|
||||||
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
|
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
|
||||||
|
@ -2416,7 +2447,9 @@
|
||||||
files.Stop();
|
files.Stop();
|
||||||
files = null;
|
files = null;
|
||||||
}
|
}
|
||||||
window.location.href = 'logout';
|
if (serverinfo.logoutOnIdleSessionTimeout) {
|
||||||
|
window.location.href = 'logout';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var ds = Math.round((serverinfo.timeout - delta) / 1000);
|
var ds = Math.round((serverinfo.timeout - delta) / 1000);
|
||||||
if (ds <= 60) {
|
if (ds <= 60) {
|
||||||
|
@ -2483,7 +2516,8 @@
|
||||||
23: "Unable to load agent icon file: {0}.",
|
23: "Unable to load agent icon file: {0}.",
|
||||||
24: "Unable to load agent logo file: {0}.",
|
24: "Unable to load agent logo file: {0}.",
|
||||||
25: "This NodeJS version does not support OpenID.",
|
25: "This NodeJS version does not support OpenID.",
|
||||||
26: "This NodeJS version does not support Discord.js."
|
26: "This NodeJS version does not support Discord.js.",
|
||||||
|
27: "Firebase now requires a service account JSON file, Firebase disabled."
|
||||||
};
|
};
|
||||||
var x = '';
|
var x = '';
|
||||||
for (var i in message.warnings) {
|
for (var i in message.warnings) {
|
||||||
|
@ -2832,11 +2866,11 @@
|
||||||
if (net.name) { x += addHtmlValue2("Name", '<b>' + EscapeHtml(net.name) + '</b>'); }
|
if (net.name) { x += addHtmlValue2("Name", '<b>' + EscapeHtml(net.name) + '</b>'); }
|
||||||
if (net.desc) { x += addHtmlValue2("Description", EscapeHtml(net.desc).replace('(R)', '®').replace('(r)', '®')); }
|
if (net.desc) { x += addHtmlValue2("Description", EscapeHtml(net.desc).replace('(R)', '®').replace('(r)', '®')); }
|
||||||
if (net.dnssuffix) { x += addHtmlValue2("DNS suffix", EscapeHtml(net.dnssuffix) + ' <img src="images/link4.png" title="' + "Copy name to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.dnssuffix) + '") width=10 height=10>'); }
|
if (net.dnssuffix) { x += addHtmlValue2("DNS suffix", EscapeHtml(net.dnssuffix) + ' <img src="images/link4.png" title="' + "Copy name to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.dnssuffix) + '") width=10 height=10>'); }
|
||||||
if (net.mac) { x += addHtmlValue2("MAC address", '<a href="https://dnslytics.com/mac-address-lookup/' + net.mac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.mac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.mac.toLowerCase()) + '") width=10 height=10>'); }
|
if (net.mac) { x += addHtmlValue2("MAC address", '<a href="https://maclookup.app/search/result?mac=' + net.mac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.mac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.mac.toLowerCase()) + '") width=10 height=10>'); }
|
||||||
if (net.v4addr) { x += addHtmlValue2("IPv4 address", EscapeHtml(net.v4addr) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4addr) + '") width=10 height=10>'); }
|
if (net.v4addr) { x += addHtmlValue2("IPv4 address", EscapeHtml(net.v4addr) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4addr) + '") width=10 height=10>'); }
|
||||||
if (net.v4mask) { x += addHtmlValue2("IPv4 mask", EscapeHtml(net.v4mask) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4mask) + '") width=10 height=10>'); }
|
if (net.v4mask) { x += addHtmlValue2("IPv4 mask", EscapeHtml(net.v4mask) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4mask) + '") width=10 height=10>'); }
|
||||||
if (net.v4gateway) { x += addHtmlValue2("IPv4 gateway", EscapeHtml(net.v4gateway) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4gateway) + '") width=10 height=10>'); }
|
if (net.v4gateway) { x += addHtmlValue2("IPv4 gateway", EscapeHtml(net.v4gateway) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4gateway) + '") width=10 height=10>'); }
|
||||||
if (net.gatewaymac) { x += addHtmlValue2("Gateway MAC", '<a href="https://dnslytics.com/mac-address-lookup/' + net.gatewaymac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.gatewaymac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.gatewaymac.toLowerCase()) + '") width=10 height=10>'); }
|
if (net.gatewaymac) { x += addHtmlValue2("Gateway MAC", '<a href="https://maclookup.app/search/result?mac=' + net.gatewaymac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.gatewaymac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.gatewaymac.toLowerCase()) + '") width=10 height=10>'); }
|
||||||
}
|
}
|
||||||
} else if (message.netif2 != null) {
|
} else if (message.netif2 != null) {
|
||||||
// New style
|
// New style
|
||||||
|
@ -2845,7 +2879,7 @@
|
||||||
if ((Array.isArray(net) == false) || (net.length < 1) || (net[0] == null) || ((typeof net[0].mac == 'string') && (net[0].mac.startsWith('00:00:00:00')))) continue;
|
if ((Array.isArray(net) == false) || (net.length < 1) || (net[0] == null) || ((typeof net[0].mac == 'string') && (net[0].mac.startsWith('00:00:00:00')))) continue;
|
||||||
x += '<hr />'
|
x += '<hr />'
|
||||||
x += addHtmlValue2("Name", '<b>' + EscapeHtml(i) + '</b>');
|
x += addHtmlValue2("Name", '<b>' + EscapeHtml(i) + '</b>');
|
||||||
if (typeof net[0].mac == 'string') { x += addHtmlValue2("MAC address", '<a href="https://dnslytics.com/mac-address-lookup/' + net[0].mac.split(':').join('').substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net[0].mac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net[0].mac.toLowerCase()) + '") width=10 height=10>'); }
|
if (typeof net[0].mac == 'string') { x += addHtmlValue2("MAC address", '<a href="https://maclookup.app/search/result?mac=' + net[0].mac.split(':').join('').substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net[0].mac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net[0].mac.toLowerCase()) + '") width=10 height=10>'); }
|
||||||
if (net[0].fqdn) { x += addHtmlValue2("FQDN", net[0].fqdn); }
|
if (net[0].fqdn) { x += addHtmlValue2("FQDN", net[0].fqdn); }
|
||||||
for (var j = 0; j < net.length; j++) {
|
for (var j = 0; j < net.length; j++) {
|
||||||
var netif = net[j];
|
var netif = net[j];
|
||||||
|
@ -2993,7 +3027,7 @@
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Q('notesPanelArea').innerHTML = (message.notes && marked && DOMPurify) ? DOMPurify.sanitize(marked.parse(decodeURIComponent(message.notes), { breaks: true }), { USE_PROFILES: { html: true } }) : '';
|
Q('notesPanelArea').innerHTML = (message.notes && marked && DOMPurify) ? DOMPurify.sanitize(marked.parse(decodeURIComponent(message.notes), { breaks: true }), { USE_PROFILES: { html: true } }) : '';
|
||||||
if (showNotesPanel && message.notes) { QV('notesPanel',true); }else{ QV('notesPanel', false); }
|
if ((showNotesPanel === 'true') && message.notes) { QV('notesPanel',true); }else{ QV('notesPanel', false); }
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3006,7 +3040,7 @@
|
||||||
var secret = message.secret;
|
var secret = message.secret;
|
||||||
if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
|
if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
|
||||||
else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
|
else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
|
||||||
QH('d2optinfo', '<table style=width:380px><tr><td style=vertical-align:top>' + format("Install <a href=\"https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2\" rel=\"noreferrer noopener\" target=_blank>Google Authenticator</a> or a compatible application and scan the barcode, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank>this link</a> or enter the secret. Then, enter the current 6 digit token below to activate 2-Step login.", message.url) + '<br /><br />' + 'Secret <img src=images/link4.png height=10 width=10 title="' + "Copy Secret to clipboard" + '" style=cursor:pointer onclick=d2CopySecretToClip()>' + '<br /><tt id=d2optsecret secret="' + message.secret + '" style=font-size:12px>' + secret + '</tt><br /><br /></td><td style=width:1px;vertical-align:top><a href="' + message.url + '" rel="noreferrer noopener" target=_blank><div id="qrcode"></div></a></td><tr><td colspan=2 style="text-align:center;border-top:1px solid black"><br />' + "Enter the token here for 2-step login:" + ' <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></td></table>');
|
QH('d2optinfo', '<table style=width:380px><tr><td style=vertical-align:top>' + format("Install" + ' <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" rel="noreferrer noopener" target=_blank>' + "Google Authenticator" + '</a> ' + "or a compatible application and scan the barcode, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank>this link</a> or enter the secret. Then, enter the current 6 digit token below to activate 2-Step login.", message.url) + '<br /><br />' + 'Secret <img src=images/link4.png height=10 width=10 title="' + "Copy Secret to clipboard" + '" style=cursor:pointer onclick=d2CopySecretToClip()>' + '<br /><tt id=d2optsecret secret="' + message.secret + '" style=font-size:12px>' + secret + '</tt><br /><br /></td><td style=width:1px;vertical-align:top><a href="' + message.url + '" rel="noreferrer noopener" target=_blank><div id="qrcode"></div></a></td><tr><td colspan=2 style="text-align:center;border-top:1px solid black"><br />' + "Enter the token here for 2-step login:" + ' <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></td></table>');
|
||||||
new QRCode(Q('qrcode'), { text: message.url, width: 128, height: 128, colorDark: '#000000', colorLight: '#EEE', correctLevel: QRCode.CorrectLevel.H });
|
new QRCode(Q('qrcode'), { text: message.url, width: 128, height: 128, colorDark: '#000000', colorLight: '#EEE', correctLevel: QRCode.CorrectLevel.H });
|
||||||
QV('idx_dlgOkButton', true);
|
QV('idx_dlgOkButton', true);
|
||||||
QE('idx_dlgOkButton', false);
|
QE('idx_dlgOkButton', false);
|
||||||
|
@ -3061,7 +3095,7 @@
|
||||||
if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
|
if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
|
||||||
var start = '<div style="border-radius:6px;border:2px solid #CCC;background-color:#BBB;width:100%;box-sizing:border-box;margin-bottom:6px"><div style="margin:3px;font-family:Arial, Helvetica, sans-serif;font-size:16px;font-weight:bold"><table style=width:100%;text-align:left>';
|
var start = '<div style="border-radius:6px;border:2px solid #CCC;background-color:#BBB;width:100%;box-sizing:border-box;margin-bottom:6px"><div style="margin:3px;font-family:Arial, Helvetica, sans-serif;font-size:16px;font-weight:bold"><table style=width:100%;text-align:left>';
|
||||||
var end = '</table></div></div>';
|
var end = '</table></div></div>';
|
||||||
var x = "<a href=\"https://www.yubico.com/\" rel=\"noreferrer noopener\" target=\"_blank\">Hardware keys</a> are used as secondary login authentication.";
|
var x = '<a href="https://www.yubico.com/" rel="noreferrer noopener" target="_blank">' + "Hardware keys" + '</a> ' + "are used as secondary login authentication.";
|
||||||
x += '<div style="max-height:150px;overflow-y:auto;overflow-x:hidden;margin-top:6px;margin-bottom:6px">';
|
x += '<div style="max-height:150px;overflow-y:auto;overflow-x:hidden;margin-top:6px;margin-bottom:6px">';
|
||||||
if (message.keys && message.keys.length > 0) {
|
if (message.keys && message.keys.length > 0) {
|
||||||
for (var i in message.keys) {
|
for (var i in message.keys) {
|
||||||
|
@ -3174,9 +3208,11 @@
|
||||||
if (currentNode && (message.event.nodeid == currentNode._id) && (currentDeviceEvents != null)) {
|
if (currentNode && (message.event.nodeid == currentNode._id) && (currentDeviceEvents != null)) {
|
||||||
// If this event has a nodeid and we are looking at this node, update the log in real time.
|
// If this event has a nodeid and we are looking at this node, update the log in real time.
|
||||||
if ((message.event.action == p16filterevents.value) || (p16filterevents.value == "")) {
|
if ((message.event.action == p16filterevents.value) || (p16filterevents.value == "")) {
|
||||||
currentDeviceEvents.unshift(message.event);
|
if(currentDeviceEvents != null) {
|
||||||
var eventLimit = parseInt(p16limitdropdown.value);
|
currentDeviceEvents.unshift(message.event);
|
||||||
while (currentDeviceEvents.length > eventLimit) { currentDeviceEvents.pop(); } // Remove element(s) at the end
|
var eventLimit = parseInt(p16limitdropdown.value);
|
||||||
|
while (currentDeviceEvents.length > eventLimit) { currentDeviceEvents.pop(); } // Remove element(s) at the end
|
||||||
|
}
|
||||||
mainUpdate(1024);
|
mainUpdate(1024);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3184,18 +3220,22 @@
|
||||||
if (currentUser && (message.event.userid == currentUser._id)) {
|
if (currentUser && (message.event.userid == currentUser._id)) {
|
||||||
// If this event has a userid and we are looking at this user, update the log in real time.
|
// If this event has a userid and we are looking at this user, update the log in real time.
|
||||||
if ((message.event.action == p31filterevents.value) || (p31filterevents.value == "")) {
|
if ((message.event.action == p31filterevents.value) || (p31filterevents.value == "")) {
|
||||||
currentUserEvents.unshift(message.event);
|
if(currentUserEvents != null) {
|
||||||
var eventLimit = parseInt(p31limitdropdown.value);
|
currentUserEvents.unshift(message.event);
|
||||||
while (currentUserEvents.length > eventLimit) { currentUserEvents.pop(); } // Remove element(s) at the end
|
var eventLimit = parseInt(p31limitdropdown.value);
|
||||||
|
while (currentUserEvents.length > eventLimit) { currentUserEvents.pop(); } // Remove element(s) at the end
|
||||||
|
}
|
||||||
mainUpdate(2048);
|
mainUpdate(2048);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this event to the main events log.
|
// Add this event to the main events log.
|
||||||
if ((message.event.action == p3filterevents.value) || (p3filterevents.value == "")) {
|
if ((message.event.action == p3filterevents.value) || (p3filterevents.value == "")) {
|
||||||
events.unshift(message.event);
|
if(events != null) {
|
||||||
var eventLimit = parseInt(p3limitdropdown.value);
|
events.unshift(message.event);
|
||||||
while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
|
var eventLimit = parseInt(p3limitdropdown.value);
|
||||||
|
while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
|
||||||
|
}
|
||||||
mainUpdate(32);
|
mainUpdate(32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3491,6 +3531,7 @@
|
||||||
// Change the node
|
// Change the node
|
||||||
node.name = message.event.node.name;
|
node.name = message.event.node.name;
|
||||||
node.rname = message.event.node.rname;
|
node.rname = message.event.node.rname;
|
||||||
|
node.lusers = message.event.node.lusers;
|
||||||
node.users = message.event.node.users;
|
node.users = message.event.node.users;
|
||||||
node.host = message.event.node.host;
|
node.host = message.event.node.host;
|
||||||
node.desc = message.event.node.desc;
|
node.desc = message.event.node.desc;
|
||||||
|
@ -4007,6 +4048,10 @@
|
||||||
var foundNode = null;
|
var foundNode = null;
|
||||||
if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
|
if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
|
||||||
if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
|
if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
|
||||||
|
} else if (args.gotodeviceip != null) {
|
||||||
|
var foundNode = null;
|
||||||
|
if (nodes != null) { for (var i in nodes) { if (nodes[i].ip == args.gotodeviceip) { foundNode = nodes[i]._id; } } }
|
||||||
|
if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
|
||||||
} else if (args.gotomesh != null) {
|
} else if (args.gotomesh != null) {
|
||||||
if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
|
if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
|
||||||
gotoMesh('mesh/' + domain + '/' + args.gotomesh);
|
gotoMesh('mesh/' + domain + '/' + args.gotomesh);
|
||||||
|
@ -4887,13 +4932,13 @@
|
||||||
r += '<span style=line-height:20px>' + groupingTags + '</span>';
|
r += '<span style=line-height:20px>' + groupingTags + '</span>';
|
||||||
}
|
}
|
||||||
if (deviceViewSettings.devsCols.indexOf('windowsav') >= 0) { // Windows AV
|
if (deviceViewSettings.devsCols.indexOf('windowsav') >= 0) { // Windows AV
|
||||||
r += '<td style=text-align:center>' + ((node.wsc && node.wsc.antiVirus != null) ? (node.wsc.antiVirus == 'OK' ? "<span style=color:green>OK</span>" : "<span style=color:red>BAD</span>") : "");
|
r += '<td style=text-align:center>' + ((node.wsc && node.wsc.antiVirus != null) ? (node.wsc.antiVirus == 'OK' ? '<span style=color:green>' + "OK" + '</span>' : '<span style=color:red>' + "BAD" + '</span>') : "");
|
||||||
}
|
}
|
||||||
if (deviceViewSettings.devsCols.indexOf('windowsupdate') >= 0) {// Windows Update
|
if (deviceViewSettings.devsCols.indexOf('windowsupdate') >= 0) {// Windows Update
|
||||||
r += '<td style=text-align:center>' + ((node.wsc && node.wsc.autoUpdate != null) ? (node.wsc.autoUpdate == 'OK' ? "<span style=color:green>OK</span>" : "<span style=color:red>BAD</span>") : "");
|
r += '<td style=text-align:center>' + ((node.wsc && node.wsc.autoUpdate != null) ? (node.wsc.autoUpdate == 'OK' ? '<span style=color:green>' + "OK" + '</span>' : '<span style=color:red>' + "BAD" + '</span>') : "");
|
||||||
}
|
}
|
||||||
if (deviceViewSettings.devsCols.indexOf('windowsfirewall') >= 0) { // Windows Firewall
|
if (deviceViewSettings.devsCols.indexOf('windowsfirewall') >= 0) { // Windows Firewall
|
||||||
r += '<td style=text-align:center>' + ((node.wsc && node.wsc.firewall != null) ? (node.wsc.firewall == 'OK' ? "<span style=color:green>OK</span>" : "<span style=color:red>BAD</span>") : "");
|
r += '<td style=text-align:center>' + ((node.wsc && node.wsc.firewall != null) ? (node.wsc.firewall == 'OK' ? '<span style=color:green>' + "OK" + '</span>' : '<span style=color:red>' + "BAD" + '</span>') : "");
|
||||||
}
|
}
|
||||||
if (deviceViewSettings.devsCols.indexOf('lastbootuptime') >= 0) { // Last Boot Up Time
|
if (deviceViewSettings.devsCols.indexOf('lastbootuptime') >= 0) { // Last Boot Up Time
|
||||||
r += '<td style=text-align:center;font-size:x-small>' + ((node.lastbootuptime != null) ? printDateTime(new Date(node.lastbootuptime)) : "");
|
r += '<td style=text-align:center;font-size:x-small>' + ((node.lastbootuptime != null) ? printDateTime(new Date(node.lastbootuptime)) : "");
|
||||||
|
@ -5384,7 +5429,7 @@
|
||||||
x += addHtmlValue("New Password*", '<input id=dp1password1 type=password style=width:230px autocomplete=off maxlength=32 onchange=validateAmtAcmSetupEx() onkeyup=validateAmtAcmSetupEx() />');
|
x += addHtmlValue("New Password*", '<input id=dp1password1 type=password style=width:230px autocomplete=off maxlength=32 onchange=validateAmtAcmSetupEx() onkeyup=validateAmtAcmSetupEx() />');
|
||||||
x += addHtmlValue("New Password*", '<input id=dp1password2 type=password style=width:230px autocomplete=off maxlength=32 onchange=validateAmtAcmSetupEx() onkeyup=validateAmtAcmSetupEx() />');
|
x += addHtmlValue("New Password*", '<input id=dp1password2 type=password style=width:230px autocomplete=off maxlength=32 onchange=validateAmtAcmSetupEx() onkeyup=validateAmtAcmSetupEx() />');
|
||||||
if ((features2 & 0x00000020) && (currentMesh.mtype == 1) && (serverinfo.amtProvServerMeshId == currentMesh._id)) { x += '<label><input id=dp1lanprov type=checkbox /> ' + "Use for bare-metal LAN activation." + '</label>'; } // Intel AMT LAN provisioning server is active.
|
if ((features2 & 0x00000020) && (currentMesh.mtype == 1) && (serverinfo.amtProvServerMeshId == currentMesh._id)) { x += '<label><input id=dp1lanprov type=checkbox /> ' + "Use for bare-metal LAN activation." + '</label>'; } // Intel AMT LAN provisioning server is active.
|
||||||
x += '<div><span id=dp10passNotify style="font-size:10px"> ' + "* 8 characters, 1 upper, 1 lower, 1 numeric, 1 non-alpha numeric." + '</span></div>';
|
x += '<div><span id=dp10passNotify style="font-size:10px"> ' + "* 8-16 characters, 1 upper, 1 lower, 1 numeric, 1 non-alpha numeric." + '</span></div>';
|
||||||
setDialogMode(2, "Intel® AMT ACM", 3, showAmtAcmSetupEx, x);
|
setDialogMode(2, "Intel® AMT ACM", 3, showAmtAcmSetupEx, x);
|
||||||
Q('dp1password0').focus();
|
Q('dp1password0').focus();
|
||||||
validateAmtAcmSetupEx();
|
validateAmtAcmSetupEx();
|
||||||
|
@ -5603,10 +5648,10 @@
|
||||||
|
|
||||||
// QR code agent install
|
// QR code agent install
|
||||||
x += '<div id=agins_qrcode style=display:none;min-height:180px><a id=agins_qrimage_a rel=\"noreferrer noopener\" target=_blank><div id=agins_qrimage style=float:right;margin-left:10px;width:180px;height:180px;cursor:pointer></div></a><div>' + format("To add a mobile device to group \"{0}\", download the MeshAgent application and scan this QR code.", EscapeHtml(mesh.name)) + '</div>';
|
x += '<div id=agins_qrcode style=display:none;min-height:180px><a id=agins_qrimage_a rel=\"noreferrer noopener\" target=_blank><div id=agins_qrimage style=float:right;margin-left:10px;width:180px;height:180px;cursor:pointer></div></a><div>' + format("To add a mobile device to group \"{0}\", download the MeshAgent application and scan this QR code.", EscapeHtml(mesh.name)) + '</div>';
|
||||||
x += "<table style=width:180px>";
|
x += '<table style=width:180px>';
|
||||||
x += "<tr><td style=text-align:center><a rel=\"noreferrer noopener\" target=_blank href=\"https://play.google.com/store/apps/details?id=com.meshcentral.agent2\"><img style=cursor:pointer src=\"images/google-play-140.png\" width=140 srcset=\"images/google-play-280.png 2x\" /></a></td></tr>";
|
x += '<tr><td style=text-align:center><a title="' + "Google Play Store" + '"rel="noreferrer noopener" target=_blank href="https://play.google.com/store/apps/details?id=com.meshcentral.agent2"><img style=cursor:pointer src="images/google-play-140.png" width=140 srcset="images/google-play-280.png 2x" /></a></td></tr>';
|
||||||
x += "<tr><td style=text-align:center><a rel=\"noreferrer noopener\" target=_blank href=\"https://www.amazon.co.uk/gp/product/B097Z4Q7SK/\"><img style=cursor:pointer src=\"images/amazon-appstore-140.png\" width=140 srcset=\"images/amazon-appstore-280.png 2x\" /></a></td></tr>";
|
x += '<tr><td style=text-align:center><a title="' + "Amazon App Store" + '" rel="noreferrer noopener" target=_blank href="https://www.amazon.co.uk/gp/product/B097Z4Q7SK/"><img style=cursor:pointer src="images/amazon-appstore-140.png" width=140 srcset="images/amazon-appstore-280.png 2x" /></a></td></tr>';
|
||||||
x += "</table>";
|
x += '</table>';
|
||||||
x += addHtmlValue("Android APK", '<a onclick=downloadFile("meshagents?id=14' + (urlargs.key?('&key=' + urlargs.key):'') + '",null,true) title="' + "APK version of the MeshAgent" + '">' + "APK" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=14&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '")>');
|
x += addHtmlValue("Android APK", '<a onclick=downloadFile("meshagents?id=14' + (urlargs.key?('&key=' + urlargs.key):'') + '",null,true) title="' + "APK version of the MeshAgent" + '">' + "APK" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=14&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '")>');
|
||||||
x += '</div>'
|
x += '</div>'
|
||||||
|
|
||||||
|
@ -5931,7 +5976,7 @@
|
||||||
setDialogMode(2, "Edit Device Tags", 3, d2groupActionFunctionTagsExec, x);
|
setDialogMode(2, "Edit Device Tags", 3, d2groupActionFunctionTagsExec, x);
|
||||||
} else if (op == 108) {
|
} else if (op == 108) {
|
||||||
// Device notification
|
// Device notification
|
||||||
var x = "<div style=margin-bottom:4px>Perform batch device notification</div>";
|
var x = '<div style=margin-bottom:4px>'+ "Perform batch device notification" + '</div>';
|
||||||
x += '<select id=d2deviceop style=width:100%;margin-bottom:4px><option value=2>' + "Toast Notification" + '</option><option value=1>' + "Message Box" + '</option><option value=3>' + "Alert Box" + '</option></select>';
|
x += '<select id=d2deviceop style=width:100%;margin-bottom:4px><option value=2>' + "Toast Notification" + '</option><option value=1>' + "Message Box" + '</option><option value=3>' + "Alert Box" + '</option></select>';
|
||||||
x += '<input id=dp2notifyTitle maxlength=256 placeholder="' + "Title" + '" style=width:100%;box-sizing:border-box;margin-bottom:4px />';
|
x += '<input id=dp2notifyTitle maxlength=256 placeholder="' + "Title" + '" style=width:100%;box-sizing:border-box;margin-bottom:4px />';
|
||||||
x += '<textarea id=d2notifyMsg style=background-color:#fcf3cf;width:100%;height:140px;resize:none;overflow-y:scroll;box-sizing:border-box;margin-bottom:4px></textarea>';
|
x += '<textarea id=d2notifyMsg style=background-color:#fcf3cf;width:100%;height:140px;resize:none;overflow-y:scroll;box-sizing:border-box;margin-bottom:4px></textarea>';
|
||||||
|
@ -6045,7 +6090,7 @@
|
||||||
meshserver.send({ action: 'getDeviceDetails', nodeids: chkNodeIds, tz: tz, tf: new Date().getTimezoneOffset(), l: getLang(), type: 'csv' }); // With details
|
meshserver.send({ action: 'getDeviceDetails', nodeids: chkNodeIds, tz: tz, tf: new Date().getTimezoneOffset(), l: getLang(), type: 'csv' }); // With details
|
||||||
} else {
|
} else {
|
||||||
// Without details
|
// Without details
|
||||||
var csv = "id,name,rname,host,icon,ip,osdesc,state,groupname,conn,pwr,av,update,firewall,bitlocker,avdetails,tags" + '\r\n', r = [];
|
var csv = "id,name,rname,host,icon,ip,osdesc,state,groupname,conn,pwr,av,update,firewall,bitlocker,avdetails,tags,lastbootuptime" + '\r\n', r = [];
|
||||||
for (var i in chkNodeIds) {
|
for (var i in chkNodeIds) {
|
||||||
var n = getNodeFromId(chkNodeIds[i]);
|
var n = getNodeFromId(chkNodeIds[i]);
|
||||||
csv += '"' + n._id.split(',').join('') + '","' + n.name.split(',').join('') + '","' + (n.rname?(n.rname.split(',').join('')):'') + '","' + (n.host?(n.host.split(',').join('')):'') + '","' + n.icon + '","' + (n.ip?n.ip:'') + '","' + (n.osdesc?(n.osdesc.split(',').join('')):'') + '","' + n.state + '","' + meshes[n.meshid].name.split(',').join('') + '","' + (n.conn?n.conn:'') + '","' + (n.pwr?n.pwr:'') + '"';
|
csv += '"' + n._id.split(',').join('') + '","' + n.name.split(',').join('') + '","' + (n.rname?(n.rname.split(',').join('')):'') + '","' + (n.host?(n.host.split(',').join('')):'') + '","' + n.icon + '","' + (n.ip?n.ip:'') + '","' + (n.osdesc?(n.osdesc.split(',').join('')):'') + '","' + n.state + '","' + meshes[n.meshid].name.split(',').join('') + '","' + (n.conn?n.conn:'') + '","' + (n.pwr?n.pwr:'') + '"';
|
||||||
|
@ -6071,6 +6116,7 @@
|
||||||
else {
|
else {
|
||||||
csv += ',';
|
csv += ',';
|
||||||
}
|
}
|
||||||
|
if (typeof n.lastbootuptime == 'number') { csv += ',"' + n.lastbootuptime + '"'; }
|
||||||
csv += '\r\n';
|
csv += '\r\n';
|
||||||
}
|
}
|
||||||
saveAs(stringToUtf8Blob(csv), "devicelist.csv");
|
saveAs(stringToUtf8Blob(csv), "devicelist.csv");
|
||||||
|
@ -7582,7 +7628,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defender for Windows Server
|
// Defender for Windows Server
|
||||||
if(node.defender && !node.wsc) {
|
if(node.defender) {
|
||||||
var y = [];
|
var y = [];
|
||||||
if (node.defender.RealTimeProtection != null) { if (node.defender.RealTimeProtection == true) { y.push("RealTimeProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("RealTimeProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
if (node.defender.RealTimeProtection != null) { if (node.defender.RealTimeProtection == true) { y.push("RealTimeProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("RealTimeProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
||||||
if (node.defender.TamperProtected != null) { if (node.defender.TamperProtected == true) { y.push("TamperProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("TamperProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
if (node.defender.TamperProtected != null) { if (node.defender.TamperProtected == true) { y.push("TamperProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("TamperProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
|
||||||
|
@ -7605,7 +7651,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Active Users
|
// Active Users
|
||||||
if (node.users && (node.users.length > 0)) { x += addDeviceAttribute(((node.users.length > 1)?"Active Users":"Active User"), EscapeHtml(node.users.join(', '))); }
|
if (node.users && node.users.length > 0) {
|
||||||
|
var u = node.users.map(function(user) {
|
||||||
|
return addKeyLinkConditional(EscapeHtml(user), "Locked", (node.lusers && node.lusers.indexOf(user) >= 0));
|
||||||
|
}).join(', ');
|
||||||
|
x += addDeviceAttribute((node.users.length > 1 ? "Active Users" : "Active User"), u);
|
||||||
|
}
|
||||||
|
|
||||||
// Display device user consent
|
// Display device user consent
|
||||||
if ((node.agent != null) && (node.agent.id != 14) && (node.mtype != 3)) {
|
if ((node.agent != null) && (node.agent.id != 14) && (node.mtype != 3)) {
|
||||||
|
@ -8192,7 +8243,7 @@
|
||||||
if (noteid == null) { noteid = encodeURIComponentEx('p'+userinfo._id); }
|
if (noteid == null) { noteid = encodeURIComponentEx('p'+userinfo._id); }
|
||||||
var x = '<textarea id=d2devNotes ro=' + readonly + ' noteid=' + noteid + ' readonly style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
|
var x = '<textarea id=d2devNotes ro=' + readonly + ' noteid=' + noteid + ' readonly style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
|
||||||
if (noteid.startsWith('node%2F%2F')) { x += ' <span style=font-size:10px>' + "Device group notes can be viewed and changed by other device group administrators." + '</span>'; }
|
if (noteid.startsWith('node%2F%2F')) { x += ' <span style=font-size:10px>' + "Device group notes can be viewed and changed by other device group administrators." + '</span>'; }
|
||||||
if (showNotesPanel) { x += ' <span style=font-size:10px><a target=_blank href=\'https://www.markdownguide.org/cheat-sheet/\'>' + "Markdown syntax supported" + '</a></span>'; }
|
if (showNotesPanel === 'true') { x += ' <span style=font-size:10px><a target=_blank href=\'https://www.markdownguide.org/cheat-sheet/\'>' + "Markdown syntax supported" + '</a></span>'; }
|
||||||
setDialogMode(2, "Notes", 3, showNotesEx, x, noteid);
|
setDialogMode(2, "Notes", 3, showNotesEx, x, noteid);
|
||||||
meshserver.send({ action: 'getNotes', id: decodeURIComponent(noteid) });
|
meshserver.send({ action: 'getNotes', id: decodeURIComponent(noteid) });
|
||||||
}
|
}
|
||||||
|
@ -8200,7 +8251,7 @@
|
||||||
function showNotesEx(buttons, tag) {
|
function showNotesEx(buttons, tag) {
|
||||||
Q('notesPanelArea').innerHTML = (marked && DOMPurify) ? DOMPurify.sanitize(marked.parse(Q('d2devNotes').value, { breaks: true }), { USE_PROFILES: { html: true } }) : Q('d2devNotes').value;
|
Q('notesPanelArea').innerHTML = (marked && DOMPurify) ? DOMPurify.sanitize(marked.parse(Q('d2devNotes').value, { breaks: true }), { USE_PROFILES: { html: true } }) : Q('d2devNotes').value;
|
||||||
meshserver.send({ action: 'setNotes', id: decodeURIComponent(tag), notes: encodeURIComponentEx(Q('d2devNotes').value) });
|
meshserver.send({ action: 'setNotes', id: decodeURIComponent(tag), notes: encodeURIComponentEx(Q('d2devNotes').value) });
|
||||||
if (showNotesPanel && Q('d2devNotes').value != '') { QV('notesPanel',true); }else{ QV('notesPanel', false); }
|
if ((showNotesPanel === 'true') && Q('d2devNotes').value != '') { QV('notesPanel',true); }else{ QV('notesPanel', false); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function openIpKvmRemoteControl(nodeid) {
|
function openIpKvmRemoteControl(nodeid) {
|
||||||
|
@ -8621,7 +8672,7 @@
|
||||||
|
|
||||||
// Draw device power bars. The bars are 766px wide.
|
// Draw device power bars. The bars are 766px wide.
|
||||||
function drawDeviceTimeline() {
|
function drawDeviceTimeline() {
|
||||||
if ((currentNode == null) || (xxcurrentView < 10) || (xxcurrentView > 19) || (currentNode.mtype == 3) || (hidePowerTimeline)) return;
|
if ((currentNode == null) || (xxcurrentView < 10) || (xxcurrentView > 19) || (currentNode.mtype == 3) || (hidePowerTimeline === 'true')) return;
|
||||||
var timeline = null, now = Date.now();
|
var timeline = null, now = Date.now();
|
||||||
if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
|
if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
|
||||||
|
|
||||||
|
@ -9592,6 +9643,7 @@
|
||||||
QH('DeskLatency', latencyStr);
|
QH('DeskLatency', latencyStr);
|
||||||
// Auto-clipboard
|
// Auto-clipboard
|
||||||
if ((((desktop.contype != 4) && (desktopsettings.autoclipboard === true)) || ((desktop.contype == 4) && (desktopsettings.rdpautoclipboard === true))) && (navigator.clipboard != null) && (navigator.clipboard.readText != null)) {
|
if ((((desktop.contype != 4) && (desktopsettings.autoclipboard === true)) || ((desktop.contype == 4) && (desktopsettings.rdpautoclipboard === true))) && (navigator.clipboard != null) && (navigator.clipboard.readText != null)) {
|
||||||
|
if (Mstsc.browser() == 'firefox') return; // this is needed because firefox pops up a PASTE option every second which is annoying
|
||||||
try {
|
try {
|
||||||
navigator.clipboard.readText().then(function(text) {
|
navigator.clipboard.readText().then(function(text) {
|
||||||
if (desktop == null) return;
|
if (desktop == null) return;
|
||||||
|
@ -10525,7 +10577,7 @@
|
||||||
|
|
||||||
function dmousedown(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousedown(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousedown(e); } } dblClickDetect(e); }
|
function dmousedown(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousedown(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousedown(e); } } dblClickDetect(e); }
|
||||||
function dmouseup(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mouseup(e); desktop.m.sendKeepAlive(); } else { desktop.m.mouseup(e); } }
|
function dmouseup(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mouseup(e); desktop.m.sendKeepAlive(); } else { desktop.m.mouseup(e); } }
|
||||||
function dmousemove(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousemove(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousemove(e); } } }
|
function dmousemove(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { Q('Desk').style.cursor = ''; if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousemove(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousemove(e); } } else if (!xxdialogMode && desktop != null && !Q('DeskControl').checked) { Q('Desk').style.cursor = 'not-allowed'; } }
|
||||||
function dmousewheel(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousewheel(e); desktop.m.sendKeepAlive(); } else { if (desktop.m.mousewheel) { desktop.m.mousewheel(e); } } haltEvent(e); return true; } return false; }
|
function dmousewheel(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousewheel(e); desktop.m.sendKeepAlive(); } else { if (desktop.m.mousewheel) { desktop.m.mousewheel(e); } } haltEvent(e); return true; } return false; }
|
||||||
function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); deskAdjust(); } }
|
function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); deskAdjust(); } }
|
||||||
function stopProcess(id, name) { setDialogMode(2, "Process Control", 3, stopProcessEx, format("Stop process #{0} \"{1}\"?", id, name), id); return false; }
|
function stopProcess(id, name) { setDialogMode(2, "Process Control", 3, stopProcessEx, format("Stop process #{0} \"{1}\"?", id, name), id); return false; }
|
||||||
|
@ -12054,6 +12106,7 @@
|
||||||
// Operating System
|
// Operating System
|
||||||
var x = '';
|
var x = '';
|
||||||
if (node.rname) { x += addDetailItem("Name", EscapeHtml(node.rname), s); }
|
if (node.rname) { x += addDetailItem("Name", EscapeHtml(node.rname), s); }
|
||||||
|
if (hardware.windows && hardware.windows.osinfo && hardware.windows.osinfo.Description) { x += addDetailItem("Description", EscapeHtml(hardware.windows.osinfo.Description), s); }
|
||||||
if (node.osdesc) { x += addDetailItem("Version", EscapeHtml(node.osdesc), s); }
|
if (node.osdesc) { x += addDetailItem("Version", EscapeHtml(node.osdesc), s); }
|
||||||
if (hardware.windows && hardware.windows.osinfo) {
|
if (hardware.windows && hardware.windows.osinfo) {
|
||||||
var m = hardware.windows.osinfo;
|
var m = hardware.windows.osinfo;
|
||||||
|
@ -12218,7 +12271,7 @@
|
||||||
}
|
}
|
||||||
if (hardware.network && hardware.network.dns) {
|
if (hardware.network && hardware.network.dns) {
|
||||||
x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
|
x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
|
||||||
x += addDetailItem("<b>DNS Servers</b>", hardware.network.dns.join(", "));
|
x += addDetailItem('<b>' + "DNS Servers" + '</b>', hardware.network.dns.join(", "));
|
||||||
x += '</div></td></tr>';
|
x += '</div></td></tr>';
|
||||||
}
|
}
|
||||||
x += '</table>';
|
x += '</table>';
|
||||||
|
@ -12878,6 +12931,25 @@
|
||||||
}, "When enabled, on each login, you will be given the option to receive a login token to you email account for added security." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled?'checked':'') + '/>' + "Enable email two-factor authentication." + '</label>');
|
}, "When enabled, on each login, you will be given the option to receive a login token to you email account for added security." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled?'checked':'') + '/>' + "Enable email two-factor authentication." + '</label>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function account_manageAuthDuo() {
|
||||||
|
if (xxdialogMode || ((features2 & 0x20000000) == 0)) return;
|
||||||
|
var duoU2Fenabled = ((userinfo.otpduo == 1));
|
||||||
|
if (duoU2Fenabled == false) {
|
||||||
|
setDialogMode(2, "Duo Authentication", 3, function () {
|
||||||
|
window.location.href = '/add-duo?rurl=' + encodeURIComponentEx(window.location.href) + ((urlargs.key)?('&key=' + urlargs.key):'');
|
||||||
|
}, "Confirm enabling of Duo 2FA login security. Once enabled you will be given the option to use Duo at login for added security. Click ok to go thru the steps to enable Duo." + '<p style="text-align: center"><img src="images/duo-2fa-250.png"></p>');
|
||||||
|
} else {
|
||||||
|
setDialogMode(2, "Duo Authentication", 3, function () {
|
||||||
|
meshserver.send({ action: 'otpduo', enabled: false });
|
||||||
|
}, '<p><label><input id=duo2facheck type=checkbox onclick=account_manageAuthDuoConfirm() />' + "Confirm disabling 2FA Duo login security." + '</label></p>' + '<p style="text-align: center"><img src="images/duo-2fa-250-disable.png"></p>');
|
||||||
|
QE('idx_dlgOkButton', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function account_manageAuthDuoConfirm() {
|
||||||
|
QE('idx_dlgOkButton', Q('duo2facheck').checked);
|
||||||
|
}
|
||||||
|
|
||||||
function account_manageAuthApp() {
|
function account_manageAuthApp() {
|
||||||
if (xxdialogMode || ((features & 4096) == 0)) return;
|
if (xxdialogMode || ((features & 4096) == 0)) return;
|
||||||
if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
|
if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
|
||||||
|
@ -13375,7 +13447,7 @@
|
||||||
|
|
||||||
function server_showRestoreDlg() {
|
function server_showRestoreDlg() {
|
||||||
if (xxdialogMode) return false;
|
if (xxdialogMode) return false;
|
||||||
var x = "Restore the server using a backup, <span style=color:red>this will delete the existing server data</span>. Only do this if you know what you are doing." + '<br /><br />';
|
var x = "Restore the server using a backup," + ' <span style=color:red>' + "this will delete the existing server data." + '</span> ' + "Only do this if you know what you are doing." + '<br /><br />';
|
||||||
x += '<form action="/restoreserver.ashx' + ((urlargs.key)?('?key=' + urlargs.key):'') + '" enctype="multipart/form-data" method="post"><div>';
|
x += '<form action="/restoreserver.ashx' + ((urlargs.key)?('?key=' + urlargs.key):'') + '" enctype="multipart/form-data" method="post"><div>';
|
||||||
x += '<input type=hidden name=auth value=' + authCookie + '>';
|
x += '<input type=hidden name=auth value=' + authCookie + '>';
|
||||||
x += '<input id=account_dlgFileInput type=file name=datafile style=width:100% accept=".zip,application/octet-stream,application/zip,application/x-zip,application/x-zip-compressed" onchange=account_validateServerRestore()><br /><br />';
|
x += '<input id=account_dlgFileInput type=file name=datafile style=width:100% accept=".zip,application/octet-stream,application/zip,application/x-zip,application/x-zip-compressed" onchange=account_validateServerRestore()><br /><br />';
|
||||||
|
@ -13807,7 +13879,7 @@
|
||||||
x += addHtmlValue("Unknown password", '<select id=dp20amtbadpass style=width:230px><option value=0>' + "Do nothing" + '</option><option value=1>' + "If in CCM, reactivate Intel® AMT" + '</option></select>');
|
x += addHtmlValue("Unknown password", '<select id=dp20amtbadpass style=width:230px><option value=0>' + "Do nothing" + '</option><option value=1>' + "If in CCM, reactivate Intel® AMT" + '</option></select>');
|
||||||
x += '</div>';
|
x += '</div>';
|
||||||
if ((features & 0x400) == 0) { x += addHtmlValue('<span title="' + "Client Initiated Remote Access" + '">' + "CIRA setup" + '</span>', '<select id=dp20amtcira style=width:230px><option value=0>' + "Do nothing" + '</option><option value=1>' + "Don't connect to server" + '</option><option value=2>' + "Connect to server" + '</option></select>'); }
|
if ((features & 0x400) == 0) { x += addHtmlValue('<span title="' + "Client Initiated Remote Access" + '">' + "CIRA setup" + '</span>', '<select id=dp20amtcira style=width:230px><option value=0>' + "Do nothing" + '</option><option value=1>' + "Don't connect to server" + '</option><option value=2>' + "Connect to server" + '</option></select>'); }
|
||||||
x += '<span id=dp10passNotify style="font-size:10px"> ' + "* 8 characters, 1 upper, 1 lower, 1 numeric, 1 non-alpha numeric." + '</span>';
|
x += '<span id=dp10passNotify style="font-size:10px"> ' + "* 8-16 characters, 1 upper, 1 lower, 1 numeric, 1 non-alpha numeric." + '</span>';
|
||||||
if ((currentMesh.mtype == 2) && (ptype == 2)) { x += '<span style="font-size:10px"> ' + "This policy will not impact devices with Intel® AMT in ACM mode." + '</span>'; }
|
if ((currentMesh.mtype == 2) && (ptype == 2)) { x += '<span style="font-size:10px"> ' + "This policy will not impact devices with Intel® AMT in ACM mode." + '</span>'; }
|
||||||
}
|
}
|
||||||
if (ptype == 0) { x = '<table style=padding-top:4px><tr><td><img style=padding-right:8px src=images/rcheckbox60.png width=60 height=60><td>' + "When this policy is selected, Intel® AMT is not managed by this server. Intel AMT can still be used by manually activating and configuring it." + '</table>'; }
|
if (ptype == 0) { x = '<table style=padding-top:4px><tr><td><img style=padding-right:8px src=images/rcheckbox60.png width=60 height=60><td>' + "When this policy is selected, Intel® AMT is not managed by this server. Intel AMT can still be used by manually activating and configuring it." + '</table>'; }
|
||||||
|
@ -15239,7 +15311,9 @@
|
||||||
156: "Verified messaging account of user {0}",
|
156: "Verified messaging account of user {0}",
|
||||||
157: "Removed messaging account of user {0}",
|
157: "Removed messaging account of user {0}",
|
||||||
158: "Displaying alert box, title=\"{0}\", message=\"{1}\"",
|
158: "Displaying alert box, title=\"{0}\", message=\"{1}\"",
|
||||||
159: "Device Powered On"
|
159: "Device Powered On",
|
||||||
|
160: "Enabled Duo two-factor authentication",
|
||||||
|
161: "Disabled Duo two-factor authentication"
|
||||||
};
|
};
|
||||||
|
|
||||||
var eventsShortMessageId = {
|
var eventsShortMessageId = {
|
||||||
|
@ -15531,7 +15605,7 @@
|
||||||
if (userdomain != '') { username += ', <span style=color:#26F>' + userdomain + '</span>'; }
|
if (userdomain != '') { username += ', <span style=color:#26F>' + userdomain + '</span>'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || ((user.phone != null) && (features & 0x04000000))) { username += ' <img src="images/key12.png" height=12 width=11 title="' + "2nd factor authentication enabled" + '" style="margin-top:2px" />'; }
|
if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || (user.otpduo == 1) || ((user.phone != null) && (features & 0x04000000))) { username += ' <img src="images/key12.png" height=12 width=11 title="' + "2nd factor authentication enabled" + '" style="margin-top:2px" />'; }
|
||||||
if (user.phone != null) { username += ' <img src="images/phone12.png" height=12 width=7 title="' + "Verified phone number" + '" style="margin-top:2px" />'; }
|
if (user.phone != null) { username += ' <img src="images/phone12.png" height=12 width=7 title="' + "Verified phone number" + '" style="margin-top:2px" />'; }
|
||||||
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" />'; }
|
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" />'; }
|
||||||
if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' <img src="images/messaging12.png" height=12 width=12 title="' + "Verified messaging account" + '" style="margin-top:2px" />'; }
|
if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' <img src="images/messaging12.png" height=12 width=12 title="' + "Verified messaging account" + '" style="margin-top:2px" />'; }
|
||||||
|
@ -15833,7 +15907,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function p4downloadUserInfoCSV() {
|
function p4downloadUserInfoCSV() {
|
||||||
var csv = "id, name, email, creation, lastlogin, groups, authfactors, siteadmin, useradmin, locked" + '\r\n';
|
var csv = "id,name,email,creation,lastlogin,groups,authfactors,siteadmin,useradmin,locked" + '\r\n';
|
||||||
for (var i in users) {
|
for (var i in users) {
|
||||||
var multiFactor = false, factors = [];
|
var multiFactor = false, factors = [];
|
||||||
if ((users[i].otpsecret > 0) || (users[i].otphkeys > 0)) {
|
if ((users[i].otpsecret > 0) || (users[i].otphkeys > 0)) {
|
||||||
|
@ -16714,12 +16788,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
var multiFactor = 0;
|
var multiFactor = 0;
|
||||||
if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpekey > 0)) {
|
if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpekey > 0) || (user.otpduo > 0)) {
|
||||||
multiFactor = 1;
|
multiFactor = 1;
|
||||||
var factors = [];
|
var factors = [];
|
||||||
if (user.otpsecret > 0) { factors.push("Authentication App"); }
|
if (user.otpsecret > 0) { factors.push("Authentication App"); }
|
||||||
if (user.otphkeys > 0) { factors.push("Security Key"); }
|
if (user.otphkeys > 0) { factors.push("Security Key"); }
|
||||||
if (user.otpekey > 0) { factors.push("Email"); }
|
if (user.otpekey > 0) { factors.push("Email"); }
|
||||||
|
if (user.otpduo > 0) { factors.push("Duo"); }
|
||||||
if (user.otpkeys > 0) { factors.push("Backup Codes"); }
|
if (user.otpkeys > 0) { factors.push("Backup Codes"); }
|
||||||
if (user.otpdev > 0) { factors.push("Device Push"); }
|
if (user.otpdev > 0) { factors.push("Device Push"); }
|
||||||
if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); }
|
if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); }
|
||||||
|
@ -19126,7 +19201,7 @@
|
||||||
function addLink(x, f) { return '<span tabindex=0 style=cursor:pointer;text-decoration:none onclick=\'' + f + '\' onkeypress="if (event.key==\'Enter\') {' + f + '} ">' + x + ' <img class=hoverButton src=images/link5.png></span>'; }
|
function addLink(x, f) { return '<span tabindex=0 style=cursor:pointer;text-decoration:none onclick=\'' + f + '\' onkeypress="if (event.key==\'Enter\') {' + f + '} ">' + x + ' <img class=hoverButton src=images/link5.png></span>'; }
|
||||||
function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
|
function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
|
||||||
function addKeyLink(x, f) { return '<span tabindex=0 style=cursor:pointer;text-decoration:none onclick=' + f + ' onkeypress="if (event.key==\'Enter\') { ' + f + ' } ">' + x + ' <img class=hoverButton src=images/key16.png></span>'; }
|
function addKeyLink(x, f) { return '<span tabindex=0 style=cursor:pointer;text-decoration:none onclick=' + f + ' onkeypress="if (event.key==\'Enter\') { ' + f + ' } ">' + x + ' <img class=hoverButton src=images/key16.png></span>'; }
|
||||||
function addKeyLinkConditional(x, f, c) { if (c) return addKeyLink(x, f); return x; }
|
function addKeyLinkConditional(x, t, c) { if (c) return '<span title=\'' + t + '\'>' + x + ' <img class=hoverButton src=images/key16.png></span>'; return x }
|
||||||
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
||||||
function addOption(q, t, i) { var option = document.createElement('option'); option.text = t; option.value = i; Q(q).add(option); }
|
function addOption(q, t, i) { var option = document.createElement('option'); option.text = t; option.value = i; Q(q).add(option); }
|
||||||
function passwordcheck(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
|
function passwordcheck(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
|
||||||
|
@ -19236,4 +19311,4 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -193,6 +193,7 @@
|
||||||
<input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken(1)" />
|
<input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken(1)" />
|
||||||
<input style="display:none;float:right" id=smsKeyButton type=button value="SMS" onclick="useSMSToken(1)" />
|
<input style="display:none;float:right" id=smsKeyButton type=button value="SMS" onclick="useSMSToken(1)" />
|
||||||
<input style="display:none;float:right" id=msgKeyButton type=button value="Messaging" onclick="useMsgToken(1)" />
|
<input style="display:none;float:right" id=msgKeyButton type=button value="Messaging" onclick="useMsgToken(1)" />
|
||||||
|
<input style="display:none;float:right" id=duoKeyButton type=button value="Duo" onclick="useDuoToken(1)" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -221,6 +222,7 @@
|
||||||
<input style="display:none;float:right" id=emailKeyButton2 type=button value="Email" onclick="useEmailToken(2)" />
|
<input style="display:none;float:right" id=emailKeyButton2 type=button value="Email" onclick="useEmailToken(2)" />
|
||||||
<input style="display:none;float:right" id=smsKeyButton2 type=button value="SMS" onclick="useSMSToken(2)" />
|
<input style="display:none;float:right" id=smsKeyButton2 type=button value="SMS" onclick="useSMSToken(2)" />
|
||||||
<input style="display:none;float:right" id=msgKeyButton2 type=button value="Messaging" onclick="useMsgToken(2)" />
|
<input style="display:none;float:right" id=msgKeyButton2 type=button value="Messaging" onclick="useMsgToken(2)" />
|
||||||
|
<input style="display:none;float:right" id=duoKeyButton2 type=button value="Duo" onclick="useDuoToken(2)" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -337,6 +339,7 @@
|
||||||
var webPageFullScreen = true;
|
var webPageFullScreen = true;
|
||||||
var nightMode = (getstore('_nightMode', '0') == '1');
|
var nightMode = (getstore('_nightMode', '0') == '1');
|
||||||
var publicKeyCredentialRequestOptions = null;
|
var publicKeyCredentialRequestOptions = null;
|
||||||
|
var otpduo = (decodeURIComponent('{{{otpduo}}}') === 'true');
|
||||||
var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
|
var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
|
||||||
var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
|
var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
|
||||||
var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
|
var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
|
||||||
|
@ -474,6 +477,7 @@
|
||||||
QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
QV('msgKeyButton', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
QV('msgKeyButton', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
|
QV('duoKeyButton', otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
|
|
||||||
// If hardware key is an option, trigger it now
|
// If hardware key is an option, trigger it now
|
||||||
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
|
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
|
||||||
|
@ -487,6 +491,7 @@
|
||||||
QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
QV('smsKeyButton2', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
QV('smsKeyButton2', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
QV('msgKeyButton2', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
QV('msgKeyButton2', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
|
QV('duoKeyButton2', otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6));
|
||||||
|
|
||||||
// If hardware key is an option, trigger it now
|
// If hardware key is an option, trigger it now
|
||||||
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
|
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
|
||||||
|
@ -617,6 +622,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useDuoToken(panelAction) {
|
||||||
|
if (panelAction == 1) {
|
||||||
|
Q('hwtokenInput').value = '**duo**';
|
||||||
|
QE('tokenOkButton', true);
|
||||||
|
Q('tokenOkButton').click();
|
||||||
|
} else if (panelAction == 2) {
|
||||||
|
Q('resetHwtokenInput').value = '**duo**';
|
||||||
|
QE('resetTokenOkButton', true);
|
||||||
|
Q('resetTokenOkButton').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showPassHint(e) {
|
function showPassHint(e) {
|
||||||
messagebox("Password Hint", passhint);
|
messagebox("Password Hint", passhint);
|
||||||
haltEvent(e);
|
haltEvent(e);
|
||||||
|
|
|
@ -223,6 +223,7 @@
|
||||||
<img id=msgKeyButton src="images/login/2fa-messaging-48.png" srcset="images/login/2fa-messaging-96.png 2x" title="Messaging" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(1)" />
|
<img id=msgKeyButton src="images/login/2fa-messaging-48.png" srcset="images/login/2fa-messaging-96.png 2x" title="Messaging" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(1)" />
|
||||||
<img id=emailKeyButton src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(1)" />
|
<img id=emailKeyButton src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(1)" />
|
||||||
<img id=pushKeyButton src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(1)" />
|
<img id=pushKeyButton src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(1)" />
|
||||||
|
<img id=duoKeyButton src="images/login/2fa-duo-48.png" srcset="images/login/2fa-duo-96.png 2x" title="Duo Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useDuoToken(1)" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -258,6 +259,7 @@
|
||||||
<img id=msgKeyButton2 src="images/login/2fa-msg-48.png" srcset="images/login/2fa-msg-96.png 2x" title="SMS" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(2)" />
|
<img id=msgKeyButton2 src="images/login/2fa-msg-48.png" srcset="images/login/2fa-msg-96.png 2x" title="SMS" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(2)" />
|
||||||
<img id=emailKeyButton2 src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(2)" />
|
<img id=emailKeyButton2 src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(2)" />
|
||||||
<img id=pushKeyButton2 src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(2)" />
|
<img id=pushKeyButton2 src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(2)" />
|
||||||
|
<img id=duoKeyButton2 src="images/login/2fa-duo-48.png" srcset="images/login/2fa-duo-96.png 2x" title="Duo Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useDuoToken(2)" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -393,6 +395,7 @@
|
||||||
var welcomeText = decodeURIComponent('{{{welcometext}}}');
|
var welcomeText = decodeURIComponent('{{{welcometext}}}');
|
||||||
var currentpanel = 0;
|
var currentpanel = 0;
|
||||||
var publicKeyCredentialRequestOptions = null;
|
var publicKeyCredentialRequestOptions = null;
|
||||||
|
var otpduo = (decodeURIComponent('{{{otpduo}}}') === 'true');
|
||||||
var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
|
var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
|
||||||
var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
|
var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
|
||||||
var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
|
var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
|
||||||
|
@ -409,7 +412,7 @@
|
||||||
var showLanguageSelect = '{{{showLanguageSelect}}}';
|
var showLanguageSelect = '{{{showLanguageSelect}}}';
|
||||||
|
|
||||||
function startup() {
|
function startup() {
|
||||||
if (decodeURIComponent('{{{loginpicture}}}') == 'true') { Q('loginPicture').src = "loginlogo.png"; }
|
if (decodeURIComponent('{{{loginpicture}}}') == 'true') { Q('loginPicture').src = 'loginlogo.png'; }
|
||||||
|
|
||||||
QV('welcomeTextRow', welcomeText != '');
|
QV('welcomeTextRow', welcomeText != '');
|
||||||
QH('welcomeText', welcomeText);
|
QH('welcomeText', welcomeText);
|
||||||
|
@ -457,7 +460,7 @@
|
||||||
// Show Language Select Box if needed
|
// Show Language Select Box if needed
|
||||||
if (showLanguageSelect === 'top' || showLanguageSelect === 'bottom') {
|
if (showLanguageSelect === 'top' || showLanguageSelect === 'bottom') {
|
||||||
var x = '<select id=d2langselect style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px" onChange="changeLanguage()">';
|
var x = '<select id=d2langselect style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px" onChange="changeLanguage()">';
|
||||||
x += '<option value="*">Use Browser Language</option>';
|
x += '<option value="*">' + "Use Browser Language" + '</option>';
|
||||||
for (var i in serverLangs) {
|
for (var i in serverLangs) {
|
||||||
var lang = serverLangs[i];
|
var lang = serverLangs[i];
|
||||||
x += '<option value="' + lang + '"' + ((urlargs.lang == lang)?' selected':'') + '>' + lang + ' - ' + (loclist[lang]?loclist[lang]:loclistex[lang]) + '</option>';
|
x += '<option value="' + lang + '"' + ((urlargs.lang == lang)?' selected':'') + '>' + lang + ' - ' + (loclist[lang]?loclist[lang]:loclistex[lang]) + '</option>';
|
||||||
|
@ -547,12 +550,14 @@
|
||||||
var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
|
var duokey = otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
QV('securityKeyButton', twofakey);
|
QV('securityKeyButton', twofakey);
|
||||||
QV('emailKeyButton', emailkey);
|
QV('emailKeyButton', emailkey);
|
||||||
QV('smsKeyButton', smskey);
|
QV('smsKeyButton', smskey);
|
||||||
QV('msgKeyButton', msgkey);
|
QV('msgKeyButton', msgkey);
|
||||||
QV('pushKeyButton', pushkey);
|
QV('pushKeyButton', pushkey);
|
||||||
QV('2farow', twofakey || emailkey || smskey || msgkey || pushkey);
|
QV('duoKeyButton', duokey);
|
||||||
|
QV('2farow', twofakey || emailkey || smskey || msgkey || pushkey || duokey);
|
||||||
|
|
||||||
// If hardware key is an option, trigger it now
|
// If hardware key is an option, trigger it now
|
||||||
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
|
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
|
||||||
|
@ -566,12 +571,14 @@
|
||||||
var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
|
var duokey = otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6);
|
||||||
QV('securityKeyButton2', twofakey);
|
QV('securityKeyButton2', twofakey);
|
||||||
QV('emailKeyButton2', emailkey);
|
QV('emailKeyButton2', emailkey);
|
||||||
QV('smsKeyButton2', smskey);
|
QV('smsKeyButton2', smskey);
|
||||||
QV('msgKeyButton2', msgkey);
|
QV('msgKeyButton2', msgkey);
|
||||||
QV('pushKeyButton', pushkey);
|
QV('pushKeyButton2', pushkey);
|
||||||
QV('2farow2', twofakey || emailkey || smskey || msgkey || pushkey);
|
QV('duoKeyButton2', duokey);
|
||||||
|
QV('2farow2', twofakey || emailkey || smskey || msgkey || pushkey || duokey);
|
||||||
|
|
||||||
// If hardware key is an option, trigger it now
|
// If hardware key is an option, trigger it now
|
||||||
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
|
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
|
||||||
|
@ -723,6 +730,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useDuoToken(panelAction) {
|
||||||
|
if (panelAction == 1) {
|
||||||
|
Q('hwtokenInput').value = '**duo**';
|
||||||
|
QE('tokenOkButton', true);
|
||||||
|
Q('tokenOkButton').click();
|
||||||
|
} else if (panelAction == 2) {
|
||||||
|
Q('resetHwtokenInput').value = '**duo**';
|
||||||
|
QE('resetTokenOkButton', true);
|
||||||
|
Q('resetTokenOkButton').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showPassHint(e) {
|
function showPassHint(e) {
|
||||||
messagebox("Password Hint", passhint);
|
messagebox("Password Hint", passhint);
|
||||||
haltEvent(e);
|
haltEvent(e);
|
||||||
|
|
|
@ -85,7 +85,6 @@
|
||||||
var remoteUserId = '{{{userid}}}';
|
var remoteUserId = '{{{userid}}}';
|
||||||
var webrtcconfiguration = '{{{webrtcconfig}}}';
|
var webrtcconfiguration = '{{{webrtcconfig}}}';
|
||||||
if (webrtcconfiguration == '') { webrtcconfiguration = null; } else { try { webrtcconfiguration = JSON.parse(decodeURIComponent(webrtcconfiguration)); } catch (ex) { console.log('Invalid WebRTC config: "' + webrtcconfiguration + '".'); webrtcconfiguration = null; } }
|
if (webrtcconfiguration == '') { webrtcconfiguration = null; } else { try { webrtcconfiguration = JSON.parse(decodeURIComponent(webrtcconfiguration)); } catch (ex) { console.log('Invalid WebRTC config: "' + webrtcconfiguration + '".'); webrtcconfiguration = null; } }
|
||||||
var windowFocus = true;
|
|
||||||
var chatTextSession = new Date().toString() + '\r\n';
|
var chatTextSession = new Date().toString() + '\r\n';
|
||||||
var localOutText = false;
|
var localOutText = false;
|
||||||
var remoteOutText = false;
|
var remoteOutText = false;
|
||||||
|
@ -124,10 +123,6 @@
|
||||||
// Setup web notifications
|
// Setup web notifications
|
||||||
try { if (Notification) { QV('notifyButton', Notification.permission != 'granted'); } } catch (ex) { notificationSupport = false; }
|
try { if (Notification) { QV('notifyButton', Notification.permission != 'granted'); } } catch (ex) { notificationSupport = false; }
|
||||||
|
|
||||||
// Track window focus
|
|
||||||
window.addEventListener('focus', function (event) { windowFocus = true; }, false);
|
|
||||||
window.addEventListener('blur', function (event) { windowFocus = false; }, false);
|
|
||||||
|
|
||||||
// Listen to drag & drop events
|
// Listen to drag & drop events
|
||||||
document.addEventListener('dragover', haltEvent, false);
|
document.addEventListener('dragover', haltEvent, false);
|
||||||
document.addEventListener('dragleave', haltEvent, false);
|
document.addEventListener('dragleave', haltEvent, false);
|
||||||
|
@ -237,9 +232,14 @@
|
||||||
// If web notifications are granted, use it.
|
// If web notifications are granted, use it.
|
||||||
if (notificationSupport) {
|
if (notificationSupport) {
|
||||||
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
||||||
if (Notification && (windowFocus == false) && (Notification.permission == 'granted')) {
|
if (Notification && (document.visibilityState === 'hidden') && (Notification.permission == 'granted')) {
|
||||||
if (notification != null) { notification.close(); notification = null; }
|
if (notification != null) { notification.close(); notification = null; }
|
||||||
notification = new Notification(Q('xtitle').innerHTML.split(' ').join(' '), { body: msg });
|
notification = new Notification(Q('xtitle').innerHTML.split(' ').join(' '), { body: msg });
|
||||||
|
notification.onclick = function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (document.visibilityState === 'hidden') window.focus();
|
||||||
|
notification.close();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -448,7 +448,7 @@
|
||||||
agentDesktop.onPreDrawImage = preCanvasDraw;
|
agentDesktop.onPreDrawImage = preCanvasDraw;
|
||||||
agentDesktop.State = 3;
|
agentDesktop.State = 3;
|
||||||
deskAdjust();
|
deskAdjust();
|
||||||
QV('ConvertAsWebM', browser == 'chrome'); // Only show the "Convert to WebM button when in Chrome
|
QV('ConvertAsWebM', true);
|
||||||
}
|
}
|
||||||
else if (recFileMetadata.protocol == 101) {
|
else if (recFileMetadata.protocol == 101) {
|
||||||
// Intel AMT Redirection
|
// Intel AMT Redirection
|
||||||
|
@ -488,7 +488,7 @@
|
||||||
if (recFileMetadata.lowcolor) { amtDesktop.lowcolor = recFileMetadata.lowcolor; }
|
if (recFileMetadata.lowcolor) { amtDesktop.lowcolor = recFileMetadata.lowcolor; }
|
||||||
amtDesktop.state = 3;
|
amtDesktop.state = 3;
|
||||||
deskAdjust();
|
deskAdjust();
|
||||||
QV('ConvertAsWebM', browser == 'chrome'); // Only show the "Convert to WebM button when in Chrome
|
QV('ConvertAsWebM', true);
|
||||||
}
|
}
|
||||||
QV('metadatadiv', true);
|
QV('metadatadiv', true);
|
||||||
QH('metadatadiv', x);
|
QH('metadatadiv', x);
|
||||||
|
@ -1097,7 +1097,6 @@
|
||||||
if (term != null) { term.dispose(); }
|
if (term != null) { term.dispose(); }
|
||||||
term = new Terminal();
|
term = new Terminal();
|
||||||
term.open(Q('XTermParent'));
|
term.open(Q('XTermParent'));
|
||||||
term.onData(function (data) { if (tunnel != null) { tunnel.sendText(data); } })
|
|
||||||
term.resize(80, 25);
|
term.resize(80, 25);
|
||||||
//term.setOption('convertEol', true); // Consider \n to be \r\n, this should be taken care of by "termios"
|
//term.setOption('convertEol', true); // Consider \n to be \r\n, this should be taken care of by "termios"
|
||||||
}
|
}
|
||||||
|
|
|
@ -820,7 +820,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSessionActivity() { sessionActivity = Date.now(); }
|
function setSessionActivity() { sessionActivity = Date.now(); }
|
||||||
function checkIdleSessionTimeout() { var delta = (Date.now() - sessionActivity); if (delta > serverinfo.timeout) { window.location.href = 'logout'; } }
|
function checkIdleSessionTimeout() {
|
||||||
|
var delta = (Date.now() - sessionActivity);
|
||||||
|
if (delta > serverinfo.timeout && serverinfo.logoutOnIdleSessionTimeout) {
|
||||||
|
window.location.href = 'logout';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Menu System
|
// Menu System
|
||||||
|
|
272
webserver.js
272
webserver.js
|
@ -918,7 +918,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null));
|
var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null));
|
||||||
|
|
||||||
// Check if a 2nd factor is present
|
// Check if a 2nd factor is present
|
||||||
return ((parent.config.settings.no2factorauth !== true) && (msg2fa || sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
|
return ((parent.config.settings.no2factorauth !== true) && (msg2fa || sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) || (user.otpduo != null) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the 2-step auth token
|
// Check the 2-step auth token
|
||||||
|
@ -1162,6 +1162,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
|
var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
|
||||||
var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null));
|
var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null));
|
||||||
var push2fa = ((parent.firebase != null) && (user.otpdev != null));
|
var push2fa = ((parent.firebase != null) && (user.otpdev != null));
|
||||||
|
var duo2fa = ((((typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) || ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor != false))) && (user.otpduo != null));
|
||||||
|
|
||||||
// Check if two factor can be skipped
|
// Check if two factor can be skipped
|
||||||
const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
|
const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
|
||||||
|
@ -1211,6 +1212,24 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((req.body.hwtoken == '**duo**') && duo2fa && (typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) {
|
||||||
|
// Redirect to duo here
|
||||||
|
const duo = require('@duosecurity/duo_universal');
|
||||||
|
const client = new duo.Client({
|
||||||
|
clientId: domain.duo2factor.integrationkey,
|
||||||
|
clientSecret: domain.duo2factor.secretkey,
|
||||||
|
apiHost: domain.duo2factor.apihostname,
|
||||||
|
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
|
||||||
|
});
|
||||||
|
// Decrypt any session data
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
sec.duostate = client.generateState();
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
parent.debug('web', 'Redirecting user ' + user._id + ' to Duo');
|
||||||
|
res.redirect(client.createAuthUrl(user._id.split('/')[2], sec.duostate));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle device push notification 2FA request
|
// Handle device push notification 2FA request
|
||||||
// We create a browser cookie, send it back and when the browser connects it's web socket, it will trigger the push notification.
|
// We create a browser cookie, send it back and when the browser connects it's web socket, it will trigger the push notification.
|
||||||
if ((req.body.hwtoken == '**push**') && push2fa && ((domain.passwordrequirements == null) || (domain.passwordrequirements.push2factor != false))) {
|
if ((req.body.hwtoken == '**push**') && push2fa && ((domain.passwordrequirements == null) || (domain.passwordrequirements.push2factor != false))) {
|
||||||
|
@ -1274,6 +1293,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
if ((user.phone != null) && (parent.smsserver != null)) { req.session.tsms = 1; }
|
if ((user.phone != null) && (parent.smsserver != null)) { req.session.tsms = 1; }
|
||||||
if ((user.msghandle != null) && (parent.msgserver != null) && (parent.msgserver.providers != 0)) { req.session.tmsg = 1; }
|
if ((user.msghandle != null) && (parent.msgserver != null) && (parent.msgserver.providers != 0)) { req.session.tmsg = 1; }
|
||||||
if ((user.otpdev != null) && (parent.firebase != null)) { req.session.tpush = 1; }
|
if ((user.otpdev != null) && (parent.firebase != null)) { req.session.tpush = 1; }
|
||||||
|
if ((user.otpduo != null)) { req.session.tduo = 1; }
|
||||||
req.session.e = parent.encryptSessionData({ tuserid: userid, tuser: xusername, tpass: xpassword });
|
req.session.e = parent.encryptSessionData({ tuserid: userid, tuser: xusername, tpass: xpassword });
|
||||||
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
|
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
|
||||||
}, randomWaitTime);
|
}, randomWaitTime);
|
||||||
|
@ -2817,6 +2837,38 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
res.set('Content-Type', 'text/html');
|
res.set('Content-Type', 'text/html');
|
||||||
let url = domain.url;
|
let url = domain.url;
|
||||||
if (Object.keys(req.query).length > 0) { url += "?" + Object.keys(req.query).map(function(key) { return encodeURIComponent(key) + "=" + encodeURIComponent(req.query[key]); }).join("&"); }
|
if (Object.keys(req.query).length > 0) { url += "?" + Object.keys(req.query).map(function(key) { return encodeURIComponent(key) + "=" + encodeURIComponent(req.query[key]); }).join("&"); }
|
||||||
|
|
||||||
|
// check for relaystate is set, test against configured server name and accepted query params
|
||||||
|
if(req.body && req.body.RelayState !== undefined){
|
||||||
|
var relayState = decodeURIComponent(req.body.RelayState);
|
||||||
|
var serverName = (obj.getWebServerName(domain, req)).replaceAll('.','\\.');
|
||||||
|
|
||||||
|
var regexstr = `(?<=https:\\/\\/(?:.+?\\.)?${serverName}\\/?)` +
|
||||||
|
`.*((?<=([\\?&])gotodevicename=(.{64})|` +
|
||||||
|
`gotonode=(.{64})|` +
|
||||||
|
`gotodeviceip=(((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4})|` +
|
||||||
|
`gotodeviceip=(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:)` +
|
||||||
|
`lang=(.{5})|` +
|
||||||
|
`sitestyle=(\\d+)|` +
|
||||||
|
`user=(.{64})|` +
|
||||||
|
`pass=(.{256})|` +
|
||||||
|
`key=|` +
|
||||||
|
`locale=|` +
|
||||||
|
`gotomesh=(.{64})|` +
|
||||||
|
`gotouser=(.{0,64})|` +
|
||||||
|
`gotougrp=(.{64})|` +
|
||||||
|
`debug=|` +
|
||||||
|
`filter=|` +
|
||||||
|
`webrtc=|` +
|
||||||
|
`hide=|` +
|
||||||
|
`viewmode=(\\d+)(?=[\\&]|\\b)))`;
|
||||||
|
|
||||||
|
var regex = new RegExp(regexstr);
|
||||||
|
if(regex.test(relayState)){
|
||||||
|
url = relayState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
|
res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3096,12 +3148,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
// Fetch the web state
|
// Fetch the web state
|
||||||
parent.debug('web', 'handleRootRequestEx: success.');
|
parent.debug('web', 'handleRootRequestEx: success.');
|
||||||
|
|
||||||
var webstate = '';
|
var webstate = '{}';
|
||||||
if ((err == null) && (states != null) && (Array.isArray(states)) && (states.length == 1) && (states[0].state != null)) { webstate = obj.filterUserWebState(states[0].state); }
|
if ((err == null) && (states != null) && (Array.isArray(states)) && (states.length == 1) && (states[0].state != null)) { webstate = obj.filterUserWebState(states[0].state); }
|
||||||
if ((webstate == '') && (typeof domain.defaultuserwebstate == 'object')) { webstate = JSON.stringify(domain.defaultuserwebstate); } // User has no web state, use defaults.
|
if ((webstate == '{}') && (typeof domain.defaultuserwebstate == 'object')) { webstate = JSON.stringify(domain.defaultuserwebstate); } // User has no web state, use defaults.
|
||||||
if (typeof domain.forceduserwebstate == 'object') { // Forces initial user web state if present, use it.
|
if (typeof domain.forceduserwebstate == 'object') { // Forces initial user web state if present, use it.
|
||||||
var webstate2 = {};
|
var webstate2 = {};
|
||||||
try { if (webstate != '') { webstate2 = JSON.parse(webstate); } } catch (ex) { }
|
try { if (webstate != '{}') { webstate2 = JSON.parse(webstate); } } catch (ex) { }
|
||||||
for (var i in domain.forceduserwebstate) { webstate2[i] = domain.forceduserwebstate[i]; }
|
for (var i in domain.forceduserwebstate) { webstate2[i] = domain.forceduserwebstate[i]; }
|
||||||
webstate = JSON.stringify(webstate2);
|
webstate = JSON.stringify(webstate2);
|
||||||
}
|
}
|
||||||
|
@ -3132,8 +3184,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
if (obj.parent.config.settings && obj.parent.config.settings.webrtcconfig && (typeof obj.parent.config.settings.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(obj.parent.config.settings.webrtcconfig)).replace(/'/g, '%27'); }
|
if (obj.parent.config.settings && obj.parent.config.settings.webrtcconfig && (typeof obj.parent.config.settings.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(obj.parent.config.settings.webrtcconfig)).replace(/'/g, '%27'); }
|
||||||
else if (args.webrtcconfig && (typeof args.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtcconfig)).replace(/'/g, '%27'); }
|
else if (args.webrtcconfig && (typeof args.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtcconfig)).replace(/'/g, '%27'); }
|
||||||
|
|
||||||
|
// Load default page style or new modern ui
|
||||||
|
var uiViewMode = 'default';
|
||||||
|
var webstateJSON = JSON.parse(webstate);
|
||||||
|
if (webstateJSON && webstateJSON.uiViewMode == 3) { uiViewMode = 'default3'; }
|
||||||
|
if (domain.sitestyle == 3) { uiViewMode = 'default3'; }
|
||||||
|
if (req.query.sitestyle == 3) { uiViewMode = 'default3'; }
|
||||||
// Refresh the session
|
// Refresh the session
|
||||||
render(dbGetFunc.req, dbGetFunc.res, getRenderPage(((domain.sitestyle == 3) || (req.query.sitestyle == 3) ? 'default3' : 'default'), dbGetFunc.req, domain), getRenderArgs({
|
render(dbGetFunc.req, dbGetFunc.res, getRenderPage(uiViewMode, dbGetFunc.req, domain), getRenderArgs({
|
||||||
authCookie: authCookie,
|
authCookie: authCookie,
|
||||||
authRelayCookie: authRelayCookie,
|
authRelayCookie: authRelayCookie,
|
||||||
viewmode: viewmode,
|
viewmode: viewmode,
|
||||||
|
@ -3282,6 +3340,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
if ((parent.msgserver != null) && (parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false))) { features2 += 0x04000000; } // User messaging 2FA is allowed
|
if ((parent.msgserver != null) && (parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false))) { features2 += 0x04000000; } // User messaging 2FA is allowed
|
||||||
if (domain.scrolltotop == true) { features2 += 0x08000000; } // Show the "Scroll to top" button
|
if (domain.scrolltotop == true) { features2 += 0x08000000; } // Show the "Scroll to top" button
|
||||||
if (domain.devicesearchbargroupname === true) { features2 += 0x10000000; } // Search bar will find by group name too
|
if (domain.devicesearchbargroupname === true) { features2 += 0x10000000; } // Search bar will find by group name too
|
||||||
|
if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.duo2factor != false)) && (typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) { features2 += 0x20000000; } // using Duo for 2FA is allowed
|
||||||
|
if (domain.showmodernuitoggle == true) { features2 += 0x40000000; } // Indicates that the new UI should be shown
|
||||||
return { features: features, features2: features2 };
|
return { features: features, features2: features2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3320,6 +3380,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
// Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5).
|
// Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5).
|
||||||
var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string'));
|
var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string'));
|
||||||
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
|
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
|
||||||
|
var otpduo = (req.session != null) && (req.session.tduo === 1);
|
||||||
|
if (((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor == false)) || (typeof domain.duo2factor != 'object')) { otpduo = false; }
|
||||||
var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1);
|
var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1);
|
||||||
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
|
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
|
||||||
var otpmsg = (parent.msgserver != null) && (req.session != null) && (req.session.tmsg === 1);
|
var otpmsg = (parent.msgserver != null) && (req.session != null) && (req.session.tmsg === 1);
|
||||||
|
@ -3402,6 +3464,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
welcomePictureFullScreen: ((typeof domain.welcomepicturefullscreen == 'boolean') ? domain.welcomepicturefullscreen : false),
|
welcomePictureFullScreen: ((typeof domain.welcomepicturefullscreen == 'boolean') ? domain.welcomepicturefullscreen : false),
|
||||||
hwstate: hwstate,
|
hwstate: hwstate,
|
||||||
otpemail: otpemail,
|
otpemail: otpemail,
|
||||||
|
otpduo: otpduo,
|
||||||
otpsms: otpsms,
|
otpsms: otpsms,
|
||||||
otpmsg: otpmsg,
|
otpmsg: otpmsg,
|
||||||
otppush: otppush,
|
otppush: otppush,
|
||||||
|
@ -3737,7 +3800,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
"scope": ".",
|
"scope": ".",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "fullscreen",
|
"display": "fullscreen",
|
||||||
"orientation": "portrait",
|
"orientation": "any",
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"icons": [{
|
"icons": [{
|
||||||
|
@ -4966,7 +5029,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
|
if (req.query.tls1only == 1) { tlsoptions.secureProtocol = 'TLSv1_method'; }
|
||||||
ws.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
|
ws.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
|
||||||
// The TLS connection method is the same as TCP, but located a bit differently.
|
// The TLS connection method is the same as TCP, but located a bit differently.
|
||||||
parent.debug('webrelay', 'TLS connected to ' + node.host + ':' + port + '.');
|
parent.debug('webrelay', user.name + ' - TLS connected to ' + node.host + ':' + port + '.');
|
||||||
ws.forwardclient.xstate = 1;
|
ws.forwardclient.xstate = 1;
|
||||||
ws._socket.resume();
|
ws._socket.resume();
|
||||||
});
|
});
|
||||||
|
@ -4979,7 +5042,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
ws.forwardclient.on('data', function (data) {
|
ws.forwardclient.on('data', function (data) {
|
||||||
if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
|
if (typeof data == 'string') { data = Buffer.from(data, 'binary'); }
|
||||||
if (obj.parent.debugLevel >= 1) { // DEBUG
|
if (obj.parent.debugLevel >= 1) { // DEBUG
|
||||||
parent.debug('webrelaydata', 'TCP relay data from ' + node.host + ', ' + data.length + ' bytes.');
|
parent.debug('webrelaydata', user.name + ' - TCP relay data from ' + node.host + ', ' + data.length + ' bytes.');
|
||||||
//if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
|
//if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
|
||||||
}
|
}
|
||||||
if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
|
if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
|
||||||
|
@ -4994,13 +5057,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
|
|
||||||
// If the TCP connection closes, disconnect the associated web socket.
|
// If the TCP connection closes, disconnect the associated web socket.
|
||||||
ws.forwardclient.on('close', function () {
|
ws.forwardclient.on('close', function () {
|
||||||
parent.debug('webrelay', 'TCP relay disconnected from ' + node.host + ':' + port + '.');
|
parent.debug('webrelay', user.name + ' - TCP relay disconnected from ' + node.host + ':' + port + '.');
|
||||||
try { ws.close(); } catch (e) { }
|
try { ws.close(); } catch (e) { }
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the TCP connection causes an error, disconnect the associated web socket.
|
// If the TCP connection causes an error, disconnect the associated web socket.
|
||||||
ws.forwardclient.on('error', function (err) {
|
ws.forwardclient.on('error', function (err) {
|
||||||
parent.debug('webrelay', 'TCP relay error from ' + node.host + ':' + port + ': ' + err);
|
parent.debug('webrelay', user.name + ' - TCP relay error from ' + node.host + ':' + port + ': ' + err);
|
||||||
try { ws.close(); } catch (e) { }
|
try { ws.close(); } catch (e) { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5011,7 +5074,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
if (node.intelamt.tls == 0) {
|
if (node.intelamt.tls == 0) {
|
||||||
// A TCP connection to Intel AMT just connected, start forwarding.
|
// A TCP connection to Intel AMT just connected, start forwarding.
|
||||||
ws.forwardclient.connect(port, node.host, function () {
|
ws.forwardclient.connect(port, node.host, function () {
|
||||||
parent.debug('webrelay', 'TCP relay connected to ' + node.host + ':' + port + '.');
|
parent.debug('webrelay', user.name + ' - TCP relay connected to ' + node.host + ':' + port + '.');
|
||||||
ws.forwardclient.xstate = 1;
|
ws.forwardclient.xstate = 1;
|
||||||
ws._socket.resume();
|
ws._socket.resume();
|
||||||
});
|
});
|
||||||
|
@ -5851,6 +5914,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// generate the server url
|
||||||
|
obj.generateBaseURL = function (domain, req) {
|
||||||
|
var serverName = obj.getWebServerName(domain, req);
|
||||||
|
var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
|
||||||
|
var xdomain = (domain.dns == null) ? domain.id : '';
|
||||||
|
if (xdomain != '') xdomain += '/';
|
||||||
|
return ('https://' + serverName + ':' + httpsPort + '/' + xdomain);
|
||||||
|
}
|
||||||
|
|
||||||
// Get the web server hostname. This may change if using a domain with a DNS name.
|
// Get the web server hostname. This may change if using a domain with a DNS name.
|
||||||
obj.getWebServerName = function (domain, req) {
|
obj.getWebServerName = function (domain, req) {
|
||||||
if (domain.dns != null) return domain.dns;
|
if (domain.dns != null) return domain.dns;
|
||||||
|
@ -6469,7 +6541,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
'Referrer-Policy': 'no-referrer',
|
'Referrer-Policy': 'no-referrer',
|
||||||
'X-XSS-Protection': '1; mode=block',
|
'X-XSS-Protection': '1; mode=block',
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'X-Content-Type-Options': 'nosniff',
|
||||||
'Content-Security-Policy': "default-src 'none'; font-src 'self'; script-src 'self' 'unsafe-inline'" + extraScriptSrc + "; connect-src 'self'" + geourl + selfurl + "; img-src 'self' blob: data:" + geourl + " data:; style-src 'self' 'unsafe-inline'; frame-src 'self' blob: mcrouter:" + extraFrameSrc + "; media-src 'self'; form-action 'self'; manifest-src 'self'"
|
'Content-Security-Policy': "default-src 'none'; font-src 'self' fonts.gstatic.com data:; script-src 'self' 'unsafe-inline' " + extraScriptSrc + "; connect-src 'self'" + geourl + selfurl + "; img-src 'self' blob: data:" + geourl + " data:; style-src 'self' 'unsafe-inline' fonts.googleapis.com; frame-src 'self' blob: mcrouter:" + extraFrameSrc + "; media-src 'self'; form-action 'self'; manifest-src 'self'"
|
||||||
};
|
};
|
||||||
if (req.headers['user-agent'] && (req.headers['user-agent'].indexOf('Chrome') >= 0)) { headers['Permissions-Policy'] = 'interest-cohort=()'; } // Remove Google's FLoC Network, only send this if Chrome browser
|
if (req.headers['user-agent'] && (req.headers['user-agent'].indexOf('Chrome') >= 0)) { headers['Permissions-Policy'] = 'interest-cohort=()'; } // Remove Google's FLoC Network, only send this if Chrome browser
|
||||||
if ((parent.config.settings.allowframing !== true) && (typeof parent.config.settings.allowframing !== 'string')) { headers['X-Frame-Options'] = 'sameorigin'; }
|
if ((parent.config.settings.allowframing !== true) && (typeof parent.config.settings.allowframing !== 'string')) { headers['X-Frame-Options'] = 'sameorigin'; }
|
||||||
|
@ -6880,6 +6952,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
obj.app.get(url + 'auth-saml', function (req, res, next) {
|
obj.app.get(url + 'auth-saml', function (req, res, next) {
|
||||||
var domain = getDomain(req);
|
var domain = getDomain(req);
|
||||||
if (domain.passport == null) { next(); return; }
|
if (domain.passport == null) { next(); return; }
|
||||||
|
//set RelayState when queries are passed
|
||||||
|
if (Object.keys(req.query).length != 0){
|
||||||
|
req.query.RelayState = encodeURIComponent(`${req.protocol}://${req.hostname}${req.originalUrl}`.replace('auth-saml/',''))
|
||||||
|
}
|
||||||
domain.passport.authenticate('saml-' + domain.id, { failureRedirect: domain.url, failureFlash: true })(req, res, next);
|
domain.passport.authenticate('saml-' + domain.id, { failureRedirect: domain.url, failureFlash: true })(req, res, next);
|
||||||
});
|
});
|
||||||
obj.app.post(url + 'auth-saml-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
|
obj.app.post(url + 'auth-saml-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
|
||||||
|
@ -6918,6 +6994,139 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup Duo HTTP handlers if supported
|
||||||
|
if ((typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) {
|
||||||
|
// Duo authentication handler
|
||||||
|
obj.app.get(url + 'auth-duo', function (req, res){
|
||||||
|
var domain = getDomain(req);
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
if ((req.query.state !== sec.duostate) || (req.query.duo_code == null)) {
|
||||||
|
// The state returned from Duo is not the same as what was in the session, so must fail
|
||||||
|
parent.debug('web', 'handleRootRequest: Duo 2FA state failed.');
|
||||||
|
req.session.loginmode = 1;
|
||||||
|
req.session.messageid = 117; // Invalid security check
|
||||||
|
res.redirect(domain.url + getQueryPortion(req)); // redirect back to main page
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// User credentials are stored in session, just check again and get userid
|
||||||
|
obj.authenticate(sec.tuser, sec.tpass, domain, function (err, userid, passhint, loginOptions) {
|
||||||
|
if ((userid != null) && (err == null)) {
|
||||||
|
// Login data correct, now exchange authorization code for 2FA
|
||||||
|
const duo = require('@duosecurity/duo_universal');
|
||||||
|
const client = new duo.Client({
|
||||||
|
clientId: domain.duo2factor.integrationkey,
|
||||||
|
clientSecret: domain.duo2factor.secretkey,
|
||||||
|
apiHost: domain.duo2factor.apihostname,
|
||||||
|
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
|
||||||
|
});
|
||||||
|
client.exchangeAuthorizationCodeFor2FAResult(req.query.duo_code, userid.split('/')[2]).then(function (data) {
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
if ((sec != null) && (sec.duoconfig == 1)) {
|
||||||
|
// Duo 2FA exchange success
|
||||||
|
parent.debug('web', 'handleRootRequest: Duo 2FA configuration success.');
|
||||||
|
|
||||||
|
// Enable Duo for this user
|
||||||
|
var user = obj.users[userid];
|
||||||
|
if (user.otpduo == null) {
|
||||||
|
user.otpduo = {};
|
||||||
|
db.SetUser(user);
|
||||||
|
|
||||||
|
// Notify change
|
||||||
|
var targets = ['*', 'server-users', user._id];
|
||||||
|
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
|
||||||
|
var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msgid: 160, msg: "Enabled duo two-factor authentication.", domain: domain.id };
|
||||||
|
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.
|
||||||
|
parent.DispatchEvent(targets, obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
delete sec.duoconfig;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
var url = req.session.duorurl;
|
||||||
|
delete req.session.duorurl;
|
||||||
|
res.redirect(url ? url : domain.url); // Redirect back to the user's original page
|
||||||
|
} else {
|
||||||
|
// Duo 2FA exchange success
|
||||||
|
parent.debug('web', 'handleRootRequest: Duo 2FA authorization success.');
|
||||||
|
req.session.userid = userid;
|
||||||
|
delete req.session.currentNode;
|
||||||
|
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
|
||||||
|
setSessionRandom(req);
|
||||||
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
obj.parent.authLog('https', 'Accepted Duo authentication for ' + userid + ' from ' + req.clientIp + ':' + req.connection.remotePort, { useragent: req.headers['user-agent'], sessionid: req.session.x });
|
||||||
|
res.redirect(domain.url + getQueryPortion(req));
|
||||||
|
}
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.log('err', err);
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
if ((sec != null) && (sec.duoconfig == 1)) {
|
||||||
|
// Duo 2FA exchange success
|
||||||
|
parent.debug('web', 'handleRootRequest: Duo 2FA configuration failed.');
|
||||||
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
delete sec.duoconfig;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
var url = req.session.duorurl;
|
||||||
|
delete req.session.duorurl;
|
||||||
|
res.redirect(url ? url : domain.url); // Redirect back to the user's original page
|
||||||
|
} else {
|
||||||
|
// Duo 2FA exchange failed
|
||||||
|
parent.debug('web', 'handleRootRequest: Duo 2FA authorization failed.');
|
||||||
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
req.session.loginmode = 1;
|
||||||
|
req.session.messageid = 117; // Invalid security check
|
||||||
|
res.redirect(domain.url + getQueryPortion(req));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Login failed
|
||||||
|
handleRootRequestEx(req, res, domain, direct);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure Duo handler
|
||||||
|
obj.app.get(url + 'add-duo', function (req, res) {
|
||||||
|
var domain = getDomain(req);
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
|
||||||
|
if (req.session.userid == null) {
|
||||||
|
res.sendStatus(404);
|
||||||
|
} else {
|
||||||
|
// Redirect to Duo here
|
||||||
|
const duo = require('@duosecurity/duo_universal');
|
||||||
|
const client = new duo.Client({
|
||||||
|
clientId: domain.duo2factor.integrationkey,
|
||||||
|
clientSecret: domain.duo2factor.secretkey,
|
||||||
|
apiHost: domain.duo2factor.apihostname,
|
||||||
|
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('&key=' + domain.loginkey) : '')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup the Duo configuration
|
||||||
|
if (req.query.rurl) { req.session.duorurl = req.query.rurl; } // Set Duo return URL
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
sec.duostate = client.generateState();
|
||||||
|
sec.duoconfig = 1;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
parent.debug('web', 'Redirecting user ' + req.session.userid + ' to Duo for configuration');
|
||||||
|
res.redirect(client.createAuthUrl(req.session.userid.split('/')[2], sec.duostate));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Server redirects
|
// Server redirects
|
||||||
if (parent.config.domains[i].redirects) { for (var j in parent.config.domains[i].redirects) { if (j[0] != '_') { obj.app.get(url + j, obj.handleDomainRedirect); } } }
|
if (parent.config.domains[i].redirects) { for (var j in parent.config.domains[i].redirects) { if (j[0] != '_') { obj.app.get(url + j, obj.handleDomainRedirect); } } }
|
||||||
|
|
||||||
|
@ -7638,7 +7847,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
|
|
||||||
async function getGroups(preset, tokenset) {
|
async function getGroups(preset, tokenset) {
|
||||||
let url = '';
|
let url = '';
|
||||||
if (preset == 'azure') { url = strategy.groups.recursive == true ? 'https://graph.microsoft.com/v1.0/me/transitiveMemberOf' : 'https://graph.microsoft.com/v1.0/me/memberOf'; }
|
if (preset == 'azure') { url = strategy.groups.recursive == true ? 'https://graph.microsoft.com/v1.0/me/transitiveMemberOf?$top=999' : 'https://graph.microsoft.com/v1.0/me/memberOf?$top=999'; }
|
||||||
if (preset == 'google') { url = strategy.custom.customer_id ? 'https://cloudidentity.googleapis.com/v1/groups?parent=customers/' + strategy.custom.customer_id : strategy.custom.identitysource ? 'https://cloudidentity.googleapis.com/v1/groups?parent=identitysources/' + strategy.custom.identitysource : null; }
|
if (preset == 'google') { url = strategy.custom.customer_id ? 'https://cloudidentity.googleapis.com/v1/groups?parent=customers/' + strategy.custom.customer_id : strategy.custom.identitysource ? 'https://cloudidentity.googleapis.com/v1/groups?parent=identitysources/' + strategy.custom.identitysource : null; }
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -8036,6 +8245,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
parent.debug('web', 'Invalid login, asking for email validation');
|
parent.debug('web', 'Invalid login, asking for email validation');
|
||||||
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, msg2fa: msg2fa, email2fasent: true })); ws.close(); } catch (e) { }
|
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, msg2fa: msg2fa, email2fasent: true })); ws.close(); } catch (e) { }
|
||||||
} else {
|
} else {
|
||||||
|
req.session.userid = user._id;
|
||||||
|
req.session.ip = req.clientIp;
|
||||||
|
setSessionRandom(req);
|
||||||
func(ws, req, domain, user, null, authData);
|
func(ws, req, domain, user, null, authData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8051,6 +8263,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, msg2fa: msg2fa, email2fasent: true })); ws.close(); } catch (e) { }
|
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, msg2fa: msg2fa, email2fasent: true })); ws.close(); } catch (e) { }
|
||||||
} else {
|
} else {
|
||||||
// We are authenticated
|
// We are authenticated
|
||||||
|
req.session.userid = user._id;
|
||||||
|
req.session.ip = req.clientIp;
|
||||||
|
setSessionRandom(req);
|
||||||
func(ws, req, domain, user);
|
func(ws, req, domain, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8179,7 +8394,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
if (emailcheck && (user.email != null) && (!(user._id.split('/')[2].startsWith('~'))) && (user.emailVerified !== true)) {
|
if (emailcheck && (user.email != null) && (!(user._id.split('/')[2].startsWith('~'))) && (user.emailVerified !== true)) {
|
||||||
parent.debug('web', 'Invalid login, asking for email validation');
|
parent.debug('web', 'Invalid login, asking for email validation');
|
||||||
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
|
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
|
||||||
} else {
|
} else {
|
||||||
|
req.session.userid = user._id;
|
||||||
|
req.session.ip = req.clientIp;
|
||||||
|
setSessionRandom(req);
|
||||||
func(ws, req, domain, user);
|
func(ws, req, domain, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8778,6 +8996,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
delete user2.otpsms;
|
delete user2.otpsms;
|
||||||
delete user2.otpmsg;
|
delete user2.otpmsg;
|
||||||
if ((typeof user2.otpekey == 'object') && (user2.otpekey != null)) { user2.otpekey = 1; } // Indicates that email 2FA is enabled.
|
if ((typeof user2.otpekey == 'object') && (user2.otpekey != null)) { user2.otpekey = 1; } // Indicates that email 2FA is enabled.
|
||||||
|
if ((typeof user2.otpduo == 'object') && (user2.otpduo != null)) { user2.otpduo = 1; } // Indicates that duo 2FA is enabled.
|
||||||
if ((typeof user2.otpsecret == 'string') && (user2.otpsecret != null)) { user2.otpsecret = 1; } // Indicates a time secret is present.
|
if ((typeof user2.otpsecret == 'string') && (user2.otpsecret != null)) { user2.otpsecret = 1; } // Indicates a time secret is present.
|
||||||
if ((typeof user2.otpkeys == 'object') && (user2.otpkeys != null)) { user2.otpkeys = 0; if (user.otpkeys != null) { for (var i = 0; i < user.otpkeys.keys.length; i++) { if (user.otpkeys.keys[i].u == true) { user2.otpkeys = 1; } } } } // Indicates the number of one time backup codes that are active.
|
if ((typeof user2.otpkeys == 'object') && (user2.otpkeys != null)) { user2.otpkeys = 0; if (user.otpkeys != null) { for (var i = 0; i < user.otpkeys.keys.length; i++) { if (user.otpkeys.keys[i].u == true) { user2.otpkeys = 1; } } } } // Indicates the number of one time backup codes that are active.
|
||||||
if ((typeof user2.otphkeys == 'object') && (user2.otphkeys != null)) { user2.otphkeys = user2.otphkeys.length; } // Indicates the number of hardware keys setup
|
if ((typeof user2.otphkeys == 'object') && (user2.otphkeys != null)) { user2.otphkeys = user2.otphkeys.length; } // Indicates the number of hardware keys setup
|
||||||
|
@ -8833,7 +9052,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter the user web site and only output state that we need to keep
|
// Filter the user web site and only output state that we need to keep
|
||||||
const acceptableUserWebStateStrings = ['webPageStackMenu', 'notifications', 'deviceView', 'nightMode', 'webPageFullScreen', 'search', 'showRealNames', 'sort', 'deskAspectRatio', 'viewsize', 'DeskControl', 'uiMode', 'footerBar','loctag'];
|
const acceptableUserWebStateStrings = ['webPageStackMenu', 'notifications', 'deviceView', 'nightMode', 'webPageFullScreen', 'search', 'showRealNames', 'sort', 'deskAspectRatio', 'viewsize', 'DeskControl', 'uiMode', 'footerBar','loctag','theme','lastThemes','uiViewMode'];
|
||||||
const acceptableUserWebStateDesktopStrings = ['encoding', 'showfocus', 'showmouse', 'showcad', 'limitFrameRate', 'noMouseRotate', 'quality', 'scaling', 'agentencoding']
|
const acceptableUserWebStateDesktopStrings = ['encoding', 'showfocus', 'showmouse', 'showcad', 'limitFrameRate', 'noMouseRotate', 'quality', 'scaling', 'agentencoding']
|
||||||
obj.filterUserWebState = function (state) {
|
obj.filterUserWebState = function (state) {
|
||||||
if (typeof state == 'string') { try { state = JSON.parse(state); } catch (ex) { return null; } }
|
if (typeof state == 'string') { try { state = JSON.parse(state); } catch (ex) { return null; } }
|
||||||
|
@ -9281,8 +9500,27 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
} catch (ex) { return { browserStr: browser, osStr: os } }
|
} catch (ex) { return { browserStr: browser, osStr: os } }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the query string portion of the URL, the ? and anything after.
|
// Return the query string portion of the URL, the ? and anything after BUT remove secret keys from authentication providers
|
||||||
function getQueryPortion(req) { var s = req.url.indexOf('?'); if (s == -1) { if (req.body && req.body.urlargs) { return req.body.urlargs; } return ''; } return req.url.substring(s); }
|
function getQueryPortion(req) {
|
||||||
|
var removeKeys = ['duo_code', 'state']; // Keys to remove
|
||||||
|
var s = req.url.indexOf('?');
|
||||||
|
if (s == -1) {
|
||||||
|
if (req.body && req.body.urlargs) {
|
||||||
|
return req.body.urlargs;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
var queryString = req.url.substring(s + 1);
|
||||||
|
var params = queryString.split('&');
|
||||||
|
var filteredParams = [];
|
||||||
|
for (var i = 0; i < params.length; i++) {
|
||||||
|
var key = params[i].split('=')[0];
|
||||||
|
if (removeKeys.indexOf(key) === -1) {
|
||||||
|
filteredParams.push(params[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (filteredParams.length > 0 ? ('?' + filteredParams.join('&')) : '');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a random Intel AMT password
|
// Generate a random Intel AMT password
|
||||||
function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
|
function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue