MeshCentral/agents/modules_meshcmd/serviceHost.js

277 lines
13 KiB
JavaScript

var SERVICE_WIN32 = 0x00000010 | 0x00000020;
var SERVICE_STATE = { STOPPED: 0x00000001, SERVICE_START_PENDING: 0x00000002, SERVICE_STOP_PENDING: 0x00000003, RUNNING: 0x00000004 };
var SERVICE_ACCEPT = { SERVICE_ACCEPT_STOP: 0x00000001, SERVICE_ACCEPT_SHUTDOWN: 0x00000004 };
var SERVICE_CONTROL = { SERVICE_CONTROL_SHUTDOWN: 0x00000005, SERVICE_CONTROL_STOP: 0x00000001 };
var NO_ERROR = 0;
var serviceManager = require('serviceManager');
function serviceHost(serviceName) {
this._ObjectID = 'serviceHost';
var emitterUtils = require('events').inherits(this);
emitterUtils.createEvent('serviceStart');
emitterUtils.createEvent('serviceStop');
emitterUtils.createEvent('normalStart');
if (process.platform == 'win32') {
this.GM = require('_GenericMarshal');
this.Advapi = this.GM.CreateNativeProxy('Advapi32.dll');
this.Advapi.CreateMethod({ method: 'StartServiceCtrlDispatcherA', threadDispatch: 1 });
this.Advapi.CreateMethod('RegisterServiceCtrlHandlerA');
this.Advapi.CreateMethod('SetServiceStatus');
this.Kernel32 = this.GM.CreateNativeProxy('Kernel32.dll');
this.Kernel32.CreateMethod('GetLastError');
this.Ole32 = this.GM.CreateNativeProxy('Ole32.dll');
this.Ole32.CreateMethod('CoInitializeEx');
this.Ole32.CreateMethod('CoUninitialize');
this._ServiceName = this.GM.CreateVariable(typeof (serviceName) == 'string' ? serviceName : serviceName.name);
this._ServiceMain = this.GM.GetGenericGlobalCallback(2);
this._ServiceMain.Parent = this;
this._ServiceMain.GM = this.GM;
this._ServiceMain.on('GlobalCallback', function onGlobalCallback(argc, argv) {
//ToDo: Check to make sure this is for us
this.Parent._ServiceStatus = this.GM.CreateVariable(28);
//typedef struct _SERVICE_STATUS {
// DWORD dwServiceType;
// DWORD dwCurrentState;
// DWORD dwControlsAccepted;
// DWORD dwWin32ExitCode;
// DWORD dwServiceSpecificExitCode;
// DWORD dwCheckPoint;
// DWORD dwWaitHint;
//} SERVICE_STATUS, *LPSERVICE_STATUS;
// Initialise service status
this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_WIN32);
this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_STATE.SERVICE_STOPPED, 4);
this.Parent._ServiceStatusHandle = this.Parent.Advapi.RegisterServiceCtrlHandlerA(this.Parent._ServiceName, this.Parent._ServiceControlHandler);
if (this.Parent._ServiceStatusHandle.Val == 0) {
process.exit(1);
}
// Service is starting
this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_STATE.SERVICE_START_PENDING, 4);
this.Parent.Advapi.SetServiceStatus(this.Parent._ServiceStatusHandle, this.Parent._ServiceStatus);
// Service running
this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_STATE.RUNNING, 4);
this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_ACCEPT.SERVICE_ACCEPT_STOP, 8);
this.Parent.Advapi.SetServiceStatus(this.Parent._ServiceStatusHandle, this.Parent._ServiceStatus);
this.Parent.Ole32.CoInitializeEx(0, 2);
this.Parent.on('~', function OnServiceHostFinalizer() {
var GM = require('_GenericMarshal');
var Advapi = GM.CreateNativeProxy('Advapi32.dll');
Advapi.CreateMethod('SetServiceStatus');
Kernel32 = this.GM.CreateNativeProxy('Kernel32.dll');
Kernel32.CreateMethod('GetLastError');
var status = GM.CreateVariable(28);
// Service was stopped
status.toBuffer().writeUInt32LE(SERVICE_WIN32);
status.toBuffer().writeUInt32LE(0x00000001, 4);
status.toBuffer().writeUInt32LE(0, 8);
Advapi.SetServiceStatus(this._ServiceStatusHandle, status);
this.Ole32.CoUninitialize();
});
this.Parent.emit('serviceStart');
});
this._ServiceControlHandler = this.GM.GetGenericGlobalCallback(1);
this._ServiceControlHandler.Parent = this;
this._ServiceControlHandler.GM = this.GM;
this._ServiceControlHandler.on('GlobalCallback', function onServiceControlHandler(code) {
switch (code.Val) {
case SERVICE_CONTROL.SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL.SERVICE_CONTROL_STOP:
this.Parent.emit('serviceStop');
return;
default:
break;
}
this.Parent.Advapi.SetServiceStatus(this.Parent._ServiceStatusHandle, this.Parent._ServiceStatus);
});
}
if (serviceName) { this._ServiceOptions = typeof (serviceName) == 'object' ? serviceName : { name: serviceName }; }
else
{
throw ('Must specify either ServiceName or Options');
}
if (!this._ServiceOptions.servicePath) {
this._ServiceOptions.servicePath = process.execPath;
}
this.run = function run() {
var serviceOperation = 0;
for (var i = 0; i < process.argv.length; ++i) {
switch (process.argv[i].toLowerCase()) {
case 'install':
if (!this._svcManager) { this._svcManager = new serviceManager(); }
try {
this._svcManager.installService(this._ServiceOptions);
} catch (e) {
console.log(e);
process.exit();
}
if (process.platform == 'win32') {
// Only do this on Windows, becuase Linux is async... It'll complete later
console.log(this._ServiceOptions.name + ' installed.');
process.exit();
}
i = process.argv.length;
serviceOperation = 1;
break;
case 'uninstall':
if (!this._svcManager) { this._svcManager = new serviceManager(); }
try {
this._svcManager.uninstallService(this._ServiceOptions);
} catch (e) {
console.log(e);
process.exit();
}
if (process.platform == 'win32') {
// Only do this on Windows, becuase Linux is async... It'll complete later
console.log(this._ServiceOptions.name + ' uninstalled.');
process.exit();
}
i = process.argv.length;
serviceOperation = 1;
break;
case 'start':
case '-d':
if (!this._svcManager) { this._svcManager = new serviceManager(); }
this._svcManager.getService(this._ServiceOptions.name).start();
console.log(this._ServiceOptions.name + ' starting.');
process.exit();
break;
case 'stop':
case '-s':
if (!this._svcManager) { this._svcManager = new serviceManager(); }
this._svcManager.getService(this._ServiceOptions.name).stop();
console.log(this._ServiceOptions.name + ' stopping.');
process.exit();
break;
}
}
if (process.platform == 'win32') {
var serviceTable = this.GM.CreateVariable(4 * this.GM.PointerSize);
this._ServiceName.pointerBuffer().copy(serviceTable.toBuffer());
this._ServiceMain.pointerBuffer().copy(serviceTable.toBuffer(), this.GM.PointerSize);
this._sscd = this.Advapi.StartServiceCtrlDispatcherA(serviceTable);
this._sscd.parent = this;
this._sscd.on('done', function OnStartServiceCtrlDispatcherA(retVal) {
if (retVal.Val == 0) { this.parent.emit('normalStart'); }
});
return;
}
var moduleName = this._ServiceOptions ? this._ServiceOptions.name : process.execPath.substring(1 + process.execPath.lastIndexOf('/'));
for (var i = 0; i < process.argv.length; ++i) {
switch (process.argv[i].toLowerCase()) {
case 'start':
case '-d':
var child = require('child_process').execFile(process.execPath, [moduleName], { type: require('child_process').SpawnTypes.DETACHED });
var pstream = null;
try {
pstream = require('fs').createWriteStream('/var/run/' + moduleName + '.pid', { flags: 'w' });
}
catch (e) {
}
if (pstream == null) {
pstream = require('fs').createWriteStream('.' + moduleName + '.pid', { flags: 'w' });
}
pstream.end(child.pid.toString());
console.log(moduleName + ' started!');
process.exit();
break;
case 'stop':
case '-s':
var pid = null;
try {
pid = parseInt(require('fs').readFileSync('/var/run/' + moduleName + '.pid', { flags: 'r' }));
require('fs').unlinkSync('/var/run/' + moduleName + '.pid');
}
catch (e) {
}
if (pid == null) {
try {
pid = parseInt(require('fs').readFileSync('.' + moduleName + '.pid', { flags: 'r' }));
require('fs').unlinkSync('.' + moduleName + '.pid');
}
catch (e) {
}
}
if (pid) {
process.kill(pid);
console.log(moduleName + ' stopped');
}
else {
console.log(moduleName + ' not running');
}
process.exit();
break;
}
}
if (serviceOperation == 0) {
// This is non-windows, so we need to check how this binary was started to determine if this was a service start
// Start by checking if we were started with start/stop
var pid = null;
try { pid = parseInt(require('fs').readFileSync('/var/run/' + moduleName + '.pid', { flags: 'r' })); } catch (e) { }
if (pid == null) { try { pid = parseInt(require('fs').readFileSync('.' + moduleName + '.pid', { flags: 'r' })); } catch (e) { } }
if (pid != null && pid == process.pid) {
this.emit('serviceStart');
} else {
// Now we need to check if we were started with systemd
if (require('processManager').getProcessInfo(1).Name == 'systemd') {
this._checkpid = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM });
this._checkpid.result = '';
this._checkpid.parent = this;
this._checkpid.on('exit', function onCheckPIDExit() {
var lines = this.result.split('\r\n');
for (i in lines) {
if (lines[i].startsWith(' Main PID:')) {
var tokens = lines[i].split(' ');
if (parseInt(tokens[3]) == process.pid) {
this.parent.emit('serviceStart');
} else {
this.parent.emit('normalStart');
}
delete this.parent._checkpid;
return;
}
}
this.parent.emit('normalStart');
delete this.parent._checkpid;
});
this._checkpid.stdout.on('data', function (chunk) { this.parent.result += chunk.toString(); });
this._checkpid.stdin.write("systemctl status " + moduleName + " | grep 'Main PID:'\n");
this._checkpid.stdin.write('exit\n');
}
else {
// This isn't even a systemd platform, so this couldn't have been a service start
this.emit('normalStart');
}
}
}
};
}
module.exports = serviceHost;