diff --git a/agents/MeshService.exe b/agents/MeshService.exe index 48a01840..016fc458 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index 91a40fda..152c8c9d 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/meshcore.js b/agents/meshcore.js index 7e95ff7e..f0f4f2cb 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -46,8 +46,13 @@ function createMeshCore(agent) { } // Get our location (lat/long) using our public IP address + var getIpLocationDataExInProgress = false; + var getIpLocationDataExCounts = [ 0, 0 ]; function getIpLocationDataEx(func) { + if (getIpLocationDataExInProgress == true) { return false; } try { + getIpLocationDataExInProgress = true; + getIpLocationDataExCounts[0]++; http.request({ host: 'ipinfo.io', // TODO: Use a HTTP proxy if needed!!!! port: 80, @@ -60,11 +65,13 @@ function createMeshCore(agent) { resp.end = function () { var location = null; try { if (typeof geoData == 'string') { var result = JSON.parse(geoData); if (result.ip && result.loc) { location = result; } } } catch (e) { } - if (func) { func(location); } + if (func) { getIpLocationDataExCounts[1]++; func(location); } } + getIpLocationDataExInProgress = false; }).end(); + return true; } - catch (e) { } + catch (e) { return false; } } // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably. @@ -247,6 +254,18 @@ function createMeshCore(agent) { // TODO!!!! break; } + case 'poweraction': { + // Server telling us to execute a power action + if ((mesh.ExecPowerState != undefined) && (data.actiontype)) { + var forced = 0; + if (data.forced == 1) { forced = 1; } + data.actiontype = parseInt(data.actiontype); + sendConsoleText('Performing power action=' + data.actiontype + ', forced=' + forced + '.'); + var r = mesh.ExecPowerState(data.actiontype, forced); + sendConsoleText('ExecPowerState returned code: ' + r); + } + break; + } case 'location': { // Update the location information of this node getIpLocationData(function (location) { mesh.SendCommand({ "action": "location", "type": "publicip", "value": location }); }); @@ -520,7 +539,7 @@ function createMeshCore(agent) { var response = null; switch (cmd) { case 'help': { // Displays available commands - response = 'Available commands: help, info, args, print, type, dbget, dbset, dbcompact, parseurl, httpget, wsconnect, wssend, wsclose, notify, ls, amt, netinfo, location.'; + response = 'Available commands: help, info, args, print, type, dbget, dbset, dbcompact, parseurl, httpget, wsconnect, wssend, wsclose, notify, ls, amt, netinfo, location, power.'; break; } case 'notify': { // Send a notification message to the mesh @@ -727,8 +746,21 @@ function createMeshCore(agent) { sendConsoleText(args['_'].join(' ')); break; } - case 'location': { - getIpLocationData(function (location) { sendConsoleText("Public IP location:\r\n" + objToString(location, 0, '.'), sessionid); }, args['_'][0]); + case 'location': { // Get location information about this computer + getIpLocationData(function (location) { sendConsoleText('IpLocation: ' + getIpLocationDataExCounts[0] + ' querie(s), ' + getIpLocationDataExCounts[1] + ' response(s), inProgress: ' + getIpLocationDataExInProgress + "\r\nPublic IP location data:\r\n" + objToString(location, 0, '.'), sessionid); }, args['_'][0]); + break; + } + case 'power': { // Execute a power action on this computer + if (mesh.ExecPowerState == undefined) { + response = 'Power command not supported on this agent.'; + } else { + if ((args['_'].length == 0) || (typeof args['_'][0] != 'number')) { + response = 'Proper usage: power (actionNumber), where actionNumber is:\r\n LOGOFF = 1\r\n SHUTDOWN = 2\r\n REBOOT = 3\r\n SLEEP = 4\r\n HIBERNATE = 5\r\n DISPLAYON = 6\r\n KEEPAWAKE = 7\r\n BEEP = 8\r\n CTRLALTDEL = 9\r\n VIBRATE = 13\r\n FLASH = 14'; // Display correct command usage + } else { + var r = mesh.ExecPowerState(args['_'][0], args['_'][1]); + response = 'Power action executed with return code: ' + r + '.'; + } + } break; } default: { // This is an unknown command, return an error message diff --git a/meshagent.js b/meshagent.js index 3e4921c1..b97178b0 100644 --- a/meshagent.js +++ b/meshagent.js @@ -26,14 +26,16 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { obj.agentInfo; obj.agentUpdate = null; var agentUpdateBlockSize = 65520; + obj.remoteaddr = obj.ws._socket.remoteAddress; + if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); } // Send a message to the mesh agent obj.send = function (data) { if (typeof data == 'string') { obj.ws.send(new Buffer(data, 'binary')); } else { obj.ws.send(data); } } // Disconnect this agent obj.close = function (arg) { - if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'Soft disconnect ' + obj.nodeid); } 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 ' + obj.nodeid); } catch (e) { console.log(e); } } // Hard close, close the TCP socket + if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'Soft disconnect ' + obj.nodeid + ' (' + 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, 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket if (obj.parent.wsagents[obj.dbNodeKey] == obj) { delete obj.parent.wsagents[obj.dbNodeKey]; obj.parent.parent.ClearConnectivityState(obj.dbMeshKey, obj.dbNodeKey, 1); @@ -211,7 +213,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { ws.on('error', function (err) { console.log(err); }); // If the mesh agent web socket is closed, clean up. - ws.on('close', function (req) { obj.close(0); }); + ws.on('close', function (req) { obj.parent.parent.debug(1, 'Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); obj.close(0); }); + // obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); }); // Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash. // Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce @@ -223,9 +226,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (obj.authenticated =! 1 || obj.meshid == null) return; // Check that the mesh exists obj.db.Get(obj.dbMeshKey, function (err, meshes) { - if (meshes.length == 0) { console.log('Agent connected with invalid domain/mesh, holding connection.'); return; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours. + if (meshes.length == 0) { console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddr + ').'); return; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours. var mesh = meshes[0]; - if (mesh.mtype != 2) { console.log('Agent connected with invalid mesh type, holding connection.'); return; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours. + if (mesh.mtype != 2) { console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddr + ').'); return; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours. // Check that the node exists obj.db.Get(obj.dbNodeKey, function (err, nodes) { @@ -269,6 +272,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { obj.parent.wsagents[obj.dbNodeKey] = obj; if (dupAgent) { // Close the duplicate agent + obj.parent.parent.debug(1, 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); dupAgent.close(); } else { // Indicate the agent is connected @@ -306,7 +310,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { delete obj.agentnonce; delete obj.unauth; if (obj.unauthsign) delete obj.unauthsign; - obj.parent.parent.debug(1, 'Verified agent connection to ' + obj.nodeid); + obj.parent.parent.debug(1, 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddr + ').'); obj.authenticated = 1; return true; } @@ -315,7 +319,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { function processAgentData(msg) { var str = msg.toString('utf8'); if (str[0] == '{') { - try { command = JSON.parse(str) } catch (e) { console.log('Unable to parse JSON'); return; } // If the command can't be parsed, ignore it. + try { command = JSON.parse(str) } catch (e) { console.log('Unable to parse JSON (' + obj.remoteaddr + ').'); return; } // If the command can't be parsed, ignore it. switch (command.action) { case 'msg': { @@ -434,9 +438,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (device.intelamt.host != command.intelamt.host) { device.intelamt.host = command.intelamt.host; change = 1; changes.push('AMT host'); } } if (mesh.mtype == 2) { - var remoteaddr = obj.ws._socket.remoteAddress; - if (remoteaddr.startsWith('::ffff:')) { remoteaddr = remoteaddr.substring(7); } - if (device.host != remoteaddr) { device.host = remoteaddr; change = 1; changes.push('host'); } + if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); } // TODO: Check that the agent has an interface that is the same as the one we got this websocket connection on. Only set if we have a match. } diff --git a/meshrelay.js b/meshrelay.js index a26bd491..ac841e33 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -17,9 +17,11 @@ module.exports.CreateMeshRelay = function (parent, ws, req) { var obj = {}; obj.ws = ws; obj.peer = null; + obj.parent = parent; obj.id = req.query['id']; + obj.remoteaddr = obj.ws._socket.remoteAddress; + if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); } - //console.log('Got relay connection for: ' + obj.id); if (obj.id == undefined) { obj.ws.close(); obj.id = null; return null; } // Attempt to connect without id, drop this. // Validate that the id is valid, we only need to do this on non-authenticated sessions. @@ -51,15 +53,18 @@ module.exports.CreateMeshRelay = function (parent, ws, req) { relayinfo.peer1.ws.peer = relayinfo.peer2.ws; relayinfo.peer2.ws.peer = relayinfo.peer1.ws; + obj.parent.parent.debug(1, 'Relay connected: ' + obj.id + ' (' + obj.remoteaddr + ' --> ' + obj.peer.remoteaddr + ')'); } else { // Connected already, drop (TODO: maybe we should re-connect?) obj.id = null; obj.ws.close(); + obj.parent.parent.debug(1, 'Relay duplicate: ' + obj.id + ' (' + obj.remoteaddr + ')'); return null; } } else { // Setup the connection, wait for peer - parent.wsrelays[obj.id] = { peer1 : obj, state : 1 }; + parent.wsrelays[obj.id] = { peer1: obj, state: 1 }; + obj.parent.parent.debug(1, 'Relay holding: ' + obj.id + ' (' + obj.remoteaddr + ')'); } } @@ -68,24 +73,26 @@ module.exports.CreateMeshRelay = function (parent, ws, req) { }; // When data is received from the mesh relay web socket - ws.on('message', function (data) - { - if (this.peer != null) { try { this.pause(); this.peer.send(data, ws.flushSink); } catch (e) { } } - }); + ws.on('message', function (data) { + if (this.peer != null) { try { this.pause(); this.peer.send(data, ws.flushSink); } catch (e) { } } + }); // If error, do nothing ws.on('error', function (err) { console.log(err); }); // If the mesh relay web socket is closed ws.on('close', function (req) { - //console.log('Got relay disconnection for: ' + obj.id); if (obj.id != null) { var relayinfo = parent.wsrelays[obj.id]; if (relayinfo.state == 2) { // Disconnect the peer - var peer = (relayinfo.peer1 == obj)?relayinfo.peer2:relayinfo.peer1; + var peer = (relayinfo.peer1 == obj) ? relayinfo.peer2 : relayinfo.peer1; + obj.parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + obj.remoteaddr + ' --> ' + peer.remoteaddr + ')'); peer.id = null; - peer.ws._socket.end(); + try { peer.ws.close(); } catch (e) { } // Soft disconnect + try { peer.ws._socket._parent.end(); } catch (e) { } // Hard disconnect + } else { + obj.parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + obj.remoteaddr + ')'); } delete parent.wsrelays[obj.id]; obj.peer = null; diff --git a/package.json b/package.json index e621c5c8..c9572310 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.0.6-v", + "version": "0.0.6-x", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/scripts/agent-redir-ws-0.1.0.js b/public/scripts/agent-redir-ws-0.1.0.js index f9243ee0..7dd6513e 100644 --- a/public/scripts/agent-redir-ws-0.1.0.js +++ b/public/scripts/agent-redir-ws-0.1.0.js @@ -30,6 +30,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) { obj.socket = new WebSocket(url); obj.socket.onopen = obj.xxOnSocketConnected; obj.socket.onmessage = obj.xxOnMessage; + obj.socket.onerror = function (e) { console.error(e); } obj.socket.onclose = obj.xxOnSocketClosed; obj.xxStateChange(1); obj.meshserver.Send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: url2 }); diff --git a/views/default.handlebars b/views/default.handlebars index 54fa71ed..a3e58efa 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1535,25 +1535,29 @@ } function groupActionFunction() { - var x = "Select an operation to perform on all selected devices.

