From 16e8913ef7cf69eb5b1c6f86610708c9981295c3 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 21 May 2020 21:25:11 -0700 Subject: [PATCH] Added CloudFlare Support, improved reverse-proxy HTTP header handling. --- meshagent.js | 4 +- meshdesktopmultiplex.js | 16 ++--- meshrelay.js | 36 +++++----- meshuser.js | 10 +-- webserver.js | 147 ++++++++++++++++++++++------------------ 5 files changed, 113 insertions(+), 100 deletions(-) diff --git a/meshagent.js b/meshagent.js index 6d971542..13f30aa9 100644 --- a/meshagent.js +++ b/meshagent.js @@ -19,14 +19,14 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { const forge = parent.parent.certificateOperations.forge; const common = parent.parent.common; parent.agentStats.createMeshAgentCount++; - parent.parent.debug('agent', 'New agent at ' + req.ip + ':' + ws._socket.remotePort); + parent.parent.debug('agent', 'New agent at ' + req.clientIp + ':' + ws._socket.remotePort); var obj = {}; obj.domain = domain; obj.authenticated = 0; obj.receivedCommands = 0; obj.agentCoreCheck = 0; - obj.remoteaddr = (req.ip.startsWith('::ffff:')) ? (req.ip.substring(7)) : req.ip; + obj.remoteaddr = req.clientIp; obj.remoteaddrport = obj.remoteaddr + ':' + ws._socket.remotePort; obj.nonce = parent.crypto.randomBytes(48).toString('binary'); //ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive, 4 minutes diff --git a/meshdesktopmultiplex.js b/meshdesktopmultiplex.js index fa5f0f8b..33e993a4 100644 --- a/meshdesktopmultiplex.js +++ b/meshdesktopmultiplex.js @@ -815,7 +815,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie } // If there is no authentication, drop this connection - if ((obj.id != null) && (obj.user == null) && (obj.ruserid == null)) { try { ws.close(); parent.parent.debug('relay', 'DesktopRelay: Connection with no authentication (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } return; } + if ((obj.id != null) && (obj.user == null) && (obj.ruserid == null)) { try { ws.close(); parent.parent.debug('relay', 'DesktopRelay: Connection with no authentication (' + obj.req.clientIp + ')'); } catch (e) { console.log(e); } return; } // Relay session count (we may remove this in the future) obj.relaySessionCounted = true; @@ -848,8 +848,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (obj.ws == null) return; // Already closed. // Close the connection - if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug('relay', 'DesktopRelay: Soft disconnect (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket - if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug('relay', 'DesktopRelay: Hard disconnect (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket + if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug('relay', 'DesktopRelay: Soft disconnect (' + obj.req.clientIp + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket + if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug('relay', 'DesktopRelay: Hard disconnect (' + obj.req.clientIp + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket if (obj.relaySessionCounted) { parent.relaySessionCount--; delete obj.relaySessionCounted; } if (obj.deskMultiplexor != null) { if (obj.deskMultiplexor.removePeer(obj) == true) { delete parent.desktoprelays[obj.nodeid]; } } @@ -948,7 +948,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if ((typeof parent.parent.args.agentping == 'number') && (obj.pingtimer == null)) { obj.pingtimer = setInterval(sendPing, parent.parent.args.agentping * 1000); } else if ((typeof parent.parent.args.agentpong == 'number') && (obj.pongtimer == null)) { obj.pongtimer = setInterval(sendPong, parent.parent.args.agentpong * 1000); } - parent.parent.debug('relay', 'DesktopRelay: Connection (' + cleanRemoteAddr(obj.req.ip) + ')'); + parent.parent.debug('relay', 'DesktopRelay: Connection (' + obj.req.clientIp + ')'); } // Create if needed and add this peer to the desktop multiplexor @@ -991,7 +991,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie ws.on('error', function (err) { //console.log('ws-error', err); parent.relaySessionErrorCount++; - console.log('Relay error from ' + cleanRemoteAddr(obj.req.ip) + ', ' + err.toString().split('\r')[0] + '.'); + console.log('Relay error from ' + obj.req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); obj.close(); }); @@ -1020,7 +1020,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one. const command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr }; parent.parent.debug('relay', 'Relay: Sending agent tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, cookie.domainid) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(obj.req.ip) + ')'); } + if (obj.sendAgentMessage(command, user._id, cookie.domainid) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); } performRelay(0); }); return obj; @@ -1040,11 +1040,11 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (obj.req.query.tcpport != null) { const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: obj.req.query.tcpport, tcpaddr: ((obj.req.query.tcpaddr == null) ? '127.0.0.1' : obj.req.query.tcpaddr) }; parent.parent.debug('relay', 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(obj.req.ip) + ')'); } + if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); } } else if (obj.req.query.udpport != null) { const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, udpport: obj.req.query.udpport, udpaddr: ((obj.req.query.udpaddr == null) ? '127.0.0.1' : obj.req.query.udpaddr) }; parent.parent.debug('relay', 'Relay: Sending agent UDP tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(obj.req.ip) + ')'); } + if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); } } performRelay(0); }); diff --git a/meshrelay.js b/meshrelay.js index 58d31833..03f015f6 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -28,7 +28,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie } // If there is no authentication, drop this connection - if ((obj.id != null) && (obj.id.startsWith('meshmessenger/') == false) && (obj.user == null) && (obj.ruserid == null)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Connection with no authentication (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } return; } + if ((obj.id != null) && (obj.id.startsWith('meshmessenger/') == false) && (obj.user == null) && (obj.ruserid == null)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Connection with no authentication (' + obj.req.clientIp + ')'); } catch (e) { console.log(e); } return; } // Relay session count (we may remove this in the future) obj.relaySessionCounted = true; @@ -65,8 +65,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Disconnect this agent obj.close = function (arg) { - if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Soft disconnect (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket - if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug('relay', 'Relay: Hard disconnect (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket + if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Soft disconnect (' + obj.req.clientIp + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket + if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug('relay', 'Relay: Hard disconnect (' + obj.req.clientIp + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket // Aggressive cleanup delete obj.id; @@ -172,7 +172,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Check that at least one connection is authenticated if ((obj.authenticated != true) && (relayinfo.peer1.authenticated != true)) { ws.close(); - parent.parent.debug('relay', 'Relay without-auth: ' + obj.id + ' (' + cleanRemoteAddr(obj.req.ip) + ')'); + parent.parent.debug('relay', 'Relay without-auth: ' + obj.id + ' (' + obj.req.clientIp + ')'); delete obj.id; delete obj.ws; delete obj.peer; @@ -189,7 +189,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie } if (u1 != u2) { ws.close(); - parent.parent.debug('relay', 'Relay auth mismatch (' + u1 + ' != ' + u2 + '): ' + obj.id + ' (' + cleanRemoteAddr(obj.req.ip) + ')'); + parent.parent.debug('relay', 'Relay auth mismatch (' + u1 + ' != ' + u2 + '): ' + obj.id + ' (' + obj.req.clientIp + ')'); delete obj.id; delete obj.ws; delete obj.peer; @@ -247,7 +247,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie } else { // Write the recording file header parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename); - var metadata = { magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, ipaddr1: cleanRemoteAddr(obj.req.ip), ipaddr2: cleanRemoteAddr(obj.peer.req.ip), time: new Date().toLocaleString(), protocol: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p), nodeid: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.nodeid ) }; + var metadata = { magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, ipaddr1: obj.req.clientIp, ipaddr2: obj.peer.req.clientIp, time: new Date().toLocaleString(), protocol: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p), nodeid: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.nodeid ) }; if (xdevicename2 != null) { metadata.devicename = xdevicename2; } var firstBlock = JSON.stringify(metadata); var logfile = { fd: fd, lock: false, filename: recFullFilename, startTime: Date.now(), size: 0 }; @@ -270,7 +270,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie try { relayinfo.peer1.ws.send('c'); } catch (ex) { } } - parent.parent.debug('relay', 'Relay connected: ' + obj.id + ' (' + cleanRemoteAddr(obj.req.ip) + ' --> ' + cleanRemoteAddr(obj.peer.req.ip) + ')'); + parent.parent.debug('relay', 'Relay connected: ' + obj.id + ' (' + obj.req.clientIp + ' --> ' + obj.peer.req.clientIp + ')'); // Log the connection if (sessionUser != null) { @@ -278,13 +278,13 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (obj.req.query.p == 1) { msg = 'Started terminal session'; } else if (obj.req.query.p == 2) { msg = 'Started desktop session'; } else if (obj.req.query.p == 5) { msg = 'Started file management session'; } - var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: sessionUser._id, username: sessionUser.name, msg: msg + ' \"' + obj.id + '\" from ' + cleanRemoteAddr(obj.peer.req.ip) + ' to ' + cleanRemoteAddr(req.ip), protocol: req.query.p, nodeid: req.query.nodeid }; + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: sessionUser._id, username: sessionUser.name, msg: msg + ' \"' + obj.id + '\" from ' + obj.peer.req.clientIp + ' to ' + req.clientIp, protocol: req.query.p, nodeid: req.query.nodeid }; parent.parent.DispatchEvent(['*', sessionUser._id], obj, event); } } else { // Connected already, drop (TODO: maybe we should re-connect?) ws.close(); - parent.parent.debug('relay', 'Relay duplicate: ' + obj.id + ' (' + cleanRemoteAddr(obj.req.ip) + ')'); + parent.parent.debug('relay', 'Relay duplicate: ' + obj.id + ' (' + obj.req.clientIp + ')'); delete obj.id; delete obj.ws; delete obj.peer; @@ -294,7 +294,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Wait for other relay connection ws._socket.pause(); // Hold traffic until the other connection parent.wsrelays[obj.id] = { peer1: obj, state: 1, timeout: setTimeout(function () { closeBothSides(); }, 30000) }; - parent.parent.debug('relay', 'Relay holding: ' + obj.id + ' (' + cleanRemoteAddr(obj.req.ip) + ') ' + (obj.authenticated ? 'Authenticated' : '')); + parent.parent.debug('relay', 'Relay holding: ' + obj.id + ' (' + obj.req.clientIp + ') ' + (obj.authenticated ? 'Authenticated' : '')); // Check if a peer server has this connection if (parent.parent.multiServer != null) { @@ -354,7 +354,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie ws.on('error', function (err) { parent.relaySessionErrorCount++; if (obj.relaySessionCounted) { parent.relaySessionCount--; delete obj.relaySessionCounted; } - console.log('Relay error from ' + cleanRemoteAddr(obj.req.ip) + ', ' + err.toString().split('\r')[0] + '.'); + console.log('Relay error from ' + obj.req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); closeBothSides(); }); @@ -374,7 +374,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Disconnect the peer try { if (peer.relaySessionCounted) { parent.relaySessionCount--; delete peer.relaySessionCounted; } } catch (ex) { console.log(ex); } - parent.parent.debug('relay', 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(obj.req.ip) + ' --> ' + cleanRemoteAddr(peer.req.ip) + ')'); + parent.parent.debug('relay', 'Relay disconnect: ' + obj.id + ' (' + obj.req.clientIp + ' --> ' + peer.req.clientIp + ')'); try { peer.ws.close(); } catch (e) { } // Soft disconnect try { peer.ws._socket._parent.end(); } catch (e) { } // Hard disconnect @@ -385,10 +385,10 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie else if (obj.req.query.p == 2) { msg = 'Ended desktop session'; } else if (obj.req.query.p == 5) { msg = 'Ended file management session'; } if (user) { - var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msg: msg + ' \"' + obj.id + '\" from ' + cleanRemoteAddr(obj.req.ip) + ' to ' + cleanRemoteAddr(obj.peer.req.ip) + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: obj.req.query.p, nodeid: obj.req.query.nodeid }; + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msg: msg + ' \"' + obj.id + '\" from ' + obj.req.clientIp + ' to ' + obj.peer.req.clientIp + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: obj.req.query.p, nodeid: obj.req.query.nodeid }; parent.parent.DispatchEvent(['*', user._id], obj, event); } else if (peer.user) { - var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: peer.user._id, username: peer.user.name, msg: msg + ' \"' + obj.id + '\" from ' + cleanRemoteAddr(obj.req.ip) + ' to ' + cleanRemoteAddr(obj.peer.req.ip) + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: obj.req.query.p, nodeid: obj.req.query.nodeid }; + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: peer.user._id, username: peer.user.name, msg: msg + ' \"' + obj.id + '\" from ' + obj.req.clientIp + ' to ' + obj.peer.req.clientIp + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: obj.req.query.p, nodeid: obj.req.query.nodeid }; parent.parent.DispatchEvent(['*', peer.user._id], obj, event); } } @@ -400,7 +400,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (peer.pingtimer != null) { clearInterval(peer.pingtimer); delete peer.pingtimer; } if (peer.pongtimer != null) { clearInterval(peer.pongtimer); delete peer.pongtimer; } } else { - parent.parent.debug('relay', 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(obj.req.ip) + ')'); + parent.parent.debug('relay', 'Relay disconnect: ' + obj.id + ' (' + obj.req.clientIp + ')'); } // Close the recording file if needed @@ -494,7 +494,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one. const command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr }; parent.parent.debug('relay', 'Relay: Sending agent tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, cookie.domainid) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(obj.req.ip) + ')'); } + if (obj.sendAgentMessage(command, user._id, cookie.domainid) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); } performRelay(); }); return obj; @@ -514,11 +514,11 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (obj.req.query.tcpport != null) { const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: obj.req.query.tcpport, tcpaddr: ((obj.req.query.tcpaddr == null) ? '127.0.0.1' : obj.req.query.tcpaddr) }; parent.parent.debug('relay', 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(obj.req.ip) + ')'); } + if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); } } else if (obj.req.query.udpport != null) { const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, udpport: obj.req.query.udpport, udpaddr: ((obj.req.query.udpaddr == null) ? '127.0.0.1' : obj.req.query.udpaddr) }; parent.parent.debug('relay', 'Relay: Sending agent UDP tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(obj.req.ip) + ')'); } + if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); } } performRelay(); }); diff --git a/meshuser.js b/meshuser.js index 87c00ea1..eab3f4bf 100644 --- a/meshuser.js +++ b/meshuser.js @@ -188,7 +188,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (typeof user.consent == 'number') { command.consent |= user.consent; } // Add user consent command.username = user.name; // Add user name command.userid = user._id; // Add user id - command.remoteaddr = cleanRemoteAddr(req.ip); // User's IP address + command.remoteaddr = req.clientIp; // User's IP address if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text delete command.nodeid; // Remove the nodeid since it's implied try { agent.send(JSON.stringify(command)); } catch (ex) { } @@ -211,7 +211,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (typeof user.consent == 'number') { command.consent |= user.consent; } // Add user consent command.username = user.name; // Add user name command.userid = user._id; // Add user id - command.remoteaddr = cleanRemoteAddr(req.ip); // User's IP address + command.remoteaddr = req.clientIp; // User's IP address if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid); } @@ -456,7 +456,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use try { ws.send(JSON.stringify({ action: 'authcookie', - cookie: parent.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, parent.parent.loginCookieEncryptionKey), + cookie: parent.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, parent.parent.loginCookieEncryptionKey), rcookie: parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey) })); } catch (ex) { } @@ -1136,11 +1136,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use for (var i in parent.wsrelays) { r += 'id: ' + i + ', ' + ((parent.wsrelays[i].state == 2)?'connected':'pending'); if (parent.wsrelays[i].peer1 != null) { - r += ', ' + cleanRemoteAddr(parent.wsrelays[i].peer1.req.ip); + r += ', ' + cleanRemoteAddr(parent.wsrelays[i].peer1.req.clientIp); if (parent.wsrelays[i].peer1.user) { r += ' (User:' + parent.wsrelays[i].peer1.user.name + ')' } } if (parent.wsrelays[i].peer2 != null) { - r += ' to ' + cleanRemoteAddr(parent.wsrelays[i].peer2.req.ip); + r += ' to ' + cleanRemoteAddr(parent.wsrelays[i].peer2.req.clientIp); if (parent.wsrelays[i].peer2.user) { r += ' (User:' + parent.wsrelays[i].peer2.user.name + ')' } } r += '\r\n'; diff --git a/webserver.js b/webserver.js index 6cc4542f..335ac0ae 100644 --- a/webserver.js +++ b/webserver.js @@ -572,22 +572,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Check if the source IP address is in the IP list, return false if not. function checkIpAddressEx(req, res, ipList, closeIfThis) { try { - var ip; - if (req.connection) { // HTTP(S) request - ip = req.ip; - - if (ip) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(ip, ipList[i])) { if (closeIfThis === true) { res.sendStatus(401); } return true; } } } + if (req.connection) { + // HTTP(S) request + if (req.clientIp) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(req.clientIp, ipList[i])) { if (closeIfThis === true) { res.sendStatus(401); } return true; } } } if (closeIfThis === false) { res.sendStatus(401); } - } else if (req._socket) { // WebSocket request - ip = req._socket.remoteAddress; - var ipex = (ip.startsWith('::ffff:')) ? ip.substring(7) : ip; - - // If a trusted reverse-proxy is sending us the remote IP address, use it. - // This is not done automatically for web socket like it's done for HTTP requests. - if ((obj.args.trustedproxy) && (res.headers['x-forwarded-for']) && ((obj.args.trustedproxy === true) || (obj.args.trustedproxy.indexOf(ipex) >= 0))) { ip = res.headers['x-forwarded-for']; } - else if ((obj.args.tlsoffload) && (res.headers['x-forwarded-for']) && ((obj.args.tlsoffload === true) || (obj.args.tlsoffload.indexOf(ipex) >= 0))) { ip = res.headers['x-forwarded-for']; } - - if (ip) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(ip, ipList[i])) { if (closeIfThis === true) { try { req.close(); } catch (e) { } } return true; } } } + } else { + // WebSocket request + if (res.clientIp) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(res.clientIp, ipList[i])) { if (closeIfThis === true) { try { req.close(); } catch (e) { } } return true; } } } if (closeIfThis === false) { try { req.close(); } catch (e) { } } } } catch (e) { console.log(e); } // Should never happen @@ -649,8 +640,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Return true if this user has 2-step auth active function checkUserOneTimePasswordRequired(domain, user, req) { // Check if we can skip 2nd factor auth because of the source IP address - if ((req != null) && (req.ip != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) { - for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.ip, domain.passwordrequirements.skip2factor[i]) === true) return false; } + if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) { + for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) return false; } } // Check if a 2nd factor cookie is present @@ -659,7 +650,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { for (var i in cookies) { if (cookies[i].startsWith('twofactor=')) { var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout. - if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === cleanRemoteAddr(req.ip))) && (twoFactorCookie.userid == user._id)) { return false; } + if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { return false; } } } } @@ -896,9 +887,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if ((req.body.token != null) || (req.body.hwtoken != null)) { randomWaitTime = 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095); // This is a fail, wait a random time. 2 to 6 seconds. req.session.messageid = 108; // Invalid token, try again. - if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr(req.ip) + ' port ' + req.port); } + if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr(req.clientIp) + ' port ' + req.port); } parent.debug('web', 'handleLoginRequest: invalid 2FA token'); - obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr(req.ip) }); + obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp }); obj.setbadLogin(req); } else { parent.debug('web', 'handleLoginRequest: 2FA token required'); @@ -919,7 +910,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var maxCookieAge = domain.twofactorcookiedurationdays; if (typeof maxCookieAge != 'number') { maxCookieAge = 30; } console.log('maxCookieAge', maxCookieAge); - const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: cleanRemoteAddr(req.ip)*/ }, obj.parent.loginCookieEncryptionKey); + const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, obj.parent.loginCookieEncryptionKey); res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true }); } @@ -936,7 +927,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Login successful - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + cleanRemoteAddr(req.ip) + ' port ' + req.connection.remotePort); } + if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } parent.debug('web', 'handleLoginRequest: successful 2FA login'); completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct); } @@ -957,12 +948,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Login successful - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + cleanRemoteAddr(req.ip) + ' port ' + req.connection.remotePort); } + if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } parent.debug('web', 'handleLoginRequest: successful login'); completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct); } else { // Login failed, log the error - if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed password for ' + xusername + ' from ' + cleanRemoteAddr(req.ip) + ' port ' + req.connection.remotePort); } + if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } // Wait a random delay setTimeout(function () { @@ -972,12 +963,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (err == 'locked') { parent.debug('web', 'handleLoginRequest: login failed, locked account'); req.session.messageid = 110; // Account locked. - obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + cleanRemoteAddr(req.ip) }); + obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + req.clientIp }); obj.setbadLogin(req); } else { parent.debug('web', 'handleLoginRequest: login failed, bad username and password'); req.session.messageid = 112; // Login failed, check username and password. - obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) }); + obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + req.clientIp }); obj.setbadLogin(req); } } @@ -1033,7 +1024,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { req.session.userid = userid; req.session.domainid = domain.id; req.session.currentNode = ''; - req.session.ip = req.ip; + req.session.ip = req.clientIp; if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; } if (req.body.host) { // TODO: This is a terrible search!!! FIX THIS. @@ -1156,7 +1147,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.users[user._id] = user; req.session.userid = user._id; req.session.domainid = domain.id; - req.session.ip = req.ip; // Bind this session to the IP address of the request + req.session.ip = req.clientIp; // Bind this session to the IP address of the request // Create a user, generate a salt and hash the password require('./pass').hash(req.body.password1, function (err, salt, hash, tag) { if (err) throw err; @@ -1244,7 +1235,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.debug('web', 'handleResetPasswordRequest: success'); req.session.userid = userid; req.session.domainid = domain.id; - req.session.ip = req.ip; // Bind this session to the IP address of the request + req.session.ip = req.clientIp; // Bind this session to the IP address of the request completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tokenusername, req.session.tokenpassword, direct); }, 0); } @@ -1309,7 +1300,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again'); if ((req.body.token != null) || (req.body.hwtoken != null)) { req.session.messageid = 108; // Invalid token, try again. - obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr(req.ip) }); + obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp }); obj.setbadLogin(req); } req.session.loginmode = '5'; @@ -1811,11 +1802,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Login using SSPI domain.sspi.authenticate(req, res, function (err) { if ((err != null) || (req.connection.user == null)) { - if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed SSPI-auth for ' + req.connection.user + ' from ' + cleanRemoteAddr(req.ip) + ' port ' + req.connection.remotePort); } + if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } parent.debug('web', 'handleRootRequest: SSPI auth required.'); res.end('Authentication Required...'); } else { - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted SSPI-auth for ' + req.connection.user + ' from ' + cleanRemoteAddr(req.ip) + ' port ' + req.connection.remotePort); } + if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted SSPI-auth for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } parent.debug('web', 'handleRootRequest: SSPI auth ok.'); handleRootRequestEx(req, res, domain, direct); } @@ -1823,12 +1814,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } else if (req.query.user && req.query.pass) { // User credentials are being passed in the URL. WARNING: Putting credentials in a URL is bad security... but people are requesting this option. obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) { - if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + req.connection.user + ' from ' + cleanRemoteAddr(req.ip) + ' port ' + req.connection.remotePort); } + if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } parent.debug('web', 'handleRootRequest: user/pass in URL auth ok.'); req.session.userid = userid; req.session.domainid = domain.id; req.session.currentNode = ''; - req.session.ip = req.ip; // Bind this session to the IP address of the request + req.session.ip = req.clientIp; // Bind this session to the IP address of the request handleRootRequestEx(req, res, domain, direct); }); } else { @@ -1854,7 +1845,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { req.session.userid = 'user/' + domain.id + '/~'; req.session.domainid = domain.id; req.session.currentNode = ''; - req.session.ip = req.ip; // Bind this session to the IP address of the request + req.session.ip = req.clientIp; // Bind this session to the IP address of the request if (obj.users[req.session.userid] == null) { // Create the dummy user ~ with impossible password parent.debug('web', 'handleRootRequestEx: created dummy user in nouser mode.'); @@ -1868,10 +1859,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase(); req.session.domainid = domain.id; req.session.currentNode = ''; - req.session.ip = req.ip; // Bind this session to the IP address of the request + req.session.ip = req.clientIp; // Bind this session to the IP address of the request } else if (req.query.login && (obj.parent.loginCookieEncryptionKey != null)) { var loginCookie = obj.parent.decodeCookie(req.query.login, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout - //if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != cleanRemoteAddr(req.ip))) { loginCookie = null; } // If the cookie if binded to an IP address, check here. + //if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // If the cookie if binded to an IP address, check here. if ((loginCookie != null) && (loginCookie.a == 3) && (loginCookie.u != null) && (loginCookie.u.split('/')[1] == domain.id)) { // If a login cookie was provided, setup the session here. parent.debug('web', 'handleRootRequestEx: cookie auth ok.'); @@ -1879,7 +1870,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { req.session.userid = loginCookie.u; req.session.domainid = domain.id; req.session.currentNode = ''; - req.session.ip = req.ip; // Bind this session to the IP address of the request + req.session.ip = req.clientIp; // Bind this session to the IP address of the request } else { parent.debug('web', 'handleRootRequestEx: cookie auth failed.'); } @@ -1896,7 +1887,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { req.session.usersGroups = req.connection.userGroups; req.session.domainid = domain.id; req.session.currentNode = ''; - req.session.ip = req.ip; // Bind this session to the IP address of the request + req.session.ip = req.clientIp; // Bind this session to the IP address of the request // Check if this user exists, create it if not. user = obj.users[req.session.userid]; @@ -1996,9 +1987,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true)) { // Check if we can skip 2nd factor auth because of the source IP address var skip2factor = false; - if ((req != null) && (req.ip != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) { + if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) { for (var i in domain.passwordrequirements.skip2factor) { - if (require('ipcheck').match(req.ip, domain.passwordrequirements.skip2factor[i]) === true) { skip2factor = true; } + if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) { skip2factor = true; } } } if (skip2factor == false) { features += 0x00040000; } // Force 2-factor auth @@ -2015,7 +2006,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (domain.urlswitching === false) { features += 0x10000000; } // Disables the URL switching feature // Create a authentication cookie - const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, obj.parent.loginCookieEncryptionKey); + const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey); const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey); // Send the master web application @@ -2196,7 +2187,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button // Create a authentication cookie - const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, obj.parent.loginCookieEncryptionKey); + const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey); const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey); var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified render(req, res, getRenderPage('xterm', req, domain), getRenderArgs({ serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, authCookie: authCookie, authRelayCookie: authRelayCookie, logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'), name: EscapeHtml(node.name) }, req, domain)); @@ -2701,7 +2692,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // If an authentication cookie is embedded in the form, use that. if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) { var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout - if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != cleanRemoteAddr(req.ip))) { loginCookie = null; } // Check cookie IP binding. + if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding. if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication } if (authUserid == null) { res.sendStatus(401); return; } @@ -2737,7 +2728,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // If an authentication cookie is embedded in the form, use that. if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) { var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout - if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != cleanRemoteAddr(req.ip))) { loginCookie = null; } // Check cookie IP binding. + if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding. if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication } if (authUserid == null) { res.sendStatus(401); return; } @@ -2895,7 +2886,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var fd = obj.fs.openSync(recFullFilename, 'w'); if (fd != null) { // Write the recording file header - var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: cleanRemoteAddr(req.ip), nodeid: node._id, intelamt: true, protocol: (req.query.p == 2) ? 101 : 100, time: new Date().toLocaleString() }) + var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: req.clientIp, nodeid: node._id, intelamt: true, protocol: (req.query.p == 2) ? 101 : 100, time: new Date().toLocaleString() }) recordingEntry(fd, 1, 0, firstBlock, function () { }); ws.logfile = { fd: fd, lock: false }; if (req.query.p == 2) { ws.send(Buffer.from(String.fromCharCode(0xF0), 'binary')); } // Intel AMT Redirection: Indicate the session is being recorded @@ -2990,7 +2981,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // If error, close the associated TCP connection. ws.on('error', function (err) { - console.log('CIRA server websocket error from ' + cleanRemoteAddr(req.ip) + ', ' + err.toString().split('\r')[0] + '.'); + console.log('CIRA server websocket error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); parent.debug('webrelay', 'Websocket relay closed on error.'); if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS @@ -3083,8 +3074,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // If error, close the associated TCP connection. ws.on('error', function (err) { - console.log('Error with relay web socket connection from ' + cleanRemoteAddr(req.ip) + ', ' + err.toString().split('\r')[0] + '.'); - parent.debug('webrelay', 'Error with relay web socket connection from ' + cleanRemoteAddr(req.ip) + '.'); + console.log('Error with relay web socket connection from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); + parent.debug('webrelay', 'Error with relay web socket connection from ' + req.clientIp + '.'); if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } } // Close the recording file @@ -3197,7 +3188,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (mesh.mtype != 1) { ws.send(JSON.stringify({ errorText: 'Invalid device group type:' + ws.meshid })); delete ws.meshid; ws.close(); return; } // Fetch the remote IP:Port for logging - ws.remoteaddr = cleanRemoteAddr(req.ip); + ws.remoteaddr = req.clientIp; ws.remoteaddrport = ws.remoteaddr + ':' + ws._socket.remotePort; // When data is received from the web socket, echo it back @@ -3389,7 +3380,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { }); // If error, do nothing. - ws.on('error', function (err) { console.log('Echo server error from ' + cleanRemoteAddr(req.ip) + ', ' + err.toString().split('\r')[0] + '.'); }); + ws.on('error', function (err) { console.log('Echo server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); }); // If closed, do nothing ws.on('close', function (req) { }); @@ -3452,7 +3443,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (auth.response === obj.common.ComputeDigesthash(auth.username, amtpass, auth.realm, 'POST', auth.uri, auth.qop, auth.nonce, auth.nc, auth.cnonce)) { // This is an authenticated Intel AMT event, update the host address - var amthost = req.ip; + var amthost = req.clientIp; if (amthost.substring(0, 7) === '::ffff:') { amthost = amthost.substring(7); } if (node.host != amthost) { // Get the mesh for this device @@ -3549,7 +3540,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // If an authentication cookie is embedded in the form, use that. if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) { var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout - if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != cleanRemoteAddr(req.ip))) { loginCookie = null; } // Check cookie IP binding. + if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding. if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication } if (authUserid == null) { res.sendStatus(401); return; } @@ -4015,9 +4006,31 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Add HTTP security headers to all responses obj.app.use(function (req, res, next) { - parent.debug('webrequest', '(' + cleanRemoteAddr(req.ip) + ') ' + req.url); + // Set the real IP address of the request + // If a trusted reverse-proxy is sending us the remote IP address, use it. + const ipex = (req.ip.startsWith('::ffff:')) ? req.ip.substring(7) : req.ip; + if ( + (obj.args.trustedproxy === true) || + ((typeof obj.args.trustedproxy == 'object') && (obj.args.trustedproxy.indexOf(ipex) >= 0)) || + ((typeof obj.args.tlsoffload == 'object') && (obj.args.tlsoffload.indexOf(ipex) >= 0)) + ) { + if (req.headers['cf-connecting-ip']) { // Use CloudFlare IP address if present + req.clientIp = req.headers['cf-connecting-ip'].split(',')[0].trim(); + } else if (res.headers['x-forwarded-for']) { + req.clientIp = req.headers['x-forwarded-for'].split(',')[0].trim(); + } else if (res.headers['x-real-ip']) { + req.clientIp = req.headers['x-real-ip'].split(',')[0].trim(); + } else { + req.clientIp = ipex; + } + } else { + req.clientIp = ipex; + } + + // Get the domain for this request + const domain = req.xdomain = getDomain(req); + parent.debug('webrequest', '(' + req.clientIp + ') ' + req.url); res.removeHeader('X-Powered-By'); - var domain = req.xdomain = getDomain(req); // If this domain has configured headers, use them. // Example headers: { 'Strict-Transport-Security': 'max-age=360000;includeSubDomains' }; @@ -4039,7 +4052,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Check the session if bound to the external IP address - if ((req.session.ip != null) && (req.ip != null) && (req.session.ip != req.ip)) { req.session = {}; } + if ((req.session.ip != null) && (req.clientIp != null) && (req.session.ip != req.clientIp)) { req.session = {}; } // Detect if this is a file sharing domain, if so, just share files. if ((domain != null) && (domain.share != null)) { @@ -4354,8 +4367,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Receive mesh agent connections obj.app.ws(url + 'agent.ashx', function (ws, req) { var domain = checkAgentIpAddress(ws, req); - if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + cleanRemoteAddr(req.ip) + ', holding.'); return; } - //console.log('Agent connect: ' + cleanRemoteAddr(req.ip)); + if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; } + //console.log('Agent connect: ' + req.clientIp); try { obj.meshAgentHandler.CreateMeshAgent(obj, obj.db, ws, req, obj.args, domain); } catch (e) { console.log(e); } }); @@ -4363,11 +4376,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (obj.parent.mqttbroker != null) { obj.app.ws(url + 'mqtt.ashx', function (ws, req) { var domain = checkAgentIpAddress(ws, req); - if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + cleanRemoteAddr(req.ip) + ', holding.'); return; } + if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; } var serialtunnel = SerialTunnel(); serialtunnel.xtransport = 'ws'; serialtunnel.xdomain = domain; - serialtunnel.xip = req.ip; + serialtunnel.xip = req.clientIp; ws.on('message', function (b) { serialtunnel.updateBuffer(Buffer.from(b, 'binary')) }); serialtunnel.forwardwrite = function (b) { ws.send(b, 'binary') } ws.on('close', function () { serialtunnel.emit('end'); }); @@ -4380,8 +4393,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Receive mesh agent connections on alternate port obj.agentapp.ws(url + 'agent.ashx', function (ws, req) { var domain = checkAgentIpAddress(ws, req); - if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + cleanRemoteAddr(req.ip) + ', holding.'); return; } - //console.log('Agent connect: ' + cleanRemoteAddr(req.ip)); + if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; } + //console.log('Agent connect: ' + req.clientIp); try { obj.meshAgentHandler.CreateMeshAgent(obj, obj.db, ws, req, obj.args, domain); } catch (e) { console.log(e); } }); @@ -4554,7 +4567,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } else { // If not authenticated, close the websocket connection parent.debug('web', 'ERR: Websocket bad user/pass auth'); - //obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + obj.args.user.toLowerCase()], obj, { action: 'authfail', userid: 'user/' + domain.id + '/' + obj.args.user.toLowerCase(), username: obj.args.user, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) }); + //obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + obj.args.user.toLowerCase()], obj, { action: 'authfail', userid: 'user/' + domain.id + '/' + obj.args.user.toLowerCase(), username: obj.args.user, domain: domain.id, msg: 'Invalid user login attempt from ' + req.clientIp }); //obj.setbadLogin(req); try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-2' })); ws.close(); } catch (e) { } } @@ -4565,8 +4578,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // This is a encrypted cookie authentication var cookie = obj.parent.decodeCookie(req.query.auth, obj.parent.loginCookieEncryptionKey, 240); // Cookie with 4 hour timeout if ((cookie == null) && (obj.parent.multiServer != null)) { cookie = obj.parent.decodeCookie(req.query.auth, obj.parent.serverKey, 240); } // Try the server key - if ((obj.args.cookieipcheck !== false) && (cookie != null) && (cookie.ip != null) && (cookie.ip != cleanRemoteAddr(req.ip) && (cookie.ip != req.ip))) { // If the cookie if binded to an IP address, check here. - parent.debug('web', 'ERR: Invalid cookie IP address, got \"' + cookie.ip + '\", expected \"' + cleanRemoteAddr(req.ip) + '\".'); + if ((obj.args.cookieipcheck !== false) && (cookie != null) && (cookie.ip != null) && (cookie.ip != req.clientIp && (cookie.ip != req.clientIp))) { // If the cookie if binded to an IP address, check here. + parent.debug('web', 'ERR: Invalid cookie IP address, got \"' + cookie.ip + '\", expected \"' + cleanRemoteAddr(req.clientIp) + '\".'); cookie = null; } if ((cookie != null) && (obj.users[cookie.userid]) && (cookie.domainid == domain.id)) { @@ -5549,7 +5562,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } obj.setbadLogin = function (ip) { // Set an IP address that just did a bad login request if (parent.config.settings.maxinvalidlogin === false) return; - if (typeof ip == 'object') { ip = cleanRemoteAddr(ip.ip); } + if (typeof ip == 'object') { ip = ip.clientIp; } var splitip = ip.split('.'); if (splitip.length == 4) { ip = (splitip[0] + '.' + splitip[1] + '.' + splitip[2] + '.*'); } if (++obj.badLoginTableLastClean > 100) { obj.cleanBadLoginTable(); } @@ -5561,7 +5574,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } obj.checkAllowLogin = function (ip) { // Check if an IP address is allowed to login if (parent.config.settings.maxinvalidlogin === false) return true; - if (typeof ip == 'object') { ip = cleanRemoteAddr(ip.ip); } + if (typeof ip == 'object') { ip = ip.clientIp; } var splitip = ip.split('.'); if (splitip.length == 4) { ip = (splitip[0] + '.' + splitip[1] + '.' + splitip[2] + '.*'); } // If this is IPv4, keep only the 3 first var cutoffTime = Date.now() - (parent.config.settings.maxinvalidlogin.time * 60000); // Time in minutes