More improvements to MeshCtrl

Ylian Saint-Hilaire committed Jul 1, 2019 at 14:44 UTC 0c445a473ecd6ea89065951dfdd76a7a2b82492b
3 files changed +185 -86
MeshCentralServer.njsproj
+1
@@ -96,6 +96,7 @@
96 <Compile Include="exeHandler.js" />
97 <Compile Include="letsencrypt.js" />
98 <Compile Include="meshaccelerator.js" />
99 + <Compile Include="meshctrl.js" />
100 <Compile Include="meshmail.js" />
101 <Compile Include="meshscanner.js" />
102 <Compile Include="certoperations.js" />
meshctrl.js
+92 -19
@@ -3,7 +3,7 @@
3 const crypto = require('crypto');
4 var settings = {};
5 const args = require('minimist')(process.argv.slice(2));
6 -const possibleCommands = ['listusers', 'listdevicegroups', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'broadcast'];
6 +const possibleCommands = ['listusers', 'listdevicegroups', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'broadcast', 'addusertodevicegroup', 'removeuserfromdevicegroup'];
7 //console.log(args);
8
9 if (args['_'].length == 0) {
@@ -11,24 +11,26 @@ if (args['_'].length == 0) {
11 console.log("Information at: https://meshcommander.com/meshcentral");
12 console.log("No action specified, use MeshCtrl like this:\r\n\r\n meshctrl [action] [arguments]\r\n");
13 console.log("Supported actions:");
14 - console.log(" Help [action] - Get help on an action.");
15 - console.log(" ServerInfo - Show server information.");
16 - console.log(" UserInfo - Show user information.");
17 - console.log(" ListUsers - List user accounts.");
18 - console.log(" ListDeviceGroups - List device groups.");
19 - console.log(" AddUser - Create a new user account.");
20 - console.log(" RemoveUser - Delete a user account.");
21 - console.log(" AddDeviceGroup - Create a new device group.");
22 - console.log(" RemoveDeviceGroup - Delete a device group.");
23 - console.log(" Broadcast - Display a message to all online users.");
14 + console.log(" Help [action] - Get help on an action.");
15 + console.log(" ServerInfo - Show server information.");
16 + console.log(" UserInfo - Show user information.");
17 + console.log(" ListUsers - List user accounts.");
18 + console.log(" ListDeviceGroups - List device groups.");
19 + console.log(" AddUser - Create a new user account.");
20 + console.log(" RemoveUser - Delete a user account.");
21 + console.log(" AddDeviceGroup - Create a new device group.");
22 + console.log(" RemoveDeviceGroup - Delete a device group.");
23 + console.log(" AddUserToDeviceGroup - Add a user to a device group.");
24 + console.log(" RemoveUserFromDeviceGroup - Remove a user from a device group.");
25 + console.log(" Broadcast - Display a message to all online users.");
26 console.log("\r\nSupported login arguments:");
25 - console.log(" --url [wss://server] - Server url, wss://localhost:443 is default.");
26 - console.log(" --loginuser [username] - Login username, admin is default.");
27 - console.log(" --loginpass [password] - Login password.");
28 - console.log(" --token [number] - 2nd factor authentication token.");
29 - console.log(" --loginkey [hex] - Server login key in hex.");
30 - console.log(" --loginkeyfile [file] - File containing server login key in hex.");
31 - console.log(" --domain [domainid] - Domain id, default is empty.");
27 + console.log(" --url [wss://server] - Server url, wss://localhost:443 is default.");
28 + console.log(" --loginuser [username] - Login username, admin is default.");
29 + console.log(" --loginpass [password] - Login password.");
30 + console.log(" --token [number] - 2nd factor authentication token.");
31 + console.log(" --loginkey [hex] - Server login key in hex.");
32 + console.log(" --loginkeyfile [file] - File containing server login key in hex.");
33 + console.log(" --domain [domainid] - Domain id, default is empty.");
34 return;
35 } else {
36 settings.cmd = args['_'][0].toLowerCase();
@@ -41,6 +43,18 @@ if (args['_'].length == 0) {
43 case 'userinfo': { ok = true; break; }
44 case 'listusers': { ok = true; break; }
45 case 'listdevicegroups': { ok = true; break; }
46 + case 'addusertodevicegroup': {
47 + if (args.userid == null) { console.log("Add user to group missing useid, use --userid [userid]"); }
48 + else if (args.id == null) { console.log("Add user to group missing group id, use --id [groupid]"); }
49 + else { ok = true; }
50 + break;
51 + }
52 + case 'removeuserfromdevicegroup': {
53 + if (args.userid == null) { console.log("Remove user from group missing useid, use --userid [userid]"); }
54 + else if (args.id == null) { console.log("Remove user from group missing group id, use --id [groupid]"); }
55 + else { ok = true; }
56 + break;
57 + }
58 case 'adddevicegroup': {
59 if (args.name == null) { console.log("Message group name, use --name [name]"); }
60 else { ok = true; }
@@ -149,11 +163,43 @@ if (args['_'].length == 0) {
163 }
164 case 'removedevicegroup': {
165 console.log("Remove a device group, Example usages:\r\n");
152 - console.log(" MeshCtrl RemoteDeviceGroup --id groupid");
166 + console.log(" MeshCtrl RemoveDeviceGroup --id groupid");
167 console.log("\r\nRequired arguments:\r\n");
168 console.log(" --id [groupid] - The group identifier.");
169 break;
170 }
171 + case 'addusertodevicegroup': {
172 + console.log("Add a user to a device group, Example usages:\r\n");
173 + console.log(" MeshCtrl AddUserToDeviceGroup --id groupid --userid userid --fullrights");
174 + console.log(" MeshCtrl AddUserToDeviceGroup --id groupid --userid userid --editgroup --manageusers");
175 + console.log("\r\nRequired arguments:\r\n");
176 + console.log(" --id [groupid] - The group identifier.");
177 + console.log(" --userid [userid] - The user identifier.");
178 + console.log("\r\nOptional arguments:\r\n");
179 + console.log(" --fullrights - Allow full rights over this device group.");
180 + console.log(" --editgroup - Allow the user to edit group information.");
181 + console.log(" --manageusers - Allow the user to add/remove users.");
182 + console.log(" --managedevices - Allow the user to edit device information.");
183 + console.log(" --remotecontrol - Allow device remote control operations.");
184 + console.log(" --agentconsole - Allow agent console operations.");
185 + console.log(" --serverfiles - Allow access to group server files.");
186 + console.log(" --wakedevices - Allow device wake operation.");
187 + console.log(" --notes - Allow editing of device notes.");
188 + console.log(" --desktopviewonly - Restrict user to view-only remote desktop.");
189 + console.log(" --limiteddesktop - Limit remote desktop keys.");
190 + console.log(" --noterminal - Hide the terminal tab from this user.");
191 + console.log(" --nofiles - Hide the files tab from this user.");
192 + console.log(" --noamt - Hide the Intel AMT tab from this user.");
193 + break;
194 + }
195 + case 'removeuserfromdevicegroup': {
196 + console.log("Remove a user from a device group, Example usages:\r\n");
197 + console.log(" MeshCtrl RemoveuserFromDeviceGroup --id groupid --userid userid");
198 + console.log("\r\nRequired arguments:\r\n");
199 + console.log(" --id [groupid] - The group identifier.");
200 + console.log(" --userid [userid] - The user identifier.");
201 + break;
202 + }
203 case 'broadcast': {
204 console.log("Display a message to all logged in users, Example usages:\r\n");
205 console.log(" MeshCtrl Broadcast --msg \"This is a test\"");
@@ -267,6 +313,31 @@ function serverConnect() {
313 ws.send(JSON.stringify(op));
314 break;
315 }
316 + case 'addusertodevicegroup': {
317 + var meshrights = 0;
318 + if (args.fullrights) { meshrights = 0xFFFFFFFF; }
319 + if (args.editgroup) { meshrights |= 1; }
320 + if (args.manageusers) { meshrights |= 2; }
321 + if (args.managedevices) { meshrights |= 4; }
322 + if (args.remotecontrol) { meshrights |= 8; }
323 + if (args.agentconsole) { meshrights |= 16; }
324 + if (args.serverfiles) { meshrights |= 32; }
325 + if (args.wakedevices) { meshrights |= 64; }
326 + if (args.notes) { meshrights |= 128; }
327 + if (args.desktopviewonly) { meshrights |= 256; }
328 + if (args.noterminal) { meshrights |= 512; }
329 + if (args.nofiles) { meshrights |= 1024; }
330 + if (args.noamt) { meshrights |= 2048; }
331 + if (args.limiteddesktop) { meshrights |= 4096; }
332 + var op = { action: 'addmeshuser', meshid: args.id, usernames: [args.userid], meshadmin: meshrights, responseid: 'meshctrl' };
333 + ws.send(JSON.stringify(op));
334 + break;
335 + }
336 + case 'removeuserfromdevicegroup': {
337 + var op = { action: 'removemeshuser', meshid: args.id, userid: args.userid, responseid: 'meshctrl' };
338 + ws.send(JSON.stringify(op));
339 + break;
340 + }
341 case 'broadcast': {
342 var op = { action: 'userbroadcast', msg: args.msg, responseid: 'meshctrl' };
343 ws.send(JSON.stringify(op));
@@ -309,6 +380,8 @@ function serverConnect() {
380 case 'deleteuser': // REMOVEUSER
381 case 'createmesh': // ADDDEVICEGROUP
382 case 'deletemesh': // REMOVEDEVICEGROUP
383 + case 'addmeshuser': //
384 + case 'removemeshuser': //
385 case 'userbroadcast': { // BROADCAST
386 if (data.responseid == 'meshctrl') {
387 if (data.meshid) { console.log(data.result, data.meshid); }
meshuser.js
+92 -67
@@ -1590,88 +1590,113 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1590 }
1591 case 'addmeshuser':
1592 {
1593 - if (common.validateString(command.meshid, 1, 1024) == false) break; // Check the meshid
1594 - if (common.validateInt(command.meshadmin) == false) break; // Mesh rights must be an integer
1595 - if (common.validateStrArray(command.usernames, 1, 64) == false) break; // Username is between 1 and 64 characters
1593 + var err = null;
1594 + try {
1595 + if (common.validateString(command.meshid, 1, 1024) == false) { err = 'Invalid groupid'; } // Check the meshid
1596 + else if (common.validateInt(command.meshadmin) == false) { err = 'Invalid group rights'; } // Mesh rights must be an integer
1597 + else if (common.validateStrArray(command.usernames, 1, 64) == false) { err = 'Invalid usernames'; } // Username is between 1 and 64 characters
1598 + else {
1599 + if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
1600 + mesh = parent.meshes[command.meshid];
1601 + if (mesh == null) { err = 'Unknown group'; }
1602 + else if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) { err = 'Permission denied'; }
1603 + else if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
1604 + }
1605 + } catch (ex) { err = 'Validation exception: ' + ex; }
1606
1597 - // Get the mesh
1598 - mesh = parent.meshes[command.meshid];
1599 - if (mesh) {
1600 - // Check if this user has rights to do this
1601 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
1602 - if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1607 + // Handle any errors
1608 + if (err != null) {
1609 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'addmeshuser', responseid: command.responseid, result: err })); } catch (ex) { } }
1610 + break;
1611 + }
1612
1604 - var unknownUsers = [];
1605 - for (var i in command.usernames) {
1606 - // Check if the user exists
1607 - var newuserid = 'user/' + domain.id + '/' + command.usernames[i].toLowerCase(), newuser = parent.users[newuserid];
1608 - if (newuser != null) {
1609 - // Add mesh to user
1610 - if (newuser.links == null) newuser.links = {};
1611 - newuser.links[command.meshid] = { rights: command.meshadmin };
1612 - db.SetUser(newuser);
1613 - parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe');
1614 -
1615 - // Add a user to the mesh
1616 - mesh.links[newuserid] = { userid: newuser.id, name: newuser.name, rights: command.meshadmin };
1617 - db.Set(common.escapeLinksFieldName(mesh));
1613 + var unknownUsers = [], removedCount = 0, failCount = 0;
1614 + for (var i in command.usernames) {
1615 + // Check if the user exists
1616 + var newuserid = 'user/' + domain.id + '/' + command.usernames[i].toLowerCase(), newuser = parent.users[newuserid];
1617 + if (newuser != null) {
1618 + // Add mesh to user
1619 + if (newuser.links == null) newuser.links = {};
1620 + newuser.links[command.meshid] = { rights: command.meshadmin };
1621 + db.SetUser(newuser);
1622 + parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe');
1623 +
1624 + // Add a user to the mesh
1625 + mesh.links[newuserid] = { userid: newuser.id, name: newuser.name, rights: command.meshadmin };
1626 + db.Set(common.escapeLinksFieldName(mesh));
1627
1619 - // Notify mesh change
1620 - var event = { etype: 'mesh', username: newuser.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Added user ' + newuser.name + ' to mesh ' + mesh.name, domain: domain.id };
1621 - if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
1622 - parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, event);
1623 - } else {
1624 - unknownUsers.push(command.usernames[i]);
1625 - }
1628 + // Notify mesh change
1629 + var event = { etype: 'mesh', username: newuser.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Added user ' + newuser.name + ' to mesh ' + mesh.name, domain: domain.id };
1630 + if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
1631 + parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, event);
1632 + removedCount++;
1633 + } else {
1634 + unknownUsers.push(command.usernames[i]);
1635 + failCount++;
1636 }
1637 + }
1638
1628 - if (unknownUsers.length > 0) {
1629 - // Send error back, user not found.
1630 - displayNotificationMessage('User' + ((unknownUsers.length > 1)?'s':'') + ' ' + EscapeHtml(unknownUsers.join(', ')) + ' not found.', 'Device Group', 'ServerNotify');
1631 - }
1639 + if (unknownUsers.length > 0) {
1640 + // Send error back, user not found.
1641 + displayNotificationMessage('User' + ((unknownUsers.length > 1)?'s':'') + ' ' + EscapeHtml(unknownUsers.join(', ')) + ' not found.', 'Device Group', 'ServerNotify');
1642 }
1643 +
1644 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'addmeshuser', responseid: command.responseid, result: 'ok', removed: removedCount, failed: failCount })); } catch (ex) { } }
1645 break;
1646 }
1647 case 'removemeshuser':
1648 {
1637 - if (common.validateString(command.userid, 1, 1024) == false) break; // Check userid
1638 - if (common.validateString(command.meshid, 1, 1024) == false) break; // Check meshid
1639 - if ((command.userid.split('/').length != 3) || (command.userid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
1649 + var err = null;
1650 + try {
1651 + if (common.validateString(command.userid, 1, 1024) == false) { err = 'Invalid userid'; } // Check userid
1652 + if (common.validateString(command.meshid, 1, 1024) == false) { err = 'Invalid groupid'; } // Check meshid
1653 + if (command.userid.indexOf('/') == -1) { command.userid = 'user/' + domain.id + '/' + command.userid; }
1654 + if ((command.userid.split('/').length != 3) || (command.userid.split('/')[1] != domain.id)) { err = 'Invalid userid'; } // Invalid domain, operation only valid for current domain
1655 + else {
1656 + if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
1657 + mesh = parent.meshes[command.meshid];
1658 + if (mesh == null) { err = 'Unknown device group'; }
1659 + else if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) { err = 'Permission denied'; }
1660 + else if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
1661 + }
1662 + } catch (ex) { err = 'Validation exception: ' + ex; }
1663
1641 - // Get the mesh
1642 - mesh = parent.meshes[command.meshid];
1643 - if (mesh) {
1644 - // Check if this user has rights to do this
1645 - if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return;
1664 + // Handle any errors
1665 + if (err != null) {
1666 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'addmeshuser', responseid: command.responseid, result: err })); } catch (ex) { } }
1667 + break;
1668 + }
1669
1647 - // Check if the user exists - Just in case we need to delete a mesh right for a non-existant user, we do it this way. Technically, it's not possible, but just in case.
1648 - var deluserid = command.userid, deluser = parent.users[deluserid];
1649 - if (deluser != null) {
1650 - // Remove mesh from user
1651 - if (deluser.links != null && deluser.links[command.meshid] != null) {
1652 - var delmeshrights = deluser.links[command.meshid].rights;
1653 - if ((delmeshrights == 0xFFFFFFFF) && (mesh.links[deluserid].rights != 0xFFFFFFFF)) return; // A non-admin can't kick out an admin
1654 - delete deluser.links[command.meshid];
1655 - db.Set(deluser);
1656 - parent.parent.DispatchEvent([deluser._id], obj, 'resubscribe');
1657 - }
1670 + // Check if the user exists - Just in case we need to delete a mesh right for a non-existant user, we do it this way. Technically, it's not possible, but just in case.
1671 + var deluserid = command.userid, deluser = parent.users[deluserid];
1672 + if (deluser != null) {
1673 + // Remove mesh from user
1674 + if (deluser.links != null && deluser.links[command.meshid] != null) {
1675 + var delmeshrights = deluser.links[command.meshid].rights;
1676 + if ((delmeshrights == 0xFFFFFFFF) && (mesh.links[deluserid].rights != 0xFFFFFFFF)) return; // A non-admin can't kick out an admin
1677 + delete deluser.links[command.meshid];
1678 + db.Set(deluser);
1679 + parent.parent.DispatchEvent([deluser._id], obj, 'resubscribe');
1680 }
1681 + }
1682
1660 - // Remove user from the mesh
1661 - if (mesh.links[command.userid] != null) {
1662 - delete mesh.links[command.userid];
1663 - db.Set(common.escapeLinksFieldName(mesh));
1683 + // Remove user from the mesh
1684 + if (mesh.links[command.userid] != null) {
1685 + delete mesh.links[command.userid];
1686 + db.Set(common.escapeLinksFieldName(mesh));
1687
1665 - // Notify mesh change
1666 - var event;
1667 - if (deluser != null) {
1668 - event = { etype: 'mesh', username: user.name, userid: deluser.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Removed user ' + deluser.name + ' from group ' + mesh.name, domain: domain.id };
1669 - } else {
1670 - event = { etype: 'mesh', username: user.name, userid: (deluserid.split('/')[2]), meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Removed user ' + (deluserid.split('/')[2]) + ' from group ' + mesh.name, domain: domain.id };
1671 - }
1672 - if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
1673 - parent.parent.DispatchEvent(['*', mesh._id, user._id, command.userid], obj, event);
1688 + // Notify mesh change
1689 + var event;
1690 + if (deluser != null) {
1691 + event = { etype: 'mesh', username: user.name, userid: deluser.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Removed user ' + deluser.name + ' from group ' + mesh.name, domain: domain.id };
1692 + } else {
1693 + event = { etype: 'mesh', username: user.name, userid: (deluserid.split('/')[2]), meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Removed user ' + (deluserid.split('/')[2]) + ' from group ' + mesh.name, domain: domain.id };
1694 }
1695 + if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
1696 + parent.parent.DispatchEvent(['*', mesh._id, user._id, command.userid], obj, event);
1697 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removemeshuser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
1698 + } else {
1699 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removemeshuser', responseid: command.responseid, result: 'User not in group' })); } catch (ex) { } }
1700 }
1701 break;
1702 }
@@ -2621,7 +2646,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2646 if (common.validateString(command.meshid, 8, 128) == false) break; // Check the meshid
2647 if (common.validateInt(command.expire, 0, 99999) == false) break; // Check the expire time in hours
2648 if (common.validateInt(command.flags, 0, 256) == false) break; // Check the flags
2624 - var mesh = parent.meshes[command.meshid];
2649 + mesh = parent.meshes[command.meshid];
2650 if (mesh == null) break;
2651 const inviteCookie = parent.parent.encodeCookie({ a: 4, mid: command.meshid, f: command.flags, expire: command.expire * 60 }, parent.parent.invitationLinkEncryptionKey);
2652 if (inviteCookie == null) break;