Merge pull request #1 from Ylianst/master

Update
This commit is contained in:
EBell 2019-05-11 22:19:29 -05:00 committed by GitHub
commit 8feefde2ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 8994 additions and 3272 deletions

View File

@ -94,6 +94,9 @@
<Compile Include="amtevents.js" />
<Compile Include="amtscanner.js" />
<Compile Include="amtscript.js" />
<Compile Include="amt\amt-ider-module.js" />
<Compile Include="amt\amt-ider.js" />
<Compile Include="amt\amt-redir-mesh.js" />
<Compile Include="exeHandler.js" />
<Compile Include="letsencrypt.js" />
<Compile Include="meshaccelerator.js" />
@ -141,6 +144,7 @@
<Content Include="agents\meshagent_x86-64" />
<Content Include="agents\meshagent_x86-64_nokvm" />
<Content Include="agents\meshagent_x86_nokvm" />
<Content Include="agents\MeshCentralRouter.exe" />
<Content Include="agents\MeshCmd-signed.exe" />
<Content Include="agents\MeshCmd64-signed.exe" />
<Content Include="agents\MeshCommander-Small.gz" />
@ -275,6 +279,7 @@
<Folder Include="agents\modules_meshcmd_min\" />
<Folder Include="agents\modules_meshcore\" />
<Folder Include="agents\modules_meshcore_min\" />
<Folder Include="amt\" />
<Folder Include="public" />
<Folder Include="public\clickonce\" />
<Folder Include="public\clickonce\minirouter\" />

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
agents/meshagent_arm64 Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -418,16 +418,21 @@ function createMeshCore(agent) {
var woptions = http.parseUri(xurl);
woptions.rejectUnauthorized = 0;
//sendConsoleText(JSON.stringify(woptions));
//sendConsoleText('TUNNEL: ' + JSON.stringify(data));
var tunnel = http.request(woptions);
tunnel.upgrade = onTunnelUpgrade;
tunnel.on('error', function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); });
tunnel.sessionid = data.sessionid;
tunnel.rights = data.rights;
tunnel.consent = data.consent;
tunnel.username = data.username;
tunnel.state = 0;
tunnel.url = xurl;
tunnel.protocol = 0;
tunnel.tcpaddr = data.tcpaddr;
tunnel.tcpport = data.tcpport;
tunnel.udpaddr = data.udpaddr;
tunnel.udpport = data.udpport;
tunnel.end();
// Put the tunnel in the tunnels list
var index = nextTunnelIndex++;
@ -520,7 +525,9 @@ function createMeshCore(agent) {
}
case 'toast': {
// Display a toast message
if (data.title && data.msg) { require('toaster').Toast(data.title, data.msg); }
if (data.title && data.msg) {
try { require('toaster').Toast(data.title, data.msg); } catch (ex) { }
}
break;
}
case 'openUrl': {
@ -604,7 +611,7 @@ function createMeshCore(agent) {
s.end = onTunnelClosed;
s.tunnel = this;
//sendConsoleText('onTunnelUpgrade');
//sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
if (this.tcpport != null) {
// This is a TCP relay connection, pause now and try to connect to the target.
@ -614,12 +621,37 @@ function createMeshCore(agent) {
if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
s.tcprelay.peerindex = this.index;
} if (this.udpport != null) {
// This is a UDP relay connection, get the UDP socket setup. // TODO: ***************
s.data = onUdpRelayServerTunnelData;
s.udprelay = require('dgram').createSocket({ type: 'udp4' });
s.udprelay.bind({ port: 0 });
s.udprelay.peerindex = this.index;
s.udprelay.on('message', onUdpRelayTargetTunnelConnect);
s.udprelay.udpport = this.udpport;
s.udprelay.udpaddr = this.udpaddr;
s.udprelay.first = true;
} else {
// This is a normal connect for KVM/Terminal/Files
s.data = onTunnelData;
}
}
// Called when UDP relay data is received // TODO****
function onUdpRelayTargetTunnelConnect(data) {
var peerTunnel = tunnels[this.peerindex];
peerTunnel.s.write(data);
}
// Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
function onUdpRelayServerTunnelData(data) {
if (this.udprelay.first === true) {
delete this.udprelay.first; // Skip the first 'c' that is received.
} else {
this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1');
}
}
// Called when the TCP relay target is connected
function onTcpRelayTargetTunnelConnect() {
var peerTunnel = tunnels[this.peerindex];
@ -632,7 +664,7 @@ function createMeshCore(agent) {
function onTcpRelayServerTunnelData(data) {
if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target
}
function onTunnelClosed() {
if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
//sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
@ -706,6 +738,11 @@ function createMeshCore(agent) {
return;
}
// Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 2)) {
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote terminal session.'); } catch (ex) { }
}
// Remote terminal using native pipes
if (process.platform == "win32")
{
@ -759,14 +796,16 @@ function createMeshCore(agent) {
return;
}
// Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 1)) {
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote desktop session.'); } catch (ex) { }
}
// Remote desktop using native pipes
this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(), tunnel: this };
this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
this.desktop = this.httprequest.desktop;
// Display a toast message
//require('toaster').Toast('MeshCentral', 'Remote Desktop Control Started.');
this.end = function () {
--this.desktop.kvm.connectionCount;
@ -781,8 +820,9 @@ function createMeshCore(agent) {
}
if (this.desktop.kvm.connectionCount == 0) {
// Display a toast message
//require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.');
// Display a toast message. This may not be supported on all platforms.
// try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { }
this.httprequest.desktop.kvm.end();
}
};
@ -811,6 +851,11 @@ function createMeshCore(agent) {
return;
}
// Perform notification if needed
if (this.httprequest.consent && (this.httprequest.consent & 4)) {
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote file access.'); } catch (ex) { }
}
// Setup files
// NOP
}
@ -1235,8 +1280,7 @@ function createMeshCore(agent) {
case 'toast': {
if (process.platform == 'win32') {
if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
require('toaster').Toast('MeshCentral', args['_'][0]);
response = 'ok';
try { require('toaster').Toast('MeshCentral', args['_'][0]); response = 'ok'; } catch (ex) { response = ex; }
}
} else {
response = 'Only supported on Windows.';
@ -1676,9 +1720,11 @@ function createMeshCore(agent) {
// Update the network interfaces information data
var netInfo = mesh.NetInfo;
netInfo.action = 'netinfo';
var netInfoStr = JSON.stringify(netInfo);
if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
if (netInfo) {
netInfo.action = 'netinfo';
var netInfoStr = JSON.stringify(netInfo);
if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
}
}
// Called periodically to check if we need to send updates to the server

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,206 @@
/*
Copyright 2019 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
require('MeshAgent').on('Connected', function (status)
{
if (status == 0)
{
return;
}
this.timeout = setTimeout(start, 10000);
});
function sendServerLog(msg)
{
require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'log', value: msg } });
}
function getMeshAgentService()
{
try
{
var ret = require('service-manager').manager.getService(process.platform == 'win32' ? 'mesh agent' : 'meshagent');
return(ret);
}
catch(e)
{
return (null);
}
}
function getARCHID() {
var ret = 0;
switch (process.platform) {
case 'linux':
// Need to detect Architecture ID
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("uname -m\nexit\n");
child.waitExit();
switch (child.stdout.str.trim()) {
case 'x86_64':
case 'amd64':
ret = 6;
break;
case 'x86':
case 'i686':
case 'i586':
case 'i386':
ret = 5;
break;
case 'armv6l':
case 'armv7l':
ret = 25;
break;
default:
break;
}
break;
case 'darwin':
ret = 16;
break;
case 'win32':
ret = process.arch == 'x64' ? 4 : 3;
break;
}
return (ret);
}
function DownloadAgentBinary(path, ID)
{
var options = require('http').parseUri(require('MeshAgent').ServerInfo.ServerUri);
var downloadUri = 'https://' + options.host + ':' + options.port + '/meshagents?id=' + (ID != null ? ID : getARCHID());
sendServerLog('Diagnostic: Attempting to downlod agent from: ' + downloadUri);
return (wget(downloadUri, path, { rejectUnauthorized: false }));
}
function giveup()
{
sendServerLog('Diagnostic: Unable to diagnose Mesh Agent');
finished();
}
function finished()
{
sendServerLog('Diagnostic: End');
require('service-manager').manager.getService('meshagentDiagnostic').stop();
}
function ConfigureAgent(agent)
{
sendServerLog('...Configuring Agent...');
var info = require('MeshAgent').ServerInfo;
var msh = 'MeshID=0x' + info.MeshID + '\n' + 'ServerID=' + info.ServerID + '\n' + 'MeshServer=' + info.ServerUri + '\n';
var cfg = require('global-tunnel').proxyConfig;
if(cfg == null)
{
msh += 'ignoreProxyFile=1\n';
}
else
{
msh += ('WebProxy=' + cfg.host + ':' + cfg.port + '\n');
}
if(process.platform == 'win32')
{
require('fs').writeFileSync(agent.appLocation().replace('.exe', '.msh'), msh);
}
else
{
require('fs').writeFileSync(agent.appLocation() + '.msh', msh);
}
}
function start()
{
sendServerLog('Diagnostic: Start');
var id = getARCHID();
var s = getMeshAgentService();
if (s == null)
{
DownloadAgentBinary('agent_temporary.bin').then(function ()
{
// SUCCESS
try
{
var agent = require('service-manager').manager.installService(
{
name: process.platform == 'win32' ? 'Mesh Agent' : 'meshagent',
target: 'meshagent',
description: 'Mesh Central Agent v2 Background Service',
displayName: 'Mesh Agent v2 Background Service',
servicePath: 'agent_temporary.bin',
startType: 'DEMAND_START'
});
require('fs').unlinkSync('agent_temporary.bin');
ConfigureAgent(agent);
}
catch(e)
{
giveup();
}
},
function ()
{
// FAILURE
giveup();
});
}
if(s!=null)
{
// Mesh Agent Installation Found
sendServerLog('Diagnostic: Mesh Agent Service => ' + (s.isRunning() ? 'RUNNING' : 'NOT-RUNNING'));
if(s.isRunning())
{
finished();
}
else
{
sendServerLog('Diagnostic: Attempting to start Mesh Agent');
s.start();
sendServerLog('Diagnostic: ' + (s.isRunning() ? '(SUCCESS)' : '(FAILED)'));
if (s.isRunning())
{
finished();
return;
}
else
{
DownloadAgentBinary(s.appLocation()).then(
function () {
sendServerLog('Diagnostic: Downloaded Successfully');
sendServerLog('Diagnostic: Attempting to start Mesh Agent');
s.start();
sendServerLog('Diagnostic: ' + (s.isRunning() ? '(SUCCESS)' : '(FAILED)'));
if (s.isRunning()) {
finished();
return;
}
else {
giveup();
}
},
function () {
sendServerLog('Diagnostic: Download Failed');
giveup();
});
}
}
}
};

View File

@ -65,6 +65,11 @@ CheckInstallAgent() {
# RaspberryPi 1 (armv6l) or RaspberryPi 2/3 (armv7l)
machineid=25
fi
if [ $machinetype == 'aarch64' ]
then
# RaspberryPi 3B+ running Ubuntu 64 (aarch64)
machineid=26
fi
# Add more machine types, detect KVM support... here.
fi
@ -152,7 +157,7 @@ DownloadAgent() {
then
# upstart
echo -e "start on runlevel [2345]\nstop on runlevel [016]\n\nrespawn\n\nchdir /usr/local/mesh\nexec /usr/local/mesh/meshagent\n\n" > /etc/init/meshagent.conf
service meshagent start
initctl start meshagent
echo 'meshagent installed as upstart/init.d service.'
echo 'To start service: sudo initctl start meshagent'
echo 'To stop service: sudo initctl stop meshagent'
@ -182,21 +187,27 @@ UninstallAgent() {
if [ $starttype -eq 1 ]
then
# systemd
rm -f /sbin/meshcmd /lib/systemd/system/meshagent.service
systemctl disable meshagent
systemctl stop meshagent
rm -f /sbin/meshcmd /lib/systemd/system/meshagent.service
systemctl stop meshagentDiagnostic &> /dev/null
rm -f /lib/systemd/system/meshagentDiagnostic.service &> /dev/null
else
if [ $starttype -eq 3 ]; then
# initd
service meshagent stop
update-rc.d -f meshagent remove
rm -f /sbin/meshcmd /etc/init.d/meshagent
service meshagentDiagnostic stop &> /dev/null
rm -f /etc/init.d/meshagentDiagnostic &> /dev/null
elif [ $starttype -eq 2 ]; then
# upstart
service meshagent stop
initctl stop meshagent
rm -f /sbin/meshcmd
rm -f /etc/init/meshagent.conf
rm -f /etc/rc2.d/S20mesh /etc/rc3.d/S20mesh /etc/rc5.d/S20mesh
initctl stop meshagentDiagnostic &> /dev/null
rm -f /etc/init/meshagentDiagnostic.conf &> /dev/null
fi
fi
@ -205,6 +216,8 @@ UninstallAgent() {
rm -rf $installpath/*
rmdir $installpath
fi
rm -rf /usr/local/mesh_services/meshagentDiagnostic &> /dev/null
rm -f /etc/cron.d/meshagentDiagnostic_periodicStart &> /dev/null
echo "Agent uninstalled."
}
@ -227,6 +240,7 @@ then
UninstallAgent
fi
else
UninstallAgent
CheckInstallAgent $1 $2 $3
fi
fi

View File

@ -109,10 +109,10 @@ module.exports.setup = function(binary, startvars) {
var argcount = ReadShort(obj.script, obj.ip + 4);
var argptr = obj.ip + 6;
var args = [];
// Clear all temp variables (This is optional)
for (var i in obj.variables) { if (i.startsWith('__')) { delete obj.variables[i]; } }
// Loop on each argument, moving forward by the argument length each time
for (var i = 0; i < argcount; i++) {
var arglen = ReadShort(obj.script, argptr);
@ -122,7 +122,7 @@ module.exports.setup = function(binary, startvars) {
if (argtyp < 2) {
// Get the value and replace all {var} with variable values
argval = argval.toString();
while (argval.split("{").length > 1) { var t = argval.split("{").pop().split("}").shift(); argval = argval.replace('{' + t + '}', obj.getVar(t)); }
if (argval != null) { while (argval.split("{").length > 1) { var t = argval.split("{").pop().split("}").shift(); argval = argval.replace('{' + t + '}', obj.getVar(t)); } }
if (argtyp == 1) { obj.variables['__' + i] = decodeURI(argval); argval = '__' + i; } // If argtyp is 1, this is a literal. Store in temp variable.
args.push(argval);
}
@ -132,7 +132,7 @@ module.exports.setup = function(binary, startvars) {
}
argptr += (2 + arglen);
}
// Move instruction pointer forward by command size
obj.ip += cmdlen;

View File

@ -72,7 +72,7 @@ function CreateWsmanComm(/*host, port, user, pass, tls, extra*/)
obj.PerformAjaxEx = function (postdata, callback, tag, url, action) {
if (obj.FailAllError != 0) { if (obj.FailAllError != 999) { obj.gotNextMessagesError({ status: obj.FailAllError }, 'error', null, [postdata, callback, tag]); } return; }
if (!postdata) postdata = "";
//console.log("SEND: " + postdata); // DEBUG
if (globalDebugFlags & 1) { console.log("SEND: " + postdata + "\r\n\r\n"); } // DEBUG
// We are in a DukTape environement
if (obj.digest == null)
@ -92,9 +92,9 @@ function CreateWsmanComm(/*host, port, user, pass, tls, extra*/)
//console.log('Request ' + (obj.RequestCount++));
req.on('error', function (e) { obj.gotNextMessagesError({ status: 600 }, 'error', null, [postdata, callback, tag]); });
req.on('response', function (response) {
//console.log('Response: ' + response.statusCode);
if (globalDebugFlags & 1) { console.log('Response: ' + response.statusCode); }
if (response.statusCode != 200) {
//console.log('ERR:' + JSON.stringify(response));
if (globalDebugFlags & 1) { console.log('ERR:' + JSON.stringify(response)); }
obj.gotNextMessagesError({ status: response.statusCode }, 'error', null, [postdata, callback, tag]);
} else {
response.acc = '';
@ -116,7 +116,7 @@ function CreateWsmanComm(/*host, port, user, pass, tls, extra*/)
obj.gotNextMessages = function (data, status, request, callArgs) {
obj.ActiveAjaxCount--;
if (obj.FailAllError == 999) return;
//console.log("RECV: " + data); // DEBUG
if (globalDebugFlags & 1) { console.log("RECV: " + data + "\r\n\r\n"); } // DEBUG
if (obj.FailAllError != 0) { callArgs[1](null, obj.FailAllError, callArgs[2]); return; }
if (request.status != 200) { callArgs[1](null, request.status, callArgs[2]); return; }
callArgs[1](data, 200, callArgs[2]);

View File

@ -177,7 +177,7 @@ function WsmanStackCreateService(/*CreateWsmanComm, host, port, user, pass, tls,
}
function _PutObjToSelectorsXml(selectorSet) {
if (!selectorSet) return '';
if ((selectorSet == null) || (selectorSet == 'null')) return '';
if (typeof selectorSet == 'string') return selectorSet;
if (selectorSet['InstanceID']) return "<w:SelectorSet><w:Selector Name=\"InstanceID\">" + selectorSet['InstanceID'] + "</w:Selector></w:SelectorSet>";
var result = '<w:SelectorSet>';

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
function CreateWsmanComm(){var a={};a.PendingAjax=[];a.ActiveAjaxCount=0;a.MaxActiveAjaxCount=1;a.FailAllError=0;a.digest=null;a.RequestCount=0;if(arguments.length==1&&typeof(arguments[0]=="object")){a.host=arguments[0].host;a.port=arguments[0].port;a.authToken=arguments[0].authToken;a.tls=arguments[0].tls}else{a.host=arguments[0];a.port=arguments[1];a.user=arguments[2];a.pass=arguments[3];a.tls=arguments[4]}a.PerformAjax=function(d,c,f,e,g,b){if((a.ActiveAjaxCount==0||((a.ActiveAjaxCount<a.MaxActiveAjaxCount)&&(a.challengeParams!=null)))&&a.PendingAjax.length==0){a.PerformAjaxEx(d,c,f,g,b)}else{if(e==1){a.PendingAjax.unshift([d,c,f,g,b])}else{a.PendingAjax.push([d,c,f,g,b])}}};a.PerformNextAjax=function(){if(a.ActiveAjaxCount>=a.MaxActiveAjaxCount||a.PendingAjax.length==0){return}var b=a.PendingAjax.shift();a.PerformAjaxEx(b[0],b[1],b[2],b[3],b[4]);a.PerformNextAjax()};a.PerformAjaxEx=function(d,c,g,h,b){if(a.FailAllError!=0){if(a.FailAllError!=999){a.gotNextMessagesError({status:a.FailAllError},"error",null,[d,c,g])}return}if(!d){d=""}if(a.digest==null){if(a.authToken){a.digest=require("http-digest").create({authToken:a.authToken})}else{a.digest=require("http-digest").create(a.user,a.pass)}a.digest.http=require("http")}var f={protocol:(a.tls==1?"https:":"http:"),method:"POST",host:a.host,path:"/wsman",port:a.port,rejectUnauthorized:false,checkServerIdentity:function(i){console.log("checkServerIdentity",JSON.stringify(i))}};var e=a.digest.request(f);e.on("error",function(i){a.gotNextMessagesError({status:600},"error",null,[d,c,g])});e.on("response",function(i){if(i.statusCode!=200){a.gotNextMessagesError({status:i.statusCode},"error",null,[d,c,g])}else{i.acc="";i.on("data",function(j){this.acc+=j});i.on("end",function(){a.gotNextMessages(i.acc,"success",{status:i.statusCode},[d,c,g])})}});e.end(d);a.ActiveAjaxCount++;return e};a.pendingAjaxCall=[];a.gotNextMessages=function(c,e,d,b){a.ActiveAjaxCount--;if(a.FailAllError==999){return}if(a.FailAllError!=0){b[1](null,a.FailAllError,b[2]);return}if(d.status!=200){b[1](null,d.status,b[2]);return}b[1](c,200,b[2]);a.PerformNextAjax()};a.gotNextMessagesError=function(d,e,c,b){a.ActiveAjaxCount--;if(a.FailAllError==999){return}if(a.FailAllError!=0){b[1](null,a.FailAllError,b[2]);return}if(a.FailAllError!=999){b[1]({Header:{HttpError:d.status}},d.status,b[2])}a.PerformNextAjax()};a.CancelAllQueries=function(b){while(a.PendingAjax.length>0){var c=a.PendingAjax.shift();c[1](null,b,c[2])}};return a}module.exports=CreateWsmanComm;
function CreateWsmanComm(){var a={};a.PendingAjax=[];a.ActiveAjaxCount=0;a.MaxActiveAjaxCount=1;a.FailAllError=0;a.digest=null;a.RequestCount=0;if(arguments.length==1&&typeof(arguments[0]=="object")){a.host=arguments[0].host;a.port=arguments[0].port;a.authToken=arguments[0].authToken;a.tls=arguments[0].tls}else{a.host=arguments[0];a.port=arguments[1];a.user=arguments[2];a.pass=arguments[3];a.tls=arguments[4]}a.PerformAjax=function(d,c,f,e,g,b){if((a.ActiveAjaxCount==0||((a.ActiveAjaxCount<a.MaxActiveAjaxCount)&&(a.challengeParams!=null)))&&a.PendingAjax.length==0){a.PerformAjaxEx(d,c,f,g,b)}else{if(e==1){a.PendingAjax.unshift([d,c,f,g,b])}else{a.PendingAjax.push([d,c,f,g,b])}}};a.PerformNextAjax=function(){if(a.ActiveAjaxCount>=a.MaxActiveAjaxCount||a.PendingAjax.length==0){return}var b=a.PendingAjax.shift();a.PerformAjaxEx(b[0],b[1],b[2],b[3],b[4]);a.PerformNextAjax()};a.PerformAjaxEx=function(d,c,g,h,b){if(a.FailAllError!=0){if(a.FailAllError!=999){a.gotNextMessagesError({status:a.FailAllError},"error",null,[d,c,g])}return}if(!d){d=""}if(globalDebugFlags&1){console.log("SEND: "+d+"\r\n\r\n")}if(a.digest==null){if(a.authToken){a.digest=require("http-digest").create({authToken:a.authToken})}else{a.digest=require("http-digest").create(a.user,a.pass)}a.digest.http=require("http")}var f={protocol:(a.tls==1?"https:":"http:"),method:"POST",host:a.host,path:"/wsman",port:a.port,rejectUnauthorized:false,checkServerIdentity:function(i){console.log("checkServerIdentity",JSON.stringify(i))}};var e=a.digest.request(f);e.on("error",function(i){a.gotNextMessagesError({status:600},"error",null,[d,c,g])});e.on("response",function(i){if(globalDebugFlags&1){console.log("Response: "+i.statusCode)}if(i.statusCode!=200){if(globalDebugFlags&1){console.log("ERR:"+JSON.stringify(i))}a.gotNextMessagesError({status:i.statusCode},"error",null,[d,c,g])}else{i.acc="";i.on("data",function(j){this.acc+=j});i.on("end",function(){a.gotNextMessages(i.acc,"success",{status:i.statusCode},[d,c,g])})}});e.end(d);a.ActiveAjaxCount++;return e};a.pendingAjaxCall=[];a.gotNextMessages=function(c,e,d,b){a.ActiveAjaxCount--;if(a.FailAllError==999){return}if(globalDebugFlags&1){console.log("RECV: "+c+"\r\n\r\n")}if(a.FailAllError!=0){b[1](null,a.FailAllError,b[2]);return}if(d.status!=200){b[1](null,d.status,b[2]);return}b[1](c,200,b[2]);a.PerformNextAjax()};a.gotNextMessagesError=function(d,e,c,b){a.ActiveAjaxCount--;if(a.FailAllError==999){return}if(a.FailAllError!=0){b[1](null,a.FailAllError,b[2]);return}if(a.FailAllError!=999){b[1]({Header:{HttpError:d.status}},d.status,b[2])}a.PerformNextAjax()};a.CancelAllQueries=function(b){while(a.PendingAjax.length>0){var c=a.PendingAjax.shift();c[1](null,b,c[2])}};return a}module.exports=CreateWsmanComm;

File diff suppressed because one or more lines are too long

View File

@ -177,7 +177,7 @@ function WsmanStackCreateService(/*CreateWsmanComm, host, port, user, pass, tls,
}
function _PutObjToSelectorsXml(selectorSet) {
if (!selectorSet) return '';
if ((selectorSet == null) || (selectorSet == 'null')) return '';
if (typeof selectorSet == 'string') return selectorSet;
if (selectorSet['InstanceID']) return "<w:SelectorSet><w:Selector Name=\"InstanceID\">" + selectorSet['InstanceID'] + "</w:Selector></w:SelectorSet>";
var result = '<w:SelectorSet>';

View File

@ -145,7 +145,7 @@ function WindowsConsole()
this.TrayIcon.remove();
handled = true;
}
if (!handled) { console.log(msg); }
//if (!handled) { console.log(msg); }
}
});
retVal.remove = function remove()

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
var TrayIconFlags={NIF_MESSAGE:1,NIF_ICON:2,NIF_TIP:4,NIF_STATE:8,NIF_INFO:16,NIF_GUID:32,NIF_REALTIME:64,NIF_SHOWTIP:128,NIM_ADD:0,NIM_MODIFY:1,NIM_DELETE:2,NIM_SETFOCUS:3,NIM_SETVERSION:4};var NOTIFYICON_VERSION_4=4;var MessageTypes={WM_APP:32768,WM_USER:1024};function WindowsConsole(){if(process.platform=="win32"){this._ObjectID="win-console";this._Marshal=require("_GenericMarshal");this._kernel32=this._Marshal.CreateNativeProxy("kernel32.dll");this._user32=this._Marshal.CreateNativeProxy("user32.dll");this._kernel32.CreateMethod("GetConsoleWindow");this._kernel32.CreateMethod("GetCurrentThread");this._user32.CreateMethod("ShowWindow");this._user32.CreateMethod("LoadImageA");this._user32.CreateMethod({method:"GetMessageA",threadDispatch:1});this._shell32=this._Marshal.CreateNativeProxy("Shell32.dll");this._shell32.CreateMethod("Shell_NotifyIconA");this._handle=this._kernel32.GetConsoleWindow();this.minimize=function(){this._user32.ShowWindow(this._handle,6)};this.restore=function(){this._user32.ShowWindow(this._handle,9)};this.hide=function(){this._user32.ShowWindow(this._handle,0)};this.show=function(){this._user32.ShowWindow(this._handle,5)};this._loadicon=function(c){var b=this._user32.LoadImageA(0,this._Marshal.CreateVariable(c),1,0,0,16|32768|64);return(b)};this.SetTrayIcon=function a(h){var b=this._Marshal.CreateVariable(this._Marshal.PointerSize==4?508:528);b.toBuffer().writeUInt32LE(b._size,0);var n=TrayIconFlags.NIF_TIP|TrayIconFlags.NIF_MESSAGE;h.filter=MessageTypes.WM_APP+1;b.Deref(this._Marshal.PointerSize==4?16:24,4).toBuffer().writeUInt32LE(h.filter);if(!h.noBalloon){n|=TrayIconFlags.NIF_INFO}if(h.icon){n|=TrayIconFlags.NIF_ICON;var c=b.Deref(this._Marshal.PointerSize==4?20:32,this._Marshal.PointerSize);h.icon.pointerBuffer().copy(c.toBuffer())}b.Deref(this._Marshal.PointerSize*2,4).toBuffer().writeUInt32LE(1);b.Deref(this._Marshal.PointerSize==4?12:20,4).toBuffer().writeUInt32LE(n);b.Deref(this._Marshal.PointerSize==4?416:432,4).toBuffer().writeUInt32LE(NOTIFYICON_VERSION_4);var m=b.Deref(this._Marshal.PointerSize==4?24:40,128);var k=b.Deref(this._Marshal.PointerSize==4?160:176,256);var l=b.Deref(this._Marshal.PointerSize==4?420:436,64);if(h.szTip){Buffer.from(h.szTip).copy(m.toBuffer())}if(h.szInfo){Buffer.from(h.szInfo).copy(k.toBuffer())}if(h.szInfoTitle){Buffer.from(h.szInfoTitle).copy(l.toBuffer())}var d=require("win-message-pump");retVal={_ObjectID:"WindowsConsole.TrayIcon",MessagePump:new d(h)};var j=require("events").inherits(retVal);j.createEvent("ToastClicked");j.createEvent("IconHover");j.createEvent("ToastDismissed");retVal.Options=h;retVal.MessagePump.TrayIcon=retVal;retVal.MessagePump.NotifyData=b;retVal.MessagePump.WindowsConsole=this;retVal.MessagePump.on("exit",function e(o){console.log("Pump Exited");if(this.TrayIcon){this.TrayIcon.remove()}});retVal.MessagePump.on("hwnd",function f(o){h.hwnd=o;o.pointerBuffer().copy(this.NotifyData.Deref(this.WindowsConsole._Marshal.PointerSize,this.WindowsConsole._Marshal.PointerSize).toBuffer());if(this.WindowsConsole._shell32.Shell_NotifyIconA(TrayIconFlags.NIM_ADD,this.NotifyData).Val==0){}});retVal.MessagePump.on("message",function g(p){if(p.message==this.TrayIcon.Options.filter){var o=false;if(p.wparam==1&&p.lparam==1029){this.TrayIcon.emit("ToastClicked");o=true}if(p.wparam==1&&p.lparam==512){this.TrayIcon.emit("IconHover");o=true}if(this.TrayIcon.Options.balloonOnly&&p.wparam==1&&(p.lparam==1028||p.lparam==1029)){this.TrayIcon.emit("ToastDismissed");this.TrayIcon.remove();o=true}if(!o){console.log(p)}}});retVal.remove=function i(){this.MessagePump.WindowsConsole._shell32.Shell_NotifyIconA(TrayIconFlags.NIM_DELETE,this.MessagePump.NotifyData);this.MessagePump.stop();delete this.MessagePump.TrayIcon;delete this.MessagePump};return(retVal)}}}module.exports=new WindowsConsole();
var TrayIconFlags={NIF_MESSAGE:1,NIF_ICON:2,NIF_TIP:4,NIF_STATE:8,NIF_INFO:16,NIF_GUID:32,NIF_REALTIME:64,NIF_SHOWTIP:128,NIM_ADD:0,NIM_MODIFY:1,NIM_DELETE:2,NIM_SETFOCUS:3,NIM_SETVERSION:4};var NOTIFYICON_VERSION_4=4;var MessageTypes={WM_APP:32768,WM_USER:1024};function WindowsConsole(){if(process.platform=="win32"){this._ObjectID="win-console";this._Marshal=require("_GenericMarshal");this._kernel32=this._Marshal.CreateNativeProxy("kernel32.dll");this._user32=this._Marshal.CreateNativeProxy("user32.dll");this._kernel32.CreateMethod("GetConsoleWindow");this._kernel32.CreateMethod("GetCurrentThread");this._user32.CreateMethod("ShowWindow");this._user32.CreateMethod("LoadImageA");this._user32.CreateMethod({method:"GetMessageA",threadDispatch:1});this._shell32=this._Marshal.CreateNativeProxy("Shell32.dll");this._shell32.CreateMethod("Shell_NotifyIconA");this._handle=this._kernel32.GetConsoleWindow();this.minimize=function(){this._user32.ShowWindow(this._handle,6)};this.restore=function(){this._user32.ShowWindow(this._handle,9)};this.hide=function(){this._user32.ShowWindow(this._handle,0)};this.show=function(){this._user32.ShowWindow(this._handle,5)};this._loadicon=function(c){var b=this._user32.LoadImageA(0,this._Marshal.CreateVariable(c),1,0,0,16|32768|64);return(b)};this.SetTrayIcon=function a(h){var b=this._Marshal.CreateVariable(this._Marshal.PointerSize==4?508:528);b.toBuffer().writeUInt32LE(b._size,0);var n=TrayIconFlags.NIF_TIP|TrayIconFlags.NIF_MESSAGE;h.filter=MessageTypes.WM_APP+1;b.Deref(this._Marshal.PointerSize==4?16:24,4).toBuffer().writeUInt32LE(h.filter);if(!h.noBalloon){n|=TrayIconFlags.NIF_INFO}if(h.icon){n|=TrayIconFlags.NIF_ICON;var c=b.Deref(this._Marshal.PointerSize==4?20:32,this._Marshal.PointerSize);h.icon.pointerBuffer().copy(c.toBuffer())}b.Deref(this._Marshal.PointerSize*2,4).toBuffer().writeUInt32LE(1);b.Deref(this._Marshal.PointerSize==4?12:20,4).toBuffer().writeUInt32LE(n);b.Deref(this._Marshal.PointerSize==4?416:432,4).toBuffer().writeUInt32LE(NOTIFYICON_VERSION_4);var m=b.Deref(this._Marshal.PointerSize==4?24:40,128);var k=b.Deref(this._Marshal.PointerSize==4?160:176,256);var l=b.Deref(this._Marshal.PointerSize==4?420:436,64);if(h.szTip){Buffer.from(h.szTip).copy(m.toBuffer())}if(h.szInfo){Buffer.from(h.szInfo).copy(k.toBuffer())}if(h.szInfoTitle){Buffer.from(h.szInfoTitle).copy(l.toBuffer())}var d=require("win-message-pump");retVal={_ObjectID:"WindowsConsole.TrayIcon",MessagePump:new d(h)};var j=require("events").inherits(retVal);j.createEvent("ToastClicked");j.createEvent("IconHover");j.createEvent("ToastDismissed");retVal.Options=h;retVal.MessagePump.TrayIcon=retVal;retVal.MessagePump.NotifyData=b;retVal.MessagePump.WindowsConsole=this;retVal.MessagePump.on("exit",function e(o){console.log("Pump Exited");if(this.TrayIcon){this.TrayIcon.remove()}});retVal.MessagePump.on("hwnd",function f(o){h.hwnd=o;o.pointerBuffer().copy(this.NotifyData.Deref(this.WindowsConsole._Marshal.PointerSize,this.WindowsConsole._Marshal.PointerSize).toBuffer());if(this.WindowsConsole._shell32.Shell_NotifyIconA(TrayIconFlags.NIM_ADD,this.NotifyData).Val==0){}});retVal.MessagePump.on("message",function g(p){if(p.message==this.TrayIcon.Options.filter){var o=false;if(p.wparam==1&&p.lparam==1029){this.TrayIcon.emit("ToastClicked");o=true}if(p.wparam==1&&p.lparam==512){this.TrayIcon.emit("IconHover");o=true}if(this.TrayIcon.Options.balloonOnly&&p.wparam==1&&(p.lparam==1028||p.lparam==1029)){this.TrayIcon.emit("ToastDismissed");this.TrayIcon.remove();o=true}}});retVal.remove=function i(){this.MessagePump.WindowsConsole._shell32.Shell_NotifyIconA(TrayIconFlags.NIM_DELETE,this.MessagePump.NotifyData);this.MessagePump.stop();delete this.MessagePump.TrayIcon;delete this.MessagePump};return(retVal)}}}module.exports=new WindowsConsole();

115
amt-ider.js Normal file
View File

@ -0,0 +1,115 @@
/**
* @description MeshCentral Server IDER handler
* @author Ylian Saint-Hilaire & Bryan Roe
* @copyright Intel Corporation 2018-2019
* @license Apache-2.0
* @version v0.0.1
*/
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
"use strict";
// Construct a MeshAgent object, called upon connection
module.exports.CreateAmtIderSession = function (parent, db, ws, req, args, domain, user) {
const fs = require('fs');
const path = require('path');
const common = parent.common;
const amtMeshRedirModule = require('./amt/amt-redir-mesh.js');
const amtMeshIderModule = require('./amt/amt-ider-module.js');
console.log('New Server IDER session from ' + user.name);
var obj = {};
obj.user = user;
obj.domain = domain;
obj.ider = null;
// Send a message to the user
//obj.send = function (data) { try { if (typeof data == 'string') { ws.send(Buffer.from(data, 'binary')); } else { ws.send(data); } } catch (e) { } }
// Disconnect this user
obj.close = function (arg) {
if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket
if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug(1, 'Hard disconnect'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
};
try {
// Check if the user is logged in
if (user == null) { try { ws.close(); } catch (e) { } return; }
// When data is received from the web socket
ws.on('message', processWebSocketData);
// If error, do nothing
ws.on('error', function (err) { console.log(err); obj.close(0); });
// If the web socket is closed
ws.on('close', function (req) { obj.close(0); });
// We are all set, start receiving data
ws._socket.resume();
} catch (e) { console.log(e); }
// Process incoming web socket data from the browser
function processWebSocketData(msg) {
var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
if (common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
switch (command.action) {
case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; }
case 'selector': {
var r = { action: 'selector', args: { html: 'Click ok to start IDER session.' }, buttons: 3 };
// TODO: Return a list of disk images for the user to select.
try { ws.send(JSON.stringify(r)); } catch (ex) { }
break;
}
case 'selectorResponse': {
console.log('selectorResponse', command.args, req.query);
// TODO: Start IDER Session
// req.query = { host: 'node//KV6AZh3KoEzr71IaM40KqpBXQCn0qysZrMYlCOcvivNkV2$zfP2MXBE4IizBn1Bw', port: '16994', tls: '0', serverauth: '1', tls1only: '1' }
command.args = {
floppyPath: '',
cdromPath: '',
iderStart: 1,
tlsv1only: true
};
obj.ider = amtMeshRedirModule.CreateAmtRedirect(amtMeshIderModule.CreateAmtRemoteIder(), domain, user, parent, parent.parent);
obj.ider.onStateChanged = onIderStateChange;
obj.ider.m.debug = true;
obj.ider.m.floppy = command.args.floppyPath;
obj.ider.m.cdrom = command.args.cdromPath;
obj.ider.m.iderStart = command.args.iderStart;
obj.ider.m.sectorStats = iderSectorStats;
obj.ider.tlsv1only = req.query.tlsv1only;
obj.ider.Start(req.query.host, req.query.port, req.query.tls);
break;
}
default: {
// Unknown user action
console.log('Unknown IDER action from user ' + user.name + ': ' + command.action + '.');
break;
}
}
}
function onIderStateChange(sender, state) {
console.log('onIderStateChange', state);
}
function iderSectorStats(mode, dev, total, start, len) {
console.log('iderSectorStats', mode, dev, total, start, len);
}
return obj;
};

624
amt/amt-ider-module.js Normal file
View File

@ -0,0 +1,624 @@
/**
* @description IDER Handling Module
* @author Ylian Saint-Hilaire
* @version v0.0.2
*/
// Construct a Intel AMT IDER object
module.exports.CreateAmtRemoteIder = function (webserver, meshcentral) {
const fs = require('fs');
var obj = {};
obj.debug = false;
obj.protocol = 3; // IDER
obj.bytesToAmt = 0;
obj.bytesFromAmt = 0;
obj.rx_timeout = 30000; // Default 30000
obj.tx_timeout = 0; // Default 0
obj.heartbeat = 20000; // Default 20000
obj.version = 1;
obj.acc = "";
obj.inSequence = 0;
obj.outSequence = 0;
obj.iderinfo = null;
obj.enabled = false;
obj.iderStart = 0; // OnReboot = 0, Graceful = 1, Now = 2
obj.floppy = null;
obj.cdrom = null;
obj.floppySize = null;
obj.cdromSize = null;
obj.floppyReady = false;
obj.cdromReady = false;
obj.sectorStats = null;
// Private method
function debug() { if (obj.debug) { console.log(...arguments); } }
// Mode Sense
var IDE_ModeSence_LS120Disk_Page_Array = String.fromCharCode(0x00, 0x26, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00);
var IDE_ModeSence_3F_LS120_Array = String.fromCharCode(0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31);
var IDE_ModeSence_FloppyDisk_Page_Array = String.fromCharCode(0x00, 0x26, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x04, 0xB0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00);
var IDE_ModeSence_3F_Floppy_Array = String.fromCharCode(0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1e, 0x04, 0xb0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31);
var IDE_ModeSence_CD_1A_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
//var IDE_ModeSence_CD_1B_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
var IDE_ModeSence_CD_1D_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1D, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
var IDE_ModeSence_CD_2A_Array = String.fromCharCode(0x00, 0x20, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
//var IDE_ModeSence_CD_01_Array = String.fromCharCode(0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00);
var IDE_ModeSence_3F_CD_Array = String.fromCharCode(0x00, 0x28, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
// 0x46 constant data
var IDE_CD_ConfigArrayHeader = String.fromCharCode(0x00, 0x00,0x00, 0x28, 0x00, 0x00, 0x00, 0x08);
var IDE_CD_ConfigArrayProfileList = String.fromCharCode(0x00, 0x00, 0x03, 0x04, 0x00, 0x08, 0x01, 0x00);
var IDE_CD_ConfigArrayCore = String.fromCharCode(0x00, 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, 0x02);
var IDE_CD_Morphing = String.fromCharCode(0x00, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00);
var IDE_CD_ConfigArrayRemovable = String.fromCharCode(0x00, 0x03, 0x03, 0x04, 0x29, 0x00, 0x00, 0x02);
var IDE_CD_ConfigArrayRandom = String.fromCharCode(0x00, 0x10, 0x01, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00);
var IDE_CD_Read = String.fromCharCode(0x00, 0x1E, 0x03, 0x00);
var IDE_CD_PowerManagement = String.fromCharCode(0x01, 0x00, 0x03, 0x00);
var IDE_CD_Timeout = String.fromCharCode(0x01, 0x05, 0x03, 0x00);
// 0x01 constant data
var IDE_ModeSence_FloppyError_Recovery_Array = String.fromCharCode(0x00, 0x12, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00);
var IDE_ModeSence_Ls120Error_Recovery_Array = String.fromCharCode(0x00, 0x12, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00);
var IDE_ModeSence_CDError_Recovery_Array = String.fromCharCode(0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00);
// Private method, called by parent when it change state
obj.xxStateChange = function (newstate) {
debug("IDER-StateChange", newstate);
if (newstate == 0) { obj.Stop(); }
if (newstate == 3) { obj.Start(); }
}
obj.diskSetup = function (floppyPath, cdromPath) {
debug(floppyPath, cdromPath);
// Setup floppy
if (floppyPath != null) {
try {
if (fs.existsSync(floppyPath) == false) { return 1; } // Floppy disk image does not exist
var stats = fs.statSync(floppyPath);
if ((stats.size % 512) != 0) { return 2; } // Invalid floppy disk image
obj.floppySize = stats.size;
obj.floppy = fs.openSync(floppyPath, 'r');
} catch (ex) { return 3; } // Unable to open floppy disk image
}
// Setup CDROM
if (cdromPath != null) {
try {
if (fs.existsSync(cdromPath) == false) { return 4; } // CDROM disk image does not exist
var stats = fs.statSync(cdromPath);
if ((stats.size % 512) != 0) { return 5; } // Invalid CDROM disk image
obj.cdromSize = stats.size;
obj.cdrom = fs.openSync(cdromPath, 'r');
} catch (ex) { return 6; } // Unable to open CDROM disk image
}
if ((obj.cdrom == null) && (obj.floppy == null)) { return 7; } // Can't do IDER with no disk images
return 0;
}
obj.Start = function () {
debug("IDER-Start");
// Get ready
obj.bytesToAmt = 0;
obj.bytesFromAmt = 0;
obj.inSequence = 0;
obj.outSequence = 0;
g_readQueue = [];
// Send first command, OPEN_SESSION
obj.SendCommand(0x40, webserver.common.ShortToStrX(obj.rx_timeout) + webserver.common.ShortToStrX(obj.tx_timeout) + webserver.common.ShortToStrX(obj.heartbeat) + webserver.common.IntToStrX(obj.version));
// Send sector stats
if (obj.sectorStats) {
obj.sectorStats(0, 0, obj.floppy ? (obj.floppySize >> 9) : 0);
obj.sectorStats(0, 1, obj.cdrom ? (obj.cdromSize >> 11) : 0);
}
}
obj.Stop = function () {
debug("IDER-Stop");
// Close the files
if (obj.floppy) { fs.close(obj.floppy); delete obj.floppy; }
if (obj.cdrom) { fs.close(obj.cdrom); delete obj.cdrom; }
// Clean up
obj.floppySize = 0;
obj.cdromSize = 0;
obj.floppyReady = false;
obj.cdromReady = false;
// Stop the redirection connection
obj.parent.Stop();
}
// Private method
obj.ProcessData = function (data) {
obj.bytesFromAmt += data.length;
obj.acc += data;
debug('IDER-ProcessData', obj.acc.length, webserver.common.rstr2hex(obj.acc));
// Process as many commands as possible
while (true) {
var len = obj.ProcessDataEx();
if (len == 0) return;
if (obj.inSequence != webserver.common.ReadIntX(obj.acc, 4)) {
debug('ERROR: Out of sequence', obj.inSequence, webserver.common.ReadIntX(obj.acc, 4));
obj.Stop();
return;
}
obj.inSequence++;
obj.acc = obj.acc.substring(len);
}
}
// Private method
obj.SendCommand = function (cmdid, data, completed, dma) {
if (data == null) { data = ''; }
var attributes = ((cmdid > 50) && (completed == true)) ? 2 : 0;
if (dma) { attributes += 1; }
var x = String.fromCharCode(cmdid, 0, 0, attributes) + webserver.common.IntToStrX(obj.outSequence++) + data;
obj.parent.xxSend(x);
obj.bytesToAmt += x.length;
if (cmdid != 0x4B) { debug('IDER-SendData', x.length, webserver.common.rstr2hex(x)); }
}
// CommandEndResponse (SCSI_SENSE)
obj.SendCommandEndResponse = function (error, sense, device, asc, asq) {
if (error) { obj.SendCommand(0x51, String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc5, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0), true); }
else { obj.SendCommand(0x51, String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87, (sense << 4), 3, 0, 0, 0, device, 0x51, sense, asc, asq), true); }
}
// DataToHost (SCSI_READ)
obj.SendDataToHost = function (device, completed, data, dma) {
var dmalen = (dma) ? 0 : data.length;
if (completed == true) {
obj.SendCommand(0x54, String.fromCharCode(0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0x85, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0, 0, 0, 0) + data, completed, dma);
} else {
obj.SendCommand(0x54, String.fromCharCode(0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + data, completed, dma);
}
}
// GetDataFromHost (SCSI_CHUNK)
obj.SendGetDataFromHost = function (device, chunksize) {
obj.SendCommand(0x52, String.fromCharCode(0, (chunksize & 0xff), (chunksize >> 8), 0, 0xb5, 0, 0, 0, (chunksize & 0xff), (chunksize >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), false);
}
// DisableEnableFeatures (STATUS_DATA)
// If type is REGS_TOGGLE (3), 4 bytes of data must be provided.
obj.SendDisableEnableFeatures = function (type, data) { if (data == null) { data = ''; } obj.SendCommand(0x48, String.fromCharCode(type) + data); }
// Private method
obj.ProcessDataEx = function () {
if (obj.acc.length < 8) return 0;
// First 8 bytes are the header
// CommandID + 0x000000 + Sequence Number
switch(obj.acc.charCodeAt(0)) {
case 0x41: // OPEN_SESSION
if (obj.acc.length < 30) return 0;
var len = obj.acc.charCodeAt(29);
if (obj.acc.length < (30 + len)) return 0;
obj.iderinfo = {};
obj.iderinfo.major = obj.acc.charCodeAt(8);
obj.iderinfo.minor = obj.acc.charCodeAt(9);
obj.iderinfo.fwmajor = obj.acc.charCodeAt(10);
obj.iderinfo.fwminor = obj.acc.charCodeAt(11);
obj.iderinfo.readbfr = webserver.common.ReadShortX(obj.acc, 16);
obj.iderinfo.writebfr = webserver.common.ReadShortX(obj.acc, 18);
obj.iderinfo.proto = obj.acc.charCodeAt(21);
obj.iderinfo.iana = webserver.common.ReadIntX(obj.acc, 25);
debug(obj.iderinfo);
if (obj.iderinfo.proto != 0) {
debug("Unknown proto", obj.iderinfo.proto);
obj.Stop();
}
if (obj.iderinfo.readbfr > 8192) {
debug("Illegal read buffer size", obj.iderinfo.readbfr);
obj.Stop();
}
if (obj.iderinfo.writebfr > 8192) {
debug("Illegal write buffer size", obj.iderinfo.writebfr);
obj.Stop();
}
if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, webserver.common.IntToStrX(0x01 + 0x08)); } // OnReboot
else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, webserver.common.IntToStrX(0x01 + 0x10)); } // Graceful
else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, webserver.common.IntToStrX(0x01 + 0x18)); } // Now
//obj.SendDisableEnableFeatures(1); // GetSupportedFeatures
return 30 + len;
case 0x43: // CLOSE
debug('CLOSE');
obj.Stop();
return 8;
case 0x44: // KEEPALIVEPING
obj.SendCommand(0x45); // Send PONG back
return 8;
case 0x45: // KEEPALIVEPONG
debug('PONG');
return 8;
case 0x46: // RESETOCCURED
if (obj.acc.length < 9) return 0;
var resetMask = obj.acc.charCodeAt(8);
if (g_media === null) {
// No operations are pending
obj.SendCommand(0x47); // Send ResetOccuredResponse
debug('RESETOCCURED1', resetMask);
} else {
// Operations are being done, sent the reset once completed.
g_reset = true;
debug('RESETOCCURED2', resetMask);
}
return 9;
case 0x49: // STATUS_DATA - DisableEnableFeaturesReply
if (obj.acc.length < 13) return 0;
var type = obj.acc.charCodeAt(8);
var value = webserver.common.ReadIntX(obj.acc, 9);
debug('STATUS_DATA', type, value);
switch (type)
{
case 1: // REGS_AVAIL
if (value & 1) {
if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, webserver.common.IntToStrX(0x01 + 0x08)); } // OnReboot
else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, webserver.common.IntToStrX(0x01 + 0x10)); } // Graceful
else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, webserver.common.IntToStrX(0x01 + 0x18)); } // Now
}
break;
case 2: // REGS_STATUS
obj.enabled = (value & 2) ? true : false;
debug("IDER Status: " + obj.enabled);
break;
case 3: // REGS_TOGGLE
if (value != 1) {
debug("Register toggle failure");
} //else { obj.SendDisableEnableFeatures(2); }
break;
}
return 13;
case 0x4A: // ERROR OCCURED
if (obj.acc.length < 11) return 0;
debug('IDER: ABORT', obj.acc.charCodeAt(8));
//obj.Stop();
return 11;
case 0x4B: // HEARTBEAT
//debug('HEARTBEAT');
return 8;
case 0x50: // COMMAND WRITTEN
if (obj.acc.length < 28) return 0;
var device = (obj.acc.charCodeAt(14) & 0x10) ? 0xB0 : 0xA0;
var deviceFlags = obj.acc.charCodeAt(14);
var cdb = obj.acc.substring(16, 28);
var featureRegister = obj.acc.charCodeAt(9);
debug('SCSI_CMD', device, webserver.common.rstr2hex(cdb), featureRegister, deviceFlags);
handleSCSI(device, cdb, featureRegister, deviceFlags);
return 28;
case 0x53: // DATA FROM HOST
if (obj.acc.length < 14) return 0;
var len = webserver.common.ReadShortX(obj.acc, 9);
if (obj.acc.length < (14 + len)) return 0;
debug('SCSI_WRITE, len = ' + (14 + len));
obj.SendCommand(0x51, String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x70, 0x03, 0x00, 0x00, 0x00, 0xa0, 0x51, 0x07, 0x27, 0x00), true);
return 14 + len;
default:
debug('Unknown IDER command', obj.acc[0]);
obj.Stop();
break;
}
return 0;
}
function handleSCSI(dev, cdb, featureRegister, deviceFlags)
{
var lba;
var len;
switch(cdb.charCodeAt(0))
{
case 0x00: // TEST_UNIT_READY:
debug("SCSI: TEST_UNIT_READY", dev);
switch (dev) {
case 0xA0: // DEV_FLOPPY
if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.floppyReady == false) { obj.floppyReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
break;
case 0xB0: // DEV_CDDVD
if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.cdromReady == false) { obj.cdromReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
break;
default:
debug("SCSI Internal error 3", dev);
return -1;
}
obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); // Indicate ready
break;
case 0x08: // READ_6
lba = ((cdb.charCodeAt(1) & 0x1f) << 16) + (cdb.charCodeAt(2) << 8) + cdb.charCodeAt(3);
len = cdb.charCodeAt(4);
if (len == 0) { len = 256; }
debug("SCSI: READ_6", dev, lba, len);
sendDiskData(dev, lba, len, featureRegister);
break;
case 0x0a: // WRITE_6
lba = ((cdb.charCodeAt(1) & 0x1f) << 16) + (cdb.charCodeAt(2) << 8) + cdb.charCodeAt(3);
len = cdb.charCodeAt(4);
if (len == 0) { len = 256; }
debug("SCSI: WRITE_6", dev, lba, len);
obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); // Write is not supported, remote no medium.
return -1;
/*
case 0x15: // MODE_SELECT_6:
debug("SCSI ERROR: MODE_SELECT_6", dev);
obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
return -1;
*/
case 0x1a: // MODE_SENSE_6
debug("SCSI: MODE_SENSE_6", dev);
if ((cdb.charCodeAt(2) == 0x3f) && (cdb.charCodeAt(3) == 0x00)) {
var a = 0, b = 0;
switch (dev) {
case 0xA0: // DEV_FLOPPY
if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
a = 0x00;
b = 0x80; // Read only = 0x80, Read write = 0x00
break;
case 0xB0: // DEV_CDDVD
if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
a = 0x05;
b = 0x80;
break;
default:
debug("SCSI Internal error 6", dev);
return -1;
}
obj.SendDataToHost(dev, true, String.fromCharCode(0, a, b, 0), featureRegister & 1);
return;
}
obj.SendCommandEndResponse(1, 0x05, dev, 0x24, 0x00);
break;
case 0x1b: // START_STOP (Called when you eject the CDROM)
//var immediate = cdb.charCodeAt(1) & 0x01;
//var loej = cdb.charCodeAt(4) & 0x02;
//var start = cdb.charCodeAt(4) & 0x01;
obj.SendCommandEndResponse(1, 0, dev);
break;
case 0x1e: // LOCK_UNLOCK - ALLOW_MEDIUM_REMOVAL
debug("SCSI: ALLOW_MEDIUM_REMOVAL", dev);
if ((dev == 0xA0) && (obj.floppy == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
if ((dev == 0xB0) && (obj.cdrom == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00);
break;
case 0x23: // READ_FORMAT_CAPACITIES (Floppy only)
debug("SCSI: READ_FORMAT_CAPACITIES", dev);
var buflen = webserver.common.ReadShort(cdb, 7);
var mediaStatus = 0, sectors;
var mcSize = buflen / 8; // Capacity descriptor size is 8
switch (dev) {
case 0xA0: // DEV_FLOPPY
if ((obj.floppy == null) || (obj.floppySize == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
sectors = (obj.floppySize >> 9) - 1;
break;
case 0xB0: // DEV_CDDVD
if ((obj.cdrom == null) || (obj.cdromSize == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
sectors = (obj.cdromSize >> 11) - 1; // Number 2048 byte blocks
break;
default:
debug("SCSI Internal error 4", dev);
return -1;
}
obj.SendDataToHost(dev, true, webserver.common.IntToStr(8) + String.fromCharCode(0x00, 0x00, 0x0b, 0x40, 0x02, 0x00, 0x02, 0x00), featureRegister & 1);
break;
case 0x25: // READ_CAPACITY
debug("SCSI: READ_CAPACITY", dev);
var len = 0;
switch(dev)
{
case 0xA0: // DEV_FLOPPY
if ((obj.floppy == null) || (obj.floppySize == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.floppy != null) { len = (obj.floppySize >> 9) - 1; }
debug('DEV_FLOPPY', len); // Number 512 byte blocks
break;
case 0xB0: // DEV_CDDVD
if ((obj.floppy == null) || (obj.floppySize == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.cdrom != null) { len = (obj.cdromSize >> 11) - 1; } // Number 2048 byte blocks
debug('DEV_CDDVD', len);
break;
default:
debug("SCSI Internal error 4", dev);
return -1;
}
//if (dev == 0xA0) { dev = 0x00; } else { dev = 0x10; } // Weird but seems to work.
debug("SCSI: READ_CAPACITY2", dev, deviceFlags);
obj.SendDataToHost(deviceFlags, true, webserver.common.IntToStr(len) + String.fromCharCode(0, 0, ((dev == 0xB0) ? 0x08 : 0x02), 0), featureRegister & 1);
break;
case 0x28: // READ_10
lba = webserver.common.ReadInt(cdb, 2);
len = webserver.common.ReadShort(cdb, 7);
debug("SCSI: READ_10", dev, lba, len);
sendDiskData(dev, lba, len, featureRegister);
break;
case 0x2a: // WRITE_10 (Floppy only)
case 0x2e: // WRITE_AND_VERIFY (Floppy only)
lba = webserver.common.ReadInt(cdb, 2);
len = webserver.common.ReadShort(cdb, 7);
debug("SCSI: WRITE_10", dev, lba, len);
obj.SendGetDataFromHost(dev, 512 * len); // Floppy writes only, accept sectors of 512 bytes
break;
case 0x43: // READ_TOC (CD Audio only)
var buflen = webserver.common.ReadShort(cdb, 7);
var msf = cdb.charCodeAt(1) & 0x02;
var format = cdb.charCodeAt(2) & 0x07;
if (format == 0) { format = cdb.charCodeAt(9) >> 6; }
debug("SCSI: READ_TOC, dev=" + dev + ", buflen=" + buflen + ", msf=" + msf + ", format=" + format);
switch (dev) {
case 0xA0: // DEV_FLOPPY
obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00); // Not implemented
return -1;
case 0xB0: // DEV_CDDVD
// NOP
break;
default:
debug("SCSI Internal error 9", dev);
return -1;
}
if (format == 1) { obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x0a, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00), featureRegister & 1); }
else if (format == 0) {
if (msf) {
obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x34, 0x13), featureRegister & 1);
} else {
obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00), featureRegister & 1);
}
}
break;
case 0x46: // GET_CONFIGURATION
var sendall = (cdb.charCodeAt(1) != 2);
var firstcode = webserver.common.ReadShort(cdb, 2);
var buflen = webserver.common.ReadShort(cdb, 7);
debug("SCSI: GET_CONFIGURATION", dev, sendall, firstcode, buflen);
if (buflen == 0) { obj.SendDataToHost(dev, true, webserver.common.IntToStr(0x003c) + webserver.common.IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
// Set the header
var r = webserver.common.IntToStr(0x0008);
// Add the data
if (firstcode == 0) { r += IDE_CD_ConfigArrayProfileList; }
if ((firstcode == 0x1) || (sendall && (firstcode < 0x1))) { r += IDE_CD_ConfigArrayCore; }
if ((firstcode == 0x2) || (sendall && (firstcode < 0x2))) { r += IDE_CD_Morphing; }
if ((firstcode == 0x3) || (sendall && (firstcode < 0x3))) { r += IDE_CD_ConfigArrayRemovable; }
if ((firstcode == 0x10) || (sendall && (firstcode < 0x10))) { r += IDE_CD_ConfigArrayRandom; }
if ((firstcode == 0x1E) || (sendall && (firstcode < 0x1E))) { r += IDE_CD_Read; }
if ((firstcode == 0x100) || (sendall && (firstcode < 0x100))) { r += IDE_CD_PowerManagement; }
if ((firstcode == 0x105) || (sendall && (firstcode < 0x105))) { r += IDE_CD_Timeout; }
// Set the length
r = webserver.common.IntToStr(r.length) + r;
// Cut the length to buflen if needed
if (r.length > buflen) { r = r.substring(0, buflen); }
obj.SendDataToHost(dev, true, r, featureRegister & 1);
return -1;
case 0x4a: // GET_EV_STATUS - GET_EVENT_STATUS_NOTIFICATION
//var buflen = (cdb.charCodeAt(7) << 8) + cdb.charCodeAt(8);
//if (buflen == 0) { obj.SendDataToHost(dev, true, webserver.common.IntToStr(0x003c) + webserver.common.IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
debug("SCSI: GET_EVENT_STATUS_NOTIFICATION", dev, cdb.charCodeAt(1), cdb.charCodeAt(4), cdb.charCodeAt(9));
if ((cdb.charCodeAt(1) != 0x01) && (cdb.charCodeAt(4) != 0x10)) {
debug('SCSI ERROR');
obj.SendCommandEndResponse(1, 0x05, dev, 0x26, 0x01);
break;
}
var present = 0x00;
if ((dev == 0xA0) && (obj.floppy != null)) { present = 0x02; }
else if ((dev == 0xB0) && (obj.cdrom != null)) { present = 0x02; }
obj.SendDataToHost(dev, true, String.fromCharCode(0x00, present, 0x80, 0x00), featureRegister & 1); // This is the original version, 4 bytes long
break;
case 0x4c:
obj.SendCommand(0x51, webserver.common.IntToStrX(0) + webserver.common.IntToStrX(0) + webserver.common.IntToStrX(0) + String.fromCharCode(0x87, 0x50, 0x03, 0x00, 0x00, 0x00, 0xb0, 0x51, 0x05, 0x20, 0x00), true);
break;
case 0x51: // READ_DISC_INFO
debug("SCSI READ_DISC_INFO", dev);
obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // Correct
return -1;
case 0x55: // MODE_SELECT_10:
debug("SCSI ERROR: MODE_SELECT_10", dev);
obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
return -1;
case 0x5a: // MODE_SENSE_10
debug("SCSI: MODE_SENSE_10", dev, cdb.charCodeAt(2) & 0x3f);
var buflen = webserver.common.ReadShort(cdb, 7);
//var pc = cdb.charCodeAt(2) & 0xc0;
var r = null;
if (buflen == 0) { obj.SendDataToHost(dev, true, webserver.common.IntToStr(0x003c) + webserver.common.IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
// 1.44 mb floppy or LS120 (sectorCount == 0x3c300)
var sectorCount = 0;
if (dev == 0xA0) {
if (obj.floppy != null) { sectorCount = (obj.floppySize >> 9); }
} else {
if (obj.cdrom != null) { sectorCount = (obj.cdromSize >> 11); }
}
switch (cdb.charCodeAt(2) & 0x3f) {
case 0x01: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_FloppyError_Recovery_Array:IDE_ModeSence_Ls120Error_Recovery_Array; } else { r = IDE_ModeSence_CDError_Recovery_Array; } break;
case 0x05: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_FloppyDisk_Page_Array:IDE_ModeSence_LS120Disk_Page_Array; } break;
case 0x3f: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_3F_Floppy_Array:IDE_ModeSence_3F_LS120_Array; } else { r = IDE_ModeSence_3F_CD_Array; } break;
case 0x1A: if (dev == 0xB0) { r = IDE_ModeSence_CD_1A_Array; } break;
case 0x1D: if (dev == 0xB0) { r = IDE_ModeSence_CD_1D_Array; } break;
case 0x2A: if (dev == 0xB0) { r = IDE_ModeSence_CD_2A_Array; } break;
}
if (r == null) {
obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // TODO: Send proper error!!!
} else {
// Set disk to read only (we don't support write).
//ms_data[3] = ms_data[3] | 0x80;
obj.SendDataToHost(dev, true, r, featureRegister & 1);
}
break;
default: // UNKNOWN COMMAND
debug("IDER: Unknown SCSI command", cdb.charCodeAt(0));
obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00);
return -1;
}
return 0;
}
function sendDiskData(dev, lba, len, featureRegister) {
var media = null;
var mediaBlocks = 0;
if (dev == 0xA0) { media = obj.floppy; if (obj.floppy != null) { mediaBlocks = (obj.floppySize >> 9); } }
if (dev == 0xB0) { media = obj.cdrom; if (obj.cdrom != null) { mediaBlocks = (obj.cdromSize >> 11); } }
if ((len < 0) || (lba + len > mediaBlocks)) { obj.SendCommandEndResponse(1, 0x05, dev, 0x21, 0x00); return 0; }
if (len == 0) { obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); return 0; }
if (media != null) {
// Send sector stats
if (obj.sectorStats) { obj.sectorStats(1, (dev == 0xA0) ? 0 : 1, mediaBlocks, lba, len); }
if (dev == 0xA0) { lba <<= 9; len <<= 9; } else { lba <<= 11; len <<= 11; }
if (g_media !== null) {
// Queue read operation
g_readQueue.push({ media: media, dev: dev, lba: lba, len: len, fr: featureRegister });
} else {
// obj.iderinfo.readbfr // TODO: MaxRead
g_media = media;
g_dev = dev;
g_lba = lba;
g_len = len;
sendDiskDataEx(featureRegister);
}
}
}
var g_readQueue = [], g_dev, g_lba, g_len, g_media = null, g_reset = false;
function sendDiskDataEx(featureRegister) {
var len = g_len, lba = g_lba;
if (g_len > obj.iderinfo.readbfr) { len = obj.iderinfo.readbfr; }
g_len -= len;
g_lba += len;
var buffer = new Buffer(len);
fs.read(g_media, buffer, 0, len, lba, function (error, bytesRead, buffer) {
obj.SendDataToHost(g_dev, (g_len == 0), buffer.toString('binary'), featureRegister & 1);
if ((g_len > 0) && (g_reset == false)) {
sendDiskDataEx(featureRegister);
} else {
g_media = null;
if (g_reset) { obj.SendCommand(0x47); g_readQueue = []; g_reset = false; } // Send ResetOccuredResponse
else if (g_readQueue.length > 0) { var op = g_readQueue.shift(); g_media = op.media; g_dev = op.dev; g_lba = op.lba; g_len = op.len; sendDiskDataEx(op.fr); } // Un-queue read operation
}
});
}
return obj;
}

