diff --git a/db.js b/db.js index 152bbe27..d682f86c 100644 --- a/db.js +++ b/db.js @@ -1409,7 +1409,7 @@ module.exports.CreateDB = function (parent, func) { obj.GetEventsWithLimit = function (ids, domain, limit, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); }; obj.GetUserEvents = function (ids, domain, username, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); }; obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); }; - obj.GetEventsTimeRange = function (ids, domain, msgids, start, end, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: 1 }).toArray(func); }; + obj.GetEventsTimeRange = function (ids, domain, msgids, start, end, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }).project({ type: 0, _id: 0, domain: 0, node: 0 }).sort({ time: 1 }).toArray(func); }; obj.GetUserLoginEvents = function (domain, userid, func) { obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }).project({ action: 1, time: 1, msgid: 1, msgArgs: 1, tokenName: 1 }).sort({ time: -1 }).toArray(func); }; obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); }; obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); }; @@ -1598,9 +1598,9 @@ module.exports.CreateDB = function (parent, func) { }; obj.GetEventsTimeRange = function (ids, domain, msgids, start, end, func) { if (obj.databaseType == 1) { - obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: 1 }).exec(func); + obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }, { type: 0, _id: 0, domain: 0, node: 0 }).sort({ time: 1 }).exec(func); } else { - obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: 1 }, func); + obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }, { type: 0, _id: 0, domain: 0, node: 0 }).sort({ time: 1 }, func); } }; obj.GetUserLoginEvents = function (domain, userid, func) { diff --git a/meshrelay.js b/meshrelay.js index 9fa7de71..c8ee2476 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -662,14 +662,19 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { else if (obj.req.query.p == 2) { msg = 'Ended desktop session', msgid = 11; } else if (obj.req.query.p == 5) { msg = 'Ended file management session', msgid = 12; } else if (obj.req.query.p == 200) { msg = 'Ended messenger session', msgid = 112; } + + // Get the nodeid and meshid of this device + var nodeid = (obj.nodeid == null) ? peer.nodeid : obj.nodeid; + var meshid = (obj.meshid == null) ? peer.meshid : obj.meshid; + if (user) { var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [obj.id, obj.req.clientIp, obj.peer.req.clientIp, Math.floor((Date.now() - ws.time) / 1000)], 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 }; if (obj.guestname) { event.guestname = obj.guestname; } else if (peer.guestname) { event.guestname = peer.guestname; } // If this is a sharing session, set the guest name here. - parent.parent.DispatchEvent(['*', user._id], obj, event); + parent.parent.DispatchEvent(['*', user._id, nodeid, meshid], obj, event); } else if (peer.user) { var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: peer.user._id, username: peer.user.name, msgid: msgid, msgArgs: [obj.id, obj.req.clientIp, obj.peer.req.clientIp, Math.floor((Date.now() - ws.time) / 1000)], 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 }; if (obj.guestname) { event.guestname = obj.guestname; } else if (peer.guestname) { event.guestname = peer.guestname; } // If this is a sharing session, set the guest name here. - parent.parent.DispatchEvent(['*', peer.user._id], obj, event); + parent.parent.DispatchEvent(['*', peer.user._id, nodeid, meshid], obj, event); } } @@ -677,6 +682,8 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { delete peer.id; delete peer.ws; delete peer.peer; + delete peer.nodeid; + delete peer.meshid; if (peer.pingtimer != null) { clearInterval(peer.pingtimer); delete peer.pingtimer; } if (peer.pongtimer != null) { clearInterval(peer.pongtimer); delete peer.pongtimer; } } else { @@ -710,7 +717,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { if (tag.logfile.startTime) { event.startTime = tag.logfile.startTime; event.lengthTime = sessionLength; } if (tag.logfile.name) { event.name = tag.logfile.name; } if (tag.logfile.icon) { event.icon = tag.logfile.icon; } - parent.parent.DispatchEvent(['*', 'recording', obj.nodeid, obj.meshid], obj, event); + parent.parent.DispatchEvent(['*', 'recording', tag.logfile.nodeid, tag.logfile.meshid], obj, event); cleanUpRecordings(); }, { ws: ws, pws: peer.ws, logfile: logfile }); @@ -725,6 +732,8 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { delete obj.id; delete obj.ws; delete obj.peer; + delete obj.nodeid; + delete obj.meshid; if (obj.pingtimer != null) { clearInterval(obj.pingtimer); delete obj.pingtimer; } if (obj.pongtimer != null) { clearInterval(obj.pongtimer); delete obj.pongtimer; } @@ -814,10 +823,14 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { parent.db.Get(cookie.nodeid, function (err, docs) { if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket const node = docs[0]; - + // Check if this user has permission to manage this computer if ((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (1)'); try { obj.close(); } catch (e) { } return; } + // Set nodeid and meshid + obj.nodeid = node._id; + obj.meshid = node.meshid; + // Send connection request to agent const rcookie = parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey); if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one. @@ -844,10 +857,14 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { parent.db.Get(obj.req.query.nodeid, function (err, docs) { if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket const node = docs[0]; - + // Check if this user has permission to manage this computer if ((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; } + // Set nodeid and meshid + obj.nodeid = node._id; + obj.meshid = node.meshid; + // Send connection request to agent if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one. const rcookie = parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey); @@ -897,6 +914,10 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { // Check if this user has permission to manage this computer if ((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; } + // Set nodeid and meshid + obj.nodeid = node._id; + obj.meshid = node.meshid; + // Send connection request to agent if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } const rcookie = parent.parent.encodeCookie({ ruserid: user._id, nodeid: node._id }, parent.parent.loginCookieEncryptionKey); @@ -930,6 +951,19 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { performRelay(0); }); return obj; + } else { + // No routing needed. Just check permissions and fill in the device nodeid and meshid. + parent.db.Get(obj.req.query.nodeid, function (err, docs) { + if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket + const node = docs[0]; + + // Check if this user has permission to manage this computer + if ((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; } + + // Set nodeid and meshid + obj.nodeid = node._id; + obj.meshid = node.meshid; + }); } } diff --git a/meshuser.js b/meshuser.js index aef4b56f..28016d17 100644 --- a/meshuser.js +++ b/meshuser.js @@ -5413,11 +5413,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (common.validateInt(command.type, 1, 1) == false) break; // Validate type if (common.validateInt(command.groupBy, 1, 3) == false) break; // Validate groupBy: 1 = User, 2 = Device, 3 = Day if ((typeof command.start != 'number') || (typeof command.end != 'number') || (command.start >= command.end)) break; // Validate start and end time + if ((command.devGroup != null) && ((user.links == null) || (user.links[command.devGroup] == null))) break; // Asking for a device group that is not allowed if (command.type == 1) { // This is the remote session report. Shows desktop, terminal, files... // If we are not user administrator on this site, only search for events with our own user id. var ids = [user._id]; - if ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0) { ids = ['*']; } + if ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0) { + if (command.devGroup != null) { + ids = [ user._id, command.devGroup ]; + } else { + if (user.links) { for (var i in user.links) { ids.push(i); } } + } + } // Get the events in the time range // MySQL or MariaDB query will ignore the MsgID filter. @@ -5438,7 +5445,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Rows for (var i in docs) { - if ((docs[i].msgid != 5) && (docs[i].msgid != 10) && (docs[i].msgid != 11) && (docs[i].msgid != 12) && (docs[i].msgid != 122)) continue; // If MySQL or MariaDB query, we can't filter on MsgID, so we have to do it here. + // If MySQL or MariaDB query, we can't filter on MsgID, so we have to do it here. + if ((docs[i].msgid != 5) && (docs[i].msgid != 10) && (docs[i].msgid != 11) && (docs[i].msgid != 12) && (docs[i].msgid != 122)) continue; + if ((command.devGroup != null) && (docs[i].ids != null) && (docs[i].ids.indexOf(command.devGroup) == -1)) continue; var entry = { time: docs[i].time.valueOf() }; diff --git a/package.json b/package.json index d660e8cc..9492315e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "sample-config-advanced.json" ], "dependencies": { + "archiver": "^4.0.2", + "archiver-zip-encrypted": "^1.0.10", "body-parser": "^1.19.0", "cbor": "~5.2.0", "compression": "^1.7.4", @@ -43,13 +45,24 @@ "express": "^4.17.0", "express-handlebars": "^3.1.0", "express-ws": "^4.0.0", + "image-size": "^1.0.0", "ipcheck": "^0.1.0", + "loadavg-windows": "^1.1.1", "minimist": "^1.2.0", + "mongodb": "^4.1.0", "multiparty": "^4.2.1", + "mysql": "^2.18.1", "nedb": "^1.8.0", "node-forge": "^0.10.0", + "node-rdpjs-2": "^0.3.5", + "node-windows": "^1.0.0-beta.5", + "otplib": "^10.2.3", + "saslprep": "^1.0.3", + "ssh2": "^1.4.0", + "web-push": "^3.4.5", "ws": "^5.2.3", - "yauzl": "^2.10.0" + "yauzl": "^2.10.0", + "yubikeyotp": "^0.2.0" }, "repository": { "type": "git", diff --git a/views/default.handlebars b/views/default.handlebars index 5fad222b..2eb8e958 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -7245,7 +7245,7 @@ x += '
'; x += addHtmlValue("Expire Time", ''); x += '
'; if (currentNode.agent.caps & 1) { x += addHtmlValue("User Consent", ''); } setDialogMode(2, "Share Device", 3, showShareDeviceEx, x); @@ -14927,6 +14927,11 @@ for (var i in options) { y += ''; } x += addHtmlValue("Group by", ''); + y = ''; + var omeshs = getOrderedList(meshes, 'name'); + for (var i in omeshs) { y += ''; } + x += addHtmlValue("Device Group", ''); + y = ''; if (settings.timeRange == null) { settings.timeRange = 1; } var options = { 1 : "Last Day", 7: "Last 7 days", 30: "Last 30 days", 0: "Time range" } @@ -14934,7 +14939,7 @@ x += addHtmlValue("Time", ''); x += ''; setDialogMode(2, "Generate Report", 3, generateReportDialogEx, x); @@ -14962,8 +14967,10 @@ } var tz = null; try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone; } catch (ex) {} - putstore('_ReportSettings', JSON.stringify({ type: parseInt(Q('d2reportType').value), groupBy: parseInt(Q('d2groupBy').value), timeRange: parseInt(Q('d2timeRange').value) })); - meshserver.send({ action: 'report', type: parseInt(Q('d2reportType').value), groupBy: parseInt(Q('d2groupBy').value), start: start, end: end, tz: tz, tf: new Date().getTimezoneOffset(), l: getLang() }); + var devGroup = decodeURIComponent(Q('d2groupId').value); + if (devGroup == 0) { devGroup = null; } + putstore('_ReportSettings', JSON.stringify({ type: parseInt(Q('d2reportType').value), groupBy: parseInt(Q('d2groupBy').value), timeRange: parseInt(Q('d2timeRange').value), devGroup: devGroup })); + meshserver.send({ action: 'report', type: parseInt(Q('d2reportType').value), groupBy: parseInt(Q('d2groupBy').value), start: start, end: end, tz: tz, tf: new Date().getTimezoneOffset(), l: getLang(), devGroup: devGroup }); } function renderReport(r) {