From e208046267b0e759b15fd45c5ccbfdc569ada420 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Sun, 30 Jun 2019 11:34:27 -0700 Subject: [PATCH] MeshCtrl improvements. --- meshctrl.js | 152 ++++++++++++++++++++++++-------- meshuser.js | 245 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 279 insertions(+), 118 deletions(-) diff --git a/meshctrl.js b/meshctrl.js index dacc44b7..d135ed5a 100644 --- a/meshctrl.js +++ b/meshctrl.js @@ -1,23 +1,28 @@ #!/usr/bin/env node +const crypto = require('crypto'); var settings = {}; const args = require('minimist')(process.argv.slice(2)); -const possibleCommands = ['listusers', 'listgroups', 'serverinfo', 'userinfo','adduser','removeuser']; +const possibleCommands = ['listusers', 'listdevicegroups', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'broadcast']; //console.log(args); -if ((args['_'].length != 1) && (args['_'][0].toLowerCase() != 'help')) { - console.log("MeshCtrl perform command line actions on a MeshCentral server."); +if (args['_'].length == 0) { + console.log("MeshCtrl performs command line actions on a MeshCentral server."); + console.log("Information at: https://meshcommander.com/meshcentral"); console.log("No action specified, use MeshCtrl like this:\r\n\r\n meshctrl [action] [arguments]\r\n"); console.log("Supported actions:"); console.log(" Help [action] - Get help on an action."); console.log(" ServerInfo - Show server information."); console.log(" UserInfo - Show user information."); console.log(" ListUsers - List user accounts."); - console.log(" ListGroups - List device groups."); + console.log(" ListDeviceGroups - List device groups."); console.log(" AddUser - Create a new user account."); console.log(" RemoveUser - Delete a user account."); + console.log(" AddDeviceGroup - Create a new device group."); + console.log(" RemoveDeviceGroup - Delete a device group."); + console.log(" Broadcast - Display a message to all online users."); console.log("\r\nSupported login arguments:"); - console.log(" --url [wss://server] - Server url, wss://localhost:443 is default."); + console.log(" --url [wss://server] - Server url, wss://localhost:443 is default."); console.log(" --loginuser [username] - Login username, admin is default."); console.log(" --loginpass [password] - Login password."); console.log(" --token [number] - 2nd factor authentication token."); @@ -36,9 +41,24 @@ if ((args['_'].length != 1) && (args['_'][0].toLowerCase() != 'help')) { case 'userinfo': { ok = true; break; } case 'listusers': { ok = true; break; } case 'listgroups': { ok = true; break; } + case 'adddevicegroup': { + if (args.name == null) { console.log("Message group name, use --name [name]"); } + else { ok = true; } + break; + } + case 'removedevicegroup': { + if (args.id == null) { console.log("Message group identifier, use --id [identifier]"); } + else { ok = true; } + break; + } + case 'broadcast': { + if (args.msg == null) { console.log("Message missing, use --msg [message]"); } + else { ok = true; } + break; + } case 'adduser': { if (args.user == null) { console.log("New account name missing, use --user [name]"); } - else if (args.pass == null) { console.log("New account password missing, use --pass [password]"); } + else if ((args.pass == null) && (args.randompass == null)) { console.log("New account password missing, use --pass [password] or --randompass"); } else { ok = true; } break; } @@ -79,10 +99,10 @@ if ((args['_'].length != 1) && (args['_'][0].toLowerCase() != 'help')) { console.log(" --json - Show result as JSON."); break; } - case 'listgroups': { + case 'listdevicegroups': { console.log("List the device groups for this account, Example usages:\r\n"); - console.log(" MeshCtrl ListGroups "); - console.log(" MeshCtrl ListGroups --json"); + console.log(" MeshCtrl ListDeviceGroups "); + console.log(" MeshCtrl ListDeviceGroups --json"); console.log("\r\nOptional arguments:\r\n"); console.log(" --idexists [id] - Return 1 if id exists, 0 if not."); console.log(" --nameexists [name] - Return id if name exists."); @@ -96,9 +116,17 @@ if ((args['_'].length != 1) && (args['_'][0].toLowerCase() != 'help')) { console.log("\r\nRequired arguments:\r\n"); console.log(" --user [name] - New account name."); console.log(" --pass [password] - New account password."); + console.log(" --randompass - Create account with a random password."); console.log("\r\nOptional arguments:\r\n"); console.log(" --email [email] - New account email address."); console.log(" --resetpass - Request password reset on next login."); + console.log(" --siteadmin - Create the account as full site administrator."); + console.log(" --manageusers - Allow this account to manage server users."); + console.log(" --fileaccess - Allow this account to store server files."); + console.log(" --serverupdate - Allow this account to update the server."); + console.log(" --locked - This account will be locked."); + console.log(" --nonewgroups - Account will not be allowed to create device groups."); + console.log(" --notools - Account not see MeshCMD download links."); break; } case 'removeuser': { @@ -108,6 +136,31 @@ if ((args['_'].length != 1) && (args['_'][0].toLowerCase() != 'help')) { console.log(" --userid [id] - Account identifier."); break; } + case 'adddevicegroup': { + console.log("Add a device group, Example usages:\r\n"); + console.log(" MeshCtrl AddDeviceGroup --name newgroupname"); + console.log(" MeshCtrl AddDeviceGroup --name newgroupname --desc description --amtonly"); + console.log("\r\nRequired arguments:\r\n"); + console.log(" --name [name] - Name of the new group."); + console.log("\r\nOptional arguments:\r\n"); + console.log(" --desc [description] - New group description."); + console.log(" --amtonly - New group is agent-less, Intel AMT only."); + break; + } + case 'removedevicegroup': { + console.log("Remove a device group, Example usages:\r\n"); + console.log(" MeshCtrl RemoteDeviceGroup --id groupid"); + console.log("\r\nRequired arguments:\r\n"); + console.log(" --id [groupid] - The group identifier."); + break; + } + case 'broadcast': { + console.log("Display a message to all logged in users, Example usages:\r\n"); + console.log(" MeshCtrl Broadcast --msg \"This is a test\""); + console.log("\r\nRequired arguments:\r\n"); + console.log(" --msg [message] - Message to display."); + break; + } default: { console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.'); } @@ -120,10 +173,11 @@ if ((args['_'].length != 1) && (args['_'][0].toLowerCase() != 'help')) { if (ok) { serverConnect(); } } +function onVerifyServer(clientName, certs) { return null; } + function serverConnect() { const WebSocket = require('ws'); - function onVerifyServer(clientName, certs) { console.log('onVerifyServer', clientName); } var url = 'wss://localhost/control.ashx'; if (args.url) { url = args.url; @@ -133,6 +187,7 @@ function serverConnect() { url += 'control.ashx'; } + // TODO: checkServerIdentity does not work??? var options = { rejectUnauthorized: false, checkServerIdentity: onVerifyServer } // Password authentication @@ -177,16 +232,43 @@ function serverConnect() { case 'serverinfo': { break; } case 'userinfo': { break; } case 'listusers': { ws.send(JSON.stringify({ action: 'users' })); break; } - case 'listgroups': { ws.send(JSON.stringify({ action: 'meshes' })); break; } + case 'listdevicegroups': { ws.send(JSON.stringify({ action: 'meshes' })); break; } case 'adduser': { - var op = { action: 'adduser', username: args.user, pass: args.pass }; + var siteadmin = 0; + if (args.siteadmin) { siteadmin = 0xFFFFFFFF; } + if (args.manageusers) { siteadmin |= 2; } + if (args.fileaccess) { siteadmin |= 8; } + if (args.serverupdate) { siteadmin |= 16; } + if (args.locked) { siteadmin |= 32; } + if (args.nonewgroups) { siteadmin |= 64; } + if (args.notools) { siteadmin |= 128; } + if (args.randompass) { args.pass = getRandomAmtPassword(); } + var op = { action: 'adduser', username: args.user, pass: args.pass, responseid: 'meshctrl' }; if (args.email) { op.email = args.email; } if (args.resetpass) { op.resetNextLogin = true; } + if (siteadmin != 0) { op.siteadmin = siteadmin; } ws.send(JSON.stringify(op)); break; } case 'removeuser': { - var op = { action: 'deleteuser', userid: args.userid }; + var op = { action: 'deleteuser', userid: args.userid, responseid: 'meshctrl' }; + ws.send(JSON.stringify(op)); + break; + } + case 'adddevicegroup': { + var op = { action: 'createmesh', meshname: args.name, meshtype: 2, responseid: 'meshctrl' }; + if (args.desc) { op.desc = args.desc; } + if (args.amtonly) { op.meshtype = 1; } + ws.send(JSON.stringify(op)); + break; + } + case 'removedevicegroup': { + var op = { action: 'deletemesh', meshid: args.id, responseid: 'meshctrl' }; + ws.send(JSON.stringify(op)); + break; + } + case 'broadcast': { + var op = { action: 'userbroadcast', msg: args.msg, responseid: 'meshctrl' }; ws.send(JSON.stringify(op)); break; } @@ -223,6 +305,19 @@ function serverConnect() { } break; } + case 'adduser': // ADDUSER + case 'deleteuser': // REMOVEUSER + case 'createmesh': // ADDDEVICEGROUP + case 'deletemesh': // REMOVEDEVICEGROUP + case 'userbroadcast': { // BROADCAST + if (data.responseid == 'meshctrl') { + if (data.meshid) { console.log(data.result, data.meshid); } + else if (data.userid) { console.log(data.result, data.userid); } + else console.log(data.result); + process.exit(); + } + break; + } case 'users': { // LISTUSERS if (args.json) { console.log(JSON.stringify(data.users, ' ', 2)); @@ -241,7 +336,7 @@ function serverConnect() { process.exit(); break; } - case 'meshes': { // LISTGROUPS + case 'meshes': { // LISTDEVICEGROUPS if (args.json) { console.log(JSON.stringify(data.meshes, ' ', 2)); } else { @@ -269,29 +364,7 @@ function serverConnect() { process.exit(); break; } - case 'event': { - switch (data.event.action) { - case 'accountcreate': { - if ((settings.cmd == 'adduser') && (data.event.account.name == args.user)) { - console.log('Account created, id: ' + data.event.account._id); - process.exit(); - } - break; - } - case 'accountremove': { - if ((settings.cmd == 'removeuser') && (data.event.userid == args.userid)) { - console.log('Account removed'); - process.exit(); - } - break; - } - } - break; - } - default: { - console.log('Unknown action: ' + data.action); - break; - } + default: { break; } } //console.log('Data', data); //setTimeout(function timeout() { ws.send(Date.now()); }, 500); @@ -300,7 +373,6 @@ function serverConnect() { // Encode an object as a cookie using a key using AES-GCM. (key must be 32 bytes or more) function encodeCookie(o, key) { - var crypto = require('crypto'); try { if (key == null) { return null; } o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time @@ -309,3 +381,7 @@ function encodeCookie(o, key) { return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } catch (e) { return null; } } + +// Generate a random Intel AMT password +function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); } +function getRandomAmtPassword() { var p; do { p = Buffer.from(crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; } diff --git a/meshuser.js b/meshuser.js index 182e1fb9..ca36eadd 100644 --- a/meshuser.js +++ b/meshuser.js @@ -40,9 +40,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use const SITERIGHT_SERVERRESTORE = 4; const SITERIGHT_FILEACCESS = 8; const SITERIGHT_SERVERUPDATE = 16; - const SITERIGHT_LOCKED = 32; - const SITERIGHT_NONEWGROUPS = 64; - const SITERIGHT_NOMESHCMD = 128; + const SITERIGHT_LOCKED = 32; // 0x00000020 + const SITERIGHT_NONEWGROUPS = 64; // 0x00000040 + const SITERIGHT_NOMESHCMD = 128; // 0x00000080 var obj = {}; obj.user = user; @@ -932,12 +932,26 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use case 'deleteuser': { // Delete a user account - if ((user.siteadmin & 2) == 0) break; - if (common.validateString(command.userid, 1, 2048) == false) break; - var delusersplit = command.userid.split('/'), deluserid = command.userid, deluser = parent.users[deluserid]; - if ((deluser == null) || (delusersplit.length != 3) || (delusersplit[1] != domain.id)) break; // Invalid domain, operation only valid for current domain - if ((deluser.siteadmin != null) && (deluser.siteadmin > 0) && (user.siteadmin != 0xFFFFFFFF)) break; // Need full admin to remote another administrator - if ((user.groups != null) && (user.groups.length > 0) && ((deluser.groups == null) || (findOne(deluser.groups, user.groups) == false))) break; // Can only perform this operation on other users of our group. + var err = null, delusersplit, deluserid, deluser; + try { + if ((user.siteadmin & 2) == 0) { err = 'Permission denied'; } + else if (common.validateString(command.userid, 1, 2048) == false) { err = 'Invalid userid'; } + else { + delusersplit = command.userid.split('/'); + deluserid = command.userid; + deluser = parent.users[deluserid]; + if (deluser == null) { err = 'User does not exists'; } + else if ((delusersplit.length != 3) || (delusersplit[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain + else if ((deluser.siteadmin != null) && (deluser.siteadmin > 0) && (user.siteadmin != 0xFFFFFFFF)) { err = 'Permission denied'; } // Need full admin to remote another administrator + else if ((user.groups != null) && (user.groups.length > 0) && ((deluser.groups == null) || (findOne(deluser.groups, user.groups) == false))) { err = 'Invalid user group'; } // Can only perform this operation on other users of our group. + } + } catch (ex) { err = 'Validation exception: ' + ex; } + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deleteuser', responseid: command.responseid, result: err })); } catch (ex) { } } + break; + } // Remove all the mesh links to this user if (deluser.links != null) { @@ -973,13 +987,24 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use parent.parent.DispatchEvent(targets, obj, { etype: 'user', userid: deluserid, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id }); parent.parent.DispatchEvent([deluserid], obj, 'close'); + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deleteuser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } } + break; } case 'userbroadcast': { - // Broadcast a message to all currently connected users. - if ((user.siteadmin & 2) == 0) break; - if (common.validateString(command.msg, 1, 256) == false) break; // Notification message is between 1 and 256 characters + var err = null; + try { + // Broadcast a message to all currently connected users. + if ((user.siteadmin & 2) == 0) { err = 'Permission denied'; } + else if (common.validateString(command.msg, 1, 512) == false) { err = 'Message is too long'; } // Notification message is between 1 and 256 characters + } catch (ex) { err = 'Validation exception: ' + ex; } + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'userbroadcast', responseid: command.responseid, result: err })); } catch (ex) { } } + break; + } // Create the notification message var notification = { action: "msg", type: "notify", domain: domain.id, "value": command.msg, "title": user.name, icon: 0, tag: "broadcast" }; @@ -1004,6 +1029,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // TODO: Notify all sessions on other peers. + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'userbroadcast', responseid: command.responseid, result: 'ok' })); } catch (ex) { } } break; } case 'adduserbatch': @@ -1074,32 +1100,53 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use case 'adduser': { // Add a new user account - if ((user.siteadmin & 2) == 0) break; - if (common.validateUsername(command.username, 1, 64) == false) break; // Username is between 1 and 64 characters, no spaces - if (common.validateString(command.pass, 1, 256) == false) break; // Password is between 1 and 256 characters - if (command.username.indexOf('/') >= 0) break; // Usernames can't have '/' - if (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements - if ((command.email != null) && (common.validateEmail(command.email, 1, 256) == false)) break; // Check if this is a valid email address - var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(); - if (newusername == '~') break; // This is a reserved user name - if (parent.users[newuserid]) break; // Account already exists + var err = null, newusername, newuserid; + try { + if ((user.siteadmin & 2) == 0) { err = 'Permission denied'; } + else if (common.validateUsername(command.username, 1, 64) == false) { err = 'Invalid username'; } // Username is between 1 and 64 characters, no spaces + else if (common.validateString(command.pass, 1, 256) == false) { err = 'Invalid password'; } // Password is between 1 and 256 characters + else if (command.username.indexOf('/') >= 0) { err = 'Invalid username'; } // Usernames can't have '/' + else if (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) { err = 'Invalid password'; } // Password does not meet requirements + else if ((command.email != null) && (common.validateEmail(command.email, 1, 256) == false)) { err = 'Invalid email'; } // Check if this is a valid email address + else { + newusername = command.username; + newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(); + if (newusername == '~') { err = 'Invalid username'; } // This is a reserved user name + else if (command.siteadmin != null) { + if ((typeof command.siteadmin != 'number') || (Number.isInteger(command.siteadmin) == false)) { err = 'Invalid site permissions'; } // Check permissions + else if ((user.siteadmin != 0xFFFFFFFF) && ((command.siteadmin & (0xFFFFFFFF - 224)) != 0)) { err = 'Invalid site permissions'; } + } + if (parent.users[newuserid]) { err = 'User already exists'; } // Account already exists + } + } catch (ex) { err = 'Validation exception'; } + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'adduser', responseid: command.responseid, result: err })); } catch (ex) { } } + break; + } // Check if we exceed the maximum number of user accounts db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) { if (maxExceed) { // Account count exceed, do notification + if (command.responseid != null) { + // Respond privately if requested + try { ws.send(JSON.stringify({ action: 'adduser', responseid: command.responseid, result: 'maxUsersExceed' })); } catch (ex) { } + } else { + // Create the notification message + var notification = { action: "msg", type: "notify", value: "Account limit reached.", title: "Server Limit", userid: user._id, username: user.name, domain: domain.id }; - // Create the notification message - var notification = { action: "msg", type: "notify", value: "Account limit reached.", title: "Server Limit", userid: user._id, username: user.name, domain: domain.id }; - - // Get the list of sessions for this user - var sessions = parent.wssessions[user._id]; - if (sessions != null) { for (i in sessions) { try { if (sessions[i].domainid == domain.id) { sessions[i].send(JSON.stringify(notification)); } } catch (ex) { } } } - // TODO: Notify all sessions on other peers. + // Get the list of sessions for this user + var sessions = parent.wssessions[user._id]; + if (sessions != null) { for (i in sessions) { try { if (sessions[i].domainid == domain.id) { sessions[i].send(JSON.stringify(notification)); } } catch (ex) { } } } + // TODO: Notify all sessions on other peers. + } } else { - // Check if this is an existing user + // Create a new user var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Math.floor(Date.now() / 1000), domain: domain.id }; - if (domain.newaccountsrights) { newuser.siteadmin = domain.newaccountsrights; } + if (command.siteadmin != null) { newuser.siteadmin = command.siteadmin; } + else if (domain.newaccountsrights) { newuser.siteadmin = domain.newaccountsrights; } if (command.email != null) { newuser.email = command.email; } // Email if (command.resetNextLogin === true) { newuser.passchange = -1; } else { newuser.passchange = Math.floor(Date.now() / 1000); } if ((user.groups != null) && (user.groups.length > 0)) { newuser.groups = user.groups; } // New account are automatically part of our groups. @@ -1107,20 +1154,26 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Create a user, generate a salt and hash the password require('./pass').hash(command.pass, function (err, salt, hash, tag) { - if (err) throw err; - newuser.salt = salt; - newuser.hash = hash; - db.SetUser(newuser); + if (err == null) { + newuser.salt = salt; + newuser.hash = hash; + db.SetUser(newuser); - var event, targets = ['*', 'server-users']; - if (newuser.groups) { for (var i in newuser.groups) { targets.push('server-users:' + i); } } - if (command.email == null) { - event = { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, username is ' + command.username, domain: domain.id }; + var event, targets = ['*', 'server-users']; + if (newuser.groups) { for (var i in newuser.groups) { targets.push('server-users:' + i); } } + if (command.email == null) { + event = { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, username is ' + command.username, domain: domain.id }; + } else { + event = { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id }; + } + if (parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come. + parent.parent.DispatchEvent(targets, obj, event); + + // OK Response + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'adduser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } } } else { - event = { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id }; + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'adduser', responseid: command.responseid, result: 'passwordHashError' })); } catch (ex) { } } } - if (parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come. - parent.parent.DispatchEvent(targets, obj, event); }, 0); } }); @@ -1386,61 +1439,91 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'createmesh': { - // Check if we have new group restriction - if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 64) != 0)) break; + var err = null; + try { + // Check if we have new group restriction + if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 64) != 0)) { err = 'Permission denied'; } - // In some situations, we need a verified email address to create a device group. - if ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (user.emailVerified !== true) && (user.siteadmin != 0xFFFFFFFF)) return; // User must verify it's email first. + // In some situations, we need a verified email address to create a device group. + else if ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (user.emailVerified !== true) && (user.siteadmin != 0xFFFFFFFF)) { err = 'Email verification required'; } // User must verify it's email first. - // Create mesh - if (common.validateString(command.meshname, 1, 64) == false) break; // Meshname is between 1 and 64 characters - if (common.validateString(command.desc, 0, 1024) == false) break; // Mesh description is between 0 and 1024 characters + // Create mesh + else if (common.validateString(command.meshname, 1, 64) == false) { err = 'Invalid group name'; } // Meshname is between 1 and 64 characters + else if ((command.desc != null) && (common.validateString(command.desc, 0, 1024) == false)) { err = 'Invalid group description'; } // Mesh description is between 0 and 1024 characters + else if ((command.meshtype != 1) && (command.meshtype != 2)) { err = 'Invalid group type'; } + } catch (ex) { err = 'Validation exception: ' + ex; } + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createmesh', responseid: command.responseid, result: err })); } catch (ex) { } } + break; + } // We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2) - if ((command.meshtype == 1) || (command.meshtype == 2)) { - parent.crypto.randomBytes(48, function (err, buf) { - // Create new device group identifier - meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); + parent.crypto.randomBytes(48, function (err, buf) { + // Create new device group identifier + meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); - // Create the new device group - var links = {}; - links[user._id] = { name: user.name, rights: 4294967295 }; - mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links }; - db.Set(common.escapeLinksFieldName(mesh)); - parent.meshes[meshid] = mesh; - parent.parent.AddEventDispatch([meshid], ws); + // Create the new device group + var links = {}; + links[user._id] = { name: user.name, rights: 4294967295 }; + mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links }; + db.Set(common.escapeLinksFieldName(mesh)); + parent.meshes[meshid] = mesh; + parent.parent.AddEventDispatch([meshid], ws); - // Change the user to make him administration of the new device group - if (user.links == null) user.links = {}; - user.links[meshid] = { rights: 4294967295 }; - user.subscriptions = parent.subscribe(user._id, ws); - db.SetUser(user); + // Change the user to make him administration of the new device group + if (user.links == null) user.links = {}; + user.links[meshid] = { rights: 4294967295 }; + user.subscriptions = parent.subscribe(user._id, ws); + db.SetUser(user); - // Event the user change - var targets = ['*', 'server-users', user._id]; - if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } } - var event = { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', domain: domain.id, nolog: 1 }; - if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come. - parent.parent.DispatchEvent(targets, obj, event); + // Event the user change + var targets = ['*', 'server-users', user._id]; + if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } } + var event = { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', domain: domain.id, nolog: 1 }; + if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come. + parent.parent.DispatchEvent(targets, obj, event); - // Event the device group creation - var event = { etype: 'mesh', username: user.name, meshid: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, action: 'createmesh', links: links, msg: 'Mesh created: ' + command.meshname, domain: domain.id }; - parent.parent.DispatchEvent(['*', meshid, user._id], obj, event); // Even if DB change stream is active, this event must be acted upon. - }); - } + // Event the device group creation + var event = { etype: 'mesh', username: user.name, meshid: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, action: 'createmesh', links: links, msg: 'Mesh created: ' + command.meshname, domain: domain.id }; + parent.parent.DispatchEvent(['*', meshid, user._id], obj, event); // Even if DB change stream is active, this event must be acted upon. + + try { ws.send(JSON.stringify({ action: 'createmesh', responseid: command.responseid, result: 'ok', meshid: meshid })); } catch (ex) { } + }); break; } case 'deletemesh': { - // Delete a mesh and all computers within it - if (common.validateString(command.meshid, 1, 1024) == false) break; // Check the meshid + var err = null; + try { + // Delete a mesh and all computers within it + if (common.validateString(command.meshid, 1, 1024) == false) { err = 'Invalid group identifier'; } // Check the meshid + } catch (ex) { err = 'Validation exception: ' + ex; } + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: err })); } catch (ex) { } } + break; + } + db.Get(command.meshid, function (err, meshes) { - if (meshes.length != 1) return; + if (meshes.length != 1) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: 'Unknown device group' })); } catch (ex) { } } + return; + } var mesh = common.unEscapeLinksFieldName(meshes[0]); // Check if this user has rights to do this - if (mesh.links[user._id] == null || mesh.links[user._id].rights != 0xFFFFFFFF) return; - if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + var err = null; + if (mesh.links[user._id] == null || mesh.links[user._id].rights != 0xFFFFFFFF) { err = 'Access denied'; } + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = 'Invalid group'; } // Invalid domain, operation only valid for current domain + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: err })); } catch (ex) { } } + return; + } // Fire the removal event first, because after this, the event will not route var event = { etype: 'mesh', username: user.name, meshid: command.meshid, name: command.meshname, action: 'deletemesh', msg: 'Mesh deleted: ' + command.meshname, domain: domain.id }; @@ -1475,6 +1558,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Delete all devices attached to this mesh in the database db.RemoveMeshDocuments(command.meshid); + + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: 'ok' })); } catch (ex) { } } }); break; }