195
amt/amt-ider.js Normal file
View File

@ -0,0 +1,195 @@
/**
* @description MeshCentral Server IDER handler
* @author Ylian Saint-Hilaire & Bryan Roe
* @copyright Intel Corporation 2018-2019
* @license Apache-2.0
* @version v0.0.1
*/
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
"use strict";
// Construct a MeshAgent object, called upon connection
module.exports.CreateAmtIderSession = function (parent, db, ws, req, args, domain, user) {
const fs = require('fs');
const path = require('path');
const common = parent.common;
const amtMeshRedirModule = require('./amt-redir-mesh.js');
const amtMeshIderModule = require('./amt-ider-module.js');
//console.log('New Server IDER session from ' + user.name);
var obj = {};
obj.user = user;
obj.domain = domain;
obj.ider = null;
// Disconnect this user
obj.close = function (arg) {
if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket
if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug(1, 'Hard disconnect'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
};
try {
// Check if the user is logged in
if (user == null) { try { ws.close(); } catch (e) { } return; }
// When data is received from the web socket
ws.on('message', processWebSocketData);
// If error, do nothing
ws.on('error', function (err) { console.log(err); obj.close(0); });
// If the web socket is closed
ws.on('close', function (req) {
// Close the IDER session
if (obj.ider) { obj.ider.Stop(); delete obj.ider; }
});
// We are all set, start receiving data
ws._socket.resume();
} catch (e) { console.log(e); }
// Process incoming web socket data from the browser
function processWebSocketData(msg) {
var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
if (common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
switch (command.action) {
case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; }
case 'start': {
// Get the list of disk images
var domainx = 'domain' + ((domain.id == '') ? '' : ('-' + domain.id));
var useridx = user._id.split('/')[2];
var userPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx);
// Look for a list of disk images for the user to select.
if (fs.existsSync(userPath)) {
// Do something
readFsRec(userPath, function (err, results) {
var floppyImages = [], cdromImages = [];
for (var i in results) {
if (results[i].toLowerCase().endsWith('.img')) { floppyImages.push(results[i].substring(userPath.length + 1)); }
else if (results[i].toLowerCase().endsWith('.iso')) { cdromImages.push(results[i].substring(userPath.length + 1)); }
}
var xx, sel = true, html = "<div style='margin:10px 5px 10px 5px'>Select disk images & start type.</div>";
// Floppy image selection
xx = "<select style=width:240px id=xxFloppyImagesSelect><option value=''>None</option>";
for (var i in floppyImages) { xx += "<option value='" + encodeURIComponent(floppyImages[i]) + "'" + (sel?" selected":"") + ">" + floppyImages[i] + "</option>"; sel = false; }
xx += "</select>";
html += "<div style=margin:5px>" + addHtmlValue("Floppy Image", xx) + "</div>";
// CDROM image selection
sel = true;
xx = "<select style=width:240px id=xxCdromImagesSelect><option value=''>None</option>";
for (var i in cdromImages) { xx += "<option value='" + encodeURIComponent(cdromImages[i]) + "'" + (sel ? " selected" : "") + ">" + cdromImages[i] + "</option>"; sel = false; }
xx += "</select>";
html += "<div style=margin:5px>" + addHtmlValue("CDROM Image", xx) + "</div>";
// Start type
xx = "<select style=width:240px id=xxIderStartType><option value=0>On next boot<option value=1>Graceful<option value=2>Immediate</select>";
html += "<div style=margin:5px>" + addHtmlValue("Session Start", xx) + "</div>";
var js = "function iderServerCall() { return { ider: 1, floppyPath: Q('xxFloppyImagesSelect').value, cdromPath: Q('xxCdromImagesSelect').value, iderStart: Q('xxIderStartType').value }; }";
try { ws.send(JSON.stringify({ action: 'dialog', args: { html: html, js: js }, buttons: 3 })); } catch (ex) { }
});
} else {
// No user folder
try { ws.send(JSON.stringify({ action: 'dialog', args: { html: 'No disk images found on remote server. Upload .img and .iso files to server "My Files" folder to enable this feature.' }, buttons: 2 })); } catch (ex) { }
}
break;
}
case 'dialogResponse': {
if (command.args.ider == 1) { // Start IDER Session
// Decode and validate file paths
if ((command.args.floppyPath != null) && (typeof command.args.floppyPath != 'string')) { command.args.floppyPath = null; } else { command.args.floppyPath = decodeURIComponent(command.args.floppyPath); }
if ((command.args.cdromPath != null) && (typeof command.args.cdromPath != 'string')) { command.args.cdromPath = null; } else { command.args.cdromPath = decodeURIComponent(command.args.cdromPath); }
// TODO: Double check that "." or ".." are not used.
if ((command.args.floppyPath != null) && (command.args.floppyPath.indexOf("..") >= 0)) { delete command.args.floppyPath; }
if ((command.args.cdromPath != null) && (command.args.cdromPath.indexOf("..") >= 0)) { delete command.args.cdromPath; }
// Get the disk image paths
var domainx = 'domain' + ((domain.id == '') ? '' : ('-' + domain.id));
var useridx = user._id.split('/')[2];
var floppyPath = null, cdromPath = null;
if (command.args.floppyPath) { floppyPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx, command.args.floppyPath); }
if (command.args.cdromPath) { cdromPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx, command.args.cdromPath); }
// Setup the IDER session
obj.ider = amtMeshRedirModule.CreateAmtRedirect(amtMeshIderModule.CreateAmtRemoteIder(parent, parent.parent), domain, user, parent, parent.parent);
obj.ider.onStateChanged = onIderStateChange;
obj.ider.m.iderStart = command.args.iderStart;
obj.ider.m.sectorStats = iderSectorStats;
obj.ider.tlsv1only = req.query.tlsv1only;
// Setup disk images
var iderError = obj.ider.m.diskSetup(floppyPath, cdromPath);
// Error with the disk images, unable to start IDER
if (iderError != 0) { try { ws.send(JSON.stringify({ action: "error", code: iderError })); } catch (ex) { } break; }
// Start the IDER session
obj.ider.Start(req.query.host, req.query.port, req.query.tls);
}
break;
}
default: {
// Unknown user action
console.log('Unknown IDER action from user ' + user.name + ': ' + command.action + '.');
break;
}
}
}
function onIderStateChange(sender, state) {
try { ws.send(JSON.stringify({ action: 'state', state: state })); } catch (ex) { }
switch (state) {
case 0:
// Close the websocket connection and clean up.
obj.ider.onStateChanged = null;
obj.ider.m.sectorStats = null;
obj.ider = null;
obj.close();
break;
}
}
function iderSectorStats(mode, dev, total, start, len) {
try { ws.send(JSON.stringify({ action: 'stats', mode: mode, dev: dev, total: total, start: start, len: len, toAmt: obj.ider.m.bytesToAmt, fromAmt: obj.ider.m.bytesFromAmt })); } catch (ex) { }
}
// Recursivly read all of the files in a fonder
function readFsRec(dir, func) {
var results = [];
fs.readdir(dir, function (err, list) {
if (err) return func(err);
var pending = list.length;
if (!pending) return func(null, results);
list.forEach(function (file) {
file = path.resolve(dir, file);
fs.stat(file, function (err, stat) {
if (stat && stat.isDirectory()) {
readFsRec(file, function (err, res) { results = results.concat(res); if (!--pending) func(null, results); });
} else {
results.push(file); if (!--pending) func(null, results);
}
});
});
});
};
function addHtmlValue(t, v) { return '<div style=height:20px><div style=float:right;width:240px;overflow:hidden><b title="' + v + '">' + v + '</b></div><div>' + t + '</div></div>'; }
return obj;
};

