Added RDP ClickOnce support.

This commit is contained in:
Ylian Saint-Hilaire 2017-10-23 14:09:58 -07:00
parent 5108b344be
commit 3801159200
28 changed files with 1825 additions and 1336 deletions

View File

@ -33,9 +33,11 @@
<Compile Include="meshcentral.js" />
<Compile Include="meshagent.js" />
<Compile Include="meshrelay.js" />
<Compile Include="meshuser.js" />
<Compile Include="mpsserver.js" />
<Compile Include="multiserver.js" />
<Compile Include="pass.js" />
<Compile Include="public\relay.js" />
<Compile Include="public\scripts\amt-0.2.0.js" />
<Compile Include="public\scripts\agent-desktop-0.0.2.js" />
<Compile Include="public\scripts\amt-desktop-0.0.2.js" />
@ -54,6 +56,15 @@
<Compile Include="redirserver.js" />
<Compile Include="webserver.js" />
<Content Include="package.json" />
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.application" />
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.config.deploy" />
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.deploy" />
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.manifest" />
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.ico.deploy" />
<Content Include="public\clickonce\minirouter\MeshMiniRouter.application" />
<Content Include="public\clickonce\minirouter\publish.htm" />
<Content Include="public\commander.htm" />
<Content Include="public\favicon.ico" />
<Content Include="public\images-isdu\ComputerIcon.png" />
<Content Include="public\images-isdu\ComputerIcon2.png" />
<Content Include="public\images-isdu\IntelLogo.png" />
@ -71,6 +82,40 @@
<Content Include="public\images-isdu\info.gif" />
<Content Include="public\images-isdu\warning.gif" />
<Content Include="public\images-isdu\isdu.ico" />
<Content Include="public\images\dot.png" />
<Content Include="public\images\icon-addnew.png" />
<Content Include="public\images\icon-graph.png" />
<Content Include="public\images\icon-installmesh.png" />
<Content Include="public\images\icon-manage.png" />
<Content Include="public\images\icon-remove.png" />
<Content Include="public\images\icons16.png" />
<Content Include="public\images\icons200-1-1.png" />
<Content Include="public\images\icons200-2-1.png" />
<Content Include="public\images\icons200-3-1.png" />
<Content Include="public\images\icons200-4-1.png" />
<Content Include="public\images\icons200-5-1.png" />
<Content Include="public\images\icons200-6-1.png" />
<Content Include="public\images\icons50.png" />
<Content Include="public\images\images16.png" />
<Content Include="public\images\info.png" />
<Content Include="public\images\link1.png" />
<Content Include="public\images\link2.png" />
<Content Include="public\images\link3.png" />
<Content Include="public\images\link4.png" />
<Content Include="public\images\logoback.png" />
<Content Include="public\images\mainaccount.png" />
<Content Include="public\images\mainwelcome.png" />
<Content Include="public\images\mapmarker-gps.png" />
<Content Include="public\images\mapmarker-ip.png" />
<Content Include="public\images\mapmarker-user.png" />
<Content Include="public\images\mapmarker-wifi.png" />
<Content Include="public\images\mapmarker.png" />
<Content Include="public\images\meshicon50.png" />
<Content Include="public\images\trash.png" />
<Content Include="public\index.html" />
<Content Include="public\relay.htm" />
<Content Include="public\scriptblocks.txt" />
<Content Include="public\sounds\chimes.mp3" />
<Content Include="public\styles\font-awesome\css\font-awesome.min.css" />
<Content Include="public\styles\font-awesome\fonts\fontawesome-webfont.eot" />
<Content Include="public\styles\font-awesome\fonts\fontawesome-webfont.svg" />
@ -108,6 +153,7 @@
<Content Include="public\styles\font-awesome\scss\_variables.scss" />
<Content Include="public\styles\style.css" />
<Content Include="readme.txt" />
<Content Include="SourceFileList.txt" />
<Content Include="views\default.handlebars" />
<Content Include="views\download.handlebars" />
<Content Include="views\login.handlebars" />
@ -116,8 +162,14 @@
<ItemGroup>
<Folder Include="agents\" />
<Folder Include="public" />
<Folder Include="public\clickonce\" />
<Folder Include="public\clickonce\minirouter\" />
<Folder Include="public\clickonce\minirouter\Application Files\" />
<Folder Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\" />
<Folder Include="public\images-isdu" />
<Folder Include="public\images\" />
<Folder Include="public\scripts\" />
<Folder Include="public\sounds\" />
<Folder Include="public\styles\" />
<Folder Include="public\styles\font-awesome\" />
<Folder Include="public\styles\font-awesome\css\" />

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -18,7 +18,7 @@ function createMeshCore(agent) {
var obj = {};
// MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
obj.meshCoreInfo = "MeshCore v3k";
obj.meshCoreInfo = "MeshCore v4";
obj.meshCoreCapabilities = 14; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript
obj.useNativePipes = true; //(process.platform == 'win32');
var meshServerConnectionState = 0;
@ -28,6 +28,7 @@ function createMeshCore(agent) {
var lastPublicLocationInfo = null;
var selfInfoUpdateTimer = null;
var http = require('http');
var net = require('net');
var fs = require('fs');
var rtc = require('ILibWebRTC');
var wifiScannerLib = null;
@ -157,7 +158,9 @@ function createMeshCore(agent) {
var w = arguments[i];
if (w != null) {
while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
if (i != 0) {
while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
}
x.push(w);
}
}
@ -283,12 +286,9 @@ function createMeshCore(agent) {
processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
}
}
else if (data.type == 'tunnel') { // Process a new tunnel connection request
if (data.value && data.sessionid) {
else if ((data.type == 'tunnel') && (data.value != null)) { // Process a new tunnel connection request
// Create a new tunnel object
sendConsoleText(data.value);
var xurl = getServerTargetUrlEx(data.value);
sendConsoleText(xurl);
if (xurl != null) {
var tunnel = http.request(http.parseUri(xurl));
tunnel.upgrade = onTunnelUpgrade;
@ -297,6 +297,8 @@ function createMeshCore(agent) {
tunnel.state = 0;
tunnel.url = xurl;
tunnel.protocol = 0;
tunnel.tcpaddr = data.tcpaddr;
tunnel.tcpport = data.tcpport;
// Put the tunnel in the tunnels list
var index = 1;
@ -307,7 +309,6 @@ function createMeshCore(agent) {
sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
}
}
}
break;
}
case 'wakeonlan': {
@ -339,7 +340,7 @@ function createMeshCore(agent) {
// Called when a file changed in the file system
function onFileWatcher(a, b) {
//console.log('onFileWatcher', a, b, this.path);
console.log('onFileWatcher', a, b, this.path);
var response = getDirectoryInfo(this.path);
if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
}
@ -364,6 +365,7 @@ function createMeshCore(agent) {
if (reqpath == '') { reqpath = '/'; }
var xpath = obj.path.join(reqpath, '*');
var results = null;
try { results = fs.readdirSync(xpath); } catch (e) { }
if (results != null) {
for (var i = 0; i < results.length; ++i) {
@ -385,7 +387,38 @@ function createMeshCore(agent) {
}
// Tunnel callback operations
function onTunnelUpgrade(response, s, head) { sendConsoleText('onTunnelUpgrade'); this.s = s; s.httprequest = this; s.end = onTunnelClosed; s.data = onTunnelData; }
function onTunnelUpgrade(response, s, head) {
this.s = s;
s.httprequest = this;
s.end = onTunnelClosed;
if (this.tcpport != null) {
// This is a TCP relay connection, pause now and try to connect to the target.
s.pause();
s.data = onTcpRelayServerTunnelData;
var connectionOptions = { port: parseInt(this.tcpport) };
if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
s.tcprelay.peerindex = this.index;
} else {
// This is a normal connect for KVM/Terminal/Files
s.data = onTunnelData;
}
}
// Called when the TCP relay target is connected
function onTcpRelayTargetTunnelConnect() {
var peerTunnel = tunnels[this.peerindex];
this.pipe(peerTunnel.s); // Pipe Target --> Server
peerTunnel.s.first = true;
peerTunnel.s.resume();
}
// Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
function onTcpRelayServerTunnelData(data) {
if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target
}
function onTunnelClosed() {
sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
if (this.httprequest.protocol == 1) { this.httprequest.process.end(); delete this.httprequest.process; }
@ -404,6 +437,7 @@ function createMeshCore(agent) {
}
function onTunnelSendOk() { sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid); }
function onTunnelData(data) {
console.log("OnTunnelData");
// If this is upload data, save it to file
if (this.httprequest.uploadFile) {
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) {
try { cmd = JSON.parse(data); } catch (e) { };
if ((cmd == null) || (cmd.action == undefined)) { return; }
if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
sendConsoleText(JSON.stringify(cmd));
//console.log(objToString(cmd, 0, '.'));
console.log(objToString(cmd, 0, '.'));
switch (cmd.action) {
case 'ls': {
// Close the watcher if required
@ -534,9 +567,7 @@ function createMeshCore(agent) {
}
// Send the folder content to the browser
sendConsoleText(JSON.stringify(cmd.path));
var response = getDirectoryInfo(cmd.path);
sendConsoleText(JSON.stringify(response));
if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
this.write(JSON.stringify(response));

View File

@ -362,6 +362,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Route a message.
// If this command has a sessionid, that is the target.
if (command.sessionid != null) {
if (typeof command.sessionid != 'string') break;
var splitsessionid = command.sessionid.split('/');
// Check that we are in the same domain and the user has rights over this node.
if ((splitsessionid[0] == 'user') && (splitsessionid[1] == domain.id)) {
@ -384,6 +385,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}
}
} else if (command.userid != null) { // If this command has a userid, that is the target.
if (typeof command.userid != 'string') break;
var splituserid = command.userid.split('/');
// Check that we are in the same domain and the user has rights over this node.
if ((splituserid[0] == 'user') && (splituserid[1] == domain.id)) {

View File

@ -10,20 +10,77 @@ module.exports.CreateMeshRelay = function (parent, ws, req) {
obj.req = req;
obj.peer = null;
obj.parent = parent;
obj.id = req.query['id'];
obj.id = req.query.id;
obj.remoteaddr = obj.ws._socket.remoteAddress;
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
if (obj.id == undefined) { obj.ws.close(); obj.id = null; return null; } // Attempt to connect without id, drop this.
ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
// Disconnect this agent
obj.close = function (arg) {
if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'Relay: Soft disconnect (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket
if (arg == 2) { try { obj.ws._socket._parent.end(); obj.parent.parent.debug(1, 'Relay: Hard disconnect (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
}
obj.sendAgentMessage = function (command, userid, domainid) {
if (command.nodeid == null) return false;
var user = obj.parent.users[userid];
if (user == null) return false;
var splitnodeid = command.nodeid.split('/');
// Check that we are in the same domain and the user has rights over this node.
if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domainid)) {
// Get the user object
// See if the node is connected
var agent = obj.parent.wsagents[command.nodeid];
if (agent != null) {
// Check if we have permission to send a message to that node
var rights = user.links[agent.dbMeshKey];
if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
command.sessionid = ws.sessionId; // Set the session id, required for responses.
command.rights = rights.rights; // Add user rights flags to the message
delete command.nodeid; // Remove the nodeid since it's implyed.
agent.send(JSON.stringify(command));
return true;
}
} else {
// Check if a peer server is connected to this agent
var routing = obj.parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type
if (routing != null) {
// Check if we have permission to send a message to that node
var rights = user.links[routing.meshid];
if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
command.fromSessionid = ws.sessionId; // Set the session id, required for responses.
command.rights = rights.rights; // Add user rights flags to the message
obj.parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
return true;
}
}
}
}
return false;
}
if (req.query.auth == null) {
// Use ExpressJS session, check if this session is a logged in user, at least one of the two connections will need to be authenticated.
try { if ((req.session) && (req.session.userid) || (req.session.domainid == getDomain(req).id)) { obj.authenticated = true; } } catch (e) { }
} else {
// Get the session from the cookie
if ((obj.parent.parent.multiServer != null) && (obj.parent.parent.multiServer.decodeCookie(req.query.auth) != null)) { obj.authenticated = true; }
var cookie = obj.parent.parent.webserver.decodeCookie(req.query.auth);
if (cookie != null) {
obj.authenticated = true;
if (cookie.tcpport != null) {
// This cookie has agent routing instructions, process this here.
if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
// Send connection request to agent
var command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr };
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 + ')'); }
}
} else {
obj.id = null;
obj.parent.parent.debug(1, 'Relay: invalid cookie (' + obj.remoteaddr + ')');
}
}
if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this.
ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
// Validate that the id is valid, we only need to do this on non-authenticated sessions.
// TODO: Figure out when this needs to be done.

View File

@ -23,7 +23,6 @@ module.exports.CreateMeshScanner = function (parent) {
// Get a list of IPv4 and IPv6 interface addresses
function getInterfaceList() {
var ipv4 = ['*'], ipv6 = ['*']; // Bind to IN_ADDR_ANY always
//if (parent.platform == 'win32') { // On Windows, also bind to each interface seperatly (TODO: REMOVE THIS AND TEST ON LINUX!!!!!!!!!!!!!!!!!!)
var interfaces = require('os').networkInterfaces();
for (var i in interfaces) {
var interface = interfaces[i];
@ -35,7 +34,6 @@ module.exports.CreateMeshScanner = function (parent) {
}
}
}
//}
return { ipv4: ipv4, ipv6: ipv6 };
}
@ -57,7 +55,7 @@ module.exports.CreateMeshScanner = function (parent) {
server4.xxtype = 4;
server4.xxlocal = localAddress;
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]; });
var bindOptions = { port: 16989, exclusive: false };
var bindOptions = { port: 16989, exclusive: true };
if (server4.xxlocal != '*') { bindOptions.address = server4.xxlocal; }
server4.bind(bindOptions, function () {
try {
@ -68,7 +66,7 @@ module.exports.CreateMeshScanner = function (parent) {
this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
obj.performScan(this);
obj.performScan(this);
} catch (e) { }
} catch (e) { console.log(e); }
});
obj.servers4[localAddress] = server4;
} catch (e) {
@ -84,12 +82,13 @@ module.exports.CreateMeshScanner = function (parent) {
obj.servers6[localAddress].xxclear = false;
} else {
// Create a new IPv6 server
try {
var server6 = obj.dgram.createSocket({ type: 'udp6', reuseAddr: true });
server6.xxclear = false;
server6.xxtype = 6;
server6.xxlocal = localAddress;
server6.on('error', function (err) { this.close(); delete obj.servers6[this.xxlocal]; });
var bindOptions = { port: 16989, exclusive: false };
var bindOptions = { port: 16989, exclusive: true };
if (server6.xxlocal != '*') { bindOptions.address = server6.xxlocal; }
server6.bind(bindOptions, function () {
try {
@ -100,9 +99,12 @@ module.exports.CreateMeshScanner = function (parent) {
this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
obj.performScan(this);
obj.performScan(this);
} catch (e) { }
} catch (e) { console.log(e); }
});
obj.servers6[localAddress] = server6;
} catch (e) {
console.log(e);
}
}
}

931
meshuser.js Normal file
View File

@ -0,0 +1,931 @@
/**
* @description Meshcentral MeshAgent
* @author Ylian Saint-Hilaire & Bryan Roe
* @version v0.0.1
*/
// Construct a MeshAgent object, called upon connection
module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
var obj = {};
obj.db = db;
obj.ws = ws;
obj.fs = parent.fs;
obj.args = args;
obj.parent = parent;
obj.domain = domain;
obj.common = parent.common;
// Send a message to the user
//obj.send = function (data) { try { if (typeof data == 'string') { obj.ws.send(new Buffer(data, 'binary')); } else { obj.ws.send(data); } } catch (e) { } }
// Disconnect this user
obj.close = function (arg) {
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
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
}
try {
// Check if the user is logged in
if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) { try { obj.ws.close(); } catch (e) { } return; }
req.session.ws = obj.ws; // Associate this websocket session with the web session
req.session.ws.userid = req.session.userid;
req.session.ws.domainid = domain.id;
var user = obj.parent.users[req.session.userid];
if (user == null) { try { obj.ws.close(); } catch (e) { } return; }
// Add this web socket session to session list
obj.ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2);
obj.parent.wssessions2[ws.sessionId] = obj.ws;
if (!obj.parent.wssessions[user._id]) { obj.parent.wssessions[user._id] = [ws]; } else { obj.parent.wssessions[user._id].push(obj.ws); }
if (obj.parent.parent.multiServer == null) {
obj.parent.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.parent.wssessions[user._id].length, nolog: 1, domain: obj.domain.id })
} else {
obj.parent.recountSessions(obj.ws.sessionId); // Recount sessions
}
// If we have peer servers, inform them of the new session
if (obj.parent.parent.multiServer != null) { obj.parent.parent.multiServer.DispatchMessage({ action: 'sessionStart', sessionid: obj.ws.sessionId }); }
// Handle events
obj.ws.HandleEvent = function (source, event) {
if (!event.domain || event.domain == obj.domain.id) {
try {
if (event == 'close') { obj.req.session.destroy(); obj.ws.close(); }
else if (event == 'resubscribe') { user.subscriptions = obj.parent.subscribe(user._id, ws); }
else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); }
else { ws.send(JSON.stringify({ action: 'event', event: event })); }
} catch (e) { }
}
}
user.subscriptions = obj.parent.subscribe(user._id, ws); // Subscribe to events
obj.ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
// When data is received from the web socket
ws.on('message', function (msg) {
var user = obj.parent.users[req.session.userid];
var command = JSON.parse(msg.toString('utf8'))
switch (command.action) {
case 'meshes':
{
// Request a list of all meshes this user as rights to
var docs = [];
for (var i in user.links) { if (obj.parent.meshes[i]) { docs.push(obj.parent.meshes[i]); } }
ws.send(JSON.stringify({ action: 'meshes', meshes: docs }));
break;
}
case 'nodes':
{
// Request a list of all meshes this user as rights to
var links = [];
for (var i in user.links) { links.push(i); }
// Request a list of all nodes
obj.db.GetAllTypeNoTypeFieldMeshFiltered(links, domain.id, 'node', function (err, docs) {
var r = {};
for (var i in docs) {
// Add the connection state
var state = obj.parent.parent.GetConnectivityState(docs[i]._id);
if (state) {
docs[i].conn = state.connectivity;
docs[i].pwr = state.powerState;
if ((state.connectivity & 1) != 0) { var agent = obj.parent.wsagents[docs[i]._id]; if (agent != null) { docs[i].agct = agent.connectTime; } }
if ((state.connectivity & 2) != 0) { var cira = obj.parent.parent.mpsserver.ciraConnections[docs[i]._id]; if (cira != null) { docs[i].cict = cira.tag.connectTime; } }
}
// Compress the meshid's
var meshid = docs[i].meshid;
if (!r[meshid]) { r[meshid] = []; }
delete docs[i].meshid;
r[meshid].push(docs[i]);
}
ws.send(JSON.stringify({ action: 'nodes', nodes: r }));
});
break;
}
case 'powertimeline':
{
// Query the database for the power timeline for a given node
// The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ]
obj.db.getPowerTimeline(command.nodeid, function (err, docs) {
if (err == null && docs.length > 0) {
var timeline = [], time = null, previousPower;
for (var i in docs) {
var doc = docs[i];
if (time == null) {
// First element
time = doc.time;
if (doc.oldPower) { timeline.push(doc.oldPower); } else { timeline.push(0); }
timeline.push(time);
timeline.push(doc.power);
previousPower = doc.power;
} else {
// Delta element
if ((previousPower != doc.power) && ((doc.time - time) > 60000)) { // To boost speed, any blocks less than a minute get approximated.
// Create a new timeline
timeline.push(doc.time - time);
timeline.push(doc.power);
time = doc.time;
previousPower = doc.power;
} else {
// Extend the previous timeline
if ((timeline.length >= 6) && (timeline[timeline.length - 3] == doc.power)) { // We can merge the block with the previous block
timeline[timeline.length - 4] += (timeline[timeline.length - 2] + (doc.time - time));
timeline.pop();
timeline.pop();
} else { // Extend the last block in the timeline
timeline[timeline.length - 2] += (doc.time - time);
timeline[timeline.length - 1] = doc.power;
}
time = doc.time;
previousPower = doc.power;
}
}
}
ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline }));
} else {
// No records found, send current state if we have it
var state = obj.parent.parent.GetConnectivityState(command.nodeid);
if (state != null) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState] })); }
}
});
break;
}
case 'files':
{
// Send the full list of server files to the browser app
if ((user.siteadmin & 8) != 0) { updateUserFiles(user, ws, domain); }
break;
}
case 'fileoperation':
{
// Check permissions
if ((user.siteadmin & 8) != 0) {
// Perform a file operation (Create Folder, Delete Folder, Delete File...)
if ((command.path != null) && (typeof command.path == 'object') && command.path.length > 0) {
var rootfolder = command.path[0];
var rootfoldersplit = rootfolder.split('/'), domainx = 'domain';
if (rootfoldersplit[1].length > 0) domainx = 'domain-' + rootfoldersplit[1];
var path = obj.path.join(obj.filespath, domainx + "/" + rootfoldersplit[0] + "-" + rootfoldersplit[2]);
for (var i = 1; i < command.path.length; i++) { if (obj.common.IsFilenameValid(command.path[i]) == false) { path = null; break; } path += ("/" + command.path[i]); }
if (path == null) break;
if ((command.fileop == 'createfolder') && (obj.common.IsFilenameValid(command.newfolder) == true)) { try { obj.fs.mkdirSync(path + "/" + command.newfolder); } catch (e) { } } // Create a new folder
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
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
obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles') // Fire an event causing this user to update this files
}
}
break;
}
case 'msg':
{
// Route a message.
// This this command has a nodeid, that is the target.
if (command.nodeid != null) {
var splitnodeid = command.nodeid.split('/');
// Check that we are in the same domain and the user has rights over this node.
if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) {
// See if the node is connected
var agent = obj.parent.wsagents[command.nodeid];
if (agent != null) {
// Check if we have permission to send a message to that node
var rights = user.links[agent.dbMeshKey];
if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
command.sessionid = ws.sessionId; // Set the session id, required for responses.
command.rights = rights.rights; // Add user rights flags to the message
delete command.nodeid; // Remove the nodeid since it's implyed.
agent.send(JSON.stringify(command));
}
} else {
// Check if a peer server is connected to this agent
var routing = obj.parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type
if (routing != null) {
// Check if we have permission to send a message to that node
var rights = user.links[routing.meshid];
if (rights != null || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking
command.fromSessionid = ws.sessionId; // Set the session id, required for responses.
command.rights = rights.rights; // Add user rights flags to the message
obj.parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
}
}
}
}
}
break;
}
case 'events':
{
// Send the list of events for this session
obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs })); });
break;
}
case 'clearevents':
{
// Delete all events
if (user.siteadmin != 0xFFFFFFFF) break;
obj.db.RemoveAllEvents(domain.id);
obj.parent.parent.DispatchEvent(['*', 'server-global'], obj, { action: 'clearevents', nolog: 1, domain: domain.id })
break;
}
case 'users':
{
// Request a list of all users
if ((user.siteadmin & 2) == 0) break;
var docs = [];
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]); } }
ws.send(JSON.stringify({ action: 'users', users: docs }));
break;
}
case 'wssessioncount':
{
// Request a list of all web socket user session count
var wssessions = {};
if ((user.siteadmin & 2) == 0) break;
if (obj.parent.parent.multiServer == null) {
// No peering, use simple session counting
for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } }
} else {
// We have peer servers, use more complex session counting
for (var userid in obj.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.sessionsCount[userid]; } }
}
ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions })); // wssessions is: userid --> count
break;
}
case 'deleteuser':
{
// Delete a user account
if ((user.siteadmin & 2) == 0) break;
var delusername = command.username, deluserid = command.userid, deluser = obj.parent.users[deluserid];
if ((deluser.siteadmin != null) && (deluser.siteadmin > 0) && (user.siteadmin != 0xFFFFFFFF)) break; // Need full admin to remote another administrator
if ((deluserid.split('/').length != 3) || (deluserid.split('/')[1] != domain.id)) break; // Invalid domain, operation only valid for current domain
// Delete all files on the server for this account
try {
var deluserpath = obj.parent.getServerRootFilePath(deluser);
if (deluserpath != null) { obj.parent.deleteFolderRec(deluserpath); }
} catch (e) { }
obj.db.Remove(deluserid);
delete obj.parent.users[deluserid];
obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: delusername, action: 'accountremove', msg: 'Account removed', domain: domain.id })
obj.parent.parent.DispatchEvent([deluserid], obj, 'close');
break;
}
case 'adduser':
{
// Add a new user account
if ((user.siteadmin & 2) == 0) break;
var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase();
if (newusername == '~') break; // This is a reserved user name
if (!obj.parent.users[newuserid]) {
var newuser = { type: 'user', _id: newuserid, name: newusername, email: command.email, creation: Date.now(), domain: domain.id };
obj.parent.users[newuserid] = newuser;
// Create a user, generate a salt and hash the password
obj.parent.hash(command.pass, function (err, salt, hash) {
if (err) throw err;
newuser.salt = salt;
newuser.hash = hash;
obj.db.SetUser(newuser);
var newuser2 = obj.common.Clone(newuser);
if (newuser2.subscriptions) { delete newuser2.subscriptions; }
if (newuser2.salt) { delete newuser2.salt; }
if (newuser2.hash) { delete newuser2.hash; }
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 })
});
}
break;
}
case 'edituser':
{
// Edit a user account, may involve changing email or administrator permissions
if (((user.siteadmin & 2) != 0) || (user.name == command.name)) {
var chguserid = 'user/' + domain.id + '/' + command.name.toLowerCase(), chguser = obj.parent.users[chguserid], change = 0;
if (chguser) {
if (command.email && chguser.email != command.email) { chguser.email = command.email; change = 1; }
if (command.quota != chguser.quota) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; }
if ((user.siteadmin == 0xFFFFFFFF) && (command.siteadmin != null) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1 }
if (change == 1) {
obj.db.Set(chguser);
obj.parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe');
var chguser2 = obj.common.Clone(chguser);
delete chguser2.salt;
delete chguser2.hash;
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 })
}
}
}
break;
}
case 'serverversion':
{
// Check the server version
if ((user.siteadmin & 16) == 0) break;
obj.parent.parent.getLatestServerVersion(function (currentVersion, latestVersion) { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); });
break;
}
case 'serverupdate':
{
// Perform server update
if ((user.siteadmin & 16) == 0) break;
obj.parent.parent.performServerUpdate();
break;
}
case 'createmesh':
{
// Create mesh
// TODO: Right now, we only create type 1 Agent-less Intel AMT mesh, or type 2 Agent mesh
if ((command.meshtype == 1) || (command.meshtype == 2)) {
// Create a type 1 agent-less Intel AMT mesh.
obj.crypto.randomBytes(48, function (err, buf) {
var meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');;
var links = {}
links[user._id] = { name: user.name, rights: 0xFFFFFFFF };
var mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links };
obj.db.Set(mesh);
obj.parent.meshes[meshid] = mesh;
obj.parent.parent.AddEventDispatch([meshid], ws);
if (user.links == null) user.links = {};
user.links[meshid] = { rights: 0xFFFFFFFF };
user.subscriptions = obj.parent.subscribe(user._id, ws);
obj.db.SetUser(user);
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 })
});
}
break;
}
case 'deletemesh':
{
// Delete a mesh and all computers within it
obj.db.Get(command.meshid, function (err, meshes) {
if (meshes.length != 1) return;
var mesh = meshes[0];
// Check if this user has rights to do this
if (mesh.links[user._id] == null || mesh.links[user._id].rights != 0xFFFFFFFF) return;
if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
// Fire the removal event first, because after this, the event will not route
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 })
// Remove all user links to this mesh
for (var i in meshes) {
var links = meshes[i].links;
for (var j in links) {
var xuser = obj.parent.users[j];
delete xuser.links[meshes[i]._id];
obj.db.Set(xuser);
obj.parent.parent.DispatchEvent([xuser._id], obj, 'resubscribe');
}
}
// Delete all files on the server for this mesh
try {
var meshpath = getServerRootFilePath(mesh);
if (meshpath != null) { deleteFolderRec(meshpath); }
} catch (e) { }
obj.parent.parent.RemoveEventDispatchId(command.meshid); // Remove all subscriptions to this mesh
obj.db.RemoveMesh(command.meshid); // Remove mesh from database
delete obj.parent.meshes[command.meshid]; // Remove mesh from memory
});
break;
}
case 'editmesh':
{
// Change the name or description of a mesh
var mesh = obj.parent.meshes[command.meshid], change = '';
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 1) == 0)) return;
if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
if (command.meshname && command.meshname != '' && command.meshname != mesh.name) { change = 'Mesh name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; }
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; }
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 }) }
}
break;
}
case 'addmeshuser':
{
// Check if the user exists
var newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(), newuser = obj.parent.users[newuserid];
if (newuser == null) {
// TODO: Send error back, user not found.
break;
}
// Get the mesh
var mesh = obj.parent.meshes[command.meshid], change = '';
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
// Add mesh to user
if (newuser.links == null) newuser.links = {};
newuser.links[command.meshid] = { rights: command.meshadmin };
obj.db.Set(newuser);
obj.parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe');
// Add a user to the mesh
mesh.links[newuserid] = { name: command.username, rights: command.meshadmin };
obj.db.Set(mesh);
// Notify mesh change
var change = 'Added user ' + command.username + ' to mesh ' + mesh.name;
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 })
}
break;
}
case 'removemeshuser':
{
if ((command.userid.split('/').length != 3) || (command.userid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
// Check if the user exists
var deluserid = command.userid, deluser = obj.parent.users[deluserid];
if (deluser == null) {
// TODO: Send error back, user not found.
break;
}
// Get the mesh
var mesh = obj.parent.meshes[command.meshid];
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
// Remove mesh from user
if (deluser.links != null && deluser.links[command.meshid] != null) {
var delmeshrights = deluser.links[command.meshid].rights;
if ((delmeshrights == 0xFFFFFFFF) && (mesh.links[user._id].rights != 0xFFFFFFFF)) return; // A non-admin can't kick out an admin
delete deluser.links[command.meshid];
obj.db.Set(deluser);
obj.parent.parent.DispatchEvent([deluser._id], obj, 'resubscribe');
}
// Remove user from the mesh
if (mesh.links[command.userid] != null) {
delete mesh.links[command.userid];
obj.db.Set(mesh);
}
// Notify mesh change
var change = 'Removed user ' + deluser.name + ' from mesh ' + mesh.name;
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 })
}
break;
}
case 'addamtdevice':
{
if (obj.args.wanonly == true) return; // This is a WAN-only server, local Intel AMT computers can't be added
if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
// Get the mesh
var mesh = obj.parent.meshes[command.meshid];
if (mesh) {
if (mesh.mtype != 1) return; // This operation is only allowed for mesh type 1, Intel AMT agentless mesh.
// Check if this user has rights to do this
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
// Create a new nodeid
obj.crypto.randomBytes(48, function (err, buf) {
// create the new node
var nodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');;
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) } };
obj.db.Set(device);
// Event the new node
var device2 = obj.common.Clone(device);
delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
var change = 'Added device ' + command.devicename + ' to mesh ' + mesh.name;
obj.parent.parent.DispatchEvent(['*', command.meshid], obj, { etype: 'node', username: user.name, action: 'addnode', node: device2, msg: change, domain: domain.id })
});
}
break;
}
case 'scanamtdevice':
{
if (obj.args.wanonly == true) return; // This is a WAN-only server, this type of scanning is not allowed.
// Ask the RMCP scanning to scan a range of IP addresses
if (obj.parent.parent.amtScanner) {
if (obj.parent.parent.amtScanner.performRangeScan(ws.userid, command.range) == false) {
obj.parent.parent.DispatchEvent(['*', ws.userid], obj, { action: 'scanamtdevice', range: command.range, results: null, nolog: 1 });
}
}
break;
}
case 'removedevices':
{
for (var i in command.nodeids) {
var nodeid = command.nodeids[i];
if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
// Get the device
obj.db.Get(nodeid, function (err, nodes) {
if (nodes.length != 1) return;
var node = nodes[0];
// Get the mesh for this device
var mesh = obj.parent.meshes[node.meshid];
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
// Delete this node including network interface information and events
obj.db.Remove(node._id);
obj.db.Remove('if' + node._id);
// Event node deletion
var change = 'Removed device ' + node.name + ' from mesh ' + mesh.name;
obj.parent.parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', username: user.name, action: 'removenode', nodeid: node._id, msg: change, domain: domain.id })
// Disconnect all connections if needed
var state = obj.parent.parent.GetConnectivityState(command.nodeid);
if ((state != null) && (state.connectivity != null)) {
if ((state.connectivity & 1) != 0) { obj.parent.wsagents[command.nodeid].close(); } // Disconnect mesh agent
if ((state.connectivity & 2) != 0) { obj.parent.parent.mpsserver.close(obj.parent.parent.mpsserver.ciraConnections[command.nodeid]); } // Disconnect CIRA connection
}
}
});
}
break;
}
case 'wakedevices':
{
// TODO: INPUT VALIDATION!!!
// TODO: We can optimize this a lot.
// - We should get a full list of all MAC's to wake first.
// - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan.
for (var i in command.nodeids) {
var nodeid = command.nodeids[i], wakeActions = 0;
if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain
// Get the device
obj.db.Get(nodeid, function (err, nodes) {
if (nodes.length != 1) return;
var node = nodes[0];
// Get the mesh for this device
var mesh = obj.parent.meshes[node.meshid];
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 64) != 0)) {
// Get the device interface information
obj.db.Get('if' + node._id, function (err, nodeifs) {
if (nodeifs.length == 1) {
var nodeif = nodeifs[0];
var macs = [];
for (var i in nodeif.netif) { if (nodeif.netif[i].mac) { macs.push(nodeif.netif[i].mac); } }
// Have the server send a wake-on-lan packet (Will not work in WAN-only)
if (obj.parent.parent.meshScanner != null) { obj.parent.parent.meshScanner.wakeOnLan(macs); wakeActions++; }
// Get the list of mesh this user as access to
var targetMeshes = [];
for (var i in user.links) { targetMeshes.push(i); }
// Go thru all the connected agents and send wake-on-lan on all the ones in the target mesh list
for (var i in obj.parent.wsagents) {
var agent = obj.parent.wsagents[i];
if ((targetMeshes.indexOf(agent.dbMeshKey) >= 0) && (agent.authenticated == 2)) {
//console.log('Asking agent ' + agent.dbNodeKey + ' to wake ' + macs.join(','));
agent.send(JSON.stringify({ action: 'wakeonlan', macs: macs }));
wakeActions++;
}
}
}
});
}
}
});
}
// Confirm we may be doing something (TODO)
ws.send(JSON.stringify({ action: 'wakedevices' }));
}
break;
}
case 'poweraction':
{
// TODO: INPUT VALIDATION!!!
for (var i in command.nodeids) {
var nodeid = command.nodeids[i], powerActions = 0;
if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain
// Get the device
obj.db.Get(nodeid, function (err, nodes) {
if (nodes.length != 1) return;
var node = nodes[0];
// Get the mesh for this device
var mesh = obj.parent.meshes[node.meshid];
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 8) != 0)) { // "Remote Control permission"
// Get this device
var agent = obj.parent.wsagents[node._id];
if (agent != null) {
// Send the power command
agent.send(JSON.stringify({ action: 'poweraction', actiontype: command.actiontype }));
powerActions++;
}
}
}
});
}
// Confirm we may be doing something (TODO)
ws.send(JSON.stringify({ action: 'poweraction' }));
}
break;
}
case 'getnetworkinfo':
{
// Argument validation
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
// Get the device
obj.db.Get(command.nodeid, function (err, nodes) {
if (nodes.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
var node = nodes[0];
// Get the mesh for this device
var mesh = obj.parent.meshes[node.meshid];
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] == null || (mesh.links[user._id].rights == 0)) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
// Get network information about this node
obj.db.Get('if' + command.nodeid, function (err, netinfos) {
if (netinfos.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; }
var netinfo = netinfos[0];
ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, updateTime: netinfo.updateTime, netif: netinfo.netif }));
});
}
});
break;
}
case 'changedevice':
{
// Argument validation
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
if ((command.userloc) && (command.userloc.length != 2) && (command.userloc.length != 0)) return;
// Change the device
obj.db.Get(command.nodeid, function (err, nodes) {
if (nodes.length != 1) return;
var node = nodes[0];
// Get the mesh for this device
var mesh = obj.parent.meshes[node.meshid];
if (mesh) {
// Check if this user has rights to do this
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
// Ready the node change event
var changes = [], change = 0, event = { etype: 'node', username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id };
event.msg = ": ";
// Look for a change
if (command.icon && (command.icon != node.icon)) { change = 1; node.icon = command.icon; changes.push('icon'); }
if (command.name && (command.name != node.name)) { change = 1; node.name = command.name; changes.push('name'); }
if (command.host && (command.host != node.host)) { change = 1; node.host = command.host; changes.push('host'); }
if (command.userloc && ((node.userloc == null) || (command.userloc[0] != node.userloc[0]) || (command.userloc[1] != node.userloc[1]))) {
change = 1;
if ((command.userloc.length == 0) && (node.userloc)) {
delete node.userloc;
changes.push('location removed');
} else {
command.userloc.push((Math.floor((new Date()) / 1000)));
node.userloc = command.userloc.join(',');
changes.push('location');
}
}
if (command.desc != null && (command.desc != node.desc)) { change = 1; node.desc = command.desc; changes.push('description'); }
if (command.intelamt != null) {
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'); }
if (command.intelamt.tls && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); }
}
if (change == 1) {
// Save the node
obj.db.Set(node);
// Event the node change
event.msg = 'Changed device ' + node.name + ' from mesh ' + mesh.name + ': ' + changes.join(', ');
var node2 = obj.common.Clone(node);
if (node2.intelamt && node2.intelamt.pass) delete node2.intelamt.pass; // Remove the Intel AMT password before eventing this.
event.node = node2;
obj.parent.parent.DispatchEvent(['*', node.meshid], obj, event);
}
}
});
break;
}
case 'uploadagentcore':
{
if (user.siteadmin != 0xFFFFFFFF) break;
if (command.path) {
if (command.path == '*') {
// Update the server default core and send a core hash request
// Load default mesh agent core if present, then perform a core update
obj.parent.parent.updateMeshCore(function () { obj.parent.sendMeshAgentCore(user, domain, command.nodeid, '*'); });
} else {
// Send a mesh agent core to the mesh agent
var file = obj.parent.getServerFilePath(user, domain, command.path);
if (file != null) {
obj.parent.readEntireTextFile(file.fullpath, function (data) {
if (data != null) {
data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
obj.parent.sendMeshAgentCore(user, domain, command.nodeid, data);
}
})
}
}
} else {
// Clear the mesh agent core on the mesh agent
obj.parent.sendMeshAgentCore(user, domain, command.nodeid, null);
}
break;
}
case 'agentdisconnect':
{
// Force mesh agent disconnection
forceMeshAgentDisconnect(user, domain, command.nodeid, command.disconnectMode);
break;
}
case 'close':
{
// Close the web socket session
if (obj.req.session && obj.req.session.ws && obj.req.session.ws == ws) delete obj.req.session.ws;
try { ws.close(); } catch (e) { }
break;
}
case 'getcookie':
{
// Check if this user has rights on this nodeid
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 (???)
if (nodes.length == 1) {
var meshlinks = user.links[nodes[0].meshid];
if ((meshlinks) && (meshlinks.rights) && (meshlinks.rights & obj.parent.MESHRIGHT_REMOTECONTROL != 0)) {
// Add a user authentication cookie to a url
var cookieContent = { userid: user._id, domainid: user.domain };
if (command.nodeid) { cookieContent.nodeid = command.nodeid; }
if (command.tcpaddr) { cookieContent.tcpaddr = command.tcpaddr; } // Indicates the browser want to agent to TCP connect to a remote address
if (command.tcpport) { cookieContent.tcpport = command.tcpport; } // Indicates the browser want to agent to TCP connect to a remote port
command.cookie = obj.parent.encodeCookie(cookieContent);
ws.send(JSON.stringify(command));
}
}
});
break;
}
}
});
// If error, do nothing
ws.on('error', function (err) { console.log(err); });
// If the web socket is closed
ws.on('close', function (req) {
obj.parent.parent.RemoveAllEventDispatch(ws);
if (req.session && req.session.ws && req.session.ws == ws) { delete req.session.ws; }
if (obj.parent.wssessions2[ws.sessionId]) { delete obj.parent.wssessions2[ws.sessionId]; }
if (obj.parent.wssessions[ws.userid]) {
var i = obj.parent.wssessions[ws.userid].indexOf(ws);
if (i >= 0) {
obj.parent.wssessions[ws.userid].splice(i, 1);
var user = obj.parent.users[ws.userid];
if (user) {
if (obj.parent.parent.multiServer == null) {
obj.parent.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.parent.wssessions[ws.userid].length, nolog: 1, domain: obj.domain.id })
} else {
obj.parent.recountSessions(ws.sessionId); // Recount sessions
}
}
if (obj.parent.wssessions[ws.userid].length == 0) { delete obj.parent.wssessions[ws.userid]; }
}
}
// If we have peer servers, inform them of the disconnected session
if (obj.parent.parent.multiServer != null) { obj.parent.parent.multiServer.DispatchMessage({ action: 'sessionEnd', sessionid: ws.sessionId }); }
});
// Send user information to web socket, this is the first thing we send
var userinfo = obj.common.Clone(obj.parent.users[req.session.userid]);
delete userinfo.salt;
delete userinfo.hash;
ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo }));
// Next, send server information
if (obj.args.notls == true) {
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 } }));
} else {
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 } }));
}
} catch (e) { console.log(e); }
// Return the maximum number of bytes allowed in the user account "My Files".
function getQuota(objid, domain) {
if (objid == null) return 0;
if (objid.startsWith('user/')) {
var user = obj.parent.users[objid];
if (user == null) return 0;
if ((user.quota != null) && (typeof user.quota == 'number')) { return user.quota; }
if ((domain != null) && (domain.userQuota != null) && (typeof domain.userQuota == 'number')) { return domain.userQuota; }
return 1048576; // By default, the server will have a 1 meg limit on user accounts
} else if (objid.startsWith('mesh/')) {
var mesh = obj.parent.meshes[objid];
if (mesh == null) return 0;
if ((mesh.quota != null) && (typeof mesh.quota == 'number')) { return mesh.quota; }
if ((domain != null) && (domain.meshQuota != null) && (typeof domain.meshQuota == 'number')) { return domain.meshQuota; }
return 1048576; // By default, the server will have a 1 meg limit on mesh accounts
}
return 0;
}
// Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
function getServerFilePath(user, domain, path) {
var splitpath = path.split('/'), serverpath = obj.path.join(obj.filespath, 'domain'), filename = '';
if ((splitpath.length < 3) || (splitpath[0] != 'user' && splitpath[0] != 'mesh') || (splitpath[1] != domain.id)) return null; // Basic validation
var objid = splitpath[0] + '/' + splitpath[1] + '/' + splitpath[2];
if (splitpath[0] == 'user' && (objid != user._id)) return null; // User validation, only self allowed
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
if (splitpath[1] != '') { serverpath += '-' + splitpath[1]; } // Add the domain if needed
serverpath += ('/' + splitpath[0] + '-' + splitpath[2]);
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
var fullpath = obj.path.resolve(obj.filespath, serverpath), quota = 0;
return { fullpath: fullpath, path: serverpath, name: filename, quota: getQuota(objid, domain) };
}
// Read the folder and all sub-folders and serialize that into json.
function readFilesRec(path) {
var r = {}, dir = obj.fs.readdirSync(path);
for (var i in dir) {
var f = { t: 3, d: 111 };
var stat = obj.fs.statSync(path + '/' + dir[i])
if ((stat.mode & 0x004000) == 0) { f.s = stat.size; f.d = stat.mtime.getTime(); } else { f.t = 2; f.f = readFilesRec(path + '/' + dir[i]); }
r[dir[i]] = f;
}
return r;
}
function updateUserFiles(user, ws, domain) {
// Request the list of server files
var files = { action: 'files', filetree: { n: 'Root', f: {} } };
// Add user files
files.filetree.f[user._id] = { t: 1, n: 'My Files', f: {} };
files.filetree.f[user._id].maxbytes = getQuota(user._id, domain);
var usersplit = user._id.split('/'), domainx = 'domain';
if (usersplit[1].length > 0) domainx = 'domain-' + usersplit[1];
// Read all files recursively
try {
files.filetree.f[user._id].f = readFilesRec(obj.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2]));
} catch (e) {
// Got an error, try to create all the folders and try again...
try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
try { obj.fs.mkdirSync(obj.path.join(obj.parent.filespath, domainx)); } catch (e) { }
try { obj.fs.mkdirSync(obj.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2])); } catch (e) { }
try { obj.fs.mkdirSync(obj.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2] + "/Public")); } catch (e) { }
try { files.filetree.f[user._id].f = readFilesRec(obj.parent.path.join(obj.parent.filespath, domainx + "/user-" + usersplit[2])); } catch (e) { }
}
// Add files for each mesh
for (var i in user.links) {
if ((user.links[i].rights & 32) != 0) { // Check that we have file permissions
var mesh = obj.parent.meshes[i];
if (mesh) {
var meshsplit = mesh._id.split('/');
files.filetree.f[mesh._id] = { t: 1, n: mesh.name, f: {} };
files.filetree.f[mesh._id].maxbytes = getQuota(mesh._id, domain);
// Read all files recursively
try {
files.filetree.f[mesh._id].f = readFilesRec(obj.parent.path.join(__dirname, "files/" + domainx + "/mesh-" + meshsplit[2]));
} catch (e) {
// Got an error, try to create all the folders and try again...
try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (e) { }
try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx + "/mesh-" + meshsplit[2])); } catch (e) { }
try { files.filetree.f[mesh._id].f = readFilesRec(obj.parent.path.join(obj.parent.filespath, domainx + "/mesh-" + meshsplit[2])); } catch (e) { }
}
}
}
}
// Respond
ws.send(JSON.stringify(files));
}
return obj;
}

