Added RDP ClickOnce support.

Ylian Saint-Hilaire committed Oct 23, 2017 at 14:09 UTC 380115920013cb0f1fdbd8c493c988a73595de87
28 files changed +1827 -1338
MeshCentralServer.njsproj
+52
@@ -33,9 +33,11 @@
33 <Compile Include="meshcentral.js" />
34 <Compile Include="meshagent.js" />
35 <Compile Include="meshrelay.js" />
36 + <Compile Include="meshuser.js" />
37 <Compile Include="mpsserver.js" />
38 <Compile Include="multiserver.js" />
39 <Compile Include="pass.js" />
40 + <Compile Include="public\relay.js" />
41 <Compile Include="public\scripts\amt-0.2.0.js" />
42 <Compile Include="public\scripts\agent-desktop-0.0.2.js" />
43 <Compile Include="public\scripts\amt-desktop-0.0.2.js" />
@@ -54,6 +56,15 @@
56 <Compile Include="redirserver.js" />
57 <Compile Include="webserver.js" />
58 <Content Include="package.json" />
59 + <Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.application" />
60 + <Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.config.deploy" />
61 + <Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.deploy" />
62 + <Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.manifest" />
63 + <Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.ico.deploy" />
64 + <Content Include="public\clickonce\minirouter\MeshMiniRouter.application" />
65 + <Content Include="public\clickonce\minirouter\publish.htm" />
66 + <Content Include="public\commander.htm" />
67 + <Content Include="public\favicon.ico" />
68 <Content Include="public\images-isdu\ComputerIcon.png" />
69 <Content Include="public\images-isdu\ComputerIcon2.png" />
70 <Content Include="public\images-isdu\IntelLogo.png" />
@@ -71,6 +82,40 @@
82 <Content Include="public\images-isdu\info.gif" />
83 <Content Include="public\images-isdu\warning.gif" />
84 <Content Include="public\images-isdu\isdu.ico" />
85 + <Content Include="public\images\dot.png" />
86 + <Content Include="public\images\icon-addnew.png" />
87 + <Content Include="public\images\icon-graph.png" />
88 + <Content Include="public\images\icon-installmesh.png" />
89 + <Content Include="public\images\icon-manage.png" />
90 + <Content Include="public\images\icon-remove.png" />
91 + <Content Include="public\images\icons16.png" />
92 + <Content Include="public\images\icons200-1-1.png" />
93 + <Content Include="public\images\icons200-2-1.png" />
94 + <Content Include="public\images\icons200-3-1.png" />
95 + <Content Include="public\images\icons200-4-1.png" />
96 + <Content Include="public\images\icons200-5-1.png" />
97 + <Content Include="public\images\icons200-6-1.png" />
98 + <Content Include="public\images\icons50.png" />
99 + <Content Include="public\images\images16.png" />
100 + <Content Include="public\images\info.png" />
101 + <Content Include="public\images\link1.png" />
102 + <Content Include="public\images\link2.png" />
103 + <Content Include="public\images\link3.png" />
104 + <Content Include="public\images\link4.png" />
105 + <Content Include="public\images\logoback.png" />
106 + <Content Include="public\images\mainaccount.png" />
107 + <Content Include="public\images\mainwelcome.png" />
108 + <Content Include="public\images\mapmarker-gps.png" />
109 + <Content Include="public\images\mapmarker-ip.png" />
110 + <Content Include="public\images\mapmarker-user.png" />
111 + <Content Include="public\images\mapmarker-wifi.png" />
112 + <Content Include="public\images\mapmarker.png" />
113 + <Content Include="public\images\meshicon50.png" />
114 + <Content Include="public\images\trash.png" />
115 + <Content Include="public\index.html" />
116 + <Content Include="public\relay.htm" />
117 + <Content Include="public\scriptblocks.txt" />
118 + <Content Include="public\sounds\chimes.mp3" />
119 <Content Include="public\styles\font-awesome\css\font-awesome.min.css" />
120 <Content Include="public\styles\font-awesome\fonts\fontawesome-webfont.eot" />
121 <Content Include="public\styles\font-awesome\fonts\fontawesome-webfont.svg" />
@@ -108,6 +153,7 @@
153 <Content Include="public\styles\font-awesome\scss\_variables.scss" />
154 <Content Include="public\styles\style.css" />
155 <Content Include="readme.txt" />
156 + <Content Include="SourceFileList.txt" />
157 <Content Include="views\default.handlebars" />
158 <Content Include="views\download.handlebars" />
159 <Content Include="views\login.handlebars" />
@@ -116,8 +162,14 @@
162 <ItemGroup>
163 <Folder Include="agents\" />
164 <Folder Include="public" />
165 + <Folder Include="public\clickonce\" />
166 + <Folder Include="public\clickonce\minirouter\" />
167 + <Folder Include="public\clickonce\minirouter\Application Files\" />
168 + <Folder Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\" />
169 <Folder Include="public\images-isdu" />
170 + <Folder Include="public\images\" />
171 <Folder Include="public\scripts\" />
172 + <Folder Include="public\sounds\" />
173 <Folder Include="public\styles\" />
174 <Folder Include="public\styles\font-awesome\" />
175 <Folder Include="public\styles\font-awesome\css\" />
agents/MeshService.exe
Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ
agents/MeshService64.exe
Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ
agents/meshagent_arm
Binary files a/agents/meshagent_arm and b/agents/meshagent_arm differ
agents/meshagent_pi
Binary files a/agents/meshagent_pi and b/agents/meshagent_pi differ
agents/meshagent_pogo
Binary files a/agents/meshagent_pogo and b/agents/meshagent_pogo differ
agents/meshagent_poky
Binary files a/agents/meshagent_poky and b/agents/meshagent_poky differ
agents/meshagent_poky64
Binary files a/agents/meshagent_poky64 and b/agents/meshagent_poky64 differ
agents/meshagent_x86
Binary files a/agents/meshagent_x86 and b/agents/meshagent_x86 differ
agents/meshagent_x86-64
Binary files a/agents/meshagent_x86-64 and b/agents/meshagent_x86-64 differ
agents/meshagent_x86-64_nokvm
Binary files a/agents/meshagent_x86-64_nokvm and b/agents/meshagent_x86-64_nokvm differ
agents/meshagent_x86_nokvm
Binary files a/agents/meshagent_x86_nokvm and b/agents/meshagent_x86_nokvm differ
agents/meshcore.js
+68 -37
@@ -18,7 +18,7 @@ function createMeshCore(agent) {
18 var obj = {};
19
20 // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
21 - obj.meshCoreInfo = "MeshCore v3k";
21 + obj.meshCoreInfo = "MeshCore v4";
22 obj.meshCoreCapabilities = 14; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript
23 obj.useNativePipes = true; //(process.platform == 'win32');
24 var meshServerConnectionState = 0;
@@ -28,6 +28,7 @@ function createMeshCore(agent) {
28 var lastPublicLocationInfo = null;
29 var selfInfoUpdateTimer = null;
30 var http = require('http');
31 + var net = require('net');
32 var fs = require('fs');
33 var rtc = require('ILibWebRTC');
34 var wifiScannerLib = null;
@@ -57,7 +58,7 @@ function createMeshCore(agent) {
58
59 // Get our location (lat/long) using our public IP address
60 var getIpLocationDataExInProgress = false;
60 - var getIpLocationDataExCounts = [ 0, 0 ];
61 + var getIpLocationDataExCounts = [0, 0];
62 function getIpLocationDataEx(func) {
63 if (getIpLocationDataExInProgress == true) { return false; }
64 try {
@@ -157,7 +158,9 @@ function createMeshCore(agent) {
158 var w = arguments[i];
159 if (w != null) {
160 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
160 - while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
161 + if (i != 0) {
162 + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
163 + }
164 x.push(w);
165 }
166 }
@@ -283,29 +286,27 @@ function createMeshCore(agent) {
286 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
287 }
288 }
286 - else if (data.type == 'tunnel') { // Process a new tunnel connection request
287 - if (data.value && data.sessionid) {
288 - // Create a new tunnel object
289 - sendConsoleText(data.value);
290 - var xurl = getServerTargetUrlEx(data.value);
291 - sendConsoleText(xurl);
292 - if (xurl != null) {
293 - var tunnel = http.request(http.parseUri(xurl));
294 - tunnel.upgrade = onTunnelUpgrade;
295 - tunnel.sessionid = data.sessionid;
296 - tunnel.rights = data.rights;
297 - tunnel.state = 0;
298 - tunnel.url = xurl;
299 - tunnel.protocol = 0;
300 -
301 - // Put the tunnel in the tunnels list
302 - var index = 1;
303 - while (tunnels[index]) { index++; }
304 - tunnel.index = index;
305 - tunnels[index] = tunnel;
306 -
307 - sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
308 - }
289 + else if ((data.type == 'tunnel') && (data.value != null)) { // Process a new tunnel connection request
290 + // Create a new tunnel object
291 + var xurl = getServerTargetUrlEx(data.value);
292 + if (xurl != null) {
293 + var tunnel = http.request(http.parseUri(xurl));
294 + tunnel.upgrade = onTunnelUpgrade;
295 + tunnel.sessionid = data.sessionid;
296 + tunnel.rights = data.rights;
297 + tunnel.state = 0;
298 + tunnel.url = xurl;
299 + tunnel.protocol = 0;
300 + tunnel.tcpaddr = data.tcpaddr;
301 + tunnel.tcpport = data.tcpport;
302 +
303 + // Put the tunnel in the tunnels list
304 + var index = 1;
305 + while (tunnels[index]) { index++; }
306 + tunnel.index = index;
307 + tunnels[index] = tunnel;
308 +
309 + sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
310 }
311 }
312 break;
@@ -339,7 +340,7 @@ function createMeshCore(agent) {
340
341 // Called when a file changed in the file system
342 function onFileWatcher(a, b) {
342 - //console.log('onFileWatcher', a, b, this.path);
343 + console.log('onFileWatcher', a, b, this.path);
344 var response = getDirectoryInfo(this.path);
345 if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
346 }
@@ -364,6 +365,7 @@ function createMeshCore(agent) {
365 if (reqpath == '') { reqpath = '/'; }
366 var xpath = obj.path.join(reqpath, '*');
367 var results = null;
368 +
369 try { results = fs.readdirSync(xpath); } catch (e) { }
370 if (results != null) {
371 for (var i = 0; i < results.length; ++i) {
@@ -385,7 +387,38 @@ function createMeshCore(agent) {
387 }
388
389 // Tunnel callback operations
388 - function onTunnelUpgrade(response, s, head) { sendConsoleText('onTunnelUpgrade'); this.s = s; s.httprequest = this; s.end = onTunnelClosed; s.data = onTunnelData; }
390 + function onTunnelUpgrade(response, s, head) {
391 + this.s = s;
392 + s.httprequest = this;
393 + s.end = onTunnelClosed;
394 +
395 + if (this.tcpport != null) {
396 + // This is a TCP relay connection, pause now and try to connect to the target.
397 + s.pause();
398 + s.data = onTcpRelayServerTunnelData;
399 + var connectionOptions = { port: parseInt(this.tcpport) };
400 + if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
401 + s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
402 + s.tcprelay.peerindex = this.index;
403 + } else {
404 + // This is a normal connect for KVM/Terminal/Files
405 + s.data = onTunnelData;
406 + }
407 + }
408 +
409 + // Called when the TCP relay target is connected
410 + function onTcpRelayTargetTunnelConnect() {
411 + var peerTunnel = tunnels[this.peerindex];
412 + this.pipe(peerTunnel.s); // Pipe Target --> Server
413 + peerTunnel.s.first = true;
414 + peerTunnel.s.resume();
415 + }
416 +
417 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
418 + function onTcpRelayServerTunnelData(data) {
419 + if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target
420 + }
421 +
422 function onTunnelClosed() {
423 sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
424 if (this.httprequest.protocol == 1) { this.httprequest.process.end(); delete this.httprequest.process; }
@@ -395,7 +428,7 @@ function createMeshCore(agent) {
428 if (this.httprequest.watcher != undefined) {
429 //console.log('Closing watcher: ' + this.httprequest.watcher.path);
430 //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
398 - delete this.httprequest.watcher;
431 + delete this.httprequest.watcher;
432 }
433
434 // If there is a upload or download active on this connection, close the file
@@ -404,6 +437,7 @@ function createMeshCore(agent) {
437 }
438 function onTunnelSendOk() { sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid); }
439 function onTunnelData(data) {
440 + console.log("OnTunnelData");
441 // If this is upload data, save it to file
442 if (this.httprequest.uploadFile) {
443 try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { this.write(JSON.stringify({ action: 'uploaderror' })); return; } // Write to the file, if there is a problem, error out.
@@ -521,8 +555,7 @@ function createMeshCore(agent) {
555 try { cmd = JSON.parse(data); } catch (e) { };
556 if ((cmd == null) || (cmd.action == undefined)) { return; }
557 if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
524 - sendConsoleText(JSON.stringify(cmd));
525 - //console.log(objToString(cmd, 0, '.'));
558 + console.log(objToString(cmd, 0, '.'));
559 switch (cmd.action) {
560 case 'ls': {
561 // Close the watcher if required
@@ -534,9 +567,7 @@ function createMeshCore(agent) {
567 }
568
569 // Send the folder content to the browser
537 - sendConsoleText(JSON.stringify(cmd.path));
570 var response = getDirectoryInfo(cmd.path);
539 - sendConsoleText(JSON.stringify(response));
571 if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
572 this.write(JSON.stringify(response));
573
@@ -605,11 +636,11 @@ function createMeshCore(agent) {
636 //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
637 }
638 }
608 -
639 +
640 // Console state
641 var consoleWebSockets = {};
642 var consoleHttpRequest = null;
612 -
643 +
644 // Console HTTP response
645 function consoleHttpResponse(response) {
646 response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
@@ -807,7 +838,7 @@ function createMeshCore(agent) {
838 response += (results[i] + "\r\n");
839 } else {
840 response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n");
810 - }
841 + }
842 }
843 break;
844 }
@@ -1001,7 +1032,7 @@ function createMeshCore(agent) {
1032 function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; }
1033 function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); }
1034 function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); }
1004 -
1035 +
1036 function onWebSocketUpgrade(response, s, head) {
1037 sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid);
1038 this.s = s;
meshagent.js
+2
@@ -362,6 +362,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
362 // Route a message.
363 // If this command has a sessionid, that is the target.
364 if (command.sessionid != null) {
365 + if (typeof command.sessionid != 'string') break;
366 var splitsessionid = command.sessionid.split('/');
367 // Check that we are in the same domain and the user has rights over this node.
368 if ((splitsessionid[0] == 'user') && (splitsessionid[1] == domain.id)) {
@@ -384,6 +385,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
385 }
386 }
387 } else if (command.userid != null) { // If this command has a userid, that is the target.
388 + if (typeof command.userid != 'string') break;
389 var splituserid = command.userid.split('/');
390 // Check that we are in the same domain and the user has rights over this node.
391 if ((splituserid[0] == 'user') && (splituserid[1] == domain.id)) {
meshrelay.js
+61 -4
@@ -10,21 +10,78 @@ module.exports.CreateMeshRelay = function (parent, ws, req) {
10 obj.req = req;
11 obj.peer = null;
12 obj.parent = parent;
13 - obj.id = req.query['id'];
13 + obj.id = req.query.id;
14 obj.remoteaddr = obj.ws._socket.remoteAddress;
15 if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
16
17 - if (obj.id == undefined) { obj.ws.close(); obj.id = null; return null; } // Attempt to connect without id, drop this.
18 - ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
17 + // Disconnect this agent
18 + obj.close = function (arg) {
19 + 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
20 + 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
21 + }
22 +
23 + obj.sendAgentMessage = function (command, userid, domainid) {
24 + if (command.nodeid == null) return false;
25 + var user = obj.parent.users[userid];
26 + if (user == null) return false;
27 + var splitnodeid = command.nodeid.split('/');
28 + // Check that we are in the same domain and the user has rights over this node.
29 + if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domainid)) {
30 + // Get the user object
31 + // See if the node is connected
32 + var agent = obj.parent.wsagents[command.nodeid];
33 + if (agent != null) {
34 + // Check if we have permission to send a message to that node
35 + var rights = user.links[agent.dbMeshKey];
36 + if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
37 + command.sessionid = ws.sessionId; // Set the session id, required for responses.
38 + command.rights = rights.rights; // Add user rights flags to the message
39 + delete command.nodeid; // Remove the nodeid since it's implyed.
40 + agent.send(JSON.stringify(command));
41 + return true;
42 + }
43 + } else {
44 + // Check if a peer server is connected to this agent
45 + var routing = obj.parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type
46 + if (routing != null) {
47 + // Check if we have permission to send a message to that node
48 + var rights = user.links[routing.meshid];
49 + if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
50 + command.fromSessionid = ws.sessionId; // Set the session id, required for responses.
51 + command.rights = rights.rights; // Add user rights flags to the message
52 + obj.parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
53 + return true;
54 + }
55 + }
56 + }
57 + }
58 + return false;
59 + }
60
61 if (req.query.auth == null) {
62 // Use ExpressJS session, check if this session is a logged in user, at least one of the two connections will need to be authenticated.
63 try { if ((req.session) && (req.session.userid) || (req.session.domainid == getDomain(req).id)) { obj.authenticated = true; } } catch (e) { }
64 } else {
65 // Get the session from the cookie
25 - if ((obj.parent.parent.multiServer != null) && (obj.parent.parent.multiServer.decodeCookie(req.query.auth) != null)) { obj.authenticated = true; }
66 + var cookie = obj.parent.parent.webserver.decodeCookie(req.query.auth);
67 + if (cookie != null) {
68 + obj.authenticated = true;
69 + if (cookie.tcpport != null) {
70 + // This cookie has agent routing instructions, process this here.
71 + if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
72 + // Send connection request to agent
73 + var command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr };
74 + if (obj.sendAgentMessage(command, cookie.userid, cookie.domainid) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); }
75 + }
76 + } else {
77 + obj.id = null;
78 + obj.parent.parent.debug(1, 'Relay: invalid cookie (' + obj.remoteaddr + ')');
79 + }
80 }
81
82 + if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this.
83 + ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
84 +
85 // Validate that the id is valid, we only need to do this on non-authenticated sessions.
86 // TODO: Figure out when this needs to be done.
87 /*
meshscanner.js
+35 -33
@@ -23,19 +23,17 @@ module.exports.CreateMeshScanner = function (parent) {
23 // Get a list of IPv4 and IPv6 interface addresses
24 function getInterfaceList() {
25 var ipv4 = ['*'], ipv6 = ['*']; // Bind to IN_ADDR_ANY always
26 - //if (parent.platform == 'win32') { // On Windows, also bind to each interface seperatly (TODO: REMOVE THIS AND TEST ON LINUX!!!!!!!!!!!!!!!!!!)
27 - var interfaces = require('os').networkInterfaces();
28 - for (var i in interfaces) {
29 - var interface = interfaces[i];
30 - for (var j in interface) {
31 - var interface2 = interface[j];
32 - if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
33 - if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
34 - if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
35 - }
26 + var interfaces = require('os').networkInterfaces();
27 + for (var i in interfaces) {
28 + var interface = interfaces[i];
29 + for (var j in interface) {
30 + var interface2 = interface[j];
31 + if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
32 + if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
33 + if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
34 }
35 }
38 - //}
36 + }
37 return { ipv4: ipv4, ipv6: ipv6 };
38 }
39
@@ -57,7 +55,7 @@ module.exports.CreateMeshScanner = function (parent) {
55 server4.xxtype = 4;
56 server4.xxlocal = localAddress;
57 server4.on('error', function (err) { if (this.xxlocal == '*') { console.log("ERROR: Server port 16989 not available, check if server is running twice."); } this.close(); delete obj.servers6[this.xxlocal]; });
60 - var bindOptions = { port: 16989, exclusive: false };
58 + var bindOptions = { port: 16989, exclusive: true };
59 if (server4.xxlocal != '*') { bindOptions.address = server4.xxlocal; }
60 server4.bind(bindOptions, function () {
61 try {
@@ -68,7 +66,7 @@ module.exports.CreateMeshScanner = function (parent) {
66 this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
67 obj.performScan(this);
68 obj.performScan(this);
71 - } catch (e) { }
69 + } catch (e) { console.log(e); }
70 });
71 obj.servers4[localAddress] = server4;
72 } catch (e) {
@@ -84,28 +82,32 @@ module.exports.CreateMeshScanner = function (parent) {
82 obj.servers6[localAddress].xxclear = false;
83 } else {
84 // Create a new IPv6 server
87 - var server6 = obj.dgram.createSocket({ type: 'udp6', reuseAddr: true });
88 - server6.xxclear = false;
89 - server6.xxtype = 6;
90 - server6.xxlocal = localAddress;
91 - server6.on('error', function (err) { this.close(); delete obj.servers6[this.xxlocal]; });
92 - var bindOptions = { port: 16989, exclusive: false };
93 - if (server6.xxlocal != '*') { bindOptions.address = server6.xxlocal; }
94 - server6.bind(bindOptions, function () {
95 - try {
96 - this.setBroadcast(true);
97 - this.setMulticastTTL(128);
98 - this.addMembership(membershipIPv6);
99 - this.on('error', function (error) { console.log('Error: ' + error); });
100 - this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
101 - obj.performScan(this);
102 - obj.performScan(this);
103 - } catch (e) { }
104 - });
105 - obj.servers6[localAddress] = server6;
85 + try {
86 + var server6 = obj.dgram.createSocket({ type: 'udp6', reuseAddr: true });
87 + server6.xxclear = false;
88 + server6.xxtype = 6;
89 + server6.xxlocal = localAddress;
90 + server6.on('error', function (err) { this.close(); delete obj.servers6[this.xxlocal]; });
91 + var bindOptions = { port: 16989, exclusive: true };
92 + if (server6.xxlocal != '*') { bindOptions.address = server6.xxlocal; }
93 + server6.bind(bindOptions, function () {
94 + try {
95 + this.setBroadcast(true);
96 + this.setMulticastTTL(128);
97 + this.addMembership(membershipIPv6);
98 + this.on('error', function (error) { console.log('Error: ' + error); });
99 + this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
100 + obj.performScan(this);
101 + obj.performScan(this);
102 + } catch (e) { console.log(e); }
103 + });
104 + obj.servers6[localAddress] = server6;
105 + } catch (e) {
106 + console.log(e);
107 + }
108 }
109 }
108 -
110 +
111 for (var i in obj.servers4) { if (obj.servers4[i].xxclear == true) { obj.servers4[i].close(); delete obj.servers4[i]; }; }
112 for (var i in obj.servers6) { if (obj.servers6[i].xxclear == true) { obj.servers6[i].close(); delete obj.servers6[i]; }; }
113 }
meshuser.js new
+931
@@ -0,0 +1,931 @@
1 +/**
2 +* @description Meshcentral MeshAgent
3 +* @author Ylian Saint-Hilaire & Bryan Roe
4 +* @version v0.0.1
5 +*/
6 +
7 +// Construct a MeshAgent object, called upon connection
8 +module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
9 + var obj = {};
10 + obj.db = db;
11 + obj.ws = ws;
12 + obj.fs = parent.fs;
13 + obj.args = args;
14 + obj.parent = parent;
15 + obj.domain = domain;
16 + obj.common = parent.common;
17 +
18 + // Send a message to the user
19 + //obj.send = function (data) { try { if (typeof data == 'string') { obj.ws.send(new Buffer(data, 'binary')); } else { obj.ws.send(data); } } catch (e) { } }
20 +
21 + // Disconnect this user
22 + obj.close = function (arg) {
23 + if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket
24 + if (arg == 2) { try { obj.ws._socket._parent.end(); obj.parent.parent.debug(1, 'Hard disconnect'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
25 + }
26 +
27 + try {
28 + // Check if the user is logged in
29 + if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) { try { obj.ws.close(); } catch (e) { } return; }
30 + req.session.ws = obj.ws; // Associate this websocket session with the web session
31 + req.session.ws.userid = req.session.userid;
32 + req.session.ws.domainid = domain.id;
33 + var user = obj.parent.users[req.session.userid];
34 + if (user == null) { try { obj.ws.close(); } catch (e) { } return; }
35 +
36 + // Add this web socket session to session list
37 + obj.ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2);
38 + obj.parent.wssessions2[ws.sessionId] = obj.ws;
39 + if (!obj.parent.wssessions[user._id]) { obj.parent.wssessions[user._id] = [ws]; } else { obj.parent.wssessions[user._id].push(obj.ws); }
40 + if (obj.parent.parent.multiServer == null) {
41 + obj.parent.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.parent.wssessions[user._id].length, nolog: 1, domain: obj.domain.id })
42 + } else {
43 + obj.parent.recountSessions(obj.ws.sessionId); // Recount sessions
44 + }
45 +
46 + // If we have peer servers, inform them of the new session
47 + if (obj.parent.parent.multiServer != null) { obj.parent.parent.multiServer.DispatchMessage({ action: 'sessionStart', sessionid: obj.ws.sessionId }); }
48 +
49 + // Handle events
50 + obj.ws.HandleEvent = function (source, event) {
51 + if (!event.domain || event.domain == obj.domain.id) {
52 + try {
53 + if (event == 'close') { obj.req.session.destroy(); obj.ws.close(); }
54 + else if (event == 'resubscribe') { user.subscriptions = obj.parent.subscribe(user._id, ws); }
55 + else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); }
56 + else { ws.send(JSON.stringify({ action: 'event', event: event })); }
57 + } catch (e) { }
58 + }
59 + }
60 +
61 + user.subscriptions = obj.parent.subscribe(user._id, ws); // Subscribe to events
62 + obj.ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
63 +
64 + // When data is received from the web socket
65 + ws.on('message', function (msg) {
66 + var user = obj.parent.users[req.session.userid];
67 + var command = JSON.parse(msg.toString('utf8'))
68 + switch (command.action) {
69 + case 'meshes':
70 + {
71 + // Request a list of all meshes this user as rights to
72 + var docs = [];
73 + for (var i in user.links) { if (obj.parent.meshes[i]) { docs.push(obj.parent.meshes[i]); } }
74 + ws.send(JSON.stringify({ action: 'meshes', meshes: docs }));
75 + break;
76 + }
77 + case 'nodes':
78 + {
79 + // Request a list of all meshes this user as rights to
80 + var links = [];
81 + for (var i in user.links) { links.push(i); }
82 +
83 + // Request a list of all nodes
84 + obj.db.GetAllTypeNoTypeFieldMeshFiltered(links, domain.id, 'node', function (err, docs) {
85 + var r = {};
86 + for (var i in docs) {
87 + // Add the connection state
88 + var state = obj.parent.parent.GetConnectivityState(docs[i]._id);
89 + if (state) {
90 + docs[i].conn = state.connectivity;
91 + docs[i].pwr = state.powerState;
92 + if ((state.connectivity & 1) != 0) { var agent = obj.parent.wsagents[docs[i]._id]; if (agent != null) { docs[i].agct = agent.connectTime; } }
93 + if ((state.connectivity & 2) != 0) { var cira = obj.parent.parent.mpsserver.ciraConnections[docs[i]._id]; if (cira != null) { docs[i].cict = cira.tag.connectTime; } }
94 + }
95 + // Compress the meshid's
96 + var meshid = docs[i].meshid;
97 + if (!r[meshid]) { r[meshid] = []; }
98 + delete docs[i].meshid;
99 + r[meshid].push(docs[i]);
100 + }
101 + ws.send(JSON.stringify({ action: 'nodes', nodes: r }));
102 + });
103 + break;
104 + }
105 + case 'powertimeline':
106 + {
107 + // Query the database for the power timeline for a given node
108 + // The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ]
109 + obj.db.getPowerTimeline(command.nodeid, function (err, docs) {
110 + if (err == null && docs.length > 0) {
111 + var timeline = [], time = null, previousPower;
112 + for (var i in docs) {
113 + var doc = docs[i];
114 + if (time == null) {
115 + // First element
116 + time = doc.time;
117 + if (doc.oldPower) { timeline.push(doc.oldPower); } else { timeline.push(0); }
118 + timeline.push(time);
119 + timeline.push(doc.power);
120 + previousPower = doc.power;
121 + } else {
122 + // Delta element
123 + if ((previousPower != doc.power) && ((doc.time - time) > 60000)) { // To boost speed, any blocks less than a minute get approximated.
124 + // Create a new timeline
125 + timeline.push(doc.time - time);
126 + timeline.push(doc.power);
127 + time = doc.time;
128 + previousPower = doc.power;
129 + } else {
130 + // Extend the previous timeline
131 + if ((timeline.length >= 6) && (timeline[timeline.length - 3] == doc.power)) { // We can merge the block with the previous block
132 + timeline[timeline.length - 4] += (timeline[timeline.length - 2] + (doc.time - time));
133 + timeline.pop();
134 + timeline.pop();
135 + } else { // Extend the last block in the timeline
136 + timeline[timeline.length - 2] += (doc.time - time);
137 + timeline[timeline.length - 1] = doc.power;
138 + }
139 + time = doc.time;
140 + previousPower = doc.power;
141 + }
142 + }
143 + }
144 + ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline }));
145 + } else {
146 + // No records found, send current state if we have it
147 + var state = obj.parent.parent.GetConnectivityState(command.nodeid);
148 + if (state != null) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState] })); }
149 + }
150 + });
151 + break;
152 + }
153 + case 'files':
154 + {
155 + // Send the full list of server files to the browser app
156 + if ((user.siteadmin & 8) != 0) { updateUserFiles(user, ws, domain); }
157 + break;
158 + }
159 + case 'fileoperation':
160 + {
161 + // Check permissions
162 + if ((user.siteadmin & 8) != 0) {
163 + // Perform a file operation (Create Folder, Delete Folder, Delete File...)
164 + if ((command.path != null) && (typeof command.path == 'object') && command.path.length > 0) {
165 + var rootfolder = command.path[0];
166 + var rootfoldersplit = rootfolder.split('/'), domainx = 'domain';
167 + if (rootfoldersplit[1].length > 0) domainx = 'domain-' + rootfoldersplit[1];
168 + var path = obj.path.join(obj.filespath, domainx + "/" + rootfoldersplit[0] + "-" + rootfoldersplit[2]);
169 + for (var i = 1; i < command.path.length; i++) { if (obj.common.IsFilenameValid(command.path[i]) == false) { path = null; break; } path += ("/" + command.path[i]); }
170 + if (path == null) break;
171 +
172 + if ((command.fileop == 'createfolder') && (obj.common.IsFilenameValid(command.newfolder) == true)) { try { obj.fs.mkdirSync(path + "/" + command.newfolder); } catch (e) { } } // Create a new folder
173 + else if (command.fileop == 'delete') { for (var i in command.delfiles) { if (obj.common.IsFilenameValid(command.delfiles[i]) == true) { var fullpath = path + "/" + command.delfiles[i]; try { obj.fs.rmdirSync(fullpath); } catch (e) { try { obj.fs.unlinkSync(fullpath); } catch (e) { } } } } } // Delete
174 + else if ((command.fileop == 'rename') && (obj.common.IsFilenameValid(command.oldname) == true) && (obj.common.IsFilenameValid(command.newname) == true)) { try { obj.fs.renameSync(path + "/" + command.oldname, path + "/" + command.newname); } catch (e) { } } // Rename
175 +
176 + obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles') // Fire an event causing this user to update this files
177 + }
178 + }
179 + break;
180 + }
181 + case 'msg':
182 + {
183 + // Route a message.
184 + // This this command has a nodeid, that is the target.
185 + if (command.nodeid != null) {
186 + var splitnodeid = command.nodeid.split('/');
187 + // Check that we are in the same domain and the user has rights over this node.
188 + if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) {
189 + // See if the node is connected
190 + var agent = obj.parent.wsagents[command.nodeid];
191 + if (agent != null) {
192 + // Check if we have permission to send a message to that node
193 + var rights = user.links[agent.dbMeshKey];
194 + if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
195 + command.sessionid = ws.sessionId; // Set the session id, required for responses.
196 + command.rights = rights.rights; // Add user rights flags to the message
197 + delete command.nodeid; // Remove the nodeid since it's implyed.
198 + agent.send(JSON.stringify(command));
199 + }
200 + } else {
201 + // Check if a peer server is connected to this agent
202 + var routing = obj.parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type
203 + if (routing != null) {
204 + // Check if we have permission to send a message to that node
205 + var rights = user.links[routing.meshid];
206 + if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
207 + command.fromSessionid = ws.sessionId; // Set the session id, required for responses.
208 + command.rights = rights.rights; // Add user rights flags to the message
209 + obj.parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
210 + }
211 + }
212 + }
213 + }
214 + }
215 + break;
216 + }
217 + case 'events':
218 + {
219 + // Send the list of events for this session
220 + obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs })); });
221 + break;
222 + }
223 + case 'clearevents':
224 + {
225 + // Delete all events
226 + if (user.siteadmin != 0xFFFFFFFF) break;
227 + obj.db.RemoveAllEvents(domain.id);
228 + obj.parent.parent.DispatchEvent(['*', 'server-global'], obj, { action: 'clearevents', nolog: 1, domain: domain.id })
229 + break;
230 + }
231 + case 'users':
232 + {
233 + // Request a list of all users
234 + if ((user.siteadmin & 2) == 0) break;
235 + var docs = [];
236 + for (var i in obj.parent.users) { if ((obj.parent.users[i].domain == domain.id) && (obj.parent.users[i].name != '~')) { docs.push(obj.parent.users[i]); } }
237 + ws.send(JSON.stringify({ action: 'users', users: docs }));
238 + break;
239 + }
240 + case 'wssessioncount':
241 + {
242 + // Request a list of all web socket user session count
243 + var wssessions = {};
244 + if ((user.siteadmin & 2) == 0) break;
245 + if (obj.parent.parent.multiServer == null) {
246 + // No peering, use simple session counting
247 + for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } }
248 + } else {
249 + // We have peer servers, use more complex session counting
250 + for (var userid in obj.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.sessionsCount[userid]; } }
251 + }
252 + ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions })); // wssessions is: userid --> count
253 + break;
254 + }
255 + case 'deleteuser':
256 + {
257 + // Delete a user account
258 + if ((user.siteadmin & 2) == 0) break;
259 + var delusername = command.username, deluserid = command.userid, deluser = obj.parent.users[deluserid];
260 + if ((deluser.siteadmin != null) && (deluser.siteadmin > 0) && (user.siteadmin != 0xFFFFFFFF)) break; // Need full admin to remote another administrator
261 + if ((deluserid.split('/').length != 3) || (deluserid.split('/')[1] != domain.id)) break; // Invalid domain, operation only valid for current domain
262 +
263 + // Delete all files on the server for this account
264 + try {
265 + var deluserpath = obj.parent.getServerRootFilePath(deluser);
266 + if (deluserpath != null) { obj.parent.deleteFolderRec(deluserpath); }
267 + } catch (e) { }
268 +
269 + obj.db.Remove(deluserid);
270 + delete obj.parent.users[deluserid];
271 + obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: delusername, action: 'accountremove', msg: 'Account removed', domain: domain.id })
272 + obj.parent.parent.DispatchEvent([deluserid], obj, 'close');
273 +
274 + break;
275 + }
276 + case 'adduser':
277 + {
278 + // Add a new user account
279 + if ((user.siteadmin & 2) == 0) break;
280 + var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase();
281 + if (newusername == '~') break; // This is a reserved user name
282 + if (!obj.parent.users[newuserid]) {
283 + var newuser = { type: 'user', _id: newuserid, name: newusername, email: command.email, creation: Date.now(), domain: domain.id };
284 + obj.parent.users[newuserid] = newuser;
285 + // Create a user, generate a salt and hash the password
286 + obj.parent.hash(command.pass, function (err, salt, hash) {
287 + if (err) throw err;
288 + newuser.salt = salt;
289 + newuser.hash = hash;
290 + obj.db.SetUser(newuser);
291 + var newuser2 = obj.common.Clone(newuser);
292 + if (newuser2.subscriptions) { delete newuser2.subscriptions; }
293 + if (newuser2.salt) { delete newuser2.salt; }
294 + if (newuser2.hash) { delete newuser2.hash; }
295 + obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: newusername, account: newuser2, action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id })
296 + });
297 + }
298 + break;
299 + }
300 + case 'edituser':
301 + {
302 + // Edit a user account, may involve changing email or administrator permissions
303 + if (((user.siteadmin & 2) != 0) || (user.name == command.name)) {
304 + var chguserid = 'user/' + domain.id + '/' + command.name.toLowerCase(), chguser = obj.parent.users[chguserid], change = 0;
305 + if (chguser) {
306 + if (command.email && chguser.email != command.email) { chguser.email = command.email; change = 1; }
307 + if (command.quota != chguser.quota) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; }
308 + if ((user.siteadmin == 0xFFFFFFFF) && (command.siteadmin != null) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1 }
309 + if (change == 1) {
310 + obj.db.Set(chguser);
311 + obj.parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe');
312 + var chguser2 = obj.common.Clone(chguser);
313 + delete chguser2.salt;
314 + delete chguser2.hash;
315 + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: chguser2, action: 'accountchange', msg: 'Account changed: ' + command.name, domain: domain.id })
316 + }
317 + }
318 + }
319 + break;
320 + }
321 + case 'serverversion':
322 + {
323 + // Check the server version
324 + if ((user.siteadmin & 16) == 0) break;
325 + obj.parent.parent.getLatestServerVersion(function (currentVersion, latestVersion) { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); });
326 + break;
327 + }
328 + case 'serverupdate':
329 + {
330 + // Perform server update
331 + if ((user.siteadmin & 16) == 0) break;
332 + obj.parent.parent.performServerUpdate();
333 + break;
334 + }
335 + case 'createmesh':
336 + {
337 + // Create mesh
338 + // TODO: Right now, we only create type 1 Agent-less Intel AMT mesh, or type 2 Agent mesh
339 + if ((command.meshtype == 1) || (command.meshtype == 2)) {
340 + // Create a type 1 agent-less Intel AMT mesh.
341 + obj.crypto.randomBytes(48, function (err, buf) {
342 + var meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');;
343 + var links = {}
344 + links[user._id] = { name: user.name, rights: 0xFFFFFFFF };
345 + var mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links };
346 + obj.db.Set(mesh);
347 + obj.parent.meshes[meshid] = mesh;
348 + obj.parent.parent.AddEventDispatch([meshid], ws);
349 + if (user.links == null) user.links = {};
350 + user.links[meshid] = { rights: 0xFFFFFFFF };
351 + user.subscriptions = obj.parent.subscribe(user._id, ws);
352 + obj.db.SetUser(user);
353 + obj.parent.parent.DispatchEvent(['*', meshid, user._id], obj, { etype: 'mesh', username: user.name, meshid: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, action: 'createmesh', links: links, msg: 'Mesh created: ' + command.meshname, domain: domain.id })
354 + });
355 + }
356 + break;
357 + }
358 + case 'deletemesh':
359 + {
360 + // Delete a mesh and all computers within it
361 + obj.db.Get(command.meshid, function (err, meshes) {
362 + if (meshes.length != 1) return;
363 + var mesh = meshes[0];
364 +
365 + // Check if this user has rights to do this
366 + if (mesh.links[user._id] == null || mesh.links[user._id].rights != 0xFFFFFFFF) return;
367 + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
368 +
369 + // Fire the removal event first, because after this, the event will not route
370 + obj.parent.parent.DispatchEvent(['*', command.meshid], obj, { etype: 'mesh', username: user.name, meshid: command.meshid, name: command.meshname, action: 'deletemesh', msg: 'Mesh deleted: ' + command.meshname, domain: domain.id })
371 +
372 + // Remove all user links to this mesh
373 + for (var i in meshes) {
374 + var links = meshes[i].links;
375 + for (var j in links) {
376 + var xuser = obj.parent.users[j];
377 + delete xuser.links[meshes[i]._id];
378 + obj.db.Set(xuser);
379 + obj.parent.parent.DispatchEvent([xuser._id], obj, 'resubscribe');
380 + }
381 + }
382 +
383 + // Delete all files on the server for this mesh
384 + try {
385 + var meshpath = getServerRootFilePath(mesh);
386 + if (meshpath != null) { deleteFolderRec(meshpath); }
387 + } catch (e) { }
388 +
389 + obj.parent.parent.RemoveEventDispatchId(command.meshid); // Remove all subscriptions to this mesh
390 + obj.db.RemoveMesh(command.meshid); // Remove mesh from database
391 + delete obj.parent.meshes[command.meshid]; // Remove mesh from memory
392 + });
393 + break;
394 + }
395 + case 'editmesh':
396 + {
397 + // Change the name or description of a mesh
398 + var mesh = obj.parent.meshes[command.meshid], change = '';
399 + if (mesh) {
400 + // Check if this user has rights to do this
401 + if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 1) == 0)) return;
402 + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
403 +
404 + if (command.meshname && command.meshname != '' && command.meshname != mesh.name) { change = 'Mesh name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; }
405 + if (command.desc != null && command.desc != mesh.desc) { if (change != '') change += ' and description changed'; else change += 'Mesh "' + mesh.name + '" description changed'; mesh.desc = command.desc; }
406 + if (change != '') { obj.db.Set(mesh); obj.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, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }) }
407 + }
408 + break;
409 + }
410 + case 'addmeshuser':
411 + {
412 + // Check if the user exists
413 + var newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(), newuser = obj.parent.users[newuserid];
414 + if (newuser == null) {
415 + // TODO: Send error back, user not found.
416 + break;
417 + }
418 +
419 + // Get the mesh
420 + var mesh = obj.parent.meshes[command.meshid], change = '';
421 + if (mesh) {
422 + // Check if this user has rights to do this
423 + if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
424 + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
425 +
426 + // Add mesh to user
427 + if (newuser.links == null) newuser.links = {};
428 + newuser.links[command.meshid] = { rights: command.meshadmin };
429 + obj.db.Set(newuser);
430 + obj.parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe');
431 +
432 + // Add a user to the mesh
433 + mesh.links[newuserid] = { name: command.username, rights: command.meshadmin };
434 + obj.db.Set(mesh);
435 +
436 + // Notify mesh change
437 + var change = 'Added user ' + command.username + ' to mesh ' + mesh.name;
438 + obj.parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, { etype: 'mesh', username: user.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id })
439 + }
440 + break;
441 + }
442 + case 'removemeshuser':
443 + {
444 + if ((command.userid.split('/').length != 3) || (command.userid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
445 +
446 + // Check if the user exists
447 + var deluserid = command.userid, deluser = obj.parent.users[deluserid];
448 + if (deluser == null) {
449 + // TODO: Send error back, user not found.
450 + break;
451 + }
452 +
453 + // Get the mesh
454 + var mesh = obj.parent.meshes[command.meshid];
455 + if (mesh) {
456 + // Check if this user has rights to do this
457 + if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
458 +
459 + // Remove mesh from user
460 + if (deluser.links != null && deluser.links[command.meshid] != null) {
461 + var delmeshrights = deluser.links[command.meshid].rights;
462 + if ((delmeshrights == 0xFFFFFFFF) && (mesh.links[user._id].rights != 0xFFFFFFFF)) return; // A non-admin can't kick out an admin
463 + delete deluser.links[command.meshid];
464 + obj.db.Set(deluser);
465 + obj.parent.parent.DispatchEvent([deluser._id], obj, 'resubscribe');
466 + }
467 +
468 + // Remove user from the mesh
469 + if (mesh.links[command.userid] != null) {
470 + delete mesh.links[command.userid];
471 + obj.db.Set(mesh);
472 + }
473 +
474 + // Notify mesh change
475 + var change = 'Removed user ' + deluser.name + ' from mesh ' + mesh.name;
476 + obj.parent.parent.DispatchEvent(['*', mesh._id, user._id, command.userid], obj, { etype: 'mesh', username: user.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id })
477 + }
478 + break;
479 + }
480 + case 'addamtdevice':
481 + {
482 + if (obj.args.wanonly == true) return; // This is a WAN-only server, local Intel AMT computers can't be added
483 +
484 + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
485 +
486 + // Get the mesh
487 + var mesh = obj.parent.meshes[command.meshid];
488 + if (mesh) {
489 + if (mesh.mtype != 1) return; // This operation is only allowed for mesh type 1, Intel AMT agentless mesh.
490 +
491 + // Check if this user has rights to do this
492 + if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
493 +
494 + // Create a new nodeid
495 + obj.crypto.randomBytes(48, function (err, buf) {
496 + // create the new node
497 + var nodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');;
498 + var device = { type: 'node', mtype: 1, _id: nodeid, meshid: command.meshid, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: parseInt(command.amttls) } };
499 + obj.db.Set(device);
500 +
501 + // Event the new node
502 + var device2 = obj.common.Clone(device);
503 + delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
504 + var change = 'Added device ' + command.devicename + ' to mesh ' + mesh.name;
505 + obj.parent.parent.DispatchEvent(['*', command.meshid], obj, { etype: 'node', username: user.name, action: 'addnode', node: device2, msg: change, domain: domain.id })
506 + });
507 + }
508 + break;
509 + }
510 + case 'scanamtdevice':
511 + {
512 + if (obj.args.wanonly == true) return; // This is a WAN-only server, this type of scanning is not allowed.
513 +
514 + // Ask the RMCP scanning to scan a range of IP addresses
515 + if (obj.parent.parent.amtScanner) {
516 + if (obj.parent.parent.amtScanner.performRangeScan(ws.userid, command.range) == false) {
517 + obj.parent.parent.DispatchEvent(['*', ws.userid], obj, { action: 'scanamtdevice', range: command.range, results: null, nolog: 1 });
518 + }
519 + }
520 + break;
521 + }
522 + case 'removedevices':
523 + {
524 + for (var i in command.nodeids) {
525 + var nodeid = command.nodeids[i];
526 + if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
527 +
528 + // Get the device
529 + obj.db.Get(nodeid, function (err, nodes) {
530 + if (nodes.length != 1) return;
531 + var node = nodes[0];
532 +
533 + // Get the mesh for this device
534 + var mesh = obj.parent.meshes[node.meshid];
535 + if (mesh) {
536 + // Check if this user has rights to do this
537 + if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
538 +
539 + // Delete this node including network interface information and events
540 + obj.db.Remove(node._id);
541 + obj.db.Remove('if' + node._id);
542 +
543 + // Event node deletion
544 + var change = 'Removed device ' + node.name + ' from mesh ' + mesh.name;
545 + obj.parent.parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', username: user.name, action: 'removenode', nodeid: node._id, msg: change, domain: domain.id })
546 +
547 + // Disconnect all connections if needed
548 + var state = obj.parent.parent.GetConnectivityState(command.nodeid);
549 + if ((state != null) && (state.connectivity != null)) {
550 + if ((state.connectivity & 1) != 0) { obj.parent.wsagents[command.nodeid].close(); } // Disconnect mesh agent
551 + if ((state.connectivity & 2) != 0) { obj.parent.parent.mpsserver.close(obj.parent.parent.mpsserver.ciraConnections[command.nodeid]); } // Disconnect CIRA connection
552 + }
553 + }
554 + });
555 + }
556 +
557 + break;
558 + }
559 + case 'wakedevices':
560 + {
561 + // TODO: INPUT VALIDATION!!!
562 + // TODO: We can optimize this a lot.
563 + // - We should get a full list of all MAC's to wake first.
564 + // - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan.
565 + for (var i in command.nodeids) {
566 + var nodeid = command.nodeids[i], wakeActions = 0;
567 + if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain
568 + // Get the device
569 + obj.db.Get(nodeid, function (err, nodes) {
570 + if (nodes.length != 1) return;
571 + var node = nodes[0];
572 +
573 + // Get the mesh for this device
574 + var mesh = obj.parent.meshes[node.meshid];
575 + if (mesh) {
576 +
577 + // Check if this user has rights to do this
578 + if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 64) != 0)) {
579 +
580 + // Get the device interface information
581 + obj.db.Get('if' + node._id, function (err, nodeifs) {
582 + if (nodeifs.length == 1) {
583 + var nodeif = nodeifs[0];
584 + var macs = [];
585 + for (var i in nodeif.netif) { if (nodeif.netif[i].mac) { macs.push(nodeif.netif[i].mac); } }
586 +
587 + // Have the server send a wake-on-lan packet (Will not work in WAN-only)
588 + if (obj.parent.parent.meshScanner != null) { obj.parent.parent.meshScanner.wakeOnLan(macs); wakeActions++; }
589 +
590 + // Get the list of mesh this user as access to
591 + var targetMeshes = [];
592 + for (var i in user.links) { targetMeshes.push(i); }
593 +
594 + // Go thru all the connected agents and send wake-on-lan on all the ones in the target mesh list
595 + for (var i in obj.parent.wsagents) {
596 + var agent = obj.parent.wsagents[i];
597 + if ((targetMeshes.indexOf(agent.dbMeshKey) >= 0) && (agent.authenticated == 2)) {
598 + //console.log('Asking agent ' + agent.dbNodeKey + ' to wake ' + macs.join(','));
599 + agent.send(JSON.stringify({ action: 'wakeonlan', macs: macs }));
600 + wakeActions++;
601 + }
602 + }
603 + }
604 + });
605 +
606 + }
607 + }
608 + });
609 + }
610 + // Confirm we may be doing something (TODO)
611 + ws.send(JSON.stringify({ action: 'wakedevices' }));
612 + }
613 +
614 + break;
615 + }
616 + case 'poweraction':
617 + {
618 + // TODO: INPUT VALIDATION!!!
619 + for (var i in command.nodeids) {
620 + var nodeid = command.nodeids[i], powerActions = 0;
621 + if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain
622 + // Get the device
623 + obj.db.Get(nodeid, function (err, nodes) {
624 + if (nodes.length != 1) return;
625 + var node = nodes[0];
626 +
627 + // Get the mesh for this device
628 + var mesh = obj.parent.meshes[node.meshid];
629 + if (mesh) {
630 +
631 + // Check if this user has rights to do this
632 + if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 8) != 0)) { // "Remote Control permission"
633 +
634 + // Get this device
635 + var agent = obj.parent.wsagents[node._id];
636 + if (agent != null) {
637 + // Send the power command
638 + agent.send(JSON.stringify({ action: 'poweraction', actiontype: command.actiontype }));
639 + powerActions++;
640 + }
641 + }
642 + }
643 + });
644 + }
645 + // Confirm we may be doing something (TODO)
646 + ws.send(JSON.stringify({ action: 'poweraction' }));
647 + }
648 + break;
649 + }
650 + case 'getnetworkinfo':
651 + {
652 + // Argument validation
653 + if ((command.nodeid == null) || (typeof command.nodeid != 'string') || (command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
654 +
655 + // Get the device
656 + obj.db.Get(command.nodeid, function (err, nodes) {
657 + if (nodes.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
658 + var node = nodes[0];
659 +
660 + // Get the mesh for this device
661 + var mesh = obj.parent.meshes[node.meshid];
662 + if (mesh) {
663 + // Check if this user has rights to do this
664 + if (mesh.links[user._id] == null || (mesh.links[user._id].rights == 0)) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
665 +
666 + // Get network information about this node
667 + obj.db.Get('if' + command.nodeid, function (err, netinfos) {
668 + if (netinfos.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
669 + var netinfo = netinfos[0];
670 + ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, updateTime: netinfo.updateTime, netif: netinfo.netif }));
671 + });
672 + }
673 + });
674 + break;
675 + }
676 + case 'changedevice':
677 + {
678 + // Argument validation
679 + if ((command.nodeid == null) || (typeof command.nodeid != 'string') || (command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
680 + if ((command.userloc) && (command.userloc.length != 2) && (command.userloc.length != 0)) return;
681 +
682 + // Change the device
683 + obj.db.Get(command.nodeid, function (err, nodes) {
684 + if (nodes.length != 1) return;
685 + var node = nodes[0];
686 +
687 + // Get the mesh for this device
688 + var mesh = obj.parent.meshes[node.meshid];
689 + if (mesh) {
690 + // Check if this user has rights to do this
691 + if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
692 +
693 + // Ready the node change event
694 + var changes = [], change = 0, event = { etype: 'node', username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id };
695 + event.msg = ": ";
696 +
697 + // Look for a change
698 + if (command.icon && (command.icon != node.icon)) { change = 1; node.icon = command.icon; changes.push('icon'); }
699 + if (command.name && (command.name != node.name)) { change = 1; node.name = command.name; changes.push('name'); }
700 + if (command.host && (command.host != node.host)) { change = 1; node.host = command.host; changes.push('host'); }
701 + if (command.userloc && ((node.userloc == null) || (command.userloc[0] != node.userloc[0]) || (command.userloc[1] != node.userloc[1]))) {
702 + change = 1;
703 + if ((command.userloc.length == 0) && (node.userloc)) {
704 + delete node.userloc;
705 + changes.push('location removed');
706 + } else {
707 + command.userloc.push((Math.floor((new Date()) / 1000)));
708 + node.userloc = command.userloc.join(',');
709 + changes.push('location');
710 + }
711 + }
712 + if (command.desc != null && (command.desc != node.desc)) { change = 1; node.desc = command.desc; changes.push('description'); }
713 + if (command.intelamt != null) {
714 + if ((command.intelamt.user != null) && (command.intelamt.pass != undefined) && ((command.intelamt.user != node.intelamt.user) || (command.intelamt.pass != node.intelamt.pass))) { change = 1; node.intelamt.user = command.intelamt.user; node.intelamt.pass = command.intelamt.pass; changes.push('Intel AMT credentials'); }
715 + if (command.intelamt.tls && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); }
716 + }
717 +
718 + if (change == 1) {
719 + // Save the node
720 + obj.db.Set(node);
721 +
722 + // Event the node change
723 + event.msg = 'Changed device ' + node.name + ' from mesh ' + mesh.name + ': ' + changes.join(', ');
724 + var node2 = obj.common.Clone(node);
725 + if (node2.intelamt && node2.intelamt.pass) delete node2.intelamt.pass; // Remove the Intel AMT password before eventing this.
726 + event.node = node2;
727 + obj.parent.parent.DispatchEvent(['*', node.meshid], obj, event);
728 + }
729 + }
730 + });
731 + break;
732 + }
733 + case 'uploadagentcore':
734 + {
735 + if (user.siteadmin != 0xFFFFFFFF) break;
736 + if (command.path) {
737 + if (command.path == '*') {
738 + // Update the server default core and send a core hash request
739 + // Load default mesh agent core if present, then perform a core update
740 + obj.parent.parent.updateMeshCore(function () { obj.parent.sendMeshAgentCore(user, domain, command.nodeid, '*'); });
741 + } else {
742 + // Send a mesh agent core to the mesh agent
743 + var file = obj.parent.getServerFilePath(user, domain, command.path);
744 + if (file != null) {
745 + obj.parent.readEntireTextFile(file.fullpath, function (data) {
746 + if (data != null) {
747 + data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
748 + obj.parent.sendMeshAgentCore(user, domain, command.nodeid, data);
749 + }
750 + })
751 + }
752 + }
753 + } else {
754 + // Clear the mesh agent core on the mesh agent
755 + obj.parent.sendMeshAgentCore(user, domain, command.nodeid, null);
756 + }
757 + break;
758 + }
759 + case 'agentdisconnect':
760 + {
761 + // Force mesh agent disconnection
762 + forceMeshAgentDisconnect(user, domain, command.nodeid, command.disconnectMode);
763 + break;
764 + }
765 + case 'close':
766 + {
767 + // Close the web socket session
768 + if (obj.req.session && obj.req.session.ws && obj.req.session.ws == ws) delete obj.req.session.ws;
769 + try { ws.close(); } catch (e) { }
770 + break;
771 + }
772 + case 'getcookie':
773 + {
774 + // Check if this user has rights on this nodeid
775 + obj.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 (???)
776 + if (nodes.length == 1) {
777 + var meshlinks = user.links[nodes[0].meshid];
778 + if ((meshlinks) && (meshlinks.rights) && (meshlinks.rights & obj.parent.MESHRIGHT_REMOTECONTROL != 0)) {
779 + // Add a user authentication cookie to a url
780 + var cookieContent = { userid: user._id, domainid: user.domain };
781 + if (command.nodeid) { cookieContent.nodeid = command.nodeid; }
782 + if (command.tcpaddr) { cookieContent.tcpaddr = command.tcpaddr; } // Indicates the browser want to agent to TCP connect to a remote address
783 + if (command.tcpport) { cookieContent.tcpport = command.tcpport; } // Indicates the browser want to agent to TCP connect to a remote port
784 + command.cookie = obj.parent.encodeCookie(cookieContent);
785 + ws.send(JSON.stringify(command));
786 + }
787 + }
788 + });
789 + break;
790 + }
791 + }
792 + });
793 +
794 + // If error, do nothing
795 + ws.on('error', function (err) { console.log(err); });
796 +
797 + // If the web socket is closed
798 + ws.on('close', function (req) {
799 + obj.parent.parent.RemoveAllEventDispatch(ws);
800 + if (req.session && req.session.ws && req.session.ws == ws) { delete req.session.ws; }
801 + if (obj.parent.wssessions2[ws.sessionId]) { delete obj.parent.wssessions2[ws.sessionId]; }
802 + if (obj.parent.wssessions[ws.userid]) {
803 + var i = obj.parent.wssessions[ws.userid].indexOf(ws);
804 + if (i >= 0) {
805 + obj.parent.wssessions[ws.userid].splice(i, 1);
806 + var user = obj.parent.users[ws.userid];
807 + if (user) {
808 + if (obj.parent.parent.multiServer == null) {
809 + obj.parent.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.parent.wssessions[ws.userid].length, nolog: 1, domain: obj.domain.id })
810 + } else {
811 + obj.parent.recountSessions(ws.sessionId); // Recount sessions
812 + }
813 + }
814 + if (obj.parent.wssessions[ws.userid].length == 0) { delete obj.parent.wssessions[ws.userid]; }
815 + }
816 + }
817 +
818 + // If we have peer servers, inform them of the disconnected session
819 + if (obj.parent.parent.multiServer != null) { obj.parent.parent.multiServer.DispatchMessage({ action: 'sessionEnd', sessionid: ws.sessionId }); }
820 + });
821 +
822 + // Send user information to web socket, this is the first thing we send
823 + var userinfo = obj.common.Clone(obj.parent.users[req.session.userid]);
824 + delete userinfo.salt;
825 + delete userinfo.hash;
826 + ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo }));
827 +
828 + // Next, send server information
829 + if (obj.args.notls == true) {
830 + ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: { name: obj.parent.certificates.CommonName, mpsport: obj.args.mpsport, mpspass: obj.args.mpspass, port: obj.args.port, https: false } }));
831 + } else {
832 + ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: { name: obj.parent.certificates.CommonName, mpsport: obj.args.mpsport, mpspass: obj.args.mpspass, redirport: obj.args.redirport, port: obj.args.port, https: true } }));
833 + }
834 + } catch (e) { console.log(e); }
835 +
836 + // Return the maximum number of bytes allowed in the user account "My Files".
837 + function getQuota(objid, domain) {
838 + if (objid == null) return 0;
839 + if (objid.startsWith('user/')) {
840 + var user = obj.parent.users[objid];
841 + if (user == null) return 0;
842 + if ((user.quota != null) && (typeof user.quota == 'number')) { return user.quota; }
843 + if ((domain != null) && (domain.userQuota != null) && (typeof domain.userQuota == 'number')) { return domain.userQuota; }
844 + return 1048576; // By default, the server will have a 1 meg limit on user accounts
845 + } else if (objid.startsWith('mesh/')) {
846 + var mesh = obj.parent.meshes[objid];
847 + if (mesh == null) return 0;
848 + if ((mesh.quota != null) && (typeof mesh.quota == 'number')) { return mesh.quota; }
849 + if ((domain != null) && (domain.meshQuota != null) && (typeof domain.meshQuota == 'number')) { return domain.meshQuota; }
850 + return 1048576; // By default, the server will have a 1 meg limit on mesh accounts
851 + }
852 + return 0;
853 + }
854 +
855 + // Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
856 + function getServerFilePath(user, domain, path) {
857 + var splitpath = path.split('/'), serverpath = obj.path.join(obj.filespath, 'domain'), filename = '';
858 + if ((splitpath.length < 3) || (splitpath[0] != 'user' && splitpath[0] != 'mesh') || (splitpath[1] != domain.id)) return null; // Basic validation
859 + var objid = splitpath[0] + '/' + splitpath[1] + '/' + splitpath[2];
860 + if (splitpath[0] == 'user' && (objid != user._id)) return null; // User validation, only self allowed
861 + if (splitpath[0] == 'mesh') { var link = user.links[objid]; if ((link == null) || (link.rights == null) || ((link.rights & 32) == 0)) { return null; } } // Check mesh server file rights
862 + if (splitpath[1] != '') { serverpath += '-' + splitpath[1]; } // Add the domain if needed
863 + serverpath += ('/' + splitpath[0] + '-' + splitpath[2]);
864 + for (var i = 3; i < splitpath.length; i++) { if (obj.common.IsFilenameValid(splitpath[i]) == true) { serverpath += '/' + splitpath[i]; filename = splitpath[i]; } else { return null; } } // Check that each folder is correct
865 + var fullpath = obj.path.resolve(obj.filespath, serverpath), quota = 0;
866 + return { fullpath: fullpath, path: serverpath, name: filename, quota: getQuota(objid, domain) };
867 + }
868 +
869 + // Read the folder and all sub-folders and serialize that into json.
870 + function readFilesRec(path) {
871 + var r = {}, dir = obj.fs.readdirSync(path);
872 + for (var i in dir) {
873 + var f = { t: 3, d: 111 };
874 + var stat = obj.fs.statSync(path + '/' + dir[i])
875 + if ((stat.mode & 0x004000) == 0) { f.s = stat.size; f.d = stat.mtime.getTime(); } else { f.t = 2; f.f = readFilesRec(path + '/' + dir[i]); }
876 + r[dir[i]] = f;
877 + }
878 + return r;
879 + }
880 +
881 + function updateUserFiles(user, ws, domain) {
882 + // Request the list of server files
883 + var files = { action: 'files', filetree: { n: 'Root', f: {} } };
884 +
885 + // Add user files
886 + files.filetree.f[user._id] = { t: 1, n: 'My Files', f: {} };
887 + files.filetree.f[user._id].maxbytes = getQuota(user._id, domain);
888 + var usersplit = user._id.split('/'), domainx = 'domain';
889 + if (usersplit[1].length > 0) domainx = 'domain-' + usersplit[1];
890 +
891 + // Read all files recursively
892 + try {
893 + files.filetree.f[user._id].f = readFilesRec(obj.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2]));
894 + } catch (e) {
895 + // Got an error, try to create all the folders and try again...
896 + try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
897 + try { obj.fs.mkdirSync(obj.path.join(obj.parent.filespath, domainx)); } catch (e) { }
898 + try { obj.fs.mkdirSync(obj.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2])); } catch (e) { }
899 + try { obj.fs.mkdirSync(obj.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2] + "/Public")); } catch (e) { }
900 + try { files.filetree.f[user._id].f = readFilesRec(obj.parent.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2])); } catch (e) { }
901 + }
902 +
903 + // Add files for each mesh
904 + for (var i in user.links) {
905 + if ((user.links[i].rights & 32) != 0) { // Check that we have file permissions
906 + var mesh = obj.parent.meshes[i];
907 + if (mesh) {
908 + var meshsplit = mesh._id.split('/');
909 + files.filetree.f[mesh._id] = { t: 1, n: mesh.name, f: {} };
910 + files.filetree.f[mesh._id].maxbytes = getQuota(mesh._id, domain);
911 +
912 + // Read all files recursively
913 + try {
914 + files.filetree.f[mesh._id].f = readFilesRec(obj.parent.path.join(__dirname, "files/" + domainx + "/mesh-" + meshsplit[2]));
915 + } catch (e) {
916 + // Got an error, try to create all the folders and try again...
917 + try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
918 + try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (e) { }
919 + try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx + "/mesh-" + meshsplit[2])); } catch (e) { }
920 + try { files.filetree.f[mesh._id].f = readFilesRec(obj.parent.path.join(obj.parent.filespath, domainx + "/mesh-" + meshsplit[2])); } catch (e) { }
921 + }
922 + }
923 + }
924 + }
925 +
926 + // Respond
927 + ws.send(JSON.stringify(files));
928 + }
929 +
930 + return obj;
931 +}
multiserver.js
+3 -33
@@ -127,14 +127,14 @@ module.exports.CreateMultiServer = function (parent, args) {
127 obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
128
129 // Send information about our server to the peer
130 - if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 })); }
130 + if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.parent.webserver.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 })); }
131 //if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
132 break;
133 }
134 case 4: {
135 // Server confirmed authentication, we are allowed to send commands to the server
136 obj.connectionState |= 8;
137 - if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 })); }
137 + if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.parent.webserver.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 })); }
138 //if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
139 break;
140 }
@@ -313,7 +313,7 @@ module.exports.CreateMultiServer = function (parent, args) {
313 function completePeerServerConnection() {
314 if (obj.authenticated != 1) return;
315 obj.send(obj.common.ShortToStr(4));
316 - obj.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 }));
316 + obj.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.parent.webserver.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificateHashBase64 }));
317 obj.authenticated = 2;
318 }
319
@@ -371,9 +371,6 @@ module.exports.CreateMultiServer = function (parent, args) {
371 if (obj.serverid == null) { obj.serverid = require("os").hostname(); }
372 if (obj.parent.config.peers.servers[obj.serverid] == null) { console.log("Error: Unable to peer with other servers, \"" + obj.serverid + "\" not present in peer servers list."); return null; }
373
374 - // Generate a cryptographic key used to encode and decode cookies
375 - obj.generateCookieKey = function () { return new Buffer(obj.crypto.randomBytes(32), 'binary'); }
376 -
374 // Return the private key of a peer server
375 obj.getServerCookieKey = function (serverid) {
376 var server = obj.peerServers[serverid];
@@ -381,32 +378,6 @@ module.exports.CreateMultiServer = function (parent, args) {
378 return null;
379 }
380
384 - // Encode an object as a cookie using a key. (key must be 32 bytes long)
385 - obj.encodeCookie = function (o, key) {
386 - try {
387 - if (key == null) { key = obj.serverKey; }
388 - o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time
389 - var iv = new Buffer(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key, iv);
390 - var crypted = Buffer.concat([cipher.update(JSON.stringify(o), 'utf8'), cipher.final()]);
391 - return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
392 - } catch (e) { return null; }
393 - }
394 -
395 - // Decode a cookie back into an object using a key. Return null if it's not a valid cookie. (key must be 32 bytes long)
396 - obj.decodeCookie = function (cookie, key) {
397 - try {
398 - if (key == null) { key = obj.serverKey; }
399 - cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64');
400 - var decipher = obj.crypto.createDecipheriv('aes-256-gcm', key, cookie.slice(0, 12));
401 - decipher.setAuthTag(cookie.slice(12, 16));
402 - var o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
403 - if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { return null; }
404 - o.time = o.time * 1000; // Decode the cookie creation time
405 - o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created
406 - return o;
407 - } catch (e) { return null; }
408 - }
409 -
381 // Dispatch an event to all other MeshCentral2 peer servers
382 obj.DispatchEvent = function (ids, source, event) {
383 var busmsg = JSON.stringify({ action: 'bus', ids: ids, event: event });
@@ -658,7 +629,6 @@ module.exports.CreateMultiServer = function (parent, args) {
629 return peerTunnel;
630 }
631
661 - obj.serverKey = obj.generateCookieKey();
632 setTimeout(function () { obj.ConnectToPeers(); }, 1000); // Delay this a little to make sure we are ready on our side.
633 return obj;
634 }
public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_7/MeshMiniRouter.application new
+21
@@ -0,0 +1,21 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<asmv1:assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns="urn:schemas-microsoft-com:asm.v2" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xrml="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:co.v1="urn:schemas-microsoft-com:clickonce.v1" xmlns:co.v2="urn:schemas-microsoft-com:clickonce.v2">
3 + <assemblyIdentity name="MeshMiniRouter.application" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" />
4 + <description asmv2:publisher="Meshcentral.com" asmv2:product="Meshcentral Mini-Router" asmv2:supportUrl="https://meshcentral.com/" xmlns="urn:schemas-microsoft-com:asm.v1" />
5 + <deployment install="false" mapFileExtensions="true" trustURLParameters="true" />
6 + <compatibleFrameworks xmlns="urn:schemas-microsoft-com:clickonce.v2">
7 + <framework targetVersion="4.5" profile="Full" supportedRuntime="4.0.30319" />
8 + </compatibleFrameworks>
9 + <dependency>
10 + <dependentAssembly dependencyType="install" codebase="Application Files\MeshMiniRouter_2_0_0_7\MeshMiniRouter.exe.manifest" size="4711">
11 + <assemblyIdentity name="MeshMiniRouter.exe" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" type="win32" />
12 + <hash>
13 + <dsig:Transforms>
14 + <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
15 + </dsig:Transforms>
16 + <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
17 + <dsig:DigestValue>VsPkLYR71TSqI8oGBBeXzb5hGi4R2xtQaGQJ0zmsTic=</dsig:DigestValue>
18 + </hash>
19 + </dependentAssembly>
20 + </dependency>
21 +</asmv1:assembly>
\ No newline at end of file
public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_7/MeshMiniRouter.exe.config.deploy new
+42
@@ -0,0 +1,42 @@
1 +<?xml version="1.0"?>
2 +<configuration>
3 + <configSections>
4 + <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
5 + <section name="MeshMiniRouterTool.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
6 + <section name="MeshMiniRouterTool.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
7 + <section name="MeshNetworkRouterTool.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
8 + </sectionGroup>
9 + </configSections>
10 + <userSettings>
11 + <MeshMiniRouterTool.Properties.Settings>
12 + <setting name="PuttyPath" serializeAs="String">
13 + <value/>
14 + </setting>
15 + <setting name="Upgraded" serializeAs="String">
16 + <value>False</value>
17 + </setting>
18 + <setting name="UltraVncPath" serializeAs="String">
19 + <value/>
20 + </setting>
21 + <setting name="HybridCloudManager" serializeAs="String">
22 + <value/>
23 + </setting>
24 + <setting name="TightVncPath" serializeAs="String">
25 + <value/>
26 + </setting>
27 + <setting name="WinSCPPath" serializeAs="String">
28 + <value/>
29 + </setting>
30 + </MeshMiniRouterTool.Properties.Settings>
31 + <MeshMiniRouterTool.Settings1>
32 + <setting name="puttypath" serializeAs="String">
33 + <value/>
34 + </setting>
35 + </MeshMiniRouterTool.Settings1>
36 + <MeshNetworkRouterTool.Properties.Settings>
37 + <setting name="Server" serializeAs="String">
38 + <value/>
39 + </setting>
40 + </MeshNetworkRouterTool.Properties.Settings>
41 + </userSettings>
42 +<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_7/MeshMiniRouter.exe.deploy
Binary files /dev/null and b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_7/MeshMiniRouter.exe.deploy differ
public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_7/MeshMiniRouter.exe.manifest new
+82
@@ -0,0 +1,82 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<asmv1:assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns="urn:schemas-microsoft-com:asm.v2" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:co.v1="urn:schemas-microsoft-com:clickonce.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:co.v2="urn:schemas-microsoft-com:clickonce.v2">
3 + <asmv1:assemblyIdentity name="MeshMiniRouter.exe" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" type="win32" />
4 + <description asmv2:iconFile="MeshMiniRouter.ico" xmlns="urn:schemas-microsoft-com:asm.v1" />
5 + <application />
6 + <entryPoint>
7 + <assemblyIdentity name="MeshMiniRouter" version="1.0.6505.20092" language="neutral" processorArchitecture="msil" />
8 + <commandLine file="MeshMiniRouter.exe" parameters="" />
9 + </entryPoint>
10 + <trustInfo>
11 + <security>
12 + <applicationRequestMinimum>
13 + <PermissionSet version="1" class="System.Security.NamedPermissionSet" Name="Internet" Description="Default rights given to Internet applications" Unrestricted="true" ID="Custom" SameSite="site" />
14 + <defaultAssemblyRequest permissionSetReference="Custom" />
15 + </applicationRequestMinimum>
16 + <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
17 + <!-- UAC Manifest Options
18 + If you want to change the Windows User Account Control level replace the
19 + requestedExecutionLevel node with one of the following.
20 +
21 + <requestedExecutionLevel level="asInvoker" uiAccess="false" />
22 + <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
23 + <requestedExecutionLevel level="highestAvailable" uiAccess="false" />
24 +
25 + Specifying requestedExecutionLevel node will disable file and registry virtualization.
26 + If you want to utilize File and Registry Virtualization for backward
27 + compatibility then delete the requestedExecutionLevel node.
28 + -->
29 + <requestedExecutionLevel level="asInvoker" uiAccess="false" />
30 + </requestedPrivileges>
31 + </security>
32 + </trustInfo>
33 + <dependency>
34 + <dependentOS>
35 + <osVersionInfo>
36 + <os majorVersion="5" minorVersion="1" buildNumber="2600" servicePackMajor="0" />
37 + </osVersionInfo>
38 + </dependentOS>
39 + </dependency>
40 + <dependency>
41 + <dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
42 + <assemblyIdentity name="Microsoft.Windows.CommonLanguageRuntime" version="4.0.30319.0" />
43 + </dependentAssembly>
44 + </dependency>
45 + <dependency>
46 + <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="MeshMiniRouter.exe" size="193536">
47 + <assemblyIdentity name="MeshMiniRouter" version="1.0.6505.20092" language="neutral" processorArchitecture="msil" />
48 + <hash>
49 + <dsig:Transforms>
50 + <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
51 + </dsig:Transforms>
52 + <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
53 + <dsig:DigestValue>G2b79xma2HCz/aTfnkt2L3I8c7mPbDDlU8IsdlFiDg8=</dsig:DigestValue>
54 + </hash>
55 + </dependentAssembly>
56 + </dependency>
57 + <file name="MeshMiniRouter.exe.config" size="2397">
58 + <hash>
59 + <dsig:Transforms>
60 + <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
61 + </dsig:Transforms>
62 + <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
63 + <dsig:DigestValue>UylUFD4/o0KBti+5/eodTDgq4BkUJD0J0aUXNd3yHbY=</dsig:DigestValue>
64 + </hash>
65 + </file>
66 + <file name="MeshMiniRouter.ico" size="1078">
67 + <hash>
68 + <dsig:Transforms>
69 + <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
70 + </dsig:Transforms>
71 + <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
72 + <dsig:DigestValue>mkjbDQuo7YXa8wZxdKEu/ECXrORwwtpRgNj8NBKbzHo=</dsig:DigestValue>
73 + </hash>
74 + </file>
75 + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
76 + <application>
77 + <!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->
78 + <!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
79 + <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
80 + </application>
81 + </compatibility>
82 +</asmv1:assembly>
\ No newline at end of file
public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_7/MeshMiniRouter.ico.deploy
Binary files /dev/null and b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_7/MeshMiniRouter.ico.deploy differ
public/clickonce/minirouter/MeshMiniRouter.application new
+21
@@ -0,0 +1,21 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<asmv1:assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns="urn:schemas-microsoft-com:asm.v2" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xrml="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:co.v1="urn:schemas-microsoft-com:clickonce.v1" xmlns:co.v2="urn:schemas-microsoft-com:clickonce.v2">
3 + <assemblyIdentity name="MeshMiniRouter.application" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" />
4 + <description asmv2:publisher="Meshcentral.com" asmv2:product="Meshcentral Mini-Router" asmv2:supportUrl="https://meshcentral.com/" xmlns="urn:schemas-microsoft-com:asm.v1" />
5 + <deployment install="false" mapFileExtensions="true" trustURLParameters="true" />
6 + <compatibleFrameworks xmlns="urn:schemas-microsoft-com:clickonce.v2">
7 + <framework targetVersion="4.5" profile="Full" supportedRuntime="4.0.30319" />
8 + </compatibleFrameworks>
9 + <dependency>
10 + <dependentAssembly dependencyType="install" codebase="Application Files\MeshMiniRouter_2_0_0_7\MeshMiniRouter.exe.manifest" size="4711">
11 + <assemblyIdentity name="MeshMiniRouter.exe" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" type="win32" />
12 + <hash>
13 + <dsig:Transforms>
14 + <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
15 + </dsig:Transforms>
16 + <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
17 + <dsig:DigestValue>VsPkLYR71TSqI8oGBBeXzb5hGi4R2xtQaGQJ0zmsTic=</dsig:DigestValue>
18 + </hash>
19 + </dependentAssembly>
20 + </dependency>
21 +</asmv1:assembly>
\ No newline at end of file
public/clickonce/minirouter/publish.htm new
+90
@@ -0,0 +1,90 @@
1 +<HTML>
2 + <HEAD>
3 + <TITLE>Meshcentral Mini-Router</TITLE>
4 + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8" />
5 + <STYLE TYPE="text/css">
6 +<!--
7 +BODY{margin-top:20px; margin-left:20px; margin-right:20px; color:#000000; font-family:Tahoma; background-color:white}
8 +A:link {font-weight:normal; color:#000066; text-decoration:none}
9 +A:visited {font-weight:normal; color:#000066; text-decoration:none}
10 +A:active {font-weight:normal; text-decoration:none}
11 +A:hover {font-weight:normal; color:#FF6600; text-decoration:none}
12 +P {margin-top:0px; margin-bottom:12px; color:#000000; font-family:Tahoma}
13 +PRE {border-right:#f0f0e0 1px solid; padding-right:5px; border-top:#f0f0e0 1px solid; margin-top:-5px; padding-left:5px; font-size:x-small; padding-bottom:5px; border-left:#f0f0e0 1px solid; padding-top:5px; border-bottom:#f0f0e0 1px solid; font-family:Courier New; background-color:#e5e5cc}
14 +TD {font-size:12px; color:#000000; font-family:Tahoma}
15 +H2 {border-top: #003366 1px solid; margin-top:25px; font-weight:bold; font-size:1.5em; margin-bottom:10px; margin-left:-15px; color:#003366}
16 +H3 {margin-top:10px; font-size: 1.1em; margin-bottom: 10px; margin-left: -15px; color: #000000}
17 +UL {margin-top:10px; margin-left:20px}
18 +OL {margin-top:10px; margin-left:20px}
19 +LI {margin-top:10px; color: #000000}
20 +FONT.value {font-weight:bold; color:darkblue}
21 +FONT.key {font-weight: bold; color: darkgreen}
22 +.divTag {border:1px; border-style:solid; background-color:#FFFFFF; text-decoration:none; height:auto; width:auto; background-color:#cecece}
23 +.BannerColumn {background-color:#000000}
24 +.Banner {border:0; padding:0; height:8px; margin-top:0px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#1c5280',EndColorStr='#FFFFFF');}
25 +.BannerTextCompany {font:bold; font-size:18pt; color:#cecece; font-family:Tahoma; height:0px; margin-top:0; margin-left:8px; margin-bottom:0; padding:0px; white-space:nowrap; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');}
26 +.BannerTextApplication {font:bold; font-size:18pt; font-family:Tahoma; height:0px; margin-top:0; margin-left:8px; margin-bottom:0; padding:0px; white-space:nowrap; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');}
27 +.BannerText {font:bold; font-size:18pt; font-family:Tahoma; height:0px; margin-top:0; margin-left:8px; margin-bottom:0; padding:0px; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');}
28 +.BannerSubhead {border:0; padding:0; height:16px; margin-top:0px; margin-left:10px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#4B3E1A',EndColorStr='#FFFFFF');}
29 +.BannerSubheadText {font:bold; height:11px; font-size:11px; font-family:Tahoma; margin-top:1; margin-left:10; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');}
30 +.FooterRule {border:0; padding:0; height:1px; margin:0px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#4B3E1A',EndColorStr='#FFFFFF');}
31 +.FooterText {font-size:11px; font-weight:normal; text-decoration:none; font-family:Tahoma; margin-top:10; margin-left:0px; margin-bottom:2; padding:0px; color:#999999; white-space:nowrap}
32 +.FooterText A:link {font-weight:normal; color:#999999; text-decoration:underline}
33 +.FooterText A:visited {font-weight:normal; color:#999999; text-decoration:underline}
34 +.FooterText A:active {font-weight:normal; color:#999999; text-decoration:underline}
35 +.FooterText A:hover {font-weight:normal; color:#FF6600; text-decoration:underline}
36 +.ClickOnceInfoText {font-size:11px; font-weight:normal; text-decoration:none; font-family:Tahoma; margin-top:0; margin-right:2px; margin-bottom:0; padding:0px; color:#000000}
37 +.InstallTextStyle {font:bold; font-size:14pt; font-family:Tahoma; a:#FF0000; text-decoration:None}
38 +.DetailsStyle {margin-left:30px}
39 +.ItemStyle {margin-left:-15px; font-weight:bold}
40 +.StartColorStr {background-color:#4B3E1A}
41 +.JustThisApp A:link {font-weight:normal; color:#000066; text-decoration:underline}
42 +.JustThisApp A:visited {font-weight:normal; color:#000066; text-decoration:underline}
43 +.JustThisApp A:active {font-weight:normal; text-decoration:underline}
44 +.JustThisApp A:hover {font-weight:normal; color:#FF6600; text-decoration:underline}
45 +-->
46 +
47 +</STYLE>
48 +
49 +</HEAD>
50 + <BODY>
51 + <TABLE WIDTH="100%" CELLPADDING="0" CELLSPACING="2" BORDER="0">
52 +
53 +<!-- Begin Banner -->
54 +<TR><TD><TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0" BGCOLOR="#cecece" WIDTH="100%"><TR><TD><TABLE BGCOLOR="#1c5280" WIDTH="100%" CELLPADDING="0" CELLSPACING="0" BORDER="0"><TR><TD CLASS="Banner" /></TR><TR><TD CLASS="Banner"><SPAN CLASS="BannerTextCompany">Meshcentral.com</SPAN></TD></TR><TR><TD CLASS="Banner"><SPAN CLASS="BannerTextApplication">Meshcentral Mini-Router</SPAN></TD></TR><TR><TD CLASS="Banner" ALIGN="RIGHT" /></TR></TABLE></TD></TR></TABLE></TD></TR>
55 +<!-- End Banner -->
56 +
57 +
58 +<!-- Begin Dialog -->
59 +<TR><TD ALIGN="LEFT"><TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0" WIDTH="540"><TR><TD WIDTH="496">
60 +
61 +<!-- Begin AppInfo -->
62 +<TABLE><TR><TD COLSPAN="3">&nbsp;</TD></TR><TR><TD><B>Name:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>Meshcentral Mini-Router</TD></TR><TR><TD COLSPAN="3">&nbsp;</TD></TR><TR><TD><B>Version:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>2.0.0.7</TD></TR><TR><TD COLSPAN="3">&nbsp;</TD></TR><TR><TD><B>Publisher:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>Meshcentral.com</TD></TR><tr><td colspan="3">&nbsp;</td></tr></TABLE>
63 +<!-- End AppInfo -->
64 +
65 +
66 +</TD></TR></TABLE>
67 +<!-- Begin Buttons -->
68 +<TR><TD ALIGN="LEFT"><TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0" WIDTH="540" STYLE="cursor:hand" ONCLICK="window.navigate(InstallButton.href)"><TR><TD ALIGN="LEFT"><TABLE CELLPADDING="1" BGCOLOR="#333333" CELLSPACING="0" BORDER="0"><TR><TD><TABLE CELLPADDING="1" BGCOLOR="#cecece" CELLSPACING="0" BORDER="0"><TR><TD><TABLE CELLPADDING="1" BGCOLOR="#efefef" CELLSPACING="0" BORDER="0"><TR><TD WIDTH="20"><SPACER TYPE="block" WIDTH="20" HEIGHT="1" /></TD><TD><A ID="InstallButton" HREF="MeshMiniRouter.application">Run</A></TD><TD width="20"><SPACER TYPE="block" WIDTH="20" HEIGHT="1" /></TD></TR></TABLE></TD></TR></TABLE></TD></TR></TABLE></TD><TD WIDTH="15%" ALIGN="right" /></TR></TABLE></TD></TR>
69 +<!-- End Buttons -->
70 +</TD></TR>
71 +<!-- End Dialog -->
72 +
73 +
74 +
75 +<!-- Spacer Row -->
76 +<TR><TD>&nbsp;</TD></TR>
77 +
78 +<TR><TD>
79 +<!-- Begin Footer -->
80 +<TABLE WIDTH="100%" CELLPADDING="0" CELLSPACING="0" BORDER="0" BGCOLOR="#ffffff"><TR><TD HEIGHT="5"><SPACER TYPE="block" HEIGHT="5" /></TD></TR><TR><TD CLASS="FooterText" ALIGN="center">
81 +<A HREF="https://meshcentral.com">Meshcentral.com Customer Support</A>
82 +&nbsp;&nbsp;&nbsp;::&nbsp;&nbsp;&nbsp;
83 +<A HREF="http://go.microsoft.com/fwlink/?LinkId=154571">ClickOnce and .NET Framework Resources</A>
84 +</TD></TR><TR><TD HEIGHT="5"><SPACER TYPE="block" HEIGHT="5" /></TD></TR><TR><TD HEIGHT="1" bgcolor="#cecece"><SPACER TYPE="block" HEIGHT="1" /></TD></TR></TABLE>
85 +<!-- End Footer -->
86 +</TD></TR>
87 +
88 +</TABLE>
89 + </BODY>
90 +</HTML>
\ No newline at end of file
redirserver.js
+3
@@ -60,6 +60,9 @@ module.exports.CreateRedirServer = function (parent, db, args, certificates) {
60 obj.app.post(url + 'amtevents.ashx', obj.parent.webserver.handleAmtEventRequest);
61 obj.app.get(url + 'meshsettings', obj.parent.webserver.handleMeshSettingsRequest);
62 obj.app.get(url + 'meshagents', obj.parent.webserver.handleMeshAgentRequest);
63 +
64 + // Indicates the clickonce folder is public
65 + obj.app.use(url + 'clickonce', obj.express.static(obj.parent.path.join(__dirname, 'public/clickonce')));
66 }
67
68 // Find a free port starting with the specified one and going up.
views/default.handlebars
+356 -332
@@ -280,320 +280,320 @@
280 <div id=p11 style=display:none>
281 <h1 id=p11deviceNameHeader><span id=p11deviceName></span> - Desktop</h1>
282 <div id="p14warning" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick="showFeaturesDlg()">
283 - <div class="icon2" style="float:left;margin:7px" />
283 + <div class=icon2 style="float:left;margin:7px"></div>
284 <div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Intel&reg; AMT Redirection port or KVM feature is disabled<span id="p14warninga">, click here to enable it.</span></div>
285 </div>
286 + <div id="p14warning2" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick="showPowerActionDlg()">
287 + <div class=icon2 style="float:left;margin:7px"></div>
288 + <div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Remote computer is not powered on, click here to issue a power command.</div>
289 + </div>
290 + <table cellpadding=0 cellspacing=0 style="width:100%;padding:0px;padding:0px;margin-top:0px">
291 + <tr id=deskarea1>
292 + <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
293 + <div style="float:right;text-align:right">
294 + <span id="p14power"></span>&nbsp;
295 + <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Left" onclick="drotate(-1)">&olarr;</div>
296 + <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Right" onclick="drotate(1)">&orarr;</div>
297 + <input id="deskFullBtn" type="button" title="Toggle full screen mode" onkeypress="return false" onkeydown="return false" value="Full" onclick="deskToggleFull()" style="margin-right:3px">
298 + <input id="deskFocusBtn" type="button" title="Toggle focus mode, when active only the region around the mouse is updated" onkeypress="return false" onkeydown="return false" value="Focus All" onclick="deskToggleFocus()" style="margin-right:3px;display:none">
299 + <input id="deskSaveBtn" type="button" title="Save a screenshot of the remote desktop" onkeypress="return false" onkeydown="return false" value="Save..." onclick=deskSaveImage() style=margin-right:3px>
300 + <input id="deskActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:3px />
301 + <input type="button" value="Settings..." title="Edit remote desktop settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" style="margin-right:3px">
302 + <input type="button" title="Change the power state of the remote machine" onkeypress="return false" onkeydown="return false" value="Power Actions..." onclick="showPowerActionDlg()" style="margin-right:3px;display:none">
303 + </div>
304 + <div>
305 + <div id="idx_deskFullBtn2" onclick=deskToggleFull() style="float:left;font-size:large;cursor:pointer;display:none">&nbsp;X</div>
306 + <input type="button" id="autoconnectbutton1" value="AutoConnect" onclick=autoConnectDesktop(event) onkeypress="return false" onkeydown="return false" style="display:none">
307 + <span id=connectbutton1span>&nbsp;<input type=button id=connectbutton1 value="Connect" onclick=connectDesktop(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
308 + <span id=connectbutton1hspan>&nbsp;<input type=button id=connectbutton1h value="HW Connect" onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
309 + <span id=disconnectbutton1span>&nbsp;<input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false"></span>
310 + &nbsp;<span id="deskstatus">Disconnected</span>
311 + </div>
312 + </td>
313 + </tr>
314 + <tr id=deskarea2>
315 + <td>
316 + <div style="background-color:gray"><div id="progressbar" style="height:2px;width:0%;background-color:red"></div></div>
317 + </td>
318 + </tr>
319 + <tr id=deskarea3>
320 + <td id=deskarea3x style="background:black;text-align:center;height:400px;position:relative">
321 + <div id="DeskFocus" style="color:transparent;border:3px dotted rgba(255,0,0,.2);position:absolute;border-radius:5px" oncontextmenu="return false" onmousedown="dmousedown(event)" onmouseup="dmouseup(event)" onmousemove="dmousemove(event)"></div>
322 + <canvas id="Desk" width="640" height="200" style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown="dmousedown(event)" onmouseup="dmouseup(event)" onmousemove="dmousemove(event)"></canvas>
323 + </td>
324 + </tr>
325 + <tr id=deskarea4>
326 + <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
327 + <div>
328 + &nbsp;
329 + <input id="DeskCAD" type="button" value="Ctrl-Alt-Del" onkeypress="return false" onkeydown="return false" onclick="sendCAD()">&nbsp;
330 + </div>
331 + </td>
332 + </tr>
333 + </table>
334 </div>
287 - <div id="p14warning2" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick="showPowerActionDlg()">
288 - <div class="icon2" style="float:left;margin:7px" />
289 - <div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Remote computer is not powered on, click here to issue a power command.</div>
335 + <div id=p12 style=display:none>
336 + <h1><span id=p12deviceName></span> - Terminal</h1>
337 + <div id="p12warning" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick=showFeaturesDlg()>
338 + <div class="icon2" style="float:left;margin:7px"></div>
339 + <div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Intel&reg; AMT Redirection port or KVM feature is disabled<span id="p14warninga">, click here to enable it.</span></div>
340 + </div>
341 + <div id="p12warning2" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick=showPowerActionDlg()>
342 + <div class="icon2" style="float:left;margin:7px"></div>
343 + <div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Remote computer is not powered on, click here to issue a power command.</div>
344 + </div>
345 + <table cellpadding=0 cellspacing=0 style="width:100%;padding:0px;padding:0px;margin-top:0px">
346 + <tr>
347 + <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
348 + <div style="float:right;text-align:right">
349 + <input id="termActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:3px />
350 + </div>
351 + <div>
352 + <input type="button" id="autoconnectbutton2" value="AutoConnect" onclick=autoConnectTerminal(event) onkeypress="return false" onkeydown="return false" style="display:none">
353 + <span id="connectbutton2span">&nbsp;<input type="button" id="connectbutton2" value="Connect" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
354 + <span id="connectbutton2hspan">&nbsp;<input type="button" id="connectbutton2h" value="HW Connect" onclick=connectTerminal(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
355 + <span id="disconnectbutton2span">&nbsp;<input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false"></span>
356 + &nbsp;<span id="termstatus">Disconnected</span>
357 + </div>
358 + </td>
359 + </tr>
360 + <tr>
361 + <td>
362 + <div style="background-color:gray"><div id="termprogressbar" style="height:2px;width:0%;background-color:red"></div></div>
363 + </td>
364 + </tr>
365 + <tr>
366 + <td style="background:black;text-align:center;height:500px;position:relative">
367 + <pre id="Term" style="background:black;margin:0;padding:0"></pre>
368 + </td>
369 + </tr>
370 + <tr>
371 + <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
372 + <div style="float:right;text-align:right">
373 + <input id="id_tfxkeysbutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Intel (F10 = ESC+[OM)" title="Toggle F1 to F10 keys emulation type" onclick="termToggleFx()">&nbsp;
374 + <input id="id_ttypebutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Extended Ascii" title="Toggle terminal emulation type" onclick="termToggleType()">&nbsp;&nbsp;
375 + <select id="specialkeylist" onkeypress="return false"></select>
376 + <input id="specialkeylistinput" type="button" onkeypress="return false" class="bottombutton" value="Send" title="Send the selected special key" onclick="sendSpecialKey()" />&nbsp;
377 + </div>
378 + <div>
379 + &nbsp;
380 + <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlcbutton" value="Ctl-C" onclick="termSendKey(3,'ctrlcbutton')" />
381 + <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlxbutton" value="Ctl-X" onclick="termSendKey(24,'ctrlxbutton')" />
382 + <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="escbutton" value="ESC" onclick="termSendKey(27,'escbutton')" />
383 + <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="bsbutton" value="Backspace" onclick="termSendKey(8,'bsbutton')" />
384 + <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="pastebutton" value="Paste" title="Paste text into the terminal" onclick="showTermPasteDialog()" />
385 + </div>
386 + </td>
387 + </tr>
388 + </table>
389 </div>
291 - </div>
292 - <table cellpadding=0 cellspacing=0 style="width:100%;padding:0px;padding:0px;margin-top:0px">
293 - <tr id=deskarea1>
294 - <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
295 - <div style="float:right;text-align:right">
296 - <span id="p14power"></span>&nbsp;
297 - <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Left" onclick="drotate(-1)">&olarr;</div>
298 - <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Right" onclick="drotate(1)">&orarr;</div>
299 - <input id="deskFullBtn" type="button" title="Toggle full screen mode" onkeypress="return false" onkeydown="return false" value="Full" onclick="deskToggleFull()" style="margin-right:3px">
300 - <input id="deskFocusBtn" type="button" title="Toggle focus mode, when active only the region around the mouse is updated" onkeypress="return false" onkeydown="return false" value="Focus All" onclick="deskToggleFocus()" style="margin-right:3px;display:none">
301 - <input id="deskSaveBtn" type="button" title="Save a screenshot of the remote desktop" onkeypress="return false" onkeydown="return false" value="Save..." onclick=deskSaveImage() style=margin-right:3px>
302 - <input id="deskActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:3px />
303 - <input type="button" value="Settings..." title="Edit remote desktop settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" style="margin-right:3px">
304 - <input type="button" title="Change the power state of the remote machine" onkeypress="return false" onkeydown="return false" value="Power Actions..." onclick="showPowerActionDlg()" style="margin-right:3px;display:none">
305 - </div>
306 - <div>
307 - <div id="idx_deskFullBtn2" onclick=deskToggleFull() style="float:left;font-size:large;cursor:pointer;display:none">&nbsp;X</div>
308 - <input type="button" id="autoconnectbutton1" value="AutoConnect" onclick=autoConnectDesktop(event) onkeypress="return false" onkeydown="return false" style="display:none">
309 - <span id=connectbutton1span>&nbsp;<input type=button id=connectbutton1 value="Connect" onclick=connectDesktop(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
310 - <span id=connectbutton1hspan>&nbsp;<input type=button id=connectbutton1h value="HW Connect" onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
311 - <span id=disconnectbutton1span>&nbsp;<input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false"></span>
312 - &nbsp;<span id="deskstatus">Disconnected</span>
313 - </div>
314 - </td>
315 - </tr>
316 - <tr id=deskarea2>
317 - <td>
318 - <div style="background-color:gray"><div id="progressbar" style="height:2px;width:0%;background-color:red"></div></div>
319 - </td>
320 - </tr>
321 - <tr id=deskarea3>
322 - <td id=deskarea3x style="background:black;text-align:center;height:400px;position:relative">
323 - <div id="DeskFocus" style="color:transparent;border:3px dotted rgba(255,0,0,.2);position:absolute;border-radius:5px" oncontextmenu="return false" onmousedown="dmousedown(event)" onmouseup="dmouseup(event)" onmousemove="dmousemove(event)"></div>
324 - <canvas id="Desk" width="640" height="200" style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown="dmousedown(event)" onmouseup="dmouseup(event)" onmousemove="dmousemove(event)"></canvas>
325 - </td>
326 - </tr>
327 - <tr id=deskarea4>
328 - <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
329 - <div>
330 - &nbsp;
331 - <input id="DeskCAD" type="button" value="Ctrl-Alt-Del" onkeypress="return false" onkeydown="return false" onclick="sendCAD()">&nbsp;
332 - </div>
333 - </td>
334 - </tr>
335 - </table>
336 - </div>
337 - <div id=p12 style=display:none>
338 - <h1><span id=p12deviceName></span> - Terminal</h1>
339 - <div id="p12warning" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick=showFeaturesDlg()>
340 - <div class="icon2" style="float:left;margin:7px"></div>
341 - <div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Intel&reg; AMT Redirection port or KVM feature is disabled<span id="p14warninga">, click here to enable it.</span></div>
342 - </div>
343 - <div id="p12warning2" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick=showPowerActionDlg()>
344 - <div class="icon2" style="float:left;margin:7px"></div>
345 - <div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Remote computer is not powered on, click here to issue a power command.</div>
346 - </div>
347 - <table cellpadding=0 cellspacing=0 style="width:100%;padding:0px;padding:0px;margin-top:0px">
348 - <tr>
349 - <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
350 - <div style="float:right;text-align:right">
351 - <input id="termActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:3px />
352 - </div>
353 - <div>
354 - <input type="button" id="autoconnectbutton2" value="AutoConnect" onclick=autoConnectTerminal(event) onkeypress="return false" onkeydown="return false" style="display:none">
355 - <span id="connectbutton2span">&nbsp;<input type="button" id="connectbutton2" value="Connect" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
356 - <span id="connectbutton2hspan">&nbsp;<input type="button" id="connectbutton2h" value="HW Connect" onclick=connectTerminal(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
357 - <span id="disconnectbutton2span">&nbsp;<input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false"></span>
358 - &nbsp;<span id="termstatus">Disconnected</span>
359 - </div>
360 - </td>
361 - </tr>
362 - <tr>
363 - <td>
364 - <div style="background-color:gray"><div id="termprogressbar" style="height:2px;width:0%;background-color:red"></div></div>
365 - </td>
366 - </tr>
367 - <tr>
368 - <td style="background:black;text-align:center;height:500px;position:relative">
369 - <pre id="Term" style="background:black;margin:0;padding:0"></pre>
370 - </td>
371 - </tr>
372 - <tr>
373 - <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
374 - <div style="float:right;text-align:right">
375 - <input id="id_tfxkeysbutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Intel (F10 = ESC+[OM)" title="Toggle F1 to F10 keys emulation type" onclick="termToggleFx()">&nbsp;
376 - <input id="id_ttypebutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Extended Ascii" title="Toggle terminal emulation type" onclick="termToggleType()">&nbsp;&nbsp;
377 - <select id="specialkeylist" onkeypress="return false"></select>
378 - <input id="specialkeylistinput" type="button" onkeypress="return false" class="bottombutton" value="Send" title="Send the selected special key" onclick="sendSpecialKey()" />&nbsp;
379 - </div>
380 - <div>
381 - &nbsp;
382 - <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlcbutton" value="Ctl-C" onclick="termSendKey(3,'ctrlcbutton')" />
383 - <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlxbutton" value="Ctl-X" onclick="termSendKey(24,'ctrlxbutton')" />
384 - <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="escbutton" value="ESC" onclick="termSendKey(27,'escbutton')" />
385 - <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="bsbutton" value="Backspace" onclick="termSendKey(8,'bsbutton')" />
386 - <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="pastebutton" value="Paste" title="Paste text into the terminal" onclick="showTermPasteDialog()" />
387 - </div>
388 - </td>
389 - </tr>
390 - </table>
391 - </div>
392 - <div id=p13 style=display:none>
393 - <h1><span id=p13deviceName></span> - Files</h1>
394 - <table id="p13toolbar" style="width: 100%" cellpadding="0" cellspacing="0">
395 - <tr>
396 - <td style="background-color:#C0C0C0;border-bottom:2px solid black;padding:2px">
397 - <div style="float:right;text-align:right">
398 - <input id="filesActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:3px />
399 - </div>
400 - <div>
401 - <input id=p13AutoConnect value="AutoConnect" onclick=autoConnectFiles(event) onkeypress="return false" onkeydown="return false" type="button" style="display:none">
402 - <input id=p13Connect value="Connect" onclick=connectFiles(event) onkeypress="return false" onkeydown="return false" type="button">
403 - <span id=p13Status>Disconnected</span>
404 - </div>
405 - </td>
406 - </tr>
407 - <tr>
408 - <td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
409 - <div id="p13rightOfButtons" style="float:right;margin-top:3px"></div>
410 - <div>
411 - <input type=button id=p13FolderUp disabled="disabled" onclick="p13folderup()" value="Up" />&nbsp;
412 - <input type=button id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="Select All" onkeypress="return false" onkeydown="return false" />&nbsp;
413 - <input type=button id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false" />&nbsp;
414 - <input type=button id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false" />&nbsp;
415 - <input type=button id=p13NewFolderButton disabled="disabled" value="New Folder" onclick="p13createfolder()" onkeypress="return false" onkeydown="return false" />&nbsp;
416 - <input type=button id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false" />&nbsp;
417 - <input type=button id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false" />&nbsp;
418 - </div>
419 - </td>
420 - </tr>
421 - <tr>
422 - <td style="background-color:#E4E9E7;height:28px">
423 - <div style=float:right>
424 - <select id=p13sortdropdown onchange=p13updateFiles()>
425 - <option value=1 selected="selected">Sort by name</option>
426 - <option value=2>Sort by size</option>
427 - <option value=3>Sort by date</option>
428 - <option value=4>Descend by name</option>
429 - <option value=5>Descend by size</option>
430 - <option value=6>Descend by date</option>
431 - </select>
432 - </div>
433 - <div>&nbsp;&nbsp;<span id="p13currentpath"></span></div>
434 - </td>
435 - </tr>
436 - </table>
437 - <div id="p13filetable" style="width:100%;height:500px;overflow:auto;-webkit-user-select:none">
438 - <div id="p13bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
439 - <div id="p13bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
440 - <span id="p13files"></span>
441 - </div>
442 - <table id="p13toolbarBottom" style=width:100% cellpadding=0 cellspacing=0>
443 - <tr><td class=style6 style="text-align:left">&nbsp;<span id="bottomstatus"></span></td></tr>
444 - </table>
445 - </div>
446 - <div id=p14 style=display:none>
447 - <h1><span id=p14deviceName></span> - Intel&reg; AMT</h1>
448 - <iframe id=p14iframe style="width:100%;height:650px;border:0;overflow:hidden" src="/commander.htm"></iframe>
449 - </div>
450 - <div id=p15 style=display:none>
451 - <h1><span id=p15deviceName></span> - Console</h1>
452 - <table cellpadding=0 cellspacing=0 style="width:100%;padding:0px;padding:0px;margin-top:0px">
453 - <tr>
454 - <td style=background:#C0C0C0>
455 - <div style=float:right;padding-right:4px>
456 - <span id=p15coreName title="Information about current core running on this agent"></span>
457 - <input type=button id=p15uploadCore value="Change Core" onclick=p15uploadCore(event) title="Change the agent Java Script code module" />
458 - </div>
459 - <div id="p15statetext" style=padding:4px></div>
460 - </td>
461 - </tr>
462 - <tr>
463 - <td>
464 - <div style="background-color:gray"><div id="consoleprogressbar" style="height:2px;width:0%;background-color:red"></div></div>
465 - </td>
466 - </tr>
467 - <tr>
468 - <td style="background:black;text-align:center;height:500px;position:relative">
469 - <div id="p15agentConsole" style="background:black;margin:0;padding:0;color:lightgray;width:100%;max-width:930px;height:100%;text-align:left;overflow-y:scroll"></div>
470 - </td>
471 - </tr>
472 - <tr>
473 - <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
474 - <table style="width:100%">
475 - <tr>
476 - <td style="width:99%">
477 - <input id=p15consoleText style=width:100% onkeyup=p15consoleSend(event) onfocus=onConsoleFocus(1) onblur=onConsoleFocus(0) />
478 - </td>
479 - <td>&nbsp;</td>
480 - <td style="width:1%"><input id="id_p15consoleClear" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Clear" onclick="p15consoleClear()"></td>
481 - </tr>
482 - </table>
483 - </td>
484 - </tr>
485 - </table>
486 - </div>
487 - <div id=p20 style=display:none>
488 - <h1><span id=p20meshName></span> - General</h1>
489 - <p id=p20info></p>
490 - </div>
491 - <br id="column_l_bottomgap" />
492 - </div>
493 - <div id=footer class=noselect>
494 - <table cellpadding=0 cellspacing=10 style="width:100%">
495 - <tr>
496 - <td style="text-align:left"></td>
497 - <td style="text-align:right"><a href="terms">Terms &amp; Privacy</a></td>
498 - </tr>
499 - </table>
500 - </div>
501 - <div id=dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:160px;width:400px;display:none">
502 - <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
503 - <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
504 - <div id=id_dialogtitle style=padding:5px></div>
505 - <div style=width:100%;margin:6px></div>
506 - </div>
507 - <div style="margin-right:16px;margin-left:8px">
508 - <div id=dialog1 style="margin:auto;text-align:center;margin:3px">
509 - <div id=id_dialogMessage style="padding:10px"></div>
510 - </div>
511 - <div id=dialog2 style="margin:auto;margin:3px">
512 - <div id=id_dialogOptions></div>
513 - </div>
514 - <div id=dialog3 style="margin:auto;margin:3px">
515 - <div style=height:26px>
516 - <select id=d3uploadMode style=float:right;width:260px onchange=d3modechange()>
517 - <option value=1>Local file upload</option>
518 - <option value=2>Server file selection</option>
519 - </select>
520 - <div>File Selection</div>
390 + <div id=p13 style=display:none>
391 + <h1><span id=p13deviceName></span> - Files</h1>
392 + <table id="p13toolbar" style="width: 100%" cellpadding="0" cellspacing="0">
393 + <tr>
394 + <td style="background-color:#C0C0C0;border-bottom:2px solid black;padding:2px">
395 + <div style="float:right;text-align:right">
396 + <input id="filesActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:3px />
397 + </div>
398 + <div>
399 + <input id=p13AutoConnect value="AutoConnect" onclick=autoConnectFiles(event) onkeypress="return false" onkeydown="return false" type="button" style="display:none">
400 + <input id=p13Connect value="Connect" onclick=connectFiles(event) onkeypress="return false" onkeydown="return false" type="button">
401 + <span id=p13Status>Disconnected</span>
402 + </div>
403 + </td>
404 + </tr>
405 + <tr>
406 + <td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
407 + <div id="p13rightOfButtons" style="float:right;margin-top:3px"></div>
408 + <div>
409 + <input type=button id=p13FolderUp disabled="disabled" onclick="p13folderup()" value="Up" />&nbsp;
410 + <input type=button id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="Select All" onkeypress="return false" onkeydown="return false" />&nbsp;
411 + <input type=button id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false" />&nbsp;
412 + <input type=button id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false" />&nbsp;
413 + <input type=button id=p13NewFolderButton disabled="disabled" value="New Folder" onclick="p13createfolder()" onkeypress="return false" onkeydown="return false" />&nbsp;
414 + <input type=button id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false" />&nbsp;
415 + <input type=button id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false" />&nbsp;
416 + </div>
417 + </td>
418 + </tr>
419 + <tr>
420 + <td style="background-color:#E4E9E7;height:28px">
421 + <div style=float:right>
422 + <select id=p13sortdropdown onchange=p13updateFiles()>
423 + <option value=1 selected="selected">Sort by name</option>
424 + <option value=2>Sort by size</option>
425 + <option value=3>Sort by date</option>
426 + <option value=4>Descend by name</option>
427 + <option value=5>Descend by size</option>
428 + <option value=6>Descend by date</option>
429 + </select>
430 + </div>
431 + <div>&nbsp;&nbsp;<span id="p13currentpath"></span></div>
432 + </td>
433 + </tr>
434 + </table>
435 + <div id="p13filetable" style="width:100%;height:500px;overflow:auto;-webkit-user-select:none">
436 + <div id="p13bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
437 + <div id="p13bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
438 + <span id="p13files"></span>
439 + </div>
440 + <table id="p13toolbarBottom" style=width:100% cellpadding=0 cellspacing=0>
441 + <tr><td class=style6 style="text-align:left">&nbsp;<span id="bottomstatus"></span></td></tr>
442 + </table>
443 </div>
522 - <div id=d3localmode style=height:26px;display:none>
523 - <form id=d3localmodeform method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame>
524 - <input type=text id=d3attrib name=attrib style=display:none />
525 - <input type=file id=d3localFile name=files style=float:right;width:260px onchange=d3setActions() />
526 - <input type=submit id=d3submit style=display:none />
527 - </form>
528 - <div>Upload File</div>
444 + <div id=p14 style=display:none>
445 + <h1><span id=p14deviceName></span> - Intel&reg; AMT</h1>
446 + <iframe id=p14iframe style="width:100%;height:650px;border:0;overflow:hidden" src="/commander.htm"></iframe>
447 </div>
530 - <div id=d3servermode>
531 - <div style="width:100%;background-color:#d3d9d6;text-align:left;padding:3px" valign=bottom>
532 - <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;
533 - </div>
534 - <div id=d3serverfiles style="width:100%;height:150px;background-color:white;padding:2px;border:1px solid gray;overflow-y:scroll"></div>
448 + <div id=p15 style=display:none>
449 + <h1><span id=p15deviceName></span> - Console</h1>
450 + <table cellpadding=0 cellspacing=0 style="width:100%;padding:0px;padding:0px;margin-top:0px">
451 + <tr>
452 + <td style=background:#C0C0C0>
453 + <div style=float:right;padding-right:4px>
454 + <span id=p15coreName title="Information about current core running on this agent"></span>
455 + <input type=button id=p15uploadCore value="Change Core" onclick=p15uploadCore(event) title="Change the agent Java Script code module" />
456 + </div>
457 + <div id="p15statetext" style=padding:4px></div>
458 + </td>
459 + </tr>
460 + <tr>
461 + <td>
462 + <div style="background-color:gray"><div id="consoleprogressbar" style="height:2px;width:0%;background-color:red"></div></div>
463 + </td>
464 + </tr>
465 + <tr>
466 + <td style="background:black;text-align:center;height:500px;position:relative">
467 + <div id="p15agentConsole" style="background:black;margin:0;padding:0;color:lightgray;width:100%;max-width:930px;height:100%;text-align:left;overflow-y:scroll"></div>
468 + </td>
469 + </tr>
470 + <tr>
471 + <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
472 + <table style="width:100%">
473 + <tr>
474 + <td style="width:99%">
475 + <input id=p15consoleText style=width:100% onkeyup=p15consoleSend(event) onfocus=onConsoleFocus(1) onblur=onConsoleFocus(0) />
476 + </td>
477 + <td>&nbsp;</td>
478 + <td style="width:1%"><input id="id_p15consoleClear" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Clear" onclick="p15consoleClear()"></td>
479 + </tr>
480 + </table>
481 + </td>
482 + </tr>
483 + </table>
484 </div>
485 + <div id=p20 style=display:none>
486 + <h1><span id=p20meshName></span> - General</h1>
487 + <p id=p20info></p>
488 + </div>
489 + <br id="column_l_bottomgap" />
490 + </div>
491 + <div id=footer class=noselect>
492 + <table cellpadding=0 cellspacing=10 style="width:100%">
493 + <tr>
494 + <td style="text-align:left"></td>
495 + <td style="text-align:right"><a href="terms">Terms &amp; Privacy</a></td>
496 + </tr>
497 + </table>
498 </div>
537 - <div id=dialog7 style="margin:auto;margin:3px">
538 - <div id="d7meshkvm">
539 - <h4 style="width:100%;border-bottom:1px solid gray">Mesh Agent Remote Desktop</h4>
540 - <div style="margin:3px 0 3px 0">
541 - <select id="d7bitmapquality" style="float:right;width:200px;height:20px" dir="rtl">
542 - <option value=50>50%</option>
543 - <option value=40>40%</option>
544 - <option selected=selected value=30>30%</option>
545 - <option value=20>20%</option>
546 - <option value=10>10%</option>
547 - <option value=5>5%</option>
548 - <option value=1>1%</option>
549 - </select>
550 - <div style="height:20px">Quality</div>
499 + <div id=dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:160px;width:400px;display:none">
500 + <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
501 + <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
502 + <div id=id_dialogtitle style=padding:5px></div>
503 + <div style=width:100%;margin:6px></div>
504 + </div>
505 + <div style="margin-right:16px;margin-left:8px">
506 + <div id=dialog1 style="margin:auto;text-align:center;margin:3px">
507 + <div id=id_dialogMessage style="padding:10px"></div>
508 </div>
552 - <div style="margin:3px 0 3px 0">
553 - <select id="d7bitmapscaling" style="float:right;width:200px;height:20px" dir="rtl">
554 - <option selected=selected value=1024>100%</option>
555 - <option value=896>87.5%</option>
556 - <option value=768>75%</option>
557 - <option value=640>62.5%</option>
558 - <option value=512>50%</option>
559 - <option value=384>37.5%</option>
560 - <option value=256>25%</option>
561 - <option value=128>12.5%</option>
562 - </select>
563 - <div style="height:20px">Scaling</div>
509 + <div id=dialog2 style="margin:auto;margin:3px">
510 + <div id=id_dialogOptions></div>
511 </div>
565 - </div>
566 - <div id="d7amtkvm">
567 - <h4 style="width:100%;border-bottom:1px solid gray">Intel&reg; AMT Hardware KVM</h4>
568 - <div style='height:26px'>
569 - <select id="d7desktopmode" style="float:right;width:200px">
570 - <option value="1">RLE8, Fastest</option>
571 - <option value="2">RLE16, Recommended</option>
572 - <option value="3">RAW8, Slow</option>
573 - <option value="4">RAW16, Very Slow</option>
574 - </select>
575 - <div>Image Encoding</div>
512 + <div id=dialog3 style="margin:auto;margin:3px">
513 + <div style=height:26px>
514 + <select id=d3uploadMode style=float:right;width:260px onchange=d3modechange()>
515 + <option value=1>Local file upload</option>
516 + <option value=2>Server file selection</option>
517 + </select>
518 + <div>File Selection</div>
519 + </div>
520 + <div id=d3localmode style=height:26px;display:none>
521 + <form id=d3localmodeform method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame>
522 + <input type=text id=d3attrib name=attrib style=display:none />
523 + <input type=file id=d3localFile name=files style=float:right;width:260px onchange=d3setActions() />
524 + <input type=submit id=d3submit style=display:none />
525 + </form>
526 + <div>Upload File</div>
527 + </div>
528 + <div id=d3servermode>
529 + <div style="width:100%;background-color:#d3d9d6;text-align:left;padding:3px" valign=bottom>
530 + <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;
531 + </div>
532 + <div id=d3serverfiles style="width:100%;height:150px;background-color:white;padding:2px;border:1px solid gray;overflow-y:scroll"></div>
533 + </div>
534 </div>
577 - <div style="height:60px">
578 - <div style="float:right;border:1px solid #666;width:200px;height:60px;overflow-y:scroll;background-color:white">
579 - <input type="checkbox" id='d7showfocus'>Show Focus Tool<br>
580 - <input type="checkbox" id='d7showcursor'>Show Local Mouse Cursor<br>
535 + <div id=dialog7 style="margin:auto;margin:3px">
536 + <div id="d7meshkvm">
537 + <h4 style="width:100%;border-bottom:1px solid gray">Mesh Agent Remote Desktop</h4>
538 + <div style="margin:3px 0 3px 0">
539 + <select id="d7bitmapquality" style="float:right;width:200px;height:20px" dir="rtl">
540 + <option value=50>50%</option>
541 + <option value=40>40%</option>
542 + <option selected=selected value=30>30%</option>
543 + <option value=20>20%</option>
544 + <option value=10>10%</option>
545 + <option value=5>5%</option>
546 + <option value=1>1%</option>
547 + </select>
548 + <div style="height:20px">Quality</div>
549 + </div>
550 + <div style="margin:3px 0 3px 0">
551 + <select id="d7bitmapscaling" style="float:right;width:200px;height:20px" dir="rtl">
552 + <option selected=selected value=1024>100%</option>
553 + <option value=896>87.5%</option>
554 + <option value=768>75%</option>
555 + <option value=640>62.5%</option>
556 + <option value=512>50%</option>
557 + <option value=384>37.5%</option>
558 + <option value=256>25%</option>
559 + <option value=128>12.5%</option>
560 + </select>
561 + <div style="height:20px">Scaling</div>
562 + </div>
563 + </div>
564 + <div id="d7amtkvm">
565 + <h4 style="width:100%;border-bottom:1px solid gray">Intel&reg; AMT Hardware KVM</h4>
566 + <div style='height:26px'>
567 + <select id="d7desktopmode" style="float:right;width:200px">
568 + <option value="1">RLE8, Fastest</option>
569 + <option value="2">RLE16, Recommended</option>
570 + <option value="3">RAW8, Slow</option>
571 + <option value="4">RAW16, Very Slow</option>
572 + </select>
573 + <div>Image Encoding</div>
574 + </div>
575 + <div style="height:60px">
576 + <div style="float:right;border:1px solid #666;width:200px;height:60px;overflow-y:scroll;background-color:white">
577 + <input type="checkbox" id='d7showfocus'>Show Focus Tool<br>
578 + <input type="checkbox" id='d7showcursor'>Show Local Mouse Cursor<br>
579 + </div>
580 + <div>Other Settings</div>
581 + </div>
582 </div>
582 - <div>Other Settings</div>
583 </div>
584 </div>
585 + <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:4px">
586 + <input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
587 + <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
588 + <div style="height:25px"><input id=idx_dlgDeleteButton type=button value=Delete style="width:80px;display:none" onclick="dialogclose(2)"></div>
589 + </div>
590 </div>
586 - </div>
587 - <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:4px">
588 - <input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
589 - <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
590 - <div style="height:25px"><input id=idx_dlgDeleteButton type=button value=Delete style="width:80px;display:none" onclick="dialogclose(2)"></div>
591 + <iframe name="fileUploadFrame" style=display:none></iframe>
592 + <form style=display:none method=post action=uploadfile.ashx enctype=multipart/form-data target=fileUploadFrame><input id=p5fileDragName name="name"><input id=p5fileDragSize name="size"><input id=p5fileDragType name="type"><input id=p5fileDragData name="data"><input id=p5fileDragLink name="link"><input type=submit id=p5loginSubmit2 style=display:none /></form>
593 + <form style=display:none method=post action=uploadnodefile.ashx enctype=multipart/form-data target=fileUploadFrame><input id=p13fileDragName name="name"><input id=p13fileDragSize name="size"><input id=p13fileDragType name="type"><input id=p13fileDragData name="data"><input id=p13fileDragLink name="link"><input type=submit id=p13loginSubmit2 style=display:none /></form>
594 + <audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3"></audio>
595 </div>
596 </div>
593 - <iframe name="fileUploadFrame" style=display:none></iframe>
594 - <form style=display:none method=post action=uploadfile.ashx enctype=multipart/form-data target=fileUploadFrame><input id=p5fileDragName name="name"><input id=p5fileDragSize name="size"><input id=p5fileDragType name="type"><input id=p5fileDragData name="data"><input id=p5fileDragLink name="link"><input type=submit id=p5loginSubmit2 style=display:none /></form>
595 - <form style=display:none method=post action=uploadnodefile.ashx enctype=multipart/form-data target=fileUploadFrame><input id=p13fileDragName name="name"><input id=p13fileDragSize name="size"><input id=p13fileDragType name="type"><input id=p13fileDragData name="data"><input id=p13fileDragLink name="link"><input type=submit id=p13loginSubmit2 style=display:none /></form>
596 - <audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3"></audio>
597 <script type="text/javascript">
598 var powerStatetable = ['', 'Powered', 'Sleep', 'Sleep', 'Sleep', 'Hibernating', 'Power off', 'Present'];
599 var StatusStrs = ['Disconnected', 'Connecting...', 'Setup...', 'Connected', 'Intel&reg; AMT Connected'];
@@ -622,6 +622,7 @@
622 var serverPublicNamePort = "{{{serverDnsName}}}:{{{serverPublicPort}}}";
623 var amtScanResults = null;
624 var debugmode = false;
625 + var clickOnce = detectClickOnce();
626
627 function startup() {
628 // Guard against other site's top frames (web bugs).
@@ -720,35 +721,42 @@
721 }
722 }
723
724 + // Return true if this browser supports clickonce
725 + function detectClickOnce() {
726 + for (var i in window.navigator.mimeTypes) { if (window.navigator.mimeTypes[i].type == "application/x-ms-application") { return true; } }
727 + var userAgent = window.navigator.userAgent.toUpperCase();
728 + return (userAgent.indexOf('.NET CLR 3.5') >= 0) || (userAgent.indexOf('(WINDOWS NT ') >= 0);
729 + }
730 +
731 function updateSiteAdmin() {
732 var noServerBackup = {{{noServerBackup}}};
725 - var siteRights = userinfo.siteadmin;
726 - if (noServerBackup == 1) { siteRights &= 0xFFFFFFFA; } // If not server backups allowed, remove server backup and restore permissions
727 -
728 - // Update account actions
729 - QV('p2AccountActions', (features & 4) == 0); // Hide Account Actions if in single user mode
730 - QV('p2ServerActions', siteRights & 5);
731 - QV('p2ServerActionsBackup', siteRights & 1);
732 - QV('p2ServerActionsRestore', siteRights & 4);
733 - QV('p2ServerActionsVersion', siteRights & 16);
734 - QV('MainMenuMyFiles', siteRights & 8);
735 - if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { go(1); }
736 -
737 - // Update user management state
738 - if ((userinfo.siteadmin & 2) != 0)
739 - {
740 - // We are user administrator
741 - if (users == null) { meshserver.Send({ action: 'users' }); }
742 - if (wssessions == null) { meshserver.Send({ action: 'wssessioncount' }); }
743 - } else {
744 - // We are not user administrator
745 - users = null;
746 - wssessions = null;
747 - updateUsers();
748 - if (xxcurrentView == 4) go(1);
749 - }
750 - meshserver.Send({ action: 'events' });
751 - QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF);
733 + var siteRights = userinfo.siteadmin;
734 + if (noServerBackup == 1) { siteRights &= 0xFFFFFFFA; } // If not server backups allowed, remove server backup and restore permissions
735 +
736 + // Update account actions
737 + QV('p2AccountActions', (features & 4) == 0); // Hide Account Actions if in single user mode
738 + QV('p2ServerActions', siteRights & 5);
739 + QV('p2ServerActionsBackup', siteRights & 1);
740 + QV('p2ServerActionsRestore', siteRights & 4);
741 + QV('p2ServerActionsVersion', siteRights & 16);
742 + QV('MainMenuMyFiles', siteRights & 8);
743 + if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { go(1); }
744 +
745 + // Update user management state
746 + if ((userinfo.siteadmin & 2) != 0)
747 + {
748 + // We are user administrator
749 + if (users == null) { meshserver.Send({ action: 'users' }); }
750 + if (wssessions == null) { meshserver.Send({ action: 'wssessioncount' }); }
751 + } else {
752 + // We are not user administrator
753 + users = null;
754 + wssessions = null;
755 + updateUsers();
756 + if (xxcurrentView == 4) go(1);
757 + }
758 + meshserver.Send({ action: 'events' });
759 + QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF);
760 }
761
762 function onMessage(server, message) {
@@ -883,6 +891,14 @@
891 events_update();
892 break;
893 }
894 + case 'getcookie': {
895 + if (message.tag == 'clickonce') {
896 + var basicPort = "{{{serverRedirPort}}}"==""?"{{{serverPublicPort}}}":"{{{serverRedirPort}}}";
897 + rdpurl = "http://" + window.location.hostname + ":" + basicPort + "/clickonce/minirouter/MeshMiniRouter.application?WS=wss%3A%2F%2F" + window.location.hostname + "%2Fmeshrelay.ashx%3Fauth=" + message.cookie + "&CH={{{webcerthash}}}&AP=" + message.protocol + "&HOL=1";
898 + window.open(rdpurl, '_blank');
899 + }
900 + break;
901 + }
902 case 'event': {
903 if (!message.event.nolog) { events.unshift(message.event); events_update(); }
904 switch (message.event.action) {
@@ -1737,10 +1753,10 @@
1753
1754 // Context menu for clicks other than on feature
1755 var contextmenu_items = [
1740 - { text: 'Refresh', callback: function () { refreshMap(true, true); } },
1741 - { text: 'Zoom to fit extent', callback: function () { zoomToFitExtent(); } },
1742 - { text: 'Center map here', callback: function(obj) { xxmap.mapView.animate({ center: obj.coordinate } ); } },
1743 - { text: 'Place node here', callback: function(obj) { placeNode(obj.coordinate); } }
1756 + { text: 'Refresh', callback: function () { refreshMap(true, true); } },
1757 + { text: 'Zoom to fit extent', callback: function () { zoomToFitExtent(); } },
1758 + { text: 'Center map here', callback: function(obj) { xxmap.mapView.animate({ center: obj.coordinate } ); } },
1759 + { text: 'Place node here', callback: function(obj) { placeNode(obj.coordinate); } }
1760 ];
1761
1762 function stringToIntHash(str) {
@@ -2394,10 +2410,14 @@
2410
2411 // Show bottom buttons
2412 x = '<div style=float:right;font-size:x-small>';
2397 - if ((meshrights & 4) != 0) x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '")>Delete Device</a>';
2413 + if ((meshrights & 4) != 0) x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '") title="Remove this device">Delete Device</a>';
2414 x += '</div><div style=font-size:x-small>';
2399 - if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '")>Interfaces</a>&nbsp;';
2400 - if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '")>Location</a>&nbsp;';
2415 + if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '") title="Show device network interface information">Interfaces</a>&nbsp;';
2416 + if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '") title="Show device locations information">Location</a>&nbsp;';
2417 +
2418 + // RDP link, show this link only of the remote machine is Windows.
2419 + if (((connectivity & 1) != 0) && (clickOnce == true) && (mesh.mtype == 2) && ((meshrights & 8) != 0) && (node.agent.id > 0) && (node.agent.id < 5)) { x += '<a style=cursor:pointer onclick=p10clickOnce("' + node._id + '","RDP") title="Requires Microsoft ClickOnce support in your browser.">RDP</a>&nbsp;'; }
2420 +
2421 x += '</div><br>'
2422
2423 QH('p10html3', x);
@@ -2606,6 +2626,10 @@
2626 meshserver.Send({ action: 'removedevices', nodeids: [ nodeid ] });
2627 }
2628
2629 + function p10clickOnce(nodeid, protocol) {
2630 + meshserver.Send({ action: 'getcookie', nodeid: nodeid, tcpport: 3389, tag: 'clickonce', protocol: 'rdp' });
2631 + }
2632 +
2633 // Show current location
2634 var d2map = null;
2635 function p10showNodeLocationDialog() {
@@ -4565,5 +4589,5 @@
4589 function parseUriArgs() { var name, r = {}, parsedUri = window.document.location.href.split(/[\?&|\=]/); parsedUri.splice(0, 1); for (x in parsedUri) { switch (x % 2) { case 0: { name = parsedUri[x]; break; } case 1: { r[name] = parsedUri[x]; var x = parseInt(r[name]); if (x == r[name]) { r[name] = x; } break; } } } return r; }
4590
4591 </script>
4568 -</body>
4592 + </body>
4593 </html>
webserver.js
+60 -899
@@ -52,6 +52,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
52 obj.express = require('express');
53 obj.meshAgentHandler = require('./meshagent.js');
54 obj.meshRelayHandler = require('./meshrelay.js')
55 + obj.meshUserHandler = require('./meshuser.js')
56 obj.interceptor = require('./interceptor');
57
58 // Variables
@@ -67,7 +68,23 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
68 obj.users = {};
69 obj.meshes = {};
70 obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
70 -
71 +
72 + // Mesh Rights
73 + const MESHRIGHT_EDITMESH = 1;
74 + const MESHRIGHT_MANAGEUSERS = 2;
75 + const MESHRIGHT_MANAGECOMPUTERS = 4;
76 + const MESHRIGHT_REMOTECONTROL = 8;
77 + const MESHRIGHT_AGENTCONSOLE = 16;
78 + const MESHRIGHT_SERVERFILES = 32;
79 + const MESHRIGHT_WAKEDEVICE = 64;
80 +
81 + // Site rights
82 + const SITERIGHT_SERVERBACKUP = 1;
83 + const SITERIGHT_MANAGEUSERS = 2;
84 + const SITERIGHT_SERVERRESTORE = 4;
85 + const SITERIGHT_FILEACCESS = 8;
86 + const SITERIGHT_SERVERUPDATE = 16;
87 +
88 // Perform hash on web certificate and agent certificate
89 obj.webCertificateHash = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' });
90 obj.webCertificateHashBase64 = new Buffer(parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
@@ -423,7 +440,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
440 if (domain.userQuota == -1) { features += 8; } // No server files mode
441 if (obj.args.tlsoffload == true) { features += 16; } // No mutual-auth CIRA
442 if ((!obj.args.user) && (obj.args.nousers != true)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
426 - res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.certificates.CommonName, serverPublicPort: args.port, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass });
443 + res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.certificates.CommonName, serverRedirPort: args.redirport, serverPublicPort: args.port, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass, webcerthash: obj.webCertificateHashBase64 });
444 } else {
445 // Send back the login application
446 res.render(obj.path.join(__dirname, 'views/login'), { loginmode: req.session.loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == ''))?0:1), serverDnsName: obj.certificates.CommonName, serverPublicPort: obj.args.port });
@@ -593,7 +610,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
610 readEntireTextFile(file.path, function (data) {
611 if (data == null) return;
612 data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
596 - sendMeshAgentCore(user, domain, fields.attrib[0], data); // Upload the core
613 + obj.sendMeshAgentCore(user, domain, fields.attrib[0], data); // Upload the core
614 try { obj.fs.unlinkSync(file.path); } catch (e) { }
615 });
616 }
@@ -677,7 +694,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
694 } else {
695 // Get the session from the cookie
696 if (obj.parent.multiServer == null) { return; }
680 - var session = obj.parent.multiServer.decodeCookie(req.query.auth);
697 + var session = obj.decodeCookie(req.query.auth);
698 if (session == null) { console.log('ERR: Invalid cookie'); return; }
699 if (session.domainid != domain.id) { console.log('ERR: Invalid domain'); return; }
700 user = obj.users[session.userid];
@@ -697,8 +714,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
714
715 // Check if this user has permission to manage this computer
716 var meshlinks = user.links[node.meshid];
700 - if (!meshlinks || !meshlinks.rights) { console.log('ERR: Access denied (1)'); return; }
701 - if ((meshlinks.rights & 8) == 0) { console.log('ERR: Access denied (2)'); return; }
717 + if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); return; }
718
719 // Check what connectivity is available for this node
720 var state = parent.GetConnectivityState(req.query.host);
@@ -942,18 +958,6 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
958 // If closed, do nothing
959 ws.on('close', function (req) { });
960 }
945 -
946 - // Read the folder and all sub-folders and serialize that into json.
947 - function readFilesRec(path) {
948 - var r = {}, dir = obj.fs.readdirSync(path);
949 - for (var i in dir) {
950 - var f = { t: 3, d: 111 };
951 - var stat = obj.fs.statSync(path + '/' + dir[i])
952 - if ((stat.mode & 0x004000) == 0) { f.s = stat.size; f.d = stat.mtime.getTime(); } else { f.t = 2; f.f = readFilesRec(path + '/' + dir[i]); }
953 - r[dir[i]] = f;
954 - }
955 - return r;
956 - }
961
962 // Get the total size of all files in a folder and all sub-folders
963 function readTotalFileSize(path) {
@@ -974,801 +978,6 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
978 });
979 obj.fs.rmdirSync(path);
980 };
977 -
978 - // Indicates we want to handle websocket requests on "/control.ashx".
979 - function handleControlRequest(ws, req) {
980 - var domain = checkUserIpAddress(ws, req);
981 - if (domain == null) return;
982 - try {
983 - // Check if the user is logged in
984 - if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) { try { ws.close(); } catch (e) { } return; }
985 - req.session.ws = ws; // Associate this websocket session with the web session
986 - req.session.ws.userid = req.session.userid;
987 - req.session.ws.domainid = domain.id;
988 - var user = obj.users[req.session.userid];
989 - if (user == null) { try { ws.close(); } catch (e) { } return; }
990 -
991 - // Add this web socket session to session list
992 - ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2);
993 - obj.wssessions2[ws.sessionId] = ws;
994 - if (!obj.wssessions[user._id]) { obj.wssessions[user._id] = [ws]; } else { obj.wssessions[user._id].push(ws); }
995 - if (obj.parent.multiServer == null) {
996 - obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[user._id].length, nolog: 1, domain: domain.id })
997 - } else {
998 - obj.recountSessions(ws.sessionId); // Recount sessions
999 - }
1000 -
1001 - // If we have peer servers, inform them of the new session
1002 - if (obj.parent.multiServer != null) { obj.parent.multiServer.DispatchMessage({ action: 'sessionStart', sessionid: ws.sessionId }); }
1003 -
1004 - // Handle events
1005 - ws.HandleEvent = function (source, event) {
1006 - if (!event.domain || event.domain == domain.id) {
1007 - try {
1008 - if (event == 'close') { req.session.destroy(); ws.close(); }
1009 - else if (event == 'resubscribe') { user.subscriptions = obj.subscribe(user._id, ws); }
1010 - else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); }
1011 - else { ws.send(JSON.stringify({ action: 'event', event: event })); }
1012 - } catch (e) { }
1013 - }
1014 - }
1015 -
1016 - user.subscriptions = obj.subscribe(user._id, ws); // Subscribe to events
1017 - ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
1018 -
1019 - // When data is received from the web socket
1020 - ws.on('message', function (msg) {
1021 - var user = obj.users[req.session.userid];
1022 - var command = JSON.parse(msg.toString('utf8'))
1023 - switch (command.action) {
1024 - case 'meshes':
1025 - {
1026 - // Request a list of all meshes this user as rights to
1027 - var docs = [];
1028 - for (var i in user.links) { if (obj.meshes[i]) { docs.push(obj.meshes[i]); } }
1029 - ws.send(JSON.stringify({ action: 'meshes', meshes: docs }));
1030 - break;
1031 - }
1032 - case 'nodes':
1033 - {
1034 - // Request a list of all meshes this user as rights to
1035 - var links = [];
1036 - for (var i in user.links) { links.push(i); }
1037 -
1038 - // Request a list of all nodes
1039 - obj.db.GetAllTypeNoTypeFieldMeshFiltered(links, domain.id, 'node', function (err, docs) {
1040 - var r = {};
1041 - for (var i in docs) {
1042 - // Add the connection state
1043 - var state = parent.GetConnectivityState(docs[i]._id);
1044 - if (state) {
1045 - docs[i].conn = state.connectivity;
1046 - docs[i].pwr = state.powerState;
1047 - if ((state.connectivity & 1) != 0) { var agent = obj.wsagents[docs[i]._id]; if (agent != null) { docs[i].agct = agent.connectTime; } }
1048 - if ((state.connectivity & 2) != 0) { var cira = obj.parent.mpsserver.ciraConnections[docs[i]._id]; if (cira != null) { docs[i].cict = cira.tag.connectTime; } }
1049 - }
1050 - // Compress the meshid's
1051 - var meshid = docs[i].meshid;
1052 - if (!r[meshid]) { r[meshid] = []; }
1053 - delete docs[i].meshid;
1054 - r[meshid].push(docs[i]);
1055 - }
1056 - ws.send(JSON.stringify({ action: 'nodes', nodes: r }));
1057 - });
1058 - break;
1059 - }
1060 - case 'powertimeline':
1061 - {
1062 - // Query the database for the power timeline for a given node
1063 - // The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ]
1064 - obj.db.getPowerTimeline(command.nodeid, function (err, docs) {
1065 - if (err == null && docs.length > 0) {
1066 - var timeline = [], time = null, previousPower;
1067 - for (var i in docs) {
1068 - var doc = docs[i];
1069 - if (time == null) {
1070 - // First element
1071 - time = doc.time;
1072 - if (doc.oldPower) { timeline.push(doc.oldPower); } else { timeline.push(0); }
1073 - timeline.push(time);
1074 - timeline.push(doc.power);
1075 - previousPower = doc.power;
1076 - } else {
1077 - // Delta element
1078 - if ((previousPower != doc.power) && ((doc.time - time) > 60000)) { // To boost speed, any blocks less than a minute get approximated.
1079 - // Create a new timeline
1080 - timeline.push(doc.time - time);
1081 - timeline.push(doc.power);
1082 - time = doc.time;
1083 - previousPower = doc.power;
1084 - } else {
1085 - // Extend the previous timeline
1086 - if ((timeline.length >= 6) && (timeline[timeline.length - 3] == doc.power)) { // We can merge the block with the previous block
1087 - timeline[timeline.length - 4] += (timeline[timeline.length - 2] + (doc.time - time));
1088 - timeline.pop();
1089 - timeline.pop();
1090 - } else { // Extend the last block in the timeline
1091 - timeline[timeline.length - 2] += (doc.time - time);
1092 - timeline[timeline.length - 1] = doc.power;
1093 - }
1094 - time = doc.time;
1095 - previousPower = doc.power;
1096 - }
1097 - }
1098 - }
1099 - ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline }));
1100 - } else {
1101 - // No records found, send current state if we have it
1102 - var state = obj.parent.GetConnectivityState(command.nodeid);
1103 - if (state != null) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState] })); }
1104 - }
1105 - });
1106 - break;
1107 - }
1108 - case 'files':
1109 - {
1110 - // Send the full list of server files to the browser app
1111 - if ((user.siteadmin & 8) != 0) { updateUserFiles(user, ws, domain); }
1112 - break;
1113 - }
1114 - case 'fileoperation':
1115 - {
1116 - // Check permissions
1117 - if ((user.siteadmin & 8) != 0) {
1118 - // Perform a file operation (Create Folder, Delete Folder, Delete File...)
1119 - if ((command.path != null) && (typeof command.path == 'object') && command.path.length > 0) {
1120 - var rootfolder = command.path[0];
1121 - var rootfoldersplit = rootfolder.split('/'), domainx = 'domain';
1122 - if (rootfoldersplit[1].length > 0) domainx = 'domain-' + rootfoldersplit[1];
1123 - var path = obj.path.join(obj.filespath, domainx + "/" + rootfoldersplit[0] + "-" + rootfoldersplit[2]);
1124 - for (var i = 1; i < command.path.length; i++) { if (obj.common.IsFilenameValid(command.path[i]) == false) { path = null; break; } path += ("/" + command.path[i]); }
1125 - if (path == null) break;
1126 -
1127 - if ((command.fileop == 'createfolder') && (obj.common.IsFilenameValid(command.newfolder) == true)) { try { obj.fs.mkdirSync(path + "/" + command.newfolder); } catch (e) { } } // Create a new folder
1128 - else if (command.fileop == 'delete') { for (var i in command.delfiles) { if (obj.common.IsFilenameValid(command.delfiles[i]) == true) { var fullpath = path + "/" + command.delfiles[i]; try { obj.fs.rmdirSync(fullpath); } catch (e) { try { obj.fs.unlinkSync(fullpath); } catch (e) { } } } } } // Delete
1129 - else if ((command.fileop == 'rename') && (obj.common.IsFilenameValid(command.oldname) == true) && (obj.common.IsFilenameValid(command.newname) == true)) { try { obj.fs.renameSync(path + "/" + command.oldname, path + "/" + command.newname); } catch (e) { } } // Rename
1130 -
1131 - obj.parent.DispatchEvent([user._id], obj, 'updatefiles') // Fire an event causing this user to update this files
1132 - }
1133 - }
1134 - break;
1135 - }
1136 - case 'msg':
1137 - {
1138 - // Route a message.
1139 - // This this command has a nodeid, that is the target.
1140 - if (command.nodeid != null) {
1141 - var splitnodeid = command.nodeid.split('/');
1142 - // Check that we are in the same domain and the user has rights over this node.
1143 - if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) {
1144 - // See if the node is connected
1145 - var agent = obj.wsagents[command.nodeid];
1146 - if (agent != null) {
1147 - // Check if we have permission to send a message to that node
1148 - var rights = user.links[agent.dbMeshKey];
1149 - if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
1150 - command.sessionid = ws.sessionId; // Set the session id, required for responses.
1151 - command.rights = rights.rights; // Add user rights flags to the message
1152 - delete command.nodeid; // Remove the nodeid since it's implyed.
1153 - agent.send(JSON.stringify(command));
1154 - }
1155 - } else {
1156 - // Check if a peer server is connected to this agent
1157 - var routing = obj.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type
1158 - if (routing != null) {
1159 - // Check if we have permission to send a message to that node
1160 - var rights = user.links[routing.meshid];
1161 - if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
1162 - command.fromSessionid = ws.sessionId; // Set the session id, required for responses.
1163 - command.rights = rights.rights; // Add user rights flags to the message
1164 - parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
1165 - }
1166 - }
1167 - }
1168 - }
1169 - }
1170 - break;
1171 - }
1172 - case 'events':
1173 - {
1174 - // Send the list of events for this session
1175 - obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs })); });
1176 - break;
1177 - }
1178 - case 'clearevents':
1179 - {
1180 - // Delete all events
1181 - if (user.siteadmin != 0xFFFFFFFF) break;
1182 - obj.db.RemoveAllEvents(domain.id);
1183 - obj.parent.DispatchEvent(['*', 'server-global'], obj, { action: 'clearevents', nolog: 1, domain: domain.id })
1184 - break;
1185 - }
1186 - case 'users':
1187 - {
1188 - // Request a list of all users
1189 - if ((user.siteadmin & 2) == 0) break;
1190 - var docs = [];
1191 - for (var i in obj.users) { if ((obj.users[i].domain == domain.id) && (obj.users[i].name != '~')) { docs.push(obj.users[i]); } }
1192 - ws.send(JSON.stringify({ action: 'users', users: docs }));
1193 - break;
1194 - }
1195 - case 'wssessioncount':
1196 - {
1197 - // Request a list of all web socket user session count
1198 - var wssessions = {};
1199 - if ((user.siteadmin & 2) == 0) break;
1200 - if (obj.parent.multiServer == null) {
1201 - // No peering, use simple session counting
1202 - for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } }
1203 - } else {
1204 - // We have peer servers, use more complex session counting
1205 - for (var userid in obj.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.sessionsCount[userid]; } }
1206 - }
1207 - ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions })); // wssessions is: userid --> count
1208 - break;
1209 - }
1210 - case 'deleteuser':
1211 - {
1212 - // Delete a user account
1213 - if ((user.siteadmin & 2) == 0) break;
1214 - var delusername = command.username, deluserid = command.userid, deluser = obj.users[deluserid];
1215 - if ((deluser.siteadmin != null) && (deluser.siteadmin > 0) && (user.siteadmin != 0xFFFFFFFF)) break; // Need full admin to remote another administrator
1216 - if ((deluserid.split('/').length != 3) || (deluserid.split('/')[1] != domain.id)) break; // Invalid domain, operation only valid for current domain
1217 -
1218 - // Delete all files on the server for this account
1219 - try {
1220 - var deluserpath = getServerRootFilePath(deluser);
1221 - if (deluserpath != null) { deleteFolderRec(deluserpath); }
1222 - } catch (e) { }
1223 -
1224 - obj.db.Remove(deluserid);
1225 - delete obj.users[deluserid];
1226 - obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: delusername, action: 'accountremove', msg: 'Account removed', domain: domain.id })
1227 - obj.parent.DispatchEvent([deluserid], obj, 'close');
1228 -
1229 - break;
1230 - }
1231 - case 'adduser':
1232 - {
1233 - // Add a new user account
1234 - if ((user.siteadmin & 2) == 0) break;
1235 - var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase();
1236 - if (newusername == '~') break; // This is a reserved user name
1237 - if (!obj.users[newuserid]) {
1238 - var newuser = { type: 'user', _id: newuserid, name: newusername, email: command.email, creation: Date.now(), domain: domain.id };
1239 - obj.users[newuserid] = newuser;
1240 - // Create a user, generate a salt and hash the password
1241 - obj.hash(command.pass, function (err, salt, hash) {
1242 - if (err) throw err;
1243 - newuser.salt = salt;
1244 - newuser.hash = hash;
1245 - obj.db.SetUser(newuser);
1246 - var newuser2 = obj.common.Clone(newuser);
1247 - if (newuser2.subscriptions) { delete newuser2.subscriptions; }
1248 - if (newuser2.salt) { delete newuser2.salt; }
1249 - if (newuser2.hash) { delete newuser2.hash; }
1250 - obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: newusername, account: newuser2, action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id })
1251 - });
1252 - }
1253 - break;
1254 - }
1255 - case 'edituser':
1256 - {
1257 - // Edit a user account, may involve changing email or administrator permissions
1258 - if (((user.siteadmin & 2) != 0) || (user.name == command.name)) {
1259 - var chguserid = 'user/' + domain.id + '/' + command.name.toLowerCase(), chguser = obj.users[chguserid], change = 0;
1260 - if (chguser) {
1261 - if (command.email && chguser.email != command.email) { chguser.email = command.email; change = 1; }
1262 - if (command.quota != chguser.quota) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; }
1263 - if ((user.siteadmin == 0xFFFFFFFF) && (command.siteadmin != null) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1 }
1264 - if (change == 1) {
1265 - obj.db.Set(chguser);
1266 - obj.parent.DispatchEvent([chguser._id], obj, 'resubscribe');
1267 - var chguser2 = obj.common.Clone(chguser);
1268 - delete chguser2.salt;
1269 - delete chguser2.hash;
1270 - obj.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: chguser2, action: 'accountchange', msg: 'Account changed: ' + command.name, domain: domain.id })
1271 - }
1272 - }
1273 - }
1274 - break;
1275 - }
1276 - case 'serverversion':
1277 - {
1278 - // Check the server version
1279 - if ((user.siteadmin & 16) == 0) break;
1280 - obj.parent.getLatestServerVersion(function (currentVersion, latestVersion) { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); });
1281 - break;
1282 - }
1283 - case 'serverupdate':
1284 - {
1285 - // Perform server update
1286 - if ((user.siteadmin & 16) == 0) break;
1287 - obj.parent.performServerUpdate();
1288 - break;
1289 - }
1290 - case 'createmesh':
1291 - {
1292 - // Create mesh
1293 - // TODO: Right now, we only create type 1 Agent-less Intel AMT mesh, or type 2 Agent mesh
1294 - if ((command.meshtype == 1) || (command.meshtype == 2)) {
1295 - // Create a type 1 agent-less Intel AMT mesh.
1296 - obj.crypto.randomBytes(48, function (err, buf) {
1297 - var meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');;
1298 - var links = {}
1299 - links[user._id] = { name: user.name, rights: 0xFFFFFFFF };
1300 - var mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links };
1301 - obj.db.Set(mesh);
1302 - obj.meshes[meshid] = mesh;
1303 - obj.parent.AddEventDispatch([meshid], ws);
1304 - if (user.links == null) user.links = {};
1305 - user.links[meshid] = { rights: 0xFFFFFFFF };
1306 - user.subscriptions = obj.subscribe(user._id, ws);
1307 - obj.db.SetUser(user);
1308 - obj.parent.DispatchEvent(['*', meshid, user._id], obj, { etype: 'mesh', username: user.name, meshid: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, action: 'createmesh', links: links, msg: 'Mesh created: ' + command.meshname, domain: domain.id })
1309 - });
1310 - }
1311 - break;
1312 - }
1313 - case 'deletemesh':
1314 - {
1315 - // Delete a mesh and all computers within it
1316 - obj.db.Get(command.meshid, function (err, meshes) {
1317 - if (meshes.length != 1) return;
1318 - var mesh = meshes[0];
1319 -
1320 - // Check if this user has rights to do this
1321 - if (mesh.links[user._id] == null || mesh.links[user._id].rights != 0xFFFFFFFF) return;
1322 - if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1323 -
1324 - // Fire the removal event first, because after this, the event will not route
1325 - obj.parent.DispatchEvent(['*', command.meshid], obj, { etype: 'mesh', username: user.name, meshid: command.meshid, name: command.meshname, action: 'deletemesh', msg: 'Mesh deleted: ' + command.meshname, domain: domain.id })
1326 -
1327 - // Remove all user links to this mesh
1328 - for (var i in meshes) {
1329 - var links = meshes[i].links;
1330 - for (var j in links) {
1331 - var xuser = obj.users[j];
1332 - delete xuser.links[meshes[i]._id];
1333 - obj.db.Set(xuser);
1334 - obj.parent.DispatchEvent([xuser._id], obj, 'resubscribe');
1335 - }
1336 - }
1337 -
1338 - // Delete all files on the server for this mesh
1339 - try {
1340 - var meshpath = getServerRootFilePath(mesh);
1341 - if (meshpath != null) { deleteFolderRec(meshpath); }
1342 - } catch (e) { }
1343 -
1344 - obj.parent.RemoveEventDispatchId(command.meshid); // Remove all subscriptions to this mesh
1345 - obj.db.RemoveMesh(command.meshid); // Remove mesh from database
1346 - delete obj.meshes[command.meshid]; // Remove mesh from memory
1347 - });
1348 - break;
1349 - }
1350 - case 'editmesh':
1351 - {
1352 - // Change the name or description of a mesh
1353 - var mesh = obj.meshes[command.meshid], change = '';
1354 - if (mesh) {
1355 - // Check if this user has rights to do this
1356 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 1) == 0)) return;
1357 - if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1358 -
1359 - if (command.meshname && command.meshname != '' && command.meshname != mesh.name) { change = 'Mesh name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; }
1360 - if (command.desc != null && command.desc != mesh.desc) { if (change != '') change += ' and description changed'; else change += 'Mesh "' + mesh.name + '" description changed'; mesh.desc = command.desc; }
1361 - if (change != '') { obj.db.Set(mesh); obj.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }) }
1362 - }
1363 - break;
1364 - }
1365 - case 'addmeshuser':
1366 - {
1367 - // Check if the user exists
1368 - var newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(), newuser = obj.users[newuserid];
1369 - if (newuser == null) {
1370 - // TODO: Send error back, user not found.
1371 - break;
1372 - }
1373 -
1374 - // Get the mesh
1375 - var mesh = obj.meshes[command.meshid], change = '';
1376 - if (mesh) {
1377 - // Check if this user has rights to do this
1378 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
1379 - if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1380 -
1381 - // Add mesh to user
1382 - if (newuser.links == null) newuser.links = {};
1383 - newuser.links[command.meshid] = { rights: command.meshadmin };
1384 - obj.db.Set(newuser);
1385 - obj.parent.DispatchEvent([newuser._id], obj, 'resubscribe');
1386 -
1387 - // Add a user to the mesh
1388 - mesh.links[newuserid] = { name: command.username, rights: command.meshadmin };
1389 - obj.db.Set(mesh);
1390 -
1391 - // Notify mesh change
1392 - var change = 'Added user ' + command.username + ' to mesh ' + mesh.name;
1393 - obj.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, { etype: 'mesh', username: user.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id })
1394 - }
1395 - break;
1396 - }
1397 - case 'removemeshuser':
1398 - {
1399 - if ((command.userid.split('/').length != 3) || (command.userid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1400 -
1401 - // Check if the user exists
1402 - var deluserid = command.userid, deluser = obj.users[deluserid];
1403 - if (deluser == null) {
1404 - // TODO: Send error back, user not found.
1405 - break;
1406 - }
1407 -
1408 - // Get the mesh
1409 - var mesh = obj.meshes[command.meshid];
1410 - if (mesh) {
1411 - // Check if this user has rights to do this
1412 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
1413 -
1414 - // Remove mesh from user
1415 - if (deluser.links != null && deluser.links[command.meshid] != null) {
1416 - var delmeshrights = deluser.links[command.meshid].rights;
1417 - if ((delmeshrights == 0xFFFFFFFF) && (mesh.links[user._id].rights != 0xFFFFFFFF)) return; // A non-admin can't kick out an admin
1418 - delete deluser.links[command.meshid];
1419 - obj.db.Set(deluser);
1420 - obj.parent.DispatchEvent([deluser._id], obj, 'resubscribe');
1421 - }
1422 -
1423 - // Remove user from the mesh
1424 - if (mesh.links[command.userid] != null) {
1425 - delete mesh.links[command.userid];
1426 - obj.db.Set(mesh);
1427 - }
1428 -
1429 - // Notify mesh change
1430 - var change = 'Removed user ' + deluser.name + ' from mesh ' + mesh.name;
1431 - obj.parent.DispatchEvent(['*', mesh._id, user._id, command.userid], obj, { etype: 'mesh', username: user.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id })
1432 - }
1433 - break;
1434 - }
1435 - case 'addamtdevice':
1436 - {
1437 - if (obj.args.wanonly == true) return; // This is a WAN-only server, local Intel AMT computers can't be added
1438 -
1439 - if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1440 -
1441 - // Get the mesh
1442 - var mesh = obj.meshes[command.meshid];
1443 - if (mesh) {
1444 - if (mesh.mtype != 1) return; // This operation is only allowed for mesh type 1, Intel AMT agentless mesh.
1445 -
1446 - // Check if this user has rights to do this
1447 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
1448 -
1449 - // Create a new nodeid
1450 - obj.crypto.randomBytes(48, function (err, buf) {
1451 - // create the new node
1452 - var nodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');;
1453 - var device = { type: 'node', mtype: 1, _id: nodeid, meshid: command.meshid, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: parseInt(command.amttls) } };
1454 - obj.db.Set(device);
1455 -
1456 - // Event the new node
1457 - var device2 = obj.common.Clone(device);
1458 - delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
1459 - var change = 'Added device ' + command.devicename + ' to mesh ' + mesh.name;
1460 - obj.parent.DispatchEvent(['*', command.meshid], obj, { etype: 'node', username: user.name, action: 'addnode', node: device2, msg: change, domain: domain.id })
1461 - });
1462 - }
1463 - break;
1464 - }
1465 - case 'scanamtdevice':
1466 - {
1467 - if (obj.args.wanonly == true) return; // This is a WAN-only server, this type of scanning is not allowed.
1468 -
1469 - // Ask the RMCP scanning to scan a range of IP addresses
1470 - if (obj.parent.amtScanner) {
1471 - if (obj.parent.amtScanner.performRangeScan(ws.userid, command.range) == false) {
1472 - obj.parent.DispatchEvent(['*', ws.userid], obj, { action: 'scanamtdevice', range: command.range, results: null, nolog: 1 });
1473 - }
1474 - }
1475 - break;
1476 - }
1477 - case 'removedevices':
1478 - {
1479 - for (var i in command.nodeids) {
1480 - var nodeid = command.nodeids[i];
1481 - if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1482 -
1483 - // Get the device
1484 - obj.db.Get(nodeid, function (err, nodes) {
1485 - if (nodes.length != 1) return;
1486 - var node = nodes[0];
1487 -
1488 - // Get the mesh for this device
1489 - var mesh = obj.meshes[node.meshid];
1490 - if (mesh) {
1491 - // Check if this user has rights to do this
1492 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
1493 -
1494 - // Delete this node including network interface information and events
1495 - obj.db.Remove(node._id);
1496 - obj.db.Remove('if' + node._id);
1497 -
1498 - // Event node deletion
1499 - var change = 'Removed device ' + node.name + ' from mesh ' + mesh.name;
1500 - obj.parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', username: user.name, action: 'removenode', nodeid: node._id, msg: change, domain: domain.id })
1501 -
1502 - // Disconnect all connections if needed
1503 - var state = obj.parent.GetConnectivityState(command.nodeid);
1504 - if ((state != null) && (state.connectivity != null)) {
1505 - if ((state.connectivity & 1) != 0) { obj.wsagents[command.nodeid].close(); } // Disconnect mesh agent
1506 - if ((state.connectivity & 2) != 0) { obj.parent.mpsserver.close(obj.parent.mpsserver.ciraConnections[command.nodeid]); } // Disconnect CIRA connection
1507 - }
1508 - }
1509 - });
1510 - }
1511 -
1512 - break;
1513 - }
1514 - case 'wakedevices':
1515 - {
1516 - // TODO: INPUT VALIDATION!!!
1517 - // TODO: We can optimize this a lot.
1518 - // - We should get a full list of all MAC's to wake first.
1519 - // - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan.
1520 - for (var i in command.nodeids) {
1521 - var nodeid = command.nodeids[i], wakeActions = 0;
1522 - if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain
1523 - // Get the device
1524 - obj.db.Get(nodeid, function (err, nodes) {
1525 - if (nodes.length != 1) return;
1526 - var node = nodes[0];
1527 -
1528 - // Get the mesh for this device
1529 - var mesh = obj.meshes[node.meshid];
1530 - if (mesh) {
1531 -
1532 - // Check if this user has rights to do this
1533 - if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 64) != 0)) {
1534 -
1535 - // Get the device interface information
1536 - obj.db.Get('if' + node._id, function (err, nodeifs) {
1537 - if (nodeifs.length == 1) {
1538 - var nodeif = nodeifs[0];
1539 - var macs = [];
1540 - for (var i in nodeif.netif) { if (nodeif.netif[i].mac) { macs.push(nodeif.netif[i].mac); } }
1541 -
1542 - // Have the server send a wake-on-lan packet (Will not work in WAN-only)
1543 - if (obj.parent.meshScanner != null) { obj.parent.meshScanner.wakeOnLan(macs); wakeActions++; }
1544 -
1545 - // Get the list of mesh this user as access to
1546 - var targetMeshes = [];
1547 - for (var i in user.links) { targetMeshes.push(i); }
1548 -
1549 - // Go thru all the connected agents and send wake-on-lan on all the ones in the target mesh list
1550 - for (var i in obj.wsagents) {
1551 - var agent = obj.wsagents[i];
1552 - if ((targetMeshes.indexOf(agent.dbMeshKey) >= 0) && (agent.authenticated == 2)) {
1553 - //console.log('Asking agent ' + agent.dbNodeKey + ' to wake ' + macs.join(','));
1554 - agent.send(JSON.stringify({ action: 'wakeonlan', macs: macs }));
1555 - wakeActions++;
1556 - }
1557 - }
1558 - }
1559 - });
1560 -
1561 - }
1562 - }
1563 - });
1564 - }
1565 - // Confirm we may be doing something (TODO)
1566 - ws.send(JSON.stringify({ action: 'wakedevices' }));
1567 - }
1568 -
1569 - break;
1570 - }
1571 - case 'poweraction':
1572 - {
1573 - // TODO: INPUT VALIDATION!!!
1574 - for (var i in command.nodeids) {
1575 - var nodeid = command.nodeids[i], powerActions = 0;
1576 - if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain
1577 - // Get the device
1578 - obj.db.Get(nodeid, function (err, nodes) {
1579 - if (nodes.length != 1) return;
1580 - var node = nodes[0];
1581 -
1582 - // Get the mesh for this device
1583 - var mesh = obj.meshes[node.meshid];
1584 - if (mesh) {
1585 -
1586 - // Check if this user has rights to do this
1587 - if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 8) != 0)) { // "Remote Control permission"
1588 -
1589 - // Get this device
1590 - var agent = obj.wsagents[node._id];
1591 - if (agent != null) {
1592 - // Send the power command
1593 - agent.send(JSON.stringify({ action: 'poweraction', actiontype: command.actiontype }));
1594 - powerActions++;
1595 - }
1596 - }
1597 - }
1598 - });
1599 - }
1600 - // Confirm we may be doing something (TODO)
1601 - ws.send(JSON.stringify({ action: 'poweraction' }));
1602 - }
1603 - break;
1604 - }
1605 - case 'getnetworkinfo':
1606 - {
1607 - // Argument validation
1608 - if ((command.nodeid == null) || (typeof command.nodeid != 'string') || (command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1609 -
1610 - // Get the device
1611 - obj.db.Get(command.nodeid, function (err, nodes) {
1612 - if (nodes.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
1613 - var node = nodes[0];
1614 -
1615 - // Get the mesh for this device
1616 - var mesh = obj.meshes[node.meshid];
1617 - if (mesh) {
1618 - // Check if this user has rights to do this
1619 - if (mesh.links[user._id] == null || (mesh.links[user._id].rights == 0)) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
1620 -
1621 - // Get network information about this node
1622 - obj.db.Get('if' + command.nodeid, function (err, netinfos) {
1623 - if (netinfos.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
1624 - var netinfo = netinfos[0];
1625 - ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, updateTime: netinfo.updateTime, netif: netinfo.netif }));
1626 - });
1627 - }
1628 - });
1629 - break;
1630 - }
1631 - case 'changedevice':
1632 - {
1633 - // Argument validation
1634 - if ((command.nodeid == null) || (typeof command.nodeid != 'string') || (command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1635 - if ((command.userloc) && (command.userloc.length != 2) && (command.userloc.length != 0)) return;
1636 -
1637 - // Change the device
1638 - obj.db.Get(command.nodeid, function (err, nodes) {
1639 - if (nodes.length != 1) return;
1640 - var node = nodes[0];
1641 -
1642 - // Get the mesh for this device
1643 - var mesh = obj.meshes[node.meshid];
1644 - if (mesh) {
1645 - // Check if this user has rights to do this
1646 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
1647 -
1648 - // Ready the node change event
1649 - var changes = [], change = 0, event = { etype: 'node', username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id };
1650 - event.msg = ": ";
1651 -
1652 - // Look for a change
1653 - if (command.icon && (command.icon != node.icon)) { change = 1; node.icon = command.icon; changes.push('icon'); }
1654 - if (command.name && (command.name != node.name)) { change = 1; node.name = command.name; changes.push('name'); }
1655 - if (command.host && (command.host != node.host)) { change = 1; node.host = command.host; changes.push('host'); }
1656 - if (command.userloc && ((node.userloc == null) || (command.userloc[0] != node.userloc[0]) || (command.userloc[1] != node.userloc[1]))) {
1657 - change = 1;
1658 - if ((command.userloc.length == 0) && (node.userloc)) {
1659 - delete node.userloc;
1660 - changes.push('location removed');
1661 - } else {
1662 - command.userloc.push((Math.floor((new Date()) / 1000)));
1663 - node.userloc = command.userloc.join(',');
1664 - changes.push('location');
1665 - }
1666 - }
1667 - if (command.desc != null && (command.desc != node.desc)) { change = 1; node.desc = command.desc; changes.push('description'); }
1668 - if (command.intelamt != null) {
1669 - if ((command.intelamt.user != null) && (command.intelamt.pass != undefined) && ((command.intelamt.user != node.intelamt.user) || (command.intelamt.pass != node.intelamt.pass))) { change = 1; node.intelamt.user = command.intelamt.user; node.intelamt.pass = command.intelamt.pass; changes.push('Intel AMT credentials'); }
1670 - if (command.intelamt.tls && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); }
1671 - }
1672 -
1673 - if (change == 1) {
1674 - // Save the node
1675 - obj.db.Set(node);
1676 -
1677 - // Event the node change
1678 - event.msg = 'Changed device ' + node.name + ' from mesh ' + mesh.name + ': ' + changes.join(', ');
1679 - var node2 = obj.common.Clone(node);
1680 - if (node2.intelamt && node2.intelamt.pass) delete node2.intelamt.pass; // Remove the Intel AMT password before eventing this.
1681 - event.node = node2;
1682 - obj.parent.DispatchEvent(['*', node.meshid], obj, event);
1683 - }
1684 - }
1685 - });
1686 - break;
1687 - }
1688 - case 'uploadagentcore':
1689 - {
1690 - if (user.siteadmin != 0xFFFFFFFF) break;
1691 - if (command.path) {
1692 - if (command.path == '*') {
1693 - // Update the server default core and send a core hash request
1694 - // Load default mesh agent core if present, then perform a core update
1695 - parent.updateMeshCore(function () { sendMeshAgentCore(user, domain, command.nodeid, '*'); });
1696 - } else {
1697 - // Send a mesh agent core to the mesh agent
1698 - var file = getServerFilePath(user, domain, command.path);
1699 - if (file != null) {
1700 - readEntireTextFile(file.fullpath, function (data) {
1701 - if (data != null) {
1702 - data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
1703 - sendMeshAgentCore(user, domain, command.nodeid, data);
1704 - }
1705 - })
1706 - }
1707 - }
1708 - } else {
1709 - // Clear the mesh agent core on the mesh agent
1710 - sendMeshAgentCore(user, domain, command.nodeid, null);
1711 - }
1712 - break;
1713 - }
1714 - case 'agentdisconnect':
1715 - {
1716 - // Force mesh agent disconnection
1717 - forceMeshAgentDisconnect(user, domain, command.nodeid, command.disconnectMode);
1718 - break;
1719 - }
1720 - case 'close':
1721 - {
1722 - // Close the web socket session
1723 - if (req.session && req.session.ws && req.session.ws == ws) delete req.session.ws;
1724 - try { ws.close(); } catch (e) { }
1725 - break;
1726 - }
1727 - }
1728 - });
1729 -
1730 - // If error, do nothing
1731 - ws.on('error', function (err) { console.log(err); });
1732 -
1733 - // If the web socket is closed
1734 - ws.on('close', function (req) {
1735 - obj.parent.RemoveAllEventDispatch(ws);
1736 - if (req.session && req.session.ws && req.session.ws == ws) { delete req.session.ws; }
1737 - if (obj.wssessions2[ws.sessionId]) { delete obj.wssessions2[ws.sessionId]; }
1738 - if (obj.wssessions[ws.userid]) {
1739 - var i = obj.wssessions[ws.userid].indexOf(ws);
1740 - if (i >= 0) {
1741 - obj.wssessions[ws.userid].splice(i, 1);
1742 - var user = obj.users[ws.userid];
1743 - if (user) {
1744 - if (obj.parent.multiServer == null) {
1745 - obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[ws.userid].length, nolog: 1, domain: domain.id })
1746 - } else {
1747 - obj.recountSessions(ws.sessionId); // Recount sessions
1748 - }
1749 - }
1750 - if (obj.wssessions[ws.userid].length == 0) { delete obj.wssessions[ws.userid]; }
1751 - }
1752 - }
1753 -
1754 - // If we have peer servers, inform them of the disconnected session
1755 - if (obj.parent.multiServer != null) { obj.parent.multiServer.DispatchMessage({ action: 'sessionEnd', sessionid: ws.sessionId }); }
1756 - });
1757 -
1758 - // Send user information to web socket, this is the first thing we send
1759 - var userinfo = obj.common.Clone(obj.users[req.session.userid]);
1760 - delete userinfo.salt;
1761 - delete userinfo.hash;
1762 - ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo }));
1763 -
1764 - // Next, send server information
1765 - if (obj.args.notls == true) {
1766 - ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: { name: obj.certificates.CommonName, mpsport: obj.args.mpsport, mpspass: obj.args.mpspass, port: obj.args.port, https: false } }));
1767 - } else {
1768 - ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: { name: obj.certificates.CommonName, mpsport: obj.args.mpsport, mpspass: obj.args.mpspass, redirport: obj.args.redirport, port: obj.args.port, https: true } }));
1769 - }
1770 - } catch (e) { console.log(e); }
1771 - }
981
982 // Handle Intel AMT events
983 // To subscribe, add "http://server:port/amtevents.ashx" to Intel AMT subscriptions.
@@ -1954,55 +1163,6 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1163 });
1164 }
1165
1957 - function updateUserFiles(user, ws, domain) {
1958 - // Request the list of server files
1959 - var files = { action: 'files', filetree: { n : 'Root', f : {} } };
1960 -
1961 - // Add user files
1962 - files.filetree.f[user._id] = { t: 1, n: 'My Files', f: {} };
1963 - files.filetree.f[user._id].maxbytes = getQuota(user._id, domain);
1964 - var usersplit = user._id.split('/'), domainx = 'domain';
1965 - if (usersplit[1].length > 0) domainx = 'domain-' + usersplit[1];
1966 -
1967 - // Read all files recursively
1968 - try {
1969 - files.filetree.f[user._id].f = readFilesRec(obj.path.join(obj.filespath, domainx + "/user-" + usersplit[2]));
1970 - } catch (e) {
1971 - // Got an error, try to create all the folders and try again...
1972 - try { obj.fs.mkdirSync(obj.filespath); } catch (e) { }
1973 - try { obj.fs.mkdirSync(obj.path.join(obj.filespath, domainx)); } catch (e) { }
1974 - try { obj.fs.mkdirSync(obj.path.join(obj.filespath, domainx + "/user-" + usersplit[2])); } catch (e) { }
1975 - try { obj.fs.mkdirSync(obj.path.join(obj.filespath, domainx + "/user-" + usersplit[2] + "/Public")); } catch (e) { }
1976 - try { files.filetree.f[user._id].f = readFilesRec(obj.path.join(obj.filespath, domainx + "/user-" + usersplit[2])); } catch (e) { }
1977 - }
1978 -
1979 - // Add files for each mesh
1980 - for (var i in user.links) {
1981 - if ((user.links[i].rights & 32) != 0) { // Check that we have file permissions
1982 - var mesh = obj.meshes[i];
1983 - if (mesh) {
1984 - var meshsplit = mesh._id.split('/');
1985 - files.filetree.f[mesh._id] = { t: 1, n: mesh.name, f: {} };
1986 - files.filetree.f[mesh._id].maxbytes = getQuota(mesh._id, domain);
1987 -
1988 - // Read all files recursively
1989 - try {
1990 - files.filetree.f[mesh._id].f = readFilesRec(obj.path.join(__dirname, "files/" + domainx + "/mesh-" + meshsplit[2]));
1991 - } catch (e) {
1992 - // Got an error, try to create all the folders and try again...
1993 - try { obj.fs.mkdirSync(obj.filespath); } catch (e) { }
1994 - try { obj.fs.mkdirSync(obj.path.join(obj.filespath, domainx)); } catch (e) { }
1995 - try { obj.fs.mkdirSync(obj.path.join(obj.filespath, domainx + "/mesh-" + meshsplit[2])); } catch (e) { }
1996 - try { files.filetree.f[mesh._id].f = readFilesRec(obj.path.join(obj.filespath, domainx + "/mesh-" + meshsplit[2])); } catch (e) { }
1997 - }
1998 - }
1999 - }
2000 - }
2001 -
2002 - // Respond
2003 - ws.send(JSON.stringify(files));
2004 - }
2005 -
1166 // Add HTTP security headers to all responses
1167 obj.app.use(function (req, res, next) {
1168 // Two more headers to take a look at:
@@ -2037,7 +1197,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1197 obj.app.post(url + 'amtevents.ashx', obj.handleAmtEventRequest);
1198 obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
1199 obj.app.ws(url + 'webrelay.ashx', handleRelayWebSocket);
2040 - obj.app.ws(url + 'control.ashx', handleControlRequest);
1200 + obj.app.ws(url + 'control.ashx', function (ws, req) { try { var domain = checkUserIpAddress(ws, req); if (domain != null) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws, req, obj.args, domain); } } catch (e) { console.log(e); } });
1201 obj.app.get(url + 'meshagents', obj.handleMeshAgentRequest);
1202 obj.app.get(url + 'meshsettings', obj.handleMeshSettingsRequest);
1203 obj.app.get(url + 'downloadfile.ashx', handleDownloadFile);
@@ -2090,11 +1250,11 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1250
1251 // Check we have agent rights
1252 var rights = user.links[agent.dbMeshKey].rights;
2093 - if ((rights != null) && ((rights & 16) != 0) && (user.siteadmin == 0xFFFFFFFF)) { agent.close(disconnectMode); }
1253 + if ((rights != null) && ((rights & MESHRIGHT_AGENTCONSOLE) != 0) && (user.siteadmin == 0xFFFFFFFF)) { agent.close(disconnectMode); }
1254 }
1255
1256 // Send the core module to the mesh agent
2097 - function sendMeshAgentCore(user, domain, nodeid, core) {
1257 + obj.sendMeshAgentCore = function(user, domain, nodeid, core) {
1258 if (nodeid == null) return;
1259 var splitnode = nodeid.split('/');
1260 if ((splitnode.length != 3) || (splitnode[1] != domain.id)) return; // Check that nodeid is valid and part of our domain
@@ -2103,7 +1263,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1263
1264 // Check we have agent rights
1265 var rights = user.links[agent.dbMeshKey].rights;
2106 - if ((rights != null) && ((rights & 16) != 0) && (user.siteadmin == 0xFFFFFFFF)) {
1266 + if ((rights != null) && ((rights & MESHRIGHT_AGENTCONSOLE) != 0) && (user.siteadmin == 0xFFFFFFFF)) {
1267 if (core == null) {
1268 // Clear the mesh agent core
1269 agent.agentCoreCheck = 1000; // Tell the agent object we are not using a custom core.
@@ -2122,25 +1282,6 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1282 }
1283 }
1284 }
2125 -
2126 - // Return the maximum number of bytes allowed in the user account "My Files".
2127 - function getQuota(objid, domain) {
2128 - if (objid == null) return 0;
2129 - if (objid.startsWith('user/')) {
2130 - var user = obj.users[objid];
2131 - if (user == null) return 0;
2132 - if ((user.quota != null) && (typeof user.quota == 'number')) { return user.quota; }
2133 - if ((domain != null) && (domain.userQuota != null) && (typeof domain.userQuota == 'number')) { return domain.userQuota; }
2134 - return 1048576; // By default, the server will have a 1 meg limit on user accounts
2135 - } else if (objid.startsWith('mesh/')) {
2136 - var mesh = obj.meshes[objid];
2137 - if (mesh == null) return 0;
2138 - if ((mesh.quota != null) && (typeof mesh.quota == 'number')) { return mesh.quota; }
2139 - if ((domain != null) && (domain.meshQuota != null) && (typeof domain.meshQuota == 'number')) { return domain.meshQuota; }
2140 - return 1048576; // By default, the server will have a 1 meg limit on mesh accounts
2141 - }
2142 - return 0;
2143 - }
1285
1286 // Get the server path of a user or mesh object
1287 function getServerRootFilePath(obj) {
@@ -2150,20 +1291,6 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1291 if (obj.domain !== '') domainname = 'domain-' + obj.domain;
1292 return obj.path.join(obj.filespath, domainname + "/" + splitname[0] + "-" + splitname[2]);
1293 }
2153 -
2154 - // Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
2155 - function getServerFilePath(user, domain, path) {
2156 - var splitpath = path.split('/'), serverpath = obj.path.join(obj.filespath, 'domain'), filename = '';
2157 - if ((splitpath.length < 3) || (splitpath[0] != 'user' && splitpath[0] != 'mesh') || (splitpath[1] != domain.id)) return null; // Basic validation
2158 - var objid = splitpath[0] + '/' + splitpath[1] + '/' + splitpath[2];
2159 - if (splitpath[0] == 'user' && (objid != user._id)) return null; // User validation, only self allowed
2160 - if (splitpath[0] == 'mesh') { var link = user.links[objid]; if ((link == null) || (link.rights == null) || ((link.rights & 32) == 0)) { return null; } } // Check mesh server file rights
2161 - if (splitpath[1] != '') { serverpath += '-' + splitpath[1]; } // Add the domain if needed
2162 - serverpath += ('/' + splitpath[0] + '-' + splitpath[2]);
2163 - for (var i = 3; i < splitpath.length; i++) { if (obj.common.IsFilenameValid(splitpath[i]) == true) { serverpath += '/' + splitpath[i]; filename = splitpath[i]; } else { return null; } } // Check that each folder is correct
2164 - var fullpath = obj.path.resolve(obj.filespath, serverpath), quota = 0;
2165 - return { fullpath: fullpath, path: serverpath, name: filename, quota: getQuota(objid, domain) };
2166 - }
1294
1295 // Read entire file and return it in callback function
1296 function readEntireTextFile(filepath, func) {
@@ -2264,5 +1391,39 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1391 }
1392 }
1393
1394 + // Generate a cryptographic key used to encode and decode cookies
1395 + obj.generateCookieKey = function () {
1396 + return new Buffer(obj.crypto.randomBytes(32), 'binary');
1397 + //return Buffer.alloc(32, 0); // Sets the key to zeros, debug only.
1398 + }
1399 +
1400 + // Encode an object as a cookie using a key. (key must be 32 bytes long)
1401 + obj.encodeCookie = function (o, key) {
1402 + try {
1403 + if (key == null) { key = obj.serverKey; }
1404 + o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time
1405 + var iv = new Buffer(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key, iv);
1406 + var crypted = Buffer.concat([cipher.update(JSON.stringify(o), 'utf8'), cipher.final()]);
1407 + return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
1408 + } catch (e) { return null; }
1409 + }
1410 +
1411 + // Decode a cookie back into an object using a key. Return null if it's not a valid cookie. (key must be 32 bytes long)
1412 + obj.decodeCookie = function (cookie, key) {
1413 + try {
1414 + if (key == null) { key = obj.serverKey; }
1415 + cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64');
1416 + var decipher = obj.crypto.createDecipheriv('aes-256-gcm', key, cookie.slice(0, 12));
1417 + decipher.setAuthTag(cookie.slice(12, 16));
1418 + var o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
1419 + if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { return null; }
1420 + o.time = o.time * 1000; // Decode the cookie creation time
1421 + o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created (in milliseconds)
1422 + if ((o.dtime > 120000) || (o.dtime < 30000)) return null; // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
1423 + return o;
1424 + } catch (e) { return null; }
1425 + }
1426 +
1427 + obj.serverKey = obj.generateCookieKey();
1428 return obj;
1429 }