508
amt/amt-redir-mesh.js Normal file
View File

@ -0,0 +1,508 @@
/**
* @description Intel AMT Redirection Transport Module - using Node
* @author Ylian Saint-Hilaire
* @version v0.0.1f
*/
// Construct a MeshServer object
module.exports.CreateAmtRedirect = function (module, domain, user, webserver, meshcentral) {
var obj = {};
obj.m = module; // This is the inner module (Terminal or Desktop)
module.parent = obj;
obj.State = 0;
obj.net = require('net');
obj.tls = require('tls');
obj.crypto = require('crypto');
const constants = require('constants');
obj.socket = null;
obj.amtuser = null;
obj.amtpass = null;
obj.connectstate = 0;
obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER
obj.xtlsoptions = null;
obj.redirTrace = false;
obj.tls1only = 0; // TODO
obj.amtaccumulator = "";
obj.amtsequence = 1;
obj.amtkeepalivetimer = null;
obj.authuri = "/RedirectionService";
obj.onStateChanged = null;
obj.forwardclient = null;
// Mesh Rights
const MESHRIGHT_EDITMESH = 1;
const MESHRIGHT_MANAGEUSERS = 2;
const MESHRIGHT_MANAGECOMPUTERS = 4;
const MESHRIGHT_REMOTECONTROL = 8;
const MESHRIGHT_AGENTCONSOLE = 16;
const MESHRIGHT_SERVERFILES = 32;
const MESHRIGHT_WAKEDEVICE = 64;
const MESHRIGHT_SETNOTES = 128;
// Site rights
const SITERIGHT_SERVERBACKUP = 1;
const SITERIGHT_MANAGEUSERS = 2;
const SITERIGHT_SERVERRESTORE = 4;
const SITERIGHT_FILEACCESS = 8;
const SITERIGHT_SERVERUPDATE = 16;
const SITERIGHT_LOCKED = 32;
function Debug(lvl) {
if ((arguments.length < 2) || (lvl > meshcentral.debugLevel)) return;
var a = []; for (var i = 1; i < arguments.length; i++) { a.push(arguments[i]); } console.log(...a);
}
// Older NodeJS does not support the keyword "class", so we do without using this syntax
// TODO: Validate that it's the same as above and that it works.
function SerialTunnel(options) {
var obj = new require('stream').Duplex(options);
obj.forwardwrite = null;
obj.updateBuffer = function (chunk) { this.push(chunk); };
obj._write = function (chunk, encoding, callback) { if (obj.forwardwrite != null) { obj.forwardwrite(chunk); } else { console.err("Failed to fwd _write."); } if (callback) callback(); }; // Pass data written to forward
obj._read = function (size) { }; // Push nothing, anything to read should be pushed from updateBuffer()
return obj;
}
obj.Start = function (nodeid) {
//console.log('Amt-Redir-Start', nodeid);
obj.connectstate = 0;
Debug(1, 'AMT redir for ' + user.name + ' to ' + nodeid + '.');
obj.xxStateChange(1);
// Fetch information about the target
meshcentral.db.Get(nodeid, function (err, docs) {
if (docs.length == 0) { console.log('ERR: Node not found'); obj.Stop(); return; }
var node = docs[0];
if (!node.intelamt) { console.log('ERR: Not AMT node'); obj.Stop(); return; }
obj.amtuser = node.intelamt.user;
obj.amtpass = node.intelamt.pass;
// Check if this user has permission to manage this computer
var meshlinks = user.links[node.meshid];
if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); obj.Stop(); return; }
// Check what connectivity is available for this node
var state = meshcentral.GetConnectivityState(nodeid);
var conn = 0;
if (!state || state.connectivity == 0) { Debug(1, 'ERR: No routing possible (1)'); obj.Stop(); return; } else { conn = state.connectivity; }
/*
// Check what server needs to handle this connection
if ((meshcentral.multiServer != null) && (cookie == null)) { // If a cookie is provided, don't allow the connection to jump again to a different server
var server = obj.parent.GetRoutingServerId(nodeid, 2); // Check for Intel CIRA connection
if (server != null) {
if (server.serverid != obj.parent.serverId) {
// Do local Intel CIRA routing using a different server
Debug(1, 'Route Intel AMT CIRA connection to peer server: ' + server.serverid);
obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
return;
}
} else {
server = obj.parent.GetRoutingServerId(nodeid, 4); // Check for local Intel AMT connection
if ((server != null) && (server.serverid != obj.parent.serverId)) {
// Do local Intel AMT routing using a different server
Debug(1, 'Route Intel AMT direct connection to peer server: ' + server.serverid);
obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
return;
}
}
}
*/
// If Intel AMT CIRA connection is available, use it
if (((conn & 2) != 0) && (meshcentral.mpsserver.ciraConnections[nodeid] != null)) {
Debug(1, 'Opening Intel AMT CIRA transport connection to ' + nodeid + '.');
var ciraconn = meshcentral.mpsserver.ciraConnections[nodeid];
// Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
var port = 16995;
if (ciraconn.tag.boundPorts.indexOf(16994) >= 0) port = 16994; // RELEASE: Always use non-TLS mode if available within CIRA
// Setup a new CIRA channel
if ((port == 16993) || (port == 16995)) {
// Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why. Well, could be broken TLS 1.0 in firmware )
var ser = new SerialTunnel();
var chnl = meshcentral.mpsserver.SetupCiraChannel(ciraconn, port);
// let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
// Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
ser.forwardwrite = function (msg) {
// TLS ---> CIRA
chnl.write(msg.toString('binary'));
};
// When APF tunnel return something, update SerialTunnel buffer
chnl.onData = function (ciraconn, data) {
// CIRA ---> TLS
Debug(3, 'Relay TLS CIRA data', data.length);
if (data.length > 0) { try { ser.updateBuffer(Buffer.from(data, 'binary')); } catch (e) { } }
};
// Handle CIRA tunnel state change
chnl.onStateChange = function (ciraconn, state) {
Debug(2, 'Relay TLS CIRA state change', state);
if (state == 0) { try { ws.close(); } catch (e) { } }
};
// TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
const TLSSocket = require('tls').TLSSocket;
const tlsoptions = { secureProtocol: ((obj.tls1only == 1) ? 'TLSv1_method' : 'SSLv23_method'), ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
const tlsock = new TLSSocket(ser, tlsoptions);
tlsock.on('error', function (err) { Debug(1, "CIRA TLS Connection Error ", err); });
tlsock.on('secureConnect', function () { Debug(2, "CIRA Secure TLS Connection"); ws._socket.resume(); });
// Decrypted tunnel from TLS communcation to be forwarded to websocket
tlsock.on('data', function (data) {
// AMT/TLS ---> WS
try {
data = data.toString('binary');
//ws.send(Buffer.from(data, 'binary'));
ws.send(data);
} catch (e) { }
});
// If TLS is on, forward it through TLSSocket
obj.forwardclient = tlsock;
obj.forwardclient.xtls = 1;
} else {
// Without TLS
obj.forwardclient = meshcentral.mpsserver.SetupCiraChannel(ciraconn, port);
obj.forwardclient.xtls = 0;
}
obj.forwardclient.onStateChange = function (ciraconn, state) {
Debug(2, 'Intel AMT CIRA relay state change', state);
if (state == 0) { try { obj.Stop(); } catch (e) { } }
else if (state == 2) { obj.xxOnSocketConnected(); }
};
obj.forwardclient.onData = function (ciraconn, data) {
Debug(4, 'Intel AMT CIRA data', data.length);
if (data.length > 0) { obj.xxOnSocketData(data); } // TODO: Add TLS support
};
obj.forwardclient.onSendOk = function (ciraconn) {
// TODO: Flow control? (Dont' really need it with AMT, but would be nice)
Debug(4, 'Intel AMT CIRA sendok');
};
return;
}
// If Intel AMT direct connection is possible, option a direct socket
if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
Debug(1, 'Opening Intel AMT transport connection to ' + nodeid + '.');
// Compute target port
var port = 16994;
if (node.intelamt.tls > 0) port = 16995; // This is a direct connection, use TLS when possible
if (node.intelamt.tls != 1) {
// If this is TCP (without TLS) set a normal TCP socket
obj.forwardclient = new obj.net.Socket();
obj.forwardclient.setEncoding('binary');
} else {
// If TLS is going to be used, setup a TLS socket
var tlsoptions = { secureProtocol: ((obj.tls1only == 1) ? 'TLSv1_method' : 'SSLv23_method'), ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
obj.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
// The TLS connection method is the same as TCP, but located a bit differently.
Debug(2, 'TLS Intel AMT transport connected to ' + node.host + ':' + port + '.');
obj.xxOnSocketConnected();
});
obj.forwardclient.setEncoding('binary');
}
// When we receive data on the TCP connection, forward it back into the web socket connection.
obj.forwardclient.on('data', function (data) {
//if (obj.parent.debugLevel >= 1) { // DEBUG
Debug(1, 'Intel AMT transport data from ' + node.host + ', ' + data.length + ' bytes.');
Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex'));
//if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
//}
obj.xxOnSocketData(data);
});
// If the TCP connection closes, disconnect the associated web socket.
obj.forwardclient.on('close', function () {
Debug(1, 'Intel AMT transport relay disconnected from ' + node.host + '.');
obj.Stop();
});
// If the TCP connection causes an error, disconnect the associated web socket.
obj.forwardclient.on('error', function (err) {
Debug(1, 'Intel AMT transport relay error from ' + node.host + ': ' + err.errno);
obj.Stop();
});
if (node.intelamt.tls == 0) {
// A TCP connection to Intel AMT just connected, start forwarding.
obj.forwardclient.connect(port, node.host, function () {
Debug(1, 'Intel AMT transport connected to ' + node.host + ':' + port + '.');
obj.xxOnSocketConnected();
});
}
return;
}
});
}
// Get the certificate of Intel AMT
obj.getPeerCertificate = function () { if (obj.xtls == true) { return obj.socket.getPeerCertificate(); } return null; }
obj.xxOnSocketConnected = function () {
//console.log('xxOnSocketConnected');
if (!obj.xtlsoptions || !obj.xtlsoptions.meshServerConnect) {
if (obj.xtls == true) {
obj.xtlsCertificate = obj.socket.getPeerCertificate();
if ((obj.xtlsFingerprint != 0) && (obj.xtlsCertificate.fingerprint.split(':').join('').toLowerCase() != obj.xtlsFingerprint)) { obj.Stop(); return; }
}
}
if (obj.redirTrace) { console.log("REDIR-CONNECTED"); }
//obj.Debug("Socket Connected");
obj.xxStateChange(2);
if (obj.protocol == 1) obj.xxSend(obj.RedirectStartSol); // TODO: Put these strings in higher level module to tighten code
if (obj.protocol == 2) obj.xxSend(obj.RedirectStartKvm); // Don't need these is the feature if not compiled-in.
if (obj.protocol == 3) obj.xxSend(obj.RedirectStartIder);
}
obj.xxOnSocketData = function (data) {
if (!data || obj.connectstate == -1) return;
if (obj.redirTrace) { console.log("REDIR-RECV(" + data.length + "): " + webserver.common.rstr2hex(data)); }
//obj.Debug("Recv(" + data.length + "): " + webserver.common.rstr2hex(data));
if ((obj.protocol > 1) && (obj.connectstate == 1)) { return obj.m.ProcessData(data); } // KVM traffic, forward it directly.
obj.amtaccumulator += data;
//obj.Debug("Recv(" + obj.amtaccumulator.length + "): " + webserver.common.rstr2hex(obj.amtaccumulator));
while (obj.amtaccumulator.length >= 1) {
var cmdsize = 0;
switch (obj.amtaccumulator.charCodeAt(0)) {
case 0x11: // StartRedirectionSessionReply (17)
if (obj.amtaccumulator.length < 4) return;
var statuscode = obj.amtaccumulator.charCodeAt(1);
switch (statuscode) {
case 0: // STATUS_SUCCESS
if (obj.amtaccumulator.length < 13) return;
var oemlen = obj.amtaccumulator.charCodeAt(12);
if (obj.amtaccumulator.length < 13 + oemlen) return;
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)); // Query authentication support
cmdsize = (13 + oemlen);
break;
default:
obj.Stop();
break;
}
break;
case 0x14: // AuthenticateSessionReply (20)
if (obj.amtaccumulator.length < 9) return;
var authDataLen = webserver.common.ReadIntX(obj.amtaccumulator, 5);
if (obj.amtaccumulator.length < 9 + authDataLen) return;
var status = obj.amtaccumulator.charCodeAt(1);
var authType = obj.amtaccumulator.charCodeAt(4);
var authData = [];
for (i = 0; i < authDataLen; i++) { authData.push(obj.amtaccumulator.charCodeAt(9 + i)); }
var authDataBuf = obj.amtaccumulator.substring(9, 9 + authDataLen);
cmdsize = 9 + authDataLen;
if (authType == 0) {
/*
// This is Kerberos code, not supported in MeshCentral.
if (obj.amtuser == '*') {
if (authData.indexOf(2) >= 0) {
// Kerberos Auth
var ticket;
if (kerberos && kerberos != null) {
var ticketReturn = kerberos.getTicket('HTTP' + ((obj.tls == 1)?'S':'') + '/' + ((obj.amtpass == '') ? (obj.host + ':' + obj.port) : obj.amtpass));
if (ticketReturn.returnCode == 0 || ticketReturn.returnCode == 0x90312) {
ticket = ticketReturn.ticket;
if (process.platform.indexOf('win') >= 0) {
// Clear kerberos tickets on both 32 and 64bit Windows platforms
try { require('child_process').exec('%windir%\\system32\\klist purge', function (error, stdout, stderr) { if (error) { require('child_process').exec('%windir%\\sysnative\\klist purge', function (error, stdout, stderr) { if (error) { console.error('Unable to purge kerberos tickets'); } }); } }); } catch (e) { console.log(e); }
}
} else {
console.error('Unexpected Kerberos error code: ' + ticketReturn.returnCode);
}
}
if (ticket) {
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x02) + webserver.common.IntToStrX(ticket.length) + ticket);
} else {
obj.Stop();
}
}
else obj.Stop();
} else {
*/
// Query
if (authData.indexOf(4) >= 0) {
// Good Digest Auth (With cnonce and all)
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x04) + webserver.common.IntToStrX(obj.amtuser.length + obj.authuri.length + 8) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00, 0x00));
}
else if (authData.indexOf(3) >= 0) {
// Bad Digest Auth (Not sure why this is supported, cnonce is not used!)
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x03) + webserver.common.IntToStrX(obj.amtuser.length + obj.authuri.length + 7) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00));
}
else if (authData.indexOf(1) >= 0) {
// Basic Auth (Probably a good idea to not support this unless this is an old version of Intel AMT)
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x01) + webserver.common.IntToStrX(obj.amtuser.length + obj.amtpass.length + 2) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(obj.amtpass.length) + obj.amtpass);
}
else obj.Stop();
/*
}
*/
}
else if ((authType == 3 || authType == 4) && status == 1) {
var curptr = 0;
// Realm
var realmlen = authDataBuf.charCodeAt(curptr);
var realm = authDataBuf.substring(curptr + 1, curptr + 1 + realmlen);
curptr += (realmlen + 1);
// Nonce
var noncelen = authDataBuf.charCodeAt(curptr);
var nonce = authDataBuf.substring(curptr + 1, curptr + 1 + noncelen);
curptr += (noncelen + 1);
// QOP
var qoplen = 0;
var qop = null;
var cnonce = obj.xxRandomValueHex(32);
var snc = '00000002';
var extra = '';
if (authType == 4) {
qoplen = authDataBuf.charCodeAt(curptr);
qop = authDataBuf.substring(curptr + 1, curptr + 1 + qoplen);
curptr += (qoplen + 1);
extra = snc + ":" + cnonce + ":" + qop + ":";
}
var digest = hex_md5(hex_md5(obj.amtuser + ":" + realm + ":" + obj.amtpass) + ":" + nonce + ":" + extra + hex_md5("POST:" + obj.authuri));
var totallen = obj.amtuser.length + realm.length + nonce.length + obj.authuri.length + cnonce.length + snc.length + digest.length + 7;
if (authType == 4) totallen += (qop.length + 1);
var buf = String.fromCharCode(0x13, 0x00, 0x00, 0x00, authType) + webserver.common.IntToStrX(totallen) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(realm.length) + realm + String.fromCharCode(nonce.length) + nonce + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(cnonce.length) + cnonce + String.fromCharCode(snc.length) + snc + String.fromCharCode(digest.length) + digest;
if (authType == 4) buf += (String.fromCharCode(qop.length) + qop);
obj.xxSend(buf);
}
else if (status == 0) { // Success
/*
if (obj.protocol == 1) {
// Serial-over-LAN: Send Intel AMT serial settings...
var MaxTxBuffer = 10000;
var TxTimeout = 100;
var TxOverflowTimeout = 0;
var RxTimeout = 10000;
var RxFlushTimeout = 100;
var Heartbeat = 0;//5000;
obj.xxSend(String.fromCharCode(0x20, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + ToShortStr(MaxTxBuffer) + ToShortStr(TxTimeout) + ToShortStr(TxOverflowTimeout) + ToShortStr(RxTimeout) + ToShortStr(RxFlushTimeout) + ToShortStr(Heartbeat) + ToIntStr(0));
}
if (obj.protocol == 2) {
// Remote Desktop: Send traffic directly...
obj.xxSend(String.fromCharCode(0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
}
*/
if (obj.protocol == 3) { // IDE-R
obj.connectstate = 1;
obj.m.Start();
if (obj.amtaccumulator.length > cmdsize) { obj.m.ProcessData(obj.amtaccumulator.substring(cmdsize)); }
cmdsize = obj.amtaccumulator.length;
}
} else obj.Stop();
break;
case 0x21: // Response to settings (33)
if (obj.amtaccumulator.length < 23) break;
cmdsize = 23;
obj.xxSend(String.fromCharCode(0x27, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + String.fromCharCode(0x00, 0x00, 0x1B, 0x00, 0x00, 0x00));
if (obj.protocol == 1) { obj.amtkeepalivetimer = setInterval(obj.xxSendAmtKeepAlive, 2000); }
obj.connectstate = 1;
obj.xxStateChange(3);
break;
case 0x29: // Serial Settings (41)
if (obj.amtaccumulator.length < 10) break;
cmdsize = 10;
break;
case 0x2A: // Incoming display data (42)
if (obj.amtaccumulator.length < 10) break;
var cs = (10 + ((obj.amtaccumulator.charCodeAt(9) & 0xFF) << 8) + (obj.amtaccumulator.charCodeAt(8) & 0xFF));
if (obj.amtaccumulator.length < cs) break;
obj.m.ProcessData(obj.amtaccumulator.substring(10, cs));
cmdsize = cs;
break;
case 0x2B: // Keep alive message (43)
if (obj.amtaccumulator.length < 8) break;
cmdsize = 8;
break;
case 0x41:
if (obj.amtaccumulator.length < 8) break;
obj.connectstate = 1;
obj.m.Start();
// KVM traffic, forward rest of accumulator directly.
if (obj.amtaccumulator.length > 8) { obj.m.ProcessData(obj.amtaccumulator.substring(8)); }
cmdsize = obj.amtaccumulator.length;
break;
default:
console.log("Unknown Intel AMT command: " + obj.amtaccumulator.charCodeAt(0) + " acclen=" + obj.amtaccumulator.length);
obj.Stop();
return;
}
if (cmdsize == 0) return;
obj.amtaccumulator = obj.amtaccumulator.substring(cmdsize);
}
}
obj.xxSend = function (x) {
if (obj.redirTrace) { console.log("REDIR-SEND(" + x.length + "): " + new Buffer(x, "binary").toString('hex'), typeof x); }
//obj.Debug("Send(" + x.length + "): " + webserver.common.rstr2hex(x));
//obj.forwardclient.write(x); // FIXES CIRA
obj.forwardclient.write(new Buffer(x, "binary"));
}
obj.Send = function (x) {
if (obj.forwardclient == null || obj.connectstate != 1) return;
if (obj.protocol == 1) { obj.xxSend(String.fromCharCode(0x28, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + ToShortStr(x.length) + x); } else { obj.xxSend(x); }
}
obj.xxSendAmtKeepAlive = function () {
if (obj.forwardclient == null) return;
obj.xxSend(String.fromCharCode(0x2B, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++));
}
obj.xxRandomValueHex = function(len) { return obj.crypto.randomBytes(Math.ceil(len / 2)).toString('hex').slice(0, len); }
obj.xxOnSocketClosed = function () {
if (obj.redirTrace) { console.log("REDIR-CLOSED"); }
//obj.Debug("Socket Closed");
obj.Stop();
}
obj.xxStateChange = function(newstate) {
if (obj.State == newstate) return;
obj.State = newstate;
obj.m.xxStateChange(obj.State);
if (obj.onStateChanged != null) obj.onStateChanged(obj, obj.State);
}
obj.Stop = function () {
if (obj.redirTrace) { console.log("REDIR-CLOSED"); }
//obj.Debug("Socket Stopped");
obj.xxStateChange(0);
obj.connectstate = -1;
obj.amtaccumulator = "";
if (obj.forwardclient != null) { try { obj.forwardclient.close(); } catch (ex) { } delete obj.forwardclient; }
if (obj.amtkeepalivetimer != null) { clearInterval(obj.amtkeepalivetimer); delete obj.amtkeepalivetimer; }
}
obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20);
obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52);
obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52);
function hex_md5(str) { return meshcentral.certificateOperations.forge.md.md5.create().update(str).digest().toHex(); }
return obj;
}
function ToIntStr(v) { return String.fromCharCode((v & 0xFF), ((v >> 8) & 0xFF), ((v >> 16) & 0xFF), ((v >> 24) & 0xFF)); }
function ToShortStr(v) { return String.fromCharCode((v & 0xFF), ((v >> 8) & 0xFF)); }

View File

