@ -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\" />
|
||||
|
BIN
agents/MeshCentralRouter.exe
Normal file
BIN
agents/meshagent_arm64
Normal file
2
agents/meshcmd.min.js
vendored
@ -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];
|
||||
@ -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,10 +1720,12 @@ function createMeshCore(agent) {
|
||||
|
||||
// Update the network interfaces information data
|
||||
var netInfo = mesh.NetInfo;
|
||||
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
|
||||
function sendPeriodicServerUpdate(flags) {
|
||||
|
2
agents/meshcore.min.js
vendored
206
agents/meshcore_diagnostic.js
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]);
|
||||
|
@ -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>';
|
||||
|
2
agents/modules_meshcmd_min/amt-script.min.js
vendored
@ -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;
|
2
agents/modules_meshcmd_min/amt-wsman.min.js
vendored
@ -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>';
|
||||
|
@ -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()
|
||||
|
2
agents/modules_meshcore_min/amt-wsman.min.js
vendored
@ -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
@ -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
@ -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
@ -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
@ -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)); }
|
@ -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 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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 { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=', '\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
@ -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;
|
||||
};
|
503
db.js
@ -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,89 +469,119 @@ 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;
|
||||
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.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
|
||||
obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
|
||||
obj.file.find({ _id: id }).toArray(func);
|
||||
}
|
||||
});
|
||||
|
||||
// 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.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)); }); } }
|
||||
|
||||
obj.cleanup = function (func) {
|
||||
// TODO: Remove all mesh links to invalid users
|
||||
// TODO: Remove all meshes that dont have any links
|
||||
// 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 }); };
|
||||
|
||||
// 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 });
|
||||
// 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 }); };
|
||||
|
||||
// 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 });
|
||||
// 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); };
|
||||
|
||||
// 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;
|
||||
// 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); };
|
||||
|
||||
// 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(); }
|
||||
}
|
||||
// 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 }).toArray(func); }
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Database actions on the main collection
|
||||
}
|
||||
} 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) { obj.file.find({ _id: id }, 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); };
|
||||
@ -363,67 +663,10 @@ module.exports.CreateDB = function (parent) {
|
||||
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');
|
||||
func(obj); // Completed function setup
|
||||
}
|
||||
|
||||
// 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 }); }); };
|
||||
|
||||
function Clone(v) { return JSON.parse(JSON.stringify(v)); }
|
||||
|
||||
return obj;
|
||||
};
|
194
meshagent.js
@ -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,10 +740,6 @@ 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;
|
||||
@ -720,20 +756,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
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);
|
||||
|
||||
|
@ -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,7 +238,9 @@ 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);
|
||||
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; }
|
||||
@ -479,7 +481,7 @@ function CreateMeshCentralServer(config, args) {
|
||||
|
||||
// Lower case all keys in the config file
|
||||
try {
|
||||
require('./common.js').objKeysToLower(config2);
|
||||
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();
|
||||
@ -500,6 +502,8 @@ function CreateMeshCentralServer(config, args) {
|
||||
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
|
||||
|
137
meshrelay.js
@ -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.
|
||||
|
||||
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) };
|
||||
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 + ')'); }
|
||||
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;
|
||||
|
370
meshuser.js
@ -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® 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,23 +781,26 @@ 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 != '~')) {
|
||||
// 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) { }
|
||||
break;
|
||||
}
|
||||
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;
|
||||
};
|
96
mpsserver.js
@ -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) {
|
||||
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
|
||||
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 {
|
||||
// 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;
|
||||
}
|
||||
} 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
|
||||
|
@ -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",
|
||||
|
2421
public/commander.htm
BIN
public/images/clipboard-128.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/computers-128.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/images/gears-128.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 10 KiB |
BIN
public/images/icons256-1-1.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
public/images/icons256-2-1.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
public/images/icons256-3-1.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
public/images/icons256-4-1.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
public/images/icons256-5-1.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/images/icons256-6-1.png
Normal file
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 24 KiB |
BIN
public/images/icons64.png
Normal file
After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.7 KiB |
BIN
public/images/leftbar-64.png
Normal file
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 12 KiB |
BIN
public/images/mesh-256.png
Normal file
After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 754 B After Width: | Height: | Size: 718 B |
BIN
public/images/meshicon256.png
Normal file
After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.9 KiB |
BIN
public/images/notify/icons128-0.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
public/images/notify/icons128-1.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
public/images/notify/icons128-2.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
public/images/notify/icons128-3.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
public/images/notify/icons128-4.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
public/images/notify/icons128-5.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
public/images/notify/icons128-6.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
public/images/notify/icons128-7.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
public/images/notify/icons128-8.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/images/server-256.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
public/images/servericon120.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/servericon64.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
public/images/user-128.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.9 KiB |
BIN
public/images/user-256.png
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 11 KiB |
BIN
public/images/webp/mesh-256.webp
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 4.4 KiB |
BIN
public/images/webp/user-256.webp
Normal file
After Width: | Height: | Size: 4.7 KiB |
@ -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); }
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
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 = 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
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function _OnSocketClosed() {
|
||||
// obj.Debug("Socket Closed");
|
||||
obj.Stop();
|
||||
}
|
||||
|
||||
obj.dialogPrompt = function(x) { _Send('H' + JSON.stringify(x)); }
|
||||
|
||||
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);
|
||||
}
|
||||
//console.log('Read from ' + lba + ' to ' + (lba + len) + ', total of ' + len);
|
||||
fr.readAsBinaryString(g_media.slice(lba, lba + len));
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
@ -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
|
||||
|
@ -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); }
|
||||
|
@ -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,
|
||||
|
@ -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® 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);
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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)">↔</div>
|
||||
<div id=topbar class="noselect style3" style="height:24px">
|
||||
<div id=uiMenuButton title="User interface selection" onclick="showUserInterfaceSelectMenu()">
|
||||
♦
|
||||
<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 @@
|
||||
<a href=terms>Terms & 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 */
|
||||
|
||||
|