diff --git a/agents/MeshCommander-Small.gz b/agents/MeshCommander-Small.gz new file mode 100644 index 00000000..ebbcae97 Binary files /dev/null and b/agents/MeshCommander-Small.gz differ diff --git a/agents/webapppush.js b/agents/webapppush.js new file mode 100644 index 00000000..4e076ce1 --- /dev/null +++ b/agents/webapppush.js @@ -0,0 +1,172 @@ +/* +Copyright 2017 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +// Polyfill String.endsWith +if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, position) { + var subjectString = this.toString(); + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } + position -= searchString.length; + var lastIndex = subjectString.lastIndexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; +} + +// Replace a string with a number if the string is an exact number +function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) == x)) { x = parseInt(x); } return x; } + +// Convert decimal to hex +function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); } + +// Convert a raw string to a hex string +function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; } + +// Convert a buffer into a string +function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; } + +// Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster +function hex2rstr(d) { + if (typeof d != "string" || d.length == 0) return ''; + var r = '', m = ('' + d).match(/../g), t; + while (t = m.shift()) r += String.fromCharCode('0x' + t); + return r +} + +// Convert an object to string with all functions +function objToString(x, p, ret) { + if (ret == undefined) ret = ''; + if (p == undefined) p = 0; + if (x == null) { return '[null]'; } + if (p > 8) { return '[...]'; } + if (x == undefined) { return '[undefined]'; } + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; } + if (typeof x == 'buffer') { return '[buffer]'; } + if (typeof x != 'object') { return x; } + var r = '{' + (ret ? '\r\n' : ' '); + for (var i in x) { r += (addPad(p + 2, ret) + i + ': ' + objToString(x[i], p + 2, ret) + (ret ? '\r\n' : ' ')); } + return r + addPad(p, ret) + '}'; +} + +// Return p number of spaces +function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } + +// Split a string taking into account the quoats. Used for command line parsing +function splitArgs(str) { + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi; + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null); + return myArray; +} + +// Parse arguments string array into an object +function parseArgs(argv) { + var results = { '_': [] }, current = null; + for (var i = 1, len = argv.length; i < len; i++) { + var x = argv[i]; + if (x.length > 2 && x[0] == '-' && x[1] == '-') { + if (current != null) { results[current] = true; } + current = x.substring(2); + } else { + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); } + } + } + if (current != null) { results[current] = true; } + return results; +} + +// Parge a URL string into an options object +function parseUrl(url) { + var x = url.split('/'); + if (x.length < 4) return null; + var y = x[2].split(':'); + var options = {}; + var options = { protocol: x[0], hostname: y[0], path: '/' + x.splice(3).join('/') }; + if (y.length == 1) { options.port = ((x[0] == 'https:') || (x[0] == 'wss:')) ? 443 : 80; } else { options.port = parseInt(y[1]); } + if (isNaN(options.port) == true) return null; + return options; +} + +// Read a entire file into a buffer +function readFileToBuffer(filePath) { + try { + var fs = require('fs'); + var stats = fs.statSync(filePath); + if (stats == null) { return null; } + var fileData = new Buffer(stats.size); + var fd = fs.openSync(filePath, 'r'); + fs.readSync(fd, fileData, 0, stats.size, 0); + fs.closeSync(fd); + return fileData; + } catch (e) { return null; } +} + +// Performs an HTTP get on a URL and return the data back +function makeHttpGetRequest(url, func) { + var http = require('http'); + var request = http.get(url, function (res) { + var htmlData = ''; + res.on('data', function (d) { htmlData += d; }); + res.on('end', function (d) { func(res.statusCode, htmlData); }); + }).on('error', function (e) { func(0, null); }); +} + +// Performs an HTTP get on a URL and return the data back (Alternative implementation) +function makeHttpGetRequest2(url, func) { + var http = require('http'); + var options = http.parseUri(url); + options.username = 'admin'; + options.password = 'P@ssw0rd'; + var request = http.request(options, function (res) { + var htmlData = ''; + res.on('data', function (d) { htmlData += d; }); + res.on('end', function () { func(res.statusCode, htmlData); }); + }); + request.on('error', function (e) { func(0, null); }); + request.end(); +} + +// Performs an HTTP get on a URL and return the data back (Alternative implementation) +function intelAmtSetStorage(url, buffer, func) { + var http = require('http'); + var options = http.parseUri(url); + options.user = 'admin'; // TODO: Does not support HTTP digest auth yet!!!!!!!!!!!!!!!! + options.pass = 'P@ssw0rd'; + var request = http.request(options, function (res) { + var htmlData = ''; + res.on('data', function (d) { htmlData += d; }); + res.on('end', function () { func(res.statusCode, htmlData); }); + }); + request.on('error', function (e) { func(0, null); }); + request.end(); +} + +//console.log(objToString(db2, 2, ' ')); + +console.log('--- Start ---'); + +var fileData = readFileToBuffer('MeshCommander-Small.gz'); +if (fileData != null) { + makeHttpGetRequest2('http://192.168.2.105:16992/index.htm', function (status, htmlData) { console.log(status, htmlData); }); + + /* + intelAmtSetStorage('http://192.168.2.105:16992/amt-storage/index.htm', fileData, function (status, htmlData) { + console.log('intelAmtSetStorage', status, htmlData); + }); + */ +} + +console.log('--- End ---'); +//process.exit(2); \ No newline at end of file diff --git a/meshagent.js b/meshagent.js index fb46b238..8f4054a9 100644 --- a/meshagent.js +++ b/meshagent.js @@ -300,7 +300,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // We have a location in the database for this remote IP var iploc = nodes[0], x = {}; x.publicip = iploc.ip; - x.iploc = iploc.loc + ',' + (Math.floor((new Date(command.value.date)) / 1000)); + x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000)); ChangeAgentLocationInfo(x); } else { // Check if we need to ask for the IP location diff --git a/meshcentral.js b/meshcentral.js index f17a6082..f90c7e3f 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -34,6 +34,8 @@ function CreateMeshCentralServer() { obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha256 hash), size:(binary size), path:(binary path) } obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha256 hash), size:(binary size), path:(binary path) } obj.multiServer = null; + obj.currentVer = null; + obj.maintenanceTimer = null; // Create data and files folders if needed try { obj.fs.mkdirSync(obj.datapath); } catch (e) { } @@ -54,7 +56,7 @@ function CreateMeshCentralServer() { 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', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport']; + var validArguments = ['_', 'notls', 'user', 'port', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', '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']; for (var arg in obj.args) { 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; } @@ -123,6 +125,14 @@ function CreateMeshCentralServer() { } else if (xprocess.xrestart == 2) { console.log('Expected exit...'); process.exit(); // User CTRL-C exit. + } else if (xprocess.xrestart == 3) { + // Server self-update exit + var child_process = require('child_process'); + var xxprocess = child_process.exec('npm install meshcentral', { cwd: obj.path.join(__dirname, '../..') }, function (error, stdout, stderr) { }); + xxprocess.data = ''; + xxprocess.stdout.on('data', function (data) { xxprocess.data += data; }); + xxprocess.stderr.on('data', function (data) { xxprocess.data += data; }); + xxprocess.on('close', function (code) { console.log('Update completed...'); setTimeout(function () { obj.launchChildServer(startLine); }, 1000); }); } else { if (error != null) { // This is an un-expected restart @@ -131,7 +141,7 @@ function CreateMeshCentralServer() { } } }); - xprocess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } console.log(data); }); + xprocess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { xprocess.xrestart = 3; } console.log(data); }); xprocess.stderr.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.fs.appendFileSync('mesherrors.txt', '-------- ' + new Date().toLocaleString() + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n'); }); xprocess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } }); } @@ -140,25 +150,22 @@ function CreateMeshCentralServer() { obj.getLatestServerVersion = function (callback) { if (callback == undefined) return; var child_process = require('child_process'); - var xprocess = child_process.exec('npm view meshcentral dist-tags.latest', function (error, stdout, stderr) { - if (xprocess.xrestart == true) { - setTimeout(function () { obj.launchChildServer(startLine); }, 500); // If exit with restart requested, restart the server. - } else { - if (error != null) { console.log('ERROR: Unable to start MeshCentral: ' + error); process.exit(); } - } - }); + var xprocess = child_process.exec('npm view meshcentral dist-tags.latest', function (error, stdout, stderr) { }); xprocess.data = ''; xprocess.stdout.on('data', function (data) { xprocess.data += data; }); xprocess.stderr.on('data', function (data) { }); xprocess.on('close', function (code) { var currentVer = null; - try { currentVer = JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version; } catch (e) { } + try { currentVer = JSON.parse(require('fs').readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { } var latestVer = null; if (code == 0) { try { latestVer = xprocess.data.split(' ').join('').split('\r').join('').split('\n').join(''); } catch (e) { } } callback(currentVer, latestVer); }); } + // Initiate server self-update + obj.performServerUpdate = function () { console.log('Starting self upgrade...'); process.exit(200); } + obj.StartEx = function () { // Look to see if data and/or file path is specified if (obj.args.datapath) { obj.datapath = obj.args.datapath; } @@ -310,6 +317,9 @@ function CreateMeshCentralServer() { obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates); } + // Start periodic maintenance + obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour + // Dispatch an event that the server is now running obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }) @@ -319,7 +329,28 @@ function CreateMeshCentralServer() { }); }); } - + + // Perform maintenance operations (called every hour) + obj.maintenanceActions = function () { + // Check if we need to perform server self-update + if (obj.args.selfupdate == true) { + obj.db.getValueOfTheDay('performSelfUpdate', 1, function (performSelfUpdate) { + if (performSelfUpdate.value > 0) { + performSelfUpdate.value--; + obj.db.Set(performSelfUpdate); + obj.getLatestServerVersion(function (currentVer, latestVer) { if (currentVer != latestVer) { obj.performServerUpdate(); return; } }); + } + }); + } + + // Clear old event entries and power entires + 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. + + // Perform other database cleanup + obj.db.cleanup(); + } + // Stop the Meshcentral server obj.Stop = function (restoreFile) { // If the database is not setup, exit now. diff --git a/package.json b/package.json index 07dea7e9..c01763b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.0.7-g", + "version": "0.0.7-n", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default.handlebars b/views/default.handlebars index a3e58efa..805025cc 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -616,7 +616,6 @@ var features = {{{features}}}; var serverPublicNamePort = "{{{serverDnsName}}}:{{{serverPublicPort}}}"; var amtScanResults = null; - //var xxmap = null; function startup() { // Guard against other site's top frames (web bugs). @@ -852,14 +851,17 @@ } case 'serverversion': { if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerUpdate')) { - console.log(message); var x = '
'; if (!message.current) { message.current = 'Unknown'; } if (!message.latest) { message.latest = 'Unknown'; } x += addHtmlValue2('Current Version', '' + EscapeHtml(message.current) + ''); x += addHtmlValue2('Latest Version', '' + EscapeHtml(message.latest) + ''); x += '
'; - QH('d2verinfo', x); + if (message.current == message.latest) { + setDialogMode(2, "MeshCentral Version", 1, null, x); + } else { + setDialogMode(2, "MeshCentral Version", 3, server_showVersionDlgEx, x + '
Select OK to start server self-update.'); + } } break; } @@ -1187,7 +1189,7 @@ QV('devMapToolbar', view == 3); QV('devListToolbarSort', view < 3); if (view == 3) { - setTimeout( function() { xxmap.map.updateSize();}, 200); + setTimeout( function() { if (xxmap.map != null) { xxmap.map.updateSize(); } }, 200); // TODO } else { // 3 wide or list view @@ -3688,10 +3690,14 @@ function server_showVersionDlg() { if (xxdialogMode) return; - setDialogMode(2, "MeshCentral Version", 1, null, "
Loading...
", 'MeshCentralServerUpdate'); + setDialogMode(2, "MeshCentral Version", 1, null, "Loading...", 'MeshCentralServerUpdate'); meshserver.Send({ action: 'serverversion' }); } + function server_showVersionDlgEx() { + meshserver.Send({ action: 'serverupdate' }); + } + // // MY MESHS // diff --git a/webserver.js b/webserver.js index 57143b12..2345e940 100644 --- a/webserver.js +++ b/webserver.js @@ -1165,6 +1165,13 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate obj.parent.getLatestServerVersion(function (currentVersion, latestVersion) { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); }); break; } + case 'serverupdate': + { + // Perform server update + if ((user.siteadmin & 16) == 0) break; + obj.parent.performServerUpdate(); + break; + } case 'createmesh': { // Create mesh