@ -549,14 +549,47 @@ module.exports.CertificateOperations = function (parent) {
// Accelerators, used to dispatch work to other processes
const fork = require("child_process").fork;
const program = require("path").join(__dirname, "meshaccelerator.js");
const acceleratorTotalCount = require("os").cpus().length;
const acceleratorTotalCount = require("os").cpus().length; // TODO: Check if this accelerator can scale.
var acceleratorCreateCount = acceleratorTotalCount;
var freeAccelerators = [];
var pendingAccelerator = [];
obj.acceleratorCertStore = null;
// Accelerator Stats
var getAcceleratorFuncCalls = 0;
var acceleratorStartFuncCall = 0;
var acceleratorPerformSignatureFuncCall = 0;
var acceleratorPerformSignaturePushFuncCall = 0;
var acceleratorPerformSignatureRunFuncCall = 0;
var acceleratorMessage = 0;
var acceleratorMessageException = 0;
var acceleratorMessageLastException = null;
var acceleratorException = 0;
var acceleratorLastException = null;
// Get stats about the accelerators
obj.getAcceleratorStats = function () {
return {
acceleratorTotalCount: acceleratorTotalCount,
acceleratorCreateCount: acceleratorCreateCount,
freeAccelerators: freeAccelerators.length,
pendingAccelerator: pendingAccelerator.length,
getAcceleratorFuncCalls: getAcceleratorFuncCalls,
startFuncCall: acceleratorStartFuncCall,
performSignatureFuncCall: acceleratorPerformSignatureFuncCall,
performSignaturePushFuncCall: acceleratorPerformSignaturePushFuncCall,
performSignatureRunFuncCall: acceleratorPerformSignatureRunFuncCall,
message: acceleratorMessage,
messageException: acceleratorMessageException,
messageLastException: acceleratorMessageLastException,
exception: acceleratorException,
lastException: acceleratorLastException
};
}
// Create a new accelerator module
obj.getAccelerator = function () {
getAcceleratorFuncCalls++;
if (obj.acceleratorCertStore == null) { return null; }
if (freeAccelerators.length > 0) { return freeAccelerators.pop(); }
if (acceleratorCreateCount > 0) {
@ -564,23 +597,26 @@ module.exports.CertificateOperations = function (parent) {
var accelerator = fork(program, [], { stdio: ["pipe", "pipe", "pipe", "ipc"] });
accelerator.accid = acceleratorCreateCount;
accelerator.on("message", function (message) {
this.func(this.tag, message);
delete this.tag;
if (pendingAccelerator.length > 0) {
var x = pendingAccelerator.shift();
if (x.tag) { this.tag = x.tag; delete x.tag; }
accelerator.send(x);
} else { freeAccelerators.push(this); }
acceleratorMessage++;
this.x.func(this.x.tag, message);
delete this.x;
if (pendingAccelerator.length > 0) { this.send(this.x = pendingAccelerator.shift()); } else { freeAccelerators.push(this); }
});
accelerator.on("exit", function (code) {
if (this.x) { pendingAccelerator.push(this.x); delete this.x; }
acceleratorCreateCount++;
if (pendingAccelerator.length > 0) { var acc = obj.getAccelerator(); acc.send(acc.x = pendingAccelerator.shift()); }
});
accelerator.on("error", function (code) { }); // Not sure if somethign should be done here to help kill the process.
accelerator.send({ action: "setState", certs: obj.acceleratorCertStore });
return accelerator;
}
return null;
};
// Set the state of the accelerators. This way, we don"t have to send certificate & keys to them each time.
obj.acceleratorStart = function (certificates) {
acceleratorStartFuncCall++;
if (obj.acceleratorCertStore != null) { console.error("ERROR: Accelerators can only be started once."); return; }
obj.acceleratorCertStore = [{ cert: certificates.agent.cert, key: certificates.agent.key }];
if (certificates.swarmserver != null) { obj.acceleratorCertStore.push({ cert: certificates.swarmserver.cert, key: certificates.swarmserver.key }); }
@ -588,22 +624,23 @@ module.exports.CertificateOperations = function (parent) {
// Perform any RSA signature, just pass in the private key and data.
obj.acceleratorPerformSignature = function (privatekey, data, tag, func) {
acceleratorPerformSignatureFuncCall++;
if (acceleratorTotalCount <= 1) {
// No accelerators available
if (typeof privatekey == "number") { privatekey = obj.acceleratorCertStore[privatekey].key; }
const sign = obj.crypto.createSign("SHA384");
sign.end(Buffer.from(data, "binary"));
func(tag, sign.sign(privatekey).toString("binary"));
try { func(tag, sign.sign(privatekey).toString("binary")); } catch (ex) { acceleratorMessageException++; acceleratorMessageLastException = ex; }
} else {
var acc = obj.getAccelerator();
if (acc == null) {
// Add to pending accelerator workload
pendingAccelerator.push({ action: "sign", key: privatekey, data: data, tag: tag });
acceleratorPerformSignaturePushFuncCall++;
pendingAccelerator.push({ action: "sign", key: privatekey, data: data, tag: tag, func: func });
} else {
// Send to accelerator now
acc.func = func;
acc.tag = tag;
acc.send({ action: "sign", key: privatekey, data: data });
acceleratorPerformSignatureRunFuncCall++;
acc.send(acc.x = { action: "sign", key: privatekey, data: data, tag: tag, func: func });
}
}
};

View File

@ -128,10 +128,11 @@ module.exports.escapeHtml = function (string) { return String(string).replace(/[
module.exports.escapeHtmlBreaks = function (string) { return String(string).replace(/[&<>"'`=\/]/g, function (s) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;', '\r': '<br />', '\n': '' }[s]; }); };
// Lowercase all the names in a object recursively
module.exports.objKeysToLower = function (obj) {
// Allow for exception keys, child of exceptions will not get lower-cased.
module.exports.objKeysToLower = function (obj, exceptions) {
for (var i in obj) {
if (i.toLowerCase() !== i) { obj[i.toLowerCase()] = obj[i]; delete obj[i]; } // LowerCase all key names
if (typeof obj[i] == 'object') { module.exports.objKeysToLower(obj[i]); } // LowerCase all key names in the child object
if ((typeof obj[i] == 'object') && ((exceptions == null) || (exceptions.indexOf(i.toLowerCase()) == -1))) { module.exports.objKeysToLower(obj[i], exceptions); } // LowerCase all key names in the child object
}
return obj;
};

487
db-test.js Normal file
View File

@ -0,0 +1,487 @@
/**
* @description MeshCentral database module
* @author Ylian Saint-Hilaire
* @copyright Intel Corporation 2018-2019
* @license Apache-2.0
* @version v0.0.2
*/
/*xjslint node: true */
/*xjslint plusplus: true */
/*xjslint maxlen: 256 */
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict";
//
// Construct Meshcentral database object
//
// The default database is NeDB
// https://github.com/louischatriot/nedb
//
// Alternativety, MongoDB can be used
// https://www.mongodb.com/
// Just run with --mongodb [connectionstring], where the connection string is documented here: https://docs.mongodb.com/manual/reference/connection-string/
// The default collection is "meshcentral", but you can override it using --mongodbcol [collection]
//
module.exports.CreateDB = function (parent, func) {
var obj = {};
var Datastore = null;
var expireEventsSeconds = (60 * 60 * 24 * 20); // By default, expire events after 20 days. (Seconds * Minutes * Hours * Days)
var expirePowerEventsSeconds = (60 * 60 * 24 * 10); // By default, expire power events after 10 days. (Seconds * Minutes * Hours * Days)
var expireServerStatsSeconds = (60 * 60 * 24 * 30); // By default, expire power events after 30 days. (Seconds * Minutes * Hours * Days)
obj.path = require('path');
obj.parent = parent;
obj.identifier = null;
obj.dbKey = null;
// Read expiration time from configuration file
if (typeof obj.parent.args.dbexpire == 'object') {
if (typeof obj.parent.args.dbexpire.events == 'number') { expireEventsSeconds = obj.parent.args.dbexpire.events; }
if (typeof obj.parent.args.dbexpire.powerevents == 'number') { expirePowerEventsSeconds = obj.parent.args.dbexpire.powerevents; }
if (typeof obj.parent.args.dbexpire.statsevents == 'number') { expireServerStatsSeconds = obj.parent.args.dbexpire.statsevents; }
}
if (obj.parent.args.mongodb) {
// Use MongoDB
obj.databaseType = 2;
Datastore = require('mongodb').MongoClient;
Datastore.connect(obj.parent.args.mongodb, function (err, client) {
if (err != null) { console.log("Unable to connect to database: " + err); process.exit(); return; }
const db = client.db('meshcentral');
var dbcollection = 'meshcentral';
if (obj.parent.args.mongodbcol) { dbcollection = obj.parent.args.mongodbcol; }
// Setup MongoDB main collection and indexes
obj.file = db.collection(dbcollection);
obj.file.find({ type: 'mesh' }, function (err, cursor) {
cursor.each(function (err, item) {
console.log(err, item);
});
});
/*
obj.file.getIndexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
console.log('Resetting main indexes...');
obj.file.dropIndexes(function (err) {
obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
});
}
});
*/
/*
// Setup the changeStream on the MongoDB main collection
obj.fileChangeStream = obj.file.watch();
obj.fileChangeStream.on('change', function (next) {
// Process next document
console.log('change', next);
});
*/
// Setup MongoDB events collection and indexes
obj.eventsfile = db.collection('events'); // Collection containing all events
/*
obj.eventsfile.getIndexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
// Reset all indexes
console.log('Resetting events indexes...');
obj.eventsfile.dropIndexes(function (err) {
obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
});
} else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
// Reset the timeout index
console.log('Resetting events expire index...');
obj.eventsfile.dropIndex("ExpireTime1", function (err) {
obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
});
}
});
*/
// Setup MongoDB power events collection and indexes
obj.powerfile = db.collection('power'); // Collection containing all power events
/*
obj.powerfile.getIndexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
// Reset all indexes
console.log('Resetting power events indexes...');
obj.powerfile.dropIndexes(function (err) {
// Create all indexes
obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
});
} else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
// Reset the timeout index
console.log('Resetting power events expire index...');
obj.powerfile.dropIndex("ExpireTime1", function (err) {
// Reset the expire power events index
obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
});
}
});
*/
// Setup MongoDB smbios collection, no indexes needed
obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
// Setup MongoDB server stats collection
obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
/*
obj.serverstatsfile.getIndexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
// Reset all indexes
console.log('Resetting server stats indexes...');
obj.serverstatsfile.dropIndexes(function (err) {
// Create all indexes
obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
obj.serverstatsfile.createIndex({ "expire": 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
});
} else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
// Reset the timeout index
console.log('Resetting server stats expire index...');
obj.serverstatsfile.dropIndex("ExpireTime1", function (err) {
// Reset the expire server stats index
obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
});
}
});
*/
func(); // Completed MongoDB setup
});
} else {
// Use NeDB (The default)
obj.databaseType = 1;
Datastore = require('nedb');
var datastoreOptions = { filename: obj.parent.getConfigFilePath('meshcentral.db'), autoload: true };
// If a DB encryption key is provided, perform database encryption
if ((typeof obj.parent.args.dbencryptkey == 'string') && (obj.parent.args.dbencryptkey.length != 0)) {
// Hash the database password into a AES256 key and setup encryption and decryption.
obj.dbKey = obj.parent.crypto.createHash('sha384').update(obj.parent.args.dbencryptkey).digest("raw").slice(0, 32);
datastoreOptions.afterSerialization = function (plaintext) {
const iv = obj.parent.crypto.randomBytes(16);
const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', obj.dbKey, iv);
var ciphertext = aes.update(plaintext);
ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
return ciphertext.toString('base64');
}
datastoreOptions.beforeDeserialization = function (ciphertext) {
const ciphertextBytes = Buffer.from(ciphertext, 'base64');
const iv = ciphertextBytes.slice(0, 16);
const data = ciphertextBytes.slice(16);
const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', obj.dbKey, iv);
var plaintextBytes = Buffer.from(aes.update(data));
plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
return plaintextBytes.toString();
}
}
// Start NeDB main collection and setup indexes
obj.file = new Datastore(datastoreOptions);
obj.file.persistence.setAutocompactionInterval(36000);
obj.file.ensureIndex({ fieldName: 'type' });
obj.file.ensureIndex({ fieldName: 'domain' });
obj.file.ensureIndex({ fieldName: 'meshid', sparse: true });
obj.file.ensureIndex({ fieldName: 'nodeid', sparse: true });
obj.file.ensureIndex({ fieldName: 'email', sparse: true });
// Setup the events collection and setup indexes
obj.eventsfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-events.db'), autoload: true });
obj.eventsfile.persistence.setAutocompactionInterval(36000);
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: 'time', expireAfterSeconds: 60 * 60 * 24 * 20 }); // Limit the power event log to 20 days (Seconds * Minutes * Hours * Days)
// Setup the power collection and setup indexes
obj.powerfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-power.db'), autoload: true });
obj.powerfile.persistence.setAutocompactionInterval(36000);
obj.powerfile.ensureIndex({ fieldName: 'nodeid' });
obj.powerfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 10 }); // Limit the power event log to 10 days (Seconds * Minutes * Hours * Days)
// Setup the SMBIOS collection
obj.smbiosfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-smbios.db'), autoload: true });
// Setup the server stats collection and setup indexes
obj.serverstatsfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-stats.db'), autoload: true });
obj.serverstatsfile.persistence.setAutocompactionInterval(36000);
obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 30 }); // Limit the server stats log to 30 days (Seconds * Minutes * Hours * Days)
obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
func(); // Completed NeDB setup
}
obj.SetupDatabase = function (func) {
// Check if the database unique identifier is present
// This is used to check that in server peering mode, everyone is using the same database.
obj.Get('DatabaseIdentifier', function (err, docs) {
if ((docs.length == 1) && (docs[0].value != null)) {
obj.identifier = docs[0].value;
} else {
obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
}
});
// Load database schema version and check if we need to update
obj.Get('SchemaVersion', function (err, docs) {
var ver = 0;
if (docs && docs.length == 1) { ver = docs[0].value; }
if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
// TODO: Any schema upgrades here...
obj.Set({ _id: 'SchemaVersion', value: 2 });
func(ver);
});
};
obj.cleanup = function (func) {
// TODO: Remove all mesh links to invalid users
// TODO: Remove all meshes that dont have any links
// Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
obj.file.remove({ type: 'event' }, { multi: true });
obj.file.remove({ type: 'power' }, { multi: true });
obj.file.remove({ type: 'smbios' }, { multi: true });
// Remove all objects that have a "meshid" that no longer points to a valid mesh.
obj.GetAllType('mesh', function (err, docs) {
var meshlist = [];
if ((err == null) && (docs.length > 0)) { for (var i in docs) { meshlist.push(docs[i]._id); } }
obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
// Fix all of the creating & login to ticks by seconds, not milliseconds.
obj.GetAllType('user', function (err, docs) {
if (err == null && docs.length > 0) {
for (var i in docs) {
var fixed = false;
// Fix account creation
if (docs[i].creation) {
if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
}
// Fix last account login
if (docs[i].login) {
if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
}
// Fix last password change
if (docs[i].passchange) {
if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
}
// Fix subscriptions
if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
// Save the user if needed
if (fixed) { obj.Set(docs[i]); }
// We are done
if (func) { func(); }
}
}
});
});
};
// Database actions on the main collection
obj.Set = function (data, func) { obj.file.update({ _id: data._id }, data, { upsert: true }, func); };
obj.Get = function (id, func)
{
if (arguments.length > 2)
{
var parms = [func];
for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
var func2 = function _func2(arg1, arg2)
{
var userCallback = _func2.userArgs.shift();
_func2.userArgs.unshift(arg2);
_func2.userArgs.unshift(arg1);
userCallback.apply(obj, _func2.userArgs);
};
func2.userArgs = parms;
obj.file.find({ _id: id }, func2);
}
else
{
obj.file.find({ _id: id }, func);
}
};
obj.GetAll = function (func) { obj.file.find({}, func); };
obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, func); };
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } obj.file.find(x, { type: 0 }, func); };
//obj.GetAllType = function (type, func) { obj.file.find({ type: type }, func); };
obj.GetAllType = function (type, func) { obj.file.find({ type: type }, function (err, cursor) { if (err) { func(err); } else { var r = []; cursor.each(function (err, item) { if (err) { func(err); } else { if (item) { r.push(item); } else { func(null, r); } } }); } }); };
obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, func); };
obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, func); };
obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }, func); };
obj.Remove = function (id) { obj.file.remove({ _id: id }); };
obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
// Database actions on the events collection
obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
obj.StoreEvent = function (event) { obj.eventsfile.insert(event); };
obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func); } };
obj.GetEventsWithLimit = function (ids, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func); } };
obj.GetUserEvents = function (ids, domain, username, func) {
if (obj.databaseType == 1) {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func);
} else {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func);
}
};
obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
if (obj.databaseType == 1) {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func);
} else {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func);
}
};
obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
// Database actions on the power collection
obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } };
obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
// Database actions on the SMBIOS collection
obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
// Database actions on the Server Stats collection
obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }, func); };
// Read a configuration file from the database
obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
// Write a configuration file to the database
obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
// List all configuration files
obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
// Get all configuration files
obj.getAllConfigFiles = function (password, func) {
obj.file.find({ type: 'cfile' }, function (err, docs) {
if (err != null) { func(null); return; }
var r = null;
for (var i = 0; i < docs.length; i++) {
var name = docs[i]._id.split('/')[1];
var data = obj.decryptData(password, docs[i].data);
if (data != null) { if (r == null) { r = {}; } r[name] = data; }
}
func(r);
});
}
// Get encryption key
obj.getEncryptDataKey = function (password) {
if (typeof password != 'string') return null;
return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
}
// Encrypt data
obj.encryptData = function (password, plaintext) {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
const iv = obj.parent.crypto.randomBytes(16);
const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv);
var ciphertext = aes.update(plaintext);
ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
return ciphertext.toString('base64');
}
// Decrypt data
obj.decryptData = function (password, ciphertext) {
try {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
const ciphertextBytes = Buffer.from(ciphertext, 'base64');
const iv = ciphertextBytes.slice(0, 16);
const data = ciphertextBytes.slice(16);
const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
var plaintextBytes = Buffer.from(aes.update(data));
plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
return plaintextBytes;
} catch (ex) { return null; }
}
// Get the number of records in the database for various types, this is the slow NeDB way.
// WARNING: This is a terrible query for database performance. Only do this when needed. This query will look at almost every document in the database.
obj.getStats = function (func) {
if (obj.databaseType == 2) {
// MongoDB version
obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
var counters = {}, totalCount = 0;
for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } }
func({ nodes: counters['node'], meshes: counters['mesh'], users: counters['user'], total: totalCount });
})
} else {
// NeDB version
obj.file.count({ type: 'node' }, function (err, nodeCount) {
obj.file.count({ type: 'mesh' }, function (err, meshCount) {
obj.file.count({ type: 'user' }, function (err, userCount) {
obj.file.count({}, function (err, totalCount) {
func({ nodes: nodeCount, meshes: meshCount, users: userCount, total: totalCount });
});
});
});
});
}
}
// This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db.
obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (docs.length == 1) { var r = docs[0]; if (r.day == t) { func({ _id: id, value: r.value, day: t }); return; } } func({ _id: id, value: startValue, day: t }); }); };
obj.escapeBase64 = function escapeBase64(val) { return (val.replace(/\+/g, '@').replace(/\//g, '$')); }
function Clone(v) { return JSON.parse(JSON.stringify(v)); }
return obj;
};

669
db.js
View File

@ -25,7 +25,7 @@
// Just run with --mongodb [connectionstring], where the connection string is documented here: https://docs.mongodb.com/manual/reference/connection-string/
// The default collection is "meshcentral", but you can override it using --mongodbcol [collection]
//
module.exports.CreateDB = function (parent) {
module.exports.CreateDB = function (parent, func) {
var obj = {};
var Datastore = null;
var expireEventsSeconds = (60 * 60 * 24 * 20); // By default, expire events after 20 days. (Seconds * Minutes * Hours * Days)
@ -36,6 +36,161 @@ module.exports.CreateDB = function (parent) {
obj.identifier = null;
obj.dbKey = null;
obj.SetupDatabase = function (func) {
// Check if the database unique identifier is present
// This is used to check that in server peering mode, everyone is using the same database.
obj.Get('DatabaseIdentifier', function (err, docs) {
if ((docs.length == 1) && (docs[0].value != null)) {
obj.identifier = docs[0].value;
} else {
obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
}
});
// Load database schema version and check if we need to update
obj.Get('SchemaVersion', function (err, docs) {
var ver = 0;
if (docs && docs.length == 1) { ver = docs[0].value; }
if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
// TODO: Any schema upgrades here...
obj.Set({ _id: 'SchemaVersion', value: 2 });
func(ver);
});
};
obj.cleanup = function (func) {
// TODO: Remove all mesh links to invalid users
// TODO: Remove all meshes that dont have any links
// Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
if (obj.databaseType == 3) {
// MongoDB
obj.file.deleteMany({ type: 'event' }, { multi: true });
obj.file.deleteMany({ type: 'power' }, { multi: true });
obj.file.deleteMany({ type: 'smbios' }, { multi: true });
} else {
// NeDB or MongoJS
obj.file.remove({ type: 'event' }, { multi: true });
obj.file.remove({ type: 'power' }, { multi: true });
obj.file.remove({ type: 'smbios' }, { multi: true });
}
// Remove all objects that have a "meshid" that no longer points to a valid mesh.
obj.GetAllType('mesh', function (err, docs) {
var meshlist = [];
if ((err == null) && (docs.length > 0)) { for (var i in docs) { meshlist.push(docs[i]._id); } }
if (obj.databaseType == 3) {
// MongoDB
obj.file.deleteMany({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
} else {
// NeDB or MongoJS
obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
}
// Fix all of the creating & login to ticks by seconds, not milliseconds.
obj.GetAllType('user', function (err, docs) {
if (err == null && docs.length > 0) {
for (var i in docs) {
var fixed = false;
// Fix account creation
if (docs[i].creation) {
if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
}
// Fix last account login
if (docs[i].login) {
if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
}
// Fix last password change
if (docs[i].passchange) {
if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
}
// Fix subscriptions
if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
// Save the user if needed
if (fixed) { obj.Set(docs[i]); }
// We are done
if (func) { func(); }
}
}
});
});
};
// Get encryption key
obj.getEncryptDataKey = function (password) {
if (typeof password != 'string') return null;
return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
}
// Encrypt data
obj.encryptData = function (password, plaintext) {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
const iv = obj.parent.crypto.randomBytes(16);
const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv);
var ciphertext = aes.update(plaintext);
ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
return ciphertext.toString('base64');
}
// Decrypt data
obj.decryptData = function (password, ciphertext) {
try {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
const ciphertextBytes = Buffer.from(ciphertext, 'base64');
const iv = ciphertextBytes.slice(0, 16);
const data = ciphertextBytes.slice(16);
const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
var plaintextBytes = Buffer.from(aes.update(data));
plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
return plaintextBytes;
} catch (ex) { return null; }
}
// Get the number of records in the database for various types, this is the slow NeDB way.
// WARNING: This is a terrible query for database performance. Only do this when needed. This query will look at almost every document in the database.
obj.getStats = function (func) {
if (obj.databaseType > 1) {
// MongoJS or MongoDB version (not tested on MongoDB)
obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
var counters = {}, totalCount = 0;
for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } }
func({ nodes: counters['node'], meshes: counters['mesh'], users: counters['user'], total: totalCount });
})
} else {
// NeDB version
obj.file.count({ type: 'node' }, function (err, nodeCount) {
obj.file.count({ type: 'mesh' }, function (err, meshCount) {
obj.file.count({ type: 'user' }, function (err, userCount) {
obj.file.count({}, function (err, totalCount) {
func({ nodes: nodeCount, meshes: meshCount, users: userCount, total: totalCount });
});
});
});
});
}
}
// This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db.
obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (docs.length == 1) { var r = docs[0]; if (r.day == t) { func({ _id: id, value: r.value, day: t }); return; } } func({ _id: id, value: startValue, day: t }); }); };
obj.escapeBase64 = function escapeBase64(val) { return (val.replace(/\+/g, '@').replace(/\//g, '$')); }
function Clone(v) { return JSON.parse(JSON.stringify(v)); }
// Read expiration time from configuration file
if (typeof obj.parent.args.dbexpire == 'object') {
if (typeof obj.parent.args.dbexpire.events == 'number') { expireEventsSeconds = obj.parent.args.dbexpire.events; }
@ -43,8 +198,121 @@ module.exports.CreateDB = function (parent) {
if (typeof obj.parent.args.dbexpire.statsevents == 'number') { expireServerStatsSeconds = obj.parent.args.dbexpire.statsevents; }
}
if (obj.parent.args.mongodb) {
if (obj.parent.args.xmongodb) {
// Use MongoDB
obj.databaseType = 3;
require('mongodb').MongoClient.connect(obj.parent.args.xmongodb, { useNewUrlParser: true }, function (err, client) {
if (err != null) { console.log("Unable to connect to database: " + err); process.exit(); return; }
Datastore = client;
const dbname = (obj.parent.args.mongodbname) ? (obj.parent.args.mongodbname) : 'meshcentral';
const dbcollectionname = (obj.parent.args.mongodbcol) ? (obj.parent.args.mongodbcol) : 'meshcentral';
const db = client.db(dbname);
// Setup MongoDB main collection and indexes
obj.file = db.collection(dbcollectionname);
obj.file.indexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
console.log('Resetting main indexes...');
obj.file.dropIndexes(function (err) {
obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
});
}
});
/*
// Setup the changeStream on the MongoDB main collection
obj.fileChangeStream = obj.file.watch({ fullDocument: 'updateLookup' });
obj.fileChangeStream.on('change', function (next) {
// Process next document
console.log('change', next);
});
*/
// Setup MongoDB events collection and indexes
obj.eventsfile = db.collection('events'); // Collection containing all events
obj.eventsfile.indexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
// Reset all indexes
console.log('Resetting events indexes...');
obj.eventsfile.dropIndexes(function (err) {
obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
});
} else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
// Reset the timeout index
console.log('Resetting events expire index...');
obj.eventsfile.dropIndex("ExpireTime1", function (err) {
obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
});
}
});
// Setup MongoDB power events collection and indexes
obj.powerfile = db.collection('power'); // Collection containing all power events
obj.powerfile.indexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
// Reset all indexes
console.log('Resetting power events indexes...');
obj.powerfile.dropIndexes(function (err) {
// Create all indexes
obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
});
} else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
// Reset the timeout index
console.log('Resetting power events expire index...');
obj.powerfile.dropIndex("ExpireTime1", function (err) {
// Reset the expire power events index
obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
});
}
});
// Setup MongoDB smbios collection, no indexes needed
obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
// Setup MongoDB server stats collection
obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
obj.serverstatsfile.indexes(function (err, indexes) {
// Check if we need to reset indexes
var indexesByName = {}, indexCount = 0;
for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
// Reset all indexes
console.log('Resetting server stats indexes...');
obj.serverstatsfile.dropIndexes(function (err) {
// Create all indexes
obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
obj.serverstatsfile.createIndex({ "expire": 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
});
} else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
// Reset the timeout index
console.log('Resetting server stats expire index...');
obj.serverstatsfile.dropIndex("ExpireTime1", function (err) {
// Reset the expire server stats index
obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
});
}
});
setupFunctions(func); // Completed setup of MongoDB
});
} else if (obj.parent.args.mongodb) {
// Use MongoJS
obj.databaseType = 2;
Datastore = require('mongojs');
var db = Datastore(obj.parent.args.mongodb);
@ -141,6 +409,8 @@ module.exports.CreateDB = function (parent) {
});
}
});
setupFunctions(func); // Completed setup of MongoJS
} else {
// Use NeDB (The default)
obj.databaseType = 1;
@ -199,231 +469,204 @@ module.exports.CreateDB = function (parent) {
obj.serverstatsfile.persistence.setAutocompactionInterval(36000);
obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 30 }); // Limit the server stats log to 30 days (Seconds * Minutes * Hours * Days)
obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
setupFunctions(func); // Completed setup of NeDB
}
obj.SetupDatabase = function (func) {
// Check if the database unique identifier is present
// This is used to check that in server peering mode, everyone is using the same database.
obj.Get('DatabaseIdentifier', function (err, docs) {
if ((docs.length == 1) && (docs[0].value != null)) {
obj.identifier = docs[0].value;
} else {
obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
}
});
// Load database schema version and check if we need to update
obj.Get('SchemaVersion', function (err, docs) {
var ver = 0;
if (docs && docs.length == 1) { ver = docs[0].value; }
if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
// TODO: Any schema upgrades here...
obj.Set({ _id: 'SchemaVersion', value: 2 });
func(ver);
});
};
obj.cleanup = function (func) {
// TODO: Remove all mesh links to invalid users
// TODO: Remove all meshes that dont have any links
// Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
obj.file.remove({ type: 'event' }, { multi: true });
obj.file.remove({ type: 'power' }, { multi: true });
obj.file.remove({ type: 'smbios' }, { multi: true });
// Remove all objects that have a "meshid" that no longer points to a valid mesh.
obj.GetAllType('mesh', function (err, docs) {
var meshlist = [];
if ((err == null) && (docs.length > 0)) { for (var i in docs) { meshlist.push(docs[i]._id); } }
obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
// Fix all of the creating & login to ticks by seconds, not milliseconds.
obj.GetAllType('user', function (err, docs) {
if (err == null && docs.length > 0) {
for (var i in docs) {
var fixed = false;
// Fix account creation
if (docs[i].creation) {
if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
}
// Fix last account login
if (docs[i].login) {
if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
}
// Fix last password change
if (docs[i].passchange) {
if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
}
// Fix subscriptions
if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
// Save the user if needed
if (fixed) { obj.Set(docs[i]); }
// We are done
if (func) { func(); }
}
function setupFunctions(func) {
if (obj.databaseType == 3) {
// Database actions on the main collection (MongoDB)
obj.Set = function (data, func) { obj.file.updateOne({ _id: data._id }, { $set: data }, { upsert: true }, func); };
obj.Get = function (id, func) {
if (arguments.length > 2) {
var parms = [func];
for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
var func2 = function _func2(arg1, arg2) {
var userCallback = _func2.userArgs.shift();
_func2.userArgs.unshift(arg2);
_func2.userArgs.unshift(arg1);
userCallback.apply(obj, _func2.userArgs);
};
func2.userArgs = parms;
obj.file.find({ _id: id }).toArray(func2);
} else {
obj.file.find({ _id: id }).toArray(func);
}
});
});
};
};
obj.GetAll = function (func) { obj.file.find({}).toArray(func); };
obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }).toArray(func); };
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } obj.file.find(x, { type: 0 }).toArray(func); };
obj.GetAllType = function (type, func) { obj.file.find({ type: type }).toArray(func); };
obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }).toArray(func); };
obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }).toArray(func); };
obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }).toArray(func); };
obj.Remove = function (id) { obj.file.deleteOne({ _id: id }); };
obj.RemoveAll = function (func) { obj.file.deleteMany({}, { multi: true }, func); };
obj.RemoveAllOfType = function (type, func) { obj.file.deleteMany({ type: type }, { multi: true }, func); };
obj.InsertMany = function (data, func) { obj.file.insertMany(data, func); };
obj.RemoveMeshDocuments = function (id) { obj.file.deleteMany({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
obj.DeleteDomain = function (domain, func) { obj.file.deleteMany({ domain: domain }, { multi: true }, func); };
obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }).toArray(func); };
obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find(obj.file, { type: 'node', meshid: meshid, 'intelamt.uuid': uuid }).toArray(func); };
obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
// Database actions on the main collection
obj.Set = function (data, func) { obj.file.update({ _id: data._id }, data, { upsert: true }, func); };
obj.Get = function (id, func) { obj.file.find({ _id: id }, func); };
obj.GetAll = function (func) { obj.file.find({}, func); };
obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, func); };
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } obj.file.find(x, { type: 0 }, func); };
obj.GetAllType = function (type, func) { obj.file.find({ type: type }, func); };
obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, func); };
obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, func); };
obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }, func); };
obj.Remove = function (id) { obj.file.remove({ _id: id }); };
obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
// Database actions on the events collection
obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); };
obj.StoreEvent = function (event) { obj.eventsfile.insertOne(event); };
obj.GetEvents = function (ids, domain, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); };
obj.GetEventsWithLimit = function (ids, domain, limit, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
obj.GetUserEvents = function (ids, domain, username, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); };
obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); };
obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); };
// Database actions on the events collection
obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
obj.StoreEvent = function (event) { obj.eventsfile.insert(event); };
obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func); } };
obj.GetEventsWithLimit = function (ids, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func); } };
obj.GetUserEvents = function (ids, domain, username, func) {
if (obj.databaseType == 1) {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func);
} else {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func);
}
};
obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
if (obj.databaseType == 1) {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func);
} else {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func);
}
};
obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
// Database actions on the power collection
obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); };
obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insertOne(event, func); };
obj.getPowerTimeline = function (nodeid, func) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).toArray(func); };
obj.removeAllPowerEvents = function () { obj.powerfile.deleteMany({}, { multi: true }); };
obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.deleteMany({ nodeid: nodeid }, { multi: true }); };
// Database actions on the power collection
obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } };
obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
// Database actions on the SMBIOS collection
obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.updateOne({ _id: smbios._id }, { $set: smbios }, { upsert: true }, func); };
obj.RemoveSMBIOS = function (id) { obj.smbiosfile.deleteOne({ _id: id }); };
obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }).toArray(func); };
// Database actions on the SMBIOS collection
obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
// Database actions on the Server Stats collection
obj.SetServerStats = function (data, func) { obj.serverstatsfile.insertOne(data, func); };
obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }).toArray(func); };
// Database actions on the Server Stats collection
obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }, func); };
// Read a configuration file from the database
obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
// Read a configuration file from the database
obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
// Write a configuration file to the database
obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
// Write a configuration file to the database
obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
// List all configuration files
obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).toArray(func); }
// List all configuration files
obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
// Get all configuration files
obj.getAllConfigFiles = function (password, func) {
obj.file.find({ type: 'cfile' }, function (err, docs) {
if (err != null) { func(null); return; }
var r = null;
for (var i = 0; i < docs.length; i++) {
var name = docs[i]._id.split('/')[1];
var data = obj.decryptData(password, docs[i].data);
if (data != null) { if (r == null) { r = {}; } r[name] = data; }
}
func(r);
});
}
// Get encryption key
obj.getEncryptDataKey = function (password) {
if (typeof password != 'string') return null;
return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
}
// Encrypt data
obj.encryptData = function (password, plaintext) {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
const iv = obj.parent.crypto.randomBytes(16);
const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv);
var ciphertext = aes.update(plaintext);
ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
return ciphertext.toString('base64');
}
// Decrypt data
obj.decryptData = function (password, ciphertext) {
try {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
const ciphertextBytes = Buffer.from(ciphertext, 'base64');
const iv = ciphertextBytes.slice(0, 16);
const data = ciphertextBytes.slice(16);
const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
var plaintextBytes = Buffer.from(aes.update(data));
plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
return plaintextBytes;
} catch (ex) { return null; }
}
// Get the number of records in the database for various types, this is the slow NeDB way.
// WARNING: This is a terrible query for database performance. Only do this when needed. This query will look at almost every document in the database.
obj.getStats = function (func) {
if (obj.databaseType == 2) {
// MongoDB version
obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
var counters = {}, totalCount = 0;
for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } }
func({ nodes: counters['node'], meshes: counters['mesh'], users: counters['user'], total: totalCount });
})
} else {
// NeDB version
obj.file.count({ type: 'node' }, function (err, nodeCount) {
obj.file.count({ type: 'mesh' }, function (err, meshCount) {
obj.file.count({ type: 'user' }, function (err, userCount) {
obj.file.count({}, function (err, totalCount) {
func({ nodes: nodeCount, meshes: meshCount, users: userCount, total: totalCount });
});
});
// Get all configuration files
obj.getAllConfigFiles = function (password, func) {
obj.file.find({ type: 'cfile' }).toArray(function (err, docs) {
if (err != null) { func(null); return; }
var r = null;
for (var i = 0; i < docs.length; i++) {
var name = docs[i]._id.split('/')[1];
var data = obj.decryptData(password, docs[i].data);
if (data != null) { if (r == null) { r = {}; } r[name] = data; }
}
func(r);
});
});
}
} else {
// Database actions on the main collection (NeDB and MongoJS)
obj.Set = function (data, func) { obj.file.update({ _id: data._id }, data, { upsert: true }, func); };
obj.Get = function (id, func) {
if (arguments.length > 2) {
var parms = [func];
for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
var func2 = function _func2(arg1, arg2) {
var userCallback = _func2.userArgs.shift();
_func2.userArgs.unshift(arg2);
_func2.userArgs.unshift(arg1);
userCallback.apply(obj, _func2.userArgs);
};
func2.userArgs = parms;
obj.file.find({ _id: id }, func2);
}
else {
obj.file.find({ _id: id }, func);
}
};
obj.GetAll = function (func) { obj.file.find({}, func); };
obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, func); };
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } obj.file.find(x, { type: 0 }, func); };
obj.GetAllType = function (type, func) { obj.file.find({ type: type }, func); };
obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, func); };
obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, func); };
obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }, func); };
obj.Remove = function (id) { obj.file.remove({ _id: id }); };
obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
// Database actions on the events collection
obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
obj.StoreEvent = function (event) { obj.eventsfile.insert(event); };
obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func); } };
obj.GetEventsWithLimit = function (ids, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func); } };
obj.GetUserEvents = function (ids, domain, username, func) {
if (obj.databaseType == 1) {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func);
} else {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func);
}
};
obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
if (obj.databaseType == 1) {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func);
} else {
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func);
}
};
obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
// Database actions on the power collection
obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } };
obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
// Database actions on the SMBIOS collection
obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
// Database actions on the Server Stats collection
obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }, func); };
// Read a configuration file from the database
obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
// Write a configuration file to the database
obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
// List all configuration files
obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
// Get all configuration files
obj.getAllConfigFiles = function (password, func) {
obj.file.find({ type: 'cfile' }, function (err, docs) {
if (err != null) { func(null); return; }
var r = null;
for (var i = 0; i < docs.length; i++) {
var name = docs[i]._id.split('/')[1];
var data = obj.decryptData(password, docs[i].data);
if (data != null) { if (r == null) { r = {}; } r[name] = data; }
}
func(r);
});
}
}
func(obj); // Completed function setup
}
// This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db.
obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (docs.length == 1) { var r = docs[0]; if (r.day == t) { func({ _id: id, value: r.value, day: t }); return; } } func({ _id: id, value: startValue, day: t }); }); };
function Clone(v) { return JSON.parse(JSON.stringify(v)); }
return obj;
};

View File

