diff --git a/common.js b/common.js index 7533e17f..63f1ff21 100644 --- a/common.js +++ b/common.js @@ -20,7 +20,7 @@ module.exports.IntToStrX = function(v) { return String.fromCharCode(v & 0xFF, (v module.exports.MakeToArray = function(v) { if (!v || v == null || typeof v == 'object') return v; return [v]; } module.exports.SplitArray = function(v) { return v.split(','); } module.exports.Clone = function(v) { return JSON.parse(JSON.stringify(v)); } -module.exports.IsFilenameValid = (function () { var x1 = /^[^\\/:\*\?"<>\|]+$/, x2 = /^\./, x3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; return function isFilenameValid(fname) { return x1.test(fname) && !x2.test(fname) && !x3.test(fname) && (fname[0] != '.'); } })(); +module.exports.IsFilenameValid = (function () { var x1 = /^[^\\/:\*\?"<>\|]+$/, x2 = /^\./, x3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; return function isFilenameValid(fname) { return module.exports.validateString(fname, 1, 4096) && x1.test(fname) && !x2.test(fname) && !x3.test(fname) && (fname[0] != '.'); } })(); // Move an element from one position in an array to a new position module.exports.ArrayElementMove = function(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1)[0]); }; @@ -125,4 +125,10 @@ module.exports.objKeysToLower = function (obj) { if (typeof obj[i] == 'object') { module.exports.objKeysToLower(obj[i]); } // LowerCase all key names in the child object } return obj; -} \ No newline at end of file +} + +// Validation methods +module.exports.validateString = function(str, minlen, maxlen) { return ((str != null) && (typeof str == 'string') && ((minlen == null) || (str.length >= minlen)) && ((maxlen == null) || (str.length <= maxlen))); } +module.exports.validateInt = function(int, minval, maxval) { return ((int != null) && (typeof int == 'number') && ((minval == null) || (int >= minval)) && ((maxval == null) || (int <= maxval))); } +module.exports.validateArray = function(array, minlen, maxlen) { return ((array != null) && Array.isArray(array) && ((minlen == null) || (array.length >= minlen)) && ((maxlen == null) || (array.length <= maxlen))); } +module.exports.validateObject = function(obj) { return ((obj != null) && (typeof obj == 'object')); } diff --git a/meshuser.js b/meshuser.js index 5a211e0c..5603af1b 100644 --- a/meshuser.js +++ b/meshuser.js @@ -28,7 +28,17 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } // Convert a mesh path array into a real path on the server side - function meshPathToRealPath(meshpath) { + function meshPathToRealPath(meshpath, user) { + if (obj.common.validateArray(meshpath, 1) == false) return null; + var splitid = meshpath[0].split('/'); + if (splitid[0] == 'user') { + // Check user access + if (meshpath[0] != user._id) return null; // Only allow own user folder + } else if (splitid[0] == 'mesh') { + // Check mesh access + var meshrights = user.links[meshpath[0]]; + if ((meshrights == null) || ((meshrights & 32) == 0)) return null; // This user must have mesh rights to "server files" + } else return null; var rootfolder = meshpath[0], rootfoldersplit = rootfolder.split('/'), domainx = 'domain'; if (rootfoldersplit[1].length > 0) domainx = 'domain-' + rootfoldersplit[1]; var path = obj.parent.path.join(obj.parent.filespath, domainx, rootfoldersplit[0] + "-" + rootfoldersplit[2]); @@ -94,8 +104,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { // When data is received from the web socket ws.on('message', function (msg) { - var user = obj.parent.users[req.session.userid]; - var command = JSON.parse(msg.toString('utf8')) + var command, user = obj.parent.users[req.session.userid]; + try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; } + if ((user == null) || (obj.common.validateString(command.action, 3, 32) == false)) return; // User must be set and action must be a string between 3 and 32 chars + switch (command.action) { case 'meshes': { @@ -114,6 +126,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } else { // Request list of all nodes for one specific meshid var meshid = command.meshid; + if (obj.common.validateString(meshid, 0, 128) == false) return; if (meshid.split('/').length == 0) { meshid = 'mesh/' + domain.id + '/' + command.meshid; } if (user.links[meshid] != null) { links.push(meshid); } } @@ -149,6 +162,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { { // Query the database for the power timeline for a given node // The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ] + if (obj.common.validateString(command.nodeid, 0, 128) == false) return; obj.db.getPowerTimeline(command.nodeid, function (err, docs) { if (err == null && docs.length > 0) { var timeline = [], time = null, previousPower; @@ -204,27 +218,26 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { // Check permissions if ((user.siteadmin & 8) != 0) { // Perform a file operation (Create Folder, Delete Folder, Delete File...) - if ((command.path != null) && (typeof command.path == 'object') && command.path.length > 0) { - var sendUpdate = true; - var path = meshPathToRealPath(command.path); // TODO: Check mesh rights!!!!! - if (path == null) break; + if (obj.common.validateString(command.fileop, 4, 16) == false) return; + var sendUpdate = true, path = meshPathToRealPath(command.path, user); // This will also check access rights + if (path == null) break; - if ((command.fileop == 'createfolder') && (obj.common.IsFilenameValid(command.newfolder) == true)) { try { obj.fs.mkdirSync(path + "/" + command.newfolder); } catch (e) { } } // Create a new folder - else if (command.fileop == 'delete') { for (var i in command.delfiles) { if (obj.common.IsFilenameValid(command.delfiles[i]) == true) { var fullpath = path + "/" + command.delfiles[i]; try { obj.fs.rmdirSync(fullpath); } catch (e) { try { obj.fs.unlinkSync(fullpath); } catch (e) { } } } } } // Delete - else if ((command.fileop == 'rename') && (obj.common.IsFilenameValid(command.oldname) == true) && (obj.common.IsFilenameValid(command.newname) == true)) { try { obj.fs.renameSync(path + "/" + command.oldname, path + "/" + command.newname); } catch (e) { } } // Rename - else if ((command.fileop == 'copy') || (command.fileop == 'move')) { - var scpath = meshPathToRealPath(command.scpath); // TODO: Check mesh rights!!!!! - if (scpath == null) break; - // TODO: Check quota if this is a copy!!!!!!!!!!!!!!!! - for (var i in command.names) { - var s = obj.path.join(scpath, command.names[i]), d = obj.path.join(path, command.names[i]); - sendUpdate = false; - copyFile(s, d, function (op) { if (op != null) { obj.fs.unlink(op, function () { obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); }); } else { obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } }, ((command.fileop == 'move') ? s : null)); - } + if ((command.fileop == 'createfolder') && (obj.common.IsFilenameValid(command.newfolder) == true)) { try { obj.fs.mkdirSync(path + "/" + command.newfolder); } catch (e) { } } // Create a new folder + else if (command.fileop == 'delete') { if (obj.common.validateArray(command.delfiles, 1) == false) return; for (var i in command.delfiles) { if (obj.common.IsFilenameValid(command.delfiles[i]) == true) { var fullpath = path + "/" + command.delfiles[i]; try { obj.fs.rmdirSync(fullpath); } catch (e) { try { obj.fs.unlinkSync(fullpath); } catch (e) { } } } } } // Delete + else if ((command.fileop == 'rename') && (obj.common.IsFilenameValid(command.oldname) == true) && (obj.common.IsFilenameValid(command.newname) == true)) { try { obj.fs.renameSync(path + "/" + command.oldname, path + "/" + command.newname); } catch (e) { } } // Rename + else if ((command.fileop == 'copy') || (command.fileop == 'move')) { + if (obj.common.validateArray(command.names, 1) == false) return; + var scpath = meshPathToRealPath(command.scpath, user); // This will also check access rights + if (scpath == null) break; + // TODO: Check quota if this is a copy!!!!!!!!!!!!!!!! + for (var i in command.names) { + var s = obj.path.join(scpath, command.names[i]), d = obj.path.join(path, command.names[i]); + sendUpdate = false; + copyFile(s, d, function (op) { if (op != null) { obj.fs.unlink(op, function () { obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); }); } else { obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } }, ((command.fileop == 'move') ? s : null)); } - - if (sendUpdate == true) { obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } // Fire an event causing this user to update this files } + + if (sendUpdate == true) { obj.parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } // Fire an event causing this user to update this files } break; } @@ -232,32 +245,31 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { { // Route a message. // This this command has a nodeid, that is the target. - if (command.nodeid != null) { - var splitnodeid = command.nodeid.split('/'); - // Check that we are in the same domain and the user has rights over this node. - if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) { - // See if the node is connected - var agent = obj.parent.wsagents[command.nodeid]; - if (agent != null) { + if (obj.common.validateString(command.nodeid, 8, 128) == false) return; + var splitnodeid = command.nodeid.split('/'); + // Check that we are in the same domain and the user has rights over this node. + if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) { + // See if the node is connected + var agent = obj.parent.wsagents[command.nodeid]; + if (agent != null) { + // Check if we have permission to send a message to that node + var rights = user.links[agent.dbMeshKey]; + if ((rights != null) && ((rights.rights & 8) != 0)) { // 8 is remote control permission + command.sessionid = ws.sessionId; // Set the session id, required for responses. + command.rights = rights.rights; // Add user rights flags to the message + delete command.nodeid; // Remove the nodeid since it's implyed. + agent.send(JSON.stringify(command)); + } + } else { + // Check if a peer server is connected to this agent + var routing = obj.parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type + if (routing != null) { // Check if we have permission to send a message to that node - var rights = user.links[agent.dbMeshKey]; + var rights = user.links[routing.meshid]; if ((rights != null) && ((rights.rights & 8) != 0)) { // 8 is remote control permission - command.sessionid = ws.sessionId; // Set the session id, required for responses. - command.rights = rights.rights; // Add user rights flags to the message - delete command.nodeid; // Remove the nodeid since it's implyed. - agent.send(JSON.stringify(command)); - } - } else { - // Check if a peer server is connected to this agent - var routing = obj.parent.parent.GetRoutingServerId(command.nodeid, 1); // 1 = MeshAgent routing type - if (routing != null) { - // Check if we have permission to send a message to that node - var rights = user.links[routing.meshid]; - if ((rights != null) && ((rights.rights & 8) != 0)) { // 8 is remote control permission - command.fromSessionid = ws.sessionId; // Set the session id, required for responses. - command.rights = rights.rights; // Add user rights flags to the message - obj.parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid); - } + command.fromSessionid = ws.sessionId; // Set the session id, required for responses. + command.rights = rights.rights; // Add user rights flags to the message + obj.parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid); } } } @@ -307,35 +319,34 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'changeemail': { // Change the email address - if ((command.email != null) && (typeof command.email == 'string') && (command.email.length < 1024)) { - var x = command.email.split('@'); - if ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2)) { - if (obj.parent.users[req.session.userid].email != command.email) { - // Check if this email is already validated on a different account - obj.db.GetUserWithVerifiedEmail(domain.id, command.email, function (err, docs) { - if (docs.length > 0) { - // Notify the duplicate email error - ws.send(JSON.stringify({ action: 'msg', type: 'notify', value: 'Failed to change email address, another account already using: ' + EscapeHtml(command.email) + '.' })); - } else { - // Update the user's email - var oldemail = user.email; - user.email = command.email; - user.emailVerified = false; - obj.parent.db.SetUser(user); + if (obj.common.validateString(command.email, 3, 1024) == false) return; + var x = command.email.split('@'); + if ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2)) { + if (obj.parent.users[req.session.userid].email != command.email) { + // Check if this email is already validated on a different account + obj.db.GetUserWithVerifiedEmail(domain.id, command.email, function (err, docs) { + if (docs.length > 0) { + // Notify the duplicate email error + ws.send(JSON.stringify({ action: 'msg', type: 'notify', value: 'Failed to change email address, another account already using: ' + EscapeHtml(command.email) + '.' })); + } else { + // Update the user's email + var oldemail = user.email; + user.email = command.email; + user.emailVerified = false; + obj.parent.db.SetUser(user); - // Event the change - var userinfo = obj.common.Clone(user); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: userinfo.name, account: userinfo, action: 'accountchange', msg: 'Changed email of user ' + userinfo.name + ' from ' + oldemail + ' to ' + user.email, domain: domain.id }) - } - }); - } + // Event the change + var userinfo = obj.common.Clone(user); + delete userinfo.hash; + delete userinfo.passhint; + delete userinfo.salt; + delete userinfo.type; + delete userinfo.domain; + delete userinfo.subscriptions; + delete userinfo.passtype; + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: userinfo.name, account: userinfo, action: 'accountchange', msg: 'Changed email of user ' + userinfo.name + ' from ' + oldemail + ' to ' + user.email, domain: domain.id }) + } + }); } } break; @@ -343,14 +354,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'verifyemail': { // Send a account email verification email - if ((command.email != null) && (typeof command.email == 'string') && (command.email.length < 1024)) { - var x = command.email.split('@'); - if ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2)) { - if (obj.parent.users[req.session.userid].email == command.email) { - // Send the verification email - if (obj.parent.parent.mailserver != null) { - obj.parent.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); - } + if (obj.common.validateString(command.email, 3, 1024) == false) return; + var x = command.email.split('@'); + if ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2)) { + if (obj.parent.users[req.session.userid].email == command.email) { + // Send the verification email + if (obj.parent.parent.mailserver != null) { + obj.parent.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); } } } @@ -363,10 +373,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if ((user.siteadmin & 2) == 0) break; if (obj.parent.parent.multiServer == null) { // No peering, use simple session counting - for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } } + for (var i in obj.parent.wssessions) { if (obj.parent.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.parent.wssessions[i].length; } } } else { // We have peer servers, use more complex session counting - for (var userid in obj.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.sessionsCount[userid]; } } + for (var userid in obj.parent.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.parent.sessionsCount[userid]; } } } ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions, tag: command.tag })); // wssessions is: userid --> count break; @@ -375,9 +385,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { { // Delete a user account if ((user.siteadmin & 2) == 0) break; - var delusername = command.username, deluserid = command.userid, deluser = obj.parent.users[deluserid]; + if (obj.common.validateString(command.userid, 1, 2048) == false) break; + var delusersplit = command.userid.split('/'), deluserid = command.userid, deluser = obj.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 ((deluserid.split('/').length != 3) || (deluserid.split('/')[1] != domain.id)) break; // Invalid domain, operation only valid for current domain // Delete all files on the server for this account try { @@ -387,7 +398,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { obj.db.Remove(deluserid); delete obj.parent.users[deluserid]; - obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: delusername, action: 'accountremove', msg: 'Account removed', domain: domain.id }) + obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id }) obj.parent.parent.DispatchEvent([deluserid], obj, 'close'); break; @@ -396,10 +407,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { { // Add a new user account if ((user.siteadmin & 2) == 0) break; + if (obj.common.validateString(command.username, 1, 64) == false) break; // Username is between 1 and 64 characters + if (obj.common.validateString(command.pass, 1, 256) == false) break; // Password is between 1 and 256 characters var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(); if (newusername == '~') break; // This is a reserved user name if (!obj.parent.users[newuserid]) { - var newuser = { type: 'user', _id: newuserid, name: newusername, email: command.email, creation: Date.now(), domain: domain.id }; + var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Date.now(), domain: domain.id }; + if (obj.common.validateString(command.email, 1, 256) == true) { newuser.email = command.email; } // Email is between 1 and 256 characters obj.parent.users[newuserid] = newuser; // Create a user, generate a salt and hash the password require('./pass').hash(command.pass, function (err, salt, hash) { @@ -422,9 +436,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if (((user.siteadmin & 2) != 0) || (user.name == command.name)) { var chguserid = 'user/' + domain.id + '/' + command.name.toLowerCase(), chguser = obj.parent.users[chguserid], change = 0; if (chguser) { - if (command.email && chguser.email != command.email) { chguser.email = command.email; change = 1; } - if (command.quota != chguser.quota) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; } - if ((user.siteadmin == 0xFFFFFFFF) && (command.siteadmin != null) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1 } + if (obj.common.validateString(command.email, 1, 256) && (chguser.email != command.email)) { chguser.email = command.email; change = 1; } + if (obj.common.validateInt(command.quota, 0) && (command.quota != chguser.quota)) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; } + if ((user.siteadmin == 0xFFFFFFFF) && obj.common.validateInt(command.siteadmin) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1 } if (change == 1) { obj.db.SetUser(chguser); obj.parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe'); @@ -442,6 +456,24 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } break; } + case 'notifyuser': + { + // Send a notification message to a user + if ((user.siteadmin & 2) == 0) break; + if (obj.common.validateString(command.userid, 1, 64) == false) break; // Meshname is between 1 and 64 characters + if (obj.common.validateString(command.msg, 1, 4096) == false) break; + + // Create the notification message + var notification = { "action": "msg", "type": "notify", "value": "" + user.name + ": " + EscapeHtml(command.msg), "userid": user._id, "username": user.name }; + + // Get the list of sessions for this user + var sessions = obj.parent.wssessions[command.userid]; + if (sessions != null) { for (var i in sessions) { sessions[i].send(JSON.stringify(notification)); } } + + if (obj.parent.parent.multiServer != null) { + // TODO: Add multi-server support + } + } case 'serverversion': { // Check the server version @@ -459,7 +491,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'createmesh': { // Create mesh - // TODO: Right now, we only create type 1 Agent-less Intel AMT mesh, or type 2 Agent mesh + if (obj.common.validateString(command.meshname, 1, 64) == false) break; // Meshname is between 1 and 64 characters + if (obj.common.validateString(command.desc, 0, 1024) == false) break; // Mesh description is between 0 and 1024 characters + + // We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2) if ((command.meshtype == 1) || (command.meshtype == 2)) { // Create a type 1 agent-less Intel AMT mesh. obj.parent.crypto.randomBytes(48, function (err, buf) { @@ -482,6 +517,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'deletemesh': { // Delete a mesh and all computers within it + if (obj.common.validateString(command.meshid, 1, 1024) == false) break; // Check the meshid obj.db.Get(command.meshid, function (err, meshes) { if (meshes.length != 1) return; var mesh = meshes[0]; @@ -519,20 +555,25 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'editmesh': { // Change the name or description of a mesh + if (obj.common.validateString(command.meshid, 1, 1024) == false) break; // Check the meshid var mesh = obj.parent.meshes[command.meshid], change = ''; if (mesh) { // Check if this user has rights to do this if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 1) == 0)) return; if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain - if (command.meshname && command.meshname != '' && command.meshname != mesh.name) { change = 'Mesh name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; } - if (command.desc != null && command.desc != mesh.desc) { if (change != '') change += ' and description changed'; else change += 'Mesh "' + mesh.name + '" description changed'; mesh.desc = command.desc; } + if ((obj.common.validateString(command.meshname, 1, 64) == true) && (command.meshname != mesh.name)) { change = 'Mesh name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; } + if ((obj.common.validateString(command.desc, 1, 1024) == true) && (command.desc != mesh.desc)) { if (change != '') change += ' and description changed'; else change += 'Mesh "' + mesh.name + '" description changed'; mesh.desc = command.desc; } if (change != '') { obj.db.Set(mesh); obj.parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }) } } break; } case 'addmeshuser': { + if (obj.common.validateString(command.meshid, 1, 1024) == false) break; // Check the meshid + if (obj.common.validateString(command.username, 1, 64) == false) break; // Username is between 1 and 64 characters + if (obj.common.validateInt(command.meshadmin) == false) break; // Mesh rights must be an integer + // Check if the user exists var newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(), newuser = obj.parent.users[newuserid]; if (newuser == null) { @@ -554,17 +595,19 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { obj.parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe'); // Add a user to the mesh - mesh.links[newuserid] = { name: command.username, rights: command.meshadmin }; + mesh.links[newuserid] = { name: newuser.name, rights: command.meshadmin }; obj.db.Set(mesh); // Notify mesh change - var change = 'Added user ' + command.username + ' to mesh ' + mesh.name; - obj.parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, { etype: 'mesh', username: user.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }) + var change = 'Added user ' + newuser.name + ' to mesh ' + mesh.name; + obj.parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, { 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: change, domain: domain.id }) } break; } case 'removemeshuser': { + if (obj.common.validateString(command.userid, 1, 1024) == false) break; // Check userid + if (obj.common.validateString(command.meshid, 1, 1024) == false) break; // Check meshid if ((command.userid.split('/').length != 3) || (command.userid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain // Check if the user exists @@ -597,15 +640,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { // Notify mesh change var change = 'Removed user ' + deluser.name + ' from mesh ' + mesh.name; - obj.parent.parent.DispatchEvent(['*', mesh._id, user._id, command.userid], obj, { etype: 'mesh', username: user.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }) + obj.parent.parent.DispatchEvent(['*', mesh._id, user._id, command.userid], obj, { 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: change, domain: domain.id }) } break; } case 'addamtdevice': { if (obj.args.wanonly == true) return; // This is a WAN-only server, local Intel AMT computers can't be added - + if (obj.common.validateString(command.meshid, 1, 1024) == false) break; // Check meshid if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + if (obj.common.validateString(command.devicename, 1, 256) == false) break; // Check device name + if (obj.common.validateString(command.hostname, 1, 256) == false) break; // Check hostname + if (obj.common.validateString(command.amtusername, 1, 16) == false) break; // Check username + if (obj.common.validateString(command.amtpassword, 1, 16) == false) break; // Check password + if (obj.common.validateInt(command.amttls, 0, 1) == false) break; // Check TLS flag // Get the mesh var mesh = obj.parent.meshes[command.meshid]; @@ -634,6 +682,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'scanamtdevice': { if (obj.args.wanonly == true) return; // This is a WAN-only server, this type of scanning is not allowed. + if (obj.common.validateString(command.range, 1, 256) == false) break; // Check range string // Ask the RMCP scanning to scan a range of IP addresses if (obj.parent.parent.amtScanner) { @@ -645,8 +694,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } case 'removedevices': { + if (obj.common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's + for (var i in command.nodeids) { var nodeid = command.nodeids[i]; + if (obj.common.validateString(nodeid, 1, 1024) == false) break; // Check nodeid if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain // Get the device @@ -669,10 +721,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { obj.parent.parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', username: user.name, action: 'removenode', nodeid: node._id, msg: change, domain: domain.id }) // Disconnect all connections if needed - var state = obj.parent.parent.GetConnectivityState(command.nodeid); + var state = obj.parent.parent.GetConnectivityState(nodeid); if ((state != null) && (state.connectivity != null)) { - if ((state.connectivity & 1) != 0) { obj.parent.wsagents[command.nodeid].close(); } // Disconnect mesh agent - if ((state.connectivity & 2) != 0) { obj.parent.parent.mpsserver.close(obj.parent.parent.mpsserver.ciraConnections[command.nodeid]); } // Disconnect CIRA connection + if ((state.connectivity & 1) != 0) { obj.parent.wsagents[nodeid].close(); } // Disconnect mesh agent + if ((state.connectivity & 2) != 0) { obj.parent.parent.mpsserver.close(obj.parent.parent.mpsserver.ciraConnections[nodeid]); } // Disconnect CIRA connection } } }); @@ -682,12 +734,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } case 'wakedevices': { - // TODO: INPUT VALIDATION!!! + if (obj.common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's // TODO: We can optimize this a lot. // - We should get a full list of all MAC's to wake first. // - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan. for (var i in command.nodeids) { var nodeid = command.nodeids[i], wakeActions = 0; + if (obj.common.validateString(nodeid, 1, 1024) == false) break; // Check nodeid if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain // Get the device obj.db.Get(nodeid, function (err, nodes) { @@ -739,9 +792,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } case 'poweraction': { - // TODO: INPUT VALIDATION!!! + if (obj.common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's for (var i in command.nodeids) { var nodeid = command.nodeids[i], powerActions = 0; + if (obj.common.validateString(nodeid, 1, 1024) == false) break; // Check nodeid if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain // Get the device obj.db.Get(nodeid, function (err, nodes) { @@ -774,7 +828,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'getnetworkinfo': { // Argument validation - if ((command.nodeid == null) || (typeof command.nodeid != 'string') || (command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + if (obj.common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid + if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain // Get the device obj.db.Get(command.nodeid, function (err, nodes) { @@ -800,7 +855,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'changedevice': { // Argument validation - if ((command.nodeid == null) || (typeof command.nodeid != 'string') || (command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + if (obj.common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid + if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain if ((command.userloc) && (command.userloc.length != 2) && (command.userloc.length != 0)) return; // Change the device @@ -857,7 +913,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'uploadagentcore': { if (user.siteadmin != 0xFFFFFFFF) break; + if (obj.common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid if (command.path) { + if (obj.common.validateString(command.path, 1, 4096) == false) break; // Check path if (command.path == '*') { // Update the server default core and send a core hash request // Load default mesh agent core if present, then perform a core update @@ -883,6 +941,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'agentdisconnect': { // Force mesh agent disconnection + if (obj.common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid + if (obj.common.validateInt(command.disconnectMode) == false) break; // Check disconnect mode obj.parent.forceMeshAgentDisconnect(user, domain, command.nodeid, command.disconnectMode); break; } @@ -896,6 +956,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { case 'getcookie': { // Check if this user has rights on this nodeid + if (obj.common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid obj.db.Get(command.nodeid, function (err, nodes) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???) if (nodes.length == 1) { var meshlinks = user.links[nodes[0].meshid]; @@ -916,6 +977,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { { if ((obj.parent.parent.mailserver == null) || (obj.args.lanonly == true)) return; // This operation requires the email server if ((obj.parent.parent.certificates.CommonName == null) || (obj.parent.parent.certificates.CommonName == 'un-configured')) return; // Server name must be configured + if (obj.common.validateString(command.meshid, 1, 1024) == false) break; // Check meshid if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain // Get the mesh diff --git a/package.json b/package.json index 726f0c3b..044836c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.5-w", + "version": "0.1.5-y", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default.handlebars b/views/default.handlebars index 448f26eb..a62c2b97 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -4197,7 +4197,7 @@ function account_createMesh() { if (xxdialogMode) return; var x = "Create a new mesh computer group using the options below.

"; - x += addHtmlValue('Mesh Name', ''); + x += addHtmlValue('Mesh Name', ''); x += addHtmlValue('Mesh Type', '
'); x += addHtmlValue('Description', '
'); setDialogMode(2, "Create Mesh", 3, account_createMeshEx, x); @@ -4747,15 +4747,24 @@ function updateUsers() { QV('MainMenuMyUsers', (users != null) && ((features & 4) == 0)); if ((users == null) || ((features & 4) != 0)) { QH('p3users', ''); return; } + + // Sort the list of user id's + var sortedUserIds = []; + for (var i in users) { sortedUserIds.push(i); } + sortedUserIds.sort(); + + // Display the users using the sorted list var x = ''; - for (var i in users) { - var user = users[i], icon = 'm2', msg = ''; + for (var i in sortedUserIds) { + var user = users[sortedUserIds[i]], icon = 'm2', msg = '', self = (user.name != userinfo.name); if (wssessions != null && wssessions[user._id]) { + if (self) { msg += ""; } var sessions = wssessions[user._id]; - if (sessions == 1) { msg = '1 active session'; } else { msg = sessions + ' active sessions'; } + if (sessions == 1) { msg += '1 active session'; } else { msg += sessions + ' active sessions'; } + if (self) { msg += ""; } } if (msg != '') msg += ', '; - if (user.name != userinfo.name) { msg += ""; } + if (self) { msg += ""; } if ((user.siteadmin == null) || (user.siteadmin == 0)) { msg += "User"; } else if (user.siteadmin == 8) { @@ -4766,7 +4775,7 @@ msg += "Partial Admin"; } if ((user.quota != null) && ((user.siteadmin & 8) != 0)) { msg += ", " + (user.quota / 1024) + " k"; } - if (user.name != userinfo.name) { msg += ""; } + if (self) { msg += ""; } var username = EscapeHtml(user.name); if (user.email != null) { username += ', ' + user.email + '' + (((serverinfo.emailcheck == true) && (user.emailVerified != true))?' (unverified)':''); } x += '
'; @@ -4779,6 +4788,16 @@ QH('p3users', x); } + function showUserAlertDialog(e, userid) { + if (xxdialogMode) return; + haltEvent(e); + setDialogMode(2, "Notify " + EscapeHtml(users[userid].name), 3, showUserAlertDialogEx, 'Send a text notification to this user.', userid); + Q('d2notifyText').focus(); + return false; + } + + function showUserAlertDialogEx(button, userid) { meshserver.send({ action: 'notifyuser', userid: userid, msg: Q('d2notifyText').value }); } + function doemail(e, addr) { if (xxdialogMode) return; haltEvent(e); @@ -4807,10 +4826,10 @@ function showCreateNewAccountDialog() { if (xxdialogMode) return; var x = ''; - x += addHtmlValue('Name', ''); - x += addHtmlValue('Email', ''); - x += addHtmlValue('Password', ''); - x += addHtmlValue('Password', ''); + x += addHtmlValue('Name', ''); + x += addHtmlValue('Email', ''); + x += addHtmlValue('Password', ''); + x += addHtmlValue('Password', ''); setDialogMode(2, "Create Account", 3, showCreateNewAccountDialogEx, x); showCreateNewAccountDialogValidate(); Q('p4name').focus(); diff --git a/views/login.handlebars b/views/login.handlebars index 437e3ccd..694d9123 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -42,11 +42,11 @@ - + - + @@ -73,27 +73,27 @@
Username:
Password:
- + - + - + - + - + - +
Username:
Email:
Password:
Password:
Password Hint:
Creation Token:
@@ -116,7 +116,7 @@ - +
Email: