Added CloudFlare Support, improved reverse-proxy HTTP header handling.

This commit is contained in:
Ylian Saint-Hilaire 2020-05-21 21:25:11 -07:00
parent a0b6d235ec
commit 31e1f61e52
5 changed files with 113 additions and 100 deletions

View File

@ -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

View File

@ -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);
});

View File

@ -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();
});

View File

@ -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';

View File

@ -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