More work on device self-sharing.

Ylian Saint-Hilaire committed Nov 10, 2021 at 13:21 UTC ecd25f3eb286fa51e4ed160d5a9831c2c1d509ea
5 files changed +35 -23
meshagent.js
+3 -3
@@ -1720,13 +1720,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
1720 if (serverName.split('.') == 1) { url = '/' + xdomain + page + '?c=' + inviteCookie; }
1721
1722 // Create a device sharing database entry
1723 - var shareEntry = { _id: 'deviceshare-' + publicid, type: 'deviceshare', nodeid: obj.dbNodeKey, p: flags, domain: domain.id, publicid: publicid, guestName: '*Agent', consent: true, url: url };
1723 + var shareEntry = { _id: 'deviceshare-' + publicid, type: 'deviceshare', nodeid: obj.dbNodeKey, p: flags, domain: domain.id, publicid: publicid, guestName: 'Agent', consent: 0x7F, url: url };
1724 if (viewOnly === true) { shareEntry.viewOnly = true; }
1725 parent.db.Set(shareEntry);
1726
1727 // Send out an event that we added a device share
1728 var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey);
1729 - var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'addedDeviceShare', msg: 'Added Device Share', msgid: 101, msgArgs: ['*Agent', 'DATETIME:' + null, 'DATETIME:' + null], domain: domain.id };
1729 + var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'addedDeviceShare', msg: 'Added device share with unlimited time', msgid: 131, msgArgs: ['Agent'], domain: domain.id };
1730 parent.parent.DispatchEvent(targets, obj, event);
1731
1732 // Send device share update
@@ -1773,7 +1773,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
1773 if (removedExact != null) {
1774 // Send out an event that we removed a device share
1775 var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []);
1776 - var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: ['*Agent'], domain: domain.id, publicid: publicid };
1776 + var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: ['Agent'], domain: domain.id, publicid: publicid };
1777 parent.parent.DispatchEvent(targets, obj, event);
1778 }
1779
meshrelay.js
+23 -16
@@ -84,6 +84,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
84 obj.user = user;
85 obj.ruserid = null;
86 obj.req = req; // Used in multi-server.js
87 + if ((cookie != null) && (cookie.nouser == 1)) { obj.nouser = true; } // This is a relay without user authentication
88
89 // Setup subscription for desktop sharing public identifier
90 // If the identifier is removed, drop the connection
@@ -96,11 +97,11 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
97 // Check relay authentication
98 if ((user == null) && (obj.req.query != null) && (obj.req.query.rauth != null)) {
99 const rcookie = parent.parent.decodeCookie(obj.req.query.rauth, parent.parent.loginCookieEncryptionKey, 240); // Cookie with 4 hour timeout
99 - if (rcookie.ruserid != null) { obj.ruserid = rcookie.ruserid; }
100 + if (rcookie.ruserid != null) { obj.ruserid = rcookie.ruserid; } else if (rcookie.nouser === 1) { obj.rnouser = true; }
101 }
102
103 // If there is no authentication, drop this connection
103 - 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; }
104 + if ((obj.id != null) && (obj.id.startsWith('meshmessenger/') == false) && (obj.user == null) && (obj.ruserid == null) && (obj.nouser !== true) && (obj.rnouser !== true)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Connection with no authentication (' + obj.req.clientIp + ')'); } catch (e) { console.log(e); } return; }
105
106 // Relay session count (we may remove this in the future)
107 obj.relaySessionCounted = true;
@@ -153,8 +154,8 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
154 obj.sendAgentMessage = function (command, userid, domainid) {
155 var rights, mesh;
156 if (command.nodeid == null) return false;
156 - var user = parent.users[userid];
157 - if (user == null) return false;
157 + var user = null;
158 + if (userid != null) { user = parent.users[userid]; if (user == null) return false; }
159 var splitnodeid = command.nodeid.split('/');
160 // Check that we are in the same domain and the user has rights over this node.
161 if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domainid)) {
@@ -163,15 +164,17 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
164 var agent = parent.wsagents[command.nodeid];
165 if (agent != null) {
166 // Check if we have permission to send a message to that node
166 - rights = parent.GetNodeRights(user, agent.dbMeshKey, agent.dbNodeKey);
167 + if (userid == null) { rights = MESHRIGHT_REMOTECONTROL; } else { rights = parent.GetNodeRights(user, agent.dbMeshKey, agent.dbNodeKey); }
168 mesh = parent.meshes[agent.dbMeshKey];
169 if ((rights != null) && (mesh != null) || ((rights & MESHRIGHT_REMOTECONTROL) != 0)) {
170 if (ws.sessionId) { command.sessionid = ws.sessionId; } // Set the session id, required for responses.
171 command.rights = rights; // Add user rights flags to the message
172 if (typeof command.consent == 'number') { command.consent = command.consent | mesh.consent; } else { command.consent = mesh.consent; } // Add user consent
173 if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
173 - command.username = user.name; // Add user name
174 - command.realname = user.realname; // Add real name
174 + if (user != null) {
175 + command.username = user.name; // Add user name
176 + command.realname = user.realname; // Add real name
177 + }
178 if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text
179 delete command.nodeid; // Remove the nodeid since it's implyed.
180 agent.send(JSON.stringify(command));
@@ -182,15 +185,17 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
185 var routing = parent.parent.GetRoutingServerIdNotSelf(command.nodeid, 1); // 1 = MeshAgent routing type
186 if (routing != null) {
187 // Check if we have permission to send a message to that node
185 - rights = parent.GetNodeRights(user, routing.meshid, command.nodeid);
188 + if (userid == null) { rights = MESHRIGHT_REMOTECONTROL; } else { rights = parent.GetNodeRights(user, routing.meshid, command.nodeid); }
189 mesh = parent.meshes[routing.meshid];
190 if (rights != null || ((rights & MESHRIGHT_REMOTECONTROL) != 0)) {
191 if (ws.sessionId) { command.fromSessionid = ws.sessionId; } // Set the session id, required for responses.
192 command.rights = rights; // Add user rights flags to the message
193 if (typeof command.consent == 'number') { command.consent = command.consent | mesh.consent; } else { command.consent = mesh.consent; } // Add user consent
194 if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
192 - command.username = user.name; // Add user name
193 - command.realname = user.realname; // Add real name
195 + if (user != null) {
196 + command.username = user.name; // Add user name
197 + command.realname = user.realname; // Add real name
198 + }
199 if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text
200 parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
201 return true;
@@ -285,7 +290,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
290 if (u1 != null) { u1 = 'user/' + domain.id + '/' + parent.args.user.toLowerCase(); }
291 if (u2 != null) { u2 = 'user/' + domain.id + '/' + parent.args.user.toLowerCase(); }
292 }
288 - if (u1 != u2) {
293 + if ((u1 != u2) && (obj.nouser !== true) && (relayinfo.peer1.nouser !== true)) {
294 ws.close();
295 parent.parent.debug('relay', 'Relay auth mismatch (' + u1 + ' != ' + u2 + '): ' + obj.id + ' (' + obj.req.clientIp + ')');
296 delete obj.id;
@@ -829,7 +834,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
834 setExpireTimer();
835
836 // Mark this relay session as authenticated if this is the user end.
832 - obj.authenticated = (user != null);
837 + obj.authenticated = ((user != null) || (obj.nouser === true));
838 if (obj.authenticated) {
839 // To build the connection URL, if we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
840 var xdomain = (domain.dns == null) ? domain.id : '';
@@ -931,7 +936,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
936 const node = docs[0];
937
938 // Check if this user has permission to manage this computer
934 - if ((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
939 + if ((obj.nouser !== true) && ((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
940
941 // Set nodeid and meshid
942 obj.nodeid = node._id;
@@ -939,8 +944,10 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
944
945 // Send connection request to agent
946 if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); }
942 - const rcookie = parent.parent.encodeCookie({ ruserid: user._id, nodeid: node._id }, parent.parent.loginCookieEncryptionKey);
943 - const command = { nodeid: node._id, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?p=' + obj.req.query.p + '&id=' + obj.id + '&rauth=' + rcookie + '&nodeid=' + node._id, soptions: {}, rights: cookie.r, guestname: cookie.gn, consent: cookie.cf, remoteaddr: cleanRemoteAddr(obj.req.clientIp) };
947 + const rcookieData = { nodeid: node._id };
948 + if (user != null) { rcookieData.ruserid = user._id; } else if (obj.nouser === true) { rcookieData.nouser = 1; }
949 + const rcookie = parent.parent.encodeCookie(rcookieData, parent.parent.loginCookieEncryptionKey);
950 + const command = { nodeid: node._id, action: 'msg', type: 'tunnel', value: '*/' + xdomain + 'meshrelay.ashx?p=' + obj.req.query.p + '&id=' + obj.id + '&rauth=' + rcookie + '&nodeid=' + node._id, soptions: {}, rights: cookie.r, guestname: cookie.gn, consent: cookie.cf, remoteaddr: cleanRemoteAddr(obj.req.clientIp) };
951 obj.guestname = cookie.gn;
952
953 // Limit what this relay connection can do
@@ -966,7 +973,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
973 if (typeof domain.notificationmessages.files == 'string') { command.soptions.notifyMsgFiles = domain.notificationmessages.files; }
974 }
975 parent.parent.debug('relay', 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
969 - 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 + ')'); }
976 + if (obj.sendAgentMessage(command, user?user._id:null, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); }
977
978 performRelay(0);
979 });
meshuser.js
+1 -1
@@ -403,7 +403,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
403 else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); }
404 else {
405 // If updating guest device shares, if we are updating a user that is not creator of the share, remove the URL.
406 - if (event.action == 'deviceShareUpdate') {
406 + if ((event.action == 'deviceShareUpdate') && (Array.isArray(event.deviceShares))) {
407 event = common.Clone(event);
408 for (var i in event.deviceShares) { if (event.deviceShares[i].userid != user._id) { delete event.deviceShares[i].url; } }
409 }
public/scripts/agent-redir-ws-0.1.1.js
+1 -1
@@ -76,7 +76,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
76 obj.xxOnControlCommand = function (msg) {
77 var controlMsg;
78 try { controlMsg = JSON.parse(msg); } catch (e) { return; }
79 - if (controlMsg.ctrlChannel != '102938') { obj.m.ProcessData(msg); return; }
79 + if (controlMsg.ctrlChannel != '102938') { if (obj.m.ProcessData) { obj.m.ProcessData(msg); } else { console.log(msg); } return; }
80 if ((typeof args != 'undefined') && args.redirtrace) { console.log('RedirRecv', controlMsg); }
81 if (controlMsg.type == 'console') {
82 obj.setConsoleMessage(controlMsg.msg, controlMsg.msgid, controlMsg.msgargs, controlMsg.timeout);
webserver.js
+7 -2
@@ -3584,6 +3584,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3584 const doc = docs[0];
3585 // Generate an old style cookie from the information in the database
3586 var cookie = { a: 5, p: doc.p, uid: doc.userid, gn: doc.guestName, nid: doc.nodeid, cf: doc.consent, pid: doc.publicid };
3587 + if ((cookie.userid == null) && (cookie.pid.startsWith('AS:node/'))) { cookie.nouser = 1; }
3588 if ((doc.startTime != null) && (doc.expireTime != null)) { cookie.start = doc.startTime; cookie.expire = doc.expireTime; }
3589 if (doc.viewOnly === true) { cookie.vo = 1; }
3590 handleSharingRequestEx(req, res, domain, cookie);
@@ -3618,7 +3619,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3619
3620 // Looks good, let's create the outbound session cookies.
3621 // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
3621 - const authCookie = obj.parent.encodeCookie({ userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo }, obj.parent.loginCookieEncryptionKey);
3622 + const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo };
3623 + if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; }
3624 + const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey);
3625
3626 // Server features
3627 var features2 = 0;
@@ -3633,7 +3636,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3636 });
3637 }
3638
3636 -
3639 // Handle domain redirection
3640 obj.handleDomainRedirect = function (req, res) {
3641 const domain = checkUserIpAddress(req, res);
@@ -6595,6 +6597,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
6597 } else if ((cookie != null) && (cookie.a === 3) && (typeof cookie.u == 'string') && (obj.users[cookie.u]) && (cookie.u.split('/')[1] == domain.id)) {
6598 // Valid cookie, we are authenticated. Cookie of format { u: 'user//name', a: 3 }
6599 func(ws, req, domain, obj.users[cookie.u], cookie);
6600 + } else if ((cookie != null) && (cookie.nouser === 1)) {
6601 + // This is a valid cookie, but no user. This is used for agent self-sharing.
6602 + func(ws, req, domain, null, cookie);
6603 } else {
6604 // This is a bad cookie, keep going anyway, maybe we have a active session that will save us.
6605 if ((cookie != null) && (cookie.domainid != domain.id)) { parent.debug('web', 'ERR: Invalid domain, got \"' + cookie.domainid + '\", expected \"' + domain.id + '\".'); }