diff --git a/agents/MeshConsole.exe b/agents/MeshConsole.exe new file mode 100644 index 00000000..d2e7dde8 Binary files /dev/null and b/agents/MeshConsole.exe differ diff --git a/agents/MeshConsole64.exe b/agents/MeshConsole64.exe new file mode 100644 index 00000000..37337e77 Binary files /dev/null and b/agents/MeshConsole64.exe differ diff --git a/agents/MeshService.exe b/agents/MeshService.exe index b1ccb42c..7c2f3064 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index 8e91d939..22cd7829 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/meshagent_arm b/agents/meshagent_arm index 39584818..e990d79d 100644 Binary files a/agents/meshagent_arm and b/agents/meshagent_arm differ diff --git a/agents/meshagent_pi b/agents/meshagent_pi index 3e48f4f9..88323854 100644 Binary files a/agents/meshagent_pi and b/agents/meshagent_pi differ diff --git a/agents/meshagent_pogo b/agents/meshagent_pogo index 511faaf2..10292ed8 100644 Binary files a/agents/meshagent_pogo and b/agents/meshagent_pogo differ diff --git a/agents/meshagent_poky b/agents/meshagent_poky index fe1b956e..74ef1f98 100644 Binary files a/agents/meshagent_poky and b/agents/meshagent_poky differ diff --git a/agents/meshagent_poky64 b/agents/meshagent_poky64 index cd9f82fe..92017af2 100644 Binary files a/agents/meshagent_poky64 and b/agents/meshagent_poky64 differ diff --git a/agents/meshagent_x86 b/agents/meshagent_x86 index 5926f5cf..7caf1b75 100644 Binary files a/agents/meshagent_x86 and b/agents/meshagent_x86 differ diff --git a/agents/meshagent_x86-64 b/agents/meshagent_x86-64 index 78a517bb..2fad0a10 100644 Binary files a/agents/meshagent_x86-64 and b/agents/meshagent_x86-64 differ diff --git a/agents/meshagent_x86-64_nokvm b/agents/meshagent_x86-64_nokvm index f82347b1..e5921911 100644 Binary files a/agents/meshagent_x86-64_nokvm and b/agents/meshagent_x86-64_nokvm differ diff --git a/agents/meshagent_x86_nokvm b/agents/meshagent_x86_nokvm index 93bf2ccd..7d925f1f 100644 Binary files a/agents/meshagent_x86_nokvm and b/agents/meshagent_x86_nokvm differ diff --git a/agents/meshcmd.js b/agents/meshcmd.js new file mode 100644 index 00000000..44396c6e --- /dev/null +++ b/agents/meshcmd.js @@ -0,0 +1,170 @@ +var fs = require('fs'); +var os = require('os'); +var net = require('net'); +var http = require('http'); +var dgram = require('dgram'); +var httpHeaders = require('http-headers'); +var tcpserver = null; +var broadcastSockets = {}; +var multicastSockets = {}; +var discoveryInterval = null; +var membershipIPv4 = '239.255.255.235'; +var membershipIPv6 = 'FF02:0:0:0:0:0:0:FE'; + +/* +// Route Settings +var settings = { + action: 'route', + localPort: 1234, + remoteName: 'AmtMachine7', + remoteNodeId: 'node//nmiPnDhT3vHKu$zg296YC5RjK53Trgh3Cimx3K8GVrFh$xch0UAAett2rbJpeddc', + remotePort: 3389, + username: 'a', + password: 'a', + serverUrl: 'wss://devbox.mesh.meshcentral.com:443/meshrelay.ashx', + serverId: 'D99362D5ED8BAEA8BF9E743B34B242256370C460FD66CB62373C6CFCB204D6D707403E396CF0EF6DC2B3A42F735135FD', // SHA384 of server HTTPS public key + serverHttpsHash: 'D9DE9E27A229B5355708A3672FB23237CC994A680B3570D242A91E36B4AE5BC9', // SHA256 of server HTTPS certificate + debugLevel: 0 +} +*/ + +// Check the server certificate fingerprint +function onVerifyServer(clientName, certs) { + try { for (var i in certs) { if (certs[i].fingerprint.replace(/:/g, '') == settings.serverHttpsHash) { return; } } } catch (e) { } + if (serverhash != null) { console.log('Error: Failed to verify server certificate.'); return false; } +} + +// Print a debug message +function debug(level, message) { if ((settings.debugLevel != null) && (settings.debugLevel >= level)) { console.log(message); } } + +// Start the router, start by listening to the local port +function run(argv) { + console.log('MeshCentral Command v1.0'); + var actionpath = 'meshaction.txt'; + if (argv.length >= 2) { actionpath = argv[1]; } + + // Load the action file + var actionfile = null; + try { actionfile = fs.readFileSync(actionpath); } catch (e) { } + if (actionfile == null) { console.log('Unable to load \"' + actionpath + '\". Create this file or specify the location as the first argument.'); process.exit(1); } + try { settings = JSON.parse(actionfile); } catch (e) { console.log(actionpath, e); process.exit(1); } + + // Validate meshaction.txt + if (settings.action == null) { console.log('No \"action\" specified.'); process.exit(1); } + settings.action = settings.action.toLowerCase(); + if (settings.action == 'route') { + if ((settings.localPort == null) || (typeof settings.localPort != 'number') || (settings.localPort < 0) || (settings.localPort > 65535)) { console.log('No or invalid \"localPort\" specified.'); process.exit(1); } + if ((settings.remoteNodeId == null) || (typeof settings.remoteNodeId != 'string')) { console.log('No or invalid \"remoteNodeId\" specified.'); process.exit(1); } + if ((settings.username == null) || (typeof settings.username != 'string')) { console.log('No or invalid \"username\" specified.'); process.exit(1); } + if ((settings.password == null) || (typeof settings.password != 'string') || (settings.password == '')) { console.log('No or invalid \"password\" specified.'); process.exit(1); } + if ((settings.serverId == null) || (typeof settings.serverId != 'string') || (settings.serverId.length != 96)) { console.log('No or invalid \"serverId\" specified.'); process.exit(1); } + if ((settings.serverHttpsHash == null) || (typeof settings.serverHttpsHash != 'string') || (settings.serverHttpsHash.length != 96)) { console.log('No or invalid \"serverHttpsHash\" specified.'); process.exit(1); } + if ((settings.remotePort == null) || (typeof settings.remotePort != 'number') || (settings.remotePort < 0) || (settings.remotePort > 65535)) { console.log('No or invalid \"remotePort\" specified.'); process.exit(1); } + } else { + console.log('Invalid \"action\" specified.'); process.exit(1); + } + + debug(1, "Settings: " + JSON.stringify(settings)); + if (settings.serverUrl != null) { startRouter(); } else { discoverMeshServer(); } +} + +// Starts the router +function startRouter() { + tcpserver = net.createServer(OnTcpClientConnected); + tcpserver.on('error', function (err) { console.log(err); process.exit(0); }); + tcpserver.listen(settings.localPort, function () { + // We started listening. + if (settings.remoteName == null) { + console.log('Redirecting local port ' + settings.localPort + ' to remote port ' + settings.remotePort + '.'); + } else { + console.log('Redirecting local port ' + settings.localPort + ' to ' + settings.remoteName + ':' + settings.remotePort + '.'); + } + console.log('Press ctrl-c to terminal.'); + + // If settings has a "cmd", run it now. + //process.exec("notepad.exe"); + }); +} + +// Called when a TCP connect is received on the local port. Launch a tunnel. +function OnTcpClientConnected(c) { + try { + // 'connection' listener + debug(1, 'Client connected'); + c.on('end', function () { disconnectTunnel(this, this.websocket, 'Client closed'); }); + c.pause(); + + try { + options = http.parseUri(settings.serverUrl + '?user=' + settings.username + '&pass=' + settings.password + '&nodeid=' + settings.remoteNodeId + '&tcpport=' + settings.remotePort); + } catch (e) { console.log('Unable to parse \"serverUrl\".'); process.exit(1); } + options.checkServerIdentity = onVerifyServer; + c.websocket = http.request(options); + c.websocket.tcp = c; + c.websocket.tunneling = false; + c.websocket.upgrade = OnWebSocket; + c.websocket.on('error', function (msg) { console.log(msg); }); + c.websocket.end(); + } catch (e) { debug(2, e); } +} + +// Disconnect both TCP & WebSocket connections and display a message. +function disconnectTunnel(tcp, ws, msg) { + if (ws != null) { try { ws.end(); } catch (e) { debug(2, e); } } + if (tcp != null) { try { tcp.end(); } catch (e) { debug(2, e); } } + debug(1, 'Tunnel disconnected: ' + msg); +} + +// Called when the web socket gets connected +function OnWebSocket(msg, s, head) { + debug(1, 'Websocket connected'); + s.on('data', function (msg) { + if (this.parent.tunneling == false) { + msg = msg.toString(); + if (msg == 'c') { + this.parent.tunneling = true; this.pipe(this.parent.tcp); this.parent.tcp.pipe(this); debug(1, 'Tunnel active'); + } else if ((msg.length > 6) && (msg.substring(0, 6) == 'error:')) { + console.log(msg.substring(6)); + disconnectTunnel(this.tcp, this, msg.substring(6)); + } + } + }); + s.on('error', function (msg) { disconnectTunnel(this.tcp, this, 'Websocket error'); }); + s.on('close', function (msg) { disconnectTunnel(this.tcp, this, 'Websocket closed'); }); + s.parent = this; +} + +// Try to discover the location of the mesh server +function discoverMeshServer() { console.log('Looking for server...'); discoveryInterval = setInterval(discoverMeshServerOnce, 5000); discoverMeshServerOnce(); } + +// Try to discover the location of the mesh server only once +function discoverMeshServerOnce() { + var interfaces = os.networkInterfaces(); + for (var adapter in interfaces) { + if (interfaces.hasOwnProperty(adapter)) { + for (var i = 0 ; i < interfaces[adapter].length; ++i) { + var addr = interfaces[adapter][i]; + multicastSockets[i] = dgram.createSocket({ type: (addr.family == "IPv4" ? "udp4" : "udp6") }); + multicastSockets[i].bind({ address: addr.address, exclusive: false }); + if (addr.family == "IPv4") { + multicastSockets[i].addMembership(membershipIPv4); + //multicastSockets[i].setMulticastLoopback(true); + multicastSockets[i].once('message', OnMulticastMessage); + multicastSockets[i].send(settings.serverId, 16989, membershipIPv4); + } + } + } + } +} + +// Called when a multicast packet is received +function OnMulticastMessage(msg, rinfo) { + var m = msg.toString().split('|'); + if ((m.length == 3) && (m[0] == 'MeshCentral2') && (m[1] == settings.serverId)) { + settings.serverUrl = m[2].replace('%s', rinfo.address).replace('/agent.ashx', '/meshrelay.ashx'); + console.log('Found server at ' + settings.serverUrl + '.'); + if (discoveryInterval != null) { clearInterval(discoveryInterval); discoveryInterval = null; } + startRouter(); + } +} + +try { run(process.argv); } catch (e) { /*console.log(e);*/ } diff --git a/agents/meshinstall-linux.sh b/agents/meshinstall-linux.sh index 068492e0..a6e7be9e 100644 --- a/agents/meshinstall-linux.sh +++ b/agents/meshinstall-linux.sh @@ -59,7 +59,7 @@ CheckInstallAgent() { DownloadAgent $url $meshid $machineid fi else - echo "MeshID is not correct, must be 64 HEX characters long." + echo "MeshID is not correct, must be 64 characters long." fi else echo "URI and/or MeshID have not been specified, must be passed in as arguments." @@ -78,7 +78,7 @@ DownloadAgent() { wget $url/meshagents?id=$machineid -q --no-check-certificate -O /usr/local/mesh/meshagent if [ $? -eq 0 ] then - echo "Mesh agent download." + echo "Mesh agent downloaded." # TODO: We could check the meshagent sha256 hash, but best to authenticate the server. chmod 755 /usr/local/mesh/meshagent wget $url/meshsettings?id=$meshid -q --no-check-certificate -O /usr/local/mesh/meshagent.msh @@ -97,6 +97,7 @@ DownloadAgent() { ln -s /usr/local/mesh/meshagent /etc/rc3.d/S20mesh ln -s /usr/local/mesh/meshagent /etc/rc5.d/S20mesh fi + echo "Mesh agent started." else echo "Unable to download mesh settings at: $url/meshsettings?id=$meshid." fi diff --git a/meshagent.js b/meshagent.js index 612da51d..624edaf2 100644 --- a/meshagent.js +++ b/meshagent.js @@ -131,7 +131,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (len == agentUpdateBlockSize) { obj.ws.send(obj.agentUpdate.buf); } else { obj.ws.send(obj.agentUpdate.buf.slice(0, len + 4)); } // Command 14, mesh agent next data block if (len < agentUpdateBlockSize) { - console.log("Agent update sent"); + //console.log("Agent update sent"); obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0) + obj.common.hex2rstr(obj.agentExeInfo.hash)); // Command 13, end mesh agent download, send agent SHA384 hash obj.fs.close(obj.agentUpdate.fd); obj.agentUpdate = null; diff --git a/meshrelay.js b/meshrelay.js index b94eaeee..1464d165 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -4,7 +4,7 @@ * @version v0.0.1 */ -module.exports.CreateMeshRelay = function (parent, ws, req) { +module.exports.CreateMeshRelay = function (parent, ws, req, domain) { var obj = {}; obj.ws = ws; obj.req = req; @@ -12,6 +12,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req) { obj.parent = parent; obj.id = req.query.id; obj.remoteaddr = obj.ws._socket.remoteAddress; + obj.domain = domain; if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); } // Disconnect this agent @@ -60,7 +61,27 @@ module.exports.CreateMeshRelay = function (parent, ws, req) { 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) { } + try { if ((req.session) && (req.session.userid) || (req.session.domainid == obj.domain.id)) { obj.authenticated = true; } } catch (e) { } + if ((obj.authenticated != true) && (req.query.user != null) && (req.query.pass != null)) { + // Check user authentication + obj.parent.authenticate(req.query.user, req.query.pass, obj.domain, function (err, userid, passhint) { + if (userid != null) { + obj.authenticated = true; + // Check is we have agent routing instructions, process this here. + if ((req.query.nodeid != null) && (req.query.tcpport != null)) { + if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one. + var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: req.query.tcpport, tcpaddr: ((req.query.tcpaddr == null) ? '127.0.0.1' : req.query.tcpaddr) }; + if (obj.sendAgentMessage(command, userid, obj.domain.id) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); } + } + } else { + obj.parent.parent.debug(1, 'Relay: User authentication failed (' + obj.remoteaddr + ')'); + obj.ws.send('error:Authentication failed'); + } + performRelay(); + }); + } else { + performRelay(); + } } else { // Get the session from the cookie var cookie = obj.parent.parent.webserver.decodeCookie(req.query.auth); @@ -76,74 +97,78 @@ module.exports.CreateMeshRelay = function (parent, ws, req) { } else { obj.id = null; obj.parent.parent.debug(1, 'Relay: invalid cookie (' + obj.remoteaddr + ')'); + obj.ws.send('error:Invalid cookie'); } + performRelay(); } - if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this. - ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive + function performRelay() { + if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this. + ws._socket.setKeepAlive(true, 240000); // 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. - /* - if (!parent.args.notls) { - // Check the identifier, if running without TLS, skip this. - var ids = obj.id.split(':'); - if (ids.length != 3) { obj.ws.close(); obj.id = null; return null; } // Invalid ID, drop this. - if (parent.crypto.createHmac('SHA384', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { obj.ws.close(); obj.id = null; return null; } // Invalid HMAC, drop this. - if ((Date.now() - parseInt(ids[1])) > 120000) { obj.ws.close(); obj.id = null; return null; } // Expired time, drop this. - obj.id = ids[0]; - } - */ + // 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. + /* + if (!parent.args.notls) { + // Check the identifier, if running without TLS, skip this. + var ids = obj.id.split(':'); + if (ids.length != 3) { obj.ws.close(); obj.id = null; return null; } // Invalid ID, drop this. + if (parent.crypto.createHmac('SHA384', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { obj.ws.close(); obj.id = null; return null; } // Invalid HMAC, drop this. + if ((Date.now() - parseInt(ids[1])) > 120000) { obj.ws.close(); obj.id = null; return null; } // Expired time, drop this. + obj.id = ids[0]; + } + */ - // Check the peer connection status - { - var relayinfo = parent.wsrelays[obj.id]; - if (relayinfo) { - if (relayinfo.state == 1) { - // Check that at least one connection is authenticated - if ((obj.authenticated != true) && (relayinfo.peer1.authenticated != true)) { + // Check the peer connection status + { + var relayinfo = parent.wsrelays[obj.id]; + if (relayinfo) { + if (relayinfo.state == 1) { + // Check that at least one connection is authenticated + if ((obj.authenticated != true) && (relayinfo.peer1.authenticated != true)) { + obj.id = null; + obj.ws.close(); + obj.parent.parent.debug(1, 'Relay without-auth: ' + obj.id + ' (' + obj.remoteaddr + ')'); + return null; + } + + // Connect to peer + obj.peer = relayinfo.peer1; + obj.peer.peer = obj; + relayinfo.peer2 = obj; + relayinfo.state = 2; + obj.ws.send('c'); // Send connect to both peers + relayinfo.peer1.ws.send('c'); + relayinfo.peer1.ws.resume(); // Release the traffic + + 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 without-auth: ' + obj.id + ' (' + obj.remoteaddr + ')'); + obj.parent.parent.debug(1, 'Relay duplicate: ' + obj.id + ' (' + obj.remoteaddr + ')'); return null; } - - // Connect to peer - obj.peer = relayinfo.peer1; - obj.peer.peer = obj; - relayinfo.peer2 = obj; - relayinfo.state = 2; - obj.ws.send('c'); // Send connect to both peers - relayinfo.peer1.ws.send('c'); - relayinfo.peer1.ws.resume(); // Release the traffic - - 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 { - // Wait for other relay connection - ws.pause(); // Hold traffic until the other connection - parent.wsrelays[obj.id] = { peer1: obj, state: 1 }; - obj.parent.parent.debug(1, 'Relay holding: ' + obj.id + ' (' + obj.remoteaddr + ')'); + // Wait for other relay connection + ws.pause(); // Hold traffic until the other connection + parent.wsrelays[obj.id] = { peer1: obj, state: 1 }; + obj.parent.parent.debug(1, 'Relay holding: ' + obj.id + ' (' + obj.remoteaddr + ')'); - // Check if a peer server has this connection - if (parent.parent.multiServer != null) { - var rsession = obj.parent.wsPeerRelays[obj.id]; - if ((rsession != null) && (rsession.serverId > obj.parent.parent.serverId)) { - // We must initiate the connection to the peer - parent.parent.multiServer.createPeerRelay(ws, req, rsession.serverId, req.session.userid); - delete parent.wsrelays[obj.id]; - } else { - // Send message to other peers that we have this connection - parent.parent.multiServer.DispatchMessage(JSON.stringify({ action: 'relay', id: obj.id })); + // Check if a peer server has this connection + if (parent.parent.multiServer != null) { + var rsession = obj.parent.wsPeerRelays[obj.id]; + if ((rsession != null) && (rsession.serverId > obj.parent.parent.serverId)) { + // We must initiate the connection to the peer + parent.parent.multiServer.createPeerRelay(ws, req, rsession.serverId, req.session.userid); + delete parent.wsrelays[obj.id]; + } else { + // Send message to other peers that we have this connection + parent.parent.multiServer.DispatchMessage(JSON.stringify({ action: 'relay', id: obj.id })); + } } } } diff --git a/meshuser.js b/meshuser.js index 11a26f98..5e51e017 100644 --- a/meshuser.js +++ b/meshuser.js @@ -338,7 +338,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { // 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) { + obj.parent.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 }; @@ -492,7 +492,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { 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) { + obj.parent.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) } }; diff --git a/package.json b/package.json index f5c5ac01..faa9af99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.0.8-w", + "version": "0.1.0-d", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default.handlebars b/views/default.handlebars index 4da4c109..411bf499 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -25,7 +25,6 @@ MeshCentral -