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;