diff --git a/meshcentral.js b/meshcentral.js index ddd46245..18d28ee4 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -1055,7 +1055,8 @@ function CreateMeshCentralServer(config, args) { 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, '$'); + var cookie = Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); + return cookie; } catch (e) { return null; } }; @@ -1067,11 +1068,11 @@ function CreateMeshCentralServer(config, args) { 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; } + if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { Debug(1, 'ERR: Bad cookie due to invalid time'); 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 (in milliseconds) if (timeout == null) { timeout = 2; } - if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) return null; // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) + if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { Debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) return o; } catch (e) { return null; } }; diff --git a/meshrelay.js b/meshrelay.js index f6c93ccf..88b65fc6 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -13,11 +13,13 @@ /*jshint esversion: 6 */ "use strict"; -module.exports.CreateMeshRelay = function (parent, ws, req, domain) { +module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie) { var obj = {}; obj.ws = ws; obj.req = req; obj.peer = null; + obj.user = user; + obj.cookie = cookie; obj.parent = parent; obj.id = req.query.id; obj.remoteaddr = obj.ws._socket.remoteAddress; @@ -69,49 +71,25 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain) { 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 == 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 if 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.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 + ')'); - obj.ws.send('error:Invalid cookie'); - } - performRelay(); - } + // Mark this relay session as authenticated if this is the user end. + obj.authenticated = (obj.user != null); + // Kick off the routing, if we have agent routing instructions, process them here. + if ((obj.cookie != null) && (obj.cookie.nodeid != null) && (obj.cookie.tcpport != null) && (obj.cookie.domainid != null)) { + // We have routing instructions in the cookie, Send connection request to agent + if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one. + var command = { nodeid: obj.cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: obj.cookie.tcpport, tcpaddr: obj.cookie.tcpaddr }; + obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command)); + if (obj.sendAgentMessage(command, obj.cookie.userid, obj.cookie.domainid) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); } + } else if ((req.query.nodeid != null) && (req.query.tcpport != null)) { + // We have routing instructions in the URL arguments, Send connection request to agent + if (obj.id == null) { 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) }; + obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command)); + 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 + ')'); } + } + performRelay(); + 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 diff --git a/package.json b/package.json index cc38f8d9..9a7ef93f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.2-e", + "version": "0.2.2-f", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.application b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.application similarity index 84% rename from public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.application rename to public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.application index f22c13df..4a987888 100644 --- a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.application +++ b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.application @@ -1,20 +1,20 @@  - + - - + + - uaxqCrqKPjDkZMXMlJ9pIvARsSxYXXLci7n8z3Q8hUU= + nyBHr6mVUVhjU6l4Bmrfa0juzDDrPD6BiiYzVMhKKVA= diff --git a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.config.deploy b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.config.deploy similarity index 100% rename from public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.config.deploy rename to public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.config.deploy diff --git a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.deploy b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.deploy similarity index 74% rename from public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.deploy rename to public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.deploy index 0a90de13..0d9a4348 100644 Binary files a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.deploy and b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.deploy differ diff --git a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.manifest b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.manifest similarity index 89% rename from public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.manifest rename to public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.manifest index 71e3dac7..41e8fa6d 100644 --- a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.exe.manifest +++ b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.exe.manifest @@ -1,10 +1,10 @@  - + - + @@ -43,14 +43,14 @@ - - + + - 2K6tEre6rIjqc6bZn7uhWlXLgAnZ82UP3jYzxNJ7WIk= + H+qrBKAsVVx/APIHP2Tq2cK3/FUh4SIShsjM6eo0fUw= diff --git a/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.ico.deploy b/public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.ico.deploy similarity index 100% rename from public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_16/MeshMiniRouter.ico.deploy rename to public/clickonce/minirouter/Application Files/MeshMiniRouter_2_0_0_17/MeshMiniRouter.ico.deploy diff --git a/public/clickonce/minirouter/MeshMiniRouter.application b/public/clickonce/minirouter/MeshMiniRouter.application index f22c13df..4a987888 100644 --- a/public/clickonce/minirouter/MeshMiniRouter.application +++ b/public/clickonce/minirouter/MeshMiniRouter.application @@ -1,20 +1,20 @@  - + - - + + - uaxqCrqKPjDkZMXMlJ9pIvARsSxYXXLci7n8z3Q8hUU= + nyBHr6mVUVhjU6l4Bmrfa0juzDDrPD6BiiYzVMhKKVA= diff --git a/public/clickonce/minirouter/publish.htm b/public/clickonce/minirouter/publish.htm index 6ed872d2..49e4503b 100644 --- a/public/clickonce/minirouter/publish.htm +++ b/public/clickonce/minirouter/publish.htm @@ -59,7 +59,7 @@ FONT.key {font-weight: bold; color: darkgreen}
-
 
Name:MeshCentral Mini-Router
 
Version:2.0.0.16
 
Publisher:Meshcentral.com
 
+
 
Name:MeshCentral Mini-Router
 
Version:2.0.0.17
 
Publisher:Meshcentral.com
 