@ -19,6 +19,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
const forge = parent.parent.certificateOperations.forge;
const common = parent.parent.common;
const agentUpdateBlockSize = 65531;
parent.agentStats.createMeshAgentCount++;
var obj = {};
obj.domain = domain;
@ -155,6 +156,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if (meshcorehash == null) {
// Clear the core
obj.send(common.ShortToStr(10) + common.ShortToStr(0)); // MeshCommand_CoreModule, ask mesh agent to clear the core
parent.agentStats.clearingCoreCount++;
parent.parent.debug(1, 'Clearing core');
} else {
// Update new core
@ -168,7 +170,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Send the updated code.
delete obj.agentCoreUpdatePending;
obj.send(common.ShortToStr(10) + common.ShortToStr(0) + argument.hash + argument.core, function () { parent.parent.taskLimiter.completed(taskid); }); // MeshCommand_CoreModule, start core update
parent.parent.debug(1, 'Updating code ' + argument.name);
parent.agentStats.updatingCoreCount++;
parent.parent.debug(1, 'Updating core ' + argument.name);
agentCoreIsStable();
} else {
// This agent is probably disconnected, nothing to do.
@ -359,6 +362,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
} else {
// Check that the server hash matches our own web certificate hash (SHA384)
if ((getWebCertHash(domain) != msg.substring(2, 50)) && (getWebCertFullHash(domain) != msg.substring(2, 50))) {
parent.agentStats.agentBadWebCertHashCount++;
console.log('Agent bad web cert hash (Agent:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + ' != Server:' + (Buffer.from(getWebCertHash(domain), 'binary').toString('hex').substring(0, 10)) + ' or ' + (new Buffer(getWebCertFullHash(domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').');
console.log('Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.');
return;
@ -388,7 +392,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Check the agent signature if we can
if (obj.unauthsign != null) {
if (processAgentSignature(obj.unauthsign) == false) { console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return; } else { completeAgentConnection(); }
if (processAgentSignature(obj.unauthsign) == false) {
parent.agentStats.agentBadSignature1Count++;
console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return;
} else { completeAgentConnection(); }
}
}
else if (cmd == 2) {
@ -403,7 +410,12 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.unauth.nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----';
// Check the agent signature if we can
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return; } }
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else {
if (processAgentSignature(msg.substring(4 + certlen)) == false) {
parent.agentStats.agentBadSignature2Count++;
console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return;
}
}
completeAgentConnection();
}
else if (cmd == 3) {
@ -522,6 +534,18 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if ((obj.authenticated != 1) || (obj.meshid == null) || obj.pendingCompleteAgentConnection || (obj.agentInfo == null)) { return; }
obj.pendingCompleteAgentConnection = true;
// If this is a recovery agent
if (obj.agentInfo.capabilities & 0x40) {
// Inform mesh agent that it's authenticated.
delete obj.pendingCompleteAgentConnection;
obj.authenticated = 2;
obj.send(common.ShortToStr(4));
// Ask for mesh core hash.
obj.send(common.ShortToStr(11) + common.ShortToStr(0));
return;
}
// Check if we have too many agent sessions
if (typeof domain.limits.maxagentsessions == 'number') {
// Count the number of agent sessions for this domain
@ -529,7 +553,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
for (var i in parent.wsagents) { if (parent.wsagents[i].domain.id == domain.id) { domainAgentSessionCount++; } }
// Check if we have too many user sessions
if (domainAgentSessionCount >= domain.limits.maxagentsessions) { return; } // Too many, hold the connection.
if (domainAgentSessionCount >= domain.limits.maxagentsessions) {
// Too many, hold the connection.
parent.agentStats.agentMaxSessionHoldCount++;
return;
}
}
/*
@ -581,6 +609,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Check if the mesh exists
if (mesh == null) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
parent.agentStats.invalidDomainMeshCount++;
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
return;
}
@ -588,6 +617,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Check if the mesh is the right type
if (mesh.mtype != 2) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
parent.agentStats.invalidMeshTypeCount++;
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
return;
}
@ -622,6 +652,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Check if the mesh exists
if (mesh == null) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
parent.agentStats.invalidDomainMesh2Count++;
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
return;
}
@ -629,6 +660,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Check if the mesh is the right type
if (mesh.mtype != 2) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
parent.agentStats.invalidMeshType2Count++;
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
return;
}
@ -647,6 +679,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if (mesh.flags && (mesh.flags & 2) && (device.name != obj.agentInfo.computerName)) { device.name = obj.agentInfo.computerName; change = 1; } // We want the server name to be sync'ed to the hostname
if (change == 1) {
// Do some clean up if needed, these values should not be in the database.
if (device.conn != null) { delete device.conn; }
if (device.pwr != null) { delete device.pwr; }
if (device.agct != null) { delete device.agct; }
if (device.cict != null) { delete device.cict; }
// Save the updated device in the database
db.Set(device);
// If this is a temporary device, don't log changes
@ -667,6 +706,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
parent.wsagents[obj.dbNodeKey] = obj;
if (dupAgent) {
// Close the duplicate agent
parent.agentStats.duplicateAgentCount++;
if (obj.nodeid != null) { parent.parent.debug(1, 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); }
dupAgent.close(3);
} else {
@ -700,40 +740,25 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Not sure why, but in rare cases, obj.agentInfo is undefined here.
if ((obj.agentInfo == null) || (typeof obj.agentInfo.capabilities != 'number')) { return; } // This is an odd case.
if ((obj.agentInfo.capabilities & 64) != 0) {
// This is a recovery agent
obj.send(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
} else {
// Check if we need to make an native update check
obj.agentExeInfo = parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
const corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
if (corename == null) { obj.send(common.ShortToStr(10) + common.ShortToStr(0)); } // MeshCommand_CoreModule, ask mesh agent to clear the core
// Check if we need to make an native update check
obj.agentExeInfo = parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
const corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
if (corename == null) { obj.send(common.ShortToStr(10) + common.ShortToStr(0)); } // MeshCommand_CoreModule, ask mesh agent to clear the core
if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
// Ask the agent for it's executable binary hash
obj.send(common.ShortToStr(12) + common.ShortToStr(0));
if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
// Ask the agent for it's executable binary hash
obj.send(common.ShortToStr(12) + common.ShortToStr(0));
} else {
// Check the mesh core, if the agent is capable of running one
if (((obj.agentInfo.capabilities & 16) != 0) && (corename != null)) {
obj.send(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
} else {
// Check the mesh core, if the agent is capable of running one
if (((obj.agentInfo.capabilities & 16) != 0) && (corename != null)) {
obj.send(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
} else {
agentCoreIsStable(); // No updates needed, agent is ready to go.
}
agentCoreIsStable(); // No updates needed, agent is ready to go.
}
}
});
}
function recoveryAgentCoreIsStable() {
// Recovery agent is doing ok, lets perform main agent checking.
// TODO
console.log('recoveryAgentCoreIsStable()');
// Close the recovery agent connection when done.
//obj.close(1);
}
// Take a basic Intel AMT policy and add all server information to it, making it ready to send to this agent.
function completeIntelAmtPolicy(amtPolicy) {
if (amtPolicy == null) return null;
@ -764,14 +789,57 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}
}
function recoveryAgentCoreIsStable(mesh) {
parent.agentStats.recoveryCoreIsStableCount++;
// Recovery agent is doing ok, lets perform main agent checking.
//console.log('recoveryAgentCoreIsStable()');
// Fetch the the real agent nodeid
db.Get('da' + obj.dbNodeKey, function (err, nodes, self)
{
if (nodes.length == 1)
{
self.realNodeKey = nodes[0].raid;
// Get agent connection state
var agentConnected = false;
var state = parent.parent.GetConnectivityState(self.realNodeKey);
if (state) { agentConnected = ((state.connectivity & 1) != 0) }
self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: self.realNodeKey, agent: agentConnected } }));
} else
{
self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
}
}, obj);
}
function agentCoreIsStable() {
parent.agentStats.coreIsStableCount++;
// Check that the mesh exists
const mesh = parent.meshes[obj.dbMeshKey];
if (mesh == null) {
parent.agentStats.meshDoesNotExistCount++;
// TODO: Mark this agent as part of a mesh that does not exists.
return; // Probably not worth doing anything else. Hold this agent.
}
// Check if this is a recovery agent
if (obj.agentInfo.capabilities & 0x40) {
recoveryAgentCoreIsStable(mesh);
return;
}
// Fetch the the diagnostic agent nodeid
db.Get('ra' + obj.dbNodeKey, function (err, nodes) {
if (nodes.length == 1) {
obj.diagnosticNodeKey = nodes[0].daid;
obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.diagnosticNodeKey } }));
}
});
// Send Intel AMT policy
if (obj.agentExeInfo && (obj.agentExeInfo.amt == true) && (mesh.amt != null)) { // Only send Intel AMT policy to agents what could have AMT.
try { obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: completeIntelAmtPolicy(common.Clone(mesh.amt)) })); } catch (ex) { }
@ -860,7 +928,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
verifier.update(buf);
verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
}
if (verified == false) { return false; } // Not a valid signature
if (verified == false) {
// Not a valid signature
parent.agentStats.invalidPkcsSignatureCount++;
return false;
}
} catch (ex) { };
}
}
@ -872,7 +944,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if (verify.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) {
const verify2 = parent.crypto.createVerify('SHA384');
verify2.end(Buffer.from(getWebCertFullHash(domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the full cert hash
if (verify2.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) { return false; }
if (verify2.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) {
parent.agentStats.invalidRsaSignatureCount++;
return false;
}
}
}
}
@ -885,6 +960,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
delete obj.unauth;
delete obj.receivedCommands;
if (obj.unauthsign) delete obj.unauthsign;
parent.agentStats.verifiedAgentConnectionCount++;
parent.parent.debug(1, 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').');
obj.authenticated = 1;
return true;
@ -894,7 +970,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
function processAgentData(msg) {
var i, str = msg.toString('utf8'), command = null;
if (str[0] == '{') {
try { command = JSON.parse(str); } catch (ex) { console.log('Unable to parse agent JSON (' + obj.remoteaddrport + '): ' + str, ex); return; } // If the command can't be parsed, ignore it.
try { command = JSON.parse(str); } catch (ex) { parent.agentStats.invalidJsonCount++; console.log('Unable to parse agent JSON (' + obj.remoteaddrport + '): ' + str, ex); return; } // If the command can't be parsed, ignore it.
if (typeof command != 'object') { return; }
switch (command.action) {
case 'msg':
@ -1066,7 +1142,58 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}
break;
}
case 'diagnostic':
{
if (typeof command.value == 'object') {
switch (command.value.command) {
case 'register': {
// Only main agent can do this
if (((obj.agentInfo.capabilities & 0x40) == 0) && (typeof command.value.value == 'string') && (command.value.value.length == 64))
{
// Store links to diagnostic agent id
var daNodeKey = 'node/' + domain.id + '/' + db.escapeBase64(command.value.value);
db.Set({ _id: 'da' + daNodeKey, domain: domain.id, time: obj.connectTime, raid: obj.dbNodeKey }); // DiagnosticAgent --> Agent
db.Set({ _id: 'ra' + obj.dbNodeKey, domain: domain.id, time: obj.connectTime, daid: daNodeKey }); // Agent --> DiagnosticAgent
}
break;
}
case 'query': {
// Only the diagnostic agent can do
if ((obj.agentInfo.capabilities & 0x40) != 0) {
// Return nodeid of main agent + connection status
db.Get('da' + obj.dbNodeKey, function (err, nodes) {
if (nodes.length == 1) {
obj.realNodeKey = nodes[0].raid;
// Get agent connection state
var agentConnected = false;
var state = parent.parent.GetConnectivityState(obj.realNodeKey);
if (state) { agentConnected = ((state.connectivity & 1) != 0) }
obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.realNodeKey, agent: agentConnected } }));
} else {
obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
}
});
}
break;
}
case 'log': {
// Only the diagnostic agent can do
if (((obj.agentInfo.capabilities & 0x40) != 0) && (typeof command.value.value == 'string') && (command.value.value.length < 256))
{
// Log a value in the event log of the main again
var event = { etype: 'node', action: 'diagnostic', nodeid: obj.realNodeKey, domain: domain.id, msg: command.value.value };
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, event);
}
break;
}
}
}
break;
}
default: {
parent.agentStats.unknownAgentActionCount++;
console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + command.action + '.');
break;
}
@ -1076,6 +1203,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Change the current core information string and event it
function ChangeAgentCoreInfo(command) {
if (obj.agentInfo.capabilities & 0x40) return;
if ((command == null) || (command == null)) return; // Safety, should never happen.
// Check that the mesh exists
@ -1119,6 +1247,12 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// If there are changes, event the new device
if (change == 1) {
// Do some clean up if needed, these values should not be in the database.
if (device.conn != null) { delete device.conn; }
if (device.pwr != null) { delete device.pwr; }
if (device.agct != null) { delete device.agct; }
if (device.cict != null) { delete device.cict; }
// Save to the database
db.Set(device);
@ -1137,6 +1271,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Change the current core information string and event it
function ChangeAgentLocationInfo(command) {
if (obj.agentInfo.capabilities & 0x40) return;
if ((command == null) || (command == null)) { return; } // Safety, should never happen.
// Check that the mesh exists
@ -1156,6 +1291,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// If there are changes, save and event
if (change == 1) {
// Do some clean up if needed, these values should not be in the database.
if (device.conn != null) { delete device.conn; }
if (device.pwr != null) { delete device.pwr; }
if (device.agct != null) { delete device.agct; }
if (device.cict != null) { delete device.cict; }
// Save the device
db.Set(device);
// Event the node change
@ -1172,6 +1314,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Update the mesh agent tab in the database
function ChangeAgentTag(tag) {
if (obj.agentInfo.capabilities & 0x40) return;
if (tag.length == 0) { tag = null; }
// Get the node and change it if needed
db.Get(obj.dbNodeKey, function (err, nodes) {
@ -1179,6 +1322,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
const device = nodes[0];
if (device.agent) {
if (device.agent.tag != tag) {
// Do some clean up if needed, these values should not be in the database.
if (device.conn != null) { delete device.conn; }
if (device.pwr != null) { delete device.pwr; }
if (device.agct != null) { delete device.agct; }
if (device.cict != null) { delete device.cict; }
// Set the new tag
device.agent.tag = tag;
db.Set(device);

View File

@ -155,7 +155,7 @@ function CreateMeshCentralServer(config, args) {
var arg = process.argv[i];
if (arg.length > 0) {
if (startLine.length > 0) startLine += ' ';
if (arg.indexOf(' ') >= 0) { startLine += '"' + arg + '"'; } else { startLine += arg; }
if ((arg.indexOf(' ') >= 0) || (arg.indexOf('&') >= 0)) { startLine += '"' + arg + '"'; } else { startLine += arg; }
}
}
obj.launchChildServer(startLine);
@ -216,7 +216,7 @@ function CreateMeshCentralServer(config, args) {
if (code == 0) { try { latestVer = xprocess.data.split(' ').join('').split('\r').join('').split('\n').join(''); } catch (e) { } }
callback(obj.currentVer, latestVer);
});
} catch (ex) { callback(obj.currentVer, null); } // If the system is running out of memory, an exception here can easily happen.
} catch (ex) { callback(obj.currentVer, null, ex); } // If the system is running out of memory, an exception here can easily happen.
};
// Initiate server self-update
@ -238,268 +238,272 @@ function CreateMeshCentralServer(config, args) {
if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(','); } }
if (typeof obj.args.debug == 'number') obj.debugLevel = obj.args.debug;
if (obj.args.debug == true) obj.debugLevel = 1;
obj.db = require('./db.js').CreateDB(obj);
obj.db.SetupDatabase(function (dbversion) {
// See if any database operations needs to be completed
if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; }
if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showevents) { obj.db.GetAllEvents(function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showpower) { obj.db.getAllPower(function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.clearpower) { obj.db.removeAllPowerEvents(function () { process.exit(); }); return; }
if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; }
if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; }
require('./db.js').CreateDB(obj,
function (db) {
obj.db = db;
obj.db.SetupDatabase(function (dbversion) {
// See if any database operations needs to be completed
if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; }
if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showevents) { obj.db.GetAllEvents(function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showpower) { obj.db.getAllPower(function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.clearpower) { obj.db.removeAllPowerEvents(function () { process.exit(); }); return; }
if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; }
if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; }
// Show a list of all configuration files in the database
if (obj.args.dblistconfigfiles) {
obj.db.GetAllType('cfile', function (err, docs) { if (err == null) { if (docs.length == 0) { console.log('No files found.'); } else { for (var i in docs) { console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' bytes.'); } } } else { console.log('Unable to read from database.'); } process.exit(); }); return;
}
// Show a list of all configuration files in the database
if (obj.args.dblistconfigfiles) {
obj.db.GetAllType('cfile', function (err, docs) { if (err == null) { if (docs.length == 0) { console.log('No files found.'); } else { for (var i in docs) { console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' bytes.'); } } } else { console.log('Unable to read from database.'); } process.exit(); }); return;
}
// Display the content of a configuration file in the database
if (obj.args.dbshowconfigfile) {
if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
obj.db.getConfigFile(obj.args.dbshowconfigfile, function (err, docs) {
if (err == null) {
if (docs.length == 0) { console.log('File not found.'); } else {
var data = obj.db.decryptData(obj.args.configkey, docs[0].data);
if (data == null) { console.log('Invalid config key.'); } else { console.log(data); }
}
} else { console.log('Unable to read from database.'); }
process.exit();
}); return;
}
// Delete all configuration files from database
if (obj.args.dbdeleteconfigfiles) {
console.log('Deleting all configuration files from the database...'); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); });
}
// Push all relevent files from meshcentral-data into the database
if (obj.args.dbpushconfigfiles) {
if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
if ((obj.args.dbpushconfigfiles !== true) && (typeof obj.args.dbpushconfigfiles != 'string')) {
console.log('Usage: --dbpulldatafiles (path) This will import files from folder into the database');
console.log(' --dbpulldatafiles This will import files from meshcentral-data into the db.');
process.exit();
} else {
if ((obj.args.dbpushconfigfiles == '*') || (obj.args.dbpushconfigfiles === true)) { obj.args.dbpushconfigfiles = obj.datapath; }
obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
if (err != null) { console.log('ERROR: Unable to read from folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
var configFound = false;
for (var i in files) { if (files[i] == 'config.json') { configFound = true; } }
if (configFound == false) { console.log('ERROR: No config.json in folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
obj.db.RemoveAllOfType('cfile', function () {
obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
var lockCount = 1
for (var i in files) {
const file = files[i];
if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) {
const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
lockCount++;
obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
}
// Display the content of a configuration file in the database
if (obj.args.dbshowconfigfile) {
if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
obj.db.getConfigFile(obj.args.dbshowconfigfile, function (err, docs) {
if (err == null) {
if (docs.length == 0) { console.log('File not found.'); } else {
var data = obj.db.decryptData(obj.args.configkey, docs[0].data);
if (data == null) { console.log('Invalid config key.'); } else { console.log(data); }
}
if (--lockCount == 0) { process.exit(); }
});
});
});
}
return;
}
} else { console.log('Unable to read from database.'); }
process.exit();
}); return;
}
// Pull all database files into meshcentral-data
if (obj.args.dbpullconfigfiles) {
if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
if (typeof obj.args.dbpullconfigfiles != 'string') {
console.log('Usage: --dbpulldatafiles (path)');
process.exit();
} else {
obj.db.GetAllType('cfile', function (err, docs) {
if (err == null) {
if (docs.length == 0) {
console.log('File not found.');
} else {
for (var i in docs) {
const file = docs[i]._id.split('/')[1], binary = obj.db.decryptData(obj.args.configkey, docs[i].data);
if (binary == null) {
console.log('Invalid config key.');
} else {
var fullFileName = obj.path.join(obj.args.dbpullconfigfiles, file);
try { obj.fs.writeFileSync(fullFileName, binary); } catch (ex) { console.log('Unable to write to ' + fullFileName); process.exit(); return; }
console.log('Pulling ' + file + ', ' + binary.length + ' bytes.');
}
}
}
// Delete all configuration files from database
if (obj.args.dbdeleteconfigfiles) {
console.log('Deleting all configuration files from the database...'); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); });
}
// Push all relevent files from meshcentral-data into the database
if (obj.args.dbpushconfigfiles) {
if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
if ((obj.args.dbpushconfigfiles !== true) && (typeof obj.args.dbpushconfigfiles != 'string')) {
console.log('Usage: --dbpulldatafiles (path) This will import files from folder into the database');
console.log(' --dbpulldatafiles This will import files from meshcentral-data into the db.');
process.exit();
} else {
console.log('Unable to read from database.');
}
process.exit();
});
}
return;
}
if (obj.args.dbexport) {
// Export the entire database to a JSON file
if (obj.args.dbexport == true) { obj.args.dbexport = obj.getConfigFilePath('meshcentral.db.json'); }
obj.db.GetAll(function (err, docs) {
obj.fs.writeFileSync(obj.args.dbexport, JSON.stringify(docs));
console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexport + '.'); process.exit();
});
return;
}
if (obj.args.dbexportmin) {
// Export a minimal database to a JSON file. Export only users, meshes and nodes.
// This is a useful command to look at the database.
if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); }
obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) {
obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs));
console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit();
});
return;
}
if (obj.args.dbimport) {
// Import the entire database from a JSON file
if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
var json = null, json2 = "", badCharCount = 0;
try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + ': ' + e); process.exit(); }
for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); }
if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
for (i in json) { if ((json[i].type == "mesh") && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } } // Escape MongoDB invalid field chars
//for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname
setTimeout(function () { // If the Mongo database is being created for the first time, there is a race condition here. This will get around it.
obj.db.RemoveAll(function () {
obj.db.InsertMany(json, function (err) {
if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit();
});
});
}, 100);
return;
}
/*
if (obj.args.dbimport) {
// Import the entire database from a very large JSON file
obj.db.RemoveAll(function () {
if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
var json = null, json2 = "", badCharCount = 0;
const StreamArray = require('stream-json/streamers/StreamArray');
const jsonStream = StreamArray.withParser();
jsonStream.on('data', function (data) { obj.db.Set(data.value); });
jsonStream.on('end', () => { console.log('Done.'); process.exit(); });
obj.fs.createReadStream(obj.args.dbimport).pipe(jsonStream.input);
});
return;
}
*/
if (obj.args.dbmerge) {
// Import the entire database from a JSON file
if (obj.args.dbmerge == true) { obj.args.dbmerge = obj.getConfigFilePath('meshcentral.db.json'); }
var json = null, json2 = "", badCharCount = 0;
try { json = obj.fs.readFileSync(obj.args.dbmerge, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
// Get all users from current database
obj.db.GetAllType('user', function (err, docs) {
var users = {}, usersCount = 0;
for (var i in docs) { users[docs[i]._id] = docs[i]; usersCount++; }
// Fetch all meshes from the database
obj.db.GetAllType('mesh', function (err, docs) {
obj.common.unEscapeAllLinksFieldName(docs);
var meshes = {}, meshesCount = 0;
for (var i in docs) { meshes[docs[i]._id] = docs[i]; meshesCount++; }
console.log('Loaded ' + usersCount + ' users and ' + meshesCount + ' meshes.');
// Look at each object in the import file
var objectToAdd = [];
for (var i in json) {
var newobj = json[i];
if (newobj.type == 'user') {
// Check if the user already exists
var existingUser = users[newobj._id];
if (existingUser) {
// Merge the links
if (typeof newobj.links == 'object') {
for (var j in newobj.links) {
if ((existingUser.links == null) || (existingUser.links[j] == null)) {
if (existingUser.links == null) { existingUser.links = {}; }
existingUser.links[j] = newobj.links[j];
if ((obj.args.dbpushconfigfiles == '*') || (obj.args.dbpushconfigfiles === true)) { obj.args.dbpushconfigfiles = obj.datapath; }
obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
if (err != null) { console.log('ERROR: Unable to read from folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
var configFound = false;
for (var i in files) { if (files[i] == 'config.json') { configFound = true; } }
if (configFound == false) { console.log('ERROR: No config.json in folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
obj.db.RemoveAllOfType('cfile', function () {
obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
var lockCount = 1
for (var i in files) {
const file = files[i];
if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) {
const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
lockCount++;
obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
}
}
}
if (existingUser.name == 'admin') { existingUser.links = {}; }
objectToAdd.push(existingUser); // Add this user
} else {
objectToAdd.push(newobj); // Add this user
}
} else if (newobj.type == 'mesh') {
// Add this object after escaping
objectToAdd.push(obj.common.escapeLinksFieldName(newobj));
} // Don't add nodes.
if (--lockCount == 0) { process.exit(); }
});
});
});
}
console.log('Importing ' + objectToAdd.length + ' object(s)...');
var pendingCalls = 1;
for (var i in objectToAdd) {
pendingCalls++;
obj.db.Set(objectToAdd[i], function (err) { if (err != null) { console.log(err); } else { if (--pendingCalls == 0) { process.exit(); } } });
}
if (--pendingCalls == 0) { process.exit(); }
});
});
return;
}
// Load configuration for database if needed
if (obj.args.loadconfigfromdb) {
var key = null;
if (typeof obj.args.configkey == 'string') { key = obj.args.configkey; }
else if (typeof obj.args.loadconfigfromdb == 'string') { key = obj.args.loadconfigfromdb; }
if (key == null) { console.log('Error, --configkey is required.'); process.exit(); return; }
obj.db.getAllConfigFiles(key, function (configFiles) {
if (configFiles == null) { console.log('Error, no configuration files found or invalid configkey.'); process.exit(); return; }
if (!configFiles['config.json']) { console.log('Error, could not file config.json from database.'); process.exit(); return; }
obj.configurationFiles = configFiles;
// Parse the new configuration file
var config2 = null;
try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from database.'); process.exit(); return; }
// Set the command line arguments to the config file if they are not present
if (!config2.settings) { config2.settings = {}; }
for (i in args) { config2.settings[i] = args[i]; }
// Lower case all keys in the config file
try {
require('./common.js').objKeysToLower(config2);
} catch (ex) {
console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
process.exit();
return;
}
// Grad some of the values from the original config.json file if present.
config2['mongodb'] = config['mongodb'];
config2['mongodbcol'] = config['mongodbcol'];
config2['dbencryptkey'] = config['dbencryptkey'];
// Pull all database files into meshcentral-data
if (obj.args.dbpullconfigfiles) {
if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
if (typeof obj.args.dbpullconfigfiles != 'string') {
console.log('Usage: --dbpulldatafiles (path)');
process.exit();
} else {
obj.db.GetAllType('cfile', function (err, docs) {
if (err == null) {
if (docs.length == 0) {
console.log('File not found.');
} else {
for (var i in docs) {
const file = docs[i]._id.split('/')[1], binary = obj.db.decryptData(obj.args.configkey, docs[i].data);
if (binary == null) {
console.log('Invalid config key.');
} else {
var fullFileName = obj.path.join(obj.args.dbpullconfigfiles, file);
try { obj.fs.writeFileSync(fullFileName, binary); } catch (ex) { console.log('Unable to write to ' + fullFileName); process.exit(); return; }
console.log('Pulling ' + file + ', ' + binary.length + ' bytes.');
}
}
}
} else {
console.log('Unable to read from database.');
}
process.exit();
});
}
return;
}
// We got a new config.json from the database, let's use it.
config = obj.config = config2;
obj.StartEx1b();
if (obj.args.dbexport) {
// Export the entire database to a JSON file
if (obj.args.dbexport == true) { obj.args.dbexport = obj.getConfigFilePath('meshcentral.db.json'); }
obj.db.GetAll(function (err, docs) {
obj.fs.writeFileSync(obj.args.dbexport, JSON.stringify(docs));
console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexport + '.'); process.exit();
});
return;
}
if (obj.args.dbexportmin) {
// Export a minimal database to a JSON file. Export only users, meshes and nodes.
// This is a useful command to look at the database.
if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); }
obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) {
obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs));
console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit();
});
return;
}
if (obj.args.dbimport) {
// Import the entire database from a JSON file
if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
var json = null, json2 = "", badCharCount = 0;
try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + ': ' + e); process.exit(); }
for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); }
if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
for (i in json) { if ((json[i].type == "mesh") && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } } // Escape MongoDB invalid field chars
//for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname
setTimeout(function () { // If the Mongo database is being created for the first time, there is a race condition here. This will get around it.
obj.db.RemoveAll(function () {
obj.db.InsertMany(json, function (err) {
if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit();
});
});
}, 100);
return;
}
/*
if (obj.args.dbimport) {
// Import the entire database from a very large JSON file
obj.db.RemoveAll(function () {
if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
var json = null, json2 = "", badCharCount = 0;
const StreamArray = require('stream-json/streamers/StreamArray');
const jsonStream = StreamArray.withParser();
jsonStream.on('data', function (data) { obj.db.Set(data.value); });
jsonStream.on('end', () => { console.log('Done.'); process.exit(); });
obj.fs.createReadStream(obj.args.dbimport).pipe(jsonStream.input);
});
return;
}
*/
if (obj.args.dbmerge) {
// Import the entire database from a JSON file
if (obj.args.dbmerge == true) { obj.args.dbmerge = obj.getConfigFilePath('meshcentral.db.json'); }
var json = null, json2 = "", badCharCount = 0;
try { json = obj.fs.readFileSync(obj.args.dbmerge, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
// Get all users from current database
obj.db.GetAllType('user', function (err, docs) {
var users = {}, usersCount = 0;
for (var i in docs) { users[docs[i]._id] = docs[i]; usersCount++; }
// Fetch all meshes from the database
obj.db.GetAllType('mesh', function (err, docs) {
obj.common.unEscapeAllLinksFieldName(docs);
var meshes = {}, meshesCount = 0;
for (var i in docs) { meshes[docs[i]._id] = docs[i]; meshesCount++; }
console.log('Loaded ' + usersCount + ' users and ' + meshesCount + ' meshes.');
// Look at each object in the import file
var objectToAdd = [];
for (var i in json) {
var newobj = json[i];
if (newobj.type == 'user') {
// Check if the user already exists
var existingUser = users[newobj._id];
if (existingUser) {
// Merge the links
if (typeof newobj.links == 'object') {
for (var j in newobj.links) {
if ((existingUser.links == null) || (existingUser.links[j] == null)) {
if (existingUser.links == null) { existingUser.links = {}; }
existingUser.links[j] = newobj.links[j];
}
}
}
if (existingUser.name == 'admin') { existingUser.links = {}; }
objectToAdd.push(existingUser); // Add this user
} else {
objectToAdd.push(newobj); // Add this user
}
} else if (newobj.type == 'mesh') {
// Add this object after escaping
objectToAdd.push(obj.common.escapeLinksFieldName(newobj));
} // Don't add nodes.
}
console.log('Importing ' + objectToAdd.length + ' object(s)...');
var pendingCalls = 1;
for (var i in objectToAdd) {
pendingCalls++;
obj.db.Set(objectToAdd[i], function (err) { if (err != null) { console.log(err); } else { if (--pendingCalls == 0) { process.exit(); } } });
}
if (--pendingCalls == 0) { process.exit(); }
});
});
return;
}
// Load configuration for database if needed
if (obj.args.loadconfigfromdb) {
var key = null;
if (typeof obj.args.configkey == 'string') { key = obj.args.configkey; }
else if (typeof obj.args.loadconfigfromdb == 'string') { key = obj.args.loadconfigfromdb; }
if (key == null) { console.log('Error, --configkey is required.'); process.exit(); return; }
obj.db.getAllConfigFiles(key, function (configFiles) {
if (configFiles == null) { console.log('Error, no configuration files found or invalid configkey.'); process.exit(); return; }
if (!configFiles['config.json']) { console.log('Error, could not file config.json from database.'); process.exit(); return; }
obj.configurationFiles = configFiles;
// Parse the new configuration file
var config2 = null;
try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from database.'); process.exit(); return; }
// Set the command line arguments to the config file if they are not present
if (!config2.settings) { config2.settings = {}; }
for (i in args) { config2.settings[i] = args[i]; }
// Lower case all keys in the config file
try {
require('./common.js').objKeysToLower(config2, ["ldapoptions"]);
} catch (ex) {
console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
process.exit();
return;
}
// Grad some of the values from the original config.json file if present.
config2['mongodb'] = config['mongodb'];
config2['mongodbcol'] = config['mongodbcol'];
config2['dbencryptkey'] = config['dbencryptkey'];
// We got a new config.json from the database, let's use it.
config = obj.config = config2;
obj.StartEx1b();
});
} else {
config = obj.config = getConfig(true);
obj.StartEx1b();
}
});
} else {
config = obj.config = getConfig(true);
obj.StartEx1b();
}
});
);
};
// Time to start the serverf or real.
@ -543,8 +547,9 @@ function CreateMeshCentralServer(config, args) {
if (obj.config.domains[''].dns != null) { console.log("ERROR: Default domain can't have a DNS name."); return; }
var xdomains = {}; for (i in obj.config.domains) { if (obj.config.domains[i].title == null) { obj.config.domains[i].title = 'MeshCentral'; } if (obj.config.domains[i].title2 == null) { obj.config.domains[i].title2 = '2.0 Beta 2'; } xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
for (i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } }
for (i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in config.json."); return; } } }
for (i in obj.config.domains) {
if (typeof config.domains[i].auth == 'string') { config.domains[i].auth = config.domains[i].auth.toLowerCase(); }
if (obj.config.domains[i].limits == null) { obj.config.domains[i].limits = {}; }
if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; }
obj.config.domains[i].id = i;
@ -552,6 +557,12 @@ function CreateMeshCentralServer(config, args) {
if (typeof obj.config.domains[i].userblockedip == 'string') { if (obj.config.domains[i].userblockedip == '') { obj.config.domains[i].userblockedip = null; } else { obj.config.domains[i].userblockedip = obj.config.domains[i].userallowedip.split(','); } }
if (typeof obj.config.domains[i].agentallowedip == 'string') { if (obj.config.domains[i].agentallowedip == '') { obj.config.domains[i].agentallowedip = null; } else { obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip.split(','); } }
if (typeof obj.config.domains[i].agentblockedip == 'string') { if (obj.config.domains[i].agentblockedip == '') { obj.config.domains[i].agentblockedip = null; } else { obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip.split(','); } }
if ((obj.config.domains[i].auth == 'ldap') && (typeof obj.config.domains[i].ldapoptions != 'object')) {
if (i == '') { console.log("ERROR: Default domain is LDAP, but is missing LDAPOptions."); } else { console.log("ERROR: Domain '" + i + "' is LDAP, but is missing LDAPOptions."); }
process.exit();
return;
}
if ((obj.config.domains[i].auth == 'ldap') || (obj.config.domains[i].auth == 'sspi')) { obj.config.domains[i].newaccounts = 0; } // No new accounts allowed in SSPI/LDAP authentication modes.
}
// Log passed arguments into Windows Service Log
@ -1197,8 +1208,8 @@ function CreateMeshCentralServer(config, args) {
// Read the agent recovery core if present
var meshAgentRecoveryCore = null;
if (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'agentrecoverycore.js')) == true) {
try { meshAgentRecoveryCore = obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'agentrecoverycore.js')).toString(); } catch (ex) { }
if (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'meshcore_diagnostic.js')) == true) {
try { meshAgentRecoveryCore = obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'meshcore_diagnostic.js')).toString(); } catch (ex) { }
if (meshAgentRecoveryCore != null) {
modulesAdd['windows-agentrecovery'] = ['var addedModules = [];\r\n'];
modulesAdd['linux-agentrecovery'] = ['var addedModules = [];\r\n'];
@ -1390,6 +1401,7 @@ function CreateMeshCentralServer(config, args) {
23: { id: 23, localname: 'MeshAgent-NodeJS', rname: 'meshagent', desc: 'NodeJS', update: false, amt: false, platform: 'node', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // Get this one from NPM
24: { id: 24, localname: 'meshagent_arm-linaro', rname: 'meshagent', desc: 'Linux ARM Linaro', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' },
25: { id: 25, localname: 'meshagent_armhf', rname: 'meshagent', desc: 'Linux ARM - HardFloat', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // "armv6l" and "armv7l"
26: { id: 26, localname: 'meshagent_arm64', rname: 'meshagent', desc: 'Linux ARMv8-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // "aarch64"
10003: { id: 3, localname: 'MeshService.exe', rname: 'meshagent.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // Unsigned version of the Windows MeshAgent x86
10004: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' } // Unsigned version of the Windows MeshAgent x64
};
@ -1618,11 +1630,12 @@ function getConfig(createSampleConfig) {
// Lower case all keys in the config file
try {
require('./common.js').objKeysToLower(config);
require('./common.js').objKeysToLower(config, ["ldapoptions"]);
} catch (ex) {
console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
process.exit();
}
return config;
}
@ -1634,7 +1647,7 @@ function InstallModules(modules, func) {
try {
var xxmodule = require(modules[i]);
} catch (e) {
if (previouslyInstalledModules[modules[i]] !== true) { previouslyInstalledModules[modules[i]] = true; missingModules.push(modules[i]); }
if (previouslyInstalledModules[modules[i]] !== true) { missingModules.push(modules[i]); }
}
}
if (missingModules.length > 0) { InstallModule(missingModules.shift(), InstallModules, modules, func); } else { func(); }
@ -1659,6 +1672,7 @@ function InstallModule(modulename, func, tag1, tag2) {
process.exit();
return;
}
previouslyInstalledModules[modulename] = true;
func(tag1, tag2);
return;
});
@ -1680,18 +1694,29 @@ function mainStart(args) {
var config = getConfig(false);
if (config == null) { process.exit(); }
// Lowercase the auth value is present
for (var i in config.domains) { if (typeof config.domains[i].auth == 'string') { config.domains[i].auth = config.domains[i].auth.toLowerCase(); } }
// Check is Windows SSPI and YubiKey OTP will be used
var sspi = false;
var ldap = false;
var allsspi = true;
var yubikey = false;
if (require('os').platform() == 'win32') { for (var i in config.domains) { if (config.domains[i].auth == 'sspi') { sspi = true; } else { allsspi = false; } } } else { allsspi = false; }
for (var i in config.domains) { if (config.domains[i].yubikey != null) { yubikey = true; } }
var domainCount = 0;
if (require('os').platform() == 'win32') { for (var i in config.domains) { domainCount++; if (config.domains[i].auth == 'sspi') { sspi = true; } else { allsspi = false; } } } else { allsspi = false; }
if (domainCount == 0) { allsspi = false; }
for (var i in config.domains) {
if (config.domains[i].yubikey != null) { yubikey = true; }
if (config.domains[i].auth == 'ldap') { ldap = true; }
}
// Build the list of required modules
var modules = ['ws', 'nedb', 'https', 'yauzl', 'xmldom', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-handlebars'];
if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules
if (ldap == true) { modules.push('ldapauth-fork'); }
if (config.letsencrypt != null) { modules.push('greenlock'); modules.push('le-store-certbot'); modules.push('le-challenge-fs'); modules.push('le-acme-core'); } // Add Greenlock Modules
if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoDB
if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoJS
else if (config.settings.mongo != null) { modules.push('mongodb'); } // Add MongoDB
if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support
// Get the current node version

View File

@ -16,16 +16,11 @@
module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie) {
var obj = {};
obj.ws = ws;
obj.req = req;
obj.peer = null;
obj.user = user;
obj.cookie = cookie;
obj.parent = parent;
obj.id = req.query.id;
obj.remoteaddr = obj.ws._socket.remoteAddress;
obj.domain = domain;
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
obj.parent.relaySessionCount++;
// Relay session count (we may remove this in the future)
obj.relaySessionCounted = true;
parent.relaySessionCount++;
// Mesh Rights
const MESHRIGHT_EDITMESH = 1;
@ -46,43 +41,59 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
const SITERIGHT_SERVERUPDATE = 16;
const SITERIGHT_LOCKED = 32;
// Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr(addr) { if (addr.startsWith('::ffff:')) { return addr.substring(7); } else { return addr; } }
// Disconnect this agent
obj.close = function (arg) {
if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'Relay: Soft disconnect (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket
if (arg == 2) { try { obj.ws._socket._parent.end(); obj.parent.parent.debug(1, 'Relay: Hard disconnect (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Relay: Soft disconnect (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket
if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug(1, 'Relay: Hard disconnect (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
// Aggressive cleanup
delete obj.id;
delete obj.ws;
delete obj.peer;
};
obj.sendAgentMessage = function (command, userid, domainid) {
var rights;
var rights, mesh;
if (command.nodeid == null) return false;
var user = obj.parent.users[userid];
var user = parent.users[userid];
if (user == null) return false;
var splitnodeid = command.nodeid.split('/');
// Check that we are in the same domain and the user has rights over this node.
if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domainid)) {
// Get the user object
// See if the node is connected
var agent = obj.parent.wsagents[command.nodeid];
var agent = parent.wsagents[command.nodeid];
if (agent != null) {
// Check if we have permission to send a message to that node
rights = user.links[agent.dbMeshKey];
if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
mesh = parent.meshes[agent.dbMeshKey];
if ((rights != null) && (mesh != null) || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
command.sessionid = ws.sessionId; // Set the session id, required for responses.
command.rights = rights.rights; // Add user rights flags to the message
command.consent = mesh.consent; // Add user consent
if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
command.username = user.name; // Add user name
delete command.nodeid; // Remove the nodeid since it's implyed.
agent.send(JSON.stringify(command));
return true;
}
} else {
// Check if a peer server is connected to this agent
var routing = obj.parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type
var routing = parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type
if (routing != null) {
// Check if we have permission to send a message to that node
rights = user.links[routing.meshid];
mesh = parent.meshes[routing.meshid];
if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
command.fromSessionid = ws.sessionId; // Set the session id, required for responses.
command.rights = rights.rights; // Add user rights flags to the message
obj.parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
command.consent = mesh.consent; // Add user consent
if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
command.username = user.name; // Add user name
parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
return true;
}
}
@ -97,12 +108,12 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
// If this is a MeshMessenger session, the ID is the two userid's and authentication must match one of them.
if (obj.id.startsWith('meshmessenger/')) {
if ((obj.id.startsWith('meshmessenger/user/') == true) && (obj.user == null)) { try { obj.close(); } catch (e) { } return null; } // If user-to-user, both sides need to be authenticated.
if ((obj.id.startsWith('meshmessenger/user/') == true) && (user == null)) { try { obj.close(); } catch (e) { } return null; } // If user-to-user, both sides need to be authenticated.
var x = obj.id.split('/'), user1 = x[1] + '/' + x[2] + '/' + x[3], user2 = x[4] + '/' + x[5] + '/' + x[6];
if ((x[1] != 'user') && (x[4] != 'user')) { try { obj.close(); } catch (e) { } return null; } // MeshMessenger session must have at least one authenticated user
if ((x[1] == 'user') && (x[4] == 'user')) {
// If this is a user-to-user session, you must be authenticated to join.
if ((obj.user._id != user1) && (obj.user._id != user2)) { try { obj.close(); } catch (e) { } return null; }
if ((user._id != user1) && (user._id != user2)) { try { obj.close(); } catch (e) { } return null; }
} else {
// If only one side of the session is a user
// !!!!! TODO: Need to make sure that one of the two sides is the correct user. !!!!!
@ -115,9 +126,9 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
if (!parent.args.notls) {
// Check the identifier, if running without TLS, skip this.
var ids = obj.id.split(':');
if (ids.length != 3) { obj.ws.close(); obj.id = null; return null; } // Invalid ID, drop this.
if (parent.crypto.createHmac('SHA384', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { obj.ws.close(); obj.id = null; return null; } // Invalid HMAC, drop this.
if ((Date.now() - parseInt(ids[1])) > 120000) { obj.ws.close(); obj.id = null; return null; } // Expired time, drop this.
if (ids.length != 3) { ws.close(); delete obj.id; return null; } // Invalid ID, drop this.
if (parent.crypto.createHmac('SHA384', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { ws.close(); delete obj.id; return null; } // Invalid HMAC, drop this.
if ((Date.now() - parseInt(ids[1])) > 120000) { ws.close(); delete obj.id; return null; } // Expired time, drop this.
obj.id = ids[0];
}
*/
@ -129,9 +140,11 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
if (relayinfo.state == 1) {
// Check that at least one connection is authenticated
if ((obj.authenticated != true) && (relayinfo.peer1.authenticated != true)) {
obj.id = null;
obj.ws.close();
obj.parent.parent.debug(1, 'Relay without-auth: ' + obj.id + ' (' + obj.remoteaddr + ')');
ws.close();
parent.parent.debug(1, 'Relay without-auth: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')');
delete obj.id;
delete obj.ws;
delete obj.peer;
return null;
}
@ -140,7 +153,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
obj.peer.peer = obj;
relayinfo.peer2 = obj;
relayinfo.state = 2;
obj.ws.send('c'); // Send connect to both peers
ws.send('c'); // Send connect to both peers
relayinfo.peer1.ws.send('c');
relayinfo.peer1.ws._socket.resume(); // Release the traffic
relayinfo.peer2.ws._socket.resume(); // Release the traffic
@ -148,24 +161,26 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
relayinfo.peer1.ws.peer = relayinfo.peer2.ws;
relayinfo.peer2.ws.peer = relayinfo.peer1.ws;
obj.parent.parent.debug(1, 'Relay connected: ' + obj.id + ' (' + obj.remoteaddr + ' --> ' + obj.peer.remoteaddr + ')');
parent.parent.debug(1, 'Relay connected: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ' --> ' + cleanRemoteAddr(obj.peer.ws._socket.remoteAddress) + ')');
} else {
// Connected already, drop (TODO: maybe we should re-connect?)
obj.id = null;
obj.ws.close();
obj.parent.parent.debug(1, 'Relay duplicate: ' + obj.id + ' (' + obj.remoteaddr + ')');
ws.close();
parent.parent.debug(1, 'Relay duplicate: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')');
delete obj.id;
delete obj.ws;
delete obj.peer;
return null;
}
} else {
// Wait for other relay connection
ws._socket.pause(); // Hold traffic until the other connection
parent.wsrelays[obj.id] = { peer1: obj, state: 1 };
obj.parent.parent.debug(1, 'Relay holding: ' + obj.id + ' (' + obj.remoteaddr + ') ' + (obj.authenticated ? 'Authenticated' : ''));
parent.parent.debug(1, 'Relay holding: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ') ' + (obj.authenticated ? 'Authenticated' : ''));
// Check if a peer server has this connection
if (parent.parent.multiServer != null) {
var rsession = obj.parent.wsPeerRelays[obj.id];
if ((rsession != null) && (rsession.serverId > obj.parent.parent.serverId)) {
var rsession = parent.wsPeerRelays[obj.id];
if ((rsession != null) && (rsession.serverId > parent.parent.serverId)) {
// We must initiate the connection to the peer
parent.parent.multiServer.createPeerRelay(ws, req, rsession.serverId, req.session.userid);
delete parent.wsrelays[obj.id];
@ -194,14 +209,15 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
// If error, close both sides of the relay.
ws.on('error', function (err) {
obj.parent.relaySessionErrorCount++;
console.log('Relay error from ' + obj.remoteaddr + ', ' + err.toString().split('\r')[0] + '.');
parent.relaySessionErrorCount++;
if (obj.relaySessionCounted) { parent.relaySessionCount--; delete obj.relaySessionCounted; }
console.log('Relay error from ' + cleanRemoteAddr(ws._socket.remoteAddress) + ', ' + err.toString().split('\r')[0] + '.');
closeBothSides();
});
// If the relay web socket is closed, close both sides.
ws.on('close', function (req) {
obj.parent.relaySessionCount--;
if (obj.relaySessionCounted) { parent.relaySessionCount--; delete obj.relaySessionCounted; }
closeBothSides();
});
@ -213,58 +229,73 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
if (relayinfo.state == 2) {
// Disconnect the peer
var peer = (relayinfo.peer1 == obj) ? relayinfo.peer2 : relayinfo.peer1;
obj.parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + obj.remoteaddr + ' --> ' + peer.remoteaddr + ')');
peer.id = null;
try { if (peer.relaySessionCounted) { parent.relaySessionCount--; delete peer.relaySessionCounted; } } catch (ex) { console.log(ex); }
parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ' --> ' + cleanRemoteAddr(peer.ws._socket.remoteAddress) + ')');
try { peer.ws.close(); } catch (e) { } // Soft disconnect
try { peer.ws._socket._parent.end(); } catch (e) { } // Hard disconnect
// Aggressive peer cleanup
delete peer.id;
delete peer.ws;
delete peer.peer;
} else {
obj.parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + obj.remoteaddr + ')');
parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')');
}
delete parent.wsrelays[obj.id];
}
obj.peer = null;
obj.id = null;
}
// Aggressive cleanup
delete obj.id;
delete obj.ws;
delete obj.peer;
}
// Mark this relay session as authenticated if this is the user end.
obj.authenticated = (obj.user != null);
obj.authenticated = (user != null);
if (obj.authenticated) {
// Kick off the routing, if we have agent routing instructions, process them here.
// Routing instructions can only be given by a authenticated user
if ((obj.cookie != null) && (obj.cookie.nodeid != null) && (obj.cookie.tcpport != null) && (obj.cookie.domainid != null)) {
if ((cookie != null) && (cookie.nodeid != null) && (cookie.tcpport != null) && (cookie.domainid != null)) {
// We have routing instructions in the cookie, but first, check user access for this node.
obj.parent.db.Get(obj.cookie.nodeid, function (err, docs) {
parent.db.Get(cookie.nodeid, function (err, docs) {
if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket
var node = docs[0];
// Check if this user has permission to manage this computer
var meshlinks = obj.user.links[node.meshid];
var meshlinks = user.links[node.meshid];
if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
// Send connection request to agent
if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
var command = { nodeid: obj.cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: obj.cookie.tcpport, tcpaddr: obj.cookie.tcpaddr };
obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, obj.user._id, obj.cookie.domainid) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); }
var command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr };
parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, user._id, cookie.domainid) == false) { delete obj.id; parent.parent.debug(1, 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); }
performRelay();
});
return obj;
} else if ((req.query.nodeid != null) && (req.query.tcpport != null)) {
} else if ((req.query.nodeid != null) && ((req.query.tcpport != null) || (req.query.udpport != null))) {
// We have routing instructions in the URL arguments, but first, check user access for this node.
obj.parent.db.Get(req.query.nodeid, function (err, docs) {
parent.db.Get(req.query.nodeid, function (err, docs) {
if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket
var node = docs[0];
// Check if this user has permission to manage this computer
var meshlinks = obj.user.links[node.meshid];
var meshlinks = user.links[node.meshid];
if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
// Send connection request to agent
if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: req.query.tcpport, tcpaddr: ((req.query.tcpaddr == null) ? '127.0.0.1' : req.query.tcpaddr) };
obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, obj.user._id, obj.domain.id) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); }
if (req.query.tcpport != null) {
var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: req.query.tcpport, tcpaddr: ((req.query.tcpaddr == null) ? '127.0.0.1' : req.query.tcpaddr) };
parent.parent.debug(1, 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug(1, 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); }
} else if (req.query.udpport != null) {
var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, udpport: req.query.udpport, udpaddr: ((req.query.udpaddr == null) ? '127.0.0.1' : req.query.udpaddr) };
parent.parent.debug(1, 'Relay: Sending agent UDP tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug(1, 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); }
}
performRelay();
});
return obj;

View File

@ -19,6 +19,24 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
const path = require('path');
const common = parent.common;
// Mesh Rights
const MESHRIGHT_EDITMESH = 1;
const MESHRIGHT_MANAGEUSERS = 2;
const MESHRIGHT_MANAGECOMPUTERS = 4;
const MESHRIGHT_REMOTECONTROL = 8;
const MESHRIGHT_AGENTCONSOLE = 16;
const MESHRIGHT_SERVERFILES = 32;
const MESHRIGHT_WAKEDEVICE = 64;
const MESHRIGHT_SETNOTES = 128;
// Site rights
const SITERIGHT_SERVERBACKUP = 1;
const SITERIGHT_MANAGEUSERS = 2;
const SITERIGHT_SERVERRESTORE = 4;
const SITERIGHT_FILEACCESS = 8;
const SITERIGHT_SERVERUPDATE = 16;
const SITERIGHT_LOCKED = 32;
var obj = {};
obj.user = user;
obj.domain = domain;
@ -43,7 +61,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var user = parent.users[obj.user._id];
if (user) {
if (parent.parent.multiServer == null) {
parent.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: obj.user.name, count: parent.wssessions[obj.user._id].length, nolog: 1, domain: domain.id });
var targets = ['*', 'server-users'];
if (obj.user.groups) { for (var i in obj.user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { action: 'wssessioncount', username: obj.user.name, count: parent.wssessions[obj.user._id].length, nolog: 1, domain: domain.id });
} else {
parent.recountSessions(ws.sessionId); // Recount sessions
}
@ -107,10 +127,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (agent != null) {
// Check if we have permission to send a message to that node
var rights = user.links[agent.dbMeshKey];
if ((rights != null) && ((rights.rights & 8) || (rights.rights & 256))) { // 8 is remote control permission, 256 is desktop read only
command.sessionid = ws.sessionId; // Set the session id, required for responses.
var mesh = parent.meshes[agent.dbMeshKey];
if ((rights != null) && (mesh != null) && ((rights.rights & 8) || (rights.rights & 256))) { // 8 is remote control permission, 256 is desktop read only
command.sessionid = ws.sessionId; // Set the session id, required for responses
command.rights = rights.rights; // Add user rights flags to the message
delete command.nodeid; // Remove the nodeid since it's implyed.
command.consent = mesh.consent; // Add user consent
if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
command.username = user.name; // Add user name
delete command.nodeid; // Remove the nodeid since it's implied
try { agent.send(JSON.stringify(command)); } catch (ex) { }
}
} else {
@ -119,9 +143,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (routing != null) {
// Check if we have permission to send a message to that node
var rights = user.links[routing.meshid];
if ((rights != null) && ((rights.rights & 8) || (rights.rights & 256))) { // 8 is remote control permission
command.fromSessionid = ws.sessionId; // Set the session id, required for responses.
var mesh = parent.meshes[agent.dbMeshKey];
if ((rights != null) && (mesh != null) && ((rights.rights & 8) || (rights.rights & 256))) { // 8 is remote control permission
command.fromSessionid = ws.sessionId; // Set the session id, required for responses
command.rights = rights.rights; // Add user rights flags to the message
command.consent = mesh.consent; // Add user consent
if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
command.username = user.name; // Add user name
parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
}
}
@ -177,7 +205,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.wssessions2[ws.sessionId] = ws;
if (!parent.wssessions[user._id]) { parent.wssessions[user._id] = [ws]; } else { parent.wssessions[user._id].push(ws); }
if (parent.parent.multiServer == null) {
parent.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: parent.wssessions[user._id].length, nolog: 1, domain: domain.id });
var targets = ['*', 'server-users'];
if (obj.user.groups) { for (var i in obj.user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { action: 'wssessioncount', username: user.name, count: parent.wssessions[user._id].length, nolog: 1, domain: domain.id });
} else {
parent.recountSessions(ws.sessionId); // Recount sessions
}
@ -212,6 +242,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
"Connected Users": Object.keys(parent.wssessions).length,
"Users Sessions": Object.keys(parent.wssessions2).length,
"Relay Sessions": parent.relaySessionCount,
"Relay Count": Object.keys(parent.wsrelays).length
};
if (parent.relaySessionErrorCount != 0) { serverStats['Relay Errors'] = parent.relaySessionErrorCount; }
if (parent.parent.mpsserver != null) { serverStats['Connected Intel&reg; AMT'] = Object.keys(parent.parent.mpsserver.ciraConnections).length; }
@ -233,8 +264,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
// Build server information object
var serverinfo = { name: parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((parent.parent.mailserver != null) && (domain.auth != 'sspi')), domainauth: (domain.auth == 'sspi') };
var serverinfo = { name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap')), domainauth: ((domain.auth == 'sspi') || (domain.auth == 'ldap')) };
if (args.notls == true) { serverinfo.https = false; } else { serverinfo.https = true; serverinfo.redirport = args.redirport; }
if (typeof domain.userconsentflags == 'number') { serverinfo.consent = domain.userconsentflags; }
// Send server information
try { ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: serverinfo })); } catch (ex) { }
@ -261,6 +293,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
try { ws.send(JSON.stringify({ action: 'authcookie', cookie: parent.parent.encodeCookie({ userid: user._id, domainid: domain.id }, parent.parent.loginCookieEncryptionKey) })); } catch (ex) { }
break;
}
case 'logincookie':
{
// If allowed, return a login cookie
if (parent.parent.config.settings.allowlogintoken === true) {
try { ws.send(JSON.stringify({ action: 'logincookie', cookie: parent.parent.encodeCookie({ u: user._id, a: 3 }, parent.parent.loginCookieEncryptionKey) })); } catch (ex) { }
}
break;
}
case 'servertimelinestats':
{
if ((user.siteadmin & 21) == 0) return; // Only site administrators with "site backup" or "site restore" or "site update" permissions can use this.
@ -309,6 +349,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
db.GetAllTypeNoTypeFieldMeshFiltered(links, domain.id, 'node', command.id, function (err, docs) {
var r = {};
for (i in docs) {
// Remove any connectivity and power state information, that should not be in the database anyway.
// TODO: Find why these are sometimes saves in the db.
if (docs[i].conn != null) { delete docs[i].conn; }
if (docs[i].pwr != null) { delete docs[i].pwr; }
if (docs[i].agct != null) { delete docs[i].agct; }
if (docs[i].cict != null) { delete docs[i].cict; }
// Add the connection state
var state = parent.parent.GetConnectivityState(docs[i]._id);
if (state) {
@ -477,7 +524,50 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
switch (cmd) {
case 'help': {
r = 'Available commands: help, info, versions, args, resetserver, showconfig, usersessions, tasklimiter, setmaxtasks, cores,\r\n'
r += 'migrationagents, swarmstats, nodeconfig, heapdump.';
r += 'migrationagents, agentstats, webstats, mpsstats, swarmstats, acceleratorsstats, updatecheck, serverupdate, nodeconfig, heapdump, relays.';
break;
}
case 'agentstats': {
var stats = parent.getAgentStats();
for (var i in stats) {
if (typeof stats[i] == 'object') { r += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { r += (i + ': ' + stats[i] + '\r\n'); }
}
break;
}
case 'webstats': {
var stats = parent.getStats();
for (var i in stats) {
if (typeof stats[i] == 'object') { r += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { r += (i + ': ' + stats[i] + '\r\n'); }
}
break;
}
case 'acceleratorsstats': {
var stats = parent.parent.certificateOperations.getAcceleratorStats();
for (var i in stats) {
if (typeof stats[i] == 'object') { r += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { r += (i + ': ' + stats[i] + '\r\n'); }
}
break;
}
case 'mpsstats': {
var stats = parent.parent.mpsserver.getStats();
for (var i in stats) {
if (typeof stats[i] == 'object') { r += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { r += (i + ': ' + stats[i] + '\r\n'); }
}
break;
}
case 'serverupdate': {
r = 'Performing server update...';
parent.parent.performServerUpdate();
break;
}
case 'updatecheck': {
parent.parent.getLatestServerVersion(function (currentVer, newVer, error) {
var r2 = 'Current Version: ' + currentVer + '\r\n';
if (newVer != null) { r2 += 'Available Version: ' + newVer + '\r\n'; }
if (error != null) { r2 += 'Exception: ' + error + '\r\n'; }
try { ws.send(JSON.stringify({ action: 'serverconsole', value: r2, tag: command.tag })); } catch (ex) { }
});
r = 'Checking server update...';
break;
}
case 'info': {
@ -579,9 +669,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
} else {
for (var i in parent.parent.swarmserver.stats) {
if (typeof parent.parent.swarmserver.stats[i] == 'object') {
r += i + ' ' + JSON.stringify(parent.parent.swarmserver.stats[i]) + '<br />';
r += i + ': ' + JSON.stringify(parent.parent.swarmserver.stats[i]) + '\r\n';
} else {
r += i + ' ' + parent.parent.swarmserver.stats[i] + '<br />';
r += i + ': ' + parent.parent.swarmserver.stats[i] + '\r\n';
}
}
}
@ -603,6 +693,16 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
break;
}
case 'relays': {
for (var i in parent.wsrelays) {
r += 'id: ' + i + ', state: ' + parent.wsrelays[i].state;
if (parent.wsrelays[i].peer1 != null) { r += ', peer1: ' + cleanRemoteAddr(parent.wsrelays[i].peer1.ws._socket.remoteAddress); }
if (parent.wsrelays[i].peer2 != null) { r += ', peer2: ' + cleanRemoteAddr(parent.wsrelays[i].peer2.ws._socket.remoteAddress); }
r += '<br />';
}
if (r == '') { r = 'No relays.'; }
break;
}
default: { // This is an unknown command, return an error message
r = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.';
break;
@ -681,7 +781,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var docs = [];
for (i in parent.users) {
if ((parent.users[i].domain == domain.id) && (parent.users[i].name != '~')) {
docs.push(parent.CloneSafeUser(parent.users[i]));
// If we are part of a user group, we can only see other members of our own group
if ((user.groups == null) || (user.groups.length == 0) || ((parent.users[i].groups != null) && (findOne(parent.users[i].groups, user.groups)))) {
docs.push(parent.CloneSafeUser(parent.users[i]));
}
}
}
try { ws.send(JSON.stringify({ action: 'users', users: docs, tag: command.tag })); } catch (ex) { }
@ -689,15 +792,15 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
case 'changeemail':
{
// Change the email address
if (domain.auth == 'sspi') return;
// Change our own email address
if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) return;
if (common.validateEmail(command.email, 1, 256) == false) return;
if (parent.users[req.session.userid].email != command.email) {
// Check if this email is already validated on a different account
db.GetUserWithVerifiedEmail(domain.id, command.email, function (err, docs) {
if (docs.length > 0) {
// Notify the duplicate email error
try { ws.send(JSON.stringify({ action: 'msg', type: 'notify', value: 'Failed to change email address, another account already using: <b>' + EscapeHtml(command.email) + '</b>.' })); } catch (ex) { }
try { ws.send(JSON.stringify({ action: 'msg', type: 'notify', title: 'Account Settings', tag: 'ServerNotify', value: 'Failed to change email address, another account already using: <b>' + EscapeHtml(command.email) + '</b>.' })); } catch (ex) { }
} else {
// Update the user's email
var oldemail = user.email;
@ -712,10 +815,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
} else {
message.msg = 'Set email of user ' + user.name + ' to ' + user.email;
}
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, message);
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, message);
// Send the verification email
if ((parent.parent.mailserver != null) && (domain.auth != 'sspi')) { parent.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
if (parent.parent.mailserver != null) { parent.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
}
});
}
@ -724,7 +830,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
case 'verifyemail':
{
// Send a account email verification email
if (domain.auth == 'sspi') return;
if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) return;
if (common.validateString(command.email, 3, 1024) == false) return;
if ((parent.parent.mailserver != null) && (parent.users[req.session.userid].email == command.email)) {
// Send the verification email
@ -739,10 +845,36 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if ((user.siteadmin & 2) == 0) break;
if (parent.parent.multiServer == null) {
// No peering, use simple session counting
for (i in parent.wssessions) { if (parent.wssessions[i][0].domainid == domain.id) { wssessions[i] = parent.wssessions[i].length; } }
for (i in parent.wssessions) {
if (parent.wssessions[i][0].domainid == domain.id) {
if ((user.groups == null) || (user.groups.length == 0)) {
// No user groups, count everything
wssessions[i] = parent.wssessions[i].length;
} else {
// Only count if session is for a user in our user groups
var sessionUser = parent.users[parent.wssessions[i][0].userid];
if ((sessionUser != null) && findOne(sessionUser.groups, user.groups)) {
wssessions[i] = parent.wssessions[i].length;
}
}
}
}
} else {
// We have peer servers, use more complex session counting
for (i in parent.sessionsCount) { if (i.split('/')[1] == domain.id) { wssessions[i] = parent.sessionsCount[i]; } }
for (i in parent.sessionsCount) {
if (i.split('/')[1] == domain.id) {
if ((user.groups == null) || (user.groups.length == 0)) {
// No user groups, count everything
wssessions[i] = parent.sessionsCount[i];
} else {
// Only count if session is for a user in our user groups
var sessionUser = parent.users[i];
if ((sessionUser != null) && findOne(sessionUser.groups, user.groups)) {
wssessions[i] = parent.sessionsCount[i];
}
}
}
}
}
try { ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions, tag: command.tag })); } catch (ex) { } // wssessions is: userid --> count
break;
@ -755,6 +887,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var delusersplit = command.userid.split('/'), deluserid = command.userid, deluser = parent.users[deluserid];
if ((deluser == null) || (delusersplit.length != 3) || (delusersplit[1] != domain.id)) break; // Invalid domain, operation only valid for current domain
if ((deluser.siteadmin != null) && (deluser.siteadmin > 0) && (user.siteadmin != 0xFFFFFFFF)) break; // Need full admin to remote another administrator
if ((user.groups != null) && (user.groups.length > 0) && ((deluser.groups == null) || (findOne(deluser.groups, user.groups) == false))) break; // Can only perform this operation on other users of our group.
// Remove all the mesh links to this user
if (deluser.links != null) {
@ -771,8 +904,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
}
// Remove notes for this user
db.Remove('nt' + deluser._id);
db.Remove('ws' + deluser._id); // Remove user web state
db.Remove('nt' + deluser._id); // Remove notes for this user
// Delete all files on the server for this account
try {
@ -782,7 +915,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
db.Remove(deluserid);
delete parent.users[deluserid];
parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id });
var targets = ['*', 'server-users'];
if (deluser.groups) { for (var i in deluser.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', userid: deluserid, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id });
parent.parent.DispatchEvent([deluserid], obj, 'close');
break;
@ -794,12 +930,23 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (common.validateString(command.msg, 1, 256) == false) break; // Notification message is between 1 and 256 characters
// Create the notification message
var notification = { action: "msg", type: "notify", domain: domain.id, "value": command.msg };
var notification = { action: "msg", type: "notify", domain: domain.id, "value": command.msg, "title": user.name, icon: 0, tag: "broadcast" };
// Send the notification on all user sessions for this server
for (var i in parent.wssessions2) {
try {
if (parent.wssessions2[i].domainid == domain.id) { parent.wssessions2[i].send(JSON.stringify(notification)); }
if (parent.wssessions2[i].domainid == domain.id) {
if ((user.groups == null) || (user.groups.length == 0)) {
// We are part of no user groups, send to everyone.
parent.wssessions2[i].send(JSON.stringify(notification));
} else {
// We are part of user groups, only send to sessions of users in our groups.
var sessionUser = parent.users[parent.wssessions2[i].userid];
if ((sessionUser != null) && findOne(sessionUser.groups, user.groups)) {
parent.wssessions2[i].send(JSON.stringify(notification));
}
}
}
} catch (ex) { }
}
@ -825,7 +972,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Account count exceed, do notification
// Create the notification message
var notification = { action: "msg", type: "notify", value: "Account limit reached.", userid: user._id, username: user.name, domain: domain.id };
var notification = { action: "msg", type: "notify", value: "Account limit reached.", title: "Server Limit", userid: user._id, username: user.name, domain: domain.id };
// Get the list of sessions for this user
var sessions = parent.wssessions[user._id];
@ -836,6 +983,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Math.floor(Date.now() / 1000), domain: domain.id };
if (command.email != null) { newuser.email = command.email; } // Email
if (command.resetNextLogin === true) { newuser.passchange = -1; } else { newuser.passchange = Math.floor(Date.now() / 1000); }
if ((user.groups != null) && (user.groups.length > 0)) { newuser.groups = user.groups; } // New account are automatically part of our groups.
parent.users[newuserid] = newuser;
// Create a user, generate a salt and hash the password
@ -844,7 +992,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
newuser.salt = salt;
newuser.hash = hash;
db.SetUser(newuser);
parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id });
var targets = ['*', 'server-users'];
if (newuser.groups) { for (var i in newuser.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id });
});
}
});
@ -853,20 +1004,66 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
case 'edituser':
{
// Edit a user account, may involve changing email or administrator permissions
if (((user.siteadmin & 2) != 0) || (user.name == command.name)) {
var chguserid = 'user/' + domain.id + '/' + command.name.toLowerCase(), chguser = parent.users[chguserid];
if (((user.siteadmin & 2) != 0) || (user._id == command.id)) {
var chguser = parent.users[command.id];
change = 0;
if (chguser) {
// If the target user is admin and we are not admin, no changes can be made.
if ((chguser.siteadmin == 0xFFFFFFFF) && (user.siteadmin != 0xFFFFFFFF)) return;
// Can only perform this operation on other users of our group.
if (user.siteadmin != 0xFFFFFFFF) {
if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) return;
}
// Validate input
if (common.validateString(command.email, 1, 256) && (chguser.email != command.email)) { chguser.email = command.email; change = 1; }
// Make changes
if ((command.emailVerified === true || command.emailVerified === false) && (chguser.emailVerified != command.emailVerified)) { chguser.emailVerified = command.emailVerified; change = 1; }
if ((common.validateInt(command.quota, 0) || command.quota == null) && (command.quota != chguser.quota)) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; }
if ((user.siteadmin == 0xFFFFFFFF) && common.validateInt(command.siteadmin) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1; }
// Site admins can change any server rights, user managers can only change AccountLock, NoMeshCmd and NoNewGroups
var chgusersiteadmin = chguser.siteadmin ? chguser.siteadmin : 0;
if (((user.siteadmin == 0xFFFFFFFF) || ((user.siteadmin & 2) && (((chgusersiteadmin ^ command.siteadmin) & 0xFFFFFF1F) == 0))) && common.validateInt(command.siteadmin) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1; }
// Went sending a notification about a group change, we need to send to all the previous and new groups.
var allTargetGroups = chguser.groups;
if ((Array.isArray(command.groups)) && ((user._id != command.id) || (user.siteadmin == 0xFFFFFFFF))) {
if (command.groups.length == 0) {
// Remove the user groups
if (chguser.groups != null) { delete chguser.groups; change = 1; }
} else {
// Arrange the user groups
var groups2 = [];
for (var i in command.groups) {
if (typeof command.groups[i] == 'string') {
var gname = command.groups[i].trim().toLowerCase();
if ((gname.length > 0) && (gname.length <= 64) && (groups2.indexOf(gname) == -1)) { groups2.push(gname); }
}
}
groups2.sort();
// Set the user groups
if (chguser.groups != groups2) { chguser.groups = groups2; change = 1; }
// Add any missing groups in the target list
if (allTargetGroups == null) { allTargetGroups = []; }
for (var i in groups2) { if (allTargetGroups.indexOf(i) == -1) { allTargetGroups.push(i); } }
}
}
if (change == 1) {
// Update the user
db.SetUser(chguser);
parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe');
parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Account changed: ' + command.name, domain: domain.id });
var targets = ['*', 'server-users', user._id, chguser._id];
if (allTargetGroups) { for (var i in allTargetGroups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Account changed: ' + chguser.name, domain: domain.id });
}
if ((chguser.siteadmin) && (chguser.siteadmin != 0xFFFFFFFF) && (chguser.siteadmin & 32)) {
// If the user is locked out of this account, disconnect now
parent.parent.DispatchEvent([chguser._id], obj, 'close'); // Disconnect all this user's sessions
}
}
@ -888,7 +1085,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
require('./pass').hash(command.newpass, function (err, salt, hash) {
if (err) {
// Send user notification of error
displayNotificationMessage('Error, password not changed.');
displayNotificationMessage('Error, password not changed.', 'Account Settings', 'ServerNotify');
} else {
// Change the password
if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true) && (command.hint != null)) {
@ -901,15 +1098,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
user.passchange = Math.floor(Date.now() / 1000);
delete user.passtype;
db.SetUser(user);
parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Account password changed: ' + user.name, domain: domain.id });
var targets = ['*', 'server-users'];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Account password changed: ' + user.name, domain: domain.id });
// Send user notification of password change
displayNotificationMessage('Password changed.');
displayNotificationMessage('Password changed.', 'Account Settings', 'ServerNotify');
}
});
} else {
// Send user notification of error
displayNotificationMessage('Current password not correct.');
displayNotificationMessage('Current password not correct.', 'Account Settings', 'ServerNotify');
}
});
break;
@ -918,14 +1118,17 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
{
// Change a user's password
if (user.siteadmin != 0xFFFFFFFF) break;
if (common.validateString(command.user, 1, 256) == false) break;
if (common.validateString(command.userid, 1, 256) == false) break;
if (common.validateString(command.pass, 1, 256) == false) break;
if ((command.hint != null) && (common.validateString(command.hint, 0, 256) == false)) break;
if (typeof command.removeMultiFactor != 'boolean') break;
if (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements
var chguser = parent.users['user/' + domain.id + '/' + command.user.toLowerCase()];
var chguser = parent.users[command.userid];
if (chguser) {
// Can only perform this operation on other users of our group.
if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
// Compute the password hash & save it
require('./pass').hash(command.pass, function (err, salt, hash) {
if (!err) {
@ -944,7 +1147,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (chguser.otpkeys) { delete chguser.otpkeys; }
}
db.SetUser(chguser);
parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Changed account credentials.', domain: domain.id });
var targets = ['*', 'server-users', user._id, chguser._id];
if (chguser.groups) { for (var i in chguser.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Changed account credentials.', domain: domain.id });
} else {
// Report that the password change failed
// TODO
@ -960,8 +1166,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (common.validateString(command.userid, 1, 2048) == false) break;
if (common.validateString(command.msg, 1, 4096) == false) break;
// Can only perform this operation on other users of our group.
var chguser = parent.users[command.userid];
if (chguser == null) break; // This user does not exists
if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
// Create the notification message
var notification = { "action": "msg", "type": "notify", "value": "<b>" + user.name + "</b>: " + EscapeHtml(command.msg), "userid": user._id, "username": user.name };
var notification = { action: "msg", type: "notify", value: command.msg, title: user.name, icon: 8, userid: user._id, username: user.name };
// Get the list of sessions for this user
var sessions = parent.wssessions[command.userid];
@ -980,9 +1191,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Setup a user-to-user session
if (common.validateString(command.userid, 1, 2048)) {
// Can only perform this operation on other users of our group.
var chguser = parent.users[command.userid];
if (chguser == null) break; // This user does not exists
if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
// Create the notification message
var notification = {
"action": "msg", "type": "notify", "value": "<b>" + user.name + "</b>: Chat Request, Click here to accept.", "userid": user._id, "username": user.name, "tag": 'meshmessenger/' + encodeURIComponent(command.userid) + '/' + encodeURIComponent(user._id)
"action": "msg", "type": "notify", "value": "Chat Request, Click here to accept.", "title": user.name, "userid": user._id, "username": user.name, "tag": 'meshmessenger/' + encodeURIComponent(command.userid) + '/' + encodeURIComponent(user._id)
};
// Get the list of sessions for this user
@ -1044,7 +1260,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 64) != 0)) break;
// In some situations, we need a verified email address to create a device group.
if ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (user.emailVerified !== true) && (user.siteadmin != 0xFFFFFFFF)) return; // User must verify it's email first.
if ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (user.emailVerified !== true) && (user.siteadmin != 0xFFFFFFFF)) return; // User must verify it's email first.
// Create mesh
if (common.validateString(command.meshname, 1, 64) == false) break; // Meshname is between 1 and 64 characters
@ -1122,6 +1338,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (common.validateString(command.meshid, 1, 1024) == false) break; // Check the meshid
mesh = parent.meshes[command.meshid];
change = '';
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 1) == 0)) return;
@ -1130,7 +1347,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if ((common.validateString(command.meshname, 1, 64) == true) && (command.meshname != mesh.name)) { change = 'Group name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; }
if ((common.validateString(command.desc, 0, 1024) == true) && (command.desc != mesh.desc)) { if (change != '') change += ' and description changed'; else change += 'Group "' + mesh.name + '" description changed'; mesh.desc = command.desc; }
if ((common.validateInt(command.flags) == true) && (command.flags != mesh.flags)) { if (change != '') change += ' and flags changed'; else change += 'Group "' + mesh.name + '" flags changed'; mesh.flags = command.flags; }
if (change != '') { db.Set(common.escapeLinksFieldName(mesh)); parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, flags: mesh.flags, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }); }
if ((common.validateInt(command.consent) == true) && (command.consent != mesh.consent)) { if (change != '') change += ' and consent changed'; else change += 'Group "' + mesh.name + '" consent changed'; mesh.consent = command.consent; }
if (change != '') { db.Set(common.escapeLinksFieldName(mesh)); parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, flags: mesh.flags, consent: mesh.consent, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }); }
}
break;
}
@ -1143,7 +1361,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Check if the user exists
var newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(), newuser = parent.users[newuserid];
if (newuser == null) {
// TODO: Send error back, user not found.
// Send error back, user not found.
displayNotificationMessage('User "' + EscapeHtml(command.username) + '" not found.', 'Device Group', 'ServerNotify');
break;
}
@ -1161,7 +1380,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe');
// Add a user to the mesh
mesh.links[newuserid] = { name: newuser.name, rights: command.meshadmin };
mesh.links[newuserid] = { userid: newuser.id, name: newuser.name, rights: command.meshadmin };
db.Set(common.escapeLinksFieldName(mesh));
// Notify mesh change
@ -1224,7 +1443,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
change = '';
if (mesh) {
// Check if this user has rights to do this
if ((mesh.links[user._id] == null) || (mesh.links[user._id].rights != 0xFFFFFFFF)) return;
if ((mesh.links[user._id] == null) || ((mesh.links[user._id].rights & 1) == 0)) return;
if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
// TODO: Check if this is a change from the existing policy
@ -1378,6 +1597,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
db.RemoveSMBIOS(node._id); // Remove SMBios data
db.RemoveAllNodeEvents(node._id); // Remove all events for this node
db.removeAllPowerEventsForNode(node._id); // Remove all power events for this node
db.Get('ra' + obj.dbNodeKey, function (err, nodes) {
if (nodes.length == 1) { db.Remove('da' + nodes[0].daid); } // Remove diagnostic agent to real agent link
db.Remove('ra' + node._id); // Remove real agent to diagnostic agent link
});
// Event node deletion
parent.parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', username: user.name, action: 'removenode', nodeid: node._id, msg: 'Removed device ' + node.name + ' from group ' + mesh.name, domain: domain.id });
@ -1598,10 +1821,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (command.intelamt.tls && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); }
}
if (command.tags) { // Node grouping tag, this is a array of strings that can't be empty and can't contain a comma
var ok = true;
var ok = true, group2 = [];
if (common.validateString(command.tags, 0, 4096) == true) { command.tags = command.tags.split(','); }
if (common.validateStrArray(command.tags, 1, 256) == true) { var groupTags = command.tags; for (var i in groupTags) { groupTags[i] = groupTags[i].trim(); if ((groupTags[i] == '') || (groupTags[i].indexOf(',') >= 0)) { ok = false; } } }
if (ok == true) { groupTags.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }); node.tags = groupTags; change = 1; }
for (var i in command.tags) { var tname = command.tags[i].trim(); if ((tname.length > 0) && (tname.length < 64) && (group2.indexOf(tname) == -1)) { group2.push(tname); } }
group2.sort();
if (node.tags != group2) { node.tags = group2; change = 1; }
} else if ((command.tags === '') && node.tags) { delete node.tags; change = 1; }
if (change == 1) {
@ -1696,13 +1920,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
db.Get(command.nodeid, function (err, nodes) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???)
if (nodes.length == 1) {
meshlinks = user.links[nodes[0].meshid];
if ((meshlinks) && (meshlinks.rights) && (meshlinks.rights & parent.MESHRIGHT_REMOTECONTROL != 0)) {
if ((meshlinks) && (meshlinks.rights) && ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) != 0)) {
// Add a user authentication cookie to a url
var cookieContent = { userid: user._id, domainid: user.domain };
if (command.nodeid) { cookieContent.nodeid = command.nodeid; }
if (command.tcpaddr) { cookieContent.tcpaddr = command.tcpaddr; } // Indicates the browser want to agent to TCP connect to a remote address
if (command.tcpport) { cookieContent.tcpport = command.tcpport; } // Indicates the browser want to agent to TCP connect to a remote port
command.cookie = parent.parent.encodeCookie(cookieContent);
command.cookie = parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey);
try { ws.send(JSON.stringify(command)); } catch (ex) { }
}
}
@ -1772,6 +1996,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (common.validateString(command.notes, 1) == false) {
db.Remove('nt' + command.id); // Delete the note for this node
} else {
// Can only perform this operation on other users of our group.
var chguser = parent.users[command.id];
if (chguser == null) break; // This user does not exists
if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this user
}
}
@ -1809,7 +2037,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
ws.send(JSON.stringify({ action: 'otpauth-setup', success: true })); // Report success
// Notify change
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added authentication application.', domain: domain.id });
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added authentication application.', domain: domain.id });
} else {
ws.send(JSON.stringify({ action: 'otpauth-setup', success: false })); // Report fail
}
@ -1828,7 +2058,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success
// Notify change
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Removed authentication application.', domain: domain.id });
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Removed authentication application.', domain: domain.id });
} else {
ws.send(JSON.stringify({ action: 'otpauth-clear', success: false })); // Report fail
}
@ -1863,7 +2095,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
// Notify change
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
break;
}
case 'otp-hkey-get':
@ -1894,7 +2128,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
// Notify change
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Removed security key.', domain: domain.id });
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Removed security key.', domain: domain.id });
break;
}
case 'otp-hkey-yubikey-add':
@ -1940,7 +2176,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: true, name: command.name, index: keyIndex }));
// Notify change TODO: Should be done on all sessions/servers for this user.
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
} else {
ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
}
@ -1997,7 +2235,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
delete obj.hardwareKeyRegistrationRequest;
// Notify change
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
}, function (error) {
ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex }));
delete obj.hardwareKeyRegistrationRequest;
@ -2053,7 +2293,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.f2l.attestationResult(clientAttestationResponse, attestationExpectations).then(function (regResult) {
// Since we are registering a WebAuthn/FIDO2 key, remove all U2F keys (Type 1).
var otphkeys2 = [];
for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type != 1) { otphkeys2.push(user.otphkeys[i]); } }
if (user.otphkeys && Array.isArray(user.otphkeys)) { for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type != 1) { otphkeys2.push(user.otphkeys[i]); } } }
user.otphkeys = otphkeys2;
// Add the new WebAuthn/FIDO2 keys
@ -2064,7 +2304,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: true, name: command.name, index: keyIndex }));
// Notify change
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
}, function (error) {
console.log('webauthn-endregister-error', error);
ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex }));
@ -2116,6 +2358,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
});
break;
}
case 'userWebState': {
if (common.validateString(command.state, 1, 10000) == false) break; // Check state size, no more than 10k
db.Set({ _id: 'ws' + user._id, state: command.state });
parent.parent.DispatchEvent([user._id], obj, { action: 'userWebState', nolog: 1, domain: domain.id, state: command.state });
break;
}
case 'getNotes':
{
// Argument validation
@ -2182,7 +2430,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
// Display a notification message for this session only.
function displayNotificationMessage(msg, tag) { ws.send(JSON.stringify({ "action": "msg", "type": "notify", "value": msg, "userid": user._id, "username": user.name, "tag": tag })); }
function displayNotificationMessage(msg, title, tag) { ws.send(JSON.stringify({ "action": "msg", "type": "notify", "value": msg, "title": title, "userid": user._id, "username": user.name, "tag": tag })); }
// Read the folder and all sub-folders and serialize that into json.
function readFilesRec(path) {
@ -2296,5 +2544,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
return results;
}
// Return true if at least one element of arr2 is in arr1
function findOne(arr1, arr2) { if ((arr1 == null) || (arr2 == null)) return false; return arr2.some(function (v) { return arr1.indexOf(v) >= 0; }); };
// Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr(addr) { if (addr.startsWith('::ffff:')) { return addr.substring(7); } else { return addr; } }
return obj;
};

