From 4c25687ee744780127150e22640df47931d139a9 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Mon, 2 Mar 2015 11:02:06 +0000 Subject: [PATCH 01/16] Added nssm_imagepath() and nssm_unquoted_imagepath(). Functions to retrieve the path to nssm.exe both with and without path quoting. --- nssm.cpp | 16 ++++++++++++++++ nssm.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/nssm.cpp b/nssm.cpp index 7eef69d..ddd1561 100644 --- a/nssm.cpp +++ b/nssm.cpp @@ -4,6 +4,9 @@ extern unsigned long tls_index; extern bool is_admin; extern imports_t imports; +static TCHAR unquoted_imagepath[PATH_LENGTH]; +static TCHAR imagepath[PATH_LENGTH]; + /* Are two strings case-insensitively equivalent? */ int str_equiv(const TCHAR *a, const TCHAR *b) { size_t len = _tcslen(a); @@ -100,6 +103,14 @@ int num_cpus() { return (int) i; } +const TCHAR *nssm_unquoted_imagepath() { + return unquoted_imagepath; +} + +const TCHAR *nssm_imagepath() { + return imagepath; +} + int _tmain(int argc, TCHAR **argv) { check_console(); @@ -118,6 +129,11 @@ int _tmain(int argc, TCHAR **argv) { /* Set up function pointers. */ if (get_imports()) exit(111); + /* Remember our path for later. */ + GetModuleFileName(0, unquoted_imagepath, _countof(unquoted_imagepath)); + GetModuleFileName(0, imagepath, _countof(imagepath)); + PathQuoteSpaces(imagepath); + /* Elevate */ if (argc > 1) { /* diff --git a/nssm.h b/nssm.h index f95391d..911804f 100644 --- a/nssm.h +++ b/nssm.h @@ -60,6 +60,8 @@ int str_number(const TCHAR *, unsigned long *, TCHAR **); int str_number(const TCHAR *, unsigned long *); int num_cpus(); int usage(int); +const TCHAR *nssm_unquoted_imagepath(); +const TCHAR *nssm_imagepath(); #define NSSM _T("NSSM") #ifdef _WIN64 -- 2.20.1 From fb96938cf944edf3bc0dfd99dbff416b0397df4f Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Mon, 2 Mar 2015 11:03:59 +0000 Subject: [PATCH 02/16] Use nssm_imagepath(). We were calling GetModuleFileName() in a number of places. Use nssm_imagepath() instead. Ensure that a quoted path is used for the service image path when creating the service, thus avoiding a theoretical security vulnerability whereby the service manager could be tricked into launching the wrong program. When setting the path to NSSM in the environment for event hooks we still use the unquoted path, as environment variables are typically unquoted. Thanks Gerald Haider. --- README.txt | 2 ++ env.cpp | 3 +-- hook.cpp | 6 ++---- nssm.cpp | 9 +-------- registry.cpp | 3 +-- service.cpp | 2 +- 6 files changed, 8 insertions(+), 17 deletions(-) diff --git a/README.txt b/README.txt index ecca5e9..f48a7f1 100644 --- a/README.txt +++ b/README.txt @@ -822,6 +822,8 @@ application's child processes. Thanks to Miguel Angel Terrón for suggesting copy/truncate rotation. Thanks to Yuriy Lesiuk for suggesting setting the environment before querying the registry for parameters. +Thanks to Gerald Haider for noticing that installing a service with NSSM in a +path containing spaces was technically a security vulnerability. Licence ------- diff --git a/env.cpp b/env.cpp index 9945d20..083f46a 100644 --- a/env.cpp +++ b/env.cpp @@ -129,8 +129,7 @@ int duplicate_environment(TCHAR *rawenv) { -1 on error. */ int test_environment(TCHAR *env) { - TCHAR path[PATH_LENGTH]; - GetModuleFileName(0, path, _countof(path)); + TCHAR *path = (TCHAR *) nssm_imagepath(); STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); diff --git a/hook.cpp b/hook.cpp index 09b98d9..780590b 100644 --- a/hook.cpp +++ b/hook.cpp @@ -254,10 +254,8 @@ int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_ /* Last control handled. */ SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control)); - /* Path to NSSM. */ - TCHAR path[PATH_LENGTH]; - GetModuleFileName(0, path, _countof(path)); - SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, path); + /* Path to NSSM, unquoted for the environment. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath()); /* NSSM version. */ SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION); diff --git a/nssm.cpp b/nssm.cpp index ddd1561..0b586f3 100644 --- a/nssm.cpp +++ b/nssm.cpp @@ -65,16 +65,10 @@ static int elevate(int argc, TCHAR **argv, unsigned long message) { ZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.lpVerb = _T("runas"); - sei.lpFile = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, PATH_LENGTH); - if (! sei.lpFile) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetModuleFileName()"), _T("elevate()")); - return 111; - } - GetModuleFileName(0, (TCHAR *) sei.lpFile, PATH_LENGTH); + sei.lpFile = (TCHAR *) nssm_imagepath(); TCHAR *args = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EXE_LENGTH * sizeof(TCHAR)); if (! args) { - HeapFree(GetProcessHeap(), 0, (void *) sei.lpFile); print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetCommandLine()"), _T("elevate()")); return 111; } @@ -91,7 +85,6 @@ static int elevate(int argc, TCHAR **argv, unsigned long message) { unsigned long exitcode = 0; if (! ShellExecuteEx(&sei)) exitcode = 100; - HeapFree(GetProcessHeap(), 0, (void *) sei.lpFile); HeapFree(GetProcessHeap(), 0, (void *) args); return exitcode; } diff --git a/registry.cpp b/registry.cpp index de3127f..5a4c652 100644 --- a/registry.cpp +++ b/registry.cpp @@ -17,8 +17,7 @@ int create_messages() { } /* Get path of this program */ - TCHAR path[PATH_LENGTH]; - GetModuleFileName(0, path, _countof(path)); + const TCHAR *path = nssm_imagepath(); /* Try to register the module but don't worry so much on failure */ RegSetValueEx(key, _T("EventMessageFile"), 0, REG_SZ, (const unsigned char *) path, (unsigned long) (_tcslen(path) + 1) * sizeof(TCHAR)); diff --git a/service.cpp b/service.cpp index 617cad1..6a3907e 100644 --- a/service.cpp +++ b/service.cpp @@ -1126,7 +1126,7 @@ int install_service(nssm_service_t *service) { } /* Get path of this program */ - GetModuleFileName(0, service->image, _countof(service->image)); + _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), nssm_imagepath()); /* Create the service - settings will be changed in edit_service() */ service->handle = CreateService(services, service->name, service->name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0); -- 2.20.1 From 66861c12e7d515f4d0e779102780d89e3fcd2a12 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 8 Mar 2015 16:45:55 +0000 Subject: [PATCH 03/16] Abstracted open_registry(). Added service_registry_path() to construct a registry path for a service which may contain the Parameters subkey and optional sub-subkey. If no Parameters subkey is needed the constructed path references the native service parameters. The open_registry() function is now a wrapper around the new open_registry_key() function which opens an arbitrary key. The new open_service_resgistry() function also wraps open_registry_key() to access native parameters. --- registry.cpp | 42 +++++++++++++++++++++++++++++++++--------- registry.h | 4 +++- settings.cpp | 20 ++++++++++++++++++++ settings.h | 1 + 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/registry.cpp b/registry.cpp index 5a4c652..5953f8a 100644 --- a/registry.cpp +++ b/registry.cpp @@ -456,18 +456,20 @@ void override_milliseconds(TCHAR *service_name, HKEY key, TCHAR *value, unsigned if (! ok) *buffer = default_value; } -HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, bool must_exist) { - /* Get registry */ - TCHAR registry[KEY_LENGTH]; - HKEY key; +static int service_registry_path(const TCHAR *service_name, bool parameters, const TCHAR *sub, TCHAR *buffer, unsigned long buflen) { int ret; - if (sub) ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, sub); - else ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY, service_name); - if (ret < 0) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REGISTRY"), _T("open_registry()"), 0); - return 0; + if (parameters) { + if (sub) ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS _T("\\%s"), service_name, sub); + else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS, service_name); } + else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY, service_name); + + return ret; +} + +static HKEY open_registry_key(const TCHAR *registry, REGSAM sam, bool must_exist) { + HKEY key; if (sam & KEY_SET_VALUE) { if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) { @@ -487,6 +489,28 @@ HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, bool return key; } +HKEY open_service_registry(const TCHAR *service_name, REGSAM sam, bool must_exist) { + /* Get registry */ + TCHAR registry[KEY_LENGTH]; + if (service_registry_path(service_name, false, 0, registry, _countof(registry)) < 0) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REGISTRY, _T("open_service_registry()"), 0); + return 0; + } + + return open_registry_key(registry, sam, must_exist); +} + +HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, bool must_exist) { + /* Get registry */ + TCHAR registry[KEY_LENGTH]; + if (service_registry_path(service_name, true, sub, registry, _countof(registry)) < 0) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REGISTRY, _T("open_registry()"), 0); + return 0; + } + + return open_registry_key(registry, sam, must_exist); +} + HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) { return open_registry(service_name, sub, sam, true); } diff --git a/registry.h b/registry.h index 1649bdf..394a49f 100644 --- a/registry.h +++ b/registry.h @@ -1,7 +1,8 @@ #ifndef REGISTRY_H #define REGISTRY_H -#define NSSM_REGISTRY _T("SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters") +#define NSSM_REGISTRY _T("SYSTEM\\CurrentControlSet\\Services\\%s") +#define NSSM_REG_PARAMETERS _T("Parameters") #define NSSM_REGISTRY_GROUPS _T("SYSTEM\\CurrentControlSet\\Control\\ServiceGroupOrder") #define NSSM_REG_GROUPS _T("List") #define NSSM_REG_EXE _T("Application") @@ -36,6 +37,7 @@ #define NSSM_REG_HOOK _T("AppEvents") #define NSSM_STDIO_LENGTH 29 +HKEY open_service_registry(const TCHAR *, REGSAM sam, bool); HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam, bool); HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam); HKEY open_registry(const TCHAR *, REGSAM sam); diff --git a/settings.cpp b/settings.cpp index c77c1c9..cbded45 100644 --- a/settings.cpp +++ b/settings.cpp @@ -726,6 +726,25 @@ int native_get_displayname(const TCHAR *service_name, void *param, const TCHAR * return ret; } +int native_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = open_service_registry(service_name, KEY_SET_VALUE, false); + if (! key) return -1; + + int ret = setting_set_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional); + RegCloseKey(key); + return ret; +} + +int native_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = open_service_registry(service_name, KEY_READ, false); + if (! key) return -1; + + ZeroMemory(value, sizeof(value_t)); + int ret = setting_get_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional); + RegCloseKey(key); + return ret; +} + int native_set_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { SC_HANDLE service_handle = (SC_HANDLE) param; if (! service_handle) return -1; @@ -1101,6 +1120,7 @@ settings_t settings[] = { { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice }, { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description }, { NSSM_NATIVE_DISPLAYNAME, REG_SZ, NULL, true, 0, native_set_displayname, native_get_displayname }, + { NSSM_NATIVE_ENVIRONMENT, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_environment, native_get_environment }, { NSSM_NATIVE_IMAGEPATH, REG_EXPAND_SZ, NULL, true, 0, native_set_imagepath, native_get_imagepath }, { NSSM_NATIVE_OBJECTNAME, REG_SZ, NSSM_LOCALSYSTEM_ACCOUNT, true, 0, native_set_objectname, native_get_objectname }, { NSSM_NATIVE_NAME, REG_SZ, NULL, true, 0, native_set_name, native_get_name }, diff --git a/settings.h b/settings.h index a629839..2fcffa5 100644 --- a/settings.h +++ b/settings.h @@ -5,6 +5,7 @@ #define NSSM_NATIVE_DEPENDONSERVICE _T("DependOnService") #define NSSM_NATIVE_DESCRIPTION _T("Description") #define NSSM_NATIVE_DISPLAYNAME _T("DisplayName") +#define NSSM_NATIVE_ENVIRONMENT _T("Environment") #define NSSM_NATIVE_IMAGEPATH _T("ImagePath") #define NSSM_NATIVE_NAME _T("Name") #define NSSM_NATIVE_OBJECTNAME _T("ObjectName") -- 2.20.1 From 61e7185453e213b98c0046cff96f0ace10d12924 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 8 Mar 2015 17:21:09 +0000 Subject: [PATCH 04/16] Describe startup environment in the README. --- README.txt | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/README.txt b/README.txt index f48a7f1..224af93 100644 --- a/README.txt +++ b/README.txt @@ -406,6 +406,59 @@ application to fail to start. Most people will want to use AppEnvironmentExtra exclusively. srvany only supports AppEnvironment. +As of version 2.25, NSSM parses AppEnvironment and AppEnvironmentExtra +itself, before reading any other registry values. As a result it is now +possible to refer to custom environment variables in Application, +AppDirectory and other parameters. + + +Merged service environment +-------------------------- +All Windows services can be passed additional environment variables by +creating a multi-valued string (REG_MULTI_SZ) registry value named +HLKM\SYSTEM\CurrentControlSet\Services\\Environment. + +The contents of this environment block will be merged into the system +environment before the service starts. + +Note, however, that the merged environment will be sorted alphabetically +before being processed. This means that in practice you cannot set, +for example, DIR=%PROGRAMFILES% in the Environment block because the +environment passed to the service will not have defined %PROGRAMFILES% +by the time it comes to define %DIR%. Environment variables defined in +AppEnvironmentExtra do not suffer from this limitation. + +As of version 2.25, NSSM can get and set the Environment block using +commands similar to: + + nssm get Environment + +It is worth reiterating that the Environment block is available to all +Windows services, not just NSSM services. + + +Service startup environment +--------------------------- +The environment NSSM passes to the application depends on how various +registry values are configured. The following flow describes how the +environment is modified. + +By default: + The service inherits the system environment. + +If \Environment is defined: + The contents of Environment are MERGED into the environment. + +If \Parameters\AppEnvironment is defined: + The service inherits the environment specified in AppEnvironment. + +If \Parameters\AppEnvironmentExtra is defined: + The contents of AppEnvironmentExtra are APPENDED to the environment. + +Note that AppEnvironment overrides the system environment and the +merged Environment block. Note also that AppEnvironmentExtra is +guaranteed to be appended to the startup environment if it is defined. + Event hooks ----------- @@ -555,6 +608,7 @@ run NSSM itself. The parameters recognised are as follows: Description: Service description. DisplayName: Service display name. + Environment: Service merged environment. ImagePath: Path to the service executable. ObjectName: User account which runs the service. Name: Service key name. -- 2.20.1 From cfa1e729fd2e5f953153239080b17ddb70de733e Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 8 Mar 2015 17:21:53 +0000 Subject: [PATCH 05/16] Incorrect capitalisation of AppStderrCopyAndTruncate. --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 224af93..12db7e8 100644 --- a/README.txt +++ b/README.txt @@ -331,7 +331,7 @@ a non-zero value of AppRotateBytesHigh. If AppRotateDelay is non-zero, NSSM will pause for the given number of milliseconds after rotation. -If AppStdoutCopyAndTruncate or AppStdErrCopyAndTruncate are non-zero, the +If AppStdoutCopyAndTruncate or AppStderrCopyAndTruncate are non-zero, the stdout (or stderr respectively) file will be rotated by first taking a copy of the file then truncating the original file to zero size. This allows NSSM to rotate files which are held open by other processes, preventing the -- 2.20.1 From e072e7ea67ab219eadb6761ceba4762b345a9b1f Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 8 Mar 2015 17:24:58 +0000 Subject: [PATCH 06/16] Clean up Parameters properly. If create_parameters() failed to write Application, AppParameters or AppDirectory it would try to clean up by deleting the Parameters key. The method used to do so was flawed. It would attempt to delete the key defined by the NSSM_REGISTRY macro but that macro contains a printf control string and is not suitable for standalone use. --- registry.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/registry.cpp b/registry.cpp index 5953f8a..e499933 100644 --- a/registry.cpp +++ b/registry.cpp @@ -32,19 +32,23 @@ int create_parameters(nssm_service_t *service, bool editing) { HKEY key = open_registry(service->name, KEY_WRITE); if (! key) return 1; + /* Remember parameters in case we need to delete them. */ + TCHAR registry[KEY_LENGTH]; + int ret = service_registry_path(service->name, true, 0, registry, _countof(registry)); + /* Try to create the parameters */ if (set_expand_string(key, NSSM_REG_EXE, service->exe)) { - RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY); + if (ret > 0) RegDeleteKey(HKEY_LOCAL_MACHINE, registry); RegCloseKey(key); return 2; } if (set_expand_string(key, NSSM_REG_FLAGS, service->flags)) { - RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY); + if (ret > 0) RegDeleteKey(HKEY_LOCAL_MACHINE, registry); RegCloseKey(key); return 3; } if (set_expand_string(key, NSSM_REG_DIR, service->dir)) { - RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY); + if (ret > 0) RegDeleteKey(HKEY_LOCAL_MACHINE, registry); RegCloseKey(key); return 4; } -- 2.20.1 From 095331018c46d251f6db151572bb1c2e76e911fa Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Wed, 17 Feb 2016 09:42:16 +0000 Subject: [PATCH 07/16] Fix crash on Windows XP. The pointer returned by GetEnvironmentStrings() on Unicode versions of Windows XP was to the raw environment block, not a copy as described in the documentation. Retrieving it into service->initial_env and keeping it around for the lifetime of the service could cause a crash. Instead we now copy the memory and immediately call FreeEnvironmentStrings(). Thanks Scott Ware. --- README.txt | 1 + env.cpp | 9 +++++++++ env.h | 1 + service.cpp | 4 ++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 12db7e8..d78d854 100644 --- a/README.txt +++ b/README.txt @@ -878,6 +878,7 @@ Thanks to Yuriy Lesiuk for suggesting setting the environment before querying the registry for parameters. Thanks to Gerald Haider for noticing that installing a service with NSSM in a path containing spaces was technically a security vulnerability. +Thanks to Scott Ware for reporting a crash saving the environment on XP 32-bit. Licence ------- diff --git a/env.cpp b/env.cpp index 083f46a..a1ca900 100644 --- a/env.cpp +++ b/env.cpp @@ -170,3 +170,12 @@ void duplicate_environment_strings(TCHAR *env) { duplicate_environment(newenv); HeapFree(GetProcessHeap(), 0, newenv); } + +/* Safely get a copy of the current environment. */ +TCHAR *copy_environment() { + TCHAR *rawenv = GetEnvironmentStrings(); + if (! rawenv) return NULL; + TCHAR *env = copy_environment_block(rawenv); + FreeEnvironmentStrings(rawenv); + return env; +} diff --git a/env.h b/env.h index 021ff4f..b690106 100644 --- a/env.h +++ b/env.h @@ -9,5 +9,6 @@ int clear_environment(); int duplicate_environment(TCHAR *); int test_environment(TCHAR *); void duplicate_environment_strings(TCHAR *); +TCHAR *copy_environment(); #endif diff --git a/service.cpp b/service.cpp index 6a3907e..b24eae2 100644 --- a/service.cpp +++ b/service.cpp @@ -774,7 +774,7 @@ void cleanup_nssm_service(nssm_service_t *service) { if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section); if (service->throttle_timer) CloseHandle(service->throttle_timer); if (service->hook_section_initialised) DeleteCriticalSection(&service->hook_section); - if (service->initial_env) FreeEnvironmentStrings(service->initial_env); + if (service->initial_env) HeapFree(GetProcessHeap(), 0, service->initial_env); HeapFree(GetProcessHeap(), 0, service); } @@ -1479,7 +1479,7 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) { service->hook_section_initialised = true; /* Remember our initial environment. */ - service->initial_env = GetEnvironmentStrings(); + service->initial_env = copy_environment(); /* Remember our creation time. */ if (get_process_creation_time(GetCurrentProcess(), &service->nssm_creation_time)) ZeroMemory(&service->nssm_creation_time, sizeof(service->nssm_creation_time)); -- 2.20.1 From 1ca188026d2d0b9a379cc1588eee314ca96528ab Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Wed, 17 Feb 2016 11:39:00 +0000 Subject: [PATCH 08/16] Compiler food. --- registry.cpp | 66 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/registry.cpp b/registry.cpp index e499933..c0ba8f1 100644 --- a/registry.cpp +++ b/registry.cpp @@ -2,6 +2,39 @@ extern const TCHAR *exit_action_strings[]; +static int service_registry_path(const TCHAR *service_name, bool parameters, const TCHAR *sub, TCHAR *buffer, unsigned long buflen) { + int ret; + + if (parameters) { + if (sub) ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS _T("\\%s"), service_name, sub); + else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS, service_name); + } + else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY, service_name); + + return ret; +} + +static HKEY open_registry_key(const TCHAR *registry, REGSAM sam, bool must_exist) { + HKEY key; + + if (sam & KEY_SET_VALUE) { + if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0); + return 0; + } + } + else { + long error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key); + if (error != ERROR_SUCCESS) { + if (error == ERROR_FILE_NOT_FOUND && ! must_exist) return 0; + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0); + return 0; + } + } + + return key; +} + int create_messages() { HKEY key; @@ -460,39 +493,6 @@ void override_milliseconds(TCHAR *service_name, HKEY key, TCHAR *value, unsigned if (! ok) *buffer = default_value; } -static int service_registry_path(const TCHAR *service_name, bool parameters, const TCHAR *sub, TCHAR *buffer, unsigned long buflen) { - int ret; - - if (parameters) { - if (sub) ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS _T("\\%s"), service_name, sub); - else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS, service_name); - } - else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY, service_name); - - return ret; -} - -static HKEY open_registry_key(const TCHAR *registry, REGSAM sam, bool must_exist) { - HKEY key; - - if (sam & KEY_SET_VALUE) { - if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0); - return 0; - } - } - else { - long error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key); - if (error != ERROR_SUCCESS) { - if (error == ERROR_FILE_NOT_FOUND && ! must_exist) return 0; - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0); - return 0; - } - } - - return key; -} - HKEY open_service_registry(const TCHAR *service_name, REGSAM sam, bool must_exist) { /* Get registry */ TCHAR registry[KEY_LENGTH]; -- 2.20.1 From 7f60a4c99c695b229e956a758b4bc29fc84b4ed4 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Mon, 22 Feb 2016 13:15:44 +0000 Subject: [PATCH 09/16] EventMessageFile should be unquoted. We were writing a quoted message source path to the registry. Stefan and Michael Scherer point out that it should be unquoted. --- README.txt | 1 + registry.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.txt b/README.txt index d78d854..02306e5 100644 --- a/README.txt +++ b/README.txt @@ -879,6 +879,7 @@ the registry for parameters. Thanks to Gerald Haider for noticing that installing a service with NSSM in a path containing spaces was technically a security vulnerability. Thanks to Scott Ware for reporting a crash saving the environment on XP 32-bit. +Thanks to Stefan and Michael Scherer for reporting a bug writing the event messages source. Licence ------- diff --git a/registry.cpp b/registry.cpp index c0ba8f1..20033f8 100644 --- a/registry.cpp +++ b/registry.cpp @@ -50,7 +50,7 @@ int create_messages() { } /* Get path of this program */ - const TCHAR *path = nssm_imagepath(); + const TCHAR *path = nssm_unquoted_imagepath(); /* Try to register the module but don't worry so much on failure */ RegSetValueEx(key, _T("EventMessageFile"), 0, REG_SZ, (const unsigned char *) path, (unsigned long) (_tcslen(path) + 1) * sizeof(TCHAR)); -- 2.20.1 From 3b12cdde22a08bdbc9e4566fcb83cf9d51324af4 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Tue, 23 Feb 2016 13:41:28 +0000 Subject: [PATCH 10/16] Fix compilation on VS2015. Paul Baxter and Mathias Breiner independently reported fixes to get the project to compile on Visual Studio 2015. --- README.txt | 1 + gui.cpp | 2 +- service.cpp | 2 +- settings.cpp | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.txt b/README.txt index 02306e5..6fb7d9a 100644 --- a/README.txt +++ b/README.txt @@ -880,6 +880,7 @@ Thanks to Gerald Haider for noticing that installing a service with NSSM in a path containing spaces was technically a security vulnerability. Thanks to Scott Ware for reporting a crash saving the environment on XP 32-bit. Thanks to Stefan and Michael Scherer for reporting a bug writing the event messages source. +Thanks to Paul Baxter and Mathias Breiner for help with Visual Studio 2015. Licence ------- diff --git a/gui.cpp b/gui.cpp index f47be58..a0dfaa2 100644 --- a/gui.cpp +++ b/gui.cpp @@ -1,6 +1,6 @@ #include "nssm.h" -static enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_DEPENDENCIES, NSSM_TAB_PROCESS, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_TAB_HOOKS, NSSM_NUM_TABS }; +static enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_DEPENDENCIES, NSSM_TAB_PROCESS, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_TAB_HOOKS, NSSM_NUM_TABS } nssm_tabs; static HWND tablist[NSSM_NUM_TABS]; static const TCHAR *hook_event_strings[] = { NSSM_HOOK_EVENT_START, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_EVENT_ROTATE, NULL }; static const TCHAR *hook_action_strings[] = { NSSM_HOOK_ACTION_PRE, NSSM_HOOK_ACTION_POST, NSSM_HOOK_ACTION_CHANGE, NSSM_HOOK_ACTION_RESUME, NULL }; diff --git a/service.cpp b/service.cpp index b24eae2..d864fa2 100644 --- a/service.cpp +++ b/service.cpp @@ -240,7 +240,7 @@ int affinity_string_to_mask(TCHAR *string, __int64 *mask) { return 0; } -inline unsigned long priority_mask() { +unsigned long priority_mask() { return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS; } diff --git a/settings.cpp b/settings.cpp index cbded45..4bbdd57 100644 --- a/settings.cpp +++ b/settings.cpp @@ -52,7 +52,7 @@ static int setting_set_number(const TCHAR *service_name, void *param, const TCHA } if (str_number(value->string, &number)) return -1; - if (default_value && number == (unsigned long) default_value) { + if (default_value && number == PtrToUlong(default_value)) { error = RegDeleteValue(key, name); if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); @@ -1058,7 +1058,7 @@ int get_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_ break; case REG_DWORD: - value->numeric = (unsigned long) setting->default_value; + value->numeric = PtrToUlong(setting->default_value); if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional); else ret = -1; break; -- 2.20.1 From 81b956b6ad12732a042bb2fffeedeca024dd7393 Mon Sep 17 00:00:00 2001 From: Mathias Breiner Date: Thu, 11 Feb 2016 19:31:26 +0100 Subject: [PATCH 11/16] Workaround for Resource Compiler warnings/errors under VS2010. VS2010 RC.exe yielded lots of warnings and some errors when including some (unnecessary) platform header files via nssm.h. So now including only what is really required. This includes a workaround for error RC2247 found here: http://stackoverflow.com/a/18317658 --- nssm.h | 11 +++++++++-- nssm.rc | Bin 67090 -> 67140 bytes 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/nssm.h b/nssm.h index 911804f..f500a33 100644 --- a/nssm.h +++ b/nssm.h @@ -33,13 +33,19 @@ #define DIR_LENGTH PATH_LENGTH - 12 #define _WIN32_WINNT 0x0500 + +#define APSTUDIO_HIDDEN_SYMBOLS +#include +#include +#undef APSTUDIO_HIDDEN_SYMBOLS +#include +#include +#ifndef NSSM_COMPILE_RC #include #include #include #include #include -#include -#include #include "service.h" #include "account.h" #include "console.h" @@ -53,6 +59,7 @@ #include "settings.h" #include "io.h" #include "gui.h" +#endif int str_equiv(const TCHAR *, const TCHAR *); void strip_basename(TCHAR *); diff --git a/nssm.rc b/nssm.rc index ee44017000699fb4c9030e615bfccbb2752fa3c3..7104cca5f9e846c65758b0d00208d5d830ad88bc 100644 GIT binary patch delta 59 zcmbQ#!*ZmDWkQK|3PUPG8bc;S9*|aG@M8!DLSKe>24@C;ARWNq$>77_3gibdI5*~Q J&1GE53IML14T%5% delta 15 XcmX@o!!oIdWkN~gvaQP)=dl6+H|qwW -- 2.20.1 From d47dca0afe81c1c8e75c518da5a51829e61ad1cd Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 28 Feb 2016 08:46:49 +0000 Subject: [PATCH 12/16] Use service_registry_path() in create_exit_action(). As a result of registry function refactoring we were constructing the default path to AppExit incorrectly. Using the function created for the purpose works correctly, unsurprisingly. Thanks Igor Zenkov. --- registry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry.cpp b/registry.cpp index 20033f8..228ef8d 100644 --- a/registry.cpp +++ b/registry.cpp @@ -189,7 +189,7 @@ int create_parameters(nssm_service_t *service, bool editing) { int create_exit_action(TCHAR *service_name, const TCHAR *action_string, bool editing) { /* Get registry */ TCHAR registry[KEY_LENGTH]; - if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, NSSM_REG_EXIT) < 0) { + if (service_registry_path(service_name, true, NSSM_REG_EXIT, registry, _countof(registry)) < 0) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REG_EXIT"), _T("create_exit_action()"), 0); return 1; } -- 2.20.1 From b6f7fe3b11fd130f46eb6b5009391cc3bb0cad49 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 28 Feb 2016 08:59:11 +0000 Subject: [PATCH 13/16] Use CRLF consistently. The mismatch of line endings has long been an annoyance. Thanks Mathias Breiner for advocating finally doing something about it. --- account.cpp | 692 ++++++++-------- account.h | 48 +- console.cpp | 350 ++++---- console.h | 14 +- env.cpp | 362 ++++---- env.h | 28 +- hook.cpp | 804 +++++++++--------- hook.h | 150 ++-- imports.cpp | 184 ++-- imports.h | 50 +- messages.mc | Bin 161060 -> 167372 bytes process.cpp | 700 ++++++++-------- process.h | 72 +- settings.cpp | 2260 +++++++++++++++++++++++++------------------------- settings.h | 96 +-- 15 files changed, 2905 insertions(+), 2905 deletions(-) diff --git a/account.cpp b/account.cpp index 70bfa3e..60d1fc6 100644 --- a/account.cpp +++ b/account.cpp @@ -1,346 +1,346 @@ -#include "nssm.h" - -#include - -extern imports_t imports; - -/* Open Policy object. */ -int open_lsa_policy(LSA_HANDLE *policy) { - LSA_OBJECT_ATTRIBUTES attributes; - ZeroMemory(&attributes, sizeof(attributes)); - - NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy); - if (status) { - print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status))); - return 1; - } - - return 0; -} - -/* Look up SID for an account. */ -int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) { - LSA_HANDLE handle; - if (! policy) { - policy = &handle; - if (open_lsa_policy(policy)) return 1; - } - - /* - LsaLookupNames() can't look up .\username but can look up - %COMPUTERNAME%\username. ChangeServiceConfig() writes .\username to the - registry when %COMPUTERNAME%\username is a passed as a parameter. We - need to preserve .\username when calling ChangeServiceConfig() without - changing the username, but expand to %COMPUTERNAME%\username when calling - LsaLookupNames(). - */ - TCHAR *expanded; - unsigned long expandedlen; - if (_tcsnicmp(_T(".\\"), username, 2)) { - expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR); - expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen); - if (! expanded) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid")); - if (policy == &handle) LsaClose(handle); - return 2; - } - memmove(expanded, username, expandedlen); - } - else { - TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1]; - expandedlen = _countof(computername); - GetComputerName(computername, &expandedlen); - expandedlen += (unsigned long) _tcslen(username); - - expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR)); - if (! expanded) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid")); - if (policy == &handle) LsaClose(handle); - return 2; - } - _sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2); - } - - LSA_UNICODE_STRING lsa_username; -#ifdef UNICODE - lsa_username.Buffer = (wchar_t *) expanded; - lsa_username.Length = (unsigned short) _tcslen(expanded) * sizeof(TCHAR); - lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR); -#else - size_t buflen; - mbstowcs_s(&buflen, NULL, 0, expanded, _TRUNCATE); - lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t); - lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t); - lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength); - if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, expanded, _TRUNCATE); - else { - if (policy == &handle) LsaClose(handle); - HeapFree(GetProcessHeap(), 0, expanded); - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()")); - return 4; - } -#endif - - LSA_REFERENCED_DOMAIN_LIST *translated_domains; - LSA_TRANSLATED_SID *translated_sid; - NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid); -#ifndef UNICODE - HeapFree(GetProcessHeap(), 0, lsa_username.Buffer); -#endif - HeapFree(GetProcessHeap(), 0, expanded); - if (policy == &handle) LsaClose(handle); - if (status) { - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_sid); - print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status))); - return 5; - } - - if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) { - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_sid); - print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); - return 6; - } - - LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex]; - if (! trust || ! IsValidSid(trust->Sid)) { - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_sid); - print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); - return 7; - } - - /* GetSidSubAuthority*() return pointers! */ - unsigned char *n = GetSidSubAuthorityCount(trust->Sid); - - /* Convert translated SID to SID. */ - *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1)); - if (! *sid) { - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_sid); - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid")); - return 8; - } - - unsigned long error; - if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) { - error = GetLastError(); - HeapFree(GetProcessHeap(), 0, *sid); - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_sid); - print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error)); - return 9; - } - - for (unsigned char i = 0; i <= *n; i++) { - unsigned long *sub = GetSidSubAuthority(*sid, i); - if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i); - else *sub = translated_sid->RelativeId; - } - - int ret = 0; - if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) { - print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); - ret = 10; - } - - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_sid); - - return ret; -} - -int username_sid(const TCHAR *username, SID **sid) { - return username_sid(username, sid, 0); -} - -int canonicalise_username(const TCHAR *username, TCHAR **canon) { - LSA_HANDLE policy; - if (open_lsa_policy(&policy)) return 1; - - SID *sid; - if (username_sid(username, &sid, &policy)) return 2; - PSID sids = { sid }; - - LSA_REFERENCED_DOMAIN_LIST *translated_domains; - LSA_TRANSLATED_NAME *translated_name; - NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name); - if (status) { - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_name); - print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status))); - return 3; - } - - LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex]; - LSA_UNICODE_STRING lsa_canon; - lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t); - lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t); - lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength); - if (! lsa_canon.Buffer) { - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_name); - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid")); - return 9; - } - - /* Buffer is wchar_t but Length is in bytes. */ - memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length); - memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t)); - memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length); - -#ifdef UNICODE - *canon = lsa_canon.Buffer; -#else - size_t buflen; - wcstombs_s(&buflen, NULL, 0, lsa_canon.Buffer, _TRUNCATE); - *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen); - if (! *canon) { - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_name); - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid")); - return 10; - } - wcstombs_s(&buflen, *canon, buflen, lsa_canon.Buffer, _TRUNCATE); - HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer); -#endif - - LsaFreeMemory(translated_domains); - LsaFreeMemory(translated_name); - - return 0; -} - -/* Do two usernames map to the same SID? */ -int username_equiv(const TCHAR *a, const TCHAR *b) { - SID *sid_a, *sid_b; - if (username_sid(a, &sid_a)) return 0; - - if (username_sid(b, &sid_b)) { - FreeSid(sid_a); - return 0; - } - - int ret = 0; - if (EqualSid(sid_a, sid_b)) ret = 1; - - FreeSid(sid_a); - FreeSid(sid_b); - - return ret; -} - -/* Does the username represent the LocalSystem account? */ -int is_localsystem(const TCHAR *username) { - if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1; - if (! imports.IsWellKnownSid) return 0; - - SID *sid; - if (username_sid(username, &sid)) return 0; - - int ret = 0; - if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1; - - FreeSid(sid); - - return ret; -} - -/* - Get well-known alias for LocalSystem and friends. - Returns a pointer to a static string. DO NOT try to free it. -*/ -const TCHAR *well_known_sid(SID *sid) { - if (! imports.IsWellKnownSid) return 0; - if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT; - if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT; - if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT; - return 0; -} - -const TCHAR *well_known_username(const TCHAR *username) { - if (! username) return NSSM_LOCALSYSTEM_ACCOUNT; - if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT; - SID *sid; - if (username_sid(username, &sid)) return 0; - - const TCHAR *well_known = well_known_sid(sid); - FreeSid(sid); - - return well_known; -} - -int grant_logon_as_service(const TCHAR *username) { - if (! username) return 0; - - /* Open Policy object. */ - LSA_OBJECT_ATTRIBUTES attributes; - ZeroMemory(&attributes, sizeof(attributes)); - - LSA_HANDLE policy; - NTSTATUS status; - - if (open_lsa_policy(&policy)) return 1; - - /* Look up SID for the account. */ - SID *sid; - if (username_sid(username, &sid, &policy)) { - LsaClose(policy); - return 2; - } - - /* - Shouldn't happen because it should have been checked before callling this function. - */ - if (well_known_sid(sid)) { - LsaClose(policy); - return 3; - } - - /* Check if the SID has the "Log on as a service" right. */ - LSA_UNICODE_STRING lsa_right; - lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT; - lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t); - lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t); - - LSA_UNICODE_STRING *rights; - unsigned long count = ~0; - status = LsaEnumerateAccountRights(policy, sid, &rights, &count); - if (status) { - /* - If the account has no rights set LsaEnumerateAccountRights() will return - STATUS_OBJECT_NAME_NOT_FOUND and set count to 0. - */ - unsigned long error = LsaNtStatusToWinError(status); - if (error != ERROR_FILE_NOT_FOUND) { - FreeSid(sid); - LsaClose(policy); - print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error)); - return 4; - } - } - - for (unsigned long i = 0; i < count; i++) { - if (rights[i].Length != lsa_right.Length) continue; - if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue; - /* The SID has the right. */ - FreeSid(sid); - LsaFreeMemory(rights); - LsaClose(policy); - return 0; - } - LsaFreeMemory(rights); - - /* Add the right. */ - status = LsaAddAccountRights(policy, sid, &lsa_right, 1); - FreeSid(sid); - LsaClose(policy); - if (status) { - print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status))); - return 5; - } - - print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username); - return 0; -} +#include "nssm.h" + +#include + +extern imports_t imports; + +/* Open Policy object. */ +int open_lsa_policy(LSA_HANDLE *policy) { + LSA_OBJECT_ATTRIBUTES attributes; + ZeroMemory(&attributes, sizeof(attributes)); + + NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy); + if (status) { + print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status))); + return 1; + } + + return 0; +} + +/* Look up SID for an account. */ +int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) { + LSA_HANDLE handle; + if (! policy) { + policy = &handle; + if (open_lsa_policy(policy)) return 1; + } + + /* + LsaLookupNames() can't look up .\username but can look up + %COMPUTERNAME%\username. ChangeServiceConfig() writes .\username to the + registry when %COMPUTERNAME%\username is a passed as a parameter. We + need to preserve .\username when calling ChangeServiceConfig() without + changing the username, but expand to %COMPUTERNAME%\username when calling + LsaLookupNames(). + */ + TCHAR *expanded; + unsigned long expandedlen; + if (_tcsnicmp(_T(".\\"), username, 2)) { + expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR); + expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen); + if (! expanded) { + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid")); + if (policy == &handle) LsaClose(handle); + return 2; + } + memmove(expanded, username, expandedlen); + } + else { + TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1]; + expandedlen = _countof(computername); + GetComputerName(computername, &expandedlen); + expandedlen += (unsigned long) _tcslen(username); + + expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR)); + if (! expanded) { + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid")); + if (policy == &handle) LsaClose(handle); + return 2; + } + _sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2); + } + + LSA_UNICODE_STRING lsa_username; +#ifdef UNICODE + lsa_username.Buffer = (wchar_t *) expanded; + lsa_username.Length = (unsigned short) _tcslen(expanded) * sizeof(TCHAR); + lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR); +#else + size_t buflen; + mbstowcs_s(&buflen, NULL, 0, expanded, _TRUNCATE); + lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t); + lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t); + lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength); + if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, expanded, _TRUNCATE); + else { + if (policy == &handle) LsaClose(handle); + HeapFree(GetProcessHeap(), 0, expanded); + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()")); + return 4; + } +#endif + + LSA_REFERENCED_DOMAIN_LIST *translated_domains; + LSA_TRANSLATED_SID *translated_sid; + NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid); +#ifndef UNICODE + HeapFree(GetProcessHeap(), 0, lsa_username.Buffer); +#endif + HeapFree(GetProcessHeap(), 0, expanded); + if (policy == &handle) LsaClose(handle); + if (status) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status))); + return 5; + } + + if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); + return 6; + } + + LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex]; + if (! trust || ! IsValidSid(trust->Sid)) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); + return 7; + } + + /* GetSidSubAuthority*() return pointers! */ + unsigned char *n = GetSidSubAuthorityCount(trust->Sid); + + /* Convert translated SID to SID. */ + *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1)); + if (! *sid) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid")); + return 8; + } + + unsigned long error; + if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) { + error = GetLastError(); + HeapFree(GetProcessHeap(), 0, *sid); + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error)); + return 9; + } + + for (unsigned char i = 0; i <= *n; i++) { + unsigned long *sub = GetSidSubAuthority(*sid, i); + if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i); + else *sub = translated_sid->RelativeId; + } + + int ret = 0; + if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) { + print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); + ret = 10; + } + + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + + return ret; +} + +int username_sid(const TCHAR *username, SID **sid) { + return username_sid(username, sid, 0); +} + +int canonicalise_username(const TCHAR *username, TCHAR **canon) { + LSA_HANDLE policy; + if (open_lsa_policy(&policy)) return 1; + + SID *sid; + if (username_sid(username, &sid, &policy)) return 2; + PSID sids = { sid }; + + LSA_REFERENCED_DOMAIN_LIST *translated_domains; + LSA_TRANSLATED_NAME *translated_name; + NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name); + if (status) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_name); + print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status))); + return 3; + } + + LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex]; + LSA_UNICODE_STRING lsa_canon; + lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t); + lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t); + lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength); + if (! lsa_canon.Buffer) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_name); + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid")); + return 9; + } + + /* Buffer is wchar_t but Length is in bytes. */ + memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length); + memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t)); + memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length); + +#ifdef UNICODE + *canon = lsa_canon.Buffer; +#else + size_t buflen; + wcstombs_s(&buflen, NULL, 0, lsa_canon.Buffer, _TRUNCATE); + *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen); + if (! *canon) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_name); + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid")); + return 10; + } + wcstombs_s(&buflen, *canon, buflen, lsa_canon.Buffer, _TRUNCATE); + HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer); +#endif + + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_name); + + return 0; +} + +/* Do two usernames map to the same SID? */ +int username_equiv(const TCHAR *a, const TCHAR *b) { + SID *sid_a, *sid_b; + if (username_sid(a, &sid_a)) return 0; + + if (username_sid(b, &sid_b)) { + FreeSid(sid_a); + return 0; + } + + int ret = 0; + if (EqualSid(sid_a, sid_b)) ret = 1; + + FreeSid(sid_a); + FreeSid(sid_b); + + return ret; +} + +/* Does the username represent the LocalSystem account? */ +int is_localsystem(const TCHAR *username) { + if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1; + if (! imports.IsWellKnownSid) return 0; + + SID *sid; + if (username_sid(username, &sid)) return 0; + + int ret = 0; + if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1; + + FreeSid(sid); + + return ret; +} + +/* + Get well-known alias for LocalSystem and friends. + Returns a pointer to a static string. DO NOT try to free it. +*/ +const TCHAR *well_known_sid(SID *sid) { + if (! imports.IsWellKnownSid) return 0; + if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT; + if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT; + if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT; + return 0; +} + +const TCHAR *well_known_username(const TCHAR *username) { + if (! username) return NSSM_LOCALSYSTEM_ACCOUNT; + if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT; + SID *sid; + if (username_sid(username, &sid)) return 0; + + const TCHAR *well_known = well_known_sid(sid); + FreeSid(sid); + + return well_known; +} + +int grant_logon_as_service(const TCHAR *username) { + if (! username) return 0; + + /* Open Policy object. */ + LSA_OBJECT_ATTRIBUTES attributes; + ZeroMemory(&attributes, sizeof(attributes)); + + LSA_HANDLE policy; + NTSTATUS status; + + if (open_lsa_policy(&policy)) return 1; + + /* Look up SID for the account. */ + SID *sid; + if (username_sid(username, &sid, &policy)) { + LsaClose(policy); + return 2; + } + + /* + Shouldn't happen because it should have been checked before callling this function. + */ + if (well_known_sid(sid)) { + LsaClose(policy); + return 3; + } + + /* Check if the SID has the "Log on as a service" right. */ + LSA_UNICODE_STRING lsa_right; + lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT; + lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t); + lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t); + + LSA_UNICODE_STRING *rights; + unsigned long count = ~0; + status = LsaEnumerateAccountRights(policy, sid, &rights, &count); + if (status) { + /* + If the account has no rights set LsaEnumerateAccountRights() will return + STATUS_OBJECT_NAME_NOT_FOUND and set count to 0. + */ + unsigned long error = LsaNtStatusToWinError(status); + if (error != ERROR_FILE_NOT_FOUND) { + FreeSid(sid); + LsaClose(policy); + print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error)); + return 4; + } + } + + for (unsigned long i = 0; i < count; i++) { + if (rights[i].Length != lsa_right.Length) continue; + if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue; + /* The SID has the right. */ + FreeSid(sid); + LsaFreeMemory(rights); + LsaClose(policy); + return 0; + } + LsaFreeMemory(rights); + + /* Add the right. */ + status = LsaAddAccountRights(policy, sid, &lsa_right, 1); + FreeSid(sid); + LsaClose(policy); + if (status) { + print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status))); + return 5; + } + + print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username); + return 0; +} diff --git a/account.h b/account.h index e6b40f0..232e1f9 100644 --- a/account.h +++ b/account.h @@ -1,24 +1,24 @@ -#ifndef ACCOUNT_H -#define ACCOUNT_H - -#include - -/* Not really an account. The canonical name is NT Authority\System. */ -#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem") -/* Other well-known accounts which can start a service without a password. */ -#define NSSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService") -#define NSSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService") -/* This is explicitly a wide string. */ -#define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight" - -int open_lsa_policy(LSA_HANDLE *); -int username_sid(const TCHAR *, SID **, LSA_HANDLE *); -int username_sid(const TCHAR *, SID **); -int username_equiv(const TCHAR *, const TCHAR *); -int canonicalise_username(const TCHAR *, TCHAR **); -int is_localsystem(const TCHAR *); -const TCHAR *well_known_sid(SID *); -const TCHAR *well_known_username(const TCHAR *); -int grant_logon_as_service(const TCHAR *); - -#endif +#ifndef ACCOUNT_H +#define ACCOUNT_H + +#include + +/* Not really an account. The canonical name is NT Authority\System. */ +#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem") +/* Other well-known accounts which can start a service without a password. */ +#define NSSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService") +#define NSSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService") +/* This is explicitly a wide string. */ +#define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight" + +int open_lsa_policy(LSA_HANDLE *); +int username_sid(const TCHAR *, SID **, LSA_HANDLE *); +int username_sid(const TCHAR *, SID **); +int username_equiv(const TCHAR *, const TCHAR *); +int canonicalise_username(const TCHAR *, TCHAR **); +int is_localsystem(const TCHAR *); +const TCHAR *well_known_sid(SID *); +const TCHAR *well_known_username(const TCHAR *); +int grant_logon_as_service(const TCHAR *); + +#endif diff --git a/console.cpp b/console.cpp index 66c99ea..9a97729 100644 --- a/console.cpp +++ b/console.cpp @@ -1,175 +1,175 @@ -#include "nssm.h" - -/* See if we were launched from a console window. */ -void check_console() { - /* If we're running in a service context there will be no console window. */ - HWND console = GetConsoleWindow(); - if (! console) return; - - unsigned long pid; - if (! GetWindowThreadProcessId(console, &pid)) return; - - /* - If the process associated with the console window handle is the same as - this process, we were not launched from an existing console. The user - probably double-clicked our executable. - */ - if (GetCurrentProcessId() != pid) return; - - /* We close our new console so that subsequent messages appear in a popup. */ - FreeConsole(); -} - -/* Helpers for drawing the banner. */ -static inline void block(unsigned int a, short x, short y, unsigned long n) { - HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); - TCHAR s = _T(' '); - - unsigned long out; - COORD c = { x, y }; - FillConsoleOutputAttribute(h, a, n, c, &out); - FillConsoleOutputCharacter(h, s, n, c, &out); -} - -static inline void R(short x, short y, unsigned long n) { - block(BACKGROUND_RED | BACKGROUND_INTENSITY, x, y, n); -} - -static inline void r(short x, short y, unsigned long n) { - block(BACKGROUND_RED, x, y, n); -} - -static inline void b(short x, short y, unsigned long n) { - block(0, x, y, n); -} - -void alloc_console(nssm_service_t *service) { - if (service->no_console) return; - - AllocConsole(); - - /* Disable accidental closure. */ - HWND window = GetConsoleWindow(); - HMENU menu = GetSystemMenu(window, false); - EnableMenuItem(menu, SC_CLOSE, MF_GRAYED); - - /* Set a title like "[NSSM] Jenkins" */ - TCHAR displayname[SERVICE_NAME_LENGTH]; - unsigned long len = _countof(displayname); - SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT); - if (services) { - if (! GetServiceDisplayName(services, service->name, displayname, &len)) ZeroMemory(displayname, sizeof(displayname)); - CloseServiceHandle(services); - } - if (! displayname[0]) _sntprintf_s(displayname, _countof(displayname), _TRUNCATE, _T("%s"), service->name); - - TCHAR title[65535]; - _sntprintf_s(title, _countof(title), _TRUNCATE, _T("[%s] %s"), NSSM, displayname); - SetConsoleTitle(title); - - /* Draw the NSSM logo on the console window. */ - short y = 0; - - b(0, y, 80); - y++; - - b(0, y, 80); - y++; - - b(0, y, 80); - y++; - - b(0, y, 80); - y++; - - b(0, y, 80); - r(18, y, 5); r(28, y, 4); r(41, y, 4); r(68, y, 1); - R(6, y, 5); R(19, y, 4); R(29, y, 1); R(32, y, 3); R(42, y, 1); R(45, y, 3); R(52, y, 5); R(69, y, 4); - y++; - - b(0, y, 80); - r(8, y, 4); r(20, y, 1); r(28, y, 1); r(33, y, 3); r(41, y, 1); r(46, y, 3); r (57, y, 1); - R(9, y, 2); R(21, y, 1); R(27, y, 1); R(34, y, 1); R(40, y, 1); R(47, y, 1); R(54, y, 3); R(68, y, 3); - y++; - - b(0, y, 80); - r(12, y, 1); r(20, y, 1); r(26, y, 1); r(34, y, 2); r(39, y, 1); r(47, y, 2); r(67, y, 2); - R(9, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(20, y, 1); r(26, y, 1); r (35, y, 1); r(39, y, 1); r(48, y, 1); r(58, y, 1); - R(10, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(56, y, 1); r(66, y, 2); - R(11, y, 3); R(21, y, 1); R(26, y, 2); R(39, y, 2); R(54, y, 1); R(57, y, 2); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(26, y, 1); r(39, y, 1); r(59, y, 1); - R(12, y, 3); R(21, y, 1); R(27, y, 2); R(40, y, 2); R(54, y, 1); R(57, y, 2); R(66, y, 1); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(12, y, 4); r(30, y, 1); r(43, y, 1); r(57, y, 1); r(65, y, 2); - R(13, y, 2); R(21, y, 1); R(27, y, 3); R(40, y, 3); R(54, y, 1); R(58, y, 2); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(13, y, 4); r(27, y, 7); r(40, y, 7); - R(14, y, 2); R(21, y, 1); R(28, y, 5); R(41, y, 5); R(54, y, 1); R(58, y, 2); R(65, y, 1); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(60, y, 1); r(65, y, 1); - R(14, y, 3); R(21, y, 1); R(29, y, 6); R(42, y, 6); R(54, y, 1); R(58, y, 2); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(31, y, 1); r(44, y, 1); r(58, y, 1); r(64, y, 1); - R(15, y, 3); R(21, y, 1); R(32, y, 4); R(45, y, 4); R(54, y, 1); R(59, y, 2); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(33, y, 1); r(46, y, 1); r(61, y, 1); r(64, y, 1); - R(16, y, 3); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(59, y, 2); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(16, y, 4); r(36, y, 1); r(49, y, 1); r(59, y, 1); r(63, y, 1); - R(17, y, 2); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(60, y, 2); R(69, y, 2); - y++; - - b(0, y, 80); - r(9, y, 1); r(17, y, 4); r(26, y, 1); r(36, y, 1); r(39, y, 1); r(49, y, 1); - R(18, y, 2); R(21, y, 1); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 2); R(63, y, 1); R(69, y, 2); - y++; - - b(0, y, 80); - r(26, y, 2); r(39, y, 2); r(63, y, 1); - R(9, y, 1); R(18, y, 4); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 3); R(69, y, 2); - y++; - - b(0, y, 80); - r(34, y, 1); r(47, y, 1); r(60, y, 1); - R(9, y, 1); R(19, y, 3); R(26, y, 2); R(35, y, 1); R(39, y, 2); R(48, y, 1); R(54, y, 1); R(61, y, 2); R(69, y, 2); - y++; - - b(0, y, 80); - r(8, y, 1); r(35, y, 1); r(48, y, 1); r(62, y, 1); r(71, y, 1); - R(9, y, 1); R(20, y, 2); R(26, y, 3); R(34, y, 1); R(39, y, 3); R(47, y, 1); R(54, y, 1); R(61, y, 1); R(69, y, 2); - y++; - - b(0, y, 80); - r(11, y, 1); r(26, y, 1); r(28, y, 5); r(39, y, 1); r(41, y, 5); r(51, y, 7); r(61, y, 1); r(66, y, 8); - R(7, y, 4); R(21, y, 1); R(29, y, 1); R(33, y, 1); R(42, y, 1); R(46, y, 1); R(52, y, 5); R(67, y, 7); - y++; - - b(0, y, 80); - y++; - - b(0, y, 80); - y++; -} +#include "nssm.h" + +/* See if we were launched from a console window. */ +void check_console() { + /* If we're running in a service context there will be no console window. */ + HWND console = GetConsoleWindow(); + if (! console) return; + + unsigned long pid; + if (! GetWindowThreadProcessId(console, &pid)) return; + + /* + If the process associated with the console window handle is the same as + this process, we were not launched from an existing console. The user + probably double-clicked our executable. + */ + if (GetCurrentProcessId() != pid) return; + + /* We close our new console so that subsequent messages appear in a popup. */ + FreeConsole(); +} + +/* Helpers for drawing the banner. */ +static inline void block(unsigned int a, short x, short y, unsigned long n) { + HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); + TCHAR s = _T(' '); + + unsigned long out; + COORD c = { x, y }; + FillConsoleOutputAttribute(h, a, n, c, &out); + FillConsoleOutputCharacter(h, s, n, c, &out); +} + +static inline void R(short x, short y, unsigned long n) { + block(BACKGROUND_RED | BACKGROUND_INTENSITY, x, y, n); +} + +static inline void r(short x, short y, unsigned long n) { + block(BACKGROUND_RED, x, y, n); +} + +static inline void b(short x, short y, unsigned long n) { + block(0, x, y, n); +} + +void alloc_console(nssm_service_t *service) { + if (service->no_console) return; + + AllocConsole(); + + /* Disable accidental closure. */ + HWND window = GetConsoleWindow(); + HMENU menu = GetSystemMenu(window, false); + EnableMenuItem(menu, SC_CLOSE, MF_GRAYED); + + /* Set a title like "[NSSM] Jenkins" */ + TCHAR displayname[SERVICE_NAME_LENGTH]; + unsigned long len = _countof(displayname); + SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT); + if (services) { + if (! GetServiceDisplayName(services, service->name, displayname, &len)) ZeroMemory(displayname, sizeof(displayname)); + CloseServiceHandle(services); + } + if (! displayname[0]) _sntprintf_s(displayname, _countof(displayname), _TRUNCATE, _T("%s"), service->name); + + TCHAR title[65535]; + _sntprintf_s(title, _countof(title), _TRUNCATE, _T("[%s] %s"), NSSM, displayname); + SetConsoleTitle(title); + + /* Draw the NSSM logo on the console window. */ + short y = 0; + + b(0, y, 80); + y++; + + b(0, y, 80); + y++; + + b(0, y, 80); + y++; + + b(0, y, 80); + y++; + + b(0, y, 80); + r(18, y, 5); r(28, y, 4); r(41, y, 4); r(68, y, 1); + R(6, y, 5); R(19, y, 4); R(29, y, 1); R(32, y, 3); R(42, y, 1); R(45, y, 3); R(52, y, 5); R(69, y, 4); + y++; + + b(0, y, 80); + r(8, y, 4); r(20, y, 1); r(28, y, 1); r(33, y, 3); r(41, y, 1); r(46, y, 3); r (57, y, 1); + R(9, y, 2); R(21, y, 1); R(27, y, 1); R(34, y, 1); R(40, y, 1); R(47, y, 1); R(54, y, 3); R(68, y, 3); + y++; + + b(0, y, 80); + r(12, y, 1); r(20, y, 1); r(26, y, 1); r(34, y, 2); r(39, y, 1); r(47, y, 2); r(67, y, 2); + R(9, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(20, y, 1); r(26, y, 1); r (35, y, 1); r(39, y, 1); r(48, y, 1); r(58, y, 1); + R(10, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(56, y, 1); r(66, y, 2); + R(11, y, 3); R(21, y, 1); R(26, y, 2); R(39, y, 2); R(54, y, 1); R(57, y, 2); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(26, y, 1); r(39, y, 1); r(59, y, 1); + R(12, y, 3); R(21, y, 1); R(27, y, 2); R(40, y, 2); R(54, y, 1); R(57, y, 2); R(66, y, 1); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(12, y, 4); r(30, y, 1); r(43, y, 1); r(57, y, 1); r(65, y, 2); + R(13, y, 2); R(21, y, 1); R(27, y, 3); R(40, y, 3); R(54, y, 1); R(58, y, 2); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(13, y, 4); r(27, y, 7); r(40, y, 7); + R(14, y, 2); R(21, y, 1); R(28, y, 5); R(41, y, 5); R(54, y, 1); R(58, y, 2); R(65, y, 1); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(60, y, 1); r(65, y, 1); + R(14, y, 3); R(21, y, 1); R(29, y, 6); R(42, y, 6); R(54, y, 1); R(58, y, 2); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(31, y, 1); r(44, y, 1); r(58, y, 1); r(64, y, 1); + R(15, y, 3); R(21, y, 1); R(32, y, 4); R(45, y, 4); R(54, y, 1); R(59, y, 2); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(33, y, 1); r(46, y, 1); r(61, y, 1); r(64, y, 1); + R(16, y, 3); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(59, y, 2); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(16, y, 4); r(36, y, 1); r(49, y, 1); r(59, y, 1); r(63, y, 1); + R(17, y, 2); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(60, y, 2); R(69, y, 2); + y++; + + b(0, y, 80); + r(9, y, 1); r(17, y, 4); r(26, y, 1); r(36, y, 1); r(39, y, 1); r(49, y, 1); + R(18, y, 2); R(21, y, 1); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 2); R(63, y, 1); R(69, y, 2); + y++; + + b(0, y, 80); + r(26, y, 2); r(39, y, 2); r(63, y, 1); + R(9, y, 1); R(18, y, 4); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 3); R(69, y, 2); + y++; + + b(0, y, 80); + r(34, y, 1); r(47, y, 1); r(60, y, 1); + R(9, y, 1); R(19, y, 3); R(26, y, 2); R(35, y, 1); R(39, y, 2); R(48, y, 1); R(54, y, 1); R(61, y, 2); R(69, y, 2); + y++; + + b(0, y, 80); + r(8, y, 1); r(35, y, 1); r(48, y, 1); r(62, y, 1); r(71, y, 1); + R(9, y, 1); R(20, y, 2); R(26, y, 3); R(34, y, 1); R(39, y, 3); R(47, y, 1); R(54, y, 1); R(61, y, 1); R(69, y, 2); + y++; + + b(0, y, 80); + r(11, y, 1); r(26, y, 1); r(28, y, 5); r(39, y, 1); r(41, y, 5); r(51, y, 7); r(61, y, 1); r(66, y, 8); + R(7, y, 4); R(21, y, 1); R(29, y, 1); R(33, y, 1); R(42, y, 1); R(46, y, 1); R(52, y, 5); R(67, y, 7); + y++; + + b(0, y, 80); + y++; + + b(0, y, 80); + y++; +} diff --git a/console.h b/console.h index 8aa8966..cbccd3c 100644 --- a/console.h +++ b/console.h @@ -1,7 +1,7 @@ -#ifndef CONSOLE_H -#define CONSOLE_H - -void check_console(); -void alloc_console(nssm_service_t *); - -#endif +#ifndef CONSOLE_H +#define CONSOLE_H + +void check_console(); +void alloc_console(nssm_service_t *); + +#endif diff --git a/env.cpp b/env.cpp index a1ca900..4427d91 100644 --- a/env.cpp +++ b/env.cpp @@ -1,181 +1,181 @@ -#include "nssm.h" - -/* Copy an environment block. */ -TCHAR *copy_environment_block(TCHAR *env) { - unsigned long len; - - if (! env) return 0; - for (len = 0; env[len]; len++) while (env[len]) len++; - if (! len++) return 0; - - TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); - if (! newenv) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("copy_environment_block()"), 0); - return 0; - } - - memmove(newenv, env, len * sizeof(TCHAR)); - return newenv; -} - -/* - The environment block starts with variables of the form - =C:=C:\Windows\System32 which we ignore. -*/ -TCHAR *useful_environment(TCHAR *rawenv) { - TCHAR *env = rawenv; - - if (env) { - while (*env == _T('=')) { - for ( ; *env; env++); - env++; - } - } - - return env; -} - -/* Expand an environment variable. Must call HeapFree() on the result. */ -TCHAR *expand_environment_string(TCHAR *string) { - unsigned long len; - - len = ExpandEnvironmentStrings(string, 0, 0); - if (! len) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0); - return 0; - } - - TCHAR *ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); - if (! ret) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("ExpandEnvironmentStrings()"), _T("expand_environment_string"), 0); - return 0; - } - - if (! ExpandEnvironmentStrings(string, ret, len)) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0); - HeapFree(GetProcessHeap(), 0, ret); - return 0; - } - - return ret; -} - -/* - Set all the environment variables from an environment block in the current - environment or remove all the variables in the block from the current - environment. -*/ -static int set_environment_block(TCHAR *env, bool set) { - int ret = 0; - - TCHAR *s, *t; - for (s = env; *s; s++) { - for (t = s; *t && *t != _T('='); t++); - if (*t == _T('=')) { - *t = _T('\0'); - if (set) { - TCHAR *expanded = expand_environment_string(++t); - if (expanded) { - if (! SetEnvironmentVariable(s, expanded)) ret++; - HeapFree(GetProcessHeap(), 0, expanded); - } - else { - if (! SetEnvironmentVariable(s, t)) ret++; - } - } - else { - if (! SetEnvironmentVariable(s, NULL)) ret++; - } - for (t++; *t; t++); - } - s = t; - } - - return ret; -} - -int set_environment_block(TCHAR *env) { - return set_environment_block(env, true); -} - -static int unset_environment_block(TCHAR *env) { - return set_environment_block(env, false); -} - -/* Remove all variables from the process environment. */ -int clear_environment() { - TCHAR *rawenv = GetEnvironmentStrings(); - TCHAR *env = useful_environment(rawenv); - - int ret = unset_environment_block(env); - - if (rawenv) FreeEnvironmentStrings(rawenv); - - return ret; -} - -/* Set the current environment to exactly duplicate an environment block. */ -int duplicate_environment(TCHAR *rawenv) { - int ret = clear_environment(); - TCHAR *env = useful_environment(rawenv); - ret += set_environment_block(env); - return ret; -} - -/* - Verify an environment block. - Returns: 1 if environment is invalid. - 0 if environment is OK. - -1 on error. -*/ -int test_environment(TCHAR *env) { - TCHAR *path = (TCHAR *) nssm_imagepath(); - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - PROCESS_INFORMATION pi; - ZeroMemory(&pi, sizeof(pi)); - unsigned long flags = CREATE_SUSPENDED; -#ifdef UNICODE - flags |= CREATE_UNICODE_ENVIRONMENT; -#endif - - /* - Try to relaunch ourselves but with the candidate environment set. - Assuming no solar flare activity, the only reason this would fail is if - the environment were invalid. - */ - if (CreateProcess(0, path, 0, 0, 0, flags, env, 0, &si, &pi)) { - TerminateProcess(pi.hProcess, 0); - } - else { - unsigned long error = GetLastError(); - if (error == ERROR_INVALID_PARAMETER) return 1; - else return -1; - } - - return 0; -} - -/* - Duplicate an environment block returned by GetEnvironmentStrings(). - Since such a block is by definition readonly, and duplicate_environment() - modifies its inputs, this function takes a copy of the input and operates - on that. -*/ -void duplicate_environment_strings(TCHAR *env) { - TCHAR *newenv = copy_environment_block(env); - if (! newenv) return; - - duplicate_environment(newenv); - HeapFree(GetProcessHeap(), 0, newenv); -} - -/* Safely get a copy of the current environment. */ -TCHAR *copy_environment() { - TCHAR *rawenv = GetEnvironmentStrings(); - if (! rawenv) return NULL; - TCHAR *env = copy_environment_block(rawenv); - FreeEnvironmentStrings(rawenv); - return env; -} +#include "nssm.h" + +/* Copy an environment block. */ +TCHAR *copy_environment_block(TCHAR *env) { + unsigned long len; + + if (! env) return 0; + for (len = 0; env[len]; len++) while (env[len]) len++; + if (! len++) return 0; + + TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); + if (! newenv) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("copy_environment_block()"), 0); + return 0; + } + + memmove(newenv, env, len * sizeof(TCHAR)); + return newenv; +} + +/* + The environment block starts with variables of the form + =C:=C:\Windows\System32 which we ignore. +*/ +TCHAR *useful_environment(TCHAR *rawenv) { + TCHAR *env = rawenv; + + if (env) { + while (*env == _T('=')) { + for ( ; *env; env++); + env++; + } + } + + return env; +} + +/* Expand an environment variable. Must call HeapFree() on the result. */ +TCHAR *expand_environment_string(TCHAR *string) { + unsigned long len; + + len = ExpandEnvironmentStrings(string, 0, 0); + if (! len) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0); + return 0; + } + + TCHAR *ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); + if (! ret) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("ExpandEnvironmentStrings()"), _T("expand_environment_string"), 0); + return 0; + } + + if (! ExpandEnvironmentStrings(string, ret, len)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0); + HeapFree(GetProcessHeap(), 0, ret); + return 0; + } + + return ret; +} + +/* + Set all the environment variables from an environment block in the current + environment or remove all the variables in the block from the current + environment. +*/ +static int set_environment_block(TCHAR *env, bool set) { + int ret = 0; + + TCHAR *s, *t; + for (s = env; *s; s++) { + for (t = s; *t && *t != _T('='); t++); + if (*t == _T('=')) { + *t = _T('\0'); + if (set) { + TCHAR *expanded = expand_environment_string(++t); + if (expanded) { + if (! SetEnvironmentVariable(s, expanded)) ret++; + HeapFree(GetProcessHeap(), 0, expanded); + } + else { + if (! SetEnvironmentVariable(s, t)) ret++; + } + } + else { + if (! SetEnvironmentVariable(s, NULL)) ret++; + } + for (t++; *t; t++); + } + s = t; + } + + return ret; +} + +int set_environment_block(TCHAR *env) { + return set_environment_block(env, true); +} + +static int unset_environment_block(TCHAR *env) { + return set_environment_block(env, false); +} + +/* Remove all variables from the process environment. */ +int clear_environment() { + TCHAR *rawenv = GetEnvironmentStrings(); + TCHAR *env = useful_environment(rawenv); + + int ret = unset_environment_block(env); + + if (rawenv) FreeEnvironmentStrings(rawenv); + + return ret; +} + +/* Set the current environment to exactly duplicate an environment block. */ +int duplicate_environment(TCHAR *rawenv) { + int ret = clear_environment(); + TCHAR *env = useful_environment(rawenv); + ret += set_environment_block(env); + return ret; +} + +/* + Verify an environment block. + Returns: 1 if environment is invalid. + 0 if environment is OK. + -1 on error. +*/ +int test_environment(TCHAR *env) { + TCHAR *path = (TCHAR *) nssm_imagepath(); + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + unsigned long flags = CREATE_SUSPENDED; +#ifdef UNICODE + flags |= CREATE_UNICODE_ENVIRONMENT; +#endif + + /* + Try to relaunch ourselves but with the candidate environment set. + Assuming no solar flare activity, the only reason this would fail is if + the environment were invalid. + */ + if (CreateProcess(0, path, 0, 0, 0, flags, env, 0, &si, &pi)) { + TerminateProcess(pi.hProcess, 0); + } + else { + unsigned long error = GetLastError(); + if (error == ERROR_INVALID_PARAMETER) return 1; + else return -1; + } + + return 0; +} + +/* + Duplicate an environment block returned by GetEnvironmentStrings(). + Since such a block is by definition readonly, and duplicate_environment() + modifies its inputs, this function takes a copy of the input and operates + on that. +*/ +void duplicate_environment_strings(TCHAR *env) { + TCHAR *newenv = copy_environment_block(env); + if (! newenv) return; + + duplicate_environment(newenv); + HeapFree(GetProcessHeap(), 0, newenv); +} + +/* Safely get a copy of the current environment. */ +TCHAR *copy_environment() { + TCHAR *rawenv = GetEnvironmentStrings(); + if (! rawenv) return NULL; + TCHAR *env = copy_environment_block(rawenv); + FreeEnvironmentStrings(rawenv); + return env; +} diff --git a/env.h b/env.h index b690106..a56456d 100644 --- a/env.h +++ b/env.h @@ -1,14 +1,14 @@ -#ifndef ENV_H -#define ENV_H - -TCHAR *copy_environment_block(TCHAR *); -TCHAR *useful_environment(TCHAR *); -TCHAR *expand_environment_string(TCHAR *); -int set_environment_block(TCHAR *); -int clear_environment(); -int duplicate_environment(TCHAR *); -int test_environment(TCHAR *); -void duplicate_environment_strings(TCHAR *); -TCHAR *copy_environment(); - -#endif +#ifndef ENV_H +#define ENV_H + +TCHAR *copy_environment_block(TCHAR *); +TCHAR *useful_environment(TCHAR *); +TCHAR *expand_environment_string(TCHAR *); +int set_environment_block(TCHAR *); +int clear_environment(); +int duplicate_environment(TCHAR *); +int test_environment(TCHAR *); +void duplicate_environment_strings(TCHAR *); +TCHAR *copy_environment(); + +#endif diff --git a/hook.cpp b/hook.cpp index 780590b..01e3a50 100644 --- a/hook.cpp +++ b/hook.cpp @@ -1,402 +1,402 @@ -#include "nssm.h" - -typedef struct { - TCHAR *name; - HANDLE process_handle; - unsigned long pid; - unsigned long deadline; - FILETIME creation_time; - kill_t k; -} hook_t; - -static unsigned long WINAPI await_hook(void *arg) { - hook_t *hook = (hook_t *) arg; - if (! hook) return NSSM_HOOK_STATUS_ERROR; - - int ret = 0; - if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = NSSM_HOOK_STATUS_TIMEOUT; - - /* Tidy up hook process tree. */ - if (hook->name) hook->k.name = hook->name; - else hook->k.name = _T("hook"); - hook->k.process_handle = hook->process_handle; - hook->k.pid = hook->pid; - hook->k.stop_method = ~0; - hook->k.kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD; - hook->k.kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD; - hook->k.kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD; - hook->k.creation_time = hook->creation_time; - GetSystemTimeAsFileTime(&hook->k.exit_time); - kill_process_tree(&hook->k, hook->pid); - - if (ret) { - CloseHandle(hook->process_handle); - if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); - HeapFree(GetProcessHeap(), 0, hook); - return ret; - } - - unsigned long exitcode; - GetExitCodeProcess(hook->process_handle, &exitcode); - CloseHandle(hook->process_handle); - - if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); - HeapFree(GetProcessHeap(), 0, hook); - - if (exitcode == NSSM_HOOK_STATUS_ABORT) return NSSM_HOOK_STATUS_ABORT; - if (exitcode) return NSSM_HOOK_STATUS_FAILED; - - return NSSM_HOOK_STATUS_SUCCESS; -} - -static void set_hook_runtime(TCHAR *v, FILETIME *start, FILETIME *now) { - if (start && now) { - ULARGE_INTEGER s; - s.LowPart = start->dwLowDateTime; - s.HighPart = start->dwHighDateTime; - if (s.QuadPart) { - ULARGE_INTEGER t; - t.LowPart = now->dwLowDateTime; - t.HighPart = now->dwHighDateTime; - if (t.QuadPart && t.QuadPart >= s.QuadPart) { - t.QuadPart -= s.QuadPart; - t.QuadPart /= 10000LL; - TCHAR number[16]; - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%llu"), t.QuadPart); - SetEnvironmentVariable(v, number); - return; - } - } - } - SetEnvironmentVariable(v, _T("")); -} - -static void add_thread_handle(hook_thread_t *hook_threads, HANDLE thread_handle, TCHAR *name) { - if (! hook_threads) return; - - int num_threads = hook_threads->num_threads + 1; - hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); - if (! data) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook_thread_t"), _T("add_thread_handle()"), 0); - return; - } - - int i; - for (i = 0; i < hook_threads->num_threads; i++) memmove(&data[i], &hook_threads->data[i], sizeof(data[i])); - memmove(data[i].name, name, sizeof(data[i].name)); - data[i].thread_handle = thread_handle; - - if (hook_threads->data) HeapFree(GetProcessHeap(), 0, hook_threads->data); - hook_threads->data = data; - hook_threads->num_threads = num_threads; -} - -bool valid_hook_name(const TCHAR *hook_event, const TCHAR *hook_action, bool quiet) { - bool valid_event = false; - bool valid_action = false; - - /* Exit/Post */ - if (str_equiv(hook_event, NSSM_HOOK_EVENT_EXIT)) { - if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; - if (quiet) return false; - print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); - return false; - } - - /* Power/{Change,Resume} */ - if (str_equiv(hook_event, NSSM_HOOK_EVENT_POWER)) { - if (str_equiv(hook_action, NSSM_HOOK_ACTION_CHANGE)) return true; - if (str_equiv(hook_action, NSSM_HOOK_ACTION_RESUME)) return true; - if (quiet) return false; - print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_CHANGE); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_RESUME); - return false; - } - - /* Rotate/{Pre,Post} */ - if (str_equiv(hook_event, NSSM_HOOK_EVENT_ROTATE)) { - if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; - if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; - if (quiet) return false; - print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); - return false; - } - - /* Start/{Pre,Post} */ - if (str_equiv(hook_event, NSSM_HOOK_EVENT_START)) { - if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; - if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; - if (quiet) return false; - print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); - return false; - } - - /* Stop/Pre */ - if (str_equiv(hook_event, NSSM_HOOK_EVENT_STOP)) { - if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; - if (quiet) return false; - print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); - return false; - } - - if (quiet) return false; - print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_EVENT); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_EXIT); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_POWER); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_ROTATE); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_START); - _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_STOP); - return false; -} - -void await_hook_threads(hook_thread_t *hook_threads, SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, unsigned long deadline) { - if (! hook_threads) return; - if (! hook_threads->num_threads) return; - - int *retain = (int *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hook_threads->num_threads * sizeof(int)); - if (! retain) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("retain"), _T("await_hook_threads()"), 0); - return; - } - - /* - We could use WaitForMultipleObjects() but await_single_object() can update - the service status as well. - */ - int num_threads = 0; - int i; - for (i = 0; i < hook_threads->num_threads; i++) { - if (deadline) { - if (await_single_handle(status_handle, status, hook_threads->data[i].thread_handle, hook_threads->data[i].name, _T(__FUNCTION__), deadline) != 1) { - CloseHandle(hook_threads->data[i].thread_handle); - continue; - } - } - else if (WaitForSingleObject(hook_threads->data[i].thread_handle, 0) != WAIT_TIMEOUT) { - CloseHandle(hook_threads->data[i].thread_handle); - continue; - } - - retain[num_threads++]= i; - } - - if (num_threads) { - hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); - if (! data) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("data"), _T("await_hook_threads()"), 0); - HeapFree(GetProcessHeap(), 0, retain); - return; - } - - for (i = 0; i < num_threads; i++) memmove(&data[i], &hook_threads->data[retain[i]], sizeof(data[i])); - - HeapFree(GetProcessHeap(), 0, hook_threads->data); - hook_threads->data = data; - hook_threads->num_threads = num_threads; - } - else { - HeapFree(GetProcessHeap(), 0, hook_threads->data); - ZeroMemory(hook_threads, sizeof(*hook_threads)); - } - - HeapFree(GetProcessHeap(), 0, retain); -} - -/* - Returns: - NSSM_HOOK_STATUS_SUCCESS if the hook ran successfully. - NSSM_HOOK_STATUS_NOTFOUND if no hook was found. - NSSM_HOOK_STATUS_ABORT if the hook failed and we should cancel service start. - NSSM_HOOK_STATUS_ERROR on error. - NSSM_HOOK_STATUS_NOTRUN if the hook didn't run. - NSSM_HOOK_STATUS_TIMEOUT if the hook timed out. - NSSM_HOOK_STATUS_FAILED if the hook failed. -*/ -int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline, bool async) { - int ret = 0; - - hook_t *hook = (hook_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(hook_t)); - if (! hook) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0); - return NSSM_HOOK_STATUS_ERROR; - } - - FILETIME now; - GetSystemTimeAsFileTime(&now); - - EnterCriticalSection(&service->hook_section); - - /* Set the environment. */ - set_service_environment(service); - - /* ABI version. */ - TCHAR number[16]; - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), NSSM_HOOK_VERSION); - SetEnvironmentVariable(NSSM_HOOK_ENV_VERSION, number); - - /* Event triggering this action. */ - SetEnvironmentVariable(NSSM_HOOK_ENV_EVENT, hook_event); - - /* Hook action. */ - SetEnvironmentVariable(NSSM_HOOK_ENV_ACTION, hook_action); - - /* Control triggering this action. May be empty. */ - if (hook_control) SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control)); - else SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, _T("")); - - /* Last control handled. */ - SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control)); - - /* Path to NSSM, unquoted for the environment. */ - SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath()); - - /* NSSM version. */ - SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION); - SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_VERSION, NSSM_VERSION); - SetEnvironmentVariable(NSSM_HOOK_ENV_BUILD_DATE, NSSM_DATE); - - /* NSSM PID. */ - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId()); - SetEnvironmentVariable(NSSM_HOOK_ENV_PID, number); - - /* NSSM runtime. */ - set_hook_runtime(NSSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now); - - /* Application PID. */ - if (service->pid) { - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid); - SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, number); - /* Application runtime. */ - set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now); - /* Exit code. */ - SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); - } - else { - SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, _T("")); - if (str_equiv(hook_event, NSSM_HOOK_EVENT_START) && str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) { - SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_RUNTIME, _T("")); - SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); - } - else { - set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time); - /* Exit code. */ - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode); - SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, number); - } - } - - /* Deadline for this script. */ - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline); - SetEnvironmentVariable(NSSM_HOOK_ENV_DEADLINE, number); - - /* Service name. */ - SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_NAME, service->name); - SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_DISPLAYNAME, service->displayname); - - /* Times the service was asked to start. */ - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_requested_count); - SetEnvironmentVariable(NSSM_HOOK_ENV_START_REQUESTED_COUNT, number); - - /* Times the service actually did start. */ - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count); - SetEnvironmentVariable(NSSM_HOOK_ENV_START_COUNT, number); - - /* Times the service exited. */ - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count); - SetEnvironmentVariable(NSSM_HOOK_ENV_EXIT_COUNT, number); - - /* Throttled count. */ - _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle); - SetEnvironmentVariable(NSSM_HOOK_ENV_THROTTLE_COUNT, number); - - /* Command line. */ - TCHAR app[CMD_LENGTH]; - _sntprintf_s(app, _countof(app), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags); - SetEnvironmentVariable(NSSM_HOOK_ENV_COMMAND_LINE, app); - - TCHAR cmd[CMD_LENGTH]; - if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0); - unset_service_environment(service); - LeaveCriticalSection(&service->hook_section); - HeapFree(GetProcessHeap(), 0, hook); - return NSSM_HOOK_STATUS_ERROR; - } - - /* No hook. */ - if (! _tcslen(cmd)) { - unset_service_environment(service); - LeaveCriticalSection(&service->hook_section); - HeapFree(GetProcessHeap(), 0, hook); - return NSSM_HOOK_STATUS_NOTFOUND; - } - - /* Run the command. */ - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - PROCESS_INFORMATION pi; - ZeroMemory(&pi, sizeof(pi)); - unsigned long flags = 0; -#ifdef UNICODE - flags |= CREATE_UNICODE_ENVIRONMENT; -#endif - ret = NSSM_HOOK_STATUS_NOTRUN; - if (CreateProcess(0, cmd, 0, 0, false, flags, 0, service->dir, &si, &pi)) { - hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR)); - if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action); - hook->process_handle = pi.hProcess; - hook->pid = pi.dwProcessId; - hook->deadline = deadline; - if (get_process_creation_time(hook->process_handle, &hook->creation_time)) GetSystemTimeAsFileTime(&hook->creation_time); - - unsigned long tid; - HANDLE thread_handle = CreateThread(NULL, 0, await_hook, (void *) hook, 0, &tid); - if (thread_handle) { - if (async) { - ret = 0; - await_hook_threads(hook_threads, service->status_handle, &service->status, 0); - add_thread_handle(hook_threads, thread_handle, hook->name); - } - else { - await_single_handle(service->status_handle, &service->status, thread_handle, hook->name, _T(__FUNCTION__), deadline + NSSM_SERVICE_STATUS_DEADLINE); - unsigned long exitcode; - GetExitCodeThread(thread_handle, &exitcode); - ret = (int) exitcode; - CloseHandle(thread_handle); - } - } - else { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0); - await_hook(hook); - if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); - HeapFree(GetProcessHeap(), 0, hook); - } - } - else { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0); - HeapFree(GetProcessHeap(), 0, hook); - } - - /* Restore our environment. */ - unset_service_environment(service); - - LeaveCriticalSection(&service->hook_section); - - return ret; -} - -int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline) { - return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, deadline, true); -} - -int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control) { - return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, NSSM_HOOK_DEADLINE); -} +#include "nssm.h" + +typedef struct { + TCHAR *name; + HANDLE process_handle; + unsigned long pid; + unsigned long deadline; + FILETIME creation_time; + kill_t k; +} hook_t; + +static unsigned long WINAPI await_hook(void *arg) { + hook_t *hook = (hook_t *) arg; + if (! hook) return NSSM_HOOK_STATUS_ERROR; + + int ret = 0; + if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = NSSM_HOOK_STATUS_TIMEOUT; + + /* Tidy up hook process tree. */ + if (hook->name) hook->k.name = hook->name; + else hook->k.name = _T("hook"); + hook->k.process_handle = hook->process_handle; + hook->k.pid = hook->pid; + hook->k.stop_method = ~0; + hook->k.kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD; + hook->k.kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD; + hook->k.kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD; + hook->k.creation_time = hook->creation_time; + GetSystemTimeAsFileTime(&hook->k.exit_time); + kill_process_tree(&hook->k, hook->pid); + + if (ret) { + CloseHandle(hook->process_handle); + if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); + HeapFree(GetProcessHeap(), 0, hook); + return ret; + } + + unsigned long exitcode; + GetExitCodeProcess(hook->process_handle, &exitcode); + CloseHandle(hook->process_handle); + + if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); + HeapFree(GetProcessHeap(), 0, hook); + + if (exitcode == NSSM_HOOK_STATUS_ABORT) return NSSM_HOOK_STATUS_ABORT; + if (exitcode) return NSSM_HOOK_STATUS_FAILED; + + return NSSM_HOOK_STATUS_SUCCESS; +} + +static void set_hook_runtime(TCHAR *v, FILETIME *start, FILETIME *now) { + if (start && now) { + ULARGE_INTEGER s; + s.LowPart = start->dwLowDateTime; + s.HighPart = start->dwHighDateTime; + if (s.QuadPart) { + ULARGE_INTEGER t; + t.LowPart = now->dwLowDateTime; + t.HighPart = now->dwHighDateTime; + if (t.QuadPart && t.QuadPart >= s.QuadPart) { + t.QuadPart -= s.QuadPart; + t.QuadPart /= 10000LL; + TCHAR number[16]; + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%llu"), t.QuadPart); + SetEnvironmentVariable(v, number); + return; + } + } + } + SetEnvironmentVariable(v, _T("")); +} + +static void add_thread_handle(hook_thread_t *hook_threads, HANDLE thread_handle, TCHAR *name) { + if (! hook_threads) return; + + int num_threads = hook_threads->num_threads + 1; + hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); + if (! data) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook_thread_t"), _T("add_thread_handle()"), 0); + return; + } + + int i; + for (i = 0; i < hook_threads->num_threads; i++) memmove(&data[i], &hook_threads->data[i], sizeof(data[i])); + memmove(data[i].name, name, sizeof(data[i].name)); + data[i].thread_handle = thread_handle; + + if (hook_threads->data) HeapFree(GetProcessHeap(), 0, hook_threads->data); + hook_threads->data = data; + hook_threads->num_threads = num_threads; +} + +bool valid_hook_name(const TCHAR *hook_event, const TCHAR *hook_action, bool quiet) { + bool valid_event = false; + bool valid_action = false; + + /* Exit/Post */ + if (str_equiv(hook_event, NSSM_HOOK_EVENT_EXIT)) { + if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; + if (quiet) return false; + print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); + return false; + } + + /* Power/{Change,Resume} */ + if (str_equiv(hook_event, NSSM_HOOK_EVENT_POWER)) { + if (str_equiv(hook_action, NSSM_HOOK_ACTION_CHANGE)) return true; + if (str_equiv(hook_action, NSSM_HOOK_ACTION_RESUME)) return true; + if (quiet) return false; + print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_CHANGE); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_RESUME); + return false; + } + + /* Rotate/{Pre,Post} */ + if (str_equiv(hook_event, NSSM_HOOK_EVENT_ROTATE)) { + if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; + if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; + if (quiet) return false; + print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); + return false; + } + + /* Start/{Pre,Post} */ + if (str_equiv(hook_event, NSSM_HOOK_EVENT_START)) { + if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; + if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; + if (quiet) return false; + print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); + return false; + } + + /* Stop/Pre */ + if (str_equiv(hook_event, NSSM_HOOK_EVENT_STOP)) { + if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; + if (quiet) return false; + print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); + return false; + } + + if (quiet) return false; + print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_EVENT); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_EXIT); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_POWER); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_ROTATE); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_START); + _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_STOP); + return false; +} + +void await_hook_threads(hook_thread_t *hook_threads, SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, unsigned long deadline) { + if (! hook_threads) return; + if (! hook_threads->num_threads) return; + + int *retain = (int *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hook_threads->num_threads * sizeof(int)); + if (! retain) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("retain"), _T("await_hook_threads()"), 0); + return; + } + + /* + We could use WaitForMultipleObjects() but await_single_object() can update + the service status as well. + */ + int num_threads = 0; + int i; + for (i = 0; i < hook_threads->num_threads; i++) { + if (deadline) { + if (await_single_handle(status_handle, status, hook_threads->data[i].thread_handle, hook_threads->data[i].name, _T(__FUNCTION__), deadline) != 1) { + CloseHandle(hook_threads->data[i].thread_handle); + continue; + } + } + else if (WaitForSingleObject(hook_threads->data[i].thread_handle, 0) != WAIT_TIMEOUT) { + CloseHandle(hook_threads->data[i].thread_handle); + continue; + } + + retain[num_threads++]= i; + } + + if (num_threads) { + hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); + if (! data) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("data"), _T("await_hook_threads()"), 0); + HeapFree(GetProcessHeap(), 0, retain); + return; + } + + for (i = 0; i < num_threads; i++) memmove(&data[i], &hook_threads->data[retain[i]], sizeof(data[i])); + + HeapFree(GetProcessHeap(), 0, hook_threads->data); + hook_threads->data = data; + hook_threads->num_threads = num_threads; + } + else { + HeapFree(GetProcessHeap(), 0, hook_threads->data); + ZeroMemory(hook_threads, sizeof(*hook_threads)); + } + + HeapFree(GetProcessHeap(), 0, retain); +} + +/* + Returns: + NSSM_HOOK_STATUS_SUCCESS if the hook ran successfully. + NSSM_HOOK_STATUS_NOTFOUND if no hook was found. + NSSM_HOOK_STATUS_ABORT if the hook failed and we should cancel service start. + NSSM_HOOK_STATUS_ERROR on error. + NSSM_HOOK_STATUS_NOTRUN if the hook didn't run. + NSSM_HOOK_STATUS_TIMEOUT if the hook timed out. + NSSM_HOOK_STATUS_FAILED if the hook failed. +*/ +int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline, bool async) { + int ret = 0; + + hook_t *hook = (hook_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(hook_t)); + if (! hook) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0); + return NSSM_HOOK_STATUS_ERROR; + } + + FILETIME now; + GetSystemTimeAsFileTime(&now); + + EnterCriticalSection(&service->hook_section); + + /* Set the environment. */ + set_service_environment(service); + + /* ABI version. */ + TCHAR number[16]; + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), NSSM_HOOK_VERSION); + SetEnvironmentVariable(NSSM_HOOK_ENV_VERSION, number); + + /* Event triggering this action. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_EVENT, hook_event); + + /* Hook action. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_ACTION, hook_action); + + /* Control triggering this action. May be empty. */ + if (hook_control) SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control)); + else SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, _T("")); + + /* Last control handled. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control)); + + /* Path to NSSM, unquoted for the environment. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath()); + + /* NSSM version. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION); + SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_VERSION, NSSM_VERSION); + SetEnvironmentVariable(NSSM_HOOK_ENV_BUILD_DATE, NSSM_DATE); + + /* NSSM PID. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId()); + SetEnvironmentVariable(NSSM_HOOK_ENV_PID, number); + + /* NSSM runtime. */ + set_hook_runtime(NSSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now); + + /* Application PID. */ + if (service->pid) { + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid); + SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, number); + /* Application runtime. */ + set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now); + /* Exit code. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); + } + else { + SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, _T("")); + if (str_equiv(hook_event, NSSM_HOOK_EVENT_START) && str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) { + SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_RUNTIME, _T("")); + SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); + } + else { + set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time); + /* Exit code. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode); + SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, number); + } + } + + /* Deadline for this script. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline); + SetEnvironmentVariable(NSSM_HOOK_ENV_DEADLINE, number); + + /* Service name. */ + SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_NAME, service->name); + SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_DISPLAYNAME, service->displayname); + + /* Times the service was asked to start. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_requested_count); + SetEnvironmentVariable(NSSM_HOOK_ENV_START_REQUESTED_COUNT, number); + + /* Times the service actually did start. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count); + SetEnvironmentVariable(NSSM_HOOK_ENV_START_COUNT, number); + + /* Times the service exited. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count); + SetEnvironmentVariable(NSSM_HOOK_ENV_EXIT_COUNT, number); + + /* Throttled count. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle); + SetEnvironmentVariable(NSSM_HOOK_ENV_THROTTLE_COUNT, number); + + /* Command line. */ + TCHAR app[CMD_LENGTH]; + _sntprintf_s(app, _countof(app), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags); + SetEnvironmentVariable(NSSM_HOOK_ENV_COMMAND_LINE, app); + + TCHAR cmd[CMD_LENGTH]; + if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0); + unset_service_environment(service); + LeaveCriticalSection(&service->hook_section); + HeapFree(GetProcessHeap(), 0, hook); + return NSSM_HOOK_STATUS_ERROR; + } + + /* No hook. */ + if (! _tcslen(cmd)) { + unset_service_environment(service); + LeaveCriticalSection(&service->hook_section); + HeapFree(GetProcessHeap(), 0, hook); + return NSSM_HOOK_STATUS_NOTFOUND; + } + + /* Run the command. */ + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + unsigned long flags = 0; +#ifdef UNICODE + flags |= CREATE_UNICODE_ENVIRONMENT; +#endif + ret = NSSM_HOOK_STATUS_NOTRUN; + if (CreateProcess(0, cmd, 0, 0, false, flags, 0, service->dir, &si, &pi)) { + hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR)); + if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action); + hook->process_handle = pi.hProcess; + hook->pid = pi.dwProcessId; + hook->deadline = deadline; + if (get_process_creation_time(hook->process_handle, &hook->creation_time)) GetSystemTimeAsFileTime(&hook->creation_time); + + unsigned long tid; + HANDLE thread_handle = CreateThread(NULL, 0, await_hook, (void *) hook, 0, &tid); + if (thread_handle) { + if (async) { + ret = 0; + await_hook_threads(hook_threads, service->status_handle, &service->status, 0); + add_thread_handle(hook_threads, thread_handle, hook->name); + } + else { + await_single_handle(service->status_handle, &service->status, thread_handle, hook->name, _T(__FUNCTION__), deadline + NSSM_SERVICE_STATUS_DEADLINE); + unsigned long exitcode; + GetExitCodeThread(thread_handle, &exitcode); + ret = (int) exitcode; + CloseHandle(thread_handle); + } + } + else { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0); + await_hook(hook); + if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); + HeapFree(GetProcessHeap(), 0, hook); + } + } + else { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0); + HeapFree(GetProcessHeap(), 0, hook); + } + + /* Restore our environment. */ + unset_service_environment(service); + + LeaveCriticalSection(&service->hook_section); + + return ret; +} + +int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline) { + return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, deadline, true); +} + +int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control) { + return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, NSSM_HOOK_DEADLINE); +} diff --git a/hook.h b/hook.h index 045e4aa..85e211e 100644 --- a/hook.h +++ b/hook.h @@ -1,75 +1,75 @@ -#ifndef HOOK_H -#define HOOK_H - -#define NSSM_HOOK_EVENT_START _T("Start") -#define NSSM_HOOK_EVENT_STOP _T("Stop") -#define NSSM_HOOK_EVENT_EXIT _T("Exit") -#define NSSM_HOOK_EVENT_POWER _T("Power") -#define NSSM_HOOK_EVENT_ROTATE _T("Rotate") - -#define NSSM_HOOK_ACTION_PRE _T("Pre") -#define NSSM_HOOK_ACTION_POST _T("Post") -#define NSSM_HOOK_ACTION_CHANGE _T("Change") -#define NSSM_HOOK_ACTION_RESUME _T("Resume") - -/* Hook name will be " (/)" */ -#define HOOK_NAME_LENGTH SERVICE_NAME_LENGTH * 2 - -#define NSSM_HOOK_VERSION 1 - -/* Hook ran successfully. */ -#define NSSM_HOOK_STATUS_SUCCESS 0 -/* No hook configured. */ -#define NSSM_HOOK_STATUS_NOTFOUND 1 -/* Hook requested abort. */ -#define NSSM_HOOK_STATUS_ABORT 99 -/* Internal error launching hook. */ -#define NSSM_HOOK_STATUS_ERROR 100 -/* Hook was not run. */ -#define NSSM_HOOK_STATUS_NOTRUN 101 -/* Hook timed out. */ -#define NSSM_HOOK_STATUS_TIMEOUT 102 -/* Hook returned non-zero. */ -#define NSSM_HOOK_STATUS_FAILED 111 - -/* Version 1. */ -#define NSSM_HOOK_ENV_VERSION _T("NSSM_HOOK_VERSION") -#define NSSM_HOOK_ENV_IMAGE_PATH _T("NSSM_EXE") -#define NSSM_HOOK_ENV_NSSM_CONFIGURATION _T("NSSM_CONFIGURATION") -#define NSSM_HOOK_ENV_NSSM_VERSION _T("NSSM_VERSION") -#define NSSM_HOOK_ENV_BUILD_DATE _T("NSSM_BUILD_DATE") -#define NSSM_HOOK_ENV_PID _T("NSSM_PID") -#define NSSM_HOOK_ENV_DEADLINE _T("NSSM_DEADLINE") -#define NSSM_HOOK_ENV_SERVICE_NAME _T("NSSM_SERVICE_NAME") -#define NSSM_HOOK_ENV_SERVICE_DISPLAYNAME _T("NSSM_SERVICE_DISPLAYNAME") -#define NSSM_HOOK_ENV_COMMAND_LINE _T("NSSM_COMMAND_LINE") -#define NSSM_HOOK_ENV_APPLICATION_PID _T("NSSM_APPLICATION_PID") -#define NSSM_HOOK_ENV_EVENT _T("NSSM_EVENT") -#define NSSM_HOOK_ENV_ACTION _T("NSSM_ACTION") -#define NSSM_HOOK_ENV_TRIGGER _T("NSSM_TRIGGER") -#define NSSM_HOOK_ENV_LAST_CONTROL _T("NSSM_LAST_CONTROL") -#define NSSM_HOOK_ENV_START_REQUESTED_COUNT _T("NSSM_START_REQUESTED_COUNT") -#define NSSM_HOOK_ENV_START_COUNT _T("NSSM_START_COUNT") -#define NSSM_HOOK_ENV_THROTTLE_COUNT _T("NSSM_THROTTLE_COUNT") -#define NSSM_HOOK_ENV_EXIT_COUNT _T("NSSM_EXIT_COUNT") -#define NSSM_HOOK_ENV_EXITCODE _T("NSSM_EXITCODE") -#define NSSM_HOOK_ENV_RUNTIME _T("NSSM_RUNTIME") -#define NSSM_HOOK_ENV_APPLICATION_RUNTIME _T("NSSM_APPLICATION_RUNTIME") - -typedef struct { - TCHAR name[HOOK_NAME_LENGTH]; - HANDLE thread_handle; -} hook_thread_data_t; - -typedef struct { - hook_thread_data_t *data; - int num_threads; -} hook_thread_t; - -bool valid_hook_name(const TCHAR *, const TCHAR *, bool); -void await_hook_threads(hook_thread_t *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long); -int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long, bool); -int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long); -int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *); - -#endif +#ifndef HOOK_H +#define HOOK_H + +#define NSSM_HOOK_EVENT_START _T("Start") +#define NSSM_HOOK_EVENT_STOP _T("Stop") +#define NSSM_HOOK_EVENT_EXIT _T("Exit") +#define NSSM_HOOK_EVENT_POWER _T("Power") +#define NSSM_HOOK_EVENT_ROTATE _T("Rotate") + +#define NSSM_HOOK_ACTION_PRE _T("Pre") +#define NSSM_HOOK_ACTION_POST _T("Post") +#define NSSM_HOOK_ACTION_CHANGE _T("Change") +#define NSSM_HOOK_ACTION_RESUME _T("Resume") + +/* Hook name will be " (/)" */ +#define HOOK_NAME_LENGTH SERVICE_NAME_LENGTH * 2 + +#define NSSM_HOOK_VERSION 1 + +/* Hook ran successfully. */ +#define NSSM_HOOK_STATUS_SUCCESS 0 +/* No hook configured. */ +#define NSSM_HOOK_STATUS_NOTFOUND 1 +/* Hook requested abort. */ +#define NSSM_HOOK_STATUS_ABORT 99 +/* Internal error launching hook. */ +#define NSSM_HOOK_STATUS_ERROR 100 +/* Hook was not run. */ +#define NSSM_HOOK_STATUS_NOTRUN 101 +/* Hook timed out. */ +#define NSSM_HOOK_STATUS_TIMEOUT 102 +/* Hook returned non-zero. */ +#define NSSM_HOOK_STATUS_FAILED 111 + +/* Version 1. */ +#define NSSM_HOOK_ENV_VERSION _T("NSSM_HOOK_VERSION") +#define NSSM_HOOK_ENV_IMAGE_PATH _T("NSSM_EXE") +#define NSSM_HOOK_ENV_NSSM_CONFIGURATION _T("NSSM_CONFIGURATION") +#define NSSM_HOOK_ENV_NSSM_VERSION _T("NSSM_VERSION") +#define NSSM_HOOK_ENV_BUILD_DATE _T("NSSM_BUILD_DATE") +#define NSSM_HOOK_ENV_PID _T("NSSM_PID") +#define NSSM_HOOK_ENV_DEADLINE _T("NSSM_DEADLINE") +#define NSSM_HOOK_ENV_SERVICE_NAME _T("NSSM_SERVICE_NAME") +#define NSSM_HOOK_ENV_SERVICE_DISPLAYNAME _T("NSSM_SERVICE_DISPLAYNAME") +#define NSSM_HOOK_ENV_COMMAND_LINE _T("NSSM_COMMAND_LINE") +#define NSSM_HOOK_ENV_APPLICATION_PID _T("NSSM_APPLICATION_PID") +#define NSSM_HOOK_ENV_EVENT _T("NSSM_EVENT") +#define NSSM_HOOK_ENV_ACTION _T("NSSM_ACTION") +#define NSSM_HOOK_ENV_TRIGGER _T("NSSM_TRIGGER") +#define NSSM_HOOK_ENV_LAST_CONTROL _T("NSSM_LAST_CONTROL") +#define NSSM_HOOK_ENV_START_REQUESTED_COUNT _T("NSSM_START_REQUESTED_COUNT") +#define NSSM_HOOK_ENV_START_COUNT _T("NSSM_START_COUNT") +#define NSSM_HOOK_ENV_THROTTLE_COUNT _T("NSSM_THROTTLE_COUNT") +#define NSSM_HOOK_ENV_EXIT_COUNT _T("NSSM_EXIT_COUNT") +#define NSSM_HOOK_ENV_EXITCODE _T("NSSM_EXITCODE") +#define NSSM_HOOK_ENV_RUNTIME _T("NSSM_RUNTIME") +#define NSSM_HOOK_ENV_APPLICATION_RUNTIME _T("NSSM_APPLICATION_RUNTIME") + +typedef struct { + TCHAR name[HOOK_NAME_LENGTH]; + HANDLE thread_handle; +} hook_thread_data_t; + +typedef struct { + hook_thread_data_t *data; + int num_threads; +} hook_thread_t; + +bool valid_hook_name(const TCHAR *, const TCHAR *, bool); +void await_hook_threads(hook_thread_t *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long); +int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long, bool); +int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long); +int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *); + +#endif diff --git a/imports.cpp b/imports.cpp index 6257131..ec3a2a8 100644 --- a/imports.cpp +++ b/imports.cpp @@ -1,92 +1,92 @@ -#include "nssm.h" - -imports_t imports; - -/* - Try to set up function pointers. - In this first implementation it is not an error if we can't load them - because we aren't currently trying to load any functions which we - absolutely need. If we later add some indispensible imports we can - return non-zero here to force an application exit. -*/ -HMODULE get_dll(const TCHAR *dll, unsigned long *error) { - *error = 0; - - HMODULE ret = LoadLibrary(dll); - if (! ret) { - *error = GetLastError(); - log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_LOADLIBRARY_FAILED, dll, error_string(*error), 0); - } - - return ret; -} - -FARPROC get_import(HMODULE library, const char *function, unsigned long *error) { - *error = 0; - - FARPROC ret = GetProcAddress(library, function); - if (! ret) { - *error = GetLastError(); - TCHAR *function_name; -#ifdef UNICODE - size_t buflen; - mbstowcs_s(&buflen, NULL, 0, function, _TRUNCATE); - function_name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen * sizeof(TCHAR)); - if (function_name) mbstowcs_s(&buflen, function_name, buflen * sizeof(TCHAR), function, _TRUNCATE); -#else - function_name = (TCHAR *) function; -#endif - if (*error != ERROR_PROC_NOT_FOUND) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_GETPROCADDRESS_FAILED, function_name, error_string(*error), 0); -#ifdef UNICODE - if (function_name) HeapFree(GetProcessHeap(), 0, function_name); -#endif - } - - return ret; -} - -int get_imports() { - unsigned long error; - - ZeroMemory(&imports, sizeof(imports)); - - imports.kernel32 = get_dll(_T("kernel32.dll"), &error); - if (imports.kernel32) { - imports.AttachConsole = (AttachConsole_ptr) get_import(imports.kernel32, "AttachConsole", &error); - if (! imports.AttachConsole) { - if (error != ERROR_PROC_NOT_FOUND) return 2; - } - - imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error); - if (! imports.SleepConditionVariableCS) { - if (error != ERROR_PROC_NOT_FOUND) return 3; - } - - imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error); - if (! imports.WakeConditionVariable) { - if (error != ERROR_PROC_NOT_FOUND) return 4; - } - } - else if (error != ERROR_MOD_NOT_FOUND) return 1; - - imports.advapi32 = get_dll(_T("advapi32.dll"), &error); - if (imports.advapi32) { - imports.CreateWellKnownSid = (CreateWellKnownSid_ptr) get_import(imports.advapi32, "CreateWellKnownSid", &error); - if (! imports.CreateWellKnownSid) { - if (error != ERROR_PROC_NOT_FOUND) return 6; - } - imports.IsWellKnownSid = (IsWellKnownSid_ptr) get_import(imports.advapi32, "IsWellKnownSid", &error); - if (! imports.IsWellKnownSid) { - if (error != ERROR_PROC_NOT_FOUND) return 7; - } - } - else if (error != ERROR_MOD_NOT_FOUND) return 5; - - return 0; -} - -void free_imports() { - if (imports.kernel32) FreeLibrary(imports.kernel32); - if (imports.advapi32) FreeLibrary(imports.advapi32); - ZeroMemory(&imports, sizeof(imports)); -} +#include "nssm.h" + +imports_t imports; + +/* + Try to set up function pointers. + In this first implementation it is not an error if we can't load them + because we aren't currently trying to load any functions which we + absolutely need. If we later add some indispensible imports we can + return non-zero here to force an application exit. +*/ +HMODULE get_dll(const TCHAR *dll, unsigned long *error) { + *error = 0; + + HMODULE ret = LoadLibrary(dll); + if (! ret) { + *error = GetLastError(); + log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_LOADLIBRARY_FAILED, dll, error_string(*error), 0); + } + + return ret; +} + +FARPROC get_import(HMODULE library, const char *function, unsigned long *error) { + *error = 0; + + FARPROC ret = GetProcAddress(library, function); + if (! ret) { + *error = GetLastError(); + TCHAR *function_name; +#ifdef UNICODE + size_t buflen; + mbstowcs_s(&buflen, NULL, 0, function, _TRUNCATE); + function_name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen * sizeof(TCHAR)); + if (function_name) mbstowcs_s(&buflen, function_name, buflen * sizeof(TCHAR), function, _TRUNCATE); +#else + function_name = (TCHAR *) function; +#endif + if (*error != ERROR_PROC_NOT_FOUND) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_GETPROCADDRESS_FAILED, function_name, error_string(*error), 0); +#ifdef UNICODE + if (function_name) HeapFree(GetProcessHeap(), 0, function_name); +#endif + } + + return ret; +} + +int get_imports() { + unsigned long error; + + ZeroMemory(&imports, sizeof(imports)); + + imports.kernel32 = get_dll(_T("kernel32.dll"), &error); + if (imports.kernel32) { + imports.AttachConsole = (AttachConsole_ptr) get_import(imports.kernel32, "AttachConsole", &error); + if (! imports.AttachConsole) { + if (error != ERROR_PROC_NOT_FOUND) return 2; + } + + imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error); + if (! imports.SleepConditionVariableCS) { + if (error != ERROR_PROC_NOT_FOUND) return 3; + } + + imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error); + if (! imports.WakeConditionVariable) { + if (error != ERROR_PROC_NOT_FOUND) return 4; + } + } + else if (error != ERROR_MOD_NOT_FOUND) return 1; + + imports.advapi32 = get_dll(_T("advapi32.dll"), &error); + if (imports.advapi32) { + imports.CreateWellKnownSid = (CreateWellKnownSid_ptr) get_import(imports.advapi32, "CreateWellKnownSid", &error); + if (! imports.CreateWellKnownSid) { + if (error != ERROR_PROC_NOT_FOUND) return 6; + } + imports.IsWellKnownSid = (IsWellKnownSid_ptr) get_import(imports.advapi32, "IsWellKnownSid", &error); + if (! imports.IsWellKnownSid) { + if (error != ERROR_PROC_NOT_FOUND) return 7; + } + } + else if (error != ERROR_MOD_NOT_FOUND) return 5; + + return 0; +} + +void free_imports() { + if (imports.kernel32) FreeLibrary(imports.kernel32); + if (imports.advapi32) FreeLibrary(imports.advapi32); + ZeroMemory(&imports, sizeof(imports)); +} diff --git a/imports.h b/imports.h index 03e2df1..008a702 100644 --- a/imports.h +++ b/imports.h @@ -1,25 +1,25 @@ -#ifndef IMPORTS_H -#define IMPORTS_H - -typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD); -typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD); -typedef void (WINAPI *WakeConditionVariable_ptr)(PCONDITION_VARIABLE); -typedef BOOL (WINAPI *CreateWellKnownSid_ptr)(WELL_KNOWN_SID_TYPE, SID *, SID *, unsigned long *); -typedef BOOL (WINAPI *IsWellKnownSid_ptr)(SID *, WELL_KNOWN_SID_TYPE); - -typedef struct { - HMODULE kernel32; - HMODULE advapi32; - AttachConsole_ptr AttachConsole; - SleepConditionVariableCS_ptr SleepConditionVariableCS; - WakeConditionVariable_ptr WakeConditionVariable; - CreateWellKnownSid_ptr CreateWellKnownSid; - IsWellKnownSid_ptr IsWellKnownSid; -} imports_t; - -HMODULE get_dll(const TCHAR *, unsigned long *); -FARPROC get_import(HMODULE, const char *, unsigned long *); -int get_imports(); -void free_imports(); - -#endif +#ifndef IMPORTS_H +#define IMPORTS_H + +typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD); +typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD); +typedef void (WINAPI *WakeConditionVariable_ptr)(PCONDITION_VARIABLE); +typedef BOOL (WINAPI *CreateWellKnownSid_ptr)(WELL_KNOWN_SID_TYPE, SID *, SID *, unsigned long *); +typedef BOOL (WINAPI *IsWellKnownSid_ptr)(SID *, WELL_KNOWN_SID_TYPE); + +typedef struct { + HMODULE kernel32; + HMODULE advapi32; + AttachConsole_ptr AttachConsole; + SleepConditionVariableCS_ptr SleepConditionVariableCS; + WakeConditionVariable_ptr WakeConditionVariable; + CreateWellKnownSid_ptr CreateWellKnownSid; + IsWellKnownSid_ptr IsWellKnownSid; +} imports_t; + +HMODULE get_dll(const TCHAR *, unsigned long *); +FARPROC get_import(HMODULE, const char *, unsigned long *); +int get_imports(); +void free_imports(); + +#endif diff --git a/messages.mc b/messages.mc index 985a4b4e795b3c62ae9968ed575518ec05c1902d..14ce8dfa0a6d218da6095d12a85a43e690aa7825 100644 GIT binary patch delta 23859 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 qI1!#hc?ri{RT~u(Int%(2Pg0R6ThBKSYI^3~t~ delta 17583 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 L3xDIqY}@|>p+YuB diff --git a/process.cpp b/process.cpp index b03b504..d1a9a49 100644 --- a/process.cpp +++ b/process.cpp @@ -1,350 +1,350 @@ -#include "nssm.h" - -extern imports_t imports; - -void service_kill_t(nssm_service_t *service, kill_t *k) { - if (! service) return; - if (! k) return; - - ZeroMemory(k, sizeof(*k)); - k->name = service->name; - k->process_handle = service->process_handle; - k->pid = service->pid; - k->exitcode = service->exitcode; - k->stop_method = service->stop_method; - k->kill_console_delay = service->kill_console_delay; - k->kill_window_delay = service->kill_window_delay; - k->kill_threads_delay = service->kill_threads_delay; - k->status_handle = service->status_handle; - k->status = &service->status; - k->creation_time = service->creation_time; - k->exit_time = service->exit_time; -} - -int get_process_creation_time(HANDLE process_handle, FILETIME *ft) { - FILETIME creation_time, exit_time, kernel_time, user_time; - - if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0); - return 1; - } - - memmove(ft, &creation_time, sizeof(creation_time)); - - return 0; -} - -int get_process_exit_time(HANDLE process_handle, FILETIME *ft) { - FILETIME creation_time, exit_time, kernel_time, user_time; - - if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0); - return 1; - } - - if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2; - memmove(ft, &exit_time, sizeof(exit_time)); - - return 0; -} - -int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) { - /* Check parent process ID matches. */ - if (pe->th32ParentProcessID != ppid) return 1; - - /* - Process IDs can be reused so do a sanity check by making sure the child - has been running for less time than the parent. - Though unlikely, it's possible that the parent exited and its process ID - was already reused, so we'll also compare against its exit time. - */ - HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID); - if (! process_handle) { - TCHAR pid_string[16]; - _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID); - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); - return 2; - } - - FILETIME ft; - if (get_process_creation_time(process_handle, &ft)) { - CloseHandle(process_handle); - return 3; - } - - CloseHandle(process_handle); - - /* Verify that the parent's creation time is not later. */ - if (CompareFileTime(&k->creation_time, &ft) > 0) return 4; - - /* Verify that the parent's exit time is not earlier. */ - if (CompareFileTime(&k->exit_time, &ft) < 0) return 5; - - return 0; -} - -/* Send some window messages and hope the window respects one or more. */ -int CALLBACK kill_window(HWND window, LPARAM arg) { - kill_t *k = (kill_t *) arg; - - unsigned long pid; - if (! GetWindowThreadProcessId(window, &pid)) return 1; - if (pid != k->pid) return 1; - - /* First try sending WM_CLOSE to request that the window close. */ - k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0); - - /* - Then tell the window that the user is logging off and it should exit - without worrying about saving any data. - */ - k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF); - - return 1; -} - -/* - Try to post a message to the message queues of threads associated with the - given process ID. Not all threads have message queues so there's no - guarantee of success, and we don't want to be left waiting for unsignalled - processes so this function returns only true if at least one thread was - successfully prodded. -*/ -int kill_threads(nssm_service_t *service, kill_t *k) { - int ret = 0; - - /* Get a snapshot of all threads in the system. */ - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (! snapshot) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0); - return 0; - } - - THREADENTRY32 te; - ZeroMemory(&te, sizeof(te)); - te.dwSize = sizeof(te); - - if (! Thread32First(snapshot, &te)) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); - CloseHandle(snapshot); - return 0; - } - - /* This thread belongs to the doomed process so signal it. */ - if (te.th32OwnerProcessID == k->pid) { - ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0); - } - - while (true) { - /* Try to get the next thread. */ - if (! Thread32Next(snapshot, &te)) { - unsigned long error = GetLastError(); - if (error == ERROR_NO_MORE_FILES) break; - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); - CloseHandle(snapshot); - return ret; - } - - if (te.th32OwnerProcessID == k->pid) { - ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0); - } - } - - CloseHandle(snapshot); - - return ret; -} - -int kill_threads(kill_t *k) { - return kill_threads(NULL, k); -} - -/* Give the process a chance to die gracefully. */ -int kill_process(nssm_service_t *service, kill_t *k) { - if (! k) return 1; - - unsigned long ret; - if (GetExitCodeProcess(k->process_handle, &ret)) { - if (ret != STILL_ACTIVE) return 1; - } - - /* Try to send a Control-C event to the console. */ - if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) { - if (! kill_console(k)) return 1; - } - - /* - Try to post messages to the windows belonging to the given process ID. - If the process is a console application it won't have any windows so there's - no guarantee of success. - */ - if (k->stop_method & NSSM_STOP_METHOD_WINDOW) { - EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k); - if (k->signalled) { - if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1; - k->signalled = 0; - } - } - - /* - Try to post messages to any thread message queues associated with the - process. Console applications might have them (but probably won't) so - there's still no guarantee of success. - */ - if (k->stop_method & NSSM_STOP_METHOD_THREADS) { - if (kill_threads(k)) { - if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1; - } - } - - /* We tried being nice. Time for extreme prejudice. */ - if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) { - return TerminateProcess(k->process_handle, k->exitcode); - } - - return 0; -} - -int kill_process(kill_t *k) { - return kill_process(NULL, k); -} - -/* Simulate a Control-C event to our console (shared with the app). */ -int kill_console(nssm_service_t *service, kill_t *k) { - unsigned long ret; - - if (! k) return 1; - - /* Check we loaded AttachConsole(). */ - if (! imports.AttachConsole) return 4; - - /* Try to attach to the process's console. */ - if (! imports.AttachConsole(k->pid)) { - ret = GetLastError(); - - switch (ret) { - case ERROR_INVALID_HANDLE: - /* The app doesn't have a console. */ - return 1; - - case ERROR_GEN_FAILURE: - /* The app already exited. */ - return 2; - - case ERROR_ACCESS_DENIED: - default: - /* We already have a console. */ - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0); - return 3; - } - } - - /* Ignore the event ourselves. */ - ret = 0; - if (! SetConsoleCtrlHandler(0, TRUE)) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0); - ret = 4; - } - - /* Send the event. */ - if (! ret) { - if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0); - ret = 5; - } - } - - /* Detach from the console. */ - if (! FreeConsole()) { - log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0); - } - - /* Wait for process to exit. */ - if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6; - - return ret; -} - -int kill_console(kill_t *k) { - return kill_console(NULL, k); -} - -void kill_process_tree(nssm_service_t * service, kill_t *k, unsigned long ppid) { - if (! k) return; - /* Shouldn't happen unless the service failed to start. */ - if (! k->pid) return; /* XXX: needed? */ - unsigned long pid = k->pid; - - TCHAR pid_string[16], code[16]; - _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid); - _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode); - log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0); - - /* We will need a process handle in order to call TerminateProcess() later. */ - HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid); - if (process_handle) { - /* Kill this process first, then its descendents. */ - TCHAR ppid_string[16]; - _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid); - log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0); - k->process_handle = process_handle; /* XXX: open directly? */ - if (! kill_process(k)) { - /* Maybe it already died. */ - unsigned long ret; - if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) { - if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); - else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0); - } - } - - CloseHandle(process_handle); - } - else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); - - /* Get a snapshot of all processes in the system. */ - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (! snapshot) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0); - return; - } - - PROCESSENTRY32 pe; - ZeroMemory(&pe, sizeof(pe)); - pe.dwSize = sizeof(pe); - - if (! Process32First(snapshot, &pe)) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); - CloseHandle(snapshot); - return; - } - - /* This is a child of the doomed process so kill it. */ - if (! check_parent(k, &pe, pid)) { - k->pid = pe.th32ProcessID; - kill_process_tree(k, ppid); - } - k->pid = pid; - - while (true) { - /* Try to get the next process. */ - if (! Process32Next(snapshot, &pe)) { - unsigned long ret = GetLastError(); - if (ret == ERROR_NO_MORE_FILES) break; - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); - CloseHandle(snapshot); - return; - } - - if (! check_parent(k, &pe, pid)) { - k->pid = pe.th32ProcessID; - kill_process_tree(k, ppid); - } - k->pid = pid; - } - - CloseHandle(snapshot); -} - -void kill_process_tree(kill_t *k, unsigned long ppid) { - return kill_process_tree(NULL, k, ppid); -} +#include "nssm.h" + +extern imports_t imports; + +void service_kill_t(nssm_service_t *service, kill_t *k) { + if (! service) return; + if (! k) return; + + ZeroMemory(k, sizeof(*k)); + k->name = service->name; + k->process_handle = service->process_handle; + k->pid = service->pid; + k->exitcode = service->exitcode; + k->stop_method = service->stop_method; + k->kill_console_delay = service->kill_console_delay; + k->kill_window_delay = service->kill_window_delay; + k->kill_threads_delay = service->kill_threads_delay; + k->status_handle = service->status_handle; + k->status = &service->status; + k->creation_time = service->creation_time; + k->exit_time = service->exit_time; +} + +int get_process_creation_time(HANDLE process_handle, FILETIME *ft) { + FILETIME creation_time, exit_time, kernel_time, user_time; + + if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0); + return 1; + } + + memmove(ft, &creation_time, sizeof(creation_time)); + + return 0; +} + +int get_process_exit_time(HANDLE process_handle, FILETIME *ft) { + FILETIME creation_time, exit_time, kernel_time, user_time; + + if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0); + return 1; + } + + if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2; + memmove(ft, &exit_time, sizeof(exit_time)); + + return 0; +} + +int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) { + /* Check parent process ID matches. */ + if (pe->th32ParentProcessID != ppid) return 1; + + /* + Process IDs can be reused so do a sanity check by making sure the child + has been running for less time than the parent. + Though unlikely, it's possible that the parent exited and its process ID + was already reused, so we'll also compare against its exit time. + */ + HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID); + if (! process_handle) { + TCHAR pid_string[16]; + _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID); + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); + return 2; + } + + FILETIME ft; + if (get_process_creation_time(process_handle, &ft)) { + CloseHandle(process_handle); + return 3; + } + + CloseHandle(process_handle); + + /* Verify that the parent's creation time is not later. */ + if (CompareFileTime(&k->creation_time, &ft) > 0) return 4; + + /* Verify that the parent's exit time is not earlier. */ + if (CompareFileTime(&k->exit_time, &ft) < 0) return 5; + + return 0; +} + +/* Send some window messages and hope the window respects one or more. */ +int CALLBACK kill_window(HWND window, LPARAM arg) { + kill_t *k = (kill_t *) arg; + + unsigned long pid; + if (! GetWindowThreadProcessId(window, &pid)) return 1; + if (pid != k->pid) return 1; + + /* First try sending WM_CLOSE to request that the window close. */ + k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0); + + /* + Then tell the window that the user is logging off and it should exit + without worrying about saving any data. + */ + k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF); + + return 1; +} + +/* + Try to post a message to the message queues of threads associated with the + given process ID. Not all threads have message queues so there's no + guarantee of success, and we don't want to be left waiting for unsignalled + processes so this function returns only true if at least one thread was + successfully prodded. +*/ +int kill_threads(nssm_service_t *service, kill_t *k) { + int ret = 0; + + /* Get a snapshot of all threads in the system. */ + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (! snapshot) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0); + return 0; + } + + THREADENTRY32 te; + ZeroMemory(&te, sizeof(te)); + te.dwSize = sizeof(te); + + if (! Thread32First(snapshot, &te)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); + CloseHandle(snapshot); + return 0; + } + + /* This thread belongs to the doomed process so signal it. */ + if (te.th32OwnerProcessID == k->pid) { + ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0); + } + + while (true) { + /* Try to get the next thread. */ + if (! Thread32Next(snapshot, &te)) { + unsigned long error = GetLastError(); + if (error == ERROR_NO_MORE_FILES) break; + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); + CloseHandle(snapshot); + return ret; + } + + if (te.th32OwnerProcessID == k->pid) { + ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0); + } + } + + CloseHandle(snapshot); + + return ret; +} + +int kill_threads(kill_t *k) { + return kill_threads(NULL, k); +} + +/* Give the process a chance to die gracefully. */ +int kill_process(nssm_service_t *service, kill_t *k) { + if (! k) return 1; + + unsigned long ret; + if (GetExitCodeProcess(k->process_handle, &ret)) { + if (ret != STILL_ACTIVE) return 1; + } + + /* Try to send a Control-C event to the console. */ + if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) { + if (! kill_console(k)) return 1; + } + + /* + Try to post messages to the windows belonging to the given process ID. + If the process is a console application it won't have any windows so there's + no guarantee of success. + */ + if (k->stop_method & NSSM_STOP_METHOD_WINDOW) { + EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k); + if (k->signalled) { + if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1; + k->signalled = 0; + } + } + + /* + Try to post messages to any thread message queues associated with the + process. Console applications might have them (but probably won't) so + there's still no guarantee of success. + */ + if (k->stop_method & NSSM_STOP_METHOD_THREADS) { + if (kill_threads(k)) { + if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1; + } + } + + /* We tried being nice. Time for extreme prejudice. */ + if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) { + return TerminateProcess(k->process_handle, k->exitcode); + } + + return 0; +} + +int kill_process(kill_t *k) { + return kill_process(NULL, k); +} + +/* Simulate a Control-C event to our console (shared with the app). */ +int kill_console(nssm_service_t *service, kill_t *k) { + unsigned long ret; + + if (! k) return 1; + + /* Check we loaded AttachConsole(). */ + if (! imports.AttachConsole) return 4; + + /* Try to attach to the process's console. */ + if (! imports.AttachConsole(k->pid)) { + ret = GetLastError(); + + switch (ret) { + case ERROR_INVALID_HANDLE: + /* The app doesn't have a console. */ + return 1; + + case ERROR_GEN_FAILURE: + /* The app already exited. */ + return 2; + + case ERROR_ACCESS_DENIED: + default: + /* We already have a console. */ + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0); + return 3; + } + } + + /* Ignore the event ourselves. */ + ret = 0; + if (! SetConsoleCtrlHandler(0, TRUE)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0); + ret = 4; + } + + /* Send the event. */ + if (! ret) { + if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0); + ret = 5; + } + } + + /* Detach from the console. */ + if (! FreeConsole()) { + log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0); + } + + /* Wait for process to exit. */ + if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6; + + return ret; +} + +int kill_console(kill_t *k) { + return kill_console(NULL, k); +} + +void kill_process_tree(nssm_service_t * service, kill_t *k, unsigned long ppid) { + if (! k) return; + /* Shouldn't happen unless the service failed to start. */ + if (! k->pid) return; /* XXX: needed? */ + unsigned long pid = k->pid; + + TCHAR pid_string[16], code[16]; + _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid); + _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode); + log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0); + + /* We will need a process handle in order to call TerminateProcess() later. */ + HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid); + if (process_handle) { + /* Kill this process first, then its descendents. */ + TCHAR ppid_string[16]; + _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid); + log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0); + k->process_handle = process_handle; /* XXX: open directly? */ + if (! kill_process(k)) { + /* Maybe it already died. */ + unsigned long ret; + if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) { + if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); + else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0); + } + } + + CloseHandle(process_handle); + } + else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); + + /* Get a snapshot of all processes in the system. */ + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (! snapshot) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0); + return; + } + + PROCESSENTRY32 pe; + ZeroMemory(&pe, sizeof(pe)); + pe.dwSize = sizeof(pe); + + if (! Process32First(snapshot, &pe)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); + CloseHandle(snapshot); + return; + } + + /* This is a child of the doomed process so kill it. */ + if (! check_parent(k, &pe, pid)) { + k->pid = pe.th32ProcessID; + kill_process_tree(k, ppid); + } + k->pid = pid; + + while (true) { + /* Try to get the next process. */ + if (! Process32Next(snapshot, &pe)) { + unsigned long ret = GetLastError(); + if (ret == ERROR_NO_MORE_FILES) break; + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); + CloseHandle(snapshot); + return; + } + + if (! check_parent(k, &pe, pid)) { + k->pid = pe.th32ProcessID; + kill_process_tree(k, ppid); + } + k->pid = pid; + } + + CloseHandle(snapshot); +} + +void kill_process_tree(kill_t *k, unsigned long ppid) { + return kill_process_tree(NULL, k, ppid); +} diff --git a/process.h b/process.h index 2e402e4..1bfc273 100644 --- a/process.h +++ b/process.h @@ -1,36 +1,36 @@ -#ifndef PROCESS_H -#define PROCESS_H - -#include - -typedef struct { - TCHAR *name; - HANDLE process_handle; - unsigned long pid; - unsigned long exitcode; - unsigned long stop_method; - unsigned long kill_console_delay; - unsigned long kill_window_delay; - unsigned long kill_threads_delay; - SERVICE_STATUS_HANDLE status_handle; - SERVICE_STATUS *status; - FILETIME creation_time; - FILETIME exit_time; - int signalled; -} kill_t; - -void service_kill_t(nssm_service_t *, kill_t *); -int get_process_creation_time(HANDLE, FILETIME *); -int get_process_exit_time(HANDLE, FILETIME *); -int check_parent(kill_t *, PROCESSENTRY32 *, unsigned long); -int CALLBACK kill_window(HWND, LPARAM); -int kill_threads(nssm_service_t *, kill_t *); -int kill_threads(kill_t *); -int kill_console(nssm_service_t *, kill_t *); -int kill_console(kill_t *); -int kill_process(nssm_service_t *, kill_t *); -int kill_process(kill_t *); -void kill_process_tree(nssm_service_t *, kill_t *, unsigned long); -void kill_process_tree(kill_t *, unsigned long); - -#endif +#ifndef PROCESS_H +#define PROCESS_H + +#include + +typedef struct { + TCHAR *name; + HANDLE process_handle; + unsigned long pid; + unsigned long exitcode; + unsigned long stop_method; + unsigned long kill_console_delay; + unsigned long kill_window_delay; + unsigned long kill_threads_delay; + SERVICE_STATUS_HANDLE status_handle; + SERVICE_STATUS *status; + FILETIME creation_time; + FILETIME exit_time; + int signalled; +} kill_t; + +void service_kill_t(nssm_service_t *, kill_t *); +int get_process_creation_time(HANDLE, FILETIME *); +int get_process_exit_time(HANDLE, FILETIME *); +int check_parent(kill_t *, PROCESSENTRY32 *, unsigned long); +int CALLBACK kill_window(HWND, LPARAM); +int kill_threads(nssm_service_t *, kill_t *); +int kill_threads(kill_t *); +int kill_console(nssm_service_t *, kill_t *); +int kill_console(kill_t *); +int kill_process(nssm_service_t *, kill_t *); +int kill_process(kill_t *); +void kill_process_tree(nssm_service_t *, kill_t *, unsigned long); +void kill_process_tree(kill_t *, unsigned long); + +#endif diff --git a/settings.cpp b/settings.cpp index 4bbdd57..45e0a58 100644 --- a/settings.cpp +++ b/settings.cpp @@ -1,1130 +1,1130 @@ -#include "nssm.h" -/* XXX: (value && value->string) is probably bogus because value is probably never null */ - -/* Affinity. */ -#define NSSM_AFFINITY_ALL _T("All") - -extern const TCHAR *exit_action_strings[]; -extern const TCHAR *startup_strings[]; -extern const TCHAR *priority_strings[]; - -/* Does the parameter refer to the default value of the setting? */ -static inline int is_default(const TCHAR *value) { - return (str_equiv(value, _T("default")) || str_equiv(value, _T("*")) || ! value[0]); -} - -static int value_from_string(const TCHAR *name, value_t *value, const TCHAR *string) { - size_t len = _tcslen(string); - if (! len++) { - value->string = 0; - return 0; - } - - value->string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); - if (! value->string) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()")); - return -1; - } - - if (_sntprintf_s(value->string, len, _TRUNCATE, _T("%s"), string) < 0) { - HeapFree(GetProcessHeap(), 0, value->string); - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()")); - return -1; - } - - return 1; -} - -/* Functions to manage NSSM-specific settings in the registry. */ -static int setting_set_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! key) return -1; - - unsigned long number; - long error; - - /* Resetting to default? */ - if (! value || ! value->string) { - error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - if (str_number(value->string, &number)) return -1; - - if (default_value && number == PtrToUlong(default_value)) { - error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - - if (set_number(key, (TCHAR *) name, number)) return -1; - - return 1; -} - -static int setting_get_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - return get_number(key, (TCHAR *) name, &value->numeric, false); -} - -static int setting_set_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! key) return -1; - - long error; - - /* Resetting to default? */ - if (! value || ! value->string) { - if (default_value) value->string = (TCHAR *) default_value; - else { - error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - } - if (default_value && _tcslen((TCHAR *) default_value) && str_equiv(value->string, (TCHAR *) default_value)) { - error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - - if (set_expand_string(key, (TCHAR *) name, value->string)) return -1; - - return 1; -} - -static int setting_get_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - TCHAR buffer[VALUE_LENGTH]; - - if (get_string(key, (TCHAR *) name, (TCHAR *) buffer, (unsigned long) sizeof(buffer), false, false, false)) return -1; - - return value_from_string(name, value, buffer); -} - -static int setting_set_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - unsigned long exitcode; - TCHAR *code; - TCHAR action_string[ACTION_LEN]; - - if (additional) { - /* Default action? */ - if (is_default(additional)) code = 0; - else { - if (str_number(additional, &exitcode)) return -1; - code = (TCHAR *) additional; - } - } - - HKEY key = open_registry(service_name, name, KEY_WRITE); - if (! key) return -1; - - long error; - int ret = 1; - - /* Resetting to default? */ - if (value && value->string) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), value->string); - else { - if (code) { - /* Delete explicit action. */ - error = RegDeleteValue(key, code); - RegCloseKey(key); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, code, service_name, error_string(error)); - return -1; - } - else { - /* Explicitly keep the default action. */ - if (default_value) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), (TCHAR *) default_value); - ret = 0; - } - } - - /* Validate the string. */ - for (int i = 0; exit_action_strings[i]; i++) { - if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) { - if (default_value && str_equiv(action_string, (TCHAR *) default_value)) ret = 0; - if (RegSetValueEx(key, code, 0, REG_SZ, (const unsigned char *) exit_action_strings[i], (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) { - print_message(stderr, NSSM_MESSAGE_SETVALUE_FAILED, code, service_name, error_string(GetLastError())); - RegCloseKey(key); - return -1; - } - - RegCloseKey(key); - return ret; - } - } - - print_message(stderr, NSSM_MESSAGE_INVALID_EXIT_ACTION, action_string); - for (int i = 0; exit_action_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), exit_action_strings[i]); - - return -1; -} - -static int setting_get_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - unsigned long exitcode = 0; - unsigned long *code = 0; - - if (additional) { - if (! is_default(additional)) { - if (str_number(additional, &exitcode)) return -1; - code = &exitcode; - } - } - - TCHAR action_string[ACTION_LEN]; - bool default_action; - if (get_exit_action(service_name, code, action_string, &default_action)) return -1; - - value_from_string(name, value, action_string); - - if (default_action && ! _tcsnicmp((const TCHAR *) action_string, (TCHAR *) default_value, ACTION_LEN)) return 0; - return 1; -} - -static inline bool split_hook_name(const TCHAR *hook_name, TCHAR *hook_event, TCHAR *hook_action) { - TCHAR *s; - - for (s = (TCHAR *) hook_name; *s; s++) { - if (*s == _T('/')) { - *s = _T('\0'); - _sntprintf_s(hook_event, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), hook_name); - _sntprintf_s(hook_action, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), ++s); - return valid_hook_name(hook_event, hook_action, false); - } - } - - print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_NAME, hook_name); - return false; -} - -static int setting_set_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - TCHAR hook_event[HOOK_NAME_LENGTH]; - TCHAR hook_action[HOOK_NAME_LENGTH]; - if (! split_hook_name(additional, hook_event, hook_action)) return -1; - - TCHAR *cmd; - if (value && value->string) cmd = value->string; - else cmd = _T(""); - - if (set_hook(service_name, hook_event, hook_action, cmd)) return -1; - if (! _tcslen(cmd)) return 0; - return 1; -} - -static int setting_get_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - TCHAR hook_event[HOOK_NAME_LENGTH]; - TCHAR hook_action[HOOK_NAME_LENGTH]; - if (! split_hook_name(additional, hook_event, hook_action)) return -1; - - TCHAR cmd[CMD_LENGTH]; - if (get_hook(service_name, hook_event, hook_action, cmd, sizeof(cmd))) return -1; - - value_from_string(name, value, cmd); - - if (! _tcslen(cmd)) return 0; - return 1; -} - -static int setting_set_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! key) return -1; - - long error; - __int64 mask; - __int64 system_affinity = 0LL; - - if (value && value->string) { - DWORD_PTR affinity; - if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, (DWORD_PTR *) &system_affinity)) system_affinity = ~0; - - if (is_default(value->string) || str_equiv(value->string, NSSM_AFFINITY_ALL)) mask = 0LL; - else if (affinity_string_to_mask(value->string, &mask)) { - print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, value->string, num_cpus() - 1); - return -1; - } - } - else mask = 0LL; - - if (! mask) { - error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - - /* Canonicalise. */ - TCHAR *canon = 0; - if (affinity_mask_to_string(mask, &canon)) canon = value->string; - - __int64 effective_affinity = mask & system_affinity; - if (effective_affinity != mask) { - /* Requested CPUs did not intersect with available CPUs? */ - if (! effective_affinity) mask = effective_affinity = system_affinity; - - TCHAR *system = 0; - if (! affinity_mask_to_string(system_affinity, &system)) { - TCHAR *effective = 0; - if (! affinity_mask_to_string(effective_affinity, &effective)) { - print_message(stderr, NSSM_MESSAGE_EFFECTIVE_AFFINITY_MASK, value->string, system, effective); - HeapFree(GetProcessHeap(), 0, effective); - } - HeapFree(GetProcessHeap(), 0, system); - } - } - - if (RegSetValueEx(key, name, 0, REG_SZ, (const unsigned char *) canon, (unsigned long) (_tcslen(canon) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) { - if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon); - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, name, error_string(GetLastError()), 0); - return -1; - } - - if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon); - return 1; -} - -static int setting_get_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! key) return -1; - - unsigned long type; - TCHAR *buffer = 0; - unsigned long buflen = 0; - - int ret = RegQueryValueEx(key, name, 0, &type, 0, &buflen); - if (ret == ERROR_FILE_NOT_FOUND) { - if (value_from_string(name, value, NSSM_AFFINITY_ALL) == 1) return 0; - return -1; - } - if (ret != ERROR_SUCCESS) return -1; - - if (type != REG_SZ) return -1; - - buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen); - if (! buffer) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("affinity"), _T("setting_get_affinity")); - return -1; - } - - if (get_string(key, (TCHAR *) name, buffer, buflen, false, false, true)) { - HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - __int64 affinity; - if (affinity_string_to_mask(buffer, &affinity)) { - print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, buffer, num_cpus() - 1); - HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - HeapFree(GetProcessHeap(), 0, buffer); - - /* Canonicalise. */ - if (affinity_mask_to_string(affinity, &buffer)) { - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - ret = value_from_string(name, value, buffer); - HeapFree(GetProcessHeap(), 0, buffer); - return ret; -} - -static int setting_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! param) return -1; - - if (! value || ! value->string || ! value->string[0]) { - long error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - - unsigned long envlen = (unsigned long) _tcslen(value->string) + 1; - TCHAR *unformatted = 0; - unsigned long newlen; - if (unformat_double_null(value->string, envlen, &unformatted, &newlen)) return -1; - - if (test_environment(unformatted)) { - HeapFree(GetProcessHeap(), 0, unformatted); - print_message(stderr, NSSM_GUI_INVALID_ENVIRONMENT); - return -1; - } - - if (RegSetValueEx(key, name, 0, REG_MULTI_SZ, (const unsigned char *) unformatted, (unsigned long) newlen * sizeof(TCHAR)) != ERROR_SUCCESS) { - if (newlen) HeapFree(GetProcessHeap(), 0, unformatted); - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0); - return -1; - } - - if (newlen) HeapFree(GetProcessHeap(), 0, unformatted); - return 1; -} - -static int setting_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! param) return -1; - - TCHAR *env = 0; - unsigned long envlen; - if (get_environment((TCHAR *) service_name, key, (TCHAR *) name, &env, &envlen)) return -1; - if (! envlen) return 0; - - TCHAR *formatted; - unsigned long newlen; - if (format_double_null(env, envlen, &formatted, &newlen)) return -1; - - int ret; - if (additional) { - /* Find named environment variable. */ - TCHAR *s; - size_t len = _tcslen(additional); - for (s = env; *s; s++) { - /* Look for = NULL NULL */ - if (! _tcsnicmp(s, additional, len) && s[len] == _T('=')) { - /* Strip = */ - s += len + 1; - ret = value_from_string(name, value, s); - HeapFree(GetProcessHeap(), 0, env); - return ret; - } - - /* Skip this string. */ - for ( ; *s; s++); - } - HeapFree(GetProcessHeap(), 0, env); - return 0; - } - - HeapFree(GetProcessHeap(), 0, env); - - ret = value_from_string(name, value, formatted); - if (newlen) HeapFree(GetProcessHeap(), 0, formatted); - return ret; -} - -static int setting_set_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! param) return -1; - - TCHAR *priority_string; - int i; - long error; - - if (value && value->string) priority_string = value->string; - else if (default_value) priority_string = (TCHAR *) default_value; - else { - error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - - for (i = 0; priority_strings[i]; i++) { - if (! str_equiv(priority_strings[i], priority_string)) continue; - - if (default_value && str_equiv(priority_string, (TCHAR *) default_value)) { - error = RegDeleteValue(key, name); - if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; - print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); - return -1; - } - - if (set_number(key, (TCHAR *) name, priority_index_to_constant(i))) return -1; - return 1; - } - - print_message(stderr, NSSM_MESSAGE_INVALID_PRIORITY, priority_string); - for (i = 0; priority_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), priority_strings[i]); - - return -1; -} - -static int setting_get_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = (HKEY) param; - if (! param) return -1; - - unsigned long constant; - switch (get_number(key, (TCHAR *) name, &constant, false)) { - case 0: return value_from_string(name, value, (const TCHAR *) default_value); - case -1: return -1; - } - - return value_from_string(name, value, priority_strings[priority_constant_to_index(constant)]); -} - -/* Functions to manage native service settings. */ -static int native_set_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - /* - Get existing service dependencies because we must set both types together. - */ - TCHAR *buffer; - unsigned long buflen; - if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1; - - if (! value || ! value->string || ! value->string[0]) { - if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) { - print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return 0; - } - - unsigned long len = (unsigned long) _tcslen(value->string) + 1; - TCHAR *unformatted = 0; - unsigned long newlen; - if (unformat_double_null(value->string, len, &unformatted, &newlen)) { - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - /* Prepend group identifier. */ - unsigned long missing = 0; - TCHAR *canon = unformatted; - size_t canonlen = 0; - TCHAR *s; - for (s = unformatted; *s; s++) { - if (*s != SC_GROUP_IDENTIFIER) missing++; - size_t len = _tcslen(s); - canonlen += len + 1; - s += len; - } - - if (missing) { - /* Missing identifiers plus double NULL terminator. */ - canonlen += missing + 1; - newlen = (unsigned long) canonlen; - - canon = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, canonlen * sizeof(TCHAR)); - if (! canon) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("native_set_dependongroup")); - if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - size_t i = 0; - for (s = unformatted; *s; s++) { - if (*s != SC_GROUP_IDENTIFIER) canon[i++] = SC_GROUP_IDENTIFIER; - size_t len = _tcslen(s); - memmove(canon + i, s, (len + 1) * sizeof(TCHAR)); - i += len + 1; - s += len; - } - } - - TCHAR *dependencies; - if (buflen > 2) { - dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR)); - if (! dependencies) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependongroup")); - if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon); - if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - memmove(dependencies, buffer, buflen * sizeof(TCHAR)); - memmove(dependencies + buflen - 1, canon, newlen * sizeof(TCHAR)); - } - else dependencies = canon; - - int ret = 1; - if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1; - if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies); - if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon); - if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - - return ret; -} - -static int native_get_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - TCHAR *buffer; - unsigned long buflen; - if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1; - - int ret; - if (buflen) { - TCHAR *formatted; - unsigned long newlen; - if (format_double_null(buffer, buflen, &formatted, &newlen)) { - HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - ret = value_from_string(name, value, formatted); - HeapFree(GetProcessHeap(), 0, formatted); - HeapFree(GetProcessHeap(), 0, buffer); - } - else { - value->string = 0; - ret = 0; - } - - return ret; -} - -static int native_set_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - /* - Get existing group dependencies because we must set both types together. - */ - TCHAR *buffer; - unsigned long buflen; - if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1; - - if (! value || ! value->string || ! value->string[0]) { - if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) { - print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return 0; - } - - unsigned long len = (unsigned long) _tcslen(value->string) + 1; - TCHAR *unformatted = 0; - unsigned long newlen; - if (unformat_double_null(value->string, len, &unformatted, &newlen)) { - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - TCHAR *dependencies; - if (buflen > 2) { - dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR)); - if (! dependencies) { - print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependonservice")); - if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - memmove(dependencies, buffer, buflen * sizeof(TCHAR)); - memmove(dependencies + buflen - 1, unformatted, newlen * sizeof(TCHAR)); - } - else dependencies = unformatted; - - int ret = 1; - if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1; - if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies); - if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); - if (buffer) HeapFree(GetProcessHeap(), 0, buffer); - - return ret; -} - -static int native_get_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - TCHAR *buffer; - unsigned long buflen; - if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1; - - int ret; - if (buflen) { - TCHAR *formatted; - unsigned long newlen; - if (format_double_null(buffer, buflen, &formatted, &newlen)) { - HeapFree(GetProcessHeap(), 0, buffer); - return -1; - } - - ret = value_from_string(name, value, formatted); - HeapFree(GetProcessHeap(), 0, formatted); - HeapFree(GetProcessHeap(), 0, buffer); - } - else { - value->string = 0; - ret = 0; - } - - return ret; -} - -int native_set_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - TCHAR *description = 0; - if (value) description = value->string; - if (set_service_description(service_name, service_handle, description)) return -1; - - if (description && description[0]) return 1; - - return 0; -} - -int native_get_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - TCHAR buffer[VALUE_LENGTH]; - if (get_service_description(service_name, service_handle, _countof(buffer), buffer)) return -1; - - if (buffer[0]) return value_from_string(name, value, buffer); - value->string = 0; - - return 0; -} - -int native_set_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - TCHAR *displayname = 0; - if (value && value->string) displayname = value->string; - else displayname = (TCHAR *) service_name; - - if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, displayname)) { - print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); - return -1; - } - - /* - If the display name and service name differ only in case, - ChangeServiceConfig() will return success but the display name will be - set to the service name, NOT the value passed to the function. - This appears to be a quirk of Windows rather than a bug here. - */ - if (displayname != service_name && ! str_equiv(displayname, service_name)) return 1; - - return 0; -} - -int native_get_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); - if (! qsc) return -1; - - int ret = value_from_string(name, value, qsc->lpDisplayName); - HeapFree(GetProcessHeap(), 0, qsc); - - return ret; -} - -int native_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = open_service_registry(service_name, KEY_SET_VALUE, false); - if (! key) return -1; - - int ret = setting_set_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional); - RegCloseKey(key); - return ret; -} - -int native_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - HKEY key = open_service_registry(service_name, KEY_READ, false); - if (! key) return -1; - - ZeroMemory(value, sizeof(value_t)); - int ret = setting_get_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional); - RegCloseKey(key); - return ret; -} - -int native_set_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - /* It makes no sense to try to reset the image path. */ - if (! value || ! value->string) { - print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name); - return -1; - } - - if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, value->string, 0, 0, 0, 0, 0, 0)) { - print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); - return -1; - } - - return 1; -} - -int native_get_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); - if (! qsc) return -1; - - int ret = value_from_string(name, value, qsc->lpBinaryPathName); - HeapFree(GetProcessHeap(), 0, qsc); - - return ret; -} - -int native_set_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - print_message(stderr, NSSM_MESSAGE_CANNOT_RENAME_SERVICE); - return -1; -} - -int native_get_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - return value_from_string(name, value, service_name); -} - -int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - /* - Logical syntax is: nssm set ObjectName - That means the username is actually passed in the additional parameter. - */ - bool localsystem = false; - TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT; - TCHAR *password = 0; - if (additional) { - username = (TCHAR *) additional; - if (value && value->string) password = value->string; - } - else if (value && value->string) username = value->string; - - const TCHAR *well_known = well_known_username(username); - size_t passwordsize = 0; - if (well_known) { - if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) localsystem = true; - username = (TCHAR *) well_known; - password = _T(""); - } - else if (! password) { - /* We need a password if the account requires it. */ - print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name); - return -1; - } - else passwordsize = _tcslen(password) * sizeof(TCHAR); - - /* - ChangeServiceConfig() will fail to set the username if the service is set - to interact with the desktop. - */ - unsigned long type = SERVICE_NO_CHANGE; - if (! localsystem) { - QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); - if (! qsc) { - if (passwordsize) SecureZeroMemory(password, passwordsize); - return -1; - } - - type = qsc->dwServiceType & ~SERVICE_INTERACTIVE_PROCESS; - HeapFree(GetProcessHeap(), 0, qsc); - } - - if (! well_known) { - if (grant_logon_as_service(username)) { - if (passwordsize) SecureZeroMemory(password, passwordsize); - print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username); - return -1; - } - } - - if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, 0)) { - if (passwordsize) SecureZeroMemory(password, passwordsize); - print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); - return -1; - } - - if (passwordsize) SecureZeroMemory(password, passwordsize); - - if (localsystem) return 0; - - return 1; -} - -int native_get_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); - if (! qsc) return -1; - - int ret = value_from_string(name, value, qsc->lpServiceStartName); - HeapFree(GetProcessHeap(), 0, qsc); - - return ret; -} - -int native_set_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - /* It makes no sense to try to reset the startup type. */ - if (! value || ! value->string) { - print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name); - return -1; - } - - /* Map NSSM_STARTUP_* constant to Windows SERVICE_*_START constant. */ - int service_startup = -1; - int i; - for (i = 0; startup_strings[i]; i++) { - if (str_equiv(value->string, startup_strings[i])) { - service_startup = i; - break; - } - } - - if (service_startup < 0) { - print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_STARTUP, value->string); - for (i = 0; startup_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), startup_strings[i]); - return -1; - } - - unsigned long startup; - switch (service_startup) { - case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break; - case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break; - default: startup = SERVICE_AUTO_START; - } - - if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) { - print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); - return -1; - } - - SERVICE_DELAYED_AUTO_START_INFO delayed; - ZeroMemory(&delayed, sizeof(delayed)); - if (service_startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1; - else delayed.fDelayedAutostart = 0; - if (! ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) { - unsigned long error = GetLastError(); - /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */ - if (error != ERROR_INVALID_LEVEL) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_MESSAGE_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service_name, error_string(error), 0); - } - } - - return 1; -} - -int native_get_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); - if (! qsc) return -1; - - unsigned long startup; - int ret = get_service_startup(service_name, service_handle, qsc, &startup); - HeapFree(GetProcessHeap(), 0, qsc); - - if (ret) return -1; - - unsigned long i; - for (i = 0; startup_strings[i]; i++); - if (startup >= i) return -1; - - return value_from_string(name, value, startup_strings[startup]); -} - -int native_set_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - /* It makes no sense to try to reset the service type. */ - if (! value || ! value->string) { - print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name); - return -1; - } - - /* - We can only manage services of type SERVICE_WIN32_OWN_PROCESS - and SERVICE_INTERACTIVE_PROCESS. - */ - unsigned long type = SERVICE_WIN32_OWN_PROCESS; - if (str_equiv(value->string, NSSM_INTERACTIVE_PROCESS)) type |= SERVICE_INTERACTIVE_PROCESS; - else if (! str_equiv(value->string, NSSM_WIN32_OWN_PROCESS)) { - print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_TYPE, value->string); - _ftprintf(stderr, _T("%s\n"), NSSM_WIN32_OWN_PROCESS); - _ftprintf(stderr, _T("%s\n"), NSSM_INTERACTIVE_PROCESS); - return -1; - } - - /* - ChangeServiceConfig() will fail if the service runs under an account - other than LOCALSYSTEM and we try to make it interactive. - */ - if (type & SERVICE_INTERACTIVE_PROCESS) { - QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); - if (! qsc) return -1; - - if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) { - HeapFree(GetProcessHeap(), 0, qsc); - print_message(stderr, NSSM_MESSAGE_INTERACTIVE_NOT_LOCALSYSTEM, value->string, service_name, NSSM_LOCALSYSTEM_ACCOUNT); - return -1; - } - - HeapFree(GetProcessHeap(), 0, qsc); - } - - if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) { - print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); - return -1; - } - - return 1; -} - -int native_get_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { - SC_HANDLE service_handle = (SC_HANDLE) param; - if (! service_handle) return -1; - - QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); - if (! qsc) return -1; - - value->numeric = qsc->dwServiceType; - HeapFree(GetProcessHeap(), 0, qsc); - - const TCHAR *string; - switch (value->numeric) { - case SERVICE_KERNEL_DRIVER: string = NSSM_KERNEL_DRIVER; break; - case SERVICE_FILE_SYSTEM_DRIVER: string = NSSM_FILE_SYSTEM_DRIVER; break; - case SERVICE_WIN32_OWN_PROCESS: string = NSSM_WIN32_OWN_PROCESS; break; - case SERVICE_WIN32_SHARE_PROCESS: string = NSSM_WIN32_SHARE_PROCESS; break; - case SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_INTERACTIVE_PROCESS; break; - case SERVICE_WIN32_SHARE_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_SHARE_INTERACTIVE_PROCESS; break; - default: string = NSSM_UNKNOWN; - } - - return value_from_string(name, value, string); -} - -int set_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) { - if (! key) return -1; - int ret; - - if (setting->set) ret = setting->set(service_name, (void *) key, setting->name, setting->default_value, value, additional); - else ret = -1; - - if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name); - else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name); - else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name); - - return ret; -} - -int set_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) { - if (! service_handle) return -1; - - int ret; - if (setting->set) ret = setting->set(service_name, service_handle, setting->name, setting->default_value, value, additional); - else ret = -1; - - if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name); - else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name); - else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name); - - return ret; -} - -/* - Returns: 1 if the value was retrieved. - 0 if the default value was retrieved. - -1 on error. -*/ -int get_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) { - if (! key) return -1; - int ret; - - switch (setting->type) { - case REG_EXPAND_SZ: - case REG_MULTI_SZ: - case REG_SZ: - value->string = (TCHAR *) setting->default_value; - if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional); - else ret = -1; - break; - - case REG_DWORD: - value->numeric = PtrToUlong(setting->default_value); - if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional); - else ret = -1; - break; - - default: - ret = -1; - break; - } - - if (ret < 0) print_message(stderr, NSSM_MESSAGE_GET_SETTING_FAILED, setting->name, service_name); - - return ret; -} - -int get_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) { - if (! service_handle) return -1; - return setting->get(service_name, service_handle, setting->name, 0, value, additional); -} - -settings_t settings[] = { - { NSSM_REG_EXE, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string }, - { NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string }, - { NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string }, - { NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action }, - { NSSM_REG_HOOK, REG_SZ, (void *) _T(""), false, ADDITIONAL_MANDATORY, setting_set_hook, setting_get_hook }, - { NSSM_REG_AFFINITY, REG_SZ, 0, false, 0, setting_set_affinity, setting_get_affinity }, - { NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment }, - { NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment }, - { NSSM_REG_NO_CONSOLE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_PRIORITY, REG_SZ, (void *) priority_strings[NSSM_NORMAL_PRIORITY], false, 0, setting_set_priority, setting_get_priority }, - { NSSM_REG_RESTART_DELAY, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string }, - { NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDIN NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDIN_FLAGS, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDOUT, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string }, - { NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDOUT NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string }, - { NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STDERR NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_KILL_PROCESS_TREE, REG_DWORD, (void *) 1, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_ROTATE_ONLINE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, - { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number }, - { NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup }, - { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice }, - { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description }, - { NSSM_NATIVE_DISPLAYNAME, REG_SZ, NULL, true, 0, native_set_displayname, native_get_displayname }, - { NSSM_NATIVE_ENVIRONMENT, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_environment, native_get_environment }, - { NSSM_NATIVE_IMAGEPATH, REG_EXPAND_SZ, NULL, true, 0, native_set_imagepath, native_get_imagepath }, - { NSSM_NATIVE_OBJECTNAME, REG_SZ, NSSM_LOCALSYSTEM_ACCOUNT, true, 0, native_set_objectname, native_get_objectname }, - { NSSM_NATIVE_NAME, REG_SZ, NULL, true, 0, native_set_name, native_get_name }, - { NSSM_NATIVE_STARTUP, REG_SZ, NULL, true, 0, native_set_startup, native_get_startup }, - { NSSM_NATIVE_TYPE, REG_SZ, NULL, true, 0, native_set_type, native_get_type }, - { NULL, NULL, NULL, NULL, NULL } -}; +#include "nssm.h" +/* XXX: (value && value->string) is probably bogus because value is probably never null */ + +/* Affinity. */ +#define NSSM_AFFINITY_ALL _T("All") + +extern const TCHAR *exit_action_strings[]; +extern const TCHAR *startup_strings[]; +extern const TCHAR *priority_strings[]; + +/* Does the parameter refer to the default value of the setting? */ +static inline int is_default(const TCHAR *value) { + return (str_equiv(value, _T("default")) || str_equiv(value, _T("*")) || ! value[0]); +} + +static int value_from_string(const TCHAR *name, value_t *value, const TCHAR *string) { + size_t len = _tcslen(string); + if (! len++) { + value->string = 0; + return 0; + } + + value->string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); + if (! value->string) { + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()")); + return -1; + } + + if (_sntprintf_s(value->string, len, _TRUNCATE, _T("%s"), string) < 0) { + HeapFree(GetProcessHeap(), 0, value->string); + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()")); + return -1; + } + + return 1; +} + +/* Functions to manage NSSM-specific settings in the registry. */ +static int setting_set_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! key) return -1; + + unsigned long number; + long error; + + /* Resetting to default? */ + if (! value || ! value->string) { + error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + if (str_number(value->string, &number)) return -1; + + if (default_value && number == PtrToUlong(default_value)) { + error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + + if (set_number(key, (TCHAR *) name, number)) return -1; + + return 1; +} + +static int setting_get_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + return get_number(key, (TCHAR *) name, &value->numeric, false); +} + +static int setting_set_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! key) return -1; + + long error; + + /* Resetting to default? */ + if (! value || ! value->string) { + if (default_value) value->string = (TCHAR *) default_value; + else { + error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + } + if (default_value && _tcslen((TCHAR *) default_value) && str_equiv(value->string, (TCHAR *) default_value)) { + error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + + if (set_expand_string(key, (TCHAR *) name, value->string)) return -1; + + return 1; +} + +static int setting_get_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + TCHAR buffer[VALUE_LENGTH]; + + if (get_string(key, (TCHAR *) name, (TCHAR *) buffer, (unsigned long) sizeof(buffer), false, false, false)) return -1; + + return value_from_string(name, value, buffer); +} + +static int setting_set_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + unsigned long exitcode; + TCHAR *code; + TCHAR action_string[ACTION_LEN]; + + if (additional) { + /* Default action? */ + if (is_default(additional)) code = 0; + else { + if (str_number(additional, &exitcode)) return -1; + code = (TCHAR *) additional; + } + } + + HKEY key = open_registry(service_name, name, KEY_WRITE); + if (! key) return -1; + + long error; + int ret = 1; + + /* Resetting to default? */ + if (value && value->string) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), value->string); + else { + if (code) { + /* Delete explicit action. */ + error = RegDeleteValue(key, code); + RegCloseKey(key); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, code, service_name, error_string(error)); + return -1; + } + else { + /* Explicitly keep the default action. */ + if (default_value) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), (TCHAR *) default_value); + ret = 0; + } + } + + /* Validate the string. */ + for (int i = 0; exit_action_strings[i]; i++) { + if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) { + if (default_value && str_equiv(action_string, (TCHAR *) default_value)) ret = 0; + if (RegSetValueEx(key, code, 0, REG_SZ, (const unsigned char *) exit_action_strings[i], (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) { + print_message(stderr, NSSM_MESSAGE_SETVALUE_FAILED, code, service_name, error_string(GetLastError())); + RegCloseKey(key); + return -1; + } + + RegCloseKey(key); + return ret; + } + } + + print_message(stderr, NSSM_MESSAGE_INVALID_EXIT_ACTION, action_string); + for (int i = 0; exit_action_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), exit_action_strings[i]); + + return -1; +} + +static int setting_get_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + unsigned long exitcode = 0; + unsigned long *code = 0; + + if (additional) { + if (! is_default(additional)) { + if (str_number(additional, &exitcode)) return -1; + code = &exitcode; + } + } + + TCHAR action_string[ACTION_LEN]; + bool default_action; + if (get_exit_action(service_name, code, action_string, &default_action)) return -1; + + value_from_string(name, value, action_string); + + if (default_action && ! _tcsnicmp((const TCHAR *) action_string, (TCHAR *) default_value, ACTION_LEN)) return 0; + return 1; +} + +static inline bool split_hook_name(const TCHAR *hook_name, TCHAR *hook_event, TCHAR *hook_action) { + TCHAR *s; + + for (s = (TCHAR *) hook_name; *s; s++) { + if (*s == _T('/')) { + *s = _T('\0'); + _sntprintf_s(hook_event, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), hook_name); + _sntprintf_s(hook_action, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), ++s); + return valid_hook_name(hook_event, hook_action, false); + } + } + + print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_NAME, hook_name); + return false; +} + +static int setting_set_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + TCHAR hook_event[HOOK_NAME_LENGTH]; + TCHAR hook_action[HOOK_NAME_LENGTH]; + if (! split_hook_name(additional, hook_event, hook_action)) return -1; + + TCHAR *cmd; + if (value && value->string) cmd = value->string; + else cmd = _T(""); + + if (set_hook(service_name, hook_event, hook_action, cmd)) return -1; + if (! _tcslen(cmd)) return 0; + return 1; +} + +static int setting_get_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + TCHAR hook_event[HOOK_NAME_LENGTH]; + TCHAR hook_action[HOOK_NAME_LENGTH]; + if (! split_hook_name(additional, hook_event, hook_action)) return -1; + + TCHAR cmd[CMD_LENGTH]; + if (get_hook(service_name, hook_event, hook_action, cmd, sizeof(cmd))) return -1; + + value_from_string(name, value, cmd); + + if (! _tcslen(cmd)) return 0; + return 1; +} + +static int setting_set_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! key) return -1; + + long error; + __int64 mask; + __int64 system_affinity = 0LL; + + if (value && value->string) { + DWORD_PTR affinity; + if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, (DWORD_PTR *) &system_affinity)) system_affinity = ~0; + + if (is_default(value->string) || str_equiv(value->string, NSSM_AFFINITY_ALL)) mask = 0LL; + else if (affinity_string_to_mask(value->string, &mask)) { + print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, value->string, num_cpus() - 1); + return -1; + } + } + else mask = 0LL; + + if (! mask) { + error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + + /* Canonicalise. */ + TCHAR *canon = 0; + if (affinity_mask_to_string(mask, &canon)) canon = value->string; + + __int64 effective_affinity = mask & system_affinity; + if (effective_affinity != mask) { + /* Requested CPUs did not intersect with available CPUs? */ + if (! effective_affinity) mask = effective_affinity = system_affinity; + + TCHAR *system = 0; + if (! affinity_mask_to_string(system_affinity, &system)) { + TCHAR *effective = 0; + if (! affinity_mask_to_string(effective_affinity, &effective)) { + print_message(stderr, NSSM_MESSAGE_EFFECTIVE_AFFINITY_MASK, value->string, system, effective); + HeapFree(GetProcessHeap(), 0, effective); + } + HeapFree(GetProcessHeap(), 0, system); + } + } + + if (RegSetValueEx(key, name, 0, REG_SZ, (const unsigned char *) canon, (unsigned long) (_tcslen(canon) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) { + if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon); + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, name, error_string(GetLastError()), 0); + return -1; + } + + if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon); + return 1; +} + +static int setting_get_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! key) return -1; + + unsigned long type; + TCHAR *buffer = 0; + unsigned long buflen = 0; + + int ret = RegQueryValueEx(key, name, 0, &type, 0, &buflen); + if (ret == ERROR_FILE_NOT_FOUND) { + if (value_from_string(name, value, NSSM_AFFINITY_ALL) == 1) return 0; + return -1; + } + if (ret != ERROR_SUCCESS) return -1; + + if (type != REG_SZ) return -1; + + buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen); + if (! buffer) { + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("affinity"), _T("setting_get_affinity")); + return -1; + } + + if (get_string(key, (TCHAR *) name, buffer, buflen, false, false, true)) { + HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + __int64 affinity; + if (affinity_string_to_mask(buffer, &affinity)) { + print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, buffer, num_cpus() - 1); + HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + HeapFree(GetProcessHeap(), 0, buffer); + + /* Canonicalise. */ + if (affinity_mask_to_string(affinity, &buffer)) { + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + ret = value_from_string(name, value, buffer); + HeapFree(GetProcessHeap(), 0, buffer); + return ret; +} + +static int setting_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! param) return -1; + + if (! value || ! value->string || ! value->string[0]) { + long error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + + unsigned long envlen = (unsigned long) _tcslen(value->string) + 1; + TCHAR *unformatted = 0; + unsigned long newlen; + if (unformat_double_null(value->string, envlen, &unformatted, &newlen)) return -1; + + if (test_environment(unformatted)) { + HeapFree(GetProcessHeap(), 0, unformatted); + print_message(stderr, NSSM_GUI_INVALID_ENVIRONMENT); + return -1; + } + + if (RegSetValueEx(key, name, 0, REG_MULTI_SZ, (const unsigned char *) unformatted, (unsigned long) newlen * sizeof(TCHAR)) != ERROR_SUCCESS) { + if (newlen) HeapFree(GetProcessHeap(), 0, unformatted); + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0); + return -1; + } + + if (newlen) HeapFree(GetProcessHeap(), 0, unformatted); + return 1; +} + +static int setting_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! param) return -1; + + TCHAR *env = 0; + unsigned long envlen; + if (get_environment((TCHAR *) service_name, key, (TCHAR *) name, &env, &envlen)) return -1; + if (! envlen) return 0; + + TCHAR *formatted; + unsigned long newlen; + if (format_double_null(env, envlen, &formatted, &newlen)) return -1; + + int ret; + if (additional) { + /* Find named environment variable. */ + TCHAR *s; + size_t len = _tcslen(additional); + for (s = env; *s; s++) { + /* Look for = NULL NULL */ + if (! _tcsnicmp(s, additional, len) && s[len] == _T('=')) { + /* Strip = */ + s += len + 1; + ret = value_from_string(name, value, s); + HeapFree(GetProcessHeap(), 0, env); + return ret; + } + + /* Skip this string. */ + for ( ; *s; s++); + } + HeapFree(GetProcessHeap(), 0, env); + return 0; + } + + HeapFree(GetProcessHeap(), 0, env); + + ret = value_from_string(name, value, formatted); + if (newlen) HeapFree(GetProcessHeap(), 0, formatted); + return ret; +} + +static int setting_set_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! param) return -1; + + TCHAR *priority_string; + int i; + long error; + + if (value && value->string) priority_string = value->string; + else if (default_value) priority_string = (TCHAR *) default_value; + else { + error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + + for (i = 0; priority_strings[i]; i++) { + if (! str_equiv(priority_strings[i], priority_string)) continue; + + if (default_value && str_equiv(priority_string, (TCHAR *) default_value)) { + error = RegDeleteValue(key, name); + if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0; + print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error)); + return -1; + } + + if (set_number(key, (TCHAR *) name, priority_index_to_constant(i))) return -1; + return 1; + } + + print_message(stderr, NSSM_MESSAGE_INVALID_PRIORITY, priority_string); + for (i = 0; priority_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), priority_strings[i]); + + return -1; +} + +static int setting_get_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = (HKEY) param; + if (! param) return -1; + + unsigned long constant; + switch (get_number(key, (TCHAR *) name, &constant, false)) { + case 0: return value_from_string(name, value, (const TCHAR *) default_value); + case -1: return -1; + } + + return value_from_string(name, value, priority_strings[priority_constant_to_index(constant)]); +} + +/* Functions to manage native service settings. */ +static int native_set_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + /* + Get existing service dependencies because we must set both types together. + */ + TCHAR *buffer; + unsigned long buflen; + if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1; + + if (! value || ! value->string || ! value->string[0]) { + if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) { + print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return 0; + } + + unsigned long len = (unsigned long) _tcslen(value->string) + 1; + TCHAR *unformatted = 0; + unsigned long newlen; + if (unformat_double_null(value->string, len, &unformatted, &newlen)) { + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + /* Prepend group identifier. */ + unsigned long missing = 0; + TCHAR *canon = unformatted; + size_t canonlen = 0; + TCHAR *s; + for (s = unformatted; *s; s++) { + if (*s != SC_GROUP_IDENTIFIER) missing++; + size_t len = _tcslen(s); + canonlen += len + 1; + s += len; + } + + if (missing) { + /* Missing identifiers plus double NULL terminator. */ + canonlen += missing + 1; + newlen = (unsigned long) canonlen; + + canon = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, canonlen * sizeof(TCHAR)); + if (! canon) { + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("native_set_dependongroup")); + if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + size_t i = 0; + for (s = unformatted; *s; s++) { + if (*s != SC_GROUP_IDENTIFIER) canon[i++] = SC_GROUP_IDENTIFIER; + size_t len = _tcslen(s); + memmove(canon + i, s, (len + 1) * sizeof(TCHAR)); + i += len + 1; + s += len; + } + } + + TCHAR *dependencies; + if (buflen > 2) { + dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR)); + if (! dependencies) { + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependongroup")); + if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon); + if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + memmove(dependencies, buffer, buflen * sizeof(TCHAR)); + memmove(dependencies + buflen - 1, canon, newlen * sizeof(TCHAR)); + } + else dependencies = canon; + + int ret = 1; + if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1; + if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies); + if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon); + if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + + return ret; +} + +static int native_get_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + TCHAR *buffer; + unsigned long buflen; + if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1; + + int ret; + if (buflen) { + TCHAR *formatted; + unsigned long newlen; + if (format_double_null(buffer, buflen, &formatted, &newlen)) { + HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + ret = value_from_string(name, value, formatted); + HeapFree(GetProcessHeap(), 0, formatted); + HeapFree(GetProcessHeap(), 0, buffer); + } + else { + value->string = 0; + ret = 0; + } + + return ret; +} + +static int native_set_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + /* + Get existing group dependencies because we must set both types together. + */ + TCHAR *buffer; + unsigned long buflen; + if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1; + + if (! value || ! value->string || ! value->string[0]) { + if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) { + print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return 0; + } + + unsigned long len = (unsigned long) _tcslen(value->string) + 1; + TCHAR *unformatted = 0; + unsigned long newlen; + if (unformat_double_null(value->string, len, &unformatted, &newlen)) { + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + TCHAR *dependencies; + if (buflen > 2) { + dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR)); + if (! dependencies) { + print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependonservice")); + if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + memmove(dependencies, buffer, buflen * sizeof(TCHAR)); + memmove(dependencies + buflen - 1, unformatted, newlen * sizeof(TCHAR)); + } + else dependencies = unformatted; + + int ret = 1; + if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1; + if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies); + if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted); + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + + return ret; +} + +static int native_get_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + TCHAR *buffer; + unsigned long buflen; + if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1; + + int ret; + if (buflen) { + TCHAR *formatted; + unsigned long newlen; + if (format_double_null(buffer, buflen, &formatted, &newlen)) { + HeapFree(GetProcessHeap(), 0, buffer); + return -1; + } + + ret = value_from_string(name, value, formatted); + HeapFree(GetProcessHeap(), 0, formatted); + HeapFree(GetProcessHeap(), 0, buffer); + } + else { + value->string = 0; + ret = 0; + } + + return ret; +} + +int native_set_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + TCHAR *description = 0; + if (value) description = value->string; + if (set_service_description(service_name, service_handle, description)) return -1; + + if (description && description[0]) return 1; + + return 0; +} + +int native_get_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + TCHAR buffer[VALUE_LENGTH]; + if (get_service_description(service_name, service_handle, _countof(buffer), buffer)) return -1; + + if (buffer[0]) return value_from_string(name, value, buffer); + value->string = 0; + + return 0; +} + +int native_set_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + TCHAR *displayname = 0; + if (value && value->string) displayname = value->string; + else displayname = (TCHAR *) service_name; + + if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, displayname)) { + print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); + return -1; + } + + /* + If the display name and service name differ only in case, + ChangeServiceConfig() will return success but the display name will be + set to the service name, NOT the value passed to the function. + This appears to be a quirk of Windows rather than a bug here. + */ + if (displayname != service_name && ! str_equiv(displayname, service_name)) return 1; + + return 0; +} + +int native_get_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); + if (! qsc) return -1; + + int ret = value_from_string(name, value, qsc->lpDisplayName); + HeapFree(GetProcessHeap(), 0, qsc); + + return ret; +} + +int native_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = open_service_registry(service_name, KEY_SET_VALUE, false); + if (! key) return -1; + + int ret = setting_set_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional); + RegCloseKey(key); + return ret; +} + +int native_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + HKEY key = open_service_registry(service_name, KEY_READ, false); + if (! key) return -1; + + ZeroMemory(value, sizeof(value_t)); + int ret = setting_get_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional); + RegCloseKey(key); + return ret; +} + +int native_set_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + /* It makes no sense to try to reset the image path. */ + if (! value || ! value->string) { + print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name); + return -1; + } + + if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, value->string, 0, 0, 0, 0, 0, 0)) { + print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); + return -1; + } + + return 1; +} + +int native_get_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); + if (! qsc) return -1; + + int ret = value_from_string(name, value, qsc->lpBinaryPathName); + HeapFree(GetProcessHeap(), 0, qsc); + + return ret; +} + +int native_set_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + print_message(stderr, NSSM_MESSAGE_CANNOT_RENAME_SERVICE); + return -1; +} + +int native_get_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + return value_from_string(name, value, service_name); +} + +int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + /* + Logical syntax is: nssm set ObjectName + That means the username is actually passed in the additional parameter. + */ + bool localsystem = false; + TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT; + TCHAR *password = 0; + if (additional) { + username = (TCHAR *) additional; + if (value && value->string) password = value->string; + } + else if (value && value->string) username = value->string; + + const TCHAR *well_known = well_known_username(username); + size_t passwordsize = 0; + if (well_known) { + if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) localsystem = true; + username = (TCHAR *) well_known; + password = _T(""); + } + else if (! password) { + /* We need a password if the account requires it. */ + print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name); + return -1; + } + else passwordsize = _tcslen(password) * sizeof(TCHAR); + + /* + ChangeServiceConfig() will fail to set the username if the service is set + to interact with the desktop. + */ + unsigned long type = SERVICE_NO_CHANGE; + if (! localsystem) { + QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); + if (! qsc) { + if (passwordsize) SecureZeroMemory(password, passwordsize); + return -1; + } + + type = qsc->dwServiceType & ~SERVICE_INTERACTIVE_PROCESS; + HeapFree(GetProcessHeap(), 0, qsc); + } + + if (! well_known) { + if (grant_logon_as_service(username)) { + if (passwordsize) SecureZeroMemory(password, passwordsize); + print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username); + return -1; + } + } + + if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, 0)) { + if (passwordsize) SecureZeroMemory(password, passwordsize); + print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); + return -1; + } + + if (passwordsize) SecureZeroMemory(password, passwordsize); + + if (localsystem) return 0; + + return 1; +} + +int native_get_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); + if (! qsc) return -1; + + int ret = value_from_string(name, value, qsc->lpServiceStartName); + HeapFree(GetProcessHeap(), 0, qsc); + + return ret; +} + +int native_set_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + /* It makes no sense to try to reset the startup type. */ + if (! value || ! value->string) { + print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name); + return -1; + } + + /* Map NSSM_STARTUP_* constant to Windows SERVICE_*_START constant. */ + int service_startup = -1; + int i; + for (i = 0; startup_strings[i]; i++) { + if (str_equiv(value->string, startup_strings[i])) { + service_startup = i; + break; + } + } + + if (service_startup < 0) { + print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_STARTUP, value->string); + for (i = 0; startup_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), startup_strings[i]); + return -1; + } + + unsigned long startup; + switch (service_startup) { + case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break; + case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break; + default: startup = SERVICE_AUTO_START; + } + + if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) { + print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); + return -1; + } + + SERVICE_DELAYED_AUTO_START_INFO delayed; + ZeroMemory(&delayed, sizeof(delayed)); + if (service_startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1; + else delayed.fDelayedAutostart = 0; + if (! ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) { + unsigned long error = GetLastError(); + /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */ + if (error != ERROR_INVALID_LEVEL) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_MESSAGE_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service_name, error_string(error), 0); + } + } + + return 1; +} + +int native_get_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); + if (! qsc) return -1; + + unsigned long startup; + int ret = get_service_startup(service_name, service_handle, qsc, &startup); + HeapFree(GetProcessHeap(), 0, qsc); + + if (ret) return -1; + + unsigned long i; + for (i = 0; startup_strings[i]; i++); + if (startup >= i) return -1; + + return value_from_string(name, value, startup_strings[startup]); +} + +int native_set_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + /* It makes no sense to try to reset the service type. */ + if (! value || ! value->string) { + print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name); + return -1; + } + + /* + We can only manage services of type SERVICE_WIN32_OWN_PROCESS + and SERVICE_INTERACTIVE_PROCESS. + */ + unsigned long type = SERVICE_WIN32_OWN_PROCESS; + if (str_equiv(value->string, NSSM_INTERACTIVE_PROCESS)) type |= SERVICE_INTERACTIVE_PROCESS; + else if (! str_equiv(value->string, NSSM_WIN32_OWN_PROCESS)) { + print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_TYPE, value->string); + _ftprintf(stderr, _T("%s\n"), NSSM_WIN32_OWN_PROCESS); + _ftprintf(stderr, _T("%s\n"), NSSM_INTERACTIVE_PROCESS); + return -1; + } + + /* + ChangeServiceConfig() will fail if the service runs under an account + other than LOCALSYSTEM and we try to make it interactive. + */ + if (type & SERVICE_INTERACTIVE_PROCESS) { + QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); + if (! qsc) return -1; + + if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) { + HeapFree(GetProcessHeap(), 0, qsc); + print_message(stderr, NSSM_MESSAGE_INTERACTIVE_NOT_LOCALSYSTEM, value->string, service_name, NSSM_LOCALSYSTEM_ACCOUNT); + return -1; + } + + HeapFree(GetProcessHeap(), 0, qsc); + } + + if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) { + print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError())); + return -1; + } + + return 1; +} + +int native_get_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { + SC_HANDLE service_handle = (SC_HANDLE) param; + if (! service_handle) return -1; + + QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle); + if (! qsc) return -1; + + value->numeric = qsc->dwServiceType; + HeapFree(GetProcessHeap(), 0, qsc); + + const TCHAR *string; + switch (value->numeric) { + case SERVICE_KERNEL_DRIVER: string = NSSM_KERNEL_DRIVER; break; + case SERVICE_FILE_SYSTEM_DRIVER: string = NSSM_FILE_SYSTEM_DRIVER; break; + case SERVICE_WIN32_OWN_PROCESS: string = NSSM_WIN32_OWN_PROCESS; break; + case SERVICE_WIN32_SHARE_PROCESS: string = NSSM_WIN32_SHARE_PROCESS; break; + case SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_INTERACTIVE_PROCESS; break; + case SERVICE_WIN32_SHARE_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_SHARE_INTERACTIVE_PROCESS; break; + default: string = NSSM_UNKNOWN; + } + + return value_from_string(name, value, string); +} + +int set_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) { + if (! key) return -1; + int ret; + + if (setting->set) ret = setting->set(service_name, (void *) key, setting->name, setting->default_value, value, additional); + else ret = -1; + + if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name); + else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name); + else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name); + + return ret; +} + +int set_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) { + if (! service_handle) return -1; + + int ret; + if (setting->set) ret = setting->set(service_name, service_handle, setting->name, setting->default_value, value, additional); + else ret = -1; + + if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name); + else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name); + else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name); + + return ret; +} + +/* + Returns: 1 if the value was retrieved. + 0 if the default value was retrieved. + -1 on error. +*/ +int get_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) { + if (! key) return -1; + int ret; + + switch (setting->type) { + case REG_EXPAND_SZ: + case REG_MULTI_SZ: + case REG_SZ: + value->string = (TCHAR *) setting->default_value; + if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional); + else ret = -1; + break; + + case REG_DWORD: + value->numeric = PtrToUlong(setting->default_value); + if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional); + else ret = -1; + break; + + default: + ret = -1; + break; + } + + if (ret < 0) print_message(stderr, NSSM_MESSAGE_GET_SETTING_FAILED, setting->name, service_name); + + return ret; +} + +int get_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) { + if (! service_handle) return -1; + return setting->get(service_name, service_handle, setting->name, 0, value, additional); +} + +settings_t settings[] = { + { NSSM_REG_EXE, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string }, + { NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string }, + { NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string }, + { NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action }, + { NSSM_REG_HOOK, REG_SZ, (void *) _T(""), false, ADDITIONAL_MANDATORY, setting_set_hook, setting_get_hook }, + { NSSM_REG_AFFINITY, REG_SZ, 0, false, 0, setting_set_affinity, setting_get_affinity }, + { NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment }, + { NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment }, + { NSSM_REG_NO_CONSOLE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_PRIORITY, REG_SZ, (void *) priority_strings[NSSM_NORMAL_PRIORITY], false, 0, setting_set_priority, setting_get_priority }, + { NSSM_REG_RESTART_DELAY, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string }, + { NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDIN NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDIN_FLAGS, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDOUT, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string }, + { NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDOUT NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string }, + { NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STDERR NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_KILL_PROCESS_TREE, REG_DWORD, (void *) 1, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_ROTATE_ONLINE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number }, + { NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup }, + { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice }, + { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description }, + { NSSM_NATIVE_DISPLAYNAME, REG_SZ, NULL, true, 0, native_set_displayname, native_get_displayname }, + { NSSM_NATIVE_ENVIRONMENT, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_environment, native_get_environment }, + { NSSM_NATIVE_IMAGEPATH, REG_EXPAND_SZ, NULL, true, 0, native_set_imagepath, native_get_imagepath }, + { NSSM_NATIVE_OBJECTNAME, REG_SZ, NSSM_LOCALSYSTEM_ACCOUNT, true, 0, native_set_objectname, native_get_objectname }, + { NSSM_NATIVE_NAME, REG_SZ, NULL, true, 0, native_set_name, native_get_name }, + { NSSM_NATIVE_STARTUP, REG_SZ, NULL, true, 0, native_set_startup, native_get_startup }, + { NSSM_NATIVE_TYPE, REG_SZ, NULL, true, 0, native_set_type, native_get_type }, + { NULL, NULL, NULL, NULL, NULL } +}; diff --git a/settings.h b/settings.h index 2fcffa5..c28df38 100644 --- a/settings.h +++ b/settings.h @@ -1,48 +1,48 @@ -#ifndef SETTINGS_H -#define SETTINGS_H - -#define NSSM_NATIVE_DEPENDONGROUP _T("DependOnGroup") -#define NSSM_NATIVE_DEPENDONSERVICE _T("DependOnService") -#define NSSM_NATIVE_DESCRIPTION _T("Description") -#define NSSM_NATIVE_DISPLAYNAME _T("DisplayName") -#define NSSM_NATIVE_ENVIRONMENT _T("Environment") -#define NSSM_NATIVE_IMAGEPATH _T("ImagePath") -#define NSSM_NATIVE_NAME _T("Name") -#define NSSM_NATIVE_OBJECTNAME _T("ObjectName") -#define NSSM_NATIVE_STARTUP _T("Start") -#define NSSM_NATIVE_TYPE _T("Type") - -/* Are additional arguments needed? */ -#define ADDITIONAL_GETTING (1 << 0) -#define ADDITIONAL_SETTING (1 << 1) -#define ADDITIONAL_RESETTING (1 << 2) -#define ADDITIONAL_CRLF (1 << 3) -#define ADDITIONAL_MANDATORY ADDITIONAL_GETTING|ADDITIONAL_SETTING|ADDITIONAL_RESETTING - -#define DEPENDENCY_SERVICES (1 << 0) -#define DEPENDENCY_GROUPS (1 << 1) -#define DEPENDENCY_ALL (DEPENDENCY_SERVICES|DEPENDENCY_GROUPS) - -typedef union { - unsigned long numeric; - TCHAR *string; -} value_t; - -typedef int (*setting_function_t)(const TCHAR *, void *, const TCHAR *, void *, value_t *, const TCHAR *); - -typedef struct { - const TCHAR *name; - unsigned long type; - void *default_value; - bool native; - int additional; - setting_function_t set; - setting_function_t get; -} settings_t; - -int set_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *); -int set_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *); -int get_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *); -int get_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *); - -#endif +#ifndef SETTINGS_H +#define SETTINGS_H + +#define NSSM_NATIVE_DEPENDONGROUP _T("DependOnGroup") +#define NSSM_NATIVE_DEPENDONSERVICE _T("DependOnService") +#define NSSM_NATIVE_DESCRIPTION _T("Description") +#define NSSM_NATIVE_DISPLAYNAME _T("DisplayName") +#define NSSM_NATIVE_ENVIRONMENT _T("Environment") +#define NSSM_NATIVE_IMAGEPATH _T("ImagePath") +#define NSSM_NATIVE_NAME _T("Name") +#define NSSM_NATIVE_OBJECTNAME _T("ObjectName") +#define NSSM_NATIVE_STARTUP _T("Start") +#define NSSM_NATIVE_TYPE _T("Type") + +/* Are additional arguments needed? */ +#define ADDITIONAL_GETTING (1 << 0) +#define ADDITIONAL_SETTING (1 << 1) +#define ADDITIONAL_RESETTING (1 << 2) +#define ADDITIONAL_CRLF (1 << 3) +#define ADDITIONAL_MANDATORY ADDITIONAL_GETTING|ADDITIONAL_SETTING|ADDITIONAL_RESETTING + +#define DEPENDENCY_SERVICES (1 << 0) +#define DEPENDENCY_GROUPS (1 << 1) +#define DEPENDENCY_ALL (DEPENDENCY_SERVICES|DEPENDENCY_GROUPS) + +typedef union { + unsigned long numeric; + TCHAR *string; +} value_t; + +typedef int (*setting_function_t)(const TCHAR *, void *, const TCHAR *, void *, value_t *, const TCHAR *); + +typedef struct { + const TCHAR *name; + unsigned long type; + void *default_value; + bool native; + int additional; + setting_function_t set; + setting_function_t get; +} settings_t; + +int set_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *); +int set_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *); +int get_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *); +int get_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *); + +#endif -- 2.20.1 From c9c68980f322acce5ba5b58a495f0535e58f67bf Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 28 Feb 2016 09:01:09 +0000 Subject: [PATCH 14/16] Help git figure out how to diff .mc and .rc files. Visual Studio wants .mc and .rc files to be UTF-16LE, which git thinks are binary. Use .gitattributes to force treating them as text only when diffing, explicitly not handling them as text when submitting as they must retain their encoding to keep Visual Studio happy. Thanks to Mathias Breiner for proposing the use of .gitattributes in the repo. I had the rules in .git/info/exclude in my working copy for a long time and forgot about the issue! --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c69ca9c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.mc diff +*.rc diff -- 2.20.1 From e26682667e15433cad518e622c3c061840b487dd Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Tue, 1 Mar 2016 10:19:13 +0000 Subject: [PATCH 15/16] Alternative open_registry_key() signature. Allow passing a pointer to an HKEY and having the function return the result of RegCreateKeyEx()/RegOpenKeyEx(). Existing functions wrap the new invocation so no code changes are needed. The new function can be used to handle ERROR_FILE_NOT_FOUND as success when must_exist is false. --- registry.cpp | 33 +++++++++++++++++++++++---------- registry.h | 1 + 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/registry.cpp b/registry.cpp index 228ef8d..e3817a4 100644 --- a/registry.cpp +++ b/registry.cpp @@ -14,24 +14,31 @@ static int service_registry_path(const TCHAR *service_name, bool parameters, con return ret; } -static HKEY open_registry_key(const TCHAR *registry, REGSAM sam, bool must_exist) { - HKEY key; +static long open_registry_key(const TCHAR *registry, REGSAM sam, HKEY *key, bool must_exist) { + long error; if (sam & KEY_SET_VALUE) { - if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) { + error = RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, key, 0); + if (error != ERROR_SUCCESS) { + *key = 0; log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0); - return 0; + return error; } } else { - long error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key); + error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, key); if (error != ERROR_SUCCESS) { - if (error == ERROR_FILE_NOT_FOUND && ! must_exist) return 0; - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0); - return 0; + *key = 0; + if (error != ERROR_FILE_NOT_FOUND || must_exist) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0); } } + return error; +} + +static HKEY open_registry_key(const TCHAR *registry, REGSAM sam, bool must_exist) { + HKEY key; + long error = open_registry_key(registry, sam, &key, must_exist); return key; } @@ -504,7 +511,7 @@ HKEY open_service_registry(const TCHAR *service_name, REGSAM sam, bool must_exis return open_registry_key(registry, sam, must_exist); } -HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, bool must_exist) { +long open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, HKEY *key, bool must_exist) { /* Get registry */ TCHAR registry[KEY_LENGTH]; if (service_registry_path(service_name, true, sub, registry, _countof(registry)) < 0) { @@ -512,7 +519,13 @@ HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, bool return 0; } - return open_registry_key(registry, sam, must_exist); + return open_registry_key(registry, sam, key, must_exist); +} + +HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, bool must_exist) { + HKEY key; + long error = open_registry(service_name, sub, sam, &key, true); + return key; } HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) { diff --git a/registry.h b/registry.h index 394a49f..0580aff 100644 --- a/registry.h +++ b/registry.h @@ -38,6 +38,7 @@ #define NSSM_STDIO_LENGTH 29 HKEY open_service_registry(const TCHAR *, REGSAM sam, bool); +long open_registry(const TCHAR *, const TCHAR *, REGSAM sam, HKEY *, bool); HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam, bool); HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam); HKEY open_registry(const TCHAR *, REGSAM sam); -- 2.20.1 From 838c282576cff8e2f0467eab8965ccaee39075f5 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Tue, 1 Mar 2016 10:22:28 +0000 Subject: [PATCH 16/16] Not an error if no hook found in the registry. Hooks are optional so we shouldn't log an error if one wasn't found. Thanks Mathias Breiner. --- README.txt | 3 ++- registry.cpp | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.txt b/README.txt index 6fb7d9a..a95ed94 100644 --- a/README.txt +++ b/README.txt @@ -880,7 +880,8 @@ Thanks to Gerald Haider for noticing that installing a service with NSSM in a path containing spaces was technically a security vulnerability. Thanks to Scott Ware for reporting a crash saving the environment on XP 32-bit. Thanks to Stefan and Michael Scherer for reporting a bug writing the event messages source. -Thanks to Paul Baxter and Mathias Breiner for help with Visual Studio 2015. +Thanks to Paul Baxter for help with Visual Studio 2015. +Thanks to Mathias Breiner for help with Visual Studio and some registry fixes. Licence ------- diff --git a/registry.cpp b/registry.cpp index e3817a4..86eaaf5 100644 --- a/registry.cpp +++ b/registry.cpp @@ -822,8 +822,15 @@ int get_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *ho log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("get_hook()"), 0); return 1; } - HKEY key = open_registry(service_name, registry, KEY_READ, false); - if (! key) return 1; + HKEY key; + long error = open_registry(service_name, registry, KEY_READ, &key, false); + if (! key) { + if (error == ERROR_FILE_NOT_FOUND) { + ZeroMemory(buffer, buflen); + return 0; + } + return 1; + } int ret = expand_parameter(key, (TCHAR *) hook_action, buffer, buflen, true, false); -- 2.20.1