"; - x += addHtmlValue('Operation', ''); + var x = "Select an operation to perform on all selected devices. Actions will be performed only with proper rights.

"; + x += addHtmlValue('Operation', ''); setDialogMode(2, "Group Action", 3, groupActionFunctionEx, x); } function groupActionFunctionEx() { var op = Q('d2groupop').value; - if (op == 1) { + if (op == 100) { // Group wake var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0; for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } } meshserver.Send({ action: 'wakedevices', nodeids: nodeids }); - } - if (op == 2) { + } else if (op == 101) { // Group delete, ask for confirmation var x = "Confirm delete selected devices(s)?

"; x += "Confirm"; setDialogMode(2, "Delete Nodes", 3, groupActionFunctionDelEx, x); QE('idx_dlgOkButton', false); + } else { + // Power operation + var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0; + for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } } + meshserver.Send({ action: 'poweraction', nodeids: nodeids, actiontype: op }); } } @@ -2463,9 +2467,9 @@ Q('p14iframe').contentWindow.setAuthCallback(updateAmtCredentials); // Display "action" button on desktop/terminal/files - QV('deskActionsBtn', (meshrights & 76) != 0); - QV('termActionsBtn', (meshrights & 76) != 0); - QV('filesActionsBtn', (meshrights & 76) != 0); + QV('deskActionsBtn', (meshrights & 72) != 0); // 72 = Wake-up + Remote Control permissions + QV('termActionsBtn', (meshrights & 72) != 0); + QV('filesActionsBtn', (meshrights & 72) != 0); // Request the power timeline if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) { powerTimelineReq = currentNode._id; meshserver.Send({ action: 'powertimeline', nodeid: currentNode._id }); } @@ -2475,16 +2479,24 @@ } function deviceActionFunction() { + var meshrights = meshes[currentNode.meshid].links['user/{{{domain}}}/' + userinfo.name.toLowerCase()].rights; var x = "Select an operation to perform on this device.

"; - x += addHtmlValue('Operation', ''); + var y = ''; + x += addHtmlValue('Operation', y); setDialogMode(2, "Device Action", 3, deviceActionFunctionEx, x); } function deviceActionFunctionEx() { var op = Q('d2deviceop').value; - if (op == 1) { + if (op == 100) { // Device wake meshserver.Send({ action: 'wakedevices', nodeids: [ currentNode._id ] }); + } else { + // Power operation + meshserver.Send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: op }); } } diff --git a/views/login.handlebars b/views/login.handlebars index ad59e6a2..8882417a 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -146,8 +146,6 @@ else if (passStrength >= 60) { QH('passWarning', 'Good Password'); } else { QH('passWarning', 'Weak Password'); } } - - console.log(passStrength); } // Return a password strength score diff --git a/webserver.js b/webserver.js index 3d5285eb..57143b12 100644 --- a/webserver.js +++ b/webserver.js @@ -1391,6 +1391,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate } 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. @@ -1443,6 +1444,40 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate 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.meshes[node.meshid]; + if (mesh) { + + // Check if this user has rights to do this + if (mesh.links[user._id] != undefined && ((mesh.links[user._id].rights & 8) != 0)) { // "Remote Control permission" + + // Get this device + var agent = obj.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':