+
+
+
+
+ |
+
{{{logoutControl}}}
+
+
+
+
+ ↺
+ ↻
+
+
+
+
+
+
+ Disconnected.
+
+
+
+ |
+
+
+ |
+
+ + + | +
+
+
+
+
+ |
+
{{{logoutControl}}}
+
+
+
+
+ ↺
+ ↻
+
+
+
+
+
+
+
+ Disconnected
+
+
+
+
+
+ |
+
+
+ |
+
+ + + | +
+
+
+
+
+ |
+
{{{message}}}
+Connect to your home or office devices from anywhere in the world using MeshCentral, the remote monitoring and management web site. You will need to download and install a special management agent on your computers. Once installed, each mesh enabled computer will show up in the "My Devices" section of this web site and you will be able to monitor them, power them on and off and take control of them.
++ + | ++ | + + +
{{{logoutControl}}}
+Please contact the site administrator for terms of use.
++ The following are the required disclosures of open source components and software incorporated into Intel® Remote Managed Services. +
+
+ 1.AJAX Control Toolkit - New BSD License
+ Copyright (c) 2009, CodePlex Foundation. All rights reserved.
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ 1.Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ 2.Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ 3.Neither the name of CodePlex Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ 2.OpenSSL – OpenSSL and SSLeay License
+ http://www.openssl.org/source/license.html +
+
+ Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved.
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ 1.Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ 2.Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ 3.All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ 4.The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.
+ 5.Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.
+ 6.Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)".
+ THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ 3.jQuery Foundation - MIT License
+ Copyright 2013 jQuery Foundation and other contributors http://jquery.com/ +
++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +
+
+ 4.jQuery User Interface - MIT License
+ Copyright 2013 jQuery Foundation and other contributors, http://jqueryui.com/ +
+
+ This software consists of voluntary contributions made by many individuals (AUTHORS.txt, http://jqueryui.com/about ). For exact contribution history,see the revision history and logs, available at http://jquery-ui.googlecode.com/svn/
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ 5.noVNC - Mozilla Public License 2.0
+ https://github.com/kanaka/noVNC/blob/master/LICENSE.txt +
+
+ Copyright (C) 2011 Joel Martin This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ 6.Rcarousel - MIT LIcense
+ https://github.com/ryrych/rcarousel/blob/master/widget/license +
+
+ Copyright (c) 2010 Wojciech 'RRH' Ryrych
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ 7.Webtoolkit Javascript Base 64 – Creative Commons Attribution 2.0 UK License
+ This software uses code from http://www.webtoolkit.info/javascript-base64.html licensed under the http://creativecommons.org/licenses/by/2.0/uk/legalcode and its source can be downloaded from http://www.webtoolkit.info/javascript-base64.html.
+ 8.Android LibJPEG +
+
+ This software uses code from https://github.com/android/platform_external_jpeg licensed under https://github.com/android/platform_external_jpeg/blob/master/NOTICE
' + err + '
'; + if (msg) res.locals.message = '' + msg + '
'; + next(); + }); + + // Fetch all users from the database, keep this in memory + obj.db.GetAllType('user', function (err, docs) { + var domainUserCount = {}; + for (var i in parent.config.domains) { domainUserCount[i] = 0; } + for (var i in docs) { var u = obj.users[docs[i]._id] = docs[i]; domainUserCount[u.domain]++; } + for (var i in parent.config.domains) { + if (domainUserCount[i] == 0) { + if (parent.config.domains[i].newAccounts == 0) { parent.config.domains[i].newAccounts = 2; } + console.log('Server ' + ((i == '') ? '' : (i + ' ')) + 'has no users, next new account will be site administrator.'); + } + } + }); + + // Fetch all meshes from the database, keep this in memory + obj.db.GetAllType('mesh', function (err, docs) { for (var i in docs) { obj.meshes[docs[i]._id] = docs[i]; } }); + + // Authenticate the user + obj.authenticate = function (name, pass, domain, fn) { + if (!module.parent) console.log('authenticating %s:%s:%s', domain.id, name, pass); + var user = obj.users['user/' + domain.id + '/' + name.toLowerCase()]; + // Query the db for the given username + if (!user) return fn(new Error('cannot find user')); + // Apply the same algorithm to the POSTed password, applying the hash against the pass / salt, if there is a match we found the user + if (user.salt == undefined) { + fn(new Error('invalid password')); + } else { + obj.hash(pass, user.salt, function (err, hash) { + if (err) return fn(err); + if (hash == user.hash) return fn(null, user._id); + fn(new Error('invalid password')); + }); + } + } + + /* + obj.restrict = function (req, res, next) { + console.log('restrict', req.url); + var domain = getDomain(req); + if (req.session.userid) { + next(); + } else { + req.session.error = 'Access denied!'; + res.redirect(domain.url + 'login'); + } + } + */ + + // Return the current domain of the request + function getDomain(req) { + var x = req.url.split('/'); + if (x.length < 2) return parent.config.domains['']; + if (parent.config.domains[x[1].toLowerCase()]) return parent.config.domains[x[1].toLowerCase()]; + return parent.config.domains['']; + } + + function handleLogoutRequest(req, res) { + var domain = getDomain(req); + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); + // Destroy the user's session to log them out will be re-created next request + if (req.session.userid) { + var user = obj.users[req.session.userid] + obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, action: 'logout', msg: 'Account logout', domain: domain.id }) + } + req.session.destroy(function () { + res.redirect(domain.url); + }); + } + + function handleLoginRequest(req, res) { + var domain = getDomain(req); + obj.authenticate(req.body.username, req.body.password, domain, function (err, userid) { + if (userid) { + var user = obj.users[userid]; + + // Save login time + user.login = Date.now(); + obj.db.SetUser(user); + + // Regenerate session when signing in to prevent fixation + req.session.regenerate(function () { + // Store the user's primary key in the session store to be retrieved, or in this case the entire user object + // req.session.success = 'Authenticated as ' + user.name + 'click to logout. You may now access /restricted.'; + delete req.session.loginmode; + req.session.userid = userid; + req.session.domainid = domain.id; + req.session.currentNode = ''; + if (req.body.viewmode) { + req.session.viewmode = req.body.viewmode; + } + if (req.body.host) { + obj.db.GetAllType('node', function (err, docs) { + for (var i = 0; i < docs.length; i++) { + if (docs[i].name == req.body.host) { + req.session.currentNode = docs[i]._id; + break; + } + } + console.log("CurrentNode: " + req.session.currentNode); + // This redirect happens after finding node is completed + res.redirect(domain.url); + }); + } else { + res.redirect(domain.url); + } + }); + + obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, action: 'login', msg: 'Account login', domain: domain.id }) + } else { + delete req.session.loginmode; + req.session.error = 'Login failed, check username and password.'; + res.redirect(domain.url); + } + }); + } + + function handleCreateAccountRequest(req, res) { + var domain = getDomain(req); + if (domain.newAccounts == 0) { res.sendStatus(401); return; } + if (!req.body.username || !req.body.email || !req.body.password1 || !req.body.password2 || (req.body.password1 != req.body.password2) || req.body.username == '~') { + req.session.loginmode = 2; + req.session.error = 'Unable to create account.';; + res.redirect(domain.url); + } else { + // Check if user exists + if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) { + req.session.loginmode = 2; + req.session.error = 'Username already exists.'; + } else { + var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Date.now(), login: Date.now(), domain: domain.id }; + var usercount = 0; + for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } } + if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newAccounts == 2) { domain.newAccounts = 0; } } // If this is the first user, give the account site admin. + obj.users[user._id] = user; + req.session.userid = user._id; + req.session.domainid = domain.id; + // Create a user, generate a salt and hash the password + obj.hash(req.body.password1, function (err, salt, hash) { + if (err) throw err; + user.salt = salt; + user.hash = hash; + obj.db.SetUser(user); + }); + obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: user, action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id }) + } + res.redirect(domain.url); + } + } + + function handleDeleteAccountRequest(req, res) { + var domain = getDomain(req); + // Check if the user is logged and we have all required parameters + if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url); return; } + var user = obj.users[req.session.userid]; + if (!user) return; + + // Check if the password is correct + obj.authenticate(user.name, req.body.apassword1, domain, function (err, userid) { + var user = obj.users[userid]; + if (user) { + obj.db.Remove(user._id); + delete obj.users[user._id]; + req.session.destroy(function () { res.redirect(domain.url); }); + obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, action: 'accountremove', msg: 'Account removed', domain: domain.id }) + } else { + res.redirect(domain.url); + } + }); + } + + // Handle password changes + function handlePasswordChangeRequest(req, res) { + var domain = getDomain(req); + // Check if the user is logged and we have all required parameters + if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url); return; } + + // Update the password + obj.hash(req.body.apassword1, function (err, salt, hash) { + if (err) throw err; + var user = obj.users[req.session.userid]; + user.salt = salt; + user.hash = hash; + user.passchange = Date.now(); + obj.db.SetUser(user); + req.session.viewmode = 2; + res.redirect(domain.url); + obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, action: 'passchange', msg: 'Account password changed: ' + user.name, domain: domain.id }) + }); + } + + // Indicates that any request to "/" should render "default" or "login" depending on login state + function handleRootRequest(req, res) { + if (!obj.args) { res.sendStatus(500); return; } + var domain = getDomain(req); + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); + // Check if we have an incomplete domain name in the path + if (domain.id != '' && req.url.split('/').length == 2) { res.redirect(domain.url); return; } + if (obj.args.nousers == true) { + // If in single user mode, setup things here. + if (req.session && req.session.loginmode) { delete req.session.loginmode; } + req.session.userid = 'user/' + domain.id + '/~'; + req.session.domainid = domain.id; + req.session.currentNode = ''; + if (obj.users[req.session.userid] == undefined) { + // Create the dummy user ~ with impossible password + obj.users[req.session.userid] = { type: 'user', _id: req.session.userid, name: '~', email: '~', domain: domain.id, siteadmin: 0xFFFFFFFF }; + obj.db.SetUser(obj.users[req.session.userid]); + } + } else if (obj.args.user && (!req.session || !req.session.userid) && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) { + // If a default user is active, setup the session here. + if (req.session && req.session.loginmode) { delete req.session.loginmode; } + req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase(); + req.session.domainid = domain.id; + req.session.currentNode = ''; + } + // If a user is logged in, serve the default app, otherwise server the login app. + if (req.session && req.session.userid) { + if (req.session.domainid != domain.id) { req.session.destroy(function () { res.redirect(domain.url); }); return; } // Check is the session is for the correct domain + var viewmode = 1; + if (req.session.viewmode) { + viewmode = req.session.viewmode; + delete req.session.viewmode; + } + var currentNode = ''; + if (req.session.currentNode) { + currentNode = req.session.currentNode; + delete req.session.currentNode; + } + var user; + var logoutcontrol; + if (!obj.args.nousers) { + user = obj.users[req.session.userid] + logoutcontrol = 'Welcome ' + user.name + '.'; + } + var features = 0; + if (obj.args.wanonly == true) { features += 1; } // WAN-only mode + if (obj.args.lanonly == true) { features += 2; } // LAN-only mode + if (obj.args.nousers == true) { features += 4; } // Single user mode + if (domain.userQuota == -1) { features += 8; } // No server files mode + if ((!obj.args.user) && (!obj.args.nousers)) { logoutcontrol += ' Logout'; } // If a default user is in use or no user mode, don't display the logout button + res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.certificates.CommonName, serverPublicPort: args.port, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass }); + } else { + // Send back the login application + res.render(obj.path.join(__dirname, 'views/login'), { loginmode: req.session.loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newAccounts, serverDnsName: obj.certificates.CommonName, serverPublicPort: obj.args.port }); + } + } + + // Get the link to the root certificate if needed + function getRootCertLink() { + // TODO: This is not quite right, we need to check if the HTTPS certificate is issued from MeshCentralRoot, if so, add this download link. + if (obj.args.notls == undefined && obj.certificates.RootName.substring(0, 16) == 'MeshCentralRoot-') { return 'Root Certificate'; } + return ''; + } + + // Renter the terms of service. + function handleTermsRequest(req, res) { + var domain = getDomain(req); + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); + if (req.session && req.session.userid) { + if (req.session.domainid != domain.id) { req.session.destroy(function () { res.redirect(domain.url); }); return; } // Check is the session is for the correct domain + var user = obj.users[req.session.userid]; + res.render(obj.path.join(__dirname, 'views/terms'), { logoutControl: 'Welcome ' + user.name + '. Logout' }); + } else { + res.render(obj.path.join(__dirname, 'views/terms'), { title: domain.title, title2: domain.title2 }); + } + } + + // Returns the server root certificate encoded in base64 + function getRootCertBase64() { + var rootcert = obj.certificates.root.cert; + var i = rootcert.indexOf("-----BEGIN CERTIFICATE-----\r\n"); + if (i >= 0) { rootcert = rootcert.substring(i + 29); } + i = rootcert.indexOf("-----END CERTIFICATE-----"); + if (i >= 0) { rootcert = rootcert.substring(i, 0); } + return new Buffer(rootcert, 'base64').toString('base64'); + } + + // Returns the mesh server root certificate + function handleRootCertRequest(req, res) { + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=' + certificates.RootName + '.cer' }); + res.send(new Buffer(getRootCertBase64(), 'base64')); + } + + // Returns an mescript for Intel AMT configuration + function handleMeScriptRequest(req, res) { + if (req.query.type == 1) { + var filename = 'cira_setup.mescript'; + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=' + filename }); + var serverNameSplit = obj.certificates.CommonName.split('.'); + if ((serverNameSplit.length == 4) && (parseInt(serverNameSplit[0]) == serverNameSplit[0]) && (parseInt(serverNameSplit[1]) == serverNameSplit[1]) && (parseInt(serverNameSplit[2]) == serverNameSplit[2]) && (parseInt(serverNameSplit[3]) == serverNameSplit[3])) { + // Server name is an IPv4 address + var filepath = obj.parent.path.join(__dirname, 'public/scripts/cira_setup_script_ip.mescript'); + readEntireTextFile(filepath, function (data) { + if (data == null) { res.sendStatus(404); return; } + var scriptFile = JSON.parse(data); + + // Change a few things in the script + scriptFile.scriptBlocks[2].vars.CertBin.value = getRootCertBase64(); // Set the root certificate + scriptFile.scriptBlocks[3].vars.IP.value = obj.certificates.CommonName; // Set the server IPv4 address name + scriptFile.scriptBlocks[3].vars.ServerName.value = obj.certificates.CommonName; // Set the server certificate name + scriptFile.scriptBlocks[3].vars.Port.value = obj.args.mpsport; // Set the server MPS port + scriptFile.scriptBlocks[3].vars.username.value = req.query.meshid; // Set the username + scriptFile.scriptBlocks[3].vars.password.value = obj.args.mpspass ? obj.args.mpspass : 'A@xew9rt'; // Set the password + scriptFile.scriptBlocks[4].vars.AccessInfo1.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the primary server name:port to set periodic timer + //scriptFile.scriptBlocks[4].vars.AccessInfo2.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the secondary server name:port to set periodic timer + if (obj.args.ciralocalfqdn != undefined) { scriptFile.scriptBlocks[6].vars.DetectionStrings.value = obj.args.ciralocalfqdn; } // Set the environment detection local FQDN's + + // Compile the script + var scriptEngine = require('./amtscript.js').CreateAmtScriptEngine(); + var runscript = scriptEngine.script_blocksToScript(scriptFile.blocks, scriptFile.scriptBlocks); + scriptFile.mescript = new Buffer(scriptEngine.script_compile(runscript), 'binary').toString('base64'); + + // Send the script + res.send(new Buffer(JSON.stringify(scriptFile, null, ' '))); + }); + } else { + // Server name is a hostname + var filepath = obj.parent.path.join(__dirname, 'public/scripts/cira_setup_script_dns.mescript'); + readEntireTextFile(filepath, function (data) { + if (data == null) { res.sendStatus(404); return; } + var scriptFile = JSON.parse(data); + + // Change a few things in the script + scriptFile.scriptBlocks[2].vars.CertBin.value = getRootCertBase64(); // Set the root certificate + scriptFile.scriptBlocks[3].vars.FQDN.value = obj.certificates.CommonName; // Set the server DNS name + scriptFile.scriptBlocks[3].vars.Port.value = obj.args.mpsport; // Set the server MPS port + scriptFile.scriptBlocks[3].vars.username.value = req.query.meshid; // Set the username + scriptFile.scriptBlocks[3].vars.password.value = obj.args.mpspass ? obj.args.mpspass : 'A@xew9rt'; // Set the password + scriptFile.scriptBlocks[4].vars.AccessInfo1.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the primary server name:port to set periodic timer + //scriptFile.scriptBlocks[4].vars.AccessInfo2.value = obj.certificates.CommonName + ':' + obj.args.mpsport; // Set the secondary server name:port to set periodic timer + if (obj.args.ciralocalfqdn != undefined) { scriptFile.scriptBlocks[6].vars.DetectionStrings.value = obj.args.ciralocalfqdn; } // Set the environment detection local FQDN's + + // Compile the script + var scriptEngine = require('./amtscript.js').CreateAmtScriptEngine(); + var runscript = scriptEngine.script_blocksToScript(scriptFile.blocks, scriptFile.scriptBlocks); + scriptFile.mescript = new Buffer(scriptEngine.script_compile(runscript), 'binary').toString('base64'); + + // Send the script + res.send(new Buffer(JSON.stringify(scriptFile, null, ' '))); + }); + } + } + else if (req.query.type == 2) { + var filename = 'cira_cleanup.mescript'; + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=' + filename }); + var filepath = obj.parent.path.join(__dirname, 'public/scripts/cira_cleanup.mescript'); + readEntireTextFile(filepath, function (data) { + if (data == null) { res.sendStatus(404); return; } + res.send(new Buffer(data)); + }); + } + } + + // Handle user public file downloads + function handleDownloadUserFiles(req, res) { + var domain = getDomain(req), domainname = 'domain', spliturl = decodeURIComponent(req.path).split('/'), filename = ''; + if ((spliturl.length < 3) || (obj.common.IsFilenameValid(spliturl[2]) == false) || (domain.userQuota == -1)) { res.sendStatus(404); return; } + if (domain.id != '') { domainname = 'domain-' + domain.id; } + var path = obj.path.join(obj.filespath, domainname + "/user-" + spliturl[2] + "/Public"); + for (var i = 3; i < spliturl.length; i++) { if (obj.common.IsFilenameValid(spliturl[i]) == true) { path += '/' + spliturl[i]; filename = spliturl[i]; } else { res.sendStatus(404); return; } } + + var stat = null; + try { stat = obj.fs.statSync(path) } catch (e) { } + if ((stat != null) && ((stat.mode & 0x004000) == 0)) { + if (req.query.download == 1) { + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=\"' + filename + '\"' }); + try { res.sendFile(obj.path.resolve(__dirname, path)); } catch (e) { res.sendStatus(404); } + } else { + res.render(obj.path.join(__dirname, 'views/download'), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, message: "" + filename + ", " + stat.size + " byte" + ((stat.size < 2)?'':'s') + "." }); + } + } else { + res.render(obj.path.join(__dirname, 'views/download'), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, message: "Invalid file link, please check the URL again." }); + } + } + + // Download a file from the server + function handleDownloadFile(req, res) { + var domain = getDomain(req); + if ((req.query.link == undefined) || (req.session == undefined) || (req.session.userid == undefined) || (domain == null) || (domain.userQuota == -1)) { res.sendStatus(404); return; } + var user = obj.users[req.session.userid]; + if (user == undefined) { res.sendStatus(404); return; } + var file = getServerFilePath(user, domain, req.query.link); + if (file == null) { res.sendStatus(404); return; } + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=\"' + file.name + '\"' }); + try { res.sendFile(file.fullpath); } catch (e) { res.sendStatus(404); } + } + + // Upload a MeshCore.js file to the server + function handleUploadMeshCoreFile(req, res) { + var domain = getDomain(req); + if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; } + var user = obj.users[req.session.userid]; + if (user.siteadmin != 0xFFFFFFFF) { res.sendStatus(401); return; } // Check if we have mesh core upload rights (Full admin only) + + var multiparty = require('multiparty'); + var form = new multiparty.Form(); + form.parse(req, function (err, fields, files) { + if ((fields == undefined) || (fields.attrib == undefined) || (fields.attrib.length != 1)) { res.sendStatus(404); return; } + for (var i in files.files) { + var file = files.files[i]; + readEntireTextFile(file.path, function (data) { + if (data == null) return; + data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw) + sendMeshAgentCore(user, domain, fields.attrib[0], data); // Upload the core + try { obj.fs.unlinkSync(file.path); } catch (e) { } + }); + } + res.send(''); + }); + } + + // Upload a file to the server + function handleUploadFile(req, res) { + var domain = getDomain(req); + if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid) || (domain.userQuota == -1)) { res.sendStatus(401); return; } + var user = obj.users[req.session.userid]; + if ((user.siteadmin & 8) == 0) { res.sendStatus(401); return; } // Check if we have file rights + + var multiparty = require('multiparty'); + var form = new multiparty.Form(); + form.parse(req, function (err, fields, files) { + if ((fields == undefined) || (fields.link == undefined) || (fields.link.length != 1)) { res.sendStatus(404); return; } + var xfile = getServerFilePath(user, domain, decodeURIComponent(fields.link[0])); + if (xfile == null) { res.sendStatus(404); return; } + // Get total bytes in the path + var totalsize = readTotalFileSize(xfile.fullpath); + if (totalsize < xfile.quota) { // Check if the quota is not already broken + if (fields.name != undefined) { + // Upload method where all the file data is within the fields. + var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*'); + if ((names.length == sizes.length) && (types.length == datas.length) && (names.length == types.length)) { + for (var i = 0; i < names.length; i++) { + if (obj.common.IsFilenameValid(names[i]) == false) { res.sendStatus(404); return; } + var filedata = new Buffer(datas[i].split(',')[1], 'base64'); + if ((totalsize + filedata.length) < xfile.quota) { // Check if quota would not be broken if we add this file + obj.fs.writeFileSync(xfile.fullpath + '/' + names[i], filedata); + } + } + } + } else { + // More typical upload method, the file data is in a multipart mime post. + for (var i in files.files) { + var file = files.files[i], fpath = xfile.fullpath + '/' + file.originalFilename; + if (obj.common.IsFilenameValid(file.originalFilename) && ((totalsize + file.size) < xfile.quota)) { // Check if quota would not be broken if we add this file + obj.fs.rename(file.path, fpath); + } else { + try { obj.fs.unlinkSync(file.path); } catch (e) { } + } + } + } + } + res.send(''); + obj.parent.DispatchEvent([user._id], obj, 'updatefiles') // Fire an event causing this user to update this files + }); + } + + // Subscribe to all events we are allowed to receive + obj.subscribe = function (userid, target) { + var user = obj.users[userid]; + var subscriptions = [userid, 'server-global']; + if (user.siteadmin != undefined) { + if (user.siteadmin == 0xFFFFFFFF) subscriptions.push('*'); + if ((user.siteadmin & 2) != 0) subscriptions.push('server-users'); + } + if (user.links != undefined) { + for (var i in user.links) { subscriptions.push(i); } + } + obj.parent.RemoveAllEventDispatch(target); + obj.parent.AddEventDispatch(subscriptions, target); + return subscriptions; + } + + // Handle a web socket relay request + function handleRelayWebSocket(ws, req) { + var node, domain = getDomain(req); + // Check if this is a logged in user + if (!req.session || !req.session.userid) { return; } // Web socket attempt without login, disconnect. + if (req.session.domainid != domain.id) { console.log('ERR: Invalid domain'); return; } + var user = obj.users[req.session.userid]; + if (!user) { console.log('ERR: Not a user'); return; } + Debug(1, 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.'); + + // Fetch information about the target + obj.db.Get(req.query.host, function (err, docs) { + if (docs.length == 0) { console.log('ERR: Node not found'); return; } + node = docs[0]; + if (!node.intelamt) { console.log('ERR: Not AMT node'); return; } + + // Check if this user has permission to manage this computer + var meshlinks = user.links[node.meshid]; + if (!meshlinks || !meshlinks.rights) { console.log('ERR: Access denied (1)'); return; } + if ((meshlinks.rights & 8) == 0) { console.log('ERR: Access denied (2)'); return; } + + // Check what connectivity is available for this node + var state = parent.connectivityByNode[req.query.host]; + var conn = 0; + if (!state || state.connectivity == 0) { + conn = 4; // DEBUG: Allow local connections for now... change this later when we can monitor Intel AMT machines and confirm routing before connections. + //Debug(1, 'ERR: No routing possible (1)'); + //try { ws.close(); } catch (e) { } + //return; + } else { + conn = state.connectivity; + } + + // If Intel AMT CIRA connection is available, use it + if (((conn & 2) != 0) && (parent.mpsserver.ciraConnections[req.query.host] != undefined)) { + var ciraconn = parent.mpsserver.ciraConnections[req.query.host]; + + // Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS + var port = 16993; + //if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA + if (ciraconn.tag.boundPorts.indexOf(16992) >= 0) port = 16992; // RELEASE: Always use non-TLS mode if available within CIRA + if (req.query.p == 2) port += 2; + + // Setup a new CIRA channel + if ((port == 16993) || (port == 16995)) { + // Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why ) + var ser = new SerialTunnel(); + var chnl = parent.mpsserver.SetupCiraChannel(ciraconn, port); + + // let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl) + // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write + ser.forwardwrite = function (msg) { + // Convert a buffer into a string, "msg = msg.toString('ascii');" does not work + // TLS ---> CIRA + var msg2 = ""; + for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); } + chnl.write(msg2); + }; + + // When APF tunnel return something, update SerialTunnel buffer + chnl.onData = function (ciraconn, data) { + // CIRA ---> TLS + Debug(3, 'Relay TLS CIRA data', data.length); + if (data.length > 0) { try { ser.updateBuffer(Buffer.from(data, 'binary')); } catch (e) { } } + } + + // Handke CIRA tunnel state change + chnl.onStateChange = function (ciraconn, state) { + Debug(2, 'Relay TLS CIRA state change', state); + if (state == 0) { try { ws.close(); } catch (e) { } } + } + + // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF + var TLSSocket = require('tls').TLSSocket; + var tlsock = new TLSSocket(ser, { secureProtocol: 'SSLv23_method', rejectUnauthorized: false }); // TLSv1_2_method + tlsock.on('error', function (err) { Debug(1, "CIRA TLS Connection Error ", err); }); + tlsock.on('secureConnect', function () { Debug(2, "CIRA Secure TLS Connection"); }); + + // Decrypted tunnel from TLS communcation to be forwarded to websocket + tlsock.on('data', function (data) { + // AMT/TLS ---> WS + try { + var data2 = ""; + for (var i = 0; i < data.length; i++) { data2 += String.fromCharCode(data[i]); } + if (ws.interceptor) { data2 = ws.interceptor.processAmtData(data2); } // Run data thru interceptor + ws.send(data2); + } catch (e) { } + }); + + // If TLS is on, forward it through TLSSocket + ws.forwardclient = tlsock; + ws.forwardclient.xtls = 1; + } else { + // Without TLS + ws.forwardclient = parent.mpsserver.SetupCiraChannel(ciraconn, port); + ws.forwardclient.xtls = 0; + } + + // When data is received from the web socket, forward the data into the associated CIRA cahnnel. + // If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway. + ws.on('message', function (msg) { + // WS ---> AMT/TLS + // Convert a buffer into a string, "msg = msg.toString('ascii');" does not work + var msg2 = ""; + for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); } + if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor + if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg2, 'binary')); } else { ws.forwardclient.write(msg2); } + }); + + // If error, do nothing + ws.on('error', function (err) { console.log(err); }); + + // If the web socket is closed, close the associated TCP connection. + ws.on('close', function (req) { + Debug(1, 'Websocket relay closed.'); + if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS + }); + + ws.forwardclient.onStateChange = function (ciraconn, state) { + Debug(2, 'Relay CIRA state change', state); + if (state == 0) { try { ws.close(); } catch (e) { } } + } + + ws.forwardclient.onData = function (ciraconn, data) { + Debug(3, 'Relay CIRA data', data.length); + if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor + if (data.length > 0) { try { ws.send(data); } catch (e) { } } // TODO: Add TLS support + } + + ws.forwardclient.onSendOk = function (ciraconn) { + // TODO: Flow control? (Dont' really need it with AMT, but would be nice) + //console.log('onSendOk'); + } + + // Fetch Intel AMT credentials & Setup interceptor + if (req.query.p == 1) { + Debug(3, 'INTERCEPTOR1', { host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); + ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); + } + else if (req.query.p == 2) { + Debug(3, 'INTERCEPTOR2', { user: node.intelamt.user, pass: node.intelamt.pass }); + ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass }); + } + + return; + } + + // If Intel AMT direct connection is possible, option a direct socket + if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port. + Debug(2, 'Opening relay TCP socket connection to ' + req.query.host + '.'); + + // Compute target port + var port = 16992; + if (node.intelamt.tls > 0) port = 16993; // This is a direct connection, use TLS when possible + if (req.query.p == 2) port += 2; + + if (node.intelamt.tls == 0) { + // If this is TCP (without TLS) set a normal TCP socket + ws.forwardclient = new obj.net.Socket(); + ws.forwardclient.setEncoding('binary'); + ws.forwardclient.xstate = 0; + ws.forwardclient.forwardwsocket = ws; + } else { + // If TLS is going to be used, setup a TLS socket + ws.forwardclient = obj.tls.connect(port, node.host, { secureProtocol: 'TLSv1_method', rejectUnauthorized: false }, function () { + // The TLS connection method is the same as TCP, but located a bit differently. + Debug(2, 'TLS connected to ' + node.host + ':' + port + '.'); + if (ws.xpendingdata && ws.xpendingdata.length > 0) { + //console.log('TLS sending pending data: ' + ws.xpendingdata.length); + ws.forwardclient.write(ws.xpendingdata); + delete ws.xpendingdata; + } + ws.forwardclient.xstate = 1; + }); + ws.forwardclient.setEncoding('binary'); + ws.forwardclient.xstate = 0; + ws.forwardclient.forwardwsocket = ws; + ws.xpendingdata = ''; + } + + // When data is received from the web socket, forward the data into the associated TCP connection. + // If the TCP connection is pending, buffer up the data until it connects. + ws.on('message', function (msg) { + // Debug(1, 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes'); // DEBUG + // Convert a buffer into a string, "msg = msg.toString('ascii');" does not work + var msg2 = ""; + for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); } + if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor + if (ws.forwardclient == undefined || ws.forwardclient.xstate == 0) { + // TCP connection is pending, buffer up the data. + if (ws.xpendingdata) { ws.xpendingdata += msg2; } else { ws.xpendingdata = msg2; } + } else { + // Forward data to the associated TCP connection. + ws.forwardclient.write(new Buffer(msg2, "ascii")); + } + }); + + // If error, do nothing + ws.on('error', function (err) { console.log(err); }); + + // If the web socket is closed, close the associated TCP connection. + ws.on('close', function (req) { + Debug(1, 'Closing relay web socket connection to ' + ws.upgradeReq.query.host + '.'); + if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } } + }); + + // When we receive data on the TCP connection, forward it back into the web socket connection. + ws.forwardclient.on('data', function (data) { + //Debug(1, 'TCP relay data from ' + node.host + ', ' + data.length + ' bytes.'); // DEBUG + if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor + try { ws.send(data); } catch (e) { } + }); + + // If the TCP connection closes, disconnect the associated web socket. + ws.forwardclient.on('close', function () { + Debug(1, 'TCP relay disconnected from ' + node.host + '.'); + try { ws.close(); } catch (e) { } + }); + + // If the TCP connection causes an error, disconnect the associated web socket. + ws.forwardclient.on('error', function (err) { + Debug(1, 'TCP relay error from ' + node.host + ': ' + err.errno); + try { ws.close(); } catch (e) { } + }); + + // Fetch Intel AMT credentials & Setup interceptor + if (req.query.p == 1) { ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass }); } + else if (req.query.p == 2) { ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass }); } + + if (node.intelamt.tls == 0) { + // A TCP connection to Intel AMT just connected, send any pending data and start forwarding. + ws.forwardclient.connect(port, node.host, function () { + Debug(1, 'TCP relay connected to ' + node.host + ':' + port + '.'); + if (ws.xpendingdata && ws.xpendingdata.length > 0) { + //console.log('TCP sending pending data: ' + ws.xpendingdata.length); + ws.forwardclient.write(new Buffer(ws.xpendingdata, "ascii")); + delete ws.xpendingdata; + } + ws.forwardclient.xstate = 1; + }); + } + return; + } + + }); + } + + // Handle the web socket echo request, just echo back the data sent + function handleEchoWebSocket(ws, req) { + // When data is received from the web socket, echo it back + ws.on('message', function (data) { + var cmd = data.toString('utf8'); + if (cmd == 'close') { + try { ws.close(); } catch (e) { console.log(e); } + } else { + try { ws.send(data); } catch (e) { console.log(e); } + } + }); + + // If error, do nothing + ws.on('error', function (err) { console.log(err); }); + + // If closed, do nothing + ws.on('close', function (req) { }); + } + + // Read the folder and all sub-folders and serialize that into json. + function readFilesRec(path) { + var r = {}, dir = obj.fs.readdirSync(path); + for (var i in dir) { + var f = { t: 3, d: 111 }; + var stat = obj.fs.statSync(path + '/' + dir[i]) + if ((stat.mode & 0x004000) == 0) { f.s = stat.size; f.d = stat.mtime.getTime(); } else { f.t = 2; f.f = readFilesRec(path + '/' + dir[i]); } + r[dir[i]] = f; + } + return r; + } + + // Get the total size of all files in a folder and all sub-folders + function readTotalFileSize(path) { + var r = 0, dir = obj.fs.readdirSync(path); + for (var i in dir) { + var stat = obj.fs.statSync(path + '/' + dir[i]) + if ((stat.mode & 0x004000) == 0) { r += stat.size; } else { r += readTotalFileSize(path + '/' + dir[i]); } + } + return r; + } + + // Delete a folder and all sub items. + function deleteFolderRec(path) { + if (obj.fs.existsSync(path) == false) return; + obj.fs.readdirSync(path).forEach(function (file, index) { + var pathx = path + "/" + file; + if (obj.fs.lstatSync(pathx).isDirectory()) { deleteFolderRec(pathx); } else { obj.fs.unlinkSync(pathx); } + }); + obj.fs.rmdirSync(path); + }; + + // Indicates we want to handle websocket requests on "/control.ashx". + function handleControlRequest(ws, req) { + var domain = getDomain(req); + try { + // Check if the user is logged in + if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) { try { ws.close(); } catch (e) { } return; } + req.session.ws = ws; // Associate this websocket session with the web session + req.session.ws.userid = req.session.userid; + req.session.ws.domainid = domain.id; + var user = obj.users[req.session.userid]; + if (user == undefined || user == null) { try { ws.close(); } catch (e) { } return; } + + // Add this web socket session to session list + ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2); + obj.wssessions2[ws.sessionId] = ws; + if (!obj.wssessions[user._id]) { obj.wssessions[user._id] = [ws]; } else { obj.wssessions[user._id].push(ws); } + obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[user._id].length, nolog: 1, domain: domain.id }) + + // Handle events + ws.HandleEvent = function (source, event) { + if (!event.domain || event.domain == domain.id) { + try { + if (event == 'close') { req.session.destroy(); ws.close(); } + else if (event == 'resubscribe') { user.subscriptions = obj.subscribe(user._id, ws); } + else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); } + else { ws.send(JSON.stringify({ action: 'event', event: event })); } + } catch (e) { } + } + } + + // Subscribe to events + user.subscriptions = obj.subscribe(user._id, ws); + + // When data is received from the web socket + ws.on('message', function (msg) { + var user = obj.users[req.session.userid]; + var command = JSON.parse(msg.toString('utf8')) + switch (command.action) { + case 'meshes': + { + // Request a list of all meshes this user as rights to + var docs = []; + for (var i in user.links) { if (obj.meshes[i]) { docs.push(obj.meshes[i]); } } + ws.send(JSON.stringify({ action: 'meshes', meshes: docs })); + break; + } + case 'nodes': + { + // Request a list of all meshes this user as rights to + var links = []; + for (var i in user.links) { links.push(i); } + + // Request a list of all nodes + obj.db.GetAllTypeNoTypeFieldMeshFiltered(links, domain.id, 'node', function (err, docs) { + var r = {}; + for (var i in docs) { + // Add the connection state + var state = parent.connectivityByNode[docs[i]._id]; + if (state) { + docs[i].conn = state.connectivity; + docs[i].pwr = state.powerState; + if ((state.connectivity & 1) != 0) { var agent = obj.wsagents[docs[i]._id]; if (agent != undefined) { docs[i].agct = agent.connectTime; } } + if ((state.connectivity & 2) != 0) { var cira = obj.parent.mpsserver.ciraConnections[docs[i]._id]; if (cira != undefined) { docs[i].cict = cira.tag.connectTime; } } + } + // Compress the meshid's + var meshid = docs[i].meshid; + if (!r[meshid]) { r[meshid] = []; } + delete docs[i].meshid; + r[meshid].push(docs[i]); + } + ws.send(JSON.stringify({ action: 'nodes', nodes: r })); + }); + break; + } + case 'powertimeline': + { + // Query the database for the power timeline for a given node + // The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ] + obj.db.getPowerTimeline(command.nodeid, function (err, docs) { + if (err == null && docs.length > 0) { + var timeline = [], time = null, previousPower; + for (var i in docs) { + var doc = docs[i]; + if (time == null) { + // First element + time = doc.time; + if (doc.oldPower) { timeline.push(doc.oldPower); } else { timeline.push(0); } + timeline.push(time); + timeline.push(doc.power); + previousPower = doc.power; + } else { + // Delta element + if ((previousPower != doc.power) && ((doc.time - time) > 60000)) { // To boost speed, any blocks less than a minute get approximated. + // Create a new timeline + timeline.push(doc.time - time); + timeline.push(doc.power); + time = doc.time; + previousPower = doc.power; + } else { + // Extend the previous timeline + if ((timeline.length >= 6) && (timeline[timeline.length - 3] == doc.power)) { // We can merge the block with the previous block + timeline[timeline.length - 4] += (timeline[timeline.length - 2] + (doc.time - time)); + timeline.pop(); + timeline.pop(); + } else { // Extend the last block in the timeline + timeline[timeline.length - 2] += (doc.time - time); + timeline[timeline.length - 1] = doc.power; + } + time = doc.time; + previousPower = doc.power; + } + } + } + ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline })); + } else { + // No records found, send current state if we have it + var state = obj.parent.connectivityByNode[command.nodeid]; + if (state != undefined) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState] })); } + } + }); + break; + } + case 'files': + { + // Send the full list of server files to the browser app + if ((user.siteadmin & 8) != 0) { updateUserFiles(user, ws, domain); } + break; + } + case 'fileoperation': + { + // Check permissions + if ((user.siteadmin & 8) != 0) { + // Perform a file operation (Create Folder, Delete Folder, Delete File...) + if ((command.path != undefined) && (typeof command.path == 'object') && command.path.length > 0) { + var rootfolder = command.path[0]; + var rootfoldersplit = rootfolder.split('/'), domainx = 'domain'; + if (rootfoldersplit[1].length > 0) domainx = 'domain-' + rootfoldersplit[1]; + var path = obj.path.join(obj.filespath, domainx + "/" + rootfoldersplit[0] + "-" + rootfoldersplit[2]); + for (var i = 1; i < command.path.length; i++) { if (obj.common.IsFilenameValid(command.path[i]) == false) { path = null; break; } path += ("/" + command.path[i]); } + 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 + + obj.parent.DispatchEvent([user._id], obj, 'updatefiles') // Fire an event causing this user to update this files + } + } + break; + } + case 'msg': + { + // Route a message. + // This this command has a nodeid, that is the target. + if (command.nodeid != undefined) { + 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.wsagents[command.nodeid]; + if (agent != undefined) { + // Check if we have permission to send a message to that node + var rights = user.links[agent.dbMeshKey]; + if (rights != undefined || ((rights & 16) != 0)) { // TODO: 16 is console permission, may need more gradular permission checking + 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)); + } + } + } + } + break; + } + case 'events': + { + // Send the list of events for this session + obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs })); }); + break; + } + case 'clearevents': + { + // Delete all events + if (user.siteadmin != 0xFFFFFFFF) break; + obj.db.RemoveAllEvents(domain.id); + obj.parent.DispatchEvent(['*', 'server-global'], obj, { action: 'clearevents', nolog: 1, domain: domain.id }) + break; + } + case 'users': + { + // Request a list of all users + if ((user.siteadmin & 2) == 0) break; + var docs = []; + for (var i in obj.users) { if ((obj.users[i].domain == domain.id) && (obj.users[i].name != '~')) { docs.push(obj.users[i]); } } + ws.send(JSON.stringify({ action: 'users', users: docs })); + break; + } + case 'wssessioncount': + { + // Request a list of all web socket session count + if ((user.siteadmin & 2) == 0) break; + var wssessions = {}; + for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } } + ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions })); + break; + } + case 'deleteuser': + { + // Delete a user account + if ((user.siteadmin & 2) == 0) break; + var delusername = command.username, deluserid = command.userid, deluser = obj.users[deluserid]; + if ((deluser.siteadmin != undefined) && (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 { + var deluserpath = getServerRootFilePath(deluser); + if (deluserpath != null) { deleteFolderRec(deluserpath); } + } catch (e) { } + + obj.db.Remove(deluserid); + delete obj.users[deluserid]; + obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: delusername, action: 'accountremove', msg: 'Account removed', domain: domain.id }) + obj.parent.DispatchEvent([deluserid], obj, 'close'); + + break; + } + case 'adduser': + { + // Add a new user account + if ((user.siteadmin & 2) == 0) break; + var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(); + if (newusername == '~') break; // This is a reserved user name + if (!obj.users[newuserid]) { + var newuser = { type: 'user', _id: newuserid, name: newusername, email: command.email, creation: Date.now(), domain: domain.id }; + obj.users[newuserid] = newuser; + // Create a user, generate a salt and hash the password + obj.hash(command.pass, function (err, salt, hash) { + if (err) throw err; + newuser.salt = salt; + newuser.hash = hash; + obj.db.SetUser(newuser); + var newuser2 = obj.common.Clone(newuser); + if (newuser2.subscriptions) { delete newuser2.subscriptions; } + if (newuser2.salt) { delete newuser2.salt; } + if (newuser2.hash) { delete newuser2.hash; } + obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: newusername, account: newuser2, action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id }) + }); + } + break; + } + case 'edituser': + { + // Edit a user account, may involve changing email or administrator permissions + if (((user.siteadmin & 2) != 0) || (user.name == command.name)) { + var chguserid = 'user/' + domain.id + '/' + command.name.toLowerCase(), chguser = obj.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 == undefined) { delete chguser.quota; } change = 1; } + if ((user.siteadmin == 0xFFFFFFFF) && (command.siteadmin != undefined) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1 } + if (change == 1) { + obj.db.Set(chguser); + obj.parent.DispatchEvent([chguser._id], obj, 'resubscribe'); + var chguser2 = obj.common.Clone(chguser); + delete chguser2.salt; + delete chguser2.hash; + obj.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: chguser2, action: 'accountchange', msg: 'Account changed: ' + command.name, domain: domain.id }) + } + } + } + break; + } + case 'serverversion': + { + // Check the server version + if ((user.siteadmin & 16) == 0) break; + obj.parent.getLatestServerVersion(function (currentVersion, latestVersion) { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); }); + break; + } + case 'createmesh': + { + // Create mesh + // TODO: Right now, we only create type 1 Agent-less Intel AMT mesh, or type 2 Agent mesh + if ((command.meshtype == 1) || (command.meshtype == 2)) { + // Create a type 1 agent-less Intel AMT mesh. + obj.crypto.randomBytes(32, function (err, buf) { + var meshid = 'mesh/' + domain.id + '/' + buf.toString('hex').toUpperCase(); + var links = {} + links[user._id] = { name: user.name, rights: 0xFFFFFFFF }; + var mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links }; + obj.db.Set(mesh); + obj.meshes[meshid] = mesh; + obj.parent.AddEventDispatch([meshid], ws); + if (user.links == undefined) user.links = {}; + user.links[meshid] = { rights: 0xFFFFFFFF }; + user.subscriptions = obj.subscribe(user._id, ws); + obj.db.SetUser(user); + obj.parent.DispatchEvent(['*', meshid, user._id], obj, { 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 }) + }); + } + break; + } + case 'deletemesh': + { + // Delete a mesh and all computers within it + obj.db.Get(command.meshid, function (err, meshes) { + if (meshes.length != 1) return; + var mesh = meshes[0]; + + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || 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 + + // Fire the removal event first, because after this, the event will not route + obj.parent.DispatchEvent(['*', command.meshid], obj, { etype: 'mesh', username: user.name, meshid: command.meshid, name: command.meshname, action: 'deletemesh', msg: 'Mesh deleted: ' + command.meshname, domain: domain.id }) + + // Remove all user links to this mesh + for (var i in meshes) { + var links = meshes[i].links; + for (var j in links) { + var xuser = obj.users[j]; + delete xuser.links[meshes[i]._id]; + obj.db.Set(xuser); + obj.parent.DispatchEvent([xuser._id], obj, 'resubscribe'); + } + } + + // Delete all files on the server for this mesh + try { + var meshpath = getServerRootFilePath(mesh); + if (meshpath != null) { deleteFolderRec(meshpath); } + } catch (e) { } + + obj.parent.RemoveEventDispatchId(command.meshid); // Remove all subscriptions to this mesh + obj.db.RemoveMesh(command.meshid); // Remove mesh from database + delete obj.meshes[command.meshid]; // Remove mesh from memory + }); + break; + } + case 'editmesh': + { + // Change the name or description of a mesh + var mesh = obj.meshes[command.meshid], change = ''; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || ((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 != undefined && 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.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': + { + // Check if the user exists + var newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(), newuser = obj.users[newuserid]; + if (newuser == undefined) { + // TODO: Send error back, user not found. + break; + } + + // Get the mesh + var mesh = obj.meshes[command.meshid], change = ''; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || ((mesh.links[user._id].rights & 2) == 0)) return; + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + + // Add mesh to user + if (newuser.links == undefined) newuser.links = {}; + newuser.links[command.meshid] = { rights: command.meshadmin }; + obj.db.Set(newuser); + obj.parent.DispatchEvent([newuser._id], obj, 'resubscribe'); + + // Add a user to the mesh + mesh.links[newuserid] = { name: command.username, rights: command.meshadmin }; + obj.db.Set(mesh); + + // Notify mesh change + var change = 'Added user ' + command.username + ' to mesh ' + mesh.name; + obj.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 }) + } + break; + } + case 'removemeshuser': + { + 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 + var deluserid = command.userid, deluser = obj.users[deluserid]; + if (deluser == undefined) { + // TODO: Send error back, user not found. + break; + } + + // Get the mesh + var mesh = obj.meshes[command.meshid]; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || ((mesh.links[user._id].rights & 2) == 0)) return; + + // Remove mesh from user + if (deluser.links != undefined && deluser.links[command.meshid] != undefined) { + var delmeshrights = deluser.links[command.meshid].rights; + if ((delmeshrights == 0xFFFFFFFF) && (mesh.links[user._id].rights != 0xFFFFFFFF)) return; // A non-admin can't kick out an admin + delete deluser.links[command.meshid]; + obj.db.Set(deluser); + obj.parent.DispatchEvent([deluser._id], obj, 'resubscribe'); + } + + // Remove user from the mesh + if (mesh.links[command.userid] != undefined) { + delete mesh.links[command.userid]; + obj.db.Set(mesh); + } + + // Notify mesh change + var change = 'Removed user ' + deluser.name + ' from mesh ' + mesh.name; + obj.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 }) + } + break; + } + case 'addamtdevice': + { + if (obj.args.wanonly == true) return; // This is a WAN-only server, local Intel AMT computers can't be added + + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + + // Get the mesh + var mesh = obj.meshes[command.meshid]; + if (mesh) { + if (mesh.mtype != 1) return; // This operation is only allowed for mesh type 1, Intel AMT agentless mesh. + + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || ((mesh.links[user._id].rights & 4) == 0)) return; + + // Create a new nodeid + obj.crypto.randomBytes(32, function (err, buf) { + // create the new node + var nodeid = 'node/' + domain.id + '/' + buf.toString('hex').toUpperCase(); + var device = { type: 'node', mtype: 1, _id: nodeid, meshid: command.meshid, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: parseInt(command.amttls) } }; + obj.db.Set(device); + + // Event the new node + var device2 = obj.common.Clone(device); + delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this. + var change = 'Added device ' + command.devicename + ' to mesh ' + mesh.name; + obj.parent.DispatchEvent(['*', command.meshid], obj, { etype: 'node', username: user.name, action: 'addnode', node: device2, msg: change, domain: domain.id }) + }); + } + break; + } + case 'scanamtdevice': + { + if (obj.args.wanonly == true) return; // This is a WAN-only server, this type of scanning is not allowed. + + // Ask the RMCP scanning to scan a range of IP addresses + if (obj.parent.amtScanner) { + if (obj.parent.amtScanner.performRangeScan(ws.userid, command.range) == false) { + obj.parent.DispatchEvent(['*', ws.userid], obj, { action: 'scanamtdevice', range: command.range, results: null, nolog: 1 }); + } + } + break; + } + case 'removedevices': + { + for (var i in command.nodeids) { + var nodeid = command.nodeids[i]; + if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + + // Get the device + obj.db.Get(nodeid, function (err, nodes) { + if (nodes.length != 1) return; + var node = nodes[0]; + + // Get the mesh for this device + var mesh = obj.meshes[node.meshid]; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || ((mesh.links[user._id].rights & 4) == 0)) return; + + // Delete this node including network interface information and events + obj.db.Remove(node._id); + obj.db.Remove('if' + node._id); + + // Event node deletion + var change = 'Removed device ' + node.name + ' from mesh ' + mesh.name; + obj.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.connectivityByNode[command.nodeid]; + if ((state != undefined) && (state.connectivity != undefined)) { + if ((state.connectivity & 1) != 0) { obj.wsagents[command.nodeid].close(); } // Disconnect mesh agent + if ((state.connectivity & 2) != 0) { obj.parent.mpsserver.close(obj.parent.mpsserver.ciraConnections[command.nodeid]); } // Disconnect CIRA connection + } + } + }); + } + + break; + } + case 'wakedevices': + { + // 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 ((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) { + if (nodes.length != 1) return; + var node = nodes[0]; + + // Get the mesh for this device + var mesh = obj.meshes[node.meshid]; + if (mesh) { + + // Check if this user has rights to do this + if (mesh.links[user._id] != undefined && ((mesh.links[user._id].rights & 64) != 0)) { + + // Get the device interface information + obj.db.Get('if' + node._id, function (err, nodeifs) { + if (nodeifs.length == 1) { + var nodeif = nodeifs[0]; + var macs = []; + for (var i in nodeif.netif) { if (nodeif.netif[i].mac) { macs.push(nodeif.netif[i].mac); } } + + // Have the server send a wake-on-lan packet (Will not work in WAN-only) + if (obj.parent.meshScanner != null) { obj.parent.meshScanner.wakeOnLan(macs); wakeActions++; } + + // Get the list of mesh this user as access to + var targetMeshes = []; + for (var i in user.links) { targetMeshes.push(i); } + + // Go thru all the connected agents and send wake-on-lan on all the ones in the target mesh list + for (var i in obj.wsagents) { + var agent = obj.wsagents[i]; + if ((targetMeshes.indexOf(agent.dbMeshKey) >= 0) && (agent.authenticated == 2)) { + //console.log('Asking agent ' + agent.dbNodeKey + ' to wake ' + macs.join(',')); + agent.send(JSON.stringify({ action: 'wakeonlan', macs: macs })); + wakeActions++; + } + } + } + }); + + } + } + }); + } + // Confirm we may be doing something (TODO) + ws.send(JSON.stringify({ action: 'wakedevices' })); + } + + break; + } + case 'getnetworkinfo': + { + // Argument validation + if ((command.nodeid == undefined) || (typeof command.nodeid != 'string') || (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) { + if (nodes.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; } + var node = nodes[0]; + + // Get the mesh for this device + var mesh = obj.meshes[node.meshid]; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || (mesh.links[user._id].rights == 0)) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; } + + // Get network information about this node + obj.db.Get('if' + command.nodeid, function (err, netinfos) { + if (netinfos.length != 1) { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, netif: null })); return; } + var netinfo = netinfos[0]; + ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, updateTime: netinfo.updateTime, netif: netinfo.netif })); + }); + } + }); + break; + } + case 'changedevice': + { + // Argument validation + if ((command.nodeid == undefined) || (typeof command.nodeid != 'string') || (command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + + // Change the device + obj.db.Get(command.nodeid, function (err, nodes) { + if (nodes.length != 1) return; + var node = nodes[0]; + + // Get the mesh for this device + var mesh = obj.meshes[node.meshid]; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == undefined || ((mesh.links[user._id].rights & 4) == 0)) return; + + // Ready the node change event + var changes = [], change = 0, event = { etype: 'node', username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id }; + event.msg = ": "; + + // 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'); } + if (command.host && (command.host != node.host)) { change = 1; node.host = command.host; changes.push('host'); } + if (command.desc != undefined && (command.desc != node.desc)) { change = 1; node.desc = command.desc; changes.push('description'); } + if ((command.intelamt != undefined) && (node.intelamt != undefined)) { + if ((command.intelamt.user != undefined) && (command.intelamt.pass != undefined) && ((command.intelamt.user != node.intelamt.user) || (command.intelamt.pass != node.intelamt.pass))) { change = 1; node.intelamt.user = command.intelamt.user; node.intelamt.pass = command.intelamt.pass; changes.push('Intel AMT credentials'); } + if (command.intelamt.tls && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); } + } + + if (change == 1) { + // Save the node + obj.db.Set(node); + + // Event the node change + event.msg = 'Changed device ' + node.name + ' from mesh ' + mesh.name + ': ' + changes.join(', '); + var node2 = obj.common.Clone(node); + if (node2.intelamt && node2.intelamt.pass) delete node2.intelamt.pass; // Remove the Intel AMT password before eventing this. + event.node = node2; + obj.parent.DispatchEvent(['*', node.meshid], obj, event); + } + } + }); + break; + } + case 'uploadagentcore': + { + if (user.siteadmin != 0xFFFFFFFF) break; + if (command.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 + parent.updateMeshCore(function () { sendMeshAgentCore(user, domain, command.nodeid, '*'); }); + } else { + // Send a mesh agent core to the mesh agent + var file = getServerFilePath(user, domain, command.path); + if (file != null) { + readEntireTextFile(file.fullpath, function (data) { + if (data != null) { + data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw) + sendMeshAgentCore(user, domain, command.nodeid, data); + } + }) + } + } + } else { + // Clear the mesh agent core on the mesh agent + sendMeshAgentCore(user, domain, command.nodeid, null); + } + break; + } + case 'agentdisconnect': + { + // Force mesh agent disconnection + forceMeshAgentDisconnect(user, domain, command.nodeid); + break; + } + case 'close': + { + // Close the web socket session + if (req.session && req.session.ws && req.session.ws == ws) delete req.session.ws; + try { ws.close(); } catch (e) { } + break; + } + } + }); + + // If error, do nothing + ws.on('error', function (err) { console.log(err); }); + + // If the web socket is closed + ws.on('close', function (req) { + obj.parent.RemoveAllEventDispatch(ws); + if (req.session && req.session.ws && req.session.ws == ws) { delete req.session.ws; } + if (obj.wssessions2[ws.sessionId]) { delete obj.wssessions2[ws.sessionId]; } + if (obj.wssessions[ws.userid]) { + var i = obj.wssessions[ws.userid].indexOf(ws); + if (i >= 0) { + obj.wssessions[ws.userid].splice(i, 1); + var user = obj.users[ws.userid]; + if (user) { obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[ws.userid].length, nolog: 1, domain: domain.id }) } + if (obj.wssessions[ws.userid].length == 0) { delete obj.wssessions[ws.userid]; } + } + } + }); + + // Send user information to web socket, this is the first thing we send + var userinfo = obj.common.Clone(obj.users[req.session.userid]); + delete userinfo.salt; + delete userinfo.hash; + ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo })); + + // Next, send server information + if (obj.args.notls == true) { + ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: { name: obj.certificates.CommonName, mpsport: obj.args.mpsport, mpspass: obj.args.mpspass, port: obj.args.port, https: false } })); + } else { + ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: { name: obj.certificates.CommonName, mpsport: obj.args.mpsport, mpspass: obj.args.mpspass, redirport: obj.args.redirport, port: obj.args.port, https: true } })); + } + } catch (e) { console.log(e); } + } + + // Handle Intel AMT events + // To subscribe, add "http://server:port/amtevents.ashx" to Intel AMT subscriptions. + obj.handleAmtEventRequest = function (req, res) { + var domain = getDomain(req); + try { + if (req.headers['authorization']) { + var authstr = req.headers['authorization']; + if (authstr.substring(0, 7) == "Digest ") { + var auth = obj.common.parseNameValueList(obj.common.quoteSplit(authstr.substring(7))); + if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA256', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) { + + // Read the data, we need to get the arg field + var eventData = ''; + req.on('data', function (chunk) { eventData += chunk; }); + req.on('end', function () { + + // Completed event read, let get the argument that must contain the nodeid + var i = eventData.indexOf('ID | Description | Link | Size | SHA256 |
---|---|---|---|---|
' + agentinfo.id + ' | ' + agentinfo.desc + ' | '; + response += '' + agentinfo.rname + ' | '; + response += '' + agentinfo.size + ' | ' + agentinfo.hash + ' |