View File

@ -32,7 +32,8 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
if (obj.args.mpstlsoffload) {
obj.server = net.createServer(onConnection);
} else {
obj.server = tls.createServer({ key: certificates.mps.key, cert: certificates.mps.cert, requestCert: true, rejectUnauthorized: false, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION }, onConnection);
// Note that in oder to support older Intel AMT CIRA connections, we have to turn on TLSv1.
obj.server = tls.createServer({ key: certificates.mps.key, cert: certificates.mps.cert, minVersion: 'TLSv1', requestCert: true, rejectUnauthorized: false, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION }, onConnection);
//obj.server.on('secureConnection', function () { /*console.log('tlsServer secureConnection');*/ });
//obj.server.on('error', function () { console.log('MPS tls server error'); });
obj.server.on('newSession', function (id, data, cb) { if (tlsSessionStoreCount > 1000) { tlsSessionStoreCount = 0; tlsSessionStore = {}; } tlsSessionStore[id.toString('hex')] = data; tlsSessionStoreCount++; cb(); });
@ -106,7 +107,58 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
ResourceShortage: 4,
};
// Stat counters
var connectionCount = 0;
var userAuthRequestCount = 0;
var incorrectPasswordCount = 0;
var meshNotFoundCount = 0;
var unknownTlsNodeCount = 0;
var unknownTlsMeshIdCount = 0;
var addedTlsDeviceCount = 0;
var unknownNodeCount = 0;
var unknownMeshIdCount = 0;
var addedDeviceCount = 0;
var ciraTimeoutCount = 0;
var protocolVersionCount = 0;
var badUserNameLengthCount = 0;
var channelOpenCount = 0;
var channelOpenConfirmCount = 0;
var channelOpenFailCount = 0;
var channelCloseCount = 0;
var disconnectCommandCount = 0;
var socketClosedCount = 0;
var socketErrorCount = 0;
// Return statistics about this MPS server
obj.getStats = function () {
return {
ciraConnections: Object.keys(obj.ciraConnections).length,
tlsSessionStore: Object.keys(tlsSessionStore).length,
connectionCount: connectionCount,
userAuthRequestCount: userAuthRequestCount,
incorrectPasswordCount: incorrectPasswordCount,
meshNotFoundCount: meshNotFoundCount,
unknownTlsNodeCount: unknownTlsNodeCount,
unknownTlsMeshIdCount: unknownTlsMeshIdCount,
addedTlsDeviceCount: addedTlsDeviceCount,
unknownNodeCount: unknownNodeCount,
unknownMeshIdCount: unknownMeshIdCount,
addedDeviceCount: addedDeviceCount,
ciraTimeoutCount: ciraTimeoutCount,
protocolVersionCount: protocolVersionCount,
badUserNameLengthCount: badUserNameLengthCount,
channelOpenCount: channelOpenCount,
channelOpenConfirmCount: channelOpenConfirmCount,
channelOpenFailCount: channelOpenFailCount,
channelCloseCount: channelCloseCount,
disconnectCommandCount: disconnectCommandCount,
socketClosedCount: socketClosedCount,
socketErrorCount: socketErrorCount
};
}
function onConnection(socket) {
connectionCount++;
if (obj.args.mpstlsoffload) {
socket.tag = { first: true, clientCert: null, accumulator: "", activetunnels: 0, boundPorts: [], socket: socket, host: null, nextchannelid: 4, channels: {}, nextsourceport: 0 };
} else {
@ -117,7 +169,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
// Setup the CIRA keep alive timer
socket.setTimeout(MAX_IDLE);
socket.on("timeout", () => { Debug(1, "MPS:CIRA timeout, disconnecting."); try { socket.end(); } catch (e) { } });
socket.on("timeout", () => { ciraTimeoutCount++; Debug(1, "MPS:CIRA timeout, disconnecting."); try { socket.end(); } catch (e) { } });
socket.addListener("data", function (data) {
if (args.mpsdebug) { var buf = Buffer.from(data, "binary"); console.log("MPS <-- (" + buf.length + "):" + buf.toString('hex')); } // Print out received bytes
@ -146,22 +198,24 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
// Fetch the mesh
obj.db.Get(socket.tag.meshid, function (err, meshes) {
if (meshes.length == 1) {
if (meshes.length === 1) {
var mesh = meshes[0];
obj.db.Get(socket.tag.nodeid, function (err, nodes) {
if (nodes.length == 0) {
if (nodes.length !== 1) {
if (mesh.mtype == 1) {
// Node is not in the database, add it. Credentials will be empty until added by the user.
var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: domainid, intelamt: { user: '', pass: '', tls: 0, state: 2 } };
obj.db.Set(device);
// Event the new node
addedTlsDeviceCount++;
var device2 = common.Clone(device);
if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
var change = 'CIRA added device ' + socket.tag.name + ' to mesh ' + mesh.name;
obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: domainid });
} else {
// New CIRA connection for unknown node, disconnect.
unknownTlsNodeCount++;
console.log('CIRA connection for unknown node with incorrect group type. meshid: ' + socket.tag.meshid);
socket.end();
return;
@ -177,6 +231,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 2, 7); // TODO: Right now report power state as "present" (7) until we can poll.
});
} else {
unknownTlsMeshIdCount++;
console.log('ERROR: Intel AMT CIRA connected with unknown groupid: ' + socket.tag.meshid);
socket.end();
return;
@ -219,6 +274,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
}
case APFProtocol.PROTOCOLVERSION: {
if (len < 93) return 0;
protocolVersionCount++;
socket.tag.MajorVersion = common.ReadInt(data, 1);
socket.tag.MinorVersion = common.ReadInt(data, 5);
socket.tag.SystemId = guidToStr(common.rstr2hex(data.substring(13, 29))).toLowerCase();
@ -227,6 +283,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
}
case APFProtocol.USERAUTH_REQUEST: {
if (len < 13) return 0;
userAuthRequestCount++;
var usernameLen = common.ReadInt(data, 1);
var username = data.substring(5, 5 + usernameLen);
var serviceNameLen = common.ReadInt(data, 5 + usernameLen);
@ -242,13 +299,13 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
Debug(3, 'MPS:USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password);
// Check the CIRA password
if ((args.mpspass != null) && (password != args.mpspass)) { Debug(1, 'MPS:Incorrect password', username, password); SendUserAuthFail(socket); return -1; }
if ((args.mpspass != null) && (password != args.mpspass)) { incorrectPasswordCount++; Debug(1, 'MPS:Incorrect password', username, password); SendUserAuthFail(socket); return -1; }
// Check the CIRA username, which should be the start of the MeshID.
if (usernameLen != 16) { Debug(1, 'MPS:Username length not 16', username, password); SendUserAuthFail(socket); return -1; }
if (usernameLen != 16) { badUserNameLengthCount++; Debug(1, 'MPS:Username length not 16', username, password); SendUserAuthFail(socket); return -1; }
var meshIdStart = '/' + username, mesh = null;
if (obj.parent.webserver.meshes) { for (var i in obj.parent.webserver.meshes) { if (obj.parent.webserver.meshes[i]._id.replace(/\@/g, 'X').replace(/\$/g, 'X').indexOf(meshIdStart) > 0) { mesh = obj.parent.webserver.meshes[i]; break; } } }
if (mesh == null) { Debug(1, 'MPS:Mesh not found', username, password); SendUserAuthFail(socket); return -1; }
if (mesh == null) { meshNotFoundCount++; Debug(1, 'MPS:Mesh not found', username, password); SendUserAuthFail(socket); return -1; }
// If this is a agent-less mesh, use the device guid 3 times as ID.
if (mesh.mtype == 1) {
@ -261,23 +318,17 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
socket.tag.connectTime = Date.now();
obj.db.Get(socket.tag.nodeid, function (err, nodes) {
if (nodes.length == 0) {
if (mesh.mtype == 1) {
// Node is not in the database, add it. Credentials will be empty until added by the user.
var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: mesh.domain, intelamt: { user: '', pass: '', tls: 0, state: 2 } };
obj.db.Set(device);
if (nodes.length !== 1) {
// Node is not in the database, add it. Credentials will be empty until added by the user.
var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: mesh.domain, intelamt: { user: '', pass: '', tls: 0, state: 2 } };
obj.db.Set(device);
// Event the new node
var device2 = common.Clone(device);
if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
var change = 'CIRA added device ' + socket.tag.name + ' to group ' + mesh.name;
obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: mesh.domain });
} else {
// New CIRA connection for unknown node, disconnect.
console.log('CIRA connection for unknown node with incorrect group type. meshid: ' + socket.tag.meshid);
socket.end();
return;
}
// Event the new node
addedDeviceCount++;
var device2 = common.Clone(device);
if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
var change = 'CIRA added device ' + socket.tag.name + ' to group ' + mesh.name;
obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: mesh.domain });
} else {
// Node is already present
var node = nodes[0];
@ -292,8 +343,9 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
} else if (mesh.mtype == 2) { // If this is a agent mesh, search the mesh for this device UUID
// Intel AMT GUID (socket.tag.SystemId) will be used to search the node
obj.db.getAmtUuidNode(mesh._id, socket.tag.SystemId, function (err, nodes) { // TODO: May need to optimize this request with indexes
if (nodes.length == 0) {
if (nodes.length !== 1) {
// New CIRA connection for unknown node, disconnect.
unknownNodeCount++;
console.log('CIRA connection for unknown node. groupid: ' + mesh._id + ', uuid: ' + socket.tag.SystemId);
socket.end();
return;
@ -313,6 +365,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
});
} else { // Unknown mesh type
// New CIRA connection for unknown node, disconnect.
unknownMeshIdCount++;
console.log('CIRA connection to a unknown group type. groupid: ' + socket.tag.meshid);
socket.end();
return;
@ -400,6 +453,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
var Source = data.substring(29 + ChannelTypeLength + TargetLen, 29 + ChannelTypeLength + TargetLen + SourceLen);
var SourcePort = common.ReadInt(data, 29 + ChannelTypeLength + TargetLen + SourceLen);
channelOpenCount++;
Debug(3, 'MPS:CHANNEL_OPEN', ChannelType, SenderChannel, WindowSize, Target + ':' + TargetPort, Source + ':' + SourcePort);
// Check if we understand this channel type
@ -430,6 +484,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
if (cirachannel == null) { /*console.log("MPS Error in CHANNEL_OPEN_CONFIRMATION: Unable to find channelid " + RecipientChannel);*/ return 17; }
cirachannel.amtchannelid = SenderChannel;
cirachannel.sendcredits = cirachannel.amtCiraWindow = WindowSize;
channelOpenConfirmCount++;
Debug(3, 'MPS:CHANNEL_OPEN_CONFIRMATION', RecipientChannel, SenderChannel, WindowSize);
if (cirachannel.closing == 1) {
// Close this channel
@ -461,6 +516,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
if (len < 17) return 0;
var RecipientChannel = common.ReadInt(data, 1);
var ReasonCode = common.ReadInt(data, 5);
channelOpenFailCount++;
Debug(3, 'MPS:CHANNEL_OPEN_FAILURE', RecipientChannel, ReasonCode);
var cirachannel = socket.tag.channels[RecipientChannel];
if (cirachannel == null) { console.log("MPS Error in CHANNEL_OPEN_FAILURE: Unable to find channelid " + RecipientChannel); return 17; }
@ -475,6 +531,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
{
if (len < 5) return 0;
var RecipientChannel = common.ReadInt(data, 1);
channelCloseCount++;
Debug(3, 'MPS:CHANNEL_CLOSE', RecipientChannel);
var cirachannel = socket.tag.channels[RecipientChannel];
if (cirachannel == null) { console.log("MPS Error in CHANNEL_CLOSE: Unable to find channelid " + RecipientChannel); return 5; }
@ -533,6 +590,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
{
if (len < 7) return 0;
var ReasonCode = common.ReadInt(data, 1);
disconnectCommandCount++;
Debug(3, 'MPS:DISCONNECT', ReasonCode);
try { delete obj.ciraConnections[socket.tag.nodeid]; } catch (e) { }
obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 2);
@ -547,12 +605,14 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
}
socket.addListener("close", function () {
socketClosedCount++;
Debug(1, 'MPS:CIRA connection closed');
try { delete obj.ciraConnections[socket.tag.nodeid]; } catch (e) { }
obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 2);
});
socket.addListener("error", function () {
socketErrorCount++;
//console.log("MPS Error: " + socket.remoteAddress);
});
@ -688,7 +748,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
// Change the device
obj.db.Get(socket.tag.nodeid, function (err, nodes) {
if (nodes.length != 1) return;
if (nodes.length !== 1) return;
var node = nodes[0];
// See if any changes need to be made
@ -696,7 +756,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
// Get the mesh for this device
obj.db.Get(node.meshid, function (err, meshes) {
if (meshes.length != 1) return;
if (meshes.length !== 1) return;
var mesh = meshes[0];
// Ready the node change event

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.3.1-x",
"version": "0.3.4-c",
"keywords": [
"Remote Management",
"Intel AMT",
@ -20,6 +20,7 @@
"sample-config.json",
"license.txt",
"readme.txt",
"amt",
"agents",
"public",
"views",
@ -37,6 +38,7 @@
"ipcheck": "^0.1.0",
"meshcentral": "*",
"minimist": "^1.2.0",
"mongojs": "^2.6.0",
"multiparty": "^4.2.1",
"nedb": "^1.8.0",
"node-forge": "^0.7.6",

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
public/images/gears-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/images/icons64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

BIN
public/images/mesh-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 B

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
public/images/user-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

BIN
public/images/user-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -189,7 +189,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
obj.ProcessDataEx = function (str) {
if (obj.accumulator != null) {
str = obj.accumulator + str;
console.log('KVM using accumulated data, total size is now ' + str.length + ' bytes.');
//console.log('KVM using accumulated data, total size is now ' + str.length + ' bytes.');
obj.accumulator = null;
}
if (obj.debugmode > 1) { console.log("KRecv(" + str.length + "): " + rstr2hex(str.substring(0, Math.min(str.length, 40)))); }
@ -202,7 +202,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
cmdsize = ReadInt(str, 4);
//console.log('JUMBO cmd=' + command + ', cmdsize=' + cmdsize + ', data received=' + str.length);
if ((cmdsize + 8) > str.length) {
console.log('KVM accumulator set to ' + str.length + ' bytes, need ' + cmdsize + ' bytes.');
//console.log('KVM accumulator set to ' + str.length + ' bytes, need ' + cmdsize + ' bytes.');
obj.accumulator = str;
return;
}
@ -212,7 +212,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
if ((cmdsize != str.length) && (obj.debugmode > 0)) { console.log(cmdsize, str.length, cmdsize == str.length); }
if ((command >= 18) && (command != 65)) { console.error("Invalid KVM command " + command + " of size " + cmdsize); console.log("Invalid KVM data", str.length, rstr2hex(str.substring(0, 40)) + '...'); return; }
if (cmdsize > str.length) {
console.log('KVM accumulator set to ' + str.length + ' bytes, need ' + cmdsize + ' bytes.');
//console.log('KVM accumulator set to ' + str.length + ' bytes, need ' + cmdsize + ' bytes.');
obj.accumulator = str;
return;
}
@ -249,17 +249,17 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
obj.send(String.fromCharCode(0x00, 0x0E, 0x00, 0x04));
break;
case 11: // GetDisplays
var myOptions = [], dcount = ((str.charCodeAt(4) & 0xFF) << 8) + (str.charCodeAt(5) & 0xFF);
var selectedDisplay = 0, displays = { }, dcount = ((str.charCodeAt(4) & 0xFF) << 8) + (str.charCodeAt(5) & 0xFF);
if (dcount > 0) {
// Many displays present
var selitem = 0, seldisp = ((str.charCodeAt(6 + (dcount * 2)) & 0xFF) << 8) + (str.charCodeAt(7 + (dcount * 2)) & 0xFF);
selectedDisplay = ((str.charCodeAt(6 + (dcount * 2)) & 0xFF) << 8) + (str.charCodeAt(7 + (dcount * 2)) & 0xFF);
for (var i = 0; i < dcount; i++) {
var disp = ((str.charCodeAt(6 + (i * 2)) & 0xFF) << 8) + (str.charCodeAt(7 + (i * 2)) & 0xFF);
if (disp == 65535) { myOptions.push('All Displays'); } else { myOptions.push('Display ' + disp); }
if (disp == seldisp) selitem = i;
if (disp == 65535) { displays[disp] = 'All Displays'; } else { displays[disp] = 'Display ' + disp; }
}
}
if (obj.onDisplayinfo != null) { obj.onDisplayinfo(obj, myOptions, selitem); }
console.log('Get Displays', displays, selectedDisplay, rstr2hex(str));
if (obj.onDisplayinfo != null) { obj.onDisplayinfo(obj, displays, selectedDisplay); }
break;
case 12: // SetDisplay
//console.log('SetDisplayConfirmed');
@ -450,11 +450,14 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
if (obj.State != 3) return;
if (Action != null && obj.Canvas != null) {
if (!event) { var event = window.event; }
var ScaleFactorHeight = (obj.Canvas.canvas.height / obj.CanvasId.clientHeight);
var ScaleFactorWidth = (obj.Canvas.canvas.width / obj.CanvasId.clientWidth);
var Offsets = obj.GetPositionOfControl(obj.Canvas.canvas);
var X = ((event.pageX - Offsets[0]) * ScaleFactorWidth);
var Y = ((event.pageY - Offsets[1]) * ScaleFactorHeight);
if (event.addx) { X += event.addx; }
if (event.addy) { Y += event.addy; }
if (X >= 0 && X <= obj.Canvas.canvas.width && Y >= 0 && Y <= obj.Canvas.canvas.height) {
var Button = 0;
@ -486,7 +489,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
}
obj.GetDisplayNumbers = function () { obj.send(String.fromCharCode(0x00, 0x0B, 0x00, 0x04)); } // Get Terminal display
obj.SetDisplay = function (number) { obj.send(String.fromCharCode(0x00, 0x0C, 0x00, 0x06, number >> 8, number & 0xFF)); } // Set Terminal display
obj.SetDisplay = function (number) { console.log('Set display', number); obj.send(String.fromCharCode(0x00, 0x0C, 0x00, 0x06, number >> 8, number & 0xFF)); } // Set Terminal display
obj.intToStr = function (x) { return String.fromCharCode((x >> 24) & 0xFF, (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF); }
obj.shortToStr = function (x) { return String.fromCharCode((x >> 8) & 0xFF, x & 0xFF); }

View File

@ -43,7 +43,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
obj.socket.onclose = obj.xxOnSocketClosed;
obj.xxStateChange(1);
//obj.meshserver.send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: url2 });
obj.meshserver.send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: "*/meshrelay.ashx?id=" + obj.tunnelid });
obj.meshserver.send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: "*/meshrelay.ashx?id=" + obj.tunnelid, usage: obj.protocol });
//obj.debug("Agent Redir Start: " + url);
}

View File

@ -779,9 +779,13 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) {
obj.mouseup = function (e) { obj.buttonmask &= (0xFFFF - (1 << e.button)); return obj.mousemove(e); }
obj.mousemove = function (e) {
if (obj.state != 4) return true;
var pos = obj.getPositionOfControl(Q(obj.canvasid));
obj.mx = (e.pageX - pos[0]) * (obj.canvas.canvas.height / Q(obj.canvasid).offsetHeight);
obj.my = ((e.pageY - pos[1] + (scrolldiv ? scrolldiv.scrollTop : 0)) * (obj.canvas.canvas.width / Q(obj.canvasid).offsetWidth));
var ScaleFactorHeight = (obj.canvas.canvas.height / Q(obj.canvasid).offsetHeight);
var ScaleFactorWidth = (obj.canvas.canvas.width / Q(obj.canvasid).offsetWidth);
var Offsets = obj.getPositionOfControl(Q(obj.canvasid));
obj.mx = ((event.pageX - Offsets[0]) * ScaleFactorWidth);
obj.my = ((event.pageY - Offsets[1]) * ScaleFactorHeight);
if (event.addx) { obj.mx += event.addx; }
if (event.addy) { obj.my += event.addy; }
// ###BEGIN###{DesktopRotation}
if (obj.noMouseRotate != true) {

View File

@ -1,135 +1,559 @@
/**
* @description Remote Desktop
* @description IDER Handling Module
* @author Ylian Saint-Hilaire
* @version v0.0.2
*/
// Construct a Intel AMT IDER object
var CreateAmtRemoteIder = function (serverurl) {
var CreateAmtRemoteIder = function () {
var obj = {};
obj.protocol = 3; // IDER
obj.state = 0;
obj.socket = null;
obj.serverurl = serverurl;
obj.bytesToAmt = 0;
obj.bytesFromAmt = 0;
obj.rx_timeout = 30000; // Default 30000
obj.tx_timeout = 0; // Default 0
obj.heartbeat = 20000; // Default 20000
obj.version = 1;
obj.acc = "";
obj.inSequence = 0;
obj.outSequence = 0;
obj.iderinfo = null;
obj.enabled = false;
obj.iderStart = 0; // OnReboot = 0, Graceful = 1, Now = 2
obj.floppy = null;
obj.cdrom = null;
obj.floppyReady = false;
obj.cdromReady = false;
obj.pingTimer = null;
// Private method
obj.Debug = function (msg) { console.log(msg); }
function debug() { if (urlvars && urlvars['idertrace']) { console.log(...arguments); } }
// Mode Sense
var IDE_ModeSence_LS120Disk_Page_Array = String.fromCharCode(0x00, 0x26, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00);
var IDE_ModeSence_3F_LS120_Array = String.fromCharCode(0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31);
var IDE_ModeSence_FloppyDisk_Page_Array = String.fromCharCode(0x00, 0x26, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x04, 0xB0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00);
var IDE_ModeSence_3F_Floppy_Array = String.fromCharCode(0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1e, 0x04, 0xb0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31);
var IDE_ModeSence_CD_1A_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
//var IDE_ModeSence_CD_1B_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
var IDE_ModeSence_CD_1D_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1D, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
var IDE_ModeSence_CD_2A_Array = String.fromCharCode(0x00, 0x20, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
//var IDE_ModeSence_CD_01_Array = String.fromCharCode(0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00);
var IDE_ModeSence_3F_CD_Array = String.fromCharCode(0x00, 0x28, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
// 0x46 constant data
var IDE_CD_ConfigArrayHeader = String.fromCharCode(0x00, 0x00,0x00, 0x28, 0x00, 0x00, 0x00, 0x08);
var IDE_CD_ConfigArrayProfileList = String.fromCharCode(0x00, 0x00, 0x03, 0x04, 0x00, 0x08, 0x01, 0x00);
var IDE_CD_ConfigArrayCore = String.fromCharCode(0x00, 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, 0x02);
var IDE_CD_Morphing = String.fromCharCode(0x00, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00);
var IDE_CD_ConfigArrayRemovable = String.fromCharCode(0x00, 0x03, 0x03, 0x04, 0x29, 0x00, 0x00, 0x02);
var IDE_CD_ConfigArrayRandom = String.fromCharCode(0x00, 0x10, 0x01, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00);
var IDE_CD_Read = String.fromCharCode(0x00, 0x1E, 0x03, 0x00);
var IDE_CD_PowerManagement = String.fromCharCode(0x01, 0x00, 0x03, 0x00);
var IDE_CD_Timeout = String.fromCharCode(0x01, 0x05, 0x03, 0x00);
// 0x01 constant data
var IDE_ModeSence_FloppyError_Recovery_Array = String.fromCharCode(0x00, 0x12, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00);
var IDE_ModeSence_Ls120Error_Recovery_Array = String.fromCharCode(0x00, 0x12, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00);
var IDE_ModeSence_CDError_Recovery_Array = String.fromCharCode(0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00);
// Private method, called by parent when it change state
obj.xxStateChange = function (newstate) {
//console.log("STATE: " + newstate);
if (newstate == 0) { obj.Stop(); } // Tell Storage Server to stop IDER
if (newstate == 3) { obj.StateChange(3); _Send('D'); } // Tell Storage Server to start IDER
debug("IDER-StateChange", newstate);
if (newstate == 0) { obj.Stop(); }
if (newstate == 3) { obj.Start(); }
}
// Private method
obj.StateChange = function (newstate) { obj.state = newstate; }
// Private method
obj.ProcessData = function (data) { _Send('F' + data); obj.bytesFromAmt += data.length; }
obj.Start = function (host, port, user, pass, tls) {
//obj.Debug("IDER-Start");
obj.host = host;
obj.port = port;
obj.user = user;
obj.pass = pass;
obj.tls = tls;
obj.Start = function () {
debug("IDER-Start");
debug(obj.floppy, obj.cdrom);
obj.bytesToAmt = 0;
obj.bytesFromAmt = 0;
obj.socket = new WebSocket(serverurl);
obj.socket.onopen = _OnSocketConnected;
obj.socket.onmessage = _OnMessage;
obj.socket.onclose = _OnSocketClosed;
obj.StateChange(1);
obj.inSequence = 0;
obj.outSequence = 0;
// Send first command, OPEN_SESSION
obj.SendCommand(0x40, ShortToStrX(obj.rx_timeout) + ShortToStrX(obj.tx_timeout) + ShortToStrX(obj.heartbeat) + IntToStrX(obj.version));
// Setup the ping timer
//obj.pingTimer = setInterval(function () { obj.SendCommand(0x44); }, 5000);
}
obj.Stop = function () {
if (obj.socket != null) { _Send('G'); obj.socket.close(); obj.socket = null; }
obj.StateChange(0);
debug("IDER-Stop");
if (obj.pingTimer) { clearInterval(obj.pingTimer); obj.pingTimer = null; }
obj.parent.Stop();
}
function _OnSocketConnected() {
obj.Debug("Socket Connected");
obj.StateChange(2);
_Send('C');
// Private method
obj.ProcessData = function (data) {
obj.bytesFromAmt += data.length;
obj.acc += data;
debug('IDER-ProcessData', obj.acc.length, rstr2hex(obj.acc));
// Process as many commands as possible
while (true) {
var len = obj.ProcessDataEx();
if (len == 0) return;
if (obj.inSequence != ReadIntX(obj.acc, 4)) { debug('ERROR: Out of sequence', obj.inSequence, ReadIntX(obj.acc, 4)); obj.Stop(); return; }
obj.inSequence++;
obj.acc = obj.acc.substring(len);
}
}
// Setup the file reader
var fileReader = new FileReader();
var fileReaderInuse = false, fileReaderAcc = [];
if (fileReader.readAsBinaryString) {
// Chrome & Firefox (Draft)
fileReader.onload = function (e) { _OnSocketData(e.target.result); if (fileReaderAcc.length == 0) { fileReaderInuse = false; } else { fileReader.readAsBinaryString(new Blob([fileReaderAcc.shift()])); } }
} else if (fileReader.readAsArrayBuffer) {
// Chrome & Firefox (Spec)
fileReader.onloadend = function (e) { _OnSocketData(e.target.result); if (fileReaderAcc.length == 0) { fileReaderInuse = false; } else { fileReader.readAsArrayBuffer(fileReaderAcc.shift()); } }
// Private method
obj.SendCommand = function (cmdid, data, completed, dma) {
if (data == null) { data = ''; }
var attributes = ((cmdid > 50) && (completed == true)) ? 2 : 0;
if (dma) { attributes += 1; }
var x = String.fromCharCode(cmdid, 0, 0, attributes) + IntToStrX(obj.outSequence++) + data;
obj.parent.xxSend(x);
obj.bytesToAmt += x.length;
if (cmdid != 0x4B) { debug('IDER-SendData', x.length, rstr2hex(x)); }
}
function _OnMessage(e) {
if (typeof e.data == 'object') {
if (fileReaderInuse == true) { fileReaderAcc.push(e.data); return; }
if (fileReader.readAsBinaryString) {
// Chrome & Firefox (Draft)
fileReaderInuse = true;
fileReader.readAsBinaryString(new Blob([e.data]));
} else if (fileReader.readAsArrayBuffer) {
// Chrome & Firefox (Spec)
fileReaderInuse = true;
fileReader.readAsArrayBuffer(e.data);
} else {
// IE10, readAsBinaryString does not exist, use an alternative.
var binary = "", bytes = new Uint8Array(e.data), length = bytes.byteLength;
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
_OnSocketData(binary);
}
// CommandEndResponse (SCSI_SENSE)
obj.SendCommandEndResponse = function (error, sense, device, asc, asq) {
if (error) { obj.SendCommand(0x51, String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc5, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0), true); }
else { obj.SendCommand(0x51, String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87, (sense << 4), 3, 0, 0, 0, device, 0x51, sense, asc, asq), true); }
}
//54 00 00 03 71 00 00 00 00 00 0c 00 b4 00 02 00 00 00 a0 58 85 00 03 00 00 00 a0 50 00 00
//54 00 00 02 26 00 00 00 00 00 0c 00 b5 00 02 00 00 0c a0 58 85 00 03 00 00 00 a0 50 00 00
// DataToHost (SCSI_READ)
obj.SendDataToHost = function (device, completed, data, dma) {
var dmalen = (dma) ? 0 : data.length;
if (completed == true) {
obj.SendCommand(0x54, String.fromCharCode(0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0x85, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0, 0, 0, 0) + data, completed, dma);
} else {
_OnSocketData(e.data);
}
};
function _OnSocketData(data) {
if (!data) return;
if (typeof data === 'object') {
// This is an ArrayBuffer, convert it to a string array (used in IE)
var binary = "";
var bytes = new Uint8Array(data);
var length = bytes.byteLength;
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
data = binary;
}
else if (typeof data !== 'string') { return; }
// console.log("CMD: " + data.substring(0, 1));
// Handle commands
switch (data.substring(0, 1)) {
case 'A': { data[0] = 'B'; _Send(data); break; } // Echo
case 'C': { obj.parent.Start(obj.host, obj.port, obj.user, obj.pass, obj.tls); break; } // Session Start
case 'E': { obj.Stop(); break; } // Stop IDER
case 'F': { obj.parent.xxSend(data.substring(1)); obj.bytesToAmt += (data.length - 1); break; } // IDER Data
case 'H': { if (obj.onDialogPrompt) obj.onDialogPrompt(obj, JSON.parse(data.substring(1))); break; } // IDER Dialog Prompt
obj.SendCommand(0x54, String.fromCharCode(0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + data, completed, dma);
}
}
function _OnSocketClosed() {
// obj.Debug("Socket Closed");
obj.Stop();
// GetDataFromHost (SCSI_CHUNK)
obj.SendGetDataFromHost = function (device, chunksize) {
obj.SendCommand(0x52, String.fromCharCode(0, (chunksize & 0xff), (chunksize >> 8), 0, 0xb5, 0, 0, 0, (chunksize & 0xff), (chunksize >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), false);
}
obj.dialogPrompt = function(x) { _Send('H' + JSON.stringify(x)); }
// DisableEnableFeatures (STATUS_DATA)
// If type is REGS_TOGGLE (3), 4 bytes of data must be provided.
obj.SendDisableEnableFeatures = function (type, data) { if (data == null) { data = ''; } obj.SendCommand(0x48, String.fromCharCode(type) + data); }
function _Send(x) {
// obj.Debug("Send(" + x.length + "): " + rstr2hex(x));
if (obj.socket != null && obj.socket.readyState == WebSocket.OPEN) {
var b = new Uint8Array(x.length);
for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); }
obj.socket.send(b.buffer);
// Private method
obj.ProcessDataEx = function () {
if (obj.acc.length < 8) return 0;
// First 8 bytes are the header
// CommandID + 0x000000 + Sequence Number
switch(obj.acc.charCodeAt(0)) {
case 0x41: // OPEN_SESSION
if (obj.acc.length < 30) return 0;
var len = obj.acc.charCodeAt(29);
if (obj.acc.length < (30 + len)) return 0;
obj.iderinfo = {};
obj.iderinfo.major = obj.acc.charCodeAt(8);
obj.iderinfo.minor = obj.acc.charCodeAt(9);
obj.iderinfo.fwmajor = obj.acc.charCodeAt(10);
obj.iderinfo.fwminor = obj.acc.charCodeAt(11);
obj.iderinfo.readbfr = ReadShortX(obj.acc, 16);
obj.iderinfo.writebfr = ReadShortX(obj.acc, 18);
obj.iderinfo.proto = obj.acc.charCodeAt(21);
obj.iderinfo.iana = ReadIntX(obj.acc, 25);
debug(obj.iderinfo);
if (obj.iderinfo.proto != 0) { debug("Unknown proto", obj.iderinfo.proto); obj.Stop(); }
if (obj.iderinfo.readbfr > 8192) { debug("Illegal read buffer size", obj.iderinfo.readbfr); obj.Stop(); }
if (obj.iderinfo.writebfr > 8192) { debug("Illegal write buffer size", obj.iderinfo.writebfr); obj.Stop(); }
if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x08)); } // OnReboot
else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x10)); } // Graceful
else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x18)); } // Now
//obj.SendDisableEnableFeatures(1); // GetSupportedFeatures
return 30 + len;
case 0x43: // CLOSE
debug('CLOSE');
obj.Stop();
return 8;
case 0x44: // KEEPALIVEPING
obj.SendCommand(0x45); // Send PONG back
return 8;
case 0x45: // KEEPALIVEPONG
debug('PONG');
return 8;
case 0x46: // RESETOCCURED
if (obj.acc.length < 9) return 0;
var resetMask = obj.acc.charCodeAt(8);
if (g_media === null) {
// No operations are pending
obj.SendCommand(0x47); // Send ResetOccuredResponse
debug('RESETOCCURED1', resetMask);
} else {
// Operations are being done, sent the reset once completed.
g_reset = true;
debug('RESETOCCURED2', resetMask);
}
return 9;
case 0x49: // STATUS_DATA - DisableEnableFeaturesReply
if (obj.acc.length < 13) return 0;
var type = obj.acc.charCodeAt(8);
var value = ReadIntX(obj.acc, 9);
debug('STATUS_DATA', type, value);
switch (type)
{
case 1: // REGS_AVAIL
if (value & 1) {
if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x08)); } // OnReboot
else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x10)); } // Graceful
else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x18)); } // Now
}
break;
case 2: // REGS_STATUS
obj.enabled = (value & 2) ? true : false;
debug("IDER Status: " + obj.enabled);
break;
case 3: // REGS_TOGGLE
if (value != 1) { debug("Register toggle failure"); } //else { obj.SendDisableEnableFeatures(2); }
break;
}
return 13;
case 0x4A: // ERROR OCCURED
if (obj.acc.length < 11) return 0;
debug('IDER: ABORT', obj.acc.charCodeAt(8));
//obj.Stop();
return 11;
case 0x4B: // HEARTBEAT
//debug('HEARTBEAT');
return 8;
case 0x50: // COMMAND WRITTEN
if (obj.acc.length < 28) return 0;
var device = (obj.acc.charCodeAt(14) & 0x10) ? 0xB0 : 0xA0;
var deviceFlags = obj.acc.charCodeAt(14);
var cdb = obj.acc.substring(16, 28);
var featureRegister = obj.acc.charCodeAt(9);
debug('SCSI_CMD', device, rstr2hex(cdb), featureRegister, deviceFlags);
handleSCSI(device, cdb, featureRegister, deviceFlags);
return 28;
case 0x53: // DATA FROM HOST
if (obj.acc.length < 14) return 0;
var len = ReadShortX(obj.acc, 9);
if (obj.acc.length < (14 + len)) return 0;
debug('SCSI_WRITE, len = ' + (14 + len));
obj.SendCommand(0x51, String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x70, 0x03, 0x00, 0x00, 0x00, 0xa0, 0x51, 0x07, 0x27, 0x00), true);
return 14 + len;
default:
debug('Unknown IDER command', obj.acc[0]);
obj.Stop();
break;
}
return 0;
}
function handleSCSI(dev, cdb, featureRegister, deviceFlags)
{
var lba;
var len;
switch(cdb.charCodeAt(0))
{
case 0x00: // TEST_UNIT_READY:
debug("SCSI: TEST_UNIT_READY", dev);
switch (dev) {
case 0xA0: // DEV_FLOPPY
if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.floppyReady == false) { obj.floppyReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
break;
case 0xB0: // DEV_CDDVD
if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.cdromReady == false) { obj.cdromReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
break;
default:
debug("SCSI Internal error 3", dev);
return -1;
}
obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); // Indicate ready
break;
case 0x08: // READ_6
lba = ((cdb.charCodeAt(1) & 0x1f) << 16) + (cdb.charCodeAt(2) << 8) + cdb.charCodeAt(3);
len = cdb.charCodeAt(4);
if (len == 0) { len = 256; }
debug("SCSI: READ_6", dev, lba, len);
sendDiskData(dev, lba, len, featureRegister);
break;
case 0x0a: // WRITE_6
lba = ((cdb.charCodeAt(1) & 0x1f) << 16) + (cdb.charCodeAt(2) << 8) + cdb.charCodeAt(3);
len = cdb.charCodeAt(4);
if (len == 0) { len = 256; }
debug("SCSI: WRITE_6", dev, lba, len);
obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); // Write is not supported, remote no medium.
return -1;
/*
case 0x15: // MODE_SELECT_6:
debug("SCSI ERROR: MODE_SELECT_6", dev);
obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
return -1;
*/
case 0x1a: // MODE_SENSE_6
debug("SCSI: MODE_SENSE_6", dev);
if ((cdb.charCodeAt(2) == 0x3f) && (cdb.charCodeAt(3) == 0x00)) {
var a = 0, b = 0;
switch (dev) {
case 0xA0: // DEV_FLOPPY
if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
a = 0x00;
b = 0x80; // Read only = 0x80, Read write = 0x00
break;
case 0xB0: // DEV_CDDVD
if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
a = 0x05;
b = 0x80;
break;
default:
debug("SCSI Internal error 6", dev);
return -1;
}
obj.SendDataToHost(dev, true, String.fromCharCode(0, a, b, 0), featureRegister & 1);
return;
}
obj.SendCommandEndResponse(1, 0x05, dev, 0x24, 0x00);
break;
case 0x1b: // START_STOP (Called when you eject the CDROM)
//var immediate = cdb.charCodeAt(1) & 0x01;
//var loej = cdb.charCodeAt(4) & 0x02;
//var start = cdb.charCodeAt(4) & 0x01;
obj.SendCommandEndResponse(1, 0, dev);
break;
case 0x1e: // LOCK_UNLOCK - ALLOW_MEDIUM_REMOVAL
debug("SCSI: ALLOW_MEDIUM_REMOVAL", dev);
if ((dev == 0xA0) && (obj.floppy == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
if ((dev == 0xB0) && (obj.cdrom == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00);
break;
case 0x23: // READ_FORMAT_CAPACITIES (Floppy only)
debug("SCSI: READ_FORMAT_CAPACITIES", dev);
var buflen = ReadShort(cdb, 7);
var mediaStatus = 0, sectors;
var mcSize = buflen / 8; // Capacity descriptor size is 8
switch (dev) {
case 0xA0: // DEV_FLOPPY
if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
sectors = (obj.floppy.size >> 9) - 1;
break;
case 0xB0: // DEV_CDDVD
if ((obj.cdrom == null) || (obj.cdrom.size == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
sectors = (obj.cdrom.size >> 11) - 1; // Number 2048 byte blocks
break;
default:
debug("SCSI Internal error 4", dev);
return -1;
}
obj.SendDataToHost(dev, true, IntToStr(8) + String.fromCharCode(0x00, 0x00, 0x0b, 0x40, 0x02, 0x00, 0x02, 0x00), featureRegister & 1);
break;
case 0x25: // READ_CAPACITY
debug("SCSI: READ_CAPACITY", dev);
var len = 0;
switch(dev)
{
case 0xA0: // DEV_FLOPPY
if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.floppy != null) { len = (obj.floppy.size >> 9) - 1; }
debug('DEV_FLOPPY', len); // Number 512 byte blocks
break;
case 0xB0: // DEV_CDDVD
if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
if (obj.cdrom != null) { len = (obj.cdrom.size >> 11) - 1; } // Number 2048 byte blocks
debug('DEV_CDDVD', len);
break;
default:
debug("SCSI Internal error 4", dev);
return -1;
}
//if (dev == 0xA0) { dev = 0x00; } else { dev = 0x10; } // Weird but seems to work.
debug("SCSI: READ_CAPACITY2", dev, deviceFlags);
obj.SendDataToHost(deviceFlags, true, IntToStr(len) + String.fromCharCode(0, 0, ((dev == 0xB0) ? 0x08 : 0x02), 0), featureRegister & 1);
break;
case 0x28: // READ_10
lba = ReadInt(cdb, 2);
len = ReadShort(cdb, 7);
debug("SCSI: READ_10", dev, lba, len);
sendDiskData(dev, lba, len, featureRegister);
break;
case 0x2a: // WRITE_10 (Floppy only)
case 0x2e: // WRITE_AND_VERIFY (Floppy only)
lba = ReadInt(cdb, 2);
len = ReadShort(cdb, 7);
debug("SCSI: WRITE_10", dev, lba, len);
obj.SendGetDataFromHost(dev, 512 * len); // Floppy writes only, accept sectors of 512 bytes
break;
case 0x43: // READ_TOC (CD Audio only)
var buflen = ReadShort(cdb, 7);
var msf = cdb.charCodeAt(1) & 0x02;
var format = cdb.charCodeAt(2) & 0x07;
if (format == 0) { format = cdb.charCodeAt(9) >> 6; }
debug("SCSI: READ_TOC, dev=" + dev + ", buflen=" + buflen + ", msf=" + msf + ", format=" + format);
switch (dev) {
case 0xA0: // DEV_FLOPPY
obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00); // Not implemented
return -1;
case 0xB0: // DEV_CDDVD
// NOP
break;
default:
debug("SCSI Internal error 9", dev);
return -1;
}
if (format == 1) { obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x0a, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00), featureRegister & 1); }
else if (format == 0) {
if (msf) {
obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x34, 0x13), featureRegister & 1);
} else {
obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00), featureRegister & 1);
}
}
break;
case 0x46: // GET_CONFIGURATION
var sendall = (cdb.charCodeAt(1) != 2);
var firstcode = ReadShort(cdb, 2);
var buflen = ReadShort(cdb, 7);
debug("SCSI: GET_CONFIGURATION", dev, sendall, firstcode, buflen);
if (buflen == 0) { obj.SendDataToHost(dev, true, IntToStr(0x003c) + IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
// Set the header
var r = IntToStr(0x0008);
// Add the data
if (firstcode == 0) { r += IDE_CD_ConfigArrayProfileList; }
if ((firstcode == 0x1) || (sendall && (firstcode < 0x1))) { r += IDE_CD_ConfigArrayCore; }
if ((firstcode == 0x2) || (sendall && (firstcode < 0x2))) { r += IDE_CD_Morphing; }
if ((firstcode == 0x3) || (sendall && (firstcode < 0x3))) { r += IDE_CD_ConfigArrayRemovable; }
if ((firstcode == 0x10) || (sendall && (firstcode < 0x10))) { r += IDE_CD_ConfigArrayRandom; }
if ((firstcode == 0x1E) || (sendall && (firstcode < 0x1E))) { r += IDE_CD_Read; }
if ((firstcode == 0x100) || (sendall && (firstcode < 0x100))) { r += IDE_CD_PowerManagement; }
if ((firstcode == 0x105) || (sendall && (firstcode < 0x105))) { r += IDE_CD_Timeout; }
// Set the length
r = IntToStr(r.length) + r;
// Cut the length to buflen if needed
if (r.length > buflen) { r = r.substring(0, buflen); }
obj.SendDataToHost(dev, true, r, featureRegister & 1);
return -1;
case 0x4a: // GET_EV_STATUS - GET_EVENT_STATUS_NOTIFICATION
//var buflen = (cdb.charCodeAt(7) << 8) + cdb.charCodeAt(8);
//if (buflen == 0) { obj.SendDataToHost(dev, true, IntToStr(0x003c) + IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
debug("SCSI: GET_EVENT_STATUS_NOTIFICATION", dev, cdb.charCodeAt(1), cdb.charCodeAt(4), cdb.charCodeAt(9));
if ((cdb.charCodeAt(1) != 0x01) && (cdb.charCodeAt(4) != 0x10)) { debug('SCSI ERROR'); obj.SendCommandEndResponse(1, 0x05, dev, 0x26, 0x01); break; }
var present = 0x00;
if ((dev == 0xA0) && (obj.floppy != null)) { present = 0x02; }
else if ((dev == 0xB0) && (obj.cdrom != null)) { present = 0x02; }
obj.SendDataToHost(dev, true, String.fromCharCode(0x00, present, 0x80, 0x00), featureRegister & 1); // This is the original version, 4 bytes long
break;
case 0x4c:
obj.SendCommand(0x51, IntToStrX(0) + IntToStrX(0) + IntToStrX(0) + String.fromCharCode(0x87, 0x50, 0x03, 0x00, 0x00, 0x00, 0xb0, 0x51, 0x05, 0x20, 0x00), true);
break;
case 0x51: // READ_DISC_INFO
debug("SCSI READ_DISC_INFO", dev);
obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // Correct
return -1;
case 0x55: // MODE_SELECT_10:
debug("SCSI ERROR: MODE_SELECT_10", dev);
obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
return -1;
case 0x5a: // MODE_SENSE_10
debug("SCSI: MODE_SENSE_10", dev, cdb.charCodeAt(2) & 0x3f);
var buflen = ReadShort(cdb, 7);
//var pc = cdb.charCodeAt(2) & 0xc0;
var r = null;
if (buflen == 0) { obj.SendDataToHost(dev, true, IntToStr(0x003c) + IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
// 1.44 mb floppy or LS120 (sectorCount == 0x3c300)
var sectorCount = 0;
if (dev == 0xA0) {
if (obj.floppy != null) { sectorCount = (obj.floppy.size >> 9); }
} else {
if (obj.cdrom != null) { sectorCount = (obj.cdrom.size >> 11); }
}
switch (cdb.charCodeAt(2) & 0x3f) {
case 0x01: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_FloppyError_Recovery_Array:IDE_ModeSence_Ls120Error_Recovery_Array; } else { r = IDE_ModeSence_CDError_Recovery_Array; } break;
case 0x05: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_FloppyDisk_Page_Array:IDE_ModeSence_LS120Disk_Page_Array; } break;
case 0x3f: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_3F_Floppy_Array:IDE_ModeSence_3F_LS120_Array; } else { r = IDE_ModeSence_3F_CD_Array; } break;
case 0x1A: if (dev == 0xB0) { r = IDE_ModeSence_CD_1A_Array; } break;
case 0x1D: if (dev == 0xB0) { r = IDE_ModeSence_CD_1D_Array; } break;
case 0x2A: if (dev == 0xB0) { r = IDE_ModeSence_CD_2A_Array; } break;
}
if (r == null) {
obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // TODO: Send proper error!!!
} else {
// Set disk to read only (we don't support write).
//ms_data[3] = ms_data[3] | 0x80;
obj.SendDataToHost(dev, true, r, featureRegister & 1);
}
break;
default: // UNKNOWN COMMAND
debug("IDER: Unknown SCSI command", cdb.charCodeAt(0));
obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00);
return -1;
}
return 0;
}
function sendDiskData(dev, lba, len, featureRegister) {
var media = null;
var mediaBlocks = 0;
if (dev == 0xA0) { media = obj.floppy; if (obj.floppy != null) { mediaBlocks = (obj.floppy.size >> 9); } }
if (dev == 0xB0) { media = obj.cdrom; if (obj.cdrom != null) { mediaBlocks = (obj.cdrom.size >> 11); } }
if ((len < 0) || (lba + len > mediaBlocks)) { obj.SendCommandEndResponse(1, 0x05, dev, 0x21, 0x00); return 0; }
if (len == 0) { obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); return 0; }
if (media != null) {
if (dev == 0xA0) { lba <<= 9; len <<= 9; } else { lba <<= 11; len <<= 11; }
if (g_media !== null) {
console.log('IDERERROR: Read while performing read');
obj.Stop();
} else {
// obj.iderinfo.readbfr // TODO: MaxRead
g_media = media;
g_dev = dev;
g_lba = lba;
g_len = len;
sendDiskDataEx(featureRegister);
}
}
}
var g_reset = false;
var g_media = null;
var g_dev;
var g_lba;
var g_len;
function sendDiskDataEx(featureRegister) {
var len = g_len, lba = g_lba;
if (g_len > obj.iderinfo.readbfr) { len = obj.iderinfo.readbfr; }
g_len -= len;
g_lba += len;
var fr = new FileReader();
fr.onload = function () {
obj.SendDataToHost(g_dev, (g_len == 0), this.result, featureRegister & 1);
if ((g_len > 0) && (g_reset == false)) {
sendDiskDataEx(featureRegister);
} else {
g_media = null;
if (g_reset) { obj.SendCommand(0x47); g_reset = false; } // Send ResetOccuredResponse
}
};
//console.log('Read from ' + lba + ' to ' + (lba + len) + ', total of ' + len);
fr.readAsBinaryString(g_media.slice(lba, lba + len));
}
return obj;

