diff --git a/agents/MeshCmd-signed.exe b/agents/MeshCmd-signed.exe index 83a6c917..4bc83c89 100644 Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ diff --git a/agents/MeshCmd64-signed.exe b/agents/MeshCmd64-signed.exe index bb44b058..edd92feb 100644 Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ diff --git a/agents/MeshService-signed.exe b/agents/MeshService-signed.exe index 8fb8cd18..8035e51b 100644 Binary files a/agents/MeshService-signed.exe and b/agents/MeshService-signed.exe differ diff --git a/agents/MeshService.exe b/agents/MeshService.exe index 703d64c0..18d8d749 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64-signed.exe b/agents/MeshService64-signed.exe index 33bc7312..0badaaaf 100644 Binary files a/agents/MeshService64-signed.exe and b/agents/MeshService64-signed.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index 7ffdd59a..d7bca0d4 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/meshcore.js b/agents/meshcore.js index 523b31b5..b83d940a 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -15,41 +15,41 @@ limitations under the License. */ -function borderController() -{ - this.container = null; - this.Start = function Start(user) - { - if (this.container == null) { - if (process.platform == 'win32') { - this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId }); - } - else { - this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid }); - } - this.container.addModule('monitor-info', getJSModule('monitor-info')); - this.container.addModule('monitor-border', getJSModule('monitor-border')); - this.container.addModule('promise', getJSModule('promise')); - this.container.ExecuteString("var border = require('monitor-border'); border.Start();"); - } - } - this.Stop = function Stop() - { - if (this.container != null) - { - this._container = this.container; - this._container.parent = this; - this.container = null; - this._container.once('exit', function () { this.parent._container = null; }); - this._container.exit(); - } - } -} function createMeshCore(agent) { var obj = {}; + function borderController() { + this.container = null; + this.Start = function Start(user) { + if (this.container == null) { + if (process.platform == 'win32') { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId }); + } + else { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid }); + } + this.container.parent = this; + this.container.addModule('monitor-info', getJSModule('monitor-info')); + this.container.addModule('monitor-border', getJSModule('monitor-border')); + this.container.addModule('promise', getJSModule('promise')); + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; }); + this.container.ExecuteString("var border = require('monitor-border'); border.Start();"); + } + } + this.Stop = function Stop() { + if (this.container != null) { + this._container = this.container; + this._container.parent = this; + this.container = null; + + this._container.exit(); + } + } + } + + require('events').EventEmitter.call(obj, true).createEvent('loggedInUsers_Updated'); obj.on('loggedInUsers_Updated', function () { @@ -1457,6 +1457,8 @@ function createMeshCore(agent) { }); require('user-sessions').emit('changed'); + require('user-sessions').on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); + require('user-sessions').on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); //console.log('Stopping.'); //process.exit(); } diff --git a/agents/modules_meshcmd/service-manager.js b/agents/modules_meshcmd/service-manager.js index 729beea6..ee6689ed 100644 --- a/agents/modules_meshcmd/service-manager.js +++ b/agents/modules_meshcmd/service-manager.js @@ -109,17 +109,22 @@ function serviceManager() } return admin; }; - this.getProgramFolder = function getProgramFolder() { - if (require('os').arch() == 'x64') { // 64 bit Windows - if (this.GM.PointerSize == 4) { return process.env['ProgramFiles(x86)']; } // 32 Bit App - return process.env['ProgramFiles']; // 64 bit App - } - return process.env['ProgramFiles']; // 32 bit Windows - }; - this.getServiceFolder = function getServiceFolder() + this.getProgramFolder = function getProgramFolder() { - return this.getProgramFolder() + '\\mesh'; + if (require('os').arch() == 'x64') + { + // 64 bit Windows + if (this.GM.PointerSize == 4) + { + return process.env['ProgramFiles(x86)']; // 32 Bit App + } + return process.env['ProgramFiles']; // 64 bit App + } + + // 32 bit Windows + return process.env['ProgramFiles']; }; + this.getServiceFolder = function getServiceFolder() { return this.getProgramFolder() + '\\mesh'; }; this.enumerateService = function () { var machineName = this.GM.CreatePointer(); diff --git a/agents/modules_meshcmd/user-sessions.js b/agents/modules_meshcmd/user-sessions.js index 4672596e..3d8571ad 100644 --- a/agents/modules_meshcmd/user-sessions.js +++ b/agents/modules_meshcmd/user-sessions.js @@ -14,10 +14,28 @@ See the License for the specific language governing permissions and limitations under the License. */ +var NOTIFY_FOR_THIS_SESSION = 0; +var NOTIFY_FOR_ALL_SESSIONS = 1; +var WM_WTSSESSION_CHANGE = 0x02B1; +var WTS_CONSOLE_CONNECT = (0x1); +var WTS_CONSOLE_DISCONNECT = (0x2); +var WTS_REMOTE_CONNECT = (0x3); +var WTS_REMOTE_DISCONNECT = (0x4); +var WTS_SESSION_LOGON = (0x5); +var WTS_SESSION_LOGOFF = (0x6); +var WTS_SESSION_LOCK = (0x7); +var WTS_SESSION_UNLOCK = (0x8); +var WTS_SESSION_REMOTE_CONTROL = (0x9); +var WTS_SESSION_CREATE = (0xA); +var WTS_SESSION_TERMINATE = (0xB); + function UserSessions() { this._ObjectID = 'user-sessions'; - require('events').EventEmitter.call(this, true).createEvent('changed'); + require('events').EventEmitter.call(this, true) + .createEvent('changed') + .createEvent('locked') + .createEvent('unlocked'); this.enumerateUsers = function enumerateUsers() { @@ -37,12 +55,15 @@ function UserSessions() if (process.platform == 'win32') { + this._serviceHooked = false; this._marshal = require('_GenericMarshal'); this._kernel32 = this._marshal.CreateNativeProxy('Kernel32.dll'); this._kernel32.CreateMethod('GetLastError'); this._wts = this._marshal.CreateNativeProxy('Wtsapi32.dll'); this._wts.CreateMethod('WTSEnumerateSessionsA'); this._wts.CreateMethod('WTSQuerySessionInformationA'); + this._wts.CreateMethod('WTSRegisterSessionNotification'); + this._wts.CreateMethod('WTSUnRegisterSessionNotification'); this._wts.CreateMethod('WTSFreeMemory'); this.SessionStates = ['Active', 'Connected', 'ConnectQuery', 'Shadow', 'Disconnected', 'Idle', 'Listening', 'Reset', 'Down', 'Init']; this.InfoClass = @@ -124,9 +145,52 @@ function UserSessions() if (cb) { cb(retVal); } return (retVal); }; + + this._immediate = setImmediate(function (self) + { + if (self._serviceHooked) { return; } // If we were hooked by a service, we won't need to do anything further + + // We need to spin up a message pump, and fetch a window handle + var message_pump = require('win-message-pump'); + self._messagepump = new message_pump({ filter: WM_WTSSESSION_CHANGE }); + self._messagepump.on('exit', function (code) { self._wts.WTSUnRegisterSessionNotification(self.hwnd); }); + self._messagepump.on('hwnd', function (h) + { + self.hwnd = h; + // Now that we have a window handle, we can register it to receive Windows Messages + self._wts.WTSRegisterSessionNotification(self.hwnd, NOTIFY_FOR_ALL_SESSIONS); + }); + self._messagepump.on('message', function (msg) + { + if (msg.message == WM_WTSSESSION_CHANGE) + { + switch(msg.wparam) + { + case WTS_SESSION_LOCK: + self.enumerateUsers().then(function (users) + { + if (users[msg.lparam]) { self.emit('locked', users[msg.lparam]); } + }); + break; + case WTS_SESSION_UNLOCK: + self.enumerateUsers().then(function (users) + { + if (users[msg.lparam]) { self.emit('unlocked', users[msg.lparam]); } + }); + break; + } + } + }); + }, this); } else { + this._linuxWatcher = require('fs').watch('/var/run/utmp'); + this._linuxWatcher.user_session = this; + this._linuxWatcher.on('change', function (a, b) + { + this.user_session.emit('changed'); + }); this.Self = function Self() { var promise = require('promise'); @@ -277,13 +341,26 @@ function UserSessions() function showActiveOnly(source) { var retVal = []; + var unique = {}; + var usernames = []; + var tmp; + for (var i in source) { if (source[i].State == 'Active') { retVal.push(source[i]); + tmp = (source[i].Domain ? (source[i].Domain + '\\') : '') + source[i].Username; + if (!unique[tmp]) { unique[tmp] = tmp;} } } + + for (var i in unique) + { + usernames.push(i); + } + + Object.defineProperty(retVal, 'usernames', { value: usernames }); return (retVal); } function getTokens(str) diff --git a/agents/modules_meshcore/service-manager.js b/agents/modules_meshcore/service-manager.js new file mode 100644 index 00000000..ee6689ed --- /dev/null +++ b/agents/modules_meshcore/service-manager.js @@ -0,0 +1,401 @@ +/* +Copyright 2018 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. +*/ + +function parseServiceStatus(token) +{ + var j = {}; + var serviceType = token.Deref(0, 4).IntVal; + j.isFileSystemDriver = ((serviceType & 0x00000002) == 0x00000002); + j.isKernelDriver = ((serviceType & 0x00000001) == 0x00000001); + j.isSharedProcess = ((serviceType & 0x00000020) == 0x00000020); + j.isOwnProcess = ((serviceType & 0x00000010) == 0x00000010); + j.isInteractive = ((serviceType & 0x00000100) == 0x00000100); + switch (token.Deref((1 * 4), 4).toBuffer().readUInt32LE()) + { + case 0x00000005: + j.state = 'CONTINUE_PENDING'; + break; + case 0x00000006: + j.state = 'PAUSE_PENDING'; + break; + case 0x00000007: + j.state = 'PAUSED'; + break; + case 0x00000004: + j.state = 'RUNNING'; + break; + case 0x00000002: + j.state = 'START_PENDING'; + break; + case 0x00000003: + j.state = 'STOP_PENDING'; + break; + case 0x00000001: + j.state = 'STOPPED'; + break; + } + var controlsAccepted = token.Deref((2 * 4), 4).toBuffer().readUInt32LE(); + j.controlsAccepted = []; + if ((controlsAccepted & 0x00000010) == 0x00000010) + { + j.controlsAccepted.push('SERVICE_CONTROL_NETBINDADD'); + j.controlsAccepted.push('SERVICE_CONTROL_NETBINDREMOVE'); + j.controlsAccepted.push('SERVICE_CONTROL_NETBINDENABLE'); + j.controlsAccepted.push('SERVICE_CONTROL_NETBINDDISABLE'); + } + if ((controlsAccepted & 0x00000008) == 0x00000008) { j.controlsAccepted.push('SERVICE_CONTROL_PARAMCHANGE'); } + if ((controlsAccepted & 0x00000002) == 0x00000002) { j.controlsAccepted.push('SERVICE_CONTROL_PAUSE'); j.controlsAccepted.push('SERVICE_CONTROL_CONTINUE'); } + if ((controlsAccepted & 0x00000100) == 0x00000100) { j.controlsAccepted.push('SERVICE_CONTROL_PRESHUTDOWN'); } + if ((controlsAccepted & 0x00000004) == 0x00000004) { j.controlsAccepted.push('SERVICE_CONTROL_SHUTDOWN'); } + if ((controlsAccepted & 0x00000001) == 0x00000001) { j.controlsAccepted.push('SERVICE_CONTROL_STOP'); } + if ((controlsAccepted & 0x00000020) == 0x00000020) { j.controlsAccepted.push('SERVICE_CONTROL_HARDWAREPROFILECHANGE'); } + if ((controlsAccepted & 0x00000040) == 0x00000040) { j.controlsAccepted.push('SERVICE_CONTROL_POWEREVENT'); } + if ((controlsAccepted & 0x00000080) == 0x00000080) { j.controlsAccepted.push('SERVICE_CONTROL_SESSIONCHANGE'); } + j.pid = token.Deref((7 * 4), 4).toBuffer().readUInt32LE(); + return (j); +} + +function serviceManager() +{ + this._ObjectID = 'service-manager'; + if (process.platform == 'win32') + { + this.GM = require('_GenericMarshal'); + this.proxy = this.GM.CreateNativeProxy('Advapi32.dll'); + this.proxy.CreateMethod('OpenSCManagerA'); + this.proxy.CreateMethod('EnumServicesStatusExA'); + this.proxy.CreateMethod('OpenServiceA'); + this.proxy.CreateMethod('QueryServiceStatusEx'); + this.proxy.CreateMethod('ControlService'); + this.proxy.CreateMethod('StartServiceA'); + this.proxy.CreateMethod('CloseServiceHandle'); + this.proxy.CreateMethod('CreateServiceA'); + this.proxy.CreateMethod('ChangeServiceConfig2A'); + this.proxy.CreateMethod('DeleteService'); + this.proxy.CreateMethod('AllocateAndInitializeSid'); + this.proxy.CreateMethod('CheckTokenMembership'); + this.proxy.CreateMethod('FreeSid'); + + this.proxy2 = this.GM.CreateNativeProxy('Kernel32.dll'); + this.proxy2.CreateMethod('GetLastError'); + + this.isAdmin = function isAdmin() { + var NTAuthority = this.GM.CreateVariable(6); + NTAuthority.toBuffer().writeInt8(5, 5); + var AdministratorsGroup = this.GM.CreatePointer(); + var admin = false; + + if (this.proxy.AllocateAndInitializeSid(NTAuthority, 2, 32, 544, 0, 0, 0, 0, 0, 0, AdministratorsGroup).Val != 0) + { + var member = this.GM.CreateInteger(); + if (this.proxy.CheckTokenMembership(0, AdministratorsGroup.Deref(), member).Val != 0) + { + if (member.toBuffer().readUInt32LE() != 0) { admin = true; } + } + this.proxy.FreeSid(AdministratorsGroup.Deref()); + } + return admin; + }; + this.getProgramFolder = function getProgramFolder() + { + if (require('os').arch() == 'x64') + { + // 64 bit Windows + if (this.GM.PointerSize == 4) + { + return process.env['ProgramFiles(x86)']; // 32 Bit App + } + return process.env['ProgramFiles']; // 64 bit App + } + + // 32 bit Windows + return process.env['ProgramFiles']; + }; + this.getServiceFolder = function getServiceFolder() { return this.getProgramFolder() + '\\mesh'; }; + + this.enumerateService = function () { + var machineName = this.GM.CreatePointer(); + var dbName = this.GM.CreatePointer(); + var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0001 | 0x0004); + + var bytesNeeded = this.GM.CreatePointer(); + var servicesReturned = this.GM.CreatePointer(); + var resumeHandle = this.GM.CreatePointer(); + //var services = this.proxy.CreateVariable(262144); + var success = this.proxy.EnumServicesStatusExA(handle, 0, 0x00000030, 0x00000003, 0x00, 0x00, bytesNeeded, servicesReturned, resumeHandle, 0x00); + if (bytesNeeded.IntVal <= 0) { + throw ('error enumerating services'); + } + var sz = bytesNeeded.IntVal; + var services = this.GM.CreateVariable(sz); + this.proxy.EnumServicesStatusExA(handle, 0, 0x00000030, 0x00000003, services, sz, bytesNeeded, servicesReturned, resumeHandle, 0x00); + console.log("servicesReturned", servicesReturned.IntVal); + + var ptrSize = dbName._size; + var blockSize = 36 + (2 * ptrSize); + blockSize += ((ptrSize - (blockSize % ptrSize)) % ptrSize); + var retVal = []; + for (var i = 0; i < servicesReturned.IntVal; ++i) { + var token = services.Deref(i * blockSize, blockSize); + var j = {}; + j.name = token.Deref(0, ptrSize).Deref().String; + j.displayName = token.Deref(ptrSize, ptrSize).Deref().String; + j.status = parseServiceStatus(token.Deref(2 * ptrSize, 36)); + retVal.push(j); + } + this.proxy.CloseServiceHandle(handle); + return (retVal); + } + this.getService = function (name) { + var serviceName = this.GM.CreateVariable(name); + var ptr = this.GM.CreatePointer(); + var bytesNeeded = this.GM.CreateVariable(ptr._size); + var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0001 | 0x0004 | 0x0020 | 0x0010); + if (handle.Val == 0) { throw ('could not open ServiceManager'); } + var h = this.proxy.OpenServiceA(handle, serviceName, 0x0004 | 0x0020 | 0x0010 | 0x00010000); + if (h.Val != 0) { + var success = this.proxy.QueryServiceStatusEx(h, 0, 0, 0, bytesNeeded); + var status = this.GM.CreateVariable(bytesNeeded.toBuffer().readUInt32LE()); + success = this.proxy.QueryServiceStatusEx(h, 0, status, status._size, bytesNeeded); + if (success != 0) { + retVal = {}; + retVal.status = parseServiceStatus(status); + retVal._scm = handle; + retVal._service = h; + retVal._GM = this.GM; + retVal._proxy = this.proxy; + require('events').inherits(retVal); + retVal.on('~', function () { this._proxy.CloseServiceHandle(this); this._proxy.CloseServiceHandle(this._scm); }); + retVal.name = name; + retVal.stop = function () { + if (this.status.state == 'RUNNING') { + var newstate = this._GM.CreateVariable(36); + var success = this._proxy.ControlService(this._service, 0x00000001, newstate); + if (success == 0) { + throw (this.name + '.stop() failed'); + } + } + else { + throw ('cannot call ' + this.name + '.stop(), when current state is: ' + this.status.state); + } + } + retVal.start = function () { + if (this.status.state == 'STOPPED') { + var success = this._proxy.StartServiceA(this._service, 0, 0); + if (success == 0) { + throw (this.name + '.start() failed'); + } + } + else { + throw ('cannot call ' + this.name + '.start(), when current state is: ' + this.status.state); + } + } + return (retVal); + } + else { + + } + } + + this.proxy.CloseServiceHandle(handle); + throw ('could not find service: ' + name); + } + } + this.installService = function installService(options) + { + if (process.platform == 'win32') + { + if (!this.isAdmin()) { throw ('Installing as Service, requires admin'); } + + // Before we start, we need to copy the binary to the right place + var folder = this.getServiceFolder(); + if (!require('fs').existsSync(folder)) { require('fs').mkdirSync(folder); } + require('fs').copyFileSync(options.servicePath, folder + '\\' + options.name + '.exe'); + options.servicePath = folder + '\\' + options.name + '.exe'; + + var servicePath = this.GM.CreateVariable('"' + options.servicePath + '"'); + var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0002); + if (handle.Val == 0) { throw ('error opening SCManager'); } + var serviceName = this.GM.CreateVariable(options.name); + var displayName = this.GM.CreateVariable(options.name); + var allAccess = 0x000F01FF; + var serviceType; + + + switch (options.startType) { + case 'BOOT_START': + serviceType = 0x00; + break; + case 'SYSTEM_START': + serviceType = 0x01; + break; + case 'AUTO_START': + serviceType = 0x02; + break; + case 'DEMAND_START': + serviceType = 0x03; + break; + default: + serviceType = 0x04; // Disabled + break; + } + + var h = this.proxy.CreateServiceA(handle, serviceName, displayName, allAccess, 0x10 | 0x100, serviceType, 0, servicePath, 0, 0, 0, 0, 0); + if (h.Val == 0) { this.proxy.CloseServiceHandle(handle); throw ('Error Creating Service: ' + this.proxy2.GetLastError().Val); } + if (options.description) { + console.log(options.description); + + var dscPtr = this.GM.CreatePointer(); + dscPtr.Val = this.GM.CreateVariable(options.description); + + if (this.proxy.ChangeServiceConfig2A(h, 1, dscPtr) == 0) { + this.proxy.CloseServiceHandle(h); + this.proxy.CloseServiceHandle(handle); + throw ('Unable to set description'); + } + } + this.proxy.CloseServiceHandle(h); + this.proxy.CloseServiceHandle(handle); + return (this.getService(options.name)); + } + if(process.platform == 'linux') + { + switch (this.getServiceType()) + { + case 'init': + require('fs').copyFileSync(options.servicePath, '/etc/init.d/' + options.name); + console.log('copying ' + options.servicePath); + var m = require('fs').statSync('/etc/init.d/' + options.name).mode; + m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP); + require('fs').chmodSync('/etc/init.d/' + options.name, m); + this._update = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); + this._update._moduleName = options.name; + this._update.on('exit', function onUpdateRC_d() { console.log(this._moduleName + ' installed'); process.exit(); }); + this._update.stdout.on('data', function (chunk) { }); + this._update.stdin.write('update-rc.d ' + options.name + ' defaults\n'); + this._update.stdin.write('exit\n'); + //update-rc.d meshagent defaults # creates symlinks for rc.d + //service meshagent start + + break; + case 'systemd': + var serviceDescription = options.description ? options.description : 'MeshCentral Agent'; + if (!require('fs').existsSync('/usr/local/mesh')) { require('fs').mkdirSync('/usr/local/mesh'); } + require('fs').copyFileSync(options.servicePath, '/usr/local/mesh/' + options.name); + var m = require('fs').statSync('/usr/local/mesh/' + options.name).mode; + m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP); + require('fs').chmodSync('/usr/local/mesh/' + options.name, m); + require('fs').writeFileSync('/lib/systemd/system/' + options.name + '.service', '[Unit]\nDescription=' + serviceDescription + '\n[Service]\nExecStart=/usr/local/mesh/' + options.name + '\nStandardOutput=null\nRestart=always\nRestartSec=3\n[Install]\nWantedBy=multi-user.target\nAlias=' + options.name + '.service\n', { flags: 'w' }); + this._update = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); + this._update._moduleName = options.name; + this._update.on('exit', function onUpdateRC_d() { console.log(this._moduleName + ' installed'); process.exit(); }); + this._update.stdout.on('data', function (chunk) { }); + this._update.stdin.write('systemctl enable ' + options.name + '.service\n'); + this._update.stdin.write('exit\n'); + + break; + default: // unknown platform service type + break; + } + } + } + this.uninstallService = function uninstallService(name) + { + if (typeof (name) == 'object') { name = name.name; } + if (process.platform == 'win32') + { + if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); } + + var service = this.getService(name); + if (service.status.state == undefined || service.status.state == 'STOPPED') + { + if (this.proxy.DeleteService(service._service) == 0) + { + throw ('Uninstall Service for: ' + name + ', failed with error: ' + this.proxy2.GetLastError()); + } + else + { + try + { + require('fs').unlinkSync(this.getServiceFolder() + '\\' + name + '.exe'); + } + catch(e) + { + } + } + } + else + { + throw ('Cannot uninstall service: ' + name + ', because it is: ' + service.status.state); + } + } + else if(process.platform == 'linux') + { + switch (this.getServiceType()) + { + case 'init': + this._update = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); + this._update._svcname = name; + this._update.on('exit', function onUninstallExit() { + try { + require('fs').unlinkSync('/etc/init.d/' + this._svcname); + console.log(this._svcname + ' uninstalled'); + + } + catch (e) { + console.log(this._svcname + ' could not be uninstalled') + } + process.exit(); + }); + this._update.stdout.on('data', function (chunk) { }); + this._update.stdin.write('service ' + name + ' stop\n'); + this._update.stdin.write('update-rc.d -f ' + name + ' remove\n'); + this._update.stdin.write('exit\n'); + break; + case 'systemd': + this._update = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); + this._update._svcname = name; + this._update.on('exit', function onUninstallExit() { + try { + require('fs').unlinkSync('/usr/local/mesh/' + this._svcname); + require('fs').unlinkSync('/lib/systemd/system/' + this._svcname + '.service'); + console.log(this._svcname + ' uninstalled'); + } + catch (e) { + console.log(this._svcname + ' could not be uninstalled') + } + process.exit(); + }); + this._update.stdout.on('data', function (chunk) { }); + this._update.stdin.write('systemctl stop ' + name + '.service\n'); + this._update.stdin.write('systemctl disable ' + name + '.service\n'); + this._update.stdin.write('exit\n'); + break; + default: // unknown platform service type + break; + } + } + } + if(process.platform == 'linux') + { + this.getServiceType = function getServiceType() + { + return (require('process-manager').getProcessInfo(1).Name); + }; + } +} + +module.exports = serviceManager; \ No newline at end of file diff --git a/agents/modules_meshcore/toaster.js b/agents/modules_meshcore/toaster.js index 316fdb9e..1664636a 100644 --- a/agents/modules_meshcore/toaster.js +++ b/agents/modules_meshcore/toaster.js @@ -18,38 +18,40 @@ var toasters = {}; function Toaster() { - this._ObjectID = 'Toaster'; + this._ObjectID = 'toaster'; this.Toast = function Toast(title, caption) { - if (process.platform != 'win32') return; - var retVal = {}; var emitter = require('events').inherits(retVal); - emitter.createEvent('Clicked'); emitter.createEvent('Dismissed'); - var session = require('user-sessions').Current(); - for (var i in session) - { - console.log(session[i]); - } - try - { - console.log('Attempting Toast Mechanism 1'); - retVal._child = require('ScriptContainer').Create({ processIsolation: true, sessionId: session.connected[0].SessionId }); - } - catch (e) { - console.log(e); - console.log('Attempting Toast Mechanism 2'); - retVal._child = require('ScriptContainer').Create({ processIsolation: true }); - } - retVal._child.parent = retVal; + retVal.title = title; + retVal.caption = caption; - retVal._child.on('exit', function (code) { this.parent.emit('Dismissed'); delete this.parent._child; }); - retVal._child.addModule('win-console', getJSModule('win-console')); - retVal._child.addModule('win-messagepump', getJSModule('win-messagepump')); + if (process.platform == 'win32') + { + emitter.createEvent('Clicked'); - var str = "\ + var session = require('user-sessions').Current(); + for (var i in session) { + console.log(session[i]); + } + try { + console.log('Attempting Toast Mechanism 1'); + retVal._child = require('ScriptContainer').Create({ processIsolation: true, sessionId: session.Active[0].SessionId }); + } + catch (e) { + console.log(e); + console.log('Attempting Toast Mechanism 2'); + retVal._child = require('ScriptContainer').Create({ processIsolation: true }); + } + retVal._child.parent = retVal; + + retVal._child.on('exit', function (code) { this.parent.emit('Dismissed'); delete this.parent._child; }); + retVal._child.addModule('win-console', getJSModule('win-console')); + retVal._child.addModule('win-message-pump', getJSModule('win-message-pump')); + + var str = "\ try{\ var toast = require('win-console');\ var balloon = toast.SetTrayIcon({ szInfo: '" + caption + "', szInfoTitle: '" + title + "', balloonOnly: true });\ @@ -61,11 +63,62 @@ function Toaster() }\ require('ScriptContainer').send('done');\ "; - retVal._child.ExecuteString(str); - toasters[retVal._hashCode()] = retVal; - retVal.on('Dismissed', function () { delete toasters[this._hashCode()]; }); - console.log('Returning'); - return (retVal); + retVal._child.ExecuteString(str); + toasters[retVal._hashCode()] = retVal; + retVal.on('Dismissed', function () { delete toasters[this._hashCode()]; }); + console.log('Returning'); + return (retVal); + } + else + { + if(!require('fs').existsSync('/usr/bin/notify-send')) + { + throw ('Toast not supported on this platform'); + } + Object.defineProperty(retVal, '_sessions', { + value: require('user-sessions').Current(function onCurrentSession(sessions) + { + this._cchild = require('child_process').execFile('/usr/bin/whoami', ['whoami'], { type: require('child_process').SpawnTypes.TERM }); + this._cchild.stdout.on('data', function (chunk) + { + if (chunk.toString().split('\r\n')[0] == 'root') + { + if (sessions[':0'].State != 'Connected' && sessions[':0'].State != 'Active') + { + // No logged in user owns the display + this.parent.parent.Parent.emit('Dismissed'); + return; + } + + // We root, so we need to direct to DISPLAY=:0 + this.parent.parent._notify = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); + this.parent.parent._notify.stdin.write('su - ' + sessions[':0'].Username + ' -c "DISPLAY=:0 notify-send \'' + this.parent.parent.Parent.title + '\' \'' + this.parent.parent.Parent.caption + '\'"\n'); + this.parent.parent._notify.stdin.write('exit\n'); + this.parent.parent._notify.stdout.on('data', function (chunk) { }); + } + else + { + // We ain't root, so that means we can just call send-notify directly + this.parent.parent._notify = require('child_process').execFile('/usr/bin/notify-send', ['notify-send', this.parent.parent.Parent.title, this.parent.parent.Parent.caption], { type: require('child_process').SpawnTypes.TERM }); + this.parent.parent._notify.stdout.on('data', function (chunk) { }); + } + + // NOTIFY-SEND has a bug where timeouts don't work, so the default is 10 seconds + this.parent.parent.Parent._timeout = setTimeout(function onFakeDismissed(obj) + { + obj.emit('Dismissed'); + }, 10000, this.parent.parent.Parent); + }); + this._cchild.parent = this; + }) + }); + retVal._sessions.Parent = retVal; + + toasters[retVal._hashCode()] = retVal; + retVal.on('Dismissed', function () { delete toasters[this._hashCode()]; }); + + return (retVal); + } }; } diff --git a/agents/modules_meshcore/user-sessions.js b/agents/modules_meshcore/user-sessions.js index 4672596e..3d8571ad 100644 --- a/agents/modules_meshcore/user-sessions.js +++ b/agents/modules_meshcore/user-sessions.js @@ -14,10 +14,28 @@ See the License for the specific language governing permissions and limitations under the License. */ +var NOTIFY_FOR_THIS_SESSION = 0; +var NOTIFY_FOR_ALL_SESSIONS = 1; +var WM_WTSSESSION_CHANGE = 0x02B1; +var WTS_CONSOLE_CONNECT = (0x1); +var WTS_CONSOLE_DISCONNECT = (0x2); +var WTS_REMOTE_CONNECT = (0x3); +var WTS_REMOTE_DISCONNECT = (0x4); +var WTS_SESSION_LOGON = (0x5); +var WTS_SESSION_LOGOFF = (0x6); +var WTS_SESSION_LOCK = (0x7); +var WTS_SESSION_UNLOCK = (0x8); +var WTS_SESSION_REMOTE_CONTROL = (0x9); +var WTS_SESSION_CREATE = (0xA); +var WTS_SESSION_TERMINATE = (0xB); + function UserSessions() { this._ObjectID = 'user-sessions'; - require('events').EventEmitter.call(this, true).createEvent('changed'); + require('events').EventEmitter.call(this, true) + .createEvent('changed') + .createEvent('locked') + .createEvent('unlocked'); this.enumerateUsers = function enumerateUsers() { @@ -37,12 +55,15 @@ function UserSessions() if (process.platform == 'win32') { + this._serviceHooked = false; this._marshal = require('_GenericMarshal'); this._kernel32 = this._marshal.CreateNativeProxy('Kernel32.dll'); this._kernel32.CreateMethod('GetLastError'); this._wts = this._marshal.CreateNativeProxy('Wtsapi32.dll'); this._wts.CreateMethod('WTSEnumerateSessionsA'); this._wts.CreateMethod('WTSQuerySessionInformationA'); + this._wts.CreateMethod('WTSRegisterSessionNotification'); + this._wts.CreateMethod('WTSUnRegisterSessionNotification'); this._wts.CreateMethod('WTSFreeMemory'); this.SessionStates = ['Active', 'Connected', 'ConnectQuery', 'Shadow', 'Disconnected', 'Idle', 'Listening', 'Reset', 'Down', 'Init']; this.InfoClass = @@ -124,9 +145,52 @@ function UserSessions() if (cb) { cb(retVal); } return (retVal); }; + + this._immediate = setImmediate(function (self) + { + if (self._serviceHooked) { return; } // If we were hooked by a service, we won't need to do anything further + + // We need to spin up a message pump, and fetch a window handle + var message_pump = require('win-message-pump'); + self._messagepump = new message_pump({ filter: WM_WTSSESSION_CHANGE }); + self._messagepump.on('exit', function (code) { self._wts.WTSUnRegisterSessionNotification(self.hwnd); }); + self._messagepump.on('hwnd', function (h) + { + self.hwnd = h; + // Now that we have a window handle, we can register it to receive Windows Messages + self._wts.WTSRegisterSessionNotification(self.hwnd, NOTIFY_FOR_ALL_SESSIONS); + }); + self._messagepump.on('message', function (msg) + { + if (msg.message == WM_WTSSESSION_CHANGE) + { + switch(msg.wparam) + { + case WTS_SESSION_LOCK: + self.enumerateUsers().then(function (users) + { + if (users[msg.lparam]) { self.emit('locked', users[msg.lparam]); } + }); + break; + case WTS_SESSION_UNLOCK: + self.enumerateUsers().then(function (users) + { + if (users[msg.lparam]) { self.emit('unlocked', users[msg.lparam]); } + }); + break; + } + } + }); + }, this); } else { + this._linuxWatcher = require('fs').watch('/var/run/utmp'); + this._linuxWatcher.user_session = this; + this._linuxWatcher.on('change', function (a, b) + { + this.user_session.emit('changed'); + }); this.Self = function Self() { var promise = require('promise'); @@ -277,13 +341,26 @@ function UserSessions() function showActiveOnly(source) { var retVal = []; + var unique = {}; + var usernames = []; + var tmp; + for (var i in source) { if (source[i].State == 'Active') { retVal.push(source[i]); + tmp = (source[i].Domain ? (source[i].Domain + '\\') : '') + source[i].Username; + if (!unique[tmp]) { unique[tmp] = tmp;} } } + + for (var i in unique) + { + usernames.push(i); + } + + Object.defineProperty(retVal, 'usernames', { value: usernames }); return (retVal); } function getTokens(str) diff --git a/agents/modules_meshcore/win-console.js b/agents/modules_meshcore/win-console.js index d2c78f97..4d0970d5 100644 --- a/agents/modules_meshcore/win-console.js +++ b/agents/modules_meshcore/win-console.js @@ -37,7 +37,7 @@ function WindowsConsole() { if (process.platform == 'win32') { - this._ObjectID = 'WindowsConsole'; + this._ObjectID = 'win-console'; this._Marshal = require('_GenericMarshal'); this._kernel32 = this._Marshal.CreateNativeProxy("kernel32.dll"); this._user32 = this._Marshal.CreateNativeProxy("user32.dll"); @@ -102,7 +102,7 @@ function WindowsConsole() if (options.szInfoTitle) { Buffer.from(options.szInfoTitle).copy(szInfoTitle.toBuffer()); } - var MessagePump = require('win-messagepump'); + var MessagePump = require('win-message-pump'); retVal = { _ObjectID: 'WindowsConsole.TrayIcon', MessagePump: new MessagePump(options) }; var retValEvents = require('events').inherits(retVal); retValEvents.createEvent('ToastClicked'); diff --git a/agents/modules_meshcore/win-messagepump.js b/agents/modules_meshcore/win-message-pump.js similarity index 95% rename from agents/modules_meshcore/win-messagepump.js rename to agents/modules_meshcore/win-message-pump.js index 8c2ce09c..44782582 100644 --- a/agents/modules_meshcore/win-messagepump.js +++ b/agents/modules_meshcore/win-message-pump.js @@ -19,7 +19,7 @@ var WM_QUIT = 0x0012; function WindowsMessagePump(options) { - this._ObjectID = 'WindowsMessagePump'; + this._ObjectID = 'win-message-pump'; this._options = options; var emitterUtils = require('events').inherits(this); emitterUtils.createEvent('hwnd'); @@ -29,11 +29,10 @@ function WindowsMessagePump(options) this._child = require('ScriptContainer').Create({ processIsolation: 0 }); this._child.MessagePump = this; - this._child.prependListener('~', function _childFinalizer() { this.MessagePump.emit('exit', 0); console.log('calling stop'); this.MessagePump.stop(); }); + this._child.prependListener('~', function _childFinalizer() { this.MessagePump.emit('exit', 0); this.MessagePump.stop(); }); this._child.once('exit', function onExit(code) { this.MessagePump.emit('exit', code); }); this._child.once('ready', function onReady() { - console.log('child ready'); var execString = "var m = require('_GenericMarshal');\ var h = null;\ @@ -110,7 +109,6 @@ function WindowsMessagePump(options) { if(this._child && this._child._hwnd) { - console.log('posting WM_QUIT'); var marshal = require('_GenericMarshal'); var User32 = marshal.CreateNativeProxy('User32.dll'); User32.CreateMethod('PostMessageA'); diff --git a/agents/modules_meshcore/win-registry.js b/agents/modules_meshcore/win-registry.js index 2fee12a5..0018080a 100644 --- a/agents/modules_meshcore/win-registry.js +++ b/agents/modules_meshcore/win-registry.js @@ -35,7 +35,7 @@ var KEY_DATA_TYPES = function windows_registry() { - this._ObjectId = 'windows_registry'; + this._ObjectId = 'win-registry'; this._marshal = require('_GenericMarshal'); this._AdvApi = this._marshal.CreateNativeProxy('Advapi32.dll'); this._AdvApi.CreateMethod('RegCreateKeyExA'); diff --git a/meshcentral.js b/meshcentral.js index 91a2cc33..f2b6e43a 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -1204,6 +1204,10 @@ process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); // Load the really basic modules var meshserver = null; function mainStart(args) { + // Check the NodeJS is version 6 or better. + if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 6) { console.log("MeshCentral requires Node v6.x or above, current version is " + process.version + "."); return; } + + // Check for any missing modules. InstallModules(['minimist'], function () { // Get the server configuration var config = getConfig(); diff --git a/meshuser.js b/meshuser.js index e13a3427..a193e26b 100644 --- a/meshuser.js +++ b/meshuser.js @@ -493,7 +493,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if (chguser) { if (obj.common.validateString(command.email, 1, 256) && (chguser.email != command.email)) { chguser.email = command.email; change = 1; } if ((command.emailVerified === true || command.emailVerified === false) && (chguser.emailVerified != command.emailVerified)) { chguser.emailVerified = command.emailVerified; change = 1; } - if (obj.common.validateInt(command.quota, 0) && (command.quota != chguser.quota)) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; } + if ((obj.common.validateInt(command.quota, 0) || command.quota == null) && (command.quota != chguser.quota)) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; } if ((user.siteadmin == 0xFFFFFFFF) && obj.common.validateInt(command.siteadmin) && (chguser.siteadmin != command.siteadmin)) { chguser.siteadmin = command.siteadmin; change = 1; } if (change == 1) { obj.db.SetUser(chguser); diff --git a/package.json b/package.json index ee3eb6b7..06a817b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.9-v", + "version": "0.1.9-w", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index d2ccc139..984c81ea 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}