diff --git a/views/default.handlebars b/views/default.handlebars index 275e4fd1..6f24583f 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1271,7 +1271,7 @@ 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"; + var 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; diff --git a/webserver.js b/webserver.js index 48ee91e4..1672e15d 100644 --- a/webserver.js +++ b/webserver.js @@ -1059,26 +1059,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { }; // Handle a web socket relay request - function handleRelayWebSocket(ws, req) { - var domain = checkUserIpAddress(ws, req); - if (domain == null) return; - // Check if this is a logged in user - var user, peering = true; - if (req.query.auth == null) { - // Use ExpressJS session - if (!req.session || !req.session.userid) { return; } // Web socket attempt without login, disconnect. - if (req.session.domainid != domain.id) { console.log('ERR: Invalid domain'); return; } - user = obj.users[req.session.userid]; - } else { - // Get the session from the cookie - if (obj.parent.multiServer == null) { return; } - var session = obj.parent.decodeCookie(req.query.auth); - if (session == null) { console.log('ERR: Invalid cookie'); return; } - if (session.domainid != domain.id) { console.log('ERR: Invalid domain'); return; } - user = obj.users[session.userid]; - peering = false; // Don't allow the connection to jump again to a different server - } - if (!user) { console.log('ERR: Not a user'); return; } + function handleRelayWebSocket(ws, req, domain, user, cookie) { + if (!(req.query.host)) { console.log('ERR: No host target specified'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket Debug(1, 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.'); ws.pause(); // Hold this socket until we are ready. @@ -1086,13 +1068,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Fetch information about the target obj.db.Get(req.query.host, function (err, docs) { - if (docs.length == 0) { console.log('ERR: Node not found'); return; } + if (docs.length == 0) { console.log('ERR: Node not found'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket var node = docs[0]; - if (!node.intelamt) { console.log('ERR: Not AMT node'); return; } + if (!node.intelamt) { console.log('ERR: Not AMT node'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket // Check if this user has permission to manage this computer var meshlinks = user.links[node.meshid]; - if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); return; } + if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { ws.close(); } catch (e) { } return; } // Check what connectivity is available for this node var state = parent.GetConnectivityState(req.query.host); @@ -1100,7 +1082,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (!state || state.connectivity == 0) { Debug(1, 'ERR: No routing possible (1)'); try { ws.close(); } catch (e) { } return; } else { conn = state.connectivity; } // Check what server needs to handle this connection - if ((obj.parent.multiServer != null) && (peering == true)) { + if ((obj.parent.multiServer != null) && (cookie == null)) { // If a cookie is provided, don't allow the connection to jump again to a different server var server = obj.parent.GetRoutingServerId(req.query.host, 2); // Check for Intel CIRA connection if (server != null) { if (server.serverid != obj.parent.serverId) { @@ -1810,10 +1792,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.app.post(url + 'uploadmeshcorefile.ashx', handleUploadMeshCoreFile); obj.app.get(url + 'userfiles/*', handleDownloadUserFiles); obj.app.ws(url + 'echo.ashx', handleEchoWebSocket); - obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { try { obj.meshRelayHandler.CreateMeshRelay(obj, ws, req, getDomain(req)); } catch (e) { console.log(e); } }); + obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, function (ws1, req1, domain, user, cookie) { obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); }); }); obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); }); - obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformSessionAuth(ws, req, handleRelayWebSocket); }); - obj.app.ws(url + 'control.ashx', function (ws, req) { PerformSessionAuth(ws, req, function (ws1, req1, domain) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain); }); }); + obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, handleRelayWebSocket); }); + obj.app.ws(url + 'control.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, function (ws1, req1, domain, user, cookie) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain); }); }); // Server picture obj.app.get(url + 'serverpic.ashx', function (req, res) { @@ -1847,47 +1829,50 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Authenticates a session and forwards - function PerformSessionAuth(ws, req, func) { + function PerformWSSessionAuth(ws, req, func) { try { + // Check IP filtering and domain var domain = checkUserIpAddress(ws, req); - if (domain != null) { - var loginok = false; - // Check if the user is logged in - if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) { - // If a default user is active, setup the session here. - if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) { - if (req.session && req.session.loginmode) { delete req.session.loginmode; } - req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase(); - req.session.domainid = domain.id; - func(ws, req, domain); - loginok = true; + if (domain == null) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); return; } catch (e) { return; } } + + // A web socket session can be authenticated in many ways (Default user, session, user/pass and cookie). Check authentication here. + if ((req.query.user != null) && (req.query.pass != null)) { + // A user/pass is provided in URL arguments + obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) { + if ((err == null) && (obj.users[userid])) { + // We are authenticated + func(ws, req, domain, obj.users[userid]); } else { - // See the the user/pass is provided in URL arguments - if ((req.query.user != null) && (req.query.pass != null)) { - loginok = true; - obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) { - var loginok2 = false; - if (err == null) { - var user = obj.users[userid]; - if (user) { - req.session.userid = userid; - req.session.domainid = domain.id; - func(ws, req, domain); - loginok2 = true; - } - } - // If not authenticated, close the websocket connection - if (loginok2 == false) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } } - }); - } + // If not authenticated, close the websocket connection + Debug(1, 'ERR: Websocket bad user/pass auth'); + try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } } + }); + return; + } else if (req.query.auth != null) { + // This is a encrypted cookie authentication + var cookie = obj.parent.decodeCookie(req.query.auth, null, 60); // Cookie with 60 minute timeout + if ((cookie != null) && (obj.users[cookie.userid])) { + // Valid cookie, we are authenticated + func(ws, req, domain, obj.users[cookie.userid], cookie); } else { - func(ws, req, domain); - loginok = true; + // This is a bad cookie + Debug(1, 'ERR: Websocket bad cookie auth: ' + req.query.auth); + try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } } - // If not authenticated, close the websocket connection - if (loginok == false) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } } + return; + } else if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) { + // A default user is active + func(ws, req, domain, obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]); + return; + } else if (req.session && (req.session.userid != null) && (req.session.domainid == obj.domain.id)) { + // This user is logged in using the ExpressJS session + func(ws, req, domain, req.session.userid); + return; } + // If not authenticated, close the websocket connection + Debug(1, 'ERR: Websocket no auth'); + try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } } catch (e) { console.log(e); } }