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"> </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"> </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"> </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"> </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> </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
+ ::
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® 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>
295
+ <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Left" onclick="drotate(-1)">↺</div>
296
+ <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Right" onclick="drotate(1)">↻</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"> 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> <input type=button id=connectbutton1 value="Connect" onclick=connectDesktop(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
308
+ <span id=connectbutton1hspan> <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> <input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false"></span>
310
+ <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
+
329
+ <input id="DeskCAD" type="button" value="Ctrl-Alt-Del" onkeypress="return false" onkeydown="return false" onclick="sendCAD()">
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® 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"> <input type="button" id="connectbutton2" value="Connect" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
354
+ <span id="connectbutton2hspan"> <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"> <input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false"></span>
356
+ <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()">
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()">
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()" />
377
+ </div>
378
+ <div>
379
+
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>
297
- <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Left" onclick="drotate(-1)">↺</div>
298
- <div style='cursor:pointer;border:none;float:right;font-size:130%;margin-right:4px' title="Rotate Right" onclick="drotate(1)">↻</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"> 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> <input type=button id=connectbutton1 value="Connect" onclick=connectDesktop(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
310
- <span id=connectbutton1hspan> <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> <input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false"></span>
312
- <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
-
331
- <input id="DeskCAD" type="button" value="Ctrl-Alt-Del" onkeypress="return false" onkeydown="return false" onclick="sendCAD()">
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® 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"> <input type="button" id="connectbutton2" value="Connect" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled"></span>
356
- <span id="connectbutton2hspan"> <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"> <input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false"></span>
358
- <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()">
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()">
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()" />
379
- </div>
380
- <div>
381
-
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" />
412
- <input type=button id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="Select All" onkeypress="return false" onkeydown="return false" />
413
- <input type=button id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false" />
414
- <input type=button id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false" />
415
- <input type=button id=p13NewFolderButton disabled="disabled" value="New Folder" onclick="p13createfolder()" onkeypress="return false" onkeydown="return false" />
416
- <input type=button id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false" />
417
- <input type=button id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false" />
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> <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>✓</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>✗</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"> <span id="bottomstatus"></span></td></tr>
444
- </table>
445
- </div>
446
- <div id=p14 style=display:none>
447
- <h1><span id=p14deviceName></span> - Intel® 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> </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 & 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" />
410
+ <input type=button id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="Select All" onkeypress="return false" onkeydown="return false" />
411
+ <input type=button id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false" />
412
+ <input type=button id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false" />
413
+ <input type=button id=p13NewFolderButton disabled="disabled" value="New Folder" onclick="p13createfolder()" onkeypress="return false" onkeydown="return false" />
414
+ <input type=button id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false" />
415
+ <input type=button id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false" />
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> <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>✓</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>✗</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"> <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® 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" />
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> </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 & 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® 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" />
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® 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® 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> ';
2400
- if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '")>Location</a> ';
2415
+ if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '") title="Show device network interface information">Interfaces</a> ';
2416
+ if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '") title="Show device locations information">Location</a> ';
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> '; }
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
}