View File

@ -127,14 +127,14 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
// Send information about our server to the peer
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 })); }
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 })); }
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break;
}
case 4: {
// Server confirmed authentication, we are allowed to send commands to the server
obj.connectionState |= 8;
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 })); }
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 })); }
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break;
}
@ -313,7 +313,7 @@ module.exports.CreateMultiServer = function (parent, args) {
function completePeerServerConnection() {
if (obj.authenticated != 1) return;
obj.send(obj.common.ShortToStr(4));
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 }));
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 }));
obj.authenticated = 2;
}
@ -371,9 +371,6 @@ module.exports.CreateMultiServer = function (parent, args) {
if (obj.serverid == null) { obj.serverid = require("os").hostname(); }
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; }
// Generate a cryptographic key used to encode and decode cookies
obj.generateCookieKey = function () { return new Buffer(obj.crypto.randomBytes(32), 'binary'); }
// Return the private key of a peer server
obj.getServerCookieKey = function (serverid) {
var server = obj.peerServers[serverid];
@ -381,32 +378,6 @@ module.exports.CreateMultiServer = function (parent, args) {
return null;
}
// Encode an object as a cookie using a key. (key must be 32 bytes long)
obj.encodeCookie = function (o, key) {
try {
if (key == null) { key = obj.serverKey; }
o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time
var iv = new Buffer(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key, iv);
var crypted = Buffer.concat([cipher.update(JSON.stringify(o), 'utf8'), cipher.final()]);
return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
} catch (e) { return null; }
}
// 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)
obj.decodeCookie = function (cookie, key) {
try {
if (key == null) { key = obj.serverKey; }
cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64');
var decipher = obj.crypto.createDecipheriv('aes-256-gcm', key, cookie.slice(0, 12));
decipher.setAuthTag(cookie.slice(12, 16));
var o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { return null; }
o.time = o.time * 1000; // Decode the cookie creation time
o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created
return o;
} catch (e) { return null; }
}
// Dispatch an event to all other MeshCentral2 peer servers
obj.DispatchEvent = function (ids, source, event) {
var busmsg = JSON.stringify({ action: 'bus', ids: ids, event: event });
@ -658,7 +629,6 @@ module.exports.CreateMultiServer = function (parent, args) {
return peerTunnel;
}
obj.serverKey = obj.generateCookieKey();
setTimeout(function () { obj.ConnectToPeers(); }, 1000); // Delay this a little to make sure we are ready on our side.
return obj;
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<assemblyIdentity name="MeshMiniRouter.application" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" />
<description asmv2:publisher="Meshcentral.com" asmv2:product="Meshcentral Mini-Router" asmv2:supportUrl="https://meshcentral.com/" xmlns="urn:schemas-microsoft-com:asm.v1" />
<deployment install="false" mapFileExtensions="true" trustURLParameters="true" />
<compatibleFrameworks xmlns="urn:schemas-microsoft-com:clickonce.v2">
<framework targetVersion="4.5" profile="Full" supportedRuntime="4.0.30319" />
</compatibleFrameworks>
<dependency>
<dependentAssembly dependencyType="install" codebase="Application Files\MeshMiniRouter_2_0_0_7\MeshMiniRouter.exe.manifest" size="4711">
<assemblyIdentity name="MeshMiniRouter.exe" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" type="win32" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>VsPkLYR71TSqI8oGBBeXzb5hGi4R2xtQaGQJ0zmsTic=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
</asmv1:assembly>

View File

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="MeshMiniRouterTool.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
<section name="MeshMiniRouterTool.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
<section name="MeshNetworkRouterTool.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
</sectionGroup>
</configSections>
<userSettings>
<MeshMiniRouterTool.Properties.Settings>
<setting name="PuttyPath" serializeAs="String">
<value/>
</setting>
<setting name="Upgraded" serializeAs="String">
<value>False</value>
</setting>
<setting name="UltraVncPath" serializeAs="String">
<value/>
</setting>
<setting name="HybridCloudManager" serializeAs="String">
<value/>
</setting>
<setting name="TightVncPath" serializeAs="String">
<value/>
</setting>
<setting name="WinSCPPath" serializeAs="String">
<value/>
</setting>
</MeshMiniRouterTool.Properties.Settings>
<MeshMiniRouterTool.Settings1>
<setting name="puttypath" serializeAs="String">
<value/>
</setting>
</MeshMiniRouterTool.Settings1>
<MeshNetworkRouterTool.Properties.Settings>
<setting name="Server" serializeAs="String">
<value/>
</setting>
</MeshNetworkRouterTool.Properties.Settings>
</userSettings>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<asmv1:assemblyIdentity name="MeshMiniRouter.exe" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" type="win32" />
<description asmv2:iconFile="MeshMiniRouter.ico" xmlns="urn:schemas-microsoft-com:asm.v1" />
<application />
<entryPoint>
<assemblyIdentity name="MeshMiniRouter" version="1.0.6505.20092" language="neutral" processorArchitecture="msil" />
<commandLine file="MeshMiniRouter.exe" parameters="" />
</entryPoint>
<trustInfo>
<security>
<applicationRequestMinimum>
<PermissionSet version="1" class="System.Security.NamedPermissionSet" Name="Internet" Description="Default rights given to Internet applications" Unrestricted="true" ID="Custom" SameSite="site" />
<defaultAssemblyRequest permissionSetReference="Custom" />
</applicationRequestMinimum>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel node will disable file and registry virtualization.
If you want to utilize File and Registry Virtualization for backward
compatibility then delete the requestedExecutionLevel node.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentOS>
<osVersionInfo>
<os majorVersion="5" minorVersion="1" buildNumber="2600" servicePackMajor="0" />
</osVersionInfo>
</dependentOS>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.Windows.CommonLanguageRuntime" version="4.0.30319.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="MeshMiniRouter.exe" size="193536">
<assemblyIdentity name="MeshMiniRouter" version="1.0.6505.20092" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>G2b79xma2HCz/aTfnkt2L3I8c7mPbDDlU8IsdlFiDg8=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
<file name="MeshMiniRouter.exe.config" size="2397">
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>UylUFD4/o0KBti+5/eodTDgq4BkUJD0J0aUXNd3yHbY=</dsig:DigestValue>
</hash>
</file>
<file name="MeshMiniRouter.ico" size="1078">
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>mkjbDQuo7YXa8wZxdKEu/ECXrORwwtpRgNj8NBKbzHo=</dsig:DigestValue>
</hash>
</file>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->
<!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
</application>
</compatibility>
</asmv1:assembly>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<assemblyIdentity name="MeshMiniRouter.application" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" />
<description asmv2:publisher="Meshcentral.com" asmv2:product="Meshcentral Mini-Router" asmv2:supportUrl="https://meshcentral.com/" xmlns="urn:schemas-microsoft-com:asm.v1" />
<deployment install="false" mapFileExtensions="true" trustURLParameters="true" />
<compatibleFrameworks xmlns="urn:schemas-microsoft-com:clickonce.v2">
<framework targetVersion="4.5" profile="Full" supportedRuntime="4.0.30319" />
</compatibleFrameworks>
<dependency>
<dependentAssembly dependencyType="install" codebase="Application Files\MeshMiniRouter_2_0_0_7\MeshMiniRouter.exe.manifest" size="4711">
<assemblyIdentity name="MeshMiniRouter.exe" version="2.0.0.7" publicKeyToken="0000000000000000" language="neutral" processorArchitecture="msil" type="win32" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>VsPkLYR71TSqI8oGBBeXzb5hGi4R2xtQaGQJ0zmsTic=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
</asmv1:assembly>

View File

@ -0,0 +1,90 @@
<HTML>
<HEAD>
<TITLE>Meshcentral Mini-Router</TITLE>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8" />
<STYLE TYPE="text/css">
<!--
BODY{margin-top:20px; margin-left:20px; margin-right:20px; color:#000000; font-family:Tahoma; background-color:white}
A:link {font-weight:normal; color:#000066; text-decoration:none}
A:visited {font-weight:normal; color:#000066; text-decoration:none}
A:active {font-weight:normal; text-decoration:none}
A:hover {font-weight:normal; color:#FF6600; text-decoration:none}
P {margin-top:0px; margin-bottom:12px; color:#000000; font-family:Tahoma}
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}
TD {font-size:12px; color:#000000; font-family:Tahoma}
H2 {border-top: #003366 1px solid; margin-top:25px; font-weight:bold; font-size:1.5em; margin-bottom:10px; margin-left:-15px; color:#003366}
H3 {margin-top:10px; font-size: 1.1em; margin-bottom: 10px; margin-left: -15px; color: #000000}
UL {margin-top:10px; margin-left:20px}
OL {margin-top:10px; margin-left:20px}
LI {margin-top:10px; color: #000000}
FONT.value {font-weight:bold; color:darkblue}
FONT.key {font-weight: bold; color: darkgreen}
.divTag {border:1px; border-style:solid; background-color:#FFFFFF; text-decoration:none; height:auto; width:auto; background-color:#cecece}
.BannerColumn {background-color:#000000}
.Banner {border:0; padding:0; height:8px; margin-top:0px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#1c5280',EndColorStr='#FFFFFF');}
.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');}
.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');}
.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');}
.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');}
.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');}
.FooterRule {border:0; padding:0; height:1px; margin:0px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#4B3E1A',EndColorStr='#FFFFFF');}
.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}
.FooterText A:link {font-weight:normal; color:#999999; text-decoration:underline}
.FooterText A:visited {font-weight:normal; color:#999999; text-decoration:underline}
.FooterText A:active {font-weight:normal; color:#999999; text-decoration:underline}
.FooterText A:hover {font-weight:normal; color:#FF6600; text-decoration:underline}
.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}
.InstallTextStyle {font:bold; font-size:14pt; font-family:Tahoma; a:#FF0000; text-decoration:None}
.DetailsStyle {margin-left:30px}
.ItemStyle {margin-left:-15px; font-weight:bold}
.StartColorStr {background-color:#4B3E1A}
.JustThisApp A:link {font-weight:normal; color:#000066; text-decoration:underline}
.JustThisApp A:visited {font-weight:normal; color:#000066; text-decoration:underline}
.JustThisApp A:active {font-weight:normal; text-decoration:underline}
.JustThisApp A:hover {font-weight:normal; color:#FF6600; text-decoration:underline}
-->
</STYLE>
</HEAD>
<BODY>
<TABLE WIDTH="100%" CELLPADDING="0" CELLSPACING="2" BORDER="0">
<!-- Begin Banner -->
<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>
<!-- End Banner -->
<!-- Begin Dialog -->
<TR><TD ALIGN="LEFT"><TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0" WIDTH="540"><TR><TD WIDTH="496">
<!-- Begin AppInfo -->
<TABLE><TR><TD COLSPAN="3">&nbsp;</TD></TR><TR><TD><B>Name:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>Meshcentral Mini-Router</TD></TR><TR><TD COLSPAN="3">&nbsp;</TD></TR><TR><TD><B>Version:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>2.0.0.7</TD></TR><TR><TD COLSPAN="3">&nbsp;</TD></TR><TR><TD><B>Publisher:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>Meshcentral.com</TD></TR><tr><td colspan="3">&nbsp;</td></tr></TABLE>
<!-- End AppInfo -->
</TD></TR></TABLE>
<!-- Begin Buttons -->
<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>
<!-- End Buttons -->
</TD></TR>
<!-- End Dialog -->
<!-- Spacer Row -->
<TR><TD>&nbsp;</TD></TR>
<TR><TD>
<!-- Begin Footer -->
<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">
<A HREF="https://meshcentral.com">Meshcentral.com Customer Support</A>
&nbsp;&nbsp;&nbsp;::&nbsp;&nbsp;&nbsp;
<A HREF="http://go.microsoft.com/fwlink/?LinkId=154571">ClickOnce and .NET Framework Resources</A>
</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>
<!-- End Footer -->
</TD></TR>
</TABLE>
</BODY>
</HTML>

View File

@ -60,6 +60,9 @@ module.exports.CreateRedirServer = function (parent, db, args, certificates) {
obj.app.post(url + 'amtevents.ashx', obj.parent.webserver.handleAmtEventRequest);
obj.app.get(url + 'meshsettings', obj.parent.webserver.handleMeshSettingsRequest);
obj.app.get(url + 'meshagents', obj.parent.webserver.handleMeshAgentRequest);
// Indicates the clickonce folder is public
obj.app.use(url + 'clickonce', obj.express.static(obj.parent.path.join(__dirname, 'public/clickonce')));
}
// Find a free port starting with the specified one and going up.

View File

@ -280,15 +280,13 @@
<div id=p11 style=display:none>
<h1 id=p11deviceNameHeader><span id=p11deviceName></span> - Desktop</h1>
<div id="p14warning" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick="showFeaturesDlg()">
<div class="icon2" style="float:left;margin:7px" />
<div class=icon2 style="float:left;margin:7px"></div>
<div style='width:auto;border-radius:8px;padding:8px;background-color:lightsalmon'>Intel&reg; AMT Redirection port or KVM feature is disabled<span id="p14warninga">, click here to enable it.</span></div>
</div>
</div>
<div id="p14warning2" style='max-width:100%;display:none;cursor:pointer;margin-bottom:5px' onclick="showPowerActionDlg()">
<div class="icon2" style="float:left;margin:7px" />
<div class=icon2 style="float:left;margin:7px"></div>
<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>
</div>
</div>
<table cellpadding=0 cellspacing=0 style="width:100%;padding:0px;padding:0px;margin-top:0px">
<tr id=deskarea1>
<td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
@ -594,6 +592,8 @@
<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>
<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>
<audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3"></audio>
</div>
</div>
<script type="text/javascript">
var powerStatetable = ['', 'Powered', 'Sleep', 'Sleep', 'Sleep', 'Hibernating', 'Power off', 'Present'];
var StatusStrs = ['Disconnected', 'Connecting...', 'Setup...', 'Connected', 'Intel&reg; AMT Connected'];
@ -622,6 +622,7 @@
var serverPublicNamePort = "{{{serverDnsName}}}:{{{serverPublicPort}}}";
var amtScanResults = null;
var debugmode = false;
var clickOnce = detectClickOnce();
function startup() {
// Guard against other site's top frames (web bugs).
@ -720,6 +721,13 @@
}
}
// Return true if this browser supports clickonce
function detectClickOnce() {
for (var i in window.navigator.mimeTypes) { if (window.navigator.mimeTypes[i].type == "application/x-ms-application") { return true; } }
var userAgent = window.navigator.userAgent.toUpperCase();
return (userAgent.indexOf('.NET CLR 3.5') >= 0) || (userAgent.indexOf('(WINDOWS NT ') >= 0);
}
function updateSiteAdmin() {
var noServerBackup = {{{noServerBackup}}};
var siteRights = userinfo.siteadmin;
@ -883,6 +891,14 @@
events_update();
break;
}
case 'getcookie': {
if (message.tag == 'clickonce') {
var basicPort = "{{{serverRedirPort}}}"==""?"{{{serverPublicPort}}}":"{{{serverRedirPort}}}";
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";
window.open(rdpurl, '_blank');
}
break;
}
case 'event': {
if (!message.event.nolog) { events.unshift(message.event); events_update(); }
switch (message.event.action) {
@ -2394,10 +2410,14 @@
// Show bottom buttons
x = '<div style=float:right;font-size:x-small>';
if ((meshrights & 4) != 0) x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '")>Delete Device</a>';
if ((meshrights & 4) != 0) x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '") title="Remove this device">Delete Device</a>';
x += '</div><div style=font-size:x-small>';
if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '")>Interfaces</a>&nbsp;';
if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '")>Location</a>&nbsp;';
if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '") title="Show device network interface information">Interfaces</a>&nbsp;';
if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '") title="Show device locations information">Location</a>&nbsp;';
// RDP link, show this link only of the remote machine is Windows.
if (((connectivity & 1) != 0) && (clickOnce == true) && (mesh.mtype == 2) && ((meshrights & 8) != 0) && (node.agent.id > 0) && (node.agent.id < 5)) { x += '<a style=cursor:pointer onclick=p10clickOnce("' + node._id + '","RDP") title="Requires Microsoft ClickOnce support in your browser.">RDP</a>&nbsp;'; }
x += '</div><br>'
QH('p10html3', x);
@ -2606,6 +2626,10 @@
meshserver.Send({ action: 'removedevices', nodeids: [ nodeid ] });
}
function p10clickOnce(nodeid, protocol) {
meshserver.Send({ action: 'getcookie', nodeid: nodeid, tcpport: 3389, tag: 'clickonce', protocol: 'rdp' });
}
// Show current location
var d2map = null;
function p10showNodeLocationDialog() {

File diff suppressed because it is too large Load Diff