\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}


\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 7a78c957..777b24b7 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -4981,11 +4981,19 @@ p5setActions(); } + function getNiceSize(bytes) { + if (bytes <= 0) return 'Storage limit exceed'; + if (bytes < 2048) return bytes + ' bytes remaining'; + if (bytes < 2097152) return Math.round(bytes / 1024) + ' kilobytes remaining'; + if (bytes < 2147483648) return Math.round(bytes / 1024 / 1024) + ' megabytes remaining'; + return Math.round(bytes / 1024 / 1024 / 1024) + ' gigabytes remaining'; + } + function p5getQuotabar(f) { while (f.t > 1 && f.t != 4) { f = f.parent; } if ((f.t != 1 && f.t != 4) || (f.maxbytes == null)) return ''; - var tf = Math.floor(f.s / 1024), tq = Math.floor((f.maxbytes - f.s) / 1024); - return ' 1?'s':'') + ". " + (Math.floor(f.maxbytes / 1024)) + 'k maxinum">' + ((tq < 0)?('Storage limit exceed'):(tq + 'k remaining')) + ' '; + var tf = Math.floor(f.s / 1024), tq = (f.maxbytes - f.s); + return ' 1 ? 's' : '') + ". " + (Math.floor(f.maxbytes / 1024 / 1024)) + 'k maxinum">' + getNiceSize(tq) + ' '; } function p5showPublicLink(u) { setDialogMode(2, "Public Link", 1, null, ''); } @@ -5057,13 +5065,14 @@ names.push(file.name); sizes.push(file.size); types.push(file.type); - reader.onload = function(event) { + reader.onload = function (event) { + console.log(event.target.result.length); datas.push(event.target.result); if (--readercount == 0) { Q('p5fileDragName').value = names.join('*'); Q('p5fileDragSize').value = sizes.join('*'); Q('p5fileDragType').value = types.join('*'); - Q('p5fileDragData').value = datas.join('*'); + Q('p5fileDragData').value = datas.join('*'); // TODO: This will not work for large files!!! ***************** Q('p5fileDragLink').value = encodeURIComponent(filetreelinkpath); Q('p5loginSubmit2').click(); } diff --git a/webserver.js b/webserver.js index 666fc8cb..098b71bc 100644 --- a/webserver.js +++ b/webserver.js @@ -935,15 +935,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (objid.startsWith('user/')) { var user = obj.users[objid]; if (user == null) return 0; + if (user.siteadmin == 0xFFFFFFFF) return null; // Administrators have no user limit if ((user.quota != null) && (typeof user.quota == 'number')) { return user.quota; } - if ((domain != null) && (domain.userQuota != null) && (typeof domain.userQuota == 'number')) { return domain.userQuota; } - return 1048576; // By default, the server will have a 1 meg limit on user accounts + if ((domain != null) && (domain.userquota != null) && (typeof domain.userquota == 'number')) { return domain.userquota; } + return null; // By default, the user will have no limit } else if (objid.startsWith('mesh/')) { var mesh = obj.meshes[objid]; if (mesh == null) return 0; if ((mesh.quota != null) && (typeof mesh.quota == 'number')) { return mesh.quota; } - if ((domain != null) && (domain.meshQuota != null) && (typeof domain.meshQuota == 'number')) { return domain.meshQuota; } - return 1048576; // By default, the server will have a 1 meg limit on mesh accounts + if ((domain != null) && (domain.meshquota != null) && (typeof domain.meshquota == 'number')) { return domain.meshquota; } + return null; // By default, the mesh will have no limit } return 0; }; @@ -997,12 +998,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var multiparty = require('multiparty'); var form = new multiparty.Form(); form.parse(req, function (err, fields, files) { - if ((fields == null) || (fields.link == null) || (fields.link.length != 1)) { res.sendStatus(404); return; } + if ((fields == null) || (fields.link == null) || (fields.link.length != 1)) { /*console.log('UploadFile, Invalid Fields:', fields, files);*/ res.sendStatus(404); return; } var xfile = obj.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 ((xfile.quota == null) || (totalsize < xfile.quota)) { // Check if the quota is not already broken if (fields.name != null) { // 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('*'); @@ -1010,7 +1011,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { 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 + if ((xfile.quota == null) || ((totalsize + filedata.length) < xfile.quota)) { // Check if quota would not be broken if we add this file // Create the user folder if needed (function (fullpath, filename, filedata) { obj.fs.mkdir(xfile.fullpath, function () { @@ -1027,7 +1028,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // 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 = obj.path.join(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 + if (obj.common.IsFilenameValid(file.originalFilename) && ((xfile.quota == null) || ((totalsize + file.size) < xfile.quota))) { // Check if quota would not be broken if we add this file obj.fs.rename(file.path, fpath, function () { obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files });