View File

@ -31,6 +31,8 @@ var CreateAmtRemoteTerminal = function (divid) {
var _TermLineWrap = true;
var _termx = 0;
var _termy = 0;
var _termsavex = 0;
var _termsavey = 0;
var _termstate = 0;
var _escNumber = [];
var _escNumberPtr = 0;
@ -110,7 +112,31 @@ var CreateAmtRemoteTerminal = function (divid) {
_altKeypadMode = false;
_termstate = 0;
break;
case '7':
// Save Cursor
_termsavex = _termx;
_termsavey = _termy;
_termstate = 0;
break;
case '8':
// Restore Cursor
_termx = _termsavex;
_termy = _termsavey;
_termstate = 0;
break;
case 'M':
// Scroll down one
var x = 1;
for (var y = _scrollRegion[1]; y >= _scrollRegion[0] + x; y--) {
for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = _tscreen[y - x][z]; _scratt[y][z] = _scratt[y - x][z]; }
}
for (var y = _scrollRegion[0] + x - 1; y > _scrollRegion[0] - 1; y--) {
for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = ' '; _scratt[y][z] = (7 << 6); }
}
_termstate = 0;
break;
default:
console.log('unknown terminal short code', b);
_termstate = 0;
break;
}
@ -203,7 +229,8 @@ var CreateAmtRemoteTerminal = function (divid) {
case 'P': // Delete X Character(s), default 1 char
var x = 1;
if (argslen == 1) { x = args[0]; }
for (i = _termx; i < (_termx + x) ; i++) { _tscreen[_termy][i] = ' '; _scratt[_termy][i] = (7 << 6); }
for (i = _termx; i < 80 - x; i++) { _tscreen[_termy][i] = _tscreen[_termy][i + x]; _scratt[_termy][i] = _scratt[_termy][i + x]; }
for (i = (80 - x); i < 80; i++) { _tscreen[_termy][i] = ' '; _scratt[_termy][i] = (7 << 6); }
break;
case 'L': // Insert X Line(s), default 1 char
var linecount = 1;
@ -244,8 +271,7 @@ var CreateAmtRemoteTerminal = function (divid) {
if (args[1] > obj.width) args[1] = obj.width;
_termy = args[0] - 1;
_termx = args[1] - 1;
}
else {
} else {
_termy = 0;
_termx = 0;
}
@ -330,6 +356,16 @@ var CreateAmtRemoteTerminal = function (divid) {
for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = ' '; _scratt[y][z] = (7 << 6); }
}
break;
case 'M': // Delete X lines, default 1
var x = 1;
if (argslen == 1) { x = args[0] }
for (var y = _termy; y <= _scrollRegion[1] - x; y++) {
for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = _tscreen[y + x][z]; _scratt[y][z] = _scratt[y + x][z]; }
}
for (var y = _scrollRegion[1] - x + 1; y < _scrollRegion[1]; y++) {
for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = ' '; _scratt[y][z] = (7 << 6); }
}
break;
case 'T': // Scroll down the scroll region X lines, default 1
var x = 1;
if (argslen == 1) { x = args[0] }
@ -340,9 +376,14 @@ var CreateAmtRemoteTerminal = function (divid) {
for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = ' '; _scratt[y][z] = (7 << 6); }
}
break;
case 'X': // Erase X characters, default 1 (untested)
var x = 1;
if (argslen == 1) { x = args[0] }
while ((x > 0) && (_termx > 0)) { _tscreen[_termy][_termx] = ' '; _termx--; x--; }
break;
default:
//if (code != '@') alert(code);
//console.log('unknown terminal code', code, args, mode);
console.log('unknown terminal code', code, args, mode);
break;
}
}
@ -614,6 +655,13 @@ var CreateAmtRemoteTerminal = function (divid) {
if (e.which == 40) { obj.TermSendKeys(String.fromCharCode(27, 91, 66)); return true; }; // Down
}
if (e.which == 33) { obj.TermSendKeys(String.fromCharCode(27, 91, 53, 126)); return true; }; // PageUp
if (e.which == 34) { obj.TermSendKeys(String.fromCharCode(27, 91, 54, 126)); return true; }; // PageDown
if (e.which == 35) { obj.TermSendKeys(String.fromCharCode(27, 91, 70)); return true; }; // End
if (e.which == 36) { obj.TermSendKeys(String.fromCharCode(27, 91, 72)); return true; }; // Home
if (e.which == 45) { obj.TermSendKeys(String.fromCharCode(27, 91, 50, 126)); return true; }; // Insert
if (e.which == 46) { obj.TermSendKeys(String.fromCharCode(27, 91, 51, 126)); return true; }; // Delete
if (e.which == 9) { obj.TermSendKeys("\t"); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return true; }; // TAB
// F1 to F12 keys

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
obj.app.get("/MeshServerRootCert.cer", function (req, res) {
// The redirection server starts before certificates are loaded, make sure to handle the case where no certificate is loaded now.
if (obj.certificates != null) {
res.set({ "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0", "Content-Type": "application/octet-stream", "Content-Disposition": "attachment; filename=" + obj.certificates.RootName + ".cer" });
res.set({ "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0", "Content-Type": "application/octet-stream", "Content-Disposition": "attachment; filename=\"" + obj.certificates.RootName + ".cer\"" });
var rootcert = obj.certificates.root.cert;
var i = rootcert.indexOf("-----BEGIN CERTIFICATE-----\r\n");
if (i >= 0) { rootcert = rootcert.substring(i + 29); }

View File

@ -45,17 +45,19 @@
"_TitlePicture": "title-sample.png",
"_UserQuota": 1048576,
"_MeshQuota": 248576,
"NewAccounts": 1,
"_NewAccounts": true,
"_NewAccountEmailDomains": [ "sample.com" ],
"Footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>",
"_CertUrl": "https://192.168.2.106:443/",
"_PasswordRequirements": { "min": 8, "max": 128, "upper": 1, "lower": 1, "numeric": 1, "nonalpha": 1, "reset": 90 },
"_PasswordRequirements": { "min": 8, "max": 128, "upper": 1, "lower": 1, "numeric": 1, "nonalpha": 1, "reset": 90, "force2factor": true },
"_AgentNoProxy": true,
"_GeoLocation": true,
"_UserAllowedIP": "127.0.0.1,192.168.1.0/24",
"_UserBlockedIP": "127.0.0.1,::1,192.168.0.100",
"_AgentAllowedIP": "192.168.0.100/24",
"_AgentBlockedIP": "127.0.0.1,::1",
"__UserConsentFlags__" : "Set to: 1 for desktop, 2 for terminal, 3 for files, 7 for all",
"_UserConsentFlags" : 7,
"_Limits": {
"_MaxUserAccounts": 100,
"_MaxUserSessions": 100,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@
<script type="text/javascript" src="scripts/zlib-adler32.js"></script>
<script type="text/javascript" src="scripts/zlib-crc32.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/filesaver.1.1.20151003.js"></script>
<title>MeshCentral</title>
<title>{{{title}}}</title>
<style>
a {
color: #036;
@ -249,10 +249,10 @@
<br style=clear:both />
</div>
<strong>Device Groups</strong>
( <a onclick=account_createMesh() style=cursor:pointer><img src="images/icon-addnew.png" width=12 height=12 border=0 /> New</a> )
<span id="p3createMeshLink1">( <a onclick=account_createMesh() style=cursor:pointer><img src="images/icon-addnew.png" width=12 height=12 border=0 /> New</a> )</span>
<br /><br />
<div id=p3meshes></div>
<div id=p3noMeshFound style=margin-left:9px;display:none>No device groups. <a onclick=account_createMesh() style=cursor:pointer><strong>Get started here!</strong></a></div>
<div id=p3noMeshFound style=margin-left:9px;display:none>No device groups.<span id="p3createMeshLink2"> <a onclick=account_createMesh() style=cursor:pointer><strong>Get started here!</strong></a></span></div>
<br style=clear:both />
</div>
</div>
@ -655,6 +655,10 @@
QV('manageAuthApp', features & 4096);
QV('manageOtp', ((features & 4096) != 0) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0)));
// On the mobile app, don't allow group creation (for now).
QV('p3createMeshLink1', false);
QV('p3createMeshLink2', false);
if (typeof userinfo.passchange == 'number') {
if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', ' - Reset on next login.'); }
else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
@ -809,7 +813,7 @@
}
case 'createmesh': {
// A new mesh was created
if (message.event.links['user/' + domain + '/' + userinfo.name.toLowerCase()] != null) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
if (message.event.links[userinfo._id] != null) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links };
updateMeshes();
updateDevices();
@ -830,7 +834,7 @@
meshes[message.event.meshid].links = message.event.links;
// Check if we lost rights to this mesh in this change.
if (meshes[message.event.meshid].links['user/' + domain + '/' + userinfo.name.toLowerCase()] == null) {
if (meshes[message.event.meshid].links[userinfo._id] == null) {
if ((xxcurrentView == 20) && (currentMesh == meshes[message.event.meshid])) go(2);
delete meshes[message.event.meshid];
@ -1149,24 +1153,31 @@
function account_showChangePassword() {
if (xxdialogMode) return;
var x = "<form action='" + domainUrl + "changepassword' method=post><table style=margin-left:10px><tr>";
x += "<td align=right>Password:</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td>";
x += "</tr><tr><td align=right>Password:</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td>";
if (features & 0x00010000) { x += "</tr><tr><td align=right>Hint:</td><td><input id=apasswordhint name=apasswordhint maxlength=250 type=text autocomplete=off /></td>"; }
x += '</tr></table><div style=padding:10px;margin-bottom:4px>';
var x = "<form action='" + domainUrl + "changepassword' method=post><table style=margin-left:10px>";
x += "<tr><td align=right>Old Password:</td><td><input id=apassword0 type=password name=apassword0 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td></tr>";
x += "<tr><td align=right>New Password:</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td></tr>";
x += "<tr><td align=right>New Password:</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td></tr>";
if (features & 0x00010000) { x += "<tr><td align=right>Hint:</td><td><input id=apasswordhint name=apasswordhint maxlength=250 type=text autocomplete=off /></td></tr>"; }
x += '</table><div style=padding:10px;margin-bottom:4px>';
x += '<input id=account_dlgCancelButton type=button value=Cancel style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
x += '<input id=account_dlgOkButton type=submit value=OK style="float:right;width:80px" onclick=dialogclose(1)>';
x += '</div><br /></form>';
setDialogMode(2, "Change Password", 0, null, x);
account_validateNewPassword();
Q('apassword1').focus();
Q('apassword0').focus();
}
function account_createMesh() {
if (xxdialogMode) return;
// Check if we are allowed to create a new device group
if ((userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "New Device Group", 1, null, "Unable to create a new device group until the email address is verified. Go to the \"My Account\" menu option to change and verify an email address."); return; }
// Check if we are disallowed from creating a device group
if ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 64) != 0)) { setDialogMode(2, "New Device Group", 1, null, "This account does not have the rights to create a new device group."); return; }
// Remind the user to verify the email address
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
// We are allowed, let's prompt to information
var x = addHtmlValue('Name', '<input id=dp3meshname style=width:170px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
@ -1190,7 +1201,8 @@
}
function account_validateNewPassword() {
var r = '', ok = (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value);
var r = '', ok = (Q('apassword0').value.length > 0) && (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value) && (Q('apassword0').value != Q('apassword1').value);
if ((features & 0x00010000) && (Q('apasswordhint').value == Q('apassword1').value)) { ok = false; }
if (Q('apassword1').value != '') {
if (passRequirements == null || passRequirements == '') {
// No password requirements, display password strength
@ -1240,7 +1252,7 @@
count++;
// Mesh rights
var meshrights = meshes[i].links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
var meshrights = meshes[i].links[userinfo._id].rights;
var rights = 'Partial Rights';
if (meshrights == 0xFFFFFFFF) rights = 'Full Administrator'; else if (meshrights == 0) rights = 'No Rights';
@ -1531,7 +1543,7 @@
// Go thru the list of nodes and display them
for (var i in nodes) {
if (nodes[i].v == false) continue;
var mesh2 = meshes[nodes[i].meshid], meshlinks = mesh2.links['user/' + domain + '/' + userinfo.name.toLowerCase()];
var mesh2 = meshes[nodes[i].meshid], meshlinks = mesh2.links[userinfo._id];
if (meshlinks == null) continue;
var meshrights = meshlinks.rights;
@ -1602,7 +1614,7 @@
// Display all empty meshes, we need to do this because users can add devices to these at any time.
if (sort == 0) {
for (var i in meshes) {
var mesh = meshes[i], meshlink = mesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()];
var mesh = meshes[i], meshlink = mesh.links[userinfo._id];
if (meshlink != null) {
var meshrights = meshlink.rights;
if (displayedMeshes[mesh._id] == null) {
@ -1690,7 +1702,7 @@
function getNodeRights(nodeid) {
var node = getNodeFromId(nodeid), mesh = meshes[node.meshid];
return mesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
return mesh.links[userinfo._id].rights;
}
var currentDevicePanel = 0;
@ -1701,11 +1713,18 @@
var powerTimeline = null;
function getCurrentNode() { return currentNode; };
function gotoDevice(nodeid, panel, refresh) {
// Remind the user to verify the email address
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
var node = getNodeFromId(nodeid);
if (node == null) { goBack(); return; }
var mesh = meshes[node.meshid];
if (mesh == null) { goBack(); return; }
var meshrights = mesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
var meshrights = mesh.links[userinfo._id].rights;
if (!currentNode || currentNode._id != node._id || refresh == true) {
currentNode = node;
@ -1746,7 +1765,7 @@
}
// Attribute: Mesh Agent
var agentsStr = ['Unknown', 'Windows 32bit console', 'Windows 64bit console', 'Windows 32bit service', 'Windows 64bit service', 'Linux 32bit', 'Linux 64bit', 'MIPS', 'XENx86', 'Android ARM', 'Linux ARM', 'OSX 32bit', 'Android x86', 'PogoPlug ARM', 'Android APK', 'Linux Poky x86-32bit', 'OSX 64bit', 'ChromeOS', 'Linux Poky x86-64bit', 'Linux NoKVM x86-32bit', 'Linux NoKVM x86-64bit', 'Windows MinCore console', 'Windows MinCore service', 'NodeJS', 'ARM-Linaro', 'ARMv6l / ARMv7l'];
var agentsStr = ['Unknown', 'Windows 32bit console', 'Windows 64bit console', 'Windows 32bit service', 'Windows 64bit service', 'Linux 32bit', 'Linux 64bit', 'MIPS', 'XENx86', 'Android ARM', 'Linux ARM', 'OSX 32bit', 'Android x86', 'PogoPlug ARM', 'Android APK', 'Linux Poky x86-32bit', 'OSX 64bit', 'ChromeOS', 'Linux Poky x86-64bit', 'Linux NoKVM x86-32bit', 'Linux NoKVM x86-64bit', 'Windows MinCore console', 'Windows MinCore service', 'NodeJS', 'ARM-Linaro', 'ARMv6l / ARMv7l', 'ARMv8 64bit'];
if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
var str = '';
if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
@ -1865,7 +1884,7 @@
function setupDeviceMenu(op, obj) {
var meshrights = 0;
if (currentNode) { meshrights = meshes[currentNode.meshid].links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights; }
if (currentNode) { meshrights = meshes[currentNode.meshid].links[userinfo._id].rights; }
if (op != null) { currentDevicePanel = op; }
QV('p10general', currentDevicePanel == 0);
QV('p10desktop', currentDevicePanel == 1); // Show if we have remote control rights or desktop view only rights
@ -1879,7 +1898,7 @@
function deviceActionFunction() {
if (xxdialogMode) return;
var meshrights = meshes[currentNode.meshid].links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
var meshrights = meshes[currentNode.meshid].links[userinfo._id].rights;
var x = "Select an operation to perform on this device.<br /><br />";
var y = '<select id=d2deviceop style=float:right;width:170px>';
if ((meshrights & 64) != 0) { y += '<option value=100>Wake-up</option>'; } // Wake-up permission
@ -2022,7 +2041,7 @@
function p10showiconselector() {
if (xxdialogMode) return;
var mesh = meshes[currentNode.meshid];
var meshrights = mesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
var meshrights = mesh.links[userinfo._id].rights;
if ((meshrights & 4) == 0) return;
var x = '<table align=center><td>';
@ -2100,7 +2119,7 @@
var mesh = meshes[currentNode.meshid];
var deskState = 0;
if (desktop != null) { deskState = desktop.State; }
var meshrights = mesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
var meshrights = mesh.links[userinfo._id].rights;
// Show the right buttons
QV('disconnectbutton1', (deskState != 0));
@ -2842,7 +2861,7 @@
if (currentMesh == null) return;
QH('p20meshName', EscapeHtml(currentMesh.name));
var meshtype = 'Unknown #' + currentMesh.mtype;
var meshrights = currentMesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
var meshrights = currentMesh.links[userinfo._id].rights;
if (currentMesh.mtype == 1) meshtype = 'Intel&reg; AMT group';
if (currentMesh.mtype == 2) meshtype = 'Software agent group';
@ -2855,7 +2874,7 @@
//x += '<br><input type=button value=Notes title="View notes about this mesh" onclick=showNotes(false,"' + encodeURIComponent(currentMesh._id) + '") />';
x += '<br style=clear:both><br>';
var currentMeshLinks = currentMesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()];
var currentMeshLinks = currentMesh.links[userinfo._id];
if (currentMeshLinks && ((currentMeshLinks.rights & 2) != 0)) { x += '<div style=margin-bottom:6px><a onclick=p20showAddMeshUserDialog() style=cursor:pointer><img src=images/icon-addnew.png border=0 height=12 width=12> Add User</a></div>'; }
/*
@ -2971,7 +2990,7 @@
}
function p20validateAddMeshUserDialog() {
var meshrights = currentMesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights;
var meshrights = currentMesh.links[userinfo._id].rights;
QE('idx_dlgOkButton', (Q('dp20username').value.length > 0));
QE('p20fulladmin', meshrights == 0xFFFFFFFF);
QE('p20editmesh', (!Q('p20fulladmin').checked) && (meshrights == 0xFFFFFFFF));
@ -3010,7 +3029,7 @@
function p20viewuser(userid) {
if (xxdialogMode) return;
userid = decodeURIComponent(userid);
var r = '', cmeshrights = currentMesh.links['user/' + domain + '/' + userinfo.name.toLowerCase()].rights, meshrights = currentMesh.links[userid].rights;
var r = '', cmeshrights = currentMesh.links[userinfo._id].rights, meshrights = currentMesh.links[userid].rights;
if (meshrights == 0xFFFFFFFF) r = ', Full Administrator'; else {
if ((meshrights & 1) != 0) r += ', Edit Device Group';
if ((meshrights & 2) != 0) r += ', Manage Device Group Users';
@ -3029,7 +3048,7 @@
if (r == '') { r = 'No Rights'; }
var buttons = 1, x = addHtmlValue('User', EscapeHtml(decodeURIComponent(userid.split('/')[2])));
x += addHtmlValue('Permissions', r);
if ((('user/' + domain + '/' + userinfo.name.toLowerCase()) != userid) && (cmeshrights == 0xFFFFFFFF || (((cmeshrights & 2) != 0) && (meshrights != 0xFFFFFFFF)))) buttons += 4;
if (((userinfo._id) != userid) && (cmeshrights == 0xFFFFFFFF || (((cmeshrights & 2) != 0) && (meshrights != 0xFFFFFFFF)))) buttons += 4;
setDialogMode(2, "Mesh User", buttons, p20viewuserEx, x, userid);
}

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

View File

@ -279,7 +279,7 @@
validateLogin();
validateCreate();
if ('{{loginmode}}' != '') { go(parseInt('{{loginmode}}')); } else { go(1); }
QV('newAccountDiv', ('{{{newAccount}}}' != '0') && ('{{{newAccount}}}' != 'false')); // If new accounts are not allowed, don't display the new account link.
QV('newAccountDiv', ('{{{newAccount}}}' === '1') || ('{{{newAccount}}}' === 'true')); // If new accounts are not allowed, don't display the new account link.
if ((passRequirements.hint === true) && (passhint != null) && (passhint.length > 0)) { QV("showPassHintLink", true); }
QV("newAccountPass", (newAccountPass == 1));
QV("resetAccountDiv", (emailCheck == true));

View File

@ -11,16 +11,22 @@
<script keeplink=1 type="text/javascript" src="scripts/u2f-api.js"></script>
<title>{{{title}}} - Login</title>
</head>
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide">
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide login">
<div id=container>
<div id=mastheadx></div>
<div id=masthead>
<div class="title">{{{title}}}</div>
<div class="title2">{{{title2}}}</div>
</div>
<div id=page_content>
<div id=topbar class="noselect style3">
<div id=toggle title="Toggle full width" onclick="toggleFullScreen(1)">&harr;</div>
<div id=topbar class="noselect style3" style="height:24px">
<div id=uiMenuButton title="User interface selection" onclick="showUserInterfaceSelectMenu()">
&diams;
<div id=uiMenu style="display:none">
<div id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface"><div class="uiSelector1"></div></div>
<div id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface"><div class="uiSelector2"></div></div>
<div id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface"><div class="uiSelector3"></div></div>
<div id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode"><div class="uiSelector4"></div></div>
</div>
</div>
</div>
<div id=column_l>
<h1>Welcome</h1>
@ -29,7 +35,7 @@
<tr>
<td id="welcomeimage">
<picture>
<img alt="" src=welcome.jpg />
<img alt="" src=welcome.jpg style="border-radius:20px" />
</picture>
</td>
<td id="logincell">
@ -216,7 +222,7 @@
&nbsp;<a href=terms>Terms &amp; Privacy</a>
</div>
</div>
</div>
</div>
<div id=dialog style="display:none">
<div id=dialogHeader>
@ -247,12 +253,16 @@
if (passRequirements != "") { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } else { passRequirements = {}; }
var passRequirementsEx = ((passRequirements.min != null) || (passRequirements.max != null) || (passRequirements.upper != null) || (passRequirements.lower != null) || (passRequirements.numeric != null) || (passRequirements.nonalpha != null));
var features = parseInt('{{{features}}}');
var webPageFullScreen = getstore('webPageFullScreen', true);
if (webPageFullScreen == 'false') { webPageFullScreen = false; }
if (webPageFullScreen == 'true') { webPageFullScreen = true; }
var welcomeText = decodeURIComponent("{{{welcometext}}}");
var currentpanel = 0;
toggleFullScreen();
var uiMode = parseInt(getstore('uiMode', '1'));
var webPageFullScreen = true;
var nightMode = (getstore('_nightMode', '0') == '1');
//var webPageFullScreen = getstore('webPageFullScreen', true);
//if (webPageFullScreen == 'false') { webPageFullScreen = false; }
//if (webPageFullScreen == 'true') { webPageFullScreen = true; }
//toggleFullScreen();
function startup() {
if ((features & 32) == 0) {
@ -262,6 +272,8 @@
if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
}
if (nightMode) { QC('body').add('night'); }
QV('createPanelHint', passRequirements.hint === true);
QV('resetpasswordpanelHint', passRequirements.hint === true);
@ -275,7 +287,7 @@
validateLogin();
validateCreate();
if ('{{loginmode}}' != '') { go(parseInt('{{loginmode}}')); } else { go(1); }
QV('newAccountDiv', ('{{{newAccount}}}' != '0') && ('{{{newAccount}}}' != 'false')); // If new accounts are not allowed, don't display the new account link.
QV('newAccountDiv', ('{{{newAccount}}}' === '1') || ('{{{newAccount}}}' === 'true')); // If new accounts are not allowed, don't display the new account link.
if ((passhint != null) && (passhint.length > 0)) { QV("showPassHintLink", true); }
QV("newAccountPass", (newAccountPass == 1));
QV("resetAccountDiv", (emailCheck == true));
@ -360,6 +372,9 @@
}, hardwareKeyChallenge.timeoutSeconds);
}
}
// Setup the user interface in the right mode
userInterfaceSelectMenu();
}
function showPassHint() {
@ -616,7 +631,7 @@
// Toggle the web page to full screen
function toggleFullScreen(toggle) {
if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
//if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
if (webPageFullScreen == false) {
// By adding body class, it will change a style of all ellements using CSS selector
// No need for JS anymore and it will be consistent style for all the templates.
@ -628,6 +643,30 @@
center();
}
// Toggle user interface menu
function showUserInterfaceSelectMenu() {
Q('uiViewButton1').classList.remove('uiSelectorSel');
Q('uiViewButton2').classList.remove('uiSelectorSel');
Q('uiViewButton3').classList.remove('uiSelectorSel');
try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
QV('uiMenu', (QS('uiMenu').display == 'none'));
if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
}
function userInterfaceSelectMenu(s) {
if (s) { uiMode = s; putstore('uiMode', uiMode); }
webPageFullScreen = (uiMode < 3);
//webPageStackMenu = (uiMode > 1);
toggleFullScreen(0);
//toggleStackMenu(0);
}
function toggleNightMode() {
nightMode = !nightMode;
if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
putstore('_nightMode', (nightMode ? '1' : '0'));
}
function center() {
/* Now we use CSS media to achive the same effect as deleted JS */

Some files were not shown because too many files have changed in this diff Show More