diff --git a/amtscanner.js b/amtscanner.js index 0d8dad9f..4f1c9b5f 100644 --- a/amtscanner.js +++ b/amtscanner.js @@ -112,7 +112,7 @@ module.exports.CreateAmtScanner = function (parent) { obj.performScan = function () { //console.log('performScan'); if (obj.action == false) { return false; } - obj.parent.db.getLocalAmtNodes(function (err, docs) { + obj.parent.db.getLocalAmtNodes(10, function (err, docs) { // TODO: handler more than 10 computer scan at the same time. for (var i in obj.scanTable) { obj.scanTable[i].present = false; } if (err == null && docs.length > 0) { for (var i in docs) { diff --git a/db.js b/db.js index 882855b0..79940905 100644 --- a/db.js +++ b/db.js @@ -109,7 +109,7 @@ module.exports.CreateDB = function (parent) { obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } } obj.clearOldEntries = function (type, days, domain) { var cutoff = Date.now() - (1000 * 60 * 60 * 24 * days); obj.file.remove({ type: type, time: { $lt: cutoff } }, { multi: true }); } obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }).exec(func); } else { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }, func); } } - obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); } + obj.getLocalAmtNodes = function (limit, func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }).limit(limit, func); } obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); } // This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db. diff --git a/meshcentral.js b/meshcentral.js index 796ceec8..d89f47f1 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -79,7 +79,7 @@ function CreateMeshCentralServer(config, args) { try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not. // Check for invalid arguments - var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen']; + var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin']; for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } } if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; } for (var i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence. @@ -199,7 +199,7 @@ function CreateMeshCentralServer(config, args) { obj.StartEx = function () { //var wincmd = require('node-windows'); //wincmd.list(function (svc) { console.log(svc); }, true); - + // Write the server state obj.updateServerState('state', 'starting'); @@ -265,12 +265,14 @@ function CreateMeshCentralServer(config, args) { if (obj.args.dbimport) { // Import the entire database from a JSON file if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); } - var json = null, json2 = ""; - try { json = obj.fs.readFileSync(obj.args.dbimport); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + '.'); process.exit(); } - for (var i = 0; i < json.length; i++) { if (json[i] >= 32) json2 += String.fromCharCode(json[i]); } // Remove all bad chars + var json = null, json2 = "", badCharCount = 0; + try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + '.'); process.exit(); } + for (var i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars + if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); } try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); } if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); } for (var i in json) { if ((json[i].type == "mesh") && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } } // Escape MongoDB invalid field chars + //for (var i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname obj.db.RemoveAll(function () { obj.db.InsertMany(json, function (err) { if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit(); }); }); return; } @@ -279,6 +281,42 @@ function CreateMeshCentralServer(config, args) { obj.db.clearOldEntries('event', 30); // Clear all event entires that are older than 30 days. obj.db.clearOldEntries('power', 10); // Clear all event entires that are older than 10 days. If a node is connected longer than 10 days, current power state will be used for everything. + // Setup a site administrator + if ((obj.args.admin) && (typeof obj.args.admin == 'string')) { + var adminname = obj.args.admin.split('/'); + if (adminname.length == 1) { adminname = 'user//' + adminname[0]; } + else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; } + else { console.log('Invalid administrator name.'); process.exit(); return; } + obj.db.Get(adminname, function (err, user) { + if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; } + user[0].siteadmin = 0xFFFFFFFF; + obj.db.Set(user[0], function () { + if (user[0].domain == '') { console.log('User ' + user[0].name + ' set to site administrator.'); } else { console.log('User ' + user[0].name + ' of domain ' + user[0].domain + ' set to site administrator.'); } + process.exit(); + return; + }); + }); + return; + } + + // Remove a site administrator + if ((obj.args.unadmin) && (typeof obj.args.unadmin == 'string')) { + var adminname = obj.args.unadmin.split('/'); + if (adminname.length == 1) { adminname = 'user//' + adminname[0]; } + else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; } + else { console.log('Invalid administrator name.'); process.exit(); return; } + obj.db.Get(adminname, function (err, user) { + if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; } + if (user[0].siteadmin) { delete user[0].siteadmin; } + obj.db.Set(user[0], function () { + if (user[0].domain == '') { console.log('User ' + user[0].name + ' is not a site administrator.'); } else { console.log('User ' + user[0].name + ' of domain ' + user[0].domain + ' is not a site administrator.'); } + process.exit(); + return; + }); + }); + return; + } + // Perform other database cleanup obj.db.cleanup(); diff --git a/meshuser.js b/meshuser.js index df05a571..5e4977fa 100644 --- a/meshuser.js +++ b/meshuser.js @@ -717,6 +717,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if (command.amttls == '0') { command.amttls = 0; } else if (command.amttls == '1') { command.amttls = 1; } // Check TLS flag if ((command.amttls != 1) && (command.amttls != 0)) break; + // If we are in WAN-only mode, hostname is not used + if ((obj.parent.parent.args.wanonly == true) && (command.hostname)) { delete command.hostname; } + // Get the mesh var mesh = obj.parent.meshes[command.meshid]; if (mesh) { @@ -972,6 +975,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { var changes = [], change = 0, event = { etype: 'node', username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id }; event.msg = ": "; + // If we are in WAN-only mode, host is not used + if ((obj.parent.parent.args.wanonly == true) && (command.host)) { delete command.host; } + // Look for a change if (command.icon && (command.icon != node.icon)) { change = 1; node.icon = command.icon; changes.push('icon'); } if (command.name && (command.name != node.name)) { change = 1; node.name = command.name; changes.push('name'); } diff --git a/public/styles/style.css b/public/styles/style.css index b483f563..f4575fd4 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -531,4 +531,10 @@ a { .deskToolsBar:hover { background-color: #EFE8B6; -} \ No newline at end of file +} + +.userTableHeader { + border-bottom: 1pt solid lightgray; + padding-top: 4px; + padding-bottom: 4px; +} diff --git a/sample-config.json b/sample-config.json index 907b8e97..6aec674b 100644 --- a/sample-config.json +++ b/sample-config.json @@ -8,7 +8,8 @@ "AllowLoginToken": true, "AllowFraming": true, "WebRTC": false, - "ClickOnce": false + "ClickOnce": false, + "UserAllowedIP" : "127.0.0.1,::1,192.168.0.100" }, "_domains": { "": { diff --git a/views/default.handlebars b/views/default.handlebars index 4a168596..50e50f5b 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -246,7 +246,10 @@
 
 
-
   
+
   +   +   +
 
@@ -748,6 +751,7 @@ var sort = 0; var searchFocus = 0; var mapSearchFocus = 0; + var userSearchFocus = 0; var consoleFocus = 0; var showRealNames = false; var meshserver = null; @@ -1412,6 +1416,18 @@ if (!xxdialogMode && xxcurrentView == 11 && desktop && Q("DeskControl").checked) return desktop.m.handleKeys(e); if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeys(e); if (!xxdialogMode && xxcurrentView == 15) return agentConsoleHandleKeys(e); + if (!xxdialogMode && xxcurrentView == 4) { + if (e.ctrlKey == true || e.altKey == true || e.metaKey == true) return; + var processed = 0; + if (e.key) { + if (e.key.length === 1 && userSearchFocus == 0) { Q('UserSearchInput').value = ((Q('UserSearchInput').value + e.key)); processed = 1; } + if (e.keyCode == 8 && userSearchFocus == 0) { var x = Q('UserSearchInput').value; Q('UserSearchInput').value = x.substring(0, x.length - 1); processed = 1; } + if (e.keyCode == 27) { Q('UserSearchInput').value = ''; processed = 1; } + } else { + if (e.charCode != 0 && userSearchFocus == 0) { Q('UserSearchInput').value = ((Q('UserSearchInput').value + String.fromCharCode(e.charCode))); processed = 1; } + } + if (processed > 0) { if (processed == 1) { onUserSearchInputChanged(); } return haltEvent(e); } + } if (xxdialogMode || xxcurrentView != 1) return; if (e.ctrlKey == true && e.charCode == 96) { showRealNames = !showRealNames; @@ -1449,6 +1465,11 @@ if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) { return terminal.m.TermHandleKeyDown(e); } if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { haltEvent(e); return false; } // F5 Refresh on files if (!xxdialogMode && xxcurrentView == 15) { return agentConsoleHandleKeys(e); } + if (!xxdialogMode && xxcurrentView == 4) { + if (e.keyCode === 8 && userSearchFocus == 0) { var x = Q('UserSearchInput').value; Q('UserSearchInput').value = (x.substring(0, x.length - 1)); processed = 1; } + if (e.keyCode === 27) { Q('UserSearchInput').value = ''; processed = 1; } + if (processed > 0) { if (processed == 1) { onSearchInputChanged(); } return haltEvent(e); } + } if (xxdialogMode || xxcurrentView != 1 || e.ctrlKey == true || e.altKey == true || e.metaKey == true) return; var processed = 0; if (Q('viewselect').value < 3) { @@ -1466,6 +1487,7 @@ if (!xxdialogMode && xxcurrentView == 11 && desktop && Q("DeskControl").checked) return desktop.m.handleKeyUp(e); if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeyUp(e); if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { p13folderup(9999); haltEvent(e); return false; } // F5 Refresh on files + if (!xxdialogMode && xxcurrentView == 4) { if ((e.keyCode === 8 && searchFocus == 0) || e.keyCode === 27) { return haltEvent(e); } } if (xxdialogMode && e.keyCode == 27) { dialogclose(0); } if (xxdialogMode || xxcurrentView != 0 || e.ctrlKey == true || e.altKey == true || e.metaKey == true) return; if (Q('viewselect').value < 3) { if ((e.keyCode === 8 && searchFocus == 0) || e.keyCode === 27) { return haltEvent(e); } } @@ -2110,6 +2132,7 @@ function deviceHostSort(a, b) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } function onSearchFocus(x) { searchFocus = x; } function onMapSearchFocus(x) { mapSearchFocus = x; } + function onUserSearchFocus(x) { userSearchFocus = x; } function onConsoleFocus(x) { consoleFocus = x; } function onSearchInputChanged() { @@ -2838,10 +2861,10 @@ x += addDeviceAttribute('Mesh', '' + EscapeHtml(meshes[node.meshid].name) + ''); // Attribute: Name - if (node.rname != null) { x += addDeviceAttribute('Name', '' + EscapeHtml(node.rname) + ''); } + if ((node.rname != null) && (node.name != node.rname)) { x += addDeviceAttribute('Name', '' + EscapeHtml(node.rname) + ''); } // Attribute: Host - if ((mesh.mtype == 1) || (node.name != node.host)) { + if ((features & 1) == 0) { // If not WAN-only, local hostname is in use if ((meshrights & 4) != 0) { if (node.host) { x += addDeviceAttribute('Hostname', '' + EscapeHtml(node.host) + ''); @@ -5126,19 +5149,27 @@ if ((users == null) || ((features & 4) != 0)) { QH('p3users', ''); return; } // Sort the list of user id's - var sortedUserIds = []; + var sortedUserIds = [], maxUsers = 100, hiddenUsers = 0; for (var i in users) { sortedUserIds.push(i); } sortedUserIds.sort(); + // Get search + var userSearch = Q('UserSearchInput').value.toLowerCase(); + // Display the users using the sorted list var x = '', addHeader = true; // Online users for (var i in sortedUserIds) { var user = users[sortedUserIds[i]], sessions = null; if (wssessions != null) { sessions = wssessions[user._id]; } - if (sessions != null) { - if (addHeader) { x += ''; addHeader = false; } - x += addUserHtml(user, sessions); + if ((sessions != null) && (user.name.toLowerCase().indexOf(userSearch) >= 0)) { + if (maxUsers > 0) { + if (addHeader) { x += ''; addHeader = false; } - x += addUserHtml(user, sessions); + if ((sessions == null) && (user.name.toLowerCase().indexOf(userSearch) >= 0)) { + if (maxUsers > 0) { + if (addHeader) { x += '
Online Users
Online Users'; addHeader = false; } + x += addUserHtml(user, sessions); + maxUsers--; + } else { + hiddenUsers++; + } } } addHeader = true; @@ -5146,12 +5177,19 @@ for (var i in sortedUserIds) { var user = users[sortedUserIds[i]], sessions = null; if (wssessions != null) { sessions = wssessions[user._id]; } - if (sessions == null) { - if (addHeader) { x += '
Offline Users
Offline Users'; addHeader = false; } + x += addUserHtml(user, sessions); + maxUsers--; + } else { + hiddenUsers++; + } } } x += '
'; + if (hiddenUsers == 1) { x += '
1 more user not shown, use search box to look for users...
'; } + else if (hiddenUsers > 1) { x += '
' + hiddenUsers + ' more users not shown, use search box to look for users...
'; } QH('p3users', x); // Update current user panel if needed @@ -5191,7 +5229,7 @@ x += '
'; x += '
'; x += '
'; - x += '
' + username + '' + msg + '
'; + x += '
' + username + '' + msg + '
'; // return x; } @@ -5294,6 +5332,8 @@ meshserver.send(x); } + function onUserSearchInputChanged() { updateUsers(); } + // // MY USERS GENERAL // diff --git a/webserver.js b/webserver.js index 7be0c285..55db66bb 100644 --- a/webserver.js +++ b/webserver.js @@ -316,6 +316,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate if (req.session.passhint) { delete req.session.passhint; } if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; } if (req.body.host) { + // TODO: This is a terrible search!!! FIX THIS. + /* obj.db.GetAllType('node', function (err, docs) { for (var i = 0; i < docs.length; i++) { if (docs[i].name == req.body.host) { @@ -327,6 +329,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate // This redirect happens after finding node is completed res.redirect(domain.url); }); + */ } else { res.redirect(domain.url); } @@ -722,6 +725,15 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified res.render(obj.path.join(__dirname, isMobileBrowser(req) ? 'views/login-mobile' : 'views/login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, footer: (domain.footer == null) ? '' : domain.footer }); + + /* + var xoptions = { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, footer: (domain.footer == null) ? '' : domain.footer }; + var xpath = obj.path.join(__dirname, isMobileBrowser(req) ? 'views/login-mobile' : 'views/login'); + console.log('Render...'); + res.render(xpath, xoptions, function (err, html) { + console.log(err, html); + }); + */ } }