diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4f8efcc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.mc diff +*.rc diff diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96e3eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +tmp/ +out/ +.vs +*.bin \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d0ed146 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +AMSM - AppKu Manifest Service Manager + +Fork of NSSM - the Non-Sucking Service Manager +https://www.nssm.cc + +License +AMSM is public domain. You may unconditionally use it and/or its source code for any purpose you wish. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..745f941 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# AppKu Manifest Service Manager +A Microsoft Windows service control tool based on the [NSSM](https://nssm.cc/) public-domain project. \ No newline at end of file diff --git a/account.cpp b/account.cpp new file mode 100644 index 0000000..47ef017 --- /dev/null +++ b/account.cpp @@ -0,0 +1,358 @@ +#include "amsm.h" + +#include + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ERROR_SUCCESS +#endif + +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 != STATUS_SUCCESS) { + print_message(stderr, AMSM_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, AMSM_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, AMSM_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; + int ret = to_utf16(expanded, &lsa_username.Buffer, (unsigned long *) &lsa_username.Length); + HeapFree(GetProcessHeap(), 0, expanded); + if (ret) { + if (policy == &handle) LsaClose(handle); + print_message(stderr, AMSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()")); + return 4; + } + lsa_username.Length *= sizeof(wchar_t); + lsa_username.MaximumLength = lsa_username.Length + sizeof(wchar_t); + + LSA_REFERENCED_DOMAIN_LIST *translated_domains; + LSA_TRANSLATED_SID *translated_sid; + NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid); + HeapFree(GetProcessHeap(), 0, lsa_username.Buffer); + if (policy == &handle) LsaClose(handle); + if (status != STATUS_SUCCESS) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + print_message(stderr, AMSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status))); + return 5; + } + + if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) { + if (translated_sid->Use != SidTypeUnknown || _tcsnicmp(AMSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN _T("\\"), username, _tcslen(AMSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + 1)) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_sid); + print_message(stderr, AMSM_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, AMSM_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, AMSM_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, AMSM_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; + } + + ret = 0; + if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) { + print_message(stderr, AMSM_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 != STATUS_SUCCESS) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_name); + print_message(stderr, AMSM_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, AMSM_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); + + unsigned long canonlen; + if (from_utf16(lsa_canon.Buffer, canon, &canonlen)) { + LsaFreeMemory(translated_domains); + LsaFreeMemory(translated_name); + print_message(stderr, AMSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid")); + return 10; + } + HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer); + + 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, AMSM_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; +} + +/* Build the virtual account name. */ +TCHAR *virtual_account(const TCHAR *service_name) { + size_t len = _tcslen(AMSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + _tcslen(service_name) + 2; + TCHAR *name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); + if (! name) { + print_message(stderr, AMSM_MESSAGE_OUT_OF_MEMORY, _T("name"), _T("virtual_account")); + return 0; + } + + _sntprintf_s(name, len, _TRUNCATE, _T("%s\\%s"), AMSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN, service_name); + return name; +} + +/* Does the username represent a virtual account for the service? */ +int is_virtual_account(const TCHAR *service_name, const TCHAR *username) { + if (! imports.IsWellKnownSid) return 0; + if (! service_name) return 0; + if (! username) return 0; + + TCHAR *canon = virtual_account(service_name); + int ret = str_equiv(canon, username); + HeapFree(GetProcessHeap(), 0, canon); + 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 AMSM_LOCALSYSTEM_ACCOUNT; + if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return AMSM_LOCALSERVICE_ACCOUNT; + if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return AMSM_NETWORKSERVICE_ACCOUNT; + return 0; +} + +const TCHAR *well_known_username(const TCHAR *username) { + if (! username) return AMSM_LOCALSYSTEM_ACCOUNT; + if (str_equiv(username, AMSM_LOCALSYSTEM_ACCOUNT)) return AMSM_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 = AMSM_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 != STATUS_SUCCESS) { + /* + 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, AMSM_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 != STATUS_SUCCESS) { + print_message(stderr, AMSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status))); + return 5; + } + + print_message(stdout, AMSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username); + return 0; +} diff --git a/account.h b/account.h new file mode 100644 index 0000000..35db3b6 --- /dev/null +++ b/account.h @@ -0,0 +1,28 @@ +#ifndef ACCOUNT_H +#define ACCOUNT_H + +#include + +/* Not really an account. The canonical name is NT Authority\System. */ +#define AMSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem") +/* Other well-known accounts which can start a service without a password. */ +#define AMSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService") +#define AMSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService") +/* Virtual service accounts. */ +#define AMSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN _T("NT Service") +/* This is explicitly a wide string. */ +#define AMSM_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 *); +TCHAR *virtual_account(const TCHAR *); +int is_virtual_account(const TCHAR *, 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/amsm.aps b/amsm.aps new file mode 100644 index 0000000..c06f318 Binary files /dev/null and b/amsm.aps differ diff --git a/amsm.cpp b/amsm.cpp new file mode 100644 index 0000000..2db4d86 --- /dev/null +++ b/amsm.cpp @@ -0,0 +1,332 @@ +#include "amsm.h" + +extern unsigned long tls_index; +extern bool is_admin; +extern imports_t imports; + +static TCHAR unquoted_imagepath[PATH_LENGTH]; +static TCHAR imagepath[PATH_LENGTH]; +static TCHAR imageargv0[PATH_LENGTH]; + +void nssm_exit(int status) { + free_imports(); + unsetup_utf8(); + exit(status); +} + +/* Are two strings case-insensitively equivalent? */ +int str_equiv(const TCHAR *a, const TCHAR *b) { + size_t len = _tcslen(a); + if (_tcslen(b) != len) return 0; + if (_tcsnicmp(a, b, len)) return 0; + return 1; +} + +/* Convert a string to a number. */ +int str_number(const TCHAR *string, unsigned long *number, TCHAR **bogus) { + if (! string) return 1; + + *number = _tcstoul(string, bogus, 0); + if (**bogus) return 2; + + return 0; +} + +/* User requested us to print our version. */ +static bool is_version(const TCHAR *s) { + if (! s || ! *s) return false; + /* /version */ + if (*s == '/') s++; + else if (*s == '-') { + /* -v, -V, -version, --version */ + s++; + if (*s == '-') s++; + else if (str_equiv(s, _T("v"))) return true; + } + if (str_equiv(s, _T("version"))) return true; + return false; +} + +int str_number(const TCHAR *string, unsigned long *number) { + TCHAR *bogus; + return str_number(string, number, &bogus); +} + +/* Does a char need to be escaped? */ +static bool needs_escape(const TCHAR c) { + if (c == _T('"')) return true; + if (c == _T('&')) return true; + if (c == _T('%')) return true; + if (c == _T('^')) return true; + if (c == _T('<')) return true; + if (c == _T('>')) return true; + if (c == _T('|')) return true; + return false; +} + +/* Does a char need to be quoted? */ +static bool needs_quote(const TCHAR c) { + if (c == _T(' ')) return true; + if (c == _T('\t')) return true; + if (c == _T('\n')) return true; + if (c == _T('\v')) return true; + if (c == _T('"')) return true; + if (c == _T('*')) return true; + return needs_escape(c); +} + +/* https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ */ +/* http://www.robvanderwoude.com/escapechars.php */ +int quote(const TCHAR *unquoted, TCHAR *buffer, size_t buflen) { + size_t i, j, n; + size_t len = _tcslen(unquoted); + if (len > buflen - 1) return 1; + + bool escape = false; + bool quotes = false; + + for (i = 0; i < len; i++) { + if (needs_escape(unquoted[i])) { + escape = quotes = true; + break; + } + if (needs_quote(unquoted[i])) quotes = true; + } + if (! quotes) { + memmove(buffer, unquoted, (len + 1) * sizeof(TCHAR)); + return 0; + } + + /* "" */ + size_t quoted_len = 2; + if (escape) quoted_len += 2; + for (i = 0; ; i++) { + n = 0; + + while (i != len && unquoted[i] == _T('\\')) { + i++; + n++; + } + + if (i == len) { + quoted_len += n * 2; + break; + } + else if (unquoted[i] == _T('"')) quoted_len += n * 2 + 2; + else quoted_len += n + 1; + if (needs_escape(unquoted[i])) quoted_len += n; + } + if (quoted_len > buflen - 1) return 1; + + TCHAR *s = buffer; + if (escape) *s++ = _T('^'); + *s++ = _T('"'); + + for (i = 0; ; i++) { + n = 0; + + while (i != len && unquoted[i] == _T('\\')) { + i++; + n++; + } + + if (i == len) { + for (j = 0; j < n * 2; j++) { + if (escape) *s++ = _T('^'); + *s++ = _T('\\'); + } + break; + } + else if (unquoted[i] == _T('"')) { + for (j = 0; j < n * 2 + 1; j++) { + if (escape) *s++ = _T('^'); + *s++ = _T('\\'); + } + if (escape && needs_escape(unquoted[i])) *s++ = _T('^'); + *s++ = unquoted[i]; + } + else { + for (j = 0; j < n; j++) { + if (escape) *s++ = _T('^'); + *s++ = _T('\\'); + } + if (escape && needs_escape(unquoted[i])) *s++ = _T('^'); + *s++ = unquoted[i]; + } + } + if (escape) *s++ = _T('^'); + *s++ = _T('"'); + *s++ = _T('\0'); + + return 0; +} + +/* Remove basename of a path. */ +void strip_basename(TCHAR *buffer) { + size_t len = _tcslen(buffer); + size_t i; + for (i = len; i && buffer[i] != _T('\\') && buffer[i] != _T('/'); i--); + /* X:\ is OK. */ + if (i && buffer[i - 1] == _T(':')) i++; + buffer[i] = _T('\0'); +} + +/* How to use me correctly */ +int usage(int ret) { + if ((! GetConsoleWindow() || ! GetStdHandle(STD_OUTPUT_HANDLE)) && GetProcessWindowStation()) popup_message(0, MB_OK, AMSM_MESSAGE_USAGE, AMSM_VERSION, AMSM_CONFIGURATION, AMSM_DATE); + else print_message(stderr, AMSM_MESSAGE_USAGE, AMSM_VERSION, AMSM_CONFIGURATION, AMSM_DATE); + return(ret); +} + +void check_admin() { + is_admin = false; + + /* Lifted from MSDN examples */ + PSID AdministratorsGroup; + SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; + if (! AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) return; + CheckTokenMembership(0, AdministratorsGroup, /*XXX*/(PBOOL) &is_admin); + FreeSid(AdministratorsGroup); +} + +static int elevate(int argc, TCHAR **argv, unsigned long message) { + print_message(stderr, message); + + SHELLEXECUTEINFO sei; + ZeroMemory(&sei, sizeof(sei)); + sei.cbSize = sizeof(sei); + sei.lpVerb = _T("runas"); + sei.lpFile = (TCHAR *) nssm_imagepath(); + + TCHAR *args = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EXE_LENGTH * sizeof(TCHAR)); + if (! args) { + print_message(stderr, AMSM_MESSAGE_OUT_OF_MEMORY, _T("GetCommandLine()"), _T("elevate()")); + return 111; + } + + /* Get command line, which includes the path to AMSM, and skip that part. */ + _sntprintf_s(args, EXE_LENGTH, _TRUNCATE, _T("%s"), GetCommandLine()); + size_t s = _tcslen(argv[0]) + 1; + if (args[0] == _T('"')) s += 2; + while (isspace(args[s])) s++; + + sei.lpParameters = args + s; + sei.nShow = SW_SHOW; + + unsigned long exitcode = 0; + if (! ShellExecuteEx(&sei)) exitcode = 100; + + HeapFree(GetProcessHeap(), 0, (void *) args); + return exitcode; +} + +int num_cpus() { + DWORD_PTR i, affinity, system_affinity; + if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) return 64; + for (i = 0; system_affinity & (1LL << i); i++) if (i == 64) break; + return (int) i; +} + +const TCHAR *nssm_unquoted_imagepath() { + return unquoted_imagepath; +} + +const TCHAR *nssm_imagepath() { + return imagepath; +} + +const TCHAR *nssm_exe() { + return imageargv0; +} + +int _tmain(int argc, TCHAR **argv) { + if (check_console()) setup_utf8(); + + /* Remember if we are admin */ + check_admin(); + + /* Set up function pointers. */ + if (get_imports()) nssm_exit(111); + + /* Remember our path for later. */ + _sntprintf_s(imageargv0, _countof(imageargv0), _TRUNCATE, _T("%s"), argv[0]); + PathQuoteSpaces(imageargv0); + GetModuleFileName(0, unquoted_imagepath, _countof(unquoted_imagepath)); + GetModuleFileName(0, imagepath, _countof(imagepath)); + PathQuoteSpaces(imagepath); + + /* Elevate */ + if (argc > 1) { + /* + Valid commands are: + start, stop, pause, continue, install, edit, get, set, reset, unset, remove + status, statuscode, rotate, list, processes, version + */ + if (is_version(argv[1])) { + _tprintf(_T("%s %s %s %s\n"), AMSM, AMSM_VERSION, AMSM_CONFIGURATION, AMSM_DATE); + nssm_exit(0); + } + if (str_equiv(argv[1], _T("start"))) nssm_exit(control_service(AMSM_SERVICE_CONTROL_START, argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("stop"))) nssm_exit(control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("restart"))) { + int ret = control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2); + if (ret) nssm_exit(ret); + nssm_exit(control_service(AMSM_SERVICE_CONTROL_START, argc - 2, argv + 2)); + } + if (str_equiv(argv[1], _T("pause"))) nssm_exit(control_service(SERVICE_CONTROL_PAUSE, argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("continue"))) nssm_exit(control_service(SERVICE_CONTROL_CONTINUE, argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("status"))) nssm_exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("statuscode"))) nssm_exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2, true)); + if (str_equiv(argv[1], _T("rotate"))) nssm_exit(control_service(AMSM_SERVICE_CONTROL_ROTATE, argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("install"))) { + if (! is_admin) nssm_exit(elevate(argc, argv, AMSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL)); + create_messages(); + nssm_exit(pre_install_service(argc - 2, argv + 2)); + } + if (str_equiv(argv[1], _T("edit")) || str_equiv(argv[1], _T("get")) || str_equiv(argv[1], _T("set")) || str_equiv(argv[1], _T("reset")) || str_equiv(argv[1], _T("unset")) || str_equiv(argv[1], _T("dump"))) { + int ret = pre_edit_service(argc - 1, argv + 1); + if (ret == 3 && ! is_admin && argc == 3) nssm_exit(elevate(argc, argv, AMSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT)); + /* There might be a password here. */ + for (int i = 0; i < argc; i++) SecureZeroMemory(argv[i], _tcslen(argv[i]) * sizeof(TCHAR)); + nssm_exit(ret); + } + if (str_equiv(argv[1], _T("list"))) nssm_exit(list_nssm_services(argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("processes"))) nssm_exit(service_process_tree(argc - 2, argv + 2)); + if (str_equiv(argv[1], _T("remove"))) { + if (! is_admin) nssm_exit(elevate(argc, argv, AMSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE)); + nssm_exit(pre_remove_service(argc - 2, argv + 2)); + } + } + + /* Thread local storage for error message buffer */ + tls_index = TlsAlloc(); + + /* Register messages */ + if (is_admin) create_messages(); + + /* + Optimisation for Windows 2000: + When we're run from the command line the StartServiceCtrlDispatcher() call + will time out after a few seconds on Windows 2000. On newer versions the + call returns instantly. Check for stdin first and only try to call the + function if there's no input stream found. Although it's possible that + we're running with input redirected it's much more likely that we're + actually running as a service. + This will save time when running with no arguments from a command prompt. + */ + if (! GetStdHandle(STD_INPUT_HANDLE)) { + /* Start service magic */ + SERVICE_TABLE_ENTRY table[] = { { AMSM, service_main }, { 0, 0 } }; + if (! StartServiceCtrlDispatcher(table)) { + unsigned long error = GetLastError(); + /* User probably ran nssm with no argument */ + if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) nssm_exit(usage(1)); + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_DISPATCHER_FAILED, error_string(error), 0); + nssm_exit(100); + } + } + else nssm_exit(usage(1)); + + /* And nothing more to do */ + nssm_exit(0); +} diff --git a/amsm.h b/amsm.h new file mode 100644 index 0000000..de5c68b --- /dev/null +++ b/amsm.h @@ -0,0 +1,162 @@ +#ifndef AMSM_H +#define AMSM_H + +/* + MSDN says, basically, that the maximum length of a path is 260 characters, + which is represented by the constant MAX_PATH. Except when it isn't. + + The maximum length of a directory path is MAX_PATH - 12 because it must be + possible to create a file in 8.3 format under any valid directory. + + Unicode versions of filesystem API functions accept paths up to 32767 + characters if the first four (wide) characters are L"\\?\" and each component + of the path, separated by L"\", does not exceed the value of + lpMaximumComponentLength returned by GetVolumeInformation(), which is + probably 255. But might not be. + + Relative paths are always limited to MAX_PATH because the L"\\?\" prefix + is not valid for a relative path. + + Note that we don't care about the last two paragraphs because we're only + concerned with allocating buffers big enough to store valid paths. If the + user tries to store invalid paths they will fit in the buffers but the + application will fail. The reason for the failure will end up in the + event log and the user will realise the mistake. + + So that's that cleared up, then. +*/ +#ifdef UNICODE +#define PATH_LENGTH 32767 +#else +#define PATH_LENGTH MAX_PATH +#endif +#define DIR_LENGTH PATH_LENGTH - 12 + +#define _WIN32_WINNT 0x0500 + +#define APSTUDIO_HIDDEN_SYMBOLS +#include +#include +#undef APSTUDIO_HIDDEN_SYMBOLS +#include +#include +#ifndef AMSM_COMPILE_RC +#include +#include +#include +#include +#include +#include "utf8.h" +#include "service.h" +#include "account.h" +#include "console.h" +#include "env.h" +#include "event.h" +#include "hook.h" +#include "imports.h" +#include "messages.h" +#include "process.h" +#include "registry.h" +#include "settings.h" +#include "io.h" +#include "gui.h" +#endif + +void nssm_exit(int); +int str_equiv(const TCHAR *, const TCHAR *); +int quote(const TCHAR *, TCHAR *, size_t); +void strip_basename(TCHAR *); +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(); +const TCHAR *nssm_exe(); + +#define AMSM _T("AMSM") +#ifdef _WIN64 +#define AMSM_ARCHITECTURE _T("64-bit") +#else +#define AMSM_ARCHITECTURE _T("32-bit") +#endif +#ifdef _DEBUG +#define AMSM_DEBUG _T(" debug") +#else +#define AMSM_DEBUG _T("") +#endif +#define AMSM_CONFIGURATION AMSM_ARCHITECTURE AMSM_DEBUG +#include "version.h" + +/* + Throttle the restart of the service if it stops before this many + milliseconds have elapsed since startup. Override in registry. +*/ +#define AMSM_RESET_THROTTLE_RESTART 1500 + +/* + How many milliseconds to wait for the application to die after sending + a Control-C event to its console. Override in registry. +*/ +#define AMSM_KILL_CONSOLE_GRACE_PERIOD 1500 +/* + How many milliseconds to wait for the application to die after posting to + its windows' message queues. Override in registry. +*/ +#define AMSM_KILL_WINDOW_GRACE_PERIOD 1500 +/* + How many milliseconds to wait for the application to die after posting to + its threads' message queues. Override in registry. +*/ +#define AMSM_KILL_THREADS_GRACE_PERIOD 1500 + +/* How many milliseconds to pause after rotating logs. */ +#define AMSM_ROTATE_DELAY 0 + +/* Margin of error for service status wait hints in milliseconds. */ +#define AMSM_WAITHINT_MARGIN 2000 + +/* Methods used to try to stop the application. */ +#define AMSM_STOP_METHOD_CONSOLE (1 << 0) +#define AMSM_STOP_METHOD_WINDOW (1 << 1) +#define AMSM_STOP_METHOD_THREADS (1 << 2) +#define AMSM_STOP_METHOD_TERMINATE (1 << 3) + +/* Startup types. */ +#define AMSM_STARTUP_AUTOMATIC 0 +#define AMSM_STARTUP_DELAYED 1 +#define AMSM_STARTUP_MANUAL 2 +#define AMSM_STARTUP_DISABLED 3 + +/* Exit actions. */ +#define AMSM_EXIT_RESTART 0 +#define AMSM_EXIT_IGNORE 1 +#define AMSM_EXIT_REALLY 2 +#define AMSM_EXIT_UNCLEAN 3 +#define AMSM_NUM_EXIT_ACTIONS 4 + +/* Process priority. */ +#define AMSM_REALTIME_PRIORITY 0 +#define AMSM_HIGH_PRIORITY 1 +#define AMSM_ABOVE_NORMAL_PRIORITY 2 +#define AMSM_NORMAL_PRIORITY 3 +#define AMSM_BELOW_NORMAL_PRIORITY 4 +#define AMSM_IDLE_PRIORITY 5 + +/* How many milliseconds to wait before updating service status. */ +#define AMSM_SERVICE_STATUS_DEADLINE 20000 + +/* User-defined service controls can be in the range 128-255. */ +#define AMSM_SERVICE_CONTROL_START 0 +#define AMSM_SERVICE_CONTROL_ROTATE 128 + +/* How many milliseconds to wait for a hook. */ +#define AMSM_HOOK_DEADLINE 60000 + +/* How many milliseconds to wait for outstanding hooks. */ +#define AMSM_HOOK_THREAD_DEADLINE 80000 + +/* How many milliseconds to wait for closing logging thread. */ +#define AMSM_CLEANUP_LOGGERS_DEADLINE 1500 + +#endif diff --git a/amsm.ico b/amsm.ico new file mode 100644 index 0000000..409dab9 Binary files /dev/null and b/amsm.ico differ diff --git a/amsm.rc b/amsm.rc new file mode 100644 index 0000000..83c5e0c Binary files /dev/null and b/amsm.rc differ diff --git a/amsm.sln b/amsm.sln new file mode 100644 index 0000000..41adfe6 --- /dev/null +++ b/amsm.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2002 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "amsm", "amsm.vcxproj", "{32995E05-606F-4D83-A2E6-C2B361B34DF1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repository", "Repository", "{C5C814EE-552D-4CA7-93B5-3CC60FF91E00}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|x64.ActiveCfg = Debug|x64 + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|x64.Build.0 = Debug|x64 + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|x86.ActiveCfg = Debug|Win32 + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|x86.Build.0 = Debug|Win32 + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|x64.ActiveCfg = Release|x64 + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|x64.Build.0 = Release|x64 + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|x86.ActiveCfg = Release|Win32 + {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1535E62B-6EB6-4B50-A63A-887D0AA67216} + EndGlobalSection +EndGlobal diff --git a/amsm.suo b/amsm.suo new file mode 100644 index 0000000..977e11e Binary files /dev/null and b/amsm.suo differ diff --git a/amsm.vcproj b/amsm.vcproj new file mode 100644 index 0000000..b5d1459 --- /dev/null +++ b/amsm.vcproj @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/amsm.vcxproj b/amsm.vcxproj new file mode 100644 index 0000000..4b00fbb --- /dev/null +++ b/amsm.vcxproj @@ -0,0 +1,348 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {32995E05-606F-4D83-A2E6-C2B361B34DF1} + amsm + 10.0.17763.0 + amsm + + + + Application + v141 + false + Unicode + + + Application + v141 + false + Unicode + + + Application + v141 + false + Unicode + + + Application + v141 + false + Unicode + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>15.0.26919.1 + + + out\$(Configuration)\win32\ + tmp\$(Configuration)\win32\ + true + + + out\$(Configuration)\win64\ + tmp\$(Configuration)\win64\ + true + + + out\$(Configuration)\win32\ + tmp\$(Configuration)\win32\ + false + + + out\$(Configuration)\win64\ + tmp\$(Configuration)\win64\ + false + + + + Setting version information + version.cmd + + + + + + + $(IntDir)$(ProjectName).tlb + + + + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x0809 + + + psapi.lib;shlwapi.lib;%(AdditionalDependencies) + true + true + $(OutDir)$(ProjectName).pdb + Console + false + + MachineX86 + + + true + $(IntDir)$(ProjectName).bsc + + + + + Setting version information + version.cmd + + + + + + + X64 + $(IntDir)$(ProjectName).tlb + + + + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + ProgramDatabase + + + _DEBUG;_WIN64;%(PreprocessorDefinitions) + 0x0809 + + + psapi.lib;shlwapi.lib;%(AdditionalDependencies) + true + true + $(OutDir)$(ProjectName).pdb + Console + false + + MachineX64 + + + true + $(IntDir)$(ProjectName).bsc + + + + + Setting version information + version.cmd + + + + + + + $(IntDir)$(ProjectName).tlb + + + + MaxSpeed + OnlyExplicitInline + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + true + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0809 + + + psapi.lib;shlwapi.lib;%(AdditionalDependencies) + true + true + $(OutDir)$(ProjectName).pdb + Console + true + true + false + + MachineX86 + + + true + $(IntDir)$(ProjectName).bsc + + + + + Setting version information + version.cmd + + + + + + + X64 + $(IntDir)$(ProjectName).tlb + + + + MaxSpeed + OnlyExplicitInline + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + true + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + ProgramDatabase + + + NDEBUG;_WIN64;%(PreprocessorDefinitions) + 0x0809 + + + psapi.lib;shlwapi.lib;%(AdditionalDependencies) + true + true + $(OutDir)$(ProjectName).pdb + Console + true + true + false + + MachineX64 + + + true + $(IntDir)$(ProjectName).bsc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Compiling messages + mc -u -U %(Filename).mc -r . -h . + + %(Filename).rc;%(Filename).h;%(Outputs) + Compiling messages + mc -u -U %(Filename).mc -r . -h . + + %(Filename).rc;%(Filename).h;%(Outputs) + Compiling messages + mc -u -U %(Filename).mc -r . -h . + + %(Filename).rc;%(Filename).h;%(Outputs) + Compiling messages + mc -u -U %(Filename).mc -r . -h . + + %(Filename).rc;%(Filename).h;%(Outputs) + + + + + + \ No newline at end of file diff --git a/amsm.vcxproj.filters b/amsm.vcxproj.filters new file mode 100644 index 0000000..9439410 --- /dev/null +++ b/amsm.vcxproj.filters @@ -0,0 +1,118 @@ + + + + + {5d8f64dc-ecb6-40e5-923e-8c5c902a084a} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {9169a1cc-afa0-4416-a4c0-da87187099f1} + h;hpp;hxx;hm;inl + + + {fdd3c88a-9003-469c-8bca-9aa63ac821a6} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/amsm.vcxproj.user b/amsm.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/amsm.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/console.cpp b/console.cpp new file mode 100644 index 0000000..f38a695 --- /dev/null +++ b/console.cpp @@ -0,0 +1,28 @@ +#include "amsm.h" + +/* See if we were launched from a console window. */ +bool check_console() { + /* If we're running in a service context there will be no console window. */ + HWND console = GetConsoleWindow(); + if (! console) return false; + + unsigned long pid; + if (! GetWindowThreadProcessId(console, &pid)) return false; + + /* + 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 true; + + /* We close our new console so that subsequent messages appear in a popup. */ + FreeConsole(); + return false; +} + +void alloc_console(nssm_service_t *service) { + if (service->no_console) return; + + AllocConsole(); +} diff --git a/console.h b/console.h new file mode 100644 index 0000000..1ea8ea4 --- /dev/null +++ b/console.h @@ -0,0 +1,7 @@ +#ifndef CONSOLE_H +#define CONSOLE_H + +bool check_console(); +void alloc_console(nssm_service_t *); + +#endif diff --git a/env.cpp b/env.cpp new file mode 100644 index 0000000..f268b2b --- /dev/null +++ b/env.cpp @@ -0,0 +1,259 @@ +#include "amsm.h" + +/* + Environment block is of the form: + + KEY1=VALUE1 NULL + KEY2=VALUE2 NULL + NULL + + A single variable KEY=VALUE has length 15: + + KEY=VALUE (13) NULL (1) + NULL (1) + + Environment variable names are case-insensitive! +*/ + +/* Find the length in characters of an environment block. */ +size_t environment_length(TCHAR *env) { + size_t len = 0; + + TCHAR *s; + for (s = env; ; s++) { + len++; + if (*s == _T('\0')) { + if (*(s + 1) == _T('\0')) { + len++; + break; + } + } + } + + return len; +} + +/* Copy an environment block. */ +TCHAR *copy_environment_block(TCHAR *env) { + TCHAR *newenv; + if (copy_double_null(env, (unsigned long) environment_length(env), &newenv)) return 0; + 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, AMSM_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, AMSM_EVENT_OUT_OF_MEMORY, _T("ExpandEnvironmentStrings()"), _T("expand_environment_string"), 0); + return 0; + } + + if (! ExpandEnvironmentStrings(string, ret, len)) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_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; +} + +/* + Create a new block with all the strings of the first block plus a new string. + If the key is already present its value will be overwritten in place. + If the key is blank or empty the new block will still be allocated and have + non-zero length. +*/ +int append_to_environment_block(TCHAR *env, unsigned long envlen, TCHAR *string, TCHAR **newenv, unsigned long *newlen) { + size_t keylen = 0; + if (string && string[0]) { + for (; string[keylen]; keylen++) { + if (string[keylen] == _T('=')) { + keylen++; + break; + } + } + } + return append_to_double_null(env, envlen, newenv, newlen, string, keylen, false); +} + +/* + Create a new block with all the strings of the first block minus the given + string. + If the key is not present the new block will be a copy of the original. + If the string is KEY=VALUE the key will only be removed if its value is + VALUE. + If the string is just KEY the key will unconditionally be removed. + If removing the string results in an empty list the new block will still be + allocated and have non-zero length. +*/ +int remove_from_environment_block(TCHAR *env, unsigned long envlen, TCHAR *string, TCHAR **newenv, unsigned long *newlen) { + if (! string || ! string[0] || string[0] == _T('=')) return 1; + + TCHAR *key = 0; + size_t len = _tcslen(string); + size_t i; + for (i = 0; i < len; i++) if (string[i] == _T('=')) break; + + /* Rewrite KEY to KEY= but leave KEY=VALUE alone. */ + size_t keylen = len; + if (i == len) keylen++; + + key = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (keylen + 1) * sizeof(TCHAR)); + if (! key) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, _T("key"), _T("remove_from_environment_block()"), 0); + return 2; + } + memmove(key, string, len * sizeof(TCHAR)); + if (keylen > len) key[keylen - 1] = _T('='); + key[keylen] = _T('\0'); + + int ret = remove_from_double_null(env, envlen, newenv, newlen, key, keylen, false); + HeapFree(GetProcessHeap(), 0, key); + + return ret; +} diff --git a/env.h b/env.h new file mode 100644 index 0000000..117d22b --- /dev/null +++ b/env.h @@ -0,0 +1,17 @@ +#ifndef ENV_H +#define ENV_H + +size_t environment_length(TCHAR *); +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(); +int append_to_environment_block(TCHAR *, unsigned long, TCHAR *, TCHAR **, unsigned long *); +int remove_from_environment_block(TCHAR *, unsigned long, TCHAR *, TCHAR **, unsigned long *); + +#endif diff --git a/event.cpp b/event.cpp new file mode 100644 index 0000000..ebee9a1 --- /dev/null +++ b/event.cpp @@ -0,0 +1,111 @@ +#include "amsm.h" + +#define AMSM_SOURCE _T("nssm") +#define AMSM_ERROR_BUFSIZE 65535 +#define AMSM_NUM_EVENT_STRINGS 16 +unsigned long tls_index; + +/* Convert error code to error string - must call LocalFree() on return value */ +TCHAR *error_string(unsigned long error) { + /* Thread-safe buffer */ + TCHAR *error_message = (TCHAR *) TlsGetValue(tls_index); + if (! error_message) { + error_message = (TCHAR *) LocalAlloc(LPTR, AMSM_ERROR_BUFSIZE); + if (! error_message) return _T(""); + TlsSetValue(tls_index, (void *) error_message); + } + + if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, GetUserDefaultLangID(), (TCHAR *) error_message, AMSM_ERROR_BUFSIZE, 0)) { + if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (TCHAR *) error_message, AMSM_ERROR_BUFSIZE, 0)) { + if (_sntprintf_s(error_message, AMSM_ERROR_BUFSIZE, _TRUNCATE, _T("system error %lu"), error) < 0) return 0; + } + } + return error_message; +} + +/* Convert message code to format string */ +TCHAR *message_string(unsigned long error) { + TCHAR *ret; + if (! FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, GetUserDefaultLangID(), (LPTSTR) &ret, AMSM_ERROR_BUFSIZE, 0)) { + if (! FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (LPTSTR) &ret, AMSM_ERROR_BUFSIZE, 0)) { + ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 32 * sizeof(TCHAR)); + if (_sntprintf_s(ret, AMSM_ERROR_BUFSIZE, _TRUNCATE, _T("system error %lu"), error) < 0) return 0; + } + } + return ret; +} + +/* Log a message to the Event Log */ +void log_event(unsigned short type, unsigned long id, ...) { + va_list arg; + int count; + TCHAR *s; + TCHAR *strings[AMSM_NUM_EVENT_STRINGS]; + + /* Open event log */ + HANDLE handle = RegisterEventSource(0, AMSM_SOURCE); + if (! handle) return; + + /* Log it */ + count = 0; + va_start(arg, id); + while ((s = va_arg(arg, TCHAR *)) && count < AMSM_NUM_EVENT_STRINGS - 1) strings[count++] = s; + strings[count] = 0; + va_end(arg); + ReportEvent(handle, type, 0, id, 0, count, 0, (const TCHAR **) strings, 0); + + /* Close event log */ + DeregisterEventSource(handle); +} + +/* Log a message to the console */ +void print_message(FILE *file, unsigned long id, ...) { + va_list arg; + + TCHAR *format = message_string(id); + if (! format) return; + + va_start(arg, id); + _vftprintf(file, format, arg); + va_end(arg); + + LocalFree(format); +} + +/* Show a GUI dialogue */ +int popup_message(HWND owner, unsigned int type, unsigned long id, ...) { + va_list arg; + + TCHAR *format = message_string(id); + if (! format) { + return MessageBox(0, _T("The message which was supposed to go here is missing!"), AMSM, MB_OK | MB_ICONEXCLAMATION); + } + + TCHAR blurb[AMSM_ERROR_BUFSIZE]; + va_start(arg, id); + if (_vsntprintf_s(blurb, _countof(blurb), _TRUNCATE, format, arg) < 0) { + va_end(arg); + LocalFree(format); + return MessageBox(0, _T("The message which was supposed to go here is too big!"), AMSM, MB_OK | MB_ICONEXCLAMATION); + } + va_end(arg); + + MSGBOXPARAMS params; + ZeroMemory(¶ms, sizeof(params)); + params.cbSize = sizeof(params); + params.hInstance = GetModuleHandle(0); + params.hwndOwner = owner; + params.lpszText = blurb; + params.lpszCaption = AMSM; + params.dwStyle = type; + if (type == MB_OK) { + params.dwStyle |= MB_USERICON; + params.lpszIcon = MAKEINTRESOURCE(IDI_AMSM); + } + + int ret = MessageBoxIndirect(¶ms); + + LocalFree(format); + + return ret; +} diff --git a/event.h b/event.h new file mode 100644 index 0000000..858cc49 --- /dev/null +++ b/event.h @@ -0,0 +1,10 @@ +#ifndef EVENT_H +#define EVENT_H + +TCHAR *error_string(unsigned long); +TCHAR *message_string(unsigned long); +void log_event(unsigned short, unsigned long, ...); +void print_message(FILE *, unsigned long, ...); +int popup_message(HWND, unsigned int, unsigned long, ...); + +#endif diff --git a/gui.cpp b/gui.cpp new file mode 100644 index 0000000..68c41cd --- /dev/null +++ b/gui.cpp @@ -0,0 +1,1367 @@ +#include "amsm.h" + +extern const TCHAR *hook_event_strings[]; +extern const TCHAR *hook_action_strings[]; + +static enum { AMSM_TAB_APPLICATION, AMSM_TAB_DETAILS, AMSM_TAB_LOGON, AMSM_TAB_DEPENDENCIES, AMSM_TAB_PROCESS, AMSM_TAB_SHUTDOWN, AMSM_TAB_EXIT, AMSM_TAB_IO, AMSM_TAB_ROTATION, AMSM_TAB_ENVIRONMENT, AMSM_TAB_HOOKS, AMSM_NUM_TABS } nssm_tabs; +static HWND tablist[AMSM_NUM_TABS]; +static int selected_tab; + +static HWND dialog(const TCHAR *templ, HWND parent, DLGPROC function, LPARAM l) { + /* The caller will deal with GetLastError()... */ + HRSRC resource = FindResourceEx(0, RT_DIALOG, templ, GetUserDefaultLangID()); + if (! resource) { + if (GetLastError() != ERROR_RESOURCE_LANG_NOT_FOUND) return 0; + resource = FindResourceEx(0, RT_DIALOG, templ, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); + if (! resource) return 0; + } + + HGLOBAL ret = LoadResource(0, resource); + if (! ret) return 0; + + return CreateDialogIndirectParam(0, (DLGTEMPLATE *) ret, parent, function, l); +} + +static HWND dialog(const TCHAR *templ, HWND parent, DLGPROC function) { + return dialog(templ, parent, function, 0); +} + +static inline void set_logon_enabled(unsigned char interact_enabled, unsigned char credentials_enabled) { + EnableWindow(GetDlgItem(tablist[AMSM_TAB_LOGON], IDC_INTERACT), interact_enabled); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_LOGON], IDC_USERNAME), credentials_enabled); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_LOGON], IDC_PASSWORD1), credentials_enabled); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_LOGON], IDC_PASSWORD2), credentials_enabled); +} + +int nssm_gui(int resource, nssm_service_t *service) { + /* Create window */ + HWND dlg = dialog(MAKEINTRESOURCE(resource), 0, nssm_dlg, (LPARAM) service); + if (! dlg) { + popup_message(0, MB_OK, AMSM_GUI_CREATEDIALOG_FAILED, error_string(GetLastError())); + return 1; + } + + /* Load the icon. */ + HANDLE icon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_AMSM), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + if (icon) SendMessage(dlg, WM_SETICON, ICON_SMALL, (LPARAM) icon); + icon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_AMSM), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0); + if (icon) SendMessage(dlg, WM_SETICON, ICON_BIG, (LPARAM) icon); + + /* Remember what the window is for. */ + SetWindowLongPtr(dlg, GWLP_USERDATA, (LONG_PTR) resource); + + /* Display the window */ + centre_window(dlg); + ShowWindow(dlg, SW_SHOW); + + /* Set service name if given */ + if (service->name[0]) { + SetDlgItemText(dlg, IDC_NAME, service->name); + /* No point making user click remove if the name is already entered */ + if (resource == IDD_REMOVE) { + HWND button = GetDlgItem(dlg, IDC_REMOVE); + if (button) { + SendMessage(button, WM_LBUTTONDOWN, 0, 0); + SendMessage(button, WM_LBUTTONUP, 0, 0); + } + } + } + + if (resource == IDD_EDIT) { + /* We'll need the service handle later. */ + SetWindowLongPtr(dlg, DWLP_USER, (LONG_PTR) service); + + /* Service name can't be edited. */ + EnableWindow(GetDlgItem(dlg, IDC_NAME), 0); + SetFocus(GetDlgItem(dlg, IDOK)); + + /* Set existing details. */ + HWND combo; + HWND list; + + /* Application tab. */ + if (service->native) SetDlgItemText(tablist[AMSM_TAB_APPLICATION], IDC_PATH, service->image); + else SetDlgItemText(tablist[AMSM_TAB_APPLICATION], IDC_PATH, service->exe); + SetDlgItemText(tablist[AMSM_TAB_APPLICATION], IDC_DIR, service->dir); + SetDlgItemText(tablist[AMSM_TAB_APPLICATION], IDC_FLAGS, service->flags); + + /* Details tab. */ + SetDlgItemText(tablist[AMSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname); + SetDlgItemText(tablist[AMSM_TAB_DETAILS], IDC_DESCRIPTION, service->description); + combo = GetDlgItem(tablist[AMSM_TAB_DETAILS], IDC_STARTUP); + SendMessage(combo, CB_SETCURSEL, service->startup, 0); + + /* Log on tab. */ + if (service->username) { + if (is_virtual_account(service->name, service->username)) { + CheckRadioButton(tablist[AMSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_VIRTUAL_SERVICE); + set_logon_enabled(0, 0); + } + else { + CheckRadioButton(tablist[AMSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_ACCOUNT); + SetDlgItemText(tablist[AMSM_TAB_LOGON], IDC_USERNAME, service->username); + set_logon_enabled(0, 1); + } + } + else { + CheckRadioButton(tablist[AMSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_LOCALSYSTEM); + if (service->type & SERVICE_INTERACTIVE_PROCESS) SendDlgItemMessage(tablist[AMSM_TAB_LOGON], IDC_INTERACT, BM_SETCHECK, BST_CHECKED, 0); + } + + /* Dependencies tab. */ + if (service->dependencieslen) { + TCHAR *formatted; + unsigned long newlen; + if (format_double_null(service->dependencies, service->dependencieslen, &formatted, &newlen)) { + popup_message(dlg, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("dependencies"), _T("nssm_dlg()")); + } + else { + SetDlgItemText(tablist[AMSM_TAB_DEPENDENCIES], IDC_DEPENDENCIES, formatted); + HeapFree(GetProcessHeap(), 0, formatted); + } + } + + /* Process tab. */ + if (service->priority) { + int priority = priority_constant_to_index(service->priority); + combo = GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_PRIORITY); + SendMessage(combo, CB_SETCURSEL, priority, 0); + } + + if (service->affinity) { + list = GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY); + SendDlgItemMessage(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_UNCHECKED, 0); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY), 1); + + DWORD_PTR affinity, system_affinity; + if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) { + if ((service->affinity & (__int64) system_affinity) != service->affinity) popup_message(dlg, MB_OK | MB_ICONWARNING, AMSM_GUI_WARN_AFFINITY); + } + + for (int i = 0; i < num_cpus(); i++) { + if (! (service->affinity & (1LL << (__int64) i))) SendMessage(list, LB_SETSEL, 0, i); + } + } + + if (service->no_console) { + SendDlgItemMessage(tablist[AMSM_TAB_PROCESS], IDC_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0); + } + + /* Shutdown tab. */ + if (! (service->stop_method & AMSM_STOP_METHOD_CONSOLE)) { + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE), 0); + } + SetDlgItemInt(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, service->kill_console_delay, 0); + if (! (service->stop_method & AMSM_STOP_METHOD_WINDOW)) { + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_WINDOW, BM_SETCHECK, BST_UNCHECKED, 0); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_WINDOW), 0); + } + SetDlgItemInt(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, service->kill_window_delay, 0); + if (! (service->stop_method & AMSM_STOP_METHOD_THREADS)) { + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_THREADS, BM_SETCHECK, BST_UNCHECKED, 0); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_THREADS), 0); + } + SetDlgItemInt(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_THREADS, service->kill_threads_delay, 0); + if (! (service->stop_method & AMSM_STOP_METHOD_TERMINATE)) { + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_TERMINATE, BM_SETCHECK, BST_UNCHECKED, 0); + } + if (! service->kill_process_tree) { + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_PROCESS_TREE, BM_SETCHECK, BST_UNCHECKED, 0); + } + + /* Restart tab. */ + SetDlgItemInt(tablist[AMSM_TAB_EXIT], IDC_THROTTLE, service->throttle_delay, 0); + combo = GetDlgItem(tablist[AMSM_TAB_EXIT], IDC_APPEXIT); + SendMessage(combo, CB_SETCURSEL, service->default_exit_action, 0); + SetDlgItemInt(tablist[AMSM_TAB_EXIT], IDC_RESTART_DELAY, service->restart_delay, 0); + + /* I/O tab. */ + SetDlgItemText(tablist[AMSM_TAB_IO], IDC_STDIN, service->stdin_path); + SetDlgItemText(tablist[AMSM_TAB_IO], IDC_STDOUT, service->stdout_path); + SetDlgItemText(tablist[AMSM_TAB_IO], IDC_STDERR, service->stderr_path); + if (service->timestamp_log) SendDlgItemMessage(tablist[AMSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_CHECKED, 0); + + /* Rotation tab. */ + if (service->stdout_disposition == CREATE_ALWAYS) SendDlgItemMessage(tablist[AMSM_TAB_ROTATION], IDC_TRUNCATE, BM_SETCHECK, BST_CHECKED, 0); + if (service->rotate_files) { + SendDlgItemMessage(tablist[AMSM_TAB_ROTATION], IDC_ROTATE, BM_SETCHECK, BST_CHECKED, 0); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_ONLINE), 1); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_SECONDS), 1); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), 1); + } + if (service->rotate_stdout_online || service->rotate_stderr_online) SendDlgItemMessage(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_CHECKED, 0); + SetDlgItemInt(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_SECONDS, service->rotate_seconds, 0); + if (! service->rotate_bytes_high) SetDlgItemInt(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, service->rotate_bytes_low, 0); + + /* Hooks tab. */ + if (service->hook_share_output_handles) SendDlgItemMessage(tablist[AMSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_CHECKED, 0); + + /* Check if advanced settings are in use. */ + if (service->stdout_disposition != service->stderr_disposition || (service->stdout_disposition && service->stdout_disposition != AMSM_STDOUT_DISPOSITION && service->stdout_disposition != CREATE_ALWAYS) || (service->stderr_disposition && service->stderr_disposition != AMSM_STDERR_DISPOSITION && service->stderr_disposition != CREATE_ALWAYS)) popup_message(dlg, MB_OK | MB_ICONWARNING, AMSM_GUI_WARN_STDIO); + if (service->rotate_bytes_high) popup_message(dlg, MB_OK | MB_ICONWARNING, AMSM_GUI_WARN_ROTATE_BYTES); + + /* Environment tab. */ + TCHAR *env; + unsigned long envlen; + if (service->env_extralen) { + env = service->env_extra; + envlen = service->env_extralen; + } + else { + env = service->env; + envlen = service->envlen; + if (envlen) SendDlgItemMessage(tablist[AMSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_SETCHECK, BST_CHECKED, 0); + } + + if (envlen) { + TCHAR *formatted; + unsigned long newlen; + if (format_double_null(env, envlen, &formatted, &newlen)) { + popup_message(dlg, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("nssm_dlg()")); + } + else { + SetDlgItemText(tablist[AMSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, formatted); + HeapFree(GetProcessHeap(), 0, formatted); + } + } + if (service->envlen && service->env_extralen) popup_message(dlg, MB_OK | MB_ICONWARNING, AMSM_GUI_WARN_ENVIRONMENT); + } + + /* Go! */ + MSG message; + while (GetMessage(&message, 0, 0, 0)) { + if (IsDialogMessage(dlg, &message)) continue; + TranslateMessage(&message); + DispatchMessage(&message); + } + + return (int) message.wParam; +} + +void centre_window(HWND window) { + HWND desktop; + RECT size, desktop_size; + unsigned long x, y; + + if (! window) return; + + /* Find window size */ + if (! GetWindowRect(window, &size)) return; + + /* Find desktop window */ + desktop = GetDesktopWindow(); + if (! desktop) return; + + /* Find desktop window size */ + if (! GetWindowRect(desktop, &desktop_size)) return; + + /* Centre window */ + x = (desktop_size.right - size.right) / 2; + y = (desktop_size.bottom - size.bottom) / 2; + MoveWindow(window, x, y, size.right - size.left, size.bottom - size.top, 0); +} + +static inline void check_stop_method(nssm_service_t *service, unsigned long method, unsigned long control) { + if (SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], control, BM_GETCHECK, 0, 0) & BST_CHECKED) return; + service->stop_method &= ~method; +} + +static inline void check_number(HWND tab, unsigned long control, unsigned long *timeout) { + BOOL translated; + unsigned long configured = GetDlgItemInt(tab, control, &translated, 0); + if (translated) *timeout = configured; +} + +static inline void set_timeout_enabled(unsigned long control, unsigned long dependent) { + unsigned char enabled = 0; + if (SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], control, BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 1; + EnableWindow(GetDlgItem(tablist[AMSM_TAB_SHUTDOWN], dependent), enabled); +} + +static inline void set_affinity_enabled(unsigned char enabled) { + EnableWindow(GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY), enabled); +} + +static inline void set_rotation_enabled(unsigned char enabled) { + EnableWindow(GetDlgItem(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_ONLINE), enabled); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_SECONDS), enabled); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), enabled); +} + +static inline int hook_env(const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *buffer, unsigned long buflen) { + return _sntprintf_s(buffer, buflen, _TRUNCATE, _T("AMSM_HOOK_%s_%s"), hook_event, hook_action); +} + +static inline void set_hook_tab(int event_index, int action_index, bool changed) { + int first_event = AMSM_GUI_HOOK_EVENT_START; + HWND combo; + combo = GetDlgItem(tablist[AMSM_TAB_HOOKS], IDC_HOOK_EVENT); + SendMessage(combo, CB_SETCURSEL, event_index, 0); + combo = GetDlgItem(tablist[AMSM_TAB_HOOKS], IDC_HOOK_ACTION); + SendMessage(combo, CB_RESETCONTENT, 0, 0); + + const TCHAR *hook_event = hook_event_strings[event_index]; + TCHAR *hook_action; + int i; + switch (event_index + first_event) { + case AMSM_GUI_HOOK_EVENT_ROTATE: + i = 0; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_ROTATE_PRE)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_PRE; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_ROTATE_POST)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_POST; + break; + + case AMSM_GUI_HOOK_EVENT_START: + i = 0; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_START_PRE)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_PRE; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_START_POST)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_POST; + break; + + case AMSM_GUI_HOOK_EVENT_STOP: + i = 0; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_STOP_PRE)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_PRE; + break; + + case AMSM_GUI_HOOK_EVENT_EXIT: + i = 0; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_EXIT_POST)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_POST; + break; + + case AMSM_GUI_HOOK_EVENT_POWER: + i = 0; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_POWER_CHANGE)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_CHANGE; + SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(AMSM_GUI_HOOK_ACTION_POWER_RESUME)); + if (action_index == i++) hook_action = AMSM_HOOK_ACTION_RESUME; + break; + } + + SendMessage(combo, CB_SETCURSEL, action_index, 0); + + TCHAR hook_name[HOOK_NAME_LENGTH]; + hook_env(hook_event, hook_action, hook_name, _countof(hook_name)); + + if (! *hook_name) return; + + TCHAR cmd[CMD_LENGTH]; + if (changed) { + GetDlgItemText(tablist[AMSM_TAB_HOOKS], IDC_HOOK, cmd, _countof(cmd)); + SetEnvironmentVariable(hook_name, cmd); + } + else { + if (! GetEnvironmentVariable(hook_name, cmd, _countof(cmd))) cmd[0] = _T('\0'); + SetDlgItemText(tablist[AMSM_TAB_HOOKS], IDC_HOOK, cmd); + } +} + +static inline int update_hook(TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action) { + TCHAR hook_name[HOOK_NAME_LENGTH]; + if (hook_env(hook_event, hook_action, hook_name, _countof(hook_name)) < 0) return 1; + TCHAR cmd[CMD_LENGTH]; + ZeroMemory(cmd, sizeof(cmd)); + GetEnvironmentVariable(hook_name, cmd, _countof(cmd)); + if (set_hook(service_name, hook_event, hook_action, cmd)) return 2; + return 0; +} + +static inline int update_hooks(TCHAR *service_name) { + int ret = 0; + ret += update_hook(service_name, AMSM_HOOK_EVENT_START, AMSM_HOOK_ACTION_PRE); + ret += update_hook(service_name, AMSM_HOOK_EVENT_START, AMSM_HOOK_ACTION_POST); + ret += update_hook(service_name, AMSM_HOOK_EVENT_STOP, AMSM_HOOK_ACTION_PRE); + ret += update_hook(service_name, AMSM_HOOK_EVENT_EXIT, AMSM_HOOK_ACTION_POST); + ret += update_hook(service_name, AMSM_HOOK_EVENT_POWER, AMSM_HOOK_ACTION_CHANGE); + ret += update_hook(service_name, AMSM_HOOK_EVENT_POWER, AMSM_HOOK_ACTION_RESUME); + ret += update_hook(service_name, AMSM_HOOK_EVENT_ROTATE, AMSM_HOOK_ACTION_PRE); + ret += update_hook(service_name, AMSM_HOOK_EVENT_ROTATE, AMSM_HOOK_ACTION_POST); + return ret; +} + +static inline void check_io(HWND owner, TCHAR *name, TCHAR *buffer, unsigned long len, unsigned long control) { + if (! SendMessage(GetDlgItem(tablist[AMSM_TAB_IO], control), WM_GETTEXTLENGTH, 0, 0)) return; + if (GetDlgItemText(tablist[AMSM_TAB_IO], control, buffer, (int) len)) return; + popup_message(owner, MB_OK | MB_ICONEXCLAMATION, AMSM_MESSAGE_PATH_TOO_LONG, name); + ZeroMemory(buffer, len * sizeof(TCHAR)); +} + +/* Set service parameters. */ +int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service) { + if (! service) return 1; + + set_nssm_service_defaults(service); + + if (orig_service) { + service->native = orig_service->native; + service->handle = orig_service->handle; + } + + /* Get service name. */ + if (! GetDlgItemText(window, IDC_NAME, service->name, _countof(service->name))) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_MISSING_SERVICE_NAME); + cleanup_nssm_service(service); + return 2; + } + + /* Get executable name */ + if (! service->native) { + if (! GetDlgItemText(tablist[AMSM_TAB_APPLICATION], IDC_PATH, service->exe, _countof(service->exe))) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_MISSING_PATH); + return 3; + } + + /* Get startup directory. */ + if (! GetDlgItemText(tablist[AMSM_TAB_APPLICATION], IDC_DIR, service->dir, _countof(service->dir))) { + _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe); + strip_basename(service->dir); + } + + /* Get flags. */ + if (SendMessage(GetDlgItem(tablist[AMSM_TAB_APPLICATION], IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) { + if (! GetDlgItemText(tablist[AMSM_TAB_APPLICATION], IDC_FLAGS, service->flags, _countof(service->flags))) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_OPTIONS); + return 4; + } + } + } + + /* Get details. */ + if (SendMessage(GetDlgItem(tablist[AMSM_TAB_DETAILS], IDC_DISPLAYNAME), WM_GETTEXTLENGTH, 0, 0)) { + if (! GetDlgItemText(tablist[AMSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname, _countof(service->displayname))) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_DISPLAYNAME); + return 5; + } + } + + if (SendMessage(GetDlgItem(tablist[AMSM_TAB_DETAILS], IDC_DESCRIPTION), WM_GETTEXTLENGTH, 0, 0)) { + if (! GetDlgItemText(tablist[AMSM_TAB_DETAILS], IDC_DESCRIPTION, service->description, _countof(service->description))) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_DESCRIPTION); + return 5; + } + } + + HWND combo = GetDlgItem(tablist[AMSM_TAB_DETAILS], IDC_STARTUP); + service->startup = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0); + if (service->startup == CB_ERR) service->startup = 0; + + /* Get logon stuff. */ + if (SendDlgItemMessage(tablist[AMSM_TAB_LOGON], IDC_LOCALSYSTEM, BM_GETCHECK, 0, 0) & BST_CHECKED) { + if (SendDlgItemMessage(tablist[AMSM_TAB_LOGON], IDC_INTERACT, BM_GETCHECK, 0, 0) & BST_CHECKED) { + service->type |= SERVICE_INTERACTIVE_PROCESS; + } + if (service->username) HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + if (service->password) { + SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR)); + HeapFree(GetProcessHeap(), 0, service->password); + } + service->password = 0; + service->passwordlen = 0; + } + else if (SendDlgItemMessage(tablist[AMSM_TAB_LOGON], IDC_VIRTUAL_SERVICE, BM_GETCHECK, 0, 0) & BST_CHECKED) { + if (service->username) HeapFree(GetProcessHeap(), 0, service->username); + service->username = virtual_account(service->name); + if (! service->username) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()")); + return 6; + } + service->usernamelen = _tcslen(service->username) + 1; + service->password = 0; + service->passwordlen = 0; + } + else { + /* Username. */ + service->usernamelen = SendMessage(GetDlgItem(tablist[AMSM_TAB_LOGON], IDC_USERNAME), WM_GETTEXTLENGTH, 0, 0); + if (! service->usernamelen) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_MISSING_USERNAME); + return 6; + } + service->usernamelen++; + + service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR)); + if (! service->username) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()")); + return 6; + } + if (! GetDlgItemText(tablist[AMSM_TAB_LOGON], IDC_USERNAME, service->username, (int) service->usernamelen)) { + HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_USERNAME); + return 6; + } + + /* + Special case for well-known accounts. + Ignore the password if we're editing and the username hasn't changed. + */ + const TCHAR *well_known = well_known_username(service->username); + if (well_known) { + if (str_equiv(well_known, AMSM_LOCALSYSTEM_ACCOUNT)) { + HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + } + else { + service->usernamelen = _tcslen(well_known) + 1; + service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR)); + if (! service->username) { + print_message(stderr, AMSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("install()")); + return 6; + } + memmove(service->username, well_known, service->usernamelen * sizeof(TCHAR)); + } + } + else { + /* Password. */ + service->passwordlen = SendMessage(GetDlgItem(tablist[AMSM_TAB_LOGON], IDC_PASSWORD1), WM_GETTEXTLENGTH, 0, 0); + size_t passwordlen = SendMessage(GetDlgItem(tablist[AMSM_TAB_LOGON], IDC_PASSWORD2), WM_GETTEXTLENGTH, 0, 0); + + if (! orig_service || ! orig_service->username || ! str_equiv(service->username, orig_service->username) || service->passwordlen || passwordlen) { + if (! service->passwordlen) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_MISSING_PASSWORD); + return 6; + } + if (passwordlen != service->passwordlen) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_MISSING_PASSWORD); + return 6; + } + service->passwordlen++; + + /* Temporary buffer for password validation. */ + TCHAR *password = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->passwordlen * sizeof(TCHAR)); + if (! password) { + HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("password confirmation"), _T("install()")); + return 6; + } + + /* Actual password buffer. */ + service->password = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->passwordlen * sizeof(TCHAR)); + if (! service->password) { + HeapFree(GetProcessHeap(), 0, password); + HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("password"), _T("install()")); + return 6; + } + + /* Get first password. */ + if (! GetDlgItemText(tablist[AMSM_TAB_LOGON], IDC_PASSWORD1, service->password, (int) service->passwordlen)) { + HeapFree(GetProcessHeap(), 0, password); + SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR)); + HeapFree(GetProcessHeap(), 0, service->password); + service->password = 0; + service->passwordlen = 0; + HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_PASSWORD); + return 6; + } + + /* Get confirmation. */ + if (! GetDlgItemText(tablist[AMSM_TAB_LOGON], IDC_PASSWORD2, password, (int) service->passwordlen)) { + SecureZeroMemory(password, service->passwordlen * sizeof(TCHAR)); + HeapFree(GetProcessHeap(), 0, password); + SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR)); + HeapFree(GetProcessHeap(), 0, service->password); + service->password = 0; + service->passwordlen = 0; + HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_PASSWORD); + return 6; + } + + /* Compare. */ + if (_tcsncmp(password, service->password, service->passwordlen)) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_MISSING_PASSWORD); + SecureZeroMemory(password, service->passwordlen * sizeof(TCHAR)); + HeapFree(GetProcessHeap(), 0, password); + SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR)); + HeapFree(GetProcessHeap(), 0, service->password); + service->password = 0; + service->passwordlen = 0; + HeapFree(GetProcessHeap(), 0, service->username); + service->username = 0; + service->usernamelen = 0; + return 6; + } + } + } + } + + /* Get dependencies. */ + unsigned long dependencieslen = (unsigned long) SendMessage(GetDlgItem(tablist[AMSM_TAB_DEPENDENCIES], IDC_DEPENDENCIES), WM_GETTEXTLENGTH, 0, 0); + if (dependencieslen) { + TCHAR *dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (dependencieslen + 2) * sizeof(TCHAR)); + if (! dependencies) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("dependencies"), _T("install()")); + cleanup_nssm_service(service); + return 6; + } + + if (! GetDlgItemText(tablist[AMSM_TAB_DEPENDENCIES], IDC_DEPENDENCIES, dependencies, dependencieslen + 1)) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_DEPENDENCIES); + HeapFree(GetProcessHeap(), 0, dependencies); + cleanup_nssm_service(service); + return 6; + } + + if (unformat_double_null(dependencies, dependencieslen, &service->dependencies, &service->dependencieslen)) { + HeapFree(GetProcessHeap(), 0, dependencies); + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("dependencies"), _T("install()")); + cleanup_nssm_service(service); + return 6; + } + + HeapFree(GetProcessHeap(), 0, dependencies); + } + + /* Remaining tabs are only for services we manage. */ + if (service->native) return 0; + + /* Get process stuff. */ + combo = GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_PRIORITY); + service->priority = priority_index_to_constant((unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0)); + + service->affinity = 0LL; + if (! (SendDlgItemMessage(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_GETCHECK, 0, 0) & BST_CHECKED)) { + HWND list = GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY); + int selected = (int) SendMessage(list, LB_GETSELCOUNT, 0, 0); + int count = (int) SendMessage(list, LB_GETCOUNT, 0, 0); + if (! selected) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_WARN_AFFINITY_NONE); + return 5; + } + else if (selected < count) { + for (int i = 0; i < count; i++) { + if (SendMessage(list, LB_GETSEL, i, 0)) service->affinity |= (1LL << (__int64) i); + } + } + } + + if (SendDlgItemMessage(tablist[AMSM_TAB_PROCESS], IDC_CONSOLE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->no_console = 0; + else service->no_console = 1; + + /* Get stop method stuff. */ + check_stop_method(service, AMSM_STOP_METHOD_CONSOLE, IDC_METHOD_CONSOLE); + check_stop_method(service, AMSM_STOP_METHOD_WINDOW, IDC_METHOD_WINDOW); + check_stop_method(service, AMSM_STOP_METHOD_THREADS, IDC_METHOD_THREADS); + check_stop_method(service, AMSM_STOP_METHOD_TERMINATE, IDC_METHOD_TERMINATE); + check_number(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, &service->kill_console_delay); + check_number(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, &service->kill_window_delay); + check_number(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_THREADS, &service->kill_threads_delay); + if (SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_PROCESS_TREE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->kill_process_tree = 1; + else service->kill_process_tree = 0; + + /* Get exit action stuff. */ + check_number(tablist[AMSM_TAB_EXIT], IDC_THROTTLE, &service->throttle_delay); + combo = GetDlgItem(tablist[AMSM_TAB_EXIT], IDC_APPEXIT); + service->default_exit_action = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0); + if (service->default_exit_action == CB_ERR) service->default_exit_action = 0; + check_number(tablist[AMSM_TAB_EXIT], IDC_RESTART_DELAY, &service->restart_delay); + + /* Get I/O stuff. */ + check_io(window, _T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN); + check_io(window, _T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT); + check_io(window, _T("stderr"), service->stderr_path, _countof(service->stderr_path), IDC_STDERR); + if (SendDlgItemMessage(tablist[AMSM_TAB_IO], IDC_TIMESTAMP, BM_GETCHECK, 0, 0) & BST_CHECKED) service->timestamp_log = true; + else service->timestamp_log = false; + + /* Override stdout and/or stderr. */ + if (SendDlgItemMessage(tablist[AMSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) { + if (service->stdout_path[0]) service->stdout_disposition = CREATE_ALWAYS; + if (service->stderr_path[0]) service->stderr_disposition = CREATE_ALWAYS; + } + + /* Get rotation stuff. */ + if (SendDlgItemMessage(tablist[AMSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) { + service->rotate_files = true; + if (SendDlgItemMessage(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->rotate_stdout_online = service->rotate_stderr_online = AMSM_ROTATE_ONLINE; + check_number(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds); + check_number(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low); + } + + /* Get hook stuff. */ + if (SendDlgItemMessage(tablist[AMSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_GETCHECK, 0, 0) & BST_CHECKED) service->hook_share_output_handles = true; + + /* Get environment. */ + unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[AMSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0); + if (envlen) { + TCHAR *env = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (envlen + 2) * sizeof(TCHAR)); + if (! env) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()")); + cleanup_nssm_service(service); + return 5; + } + + if (! GetDlgItemText(tablist[AMSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, env, envlen + 1)) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_ENVIRONMENT); + HeapFree(GetProcessHeap(), 0, env); + cleanup_nssm_service(service); + return 5; + } + + TCHAR *newenv; + unsigned long newlen; + if (unformat_double_null(env, envlen, &newenv, &newlen)) { + HeapFree(GetProcessHeap(), 0, env); + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()")); + cleanup_nssm_service(service); + return 5; + } + + HeapFree(GetProcessHeap(), 0, env); + env = newenv; + envlen = newlen; + + /* Test the environment is valid. */ + if (test_environment(env)) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INVALID_ENVIRONMENT); + HeapFree(GetProcessHeap(), 0, env); + cleanup_nssm_service(service); + return 5; + } + + if (SendDlgItemMessage(tablist[AMSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_GETCHECK, 0, 0) & BST_CHECKED) { + service->env = env; + service->envlen = envlen; + } + else { + service->env_extra = env; + service->env_extralen = envlen; + } + } + + return 0; +} + +/* Install the service. */ +int install(HWND window) { + if (! window) return 1; + + nssm_service_t *service = alloc_nssm_service(); + if (service) { + int ret = configure(window, service, 0); + if (ret) return ret; + } + + /* See if it works. */ + switch (install_service(service)) { + case 1: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("install()")); + cleanup_nssm_service(service); + return 1; + + case 2: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED); + cleanup_nssm_service(service); + return 2; + + case 3: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_MESSAGE_PATH_TOO_LONG, AMSM); + cleanup_nssm_service(service); + return 3; + + case 4: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH); + cleanup_nssm_service(service); + return 4; + + case 5: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_INSTALL_SERVICE_FAILED); + cleanup_nssm_service(service); + return 5; + + case 6: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_CREATE_PARAMETERS_FAILED); + cleanup_nssm_service(service); + return 6; + } + + update_hooks(service->name); + + popup_message(window, MB_OK, AMSM_MESSAGE_SERVICE_INSTALLED, service->name); + cleanup_nssm_service(service); + return 0; +} + +/* Remove the service */ +int remove(HWND window) { + if (! window) return 1; + + /* See if it works */ + nssm_service_t *service = alloc_nssm_service(); + if (service) { + /* Get service name */ + if (! GetDlgItemText(window, IDC_NAME, service->name, _countof(service->name))) { + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_MISSING_SERVICE_NAME); + cleanup_nssm_service(service); + return 2; + } + + /* Confirm */ + if (popup_message(window, MB_YESNO, AMSM_GUI_ASK_REMOVE_SERVICE, service->name) != IDYES) { + cleanup_nssm_service(service); + return 0; + } + } + + switch (remove_service(service)) { + case 1: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("remove()")); + cleanup_nssm_service(service); + return 1; + + case 2: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED); + cleanup_nssm_service(service); + return 2; + + case 3: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_SERVICE_NOT_INSTALLED); + cleanup_nssm_service(service); + return 3; + + case 4: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_REMOVE_SERVICE_FAILED); + cleanup_nssm_service(service); + return 4; + } + + popup_message(window, MB_OK, AMSM_MESSAGE_SERVICE_REMOVED, service->name); + cleanup_nssm_service(service); + return 0; +} + +int edit(HWND window, nssm_service_t *orig_service) { + if (! window) return 1; + + nssm_service_t *service = alloc_nssm_service(); + if (service) { + int ret = configure(window, service, orig_service); + if (ret) return ret; + } + + switch (edit_service(service, true)) { + case 1: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("edit()")); + cleanup_nssm_service(service); + return 1; + + case 3: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_MESSAGE_PATH_TOO_LONG, AMSM); + cleanup_nssm_service(service); + return 3; + + case 4: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH); + cleanup_nssm_service(service); + return 4; + + case 5: + case 6: + popup_message(window, MB_OK | MB_ICONEXCLAMATION, AMSM_GUI_EDIT_PARAMETERS_FAILED); + cleanup_nssm_service(service); + return 6; + } + + update_hooks(service->name); + + popup_message(window, MB_OK, AMSM_MESSAGE_SERVICE_EDITED, service->name); + cleanup_nssm_service(service); + return 0; +} + +static TCHAR *browse_filter(int message) { + switch (message) { + case AMSM_GUI_BROWSE_FILTER_APPLICATIONS: return _T("*.exe;*.bat;*.cmd"); + case AMSM_GUI_BROWSE_FILTER_DIRECTORIES: return _T("."); + case AMSM_GUI_BROWSE_FILTER_ALL_FILES: /* Fall through. */ + default: return _T("*.*"); + } +} + +UINT_PTR CALLBACK browse_hook(HWND dlg, UINT message, WPARAM w, LPARAM l) { + switch (message) { + case WM_INITDIALOG: + return 1; + } + + return 0; +} + +/* Browse for application */ +void browse(HWND window, TCHAR *current, unsigned long flags, ...) { + if (! window) return; + + va_list arg; + size_t bufsize = 256; + size_t len = bufsize; + int i; + + OPENFILENAME ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, bufsize * sizeof(TCHAR)); + /* XXX: Escaping nulls with FormatMessage is tricky */ + if (ofn.lpstrFilter) { + ZeroMemory((void *) ofn.lpstrFilter, bufsize); + len = 0; + /* "Applications" + NULL + "*.exe" + NULL */ + va_start(arg, flags); + while (i = va_arg(arg, int)) { + TCHAR *localised = message_string(i); + _sntprintf_s((TCHAR *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, localised); + len += _tcslen(localised) + 1; + LocalFree(localised); + TCHAR *filter = browse_filter(i); + _sntprintf_s((TCHAR *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, _T("%s"), filter); + len += _tcslen(filter) + 1; + } + va_end(arg); + /* Remainder of the buffer is already zeroed */ + } + ofn.lpstrFile = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, PATH_LENGTH * sizeof(TCHAR)); + if (ofn.lpstrFile) { + if (flags & OFN_NOVALIDATE) { + /* Directory hack. */ + _sntprintf_s(ofn.lpstrFile, PATH_LENGTH, _TRUNCATE, _T(":%s:"), message_string(AMSM_GUI_BROWSE_FILTER_DIRECTORIES)); + ofn.nMaxFile = DIR_LENGTH; + } + else { + _sntprintf_s(ofn.lpstrFile, PATH_LENGTH, _TRUNCATE, _T("%s"), current); + ofn.nMaxFile = PATH_LENGTH; + } + } + ofn.lpstrTitle = message_string(AMSM_GUI_BROWSE_TITLE); + ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | flags; + + if (GetOpenFileName(&ofn)) { + /* Directory hack. */ + if (flags & OFN_NOVALIDATE) strip_basename(ofn.lpstrFile); + SendMessage(window, WM_SETTEXT, 0, (LPARAM) ofn.lpstrFile); + } + if (ofn.lpstrFilter) HeapFree(GetProcessHeap(), 0, (void *) ofn.lpstrFilter); + if (ofn.lpstrFile) HeapFree(GetProcessHeap(), 0, ofn.lpstrFile); +} + +INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) { + switch (message) { + case WM_INITDIALOG: + return 1; + + /* Button was pressed or control was controlled. */ + case WM_COMMAND: + HWND dlg; + TCHAR buffer[PATH_LENGTH]; + unsigned char enabled; + + switch (LOWORD(w)) { + /* Browse for application. */ + case IDC_BROWSE: + dlg = GetDlgItem(tab, IDC_PATH); + GetDlgItemText(tab, IDC_PATH, buffer, _countof(buffer)); + browse(dlg, buffer, OFN_FILEMUSTEXIST, AMSM_GUI_BROWSE_FILTER_APPLICATIONS, AMSM_GUI_BROWSE_FILTER_ALL_FILES, 0); + /* Fill in startup directory if it wasn't already specified. */ + GetDlgItemText(tab, IDC_DIR, buffer, _countof(buffer)); + if (! buffer[0]) { + GetDlgItemText(tab, IDC_PATH, buffer, _countof(buffer)); + strip_basename(buffer); + SetDlgItemText(tab, IDC_DIR, buffer); + } + break; + + /* Browse for startup directory. */ + case IDC_BROWSE_DIR: + dlg = GetDlgItem(tab, IDC_DIR); + GetDlgItemText(tab, IDC_DIR, buffer, _countof(buffer)); + browse(dlg, buffer, OFN_NOVALIDATE, AMSM_GUI_BROWSE_FILTER_DIRECTORIES, 0); + break; + + /* Log on. */ + case IDC_LOCALSYSTEM: + set_logon_enabled(1, 0); + break; + + case IDC_VIRTUAL_SERVICE: + set_logon_enabled(0, 0); + break; + + case IDC_ACCOUNT: + set_logon_enabled(0, 1); + break; + + /* Affinity. */ + case IDC_AFFINITY_ALL: + if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 0; + else enabled = 1; + set_affinity_enabled(enabled); + break; + + /* Shutdown methods. */ + case IDC_METHOD_CONSOLE: + set_timeout_enabled(LOWORD(w), IDC_KILL_CONSOLE); + break; + + case IDC_METHOD_WINDOW: + set_timeout_enabled(LOWORD(w), IDC_KILL_WINDOW); + break; + + case IDC_METHOD_THREADS: + set_timeout_enabled(LOWORD(w), IDC_KILL_THREADS); + break; + + /* Browse for stdin. */ + case IDC_BROWSE_STDIN: + dlg = GetDlgItem(tab, IDC_STDIN); + GetDlgItemText(tab, IDC_STDIN, buffer, _countof(buffer)); + browse(dlg, buffer, 0, AMSM_GUI_BROWSE_FILTER_ALL_FILES, 0); + break; + + /* Browse for stdout. */ + case IDC_BROWSE_STDOUT: + dlg = GetDlgItem(tab, IDC_STDOUT); + GetDlgItemText(tab, IDC_STDOUT, buffer, _countof(buffer)); + browse(dlg, buffer, 0, AMSM_GUI_BROWSE_FILTER_ALL_FILES, 0); + /* Fill in stderr if it wasn't already specified. */ + GetDlgItemText(tab, IDC_STDERR, buffer, _countof(buffer)); + if (! buffer[0]) { + GetDlgItemText(tab, IDC_STDOUT, buffer, _countof(buffer)); + SetDlgItemText(tab, IDC_STDERR, buffer); + } + break; + + /* Browse for stderr. */ + case IDC_BROWSE_STDERR: + dlg = GetDlgItem(tab, IDC_STDERR); + GetDlgItemText(tab, IDC_STDERR, buffer, _countof(buffer)); + browse(dlg, buffer, 0, AMSM_GUI_BROWSE_FILTER_ALL_FILES, 0); + break; + + /* Rotation. */ + case IDC_ROTATE: + if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 1; + else enabled = 0; + set_rotation_enabled(enabled); + break; + + /* Hook event. */ + case IDC_HOOK_EVENT: + if (HIWORD(w) == CBN_SELCHANGE) set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), 0, false); + break; + + /* Hook action. */ + case IDC_HOOK_ACTION: + if (HIWORD(w) == CBN_SELCHANGE) set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), (int) SendMessage(GetDlgItem(tab, IDC_HOOK_ACTION), CB_GETCURSEL, 0, 0), false); + break; + + /* Browse for hook. */ + case IDC_BROWSE_HOOK: + dlg = GetDlgItem(tab, IDC_HOOK); + GetDlgItemText(tab, IDC_HOOK, buffer, _countof(buffer)); + browse(dlg, _T(""), OFN_FILEMUSTEXIST, AMSM_GUI_BROWSE_FILTER_ALL_FILES, 0); + break; + + /* Hook. */ + case IDC_HOOK: + set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), (int) SendMessage(GetDlgItem(tab, IDC_HOOK_ACTION), CB_GETCURSEL, 0, 0), true); + break; + } + return 1; + } + + return 0; +} + +/* Install/remove dialogue callback */ +INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) { + nssm_service_t *service; + + switch (message) { + /* Creating the dialogue */ + case WM_INITDIALOG: + service = (nssm_service_t *) l; + + SetFocus(GetDlgItem(window, IDC_NAME)); + + HWND tabs; + HWND combo; + HWND list; + int i, n; + tabs = GetDlgItem(window, IDC_TAB1); + if (! tabs) return 0; + + /* Set up tabs. */ + TCITEM tab; + ZeroMemory(&tab, sizeof(tab)); + tab.mask = TCIF_TEXT; + + selected_tab = 0; + + /* Application tab. */ + if (service->native) tab.pszText = message_string(AMSM_GUI_TAB_NATIVE); + else tab.pszText = message_string(AMSM_GUI_TAB_APPLICATION); + tab.cchTextMax = (int) _tcslen(tab.pszText); + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_APPLICATION, (LPARAM) &tab); + if (service->native) { + tablist[AMSM_TAB_APPLICATION] = dialog(MAKEINTRESOURCE(IDD_NATIVE), window, tab_dlg); + EnableWindow(tablist[AMSM_TAB_APPLICATION], 0); + EnableWindow(GetDlgItem(tablist[AMSM_TAB_APPLICATION], IDC_PATH), 0); + } + else tablist[AMSM_TAB_APPLICATION] = dialog(MAKEINTRESOURCE(IDD_APPLICATION), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_APPLICATION], SW_SHOW); + + /* Details tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_DETAILS); + tab.cchTextMax = (int) _tcslen(tab.pszText); + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_DETAILS, (LPARAM) &tab); + tablist[AMSM_TAB_DETAILS] = dialog(MAKEINTRESOURCE(IDD_DETAILS), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_DETAILS], SW_HIDE); + + /* Set defaults. */ + combo = GetDlgItem(tablist[AMSM_TAB_DETAILS], IDC_STARTUP); + SendMessage(combo, CB_INSERTSTRING, AMSM_STARTUP_AUTOMATIC, (LPARAM) message_string(AMSM_GUI_STARTUP_AUTOMATIC)); + SendMessage(combo, CB_INSERTSTRING, AMSM_STARTUP_DELAYED, (LPARAM) message_string(AMSM_GUI_STARTUP_DELAYED)); + SendMessage(combo, CB_INSERTSTRING, AMSM_STARTUP_MANUAL, (LPARAM) message_string(AMSM_GUI_STARTUP_MANUAL)); + SendMessage(combo, CB_INSERTSTRING, AMSM_STARTUP_DISABLED, (LPARAM) message_string(AMSM_GUI_STARTUP_DISABLED)); + SendMessage(combo, CB_SETCURSEL, AMSM_STARTUP_AUTOMATIC, 0); + + /* Logon tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_LOGON); + tab.cchTextMax = (int) _tcslen(tab.pszText); + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_LOGON, (LPARAM) &tab); + tablist[AMSM_TAB_LOGON] = dialog(MAKEINTRESOURCE(IDD_LOGON), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_LOGON], SW_HIDE); + + /* Set defaults. */ + CheckRadioButton(tablist[AMSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_LOCALSYSTEM); + set_logon_enabled(1, 0); + + /* Dependencies tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_DEPENDENCIES); + tab.cchTextMax = (int) _tcslen(tab.pszText); + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_DEPENDENCIES, (LPARAM) &tab); + tablist[AMSM_TAB_DEPENDENCIES] = dialog(MAKEINTRESOURCE(IDD_DEPENDENCIES), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_DEPENDENCIES], SW_HIDE); + + /* Remaining tabs are only for services we manage. */ + if (service->native) return 1; + + /* Process tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_PROCESS); + tab.cchTextMax = (int) _tcslen(tab.pszText); + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_PROCESS, (LPARAM) &tab); + tablist[AMSM_TAB_PROCESS] = dialog(MAKEINTRESOURCE(IDD_PROCESS), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_PROCESS], SW_HIDE); + + /* Set defaults. */ + combo = GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_PRIORITY); + SendMessage(combo, CB_INSERTSTRING, AMSM_REALTIME_PRIORITY, (LPARAM) message_string(AMSM_GUI_REALTIME_PRIORITY_CLASS)); + SendMessage(combo, CB_INSERTSTRING, AMSM_HIGH_PRIORITY, (LPARAM) message_string(AMSM_GUI_HIGH_PRIORITY_CLASS)); + SendMessage(combo, CB_INSERTSTRING, AMSM_ABOVE_NORMAL_PRIORITY, (LPARAM) message_string(AMSM_GUI_ABOVE_NORMAL_PRIORITY_CLASS)); + SendMessage(combo, CB_INSERTSTRING, AMSM_NORMAL_PRIORITY, (LPARAM) message_string(AMSM_GUI_NORMAL_PRIORITY_CLASS)); + SendMessage(combo, CB_INSERTSTRING, AMSM_BELOW_NORMAL_PRIORITY, (LPARAM) message_string(AMSM_GUI_BELOW_NORMAL_PRIORITY_CLASS)); + SendMessage(combo, CB_INSERTSTRING, AMSM_IDLE_PRIORITY, (LPARAM) message_string(AMSM_GUI_IDLE_PRIORITY_CLASS)); + SendMessage(combo, CB_SETCURSEL, AMSM_NORMAL_PRIORITY, 0); + + SendDlgItemMessage(tablist[AMSM_TAB_PROCESS], IDC_CONSOLE, BM_SETCHECK, BST_CHECKED, 0); + + list = GetDlgItem(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY); + n = num_cpus(); + SendMessage(list, LB_SETCOLUMNWIDTH, 16, 0); + for (i = 0; i < n; i++) { + TCHAR buffer[3]; + _sntprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("%d"), i); + SendMessage(list, LB_ADDSTRING, 0, (LPARAM) buffer); + } + + /* + Size to fit. + The box is high enough for four rows. It is wide enough for eight + columns without scrolling. With scrollbars it shrinks to two rows. + Note that the above only holds if we set the column width BEFORE + adding the strings. + */ + if (n < 32) { + int columns = (n - 1) / 4; + RECT rect; + GetWindowRect(list, &rect); + int width = rect.right - rect.left; + width -= (7 - columns) * 16; + int height = rect.bottom - rect.top; + if (n < 4) height -= (int) SendMessage(list, LB_GETITEMHEIGHT, 0, 0) * (4 - n); + SetWindowPos(list, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + } + SendMessage(list, LB_SELITEMRANGE, 1, MAKELPARAM(0, n)); + + SendDlgItemMessage(tablist[AMSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_CHECKED, 0); + set_affinity_enabled(0); + + /* Shutdown tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_SHUTDOWN); + tab.cchTextMax = (int) _tcslen(tab.pszText); + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_SHUTDOWN, (LPARAM) &tab); + tablist[AMSM_TAB_SHUTDOWN] = dialog(MAKEINTRESOURCE(IDD_SHUTDOWN), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_SHUTDOWN], SW_HIDE); + + /* Set defaults. */ + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_CHECKED, 0); + SetDlgItemInt(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, AMSM_KILL_CONSOLE_GRACE_PERIOD, 0); + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_WINDOW, BM_SETCHECK, BST_CHECKED, 0); + SetDlgItemInt(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, AMSM_KILL_WINDOW_GRACE_PERIOD, 0); + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_THREADS, BM_SETCHECK, BST_CHECKED, 0); + SetDlgItemInt(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_THREADS, AMSM_KILL_THREADS_GRACE_PERIOD, 0); + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_METHOD_TERMINATE, BM_SETCHECK, BST_CHECKED, 0); + SendDlgItemMessage(tablist[AMSM_TAB_SHUTDOWN], IDC_KILL_PROCESS_TREE, BM_SETCHECK, BST_CHECKED, 1); + + /* Restart tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_EXIT); + tab.cchTextMax = (int) _tcslen(tab.pszText); + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_EXIT, (LPARAM) &tab); + tablist[AMSM_TAB_EXIT] = dialog(MAKEINTRESOURCE(IDD_APPEXIT), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_EXIT], SW_HIDE); + + /* Set defaults. */ + SetDlgItemInt(tablist[AMSM_TAB_EXIT], IDC_THROTTLE, AMSM_RESET_THROTTLE_RESTART, 0); + combo = GetDlgItem(tablist[AMSM_TAB_EXIT], IDC_APPEXIT); + SendMessage(combo, CB_INSERTSTRING, AMSM_EXIT_RESTART, (LPARAM) message_string(AMSM_GUI_EXIT_RESTART)); + SendMessage(combo, CB_INSERTSTRING, AMSM_EXIT_IGNORE, (LPARAM) message_string(AMSM_GUI_EXIT_IGNORE)); + SendMessage(combo, CB_INSERTSTRING, AMSM_EXIT_REALLY, (LPARAM) message_string(AMSM_GUI_EXIT_REALLY)); + SendMessage(combo, CB_INSERTSTRING, AMSM_EXIT_UNCLEAN, (LPARAM) message_string(AMSM_GUI_EXIT_UNCLEAN)); + SendMessage(combo, CB_SETCURSEL, AMSM_EXIT_RESTART, 0); + SetDlgItemInt(tablist[AMSM_TAB_EXIT], IDC_RESTART_DELAY, 0, 0); + + /* I/O tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_IO); + tab.cchTextMax = (int) _tcslen(tab.pszText) + 1; + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_IO, (LPARAM) &tab); + tablist[AMSM_TAB_IO] = dialog(MAKEINTRESOURCE(IDD_IO), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_IO], SW_HIDE); + + /* Set defaults. */ + SendDlgItemMessage(tablist[AMSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_UNCHECKED, 0); + + /* Rotation tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_ROTATION); + tab.cchTextMax = (int) _tcslen(tab.pszText) + 1; + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_ROTATION, (LPARAM) &tab); + tablist[AMSM_TAB_ROTATION] = dialog(MAKEINTRESOURCE(IDD_ROTATION), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_ROTATION], SW_HIDE); + + /* Set defaults. */ + SendDlgItemMessage(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_UNCHECKED, 0); + SetDlgItemInt(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_SECONDS, 0, 0); + SetDlgItemInt(tablist[AMSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, 0, 0); + set_rotation_enabled(0); + + /* Environment tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_ENVIRONMENT); + tab.cchTextMax = (int) _tcslen(tab.pszText) + 1; + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_ENVIRONMENT, (LPARAM) &tab); + tablist[AMSM_TAB_ENVIRONMENT] = dialog(MAKEINTRESOURCE(IDD_ENVIRONMENT), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_ENVIRONMENT], SW_HIDE); + + /* Hooks tab. */ + tab.pszText = message_string(AMSM_GUI_TAB_HOOKS); + tab.cchTextMax = (int) _tcslen(tab.pszText) + 1; + SendMessage(tabs, TCM_INSERTITEM, AMSM_TAB_HOOKS, (LPARAM) &tab); + tablist[AMSM_TAB_HOOKS] = dialog(MAKEINTRESOURCE(IDD_HOOKS), window, tab_dlg); + ShowWindow(tablist[AMSM_TAB_HOOKS], SW_HIDE); + + /* Set defaults. */ + combo = GetDlgItem(tablist[AMSM_TAB_HOOKS], IDC_HOOK_EVENT); + SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(AMSM_GUI_HOOK_EVENT_START)); + SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(AMSM_GUI_HOOK_EVENT_STOP)); + SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(AMSM_GUI_HOOK_EVENT_EXIT)); + SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(AMSM_GUI_HOOK_EVENT_POWER)); + SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(AMSM_GUI_HOOK_EVENT_ROTATE)); + SendDlgItemMessage(tablist[AMSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_UNCHECKED, 0); + if (_tcslen(service->name)) { + TCHAR hook_name[HOOK_NAME_LENGTH]; + TCHAR cmd[CMD_LENGTH]; + for (i = 0; hook_event_strings[i]; i++) { + const TCHAR *hook_event = hook_event_strings[i]; + int j; + for (j = 0; hook_action_strings[j]; j++) { + const TCHAR *hook_action = hook_action_strings[j]; + if (! valid_hook_name(hook_event, hook_action, true)) continue; + if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) continue; + if (hook_env(hook_event, hook_action, hook_name, _countof(hook_name)) < 0) continue; + SetEnvironmentVariable(hook_name, cmd); + } + } + } + set_hook_tab(0, 0, false); + + return 1; + + /* Tab change. */ + case WM_NOTIFY: + NMHDR *notification; + + notification = (NMHDR *) l; + switch (notification->code) { + case TCN_SELCHANGE: + HWND tabs; + int selection; + + tabs = GetDlgItem(window, IDC_TAB1); + if (! tabs) return 0; + + selection = (int) SendMessage(tabs, TCM_GETCURSEL, 0, 0); + if (selection != selected_tab) { + ShowWindow(tablist[selected_tab], SW_HIDE); + ShowWindow(tablist[selection], SW_SHOWDEFAULT); + SetFocus(GetDlgItem(window, IDOK)); + selected_tab = selection; + } + return 1; + } + + return 0; + + /* Button was pressed or control was controlled */ + case WM_COMMAND: + switch (LOWORD(w)) { + /* OK button */ + case IDOK: + if ((int) GetWindowLongPtr(window, GWLP_USERDATA) == IDD_EDIT) { + if (! edit(window, (nssm_service_t *) GetWindowLongPtr(window, DWLP_USER))) PostQuitMessage(0); + } + else if (! install(window)) PostQuitMessage(0); + break; + + /* Cancel button */ + case IDCANCEL: + DestroyWindow(window); + break; + + /* Remove button */ + case IDC_REMOVE: + if (! remove(window)) PostQuitMessage(0); + break; + } + return 1; + + /* Window closing */ + case WM_CLOSE: + DestroyWindow(window); + return 0; + case WM_DESTROY: + PostQuitMessage(0); + } + return 0; +} diff --git a/gui.h b/gui.h new file mode 100644 index 0000000..7c13c25 --- /dev/null +++ b/gui.h @@ -0,0 +1,18 @@ +#ifndef GUI_H +#define GUI_H + +#include +#include +#include +#include "resource.h" + +int nssm_gui(int, nssm_service_t *); +void centre_window(HWND); +int configure(HWND, nssm_service_t *, nssm_service_t *); +int install(HWND); +int remove(HWND); +int edit(HWND, nssm_service_t *); +void browse(HWND); +INT_PTR CALLBACK nssm_dlg(HWND, UINT, WPARAM, LPARAM); + +#endif diff --git a/hook.cpp b/hook.cpp new file mode 100644 index 0000000..1d67df8 --- /dev/null +++ b/hook.cpp @@ -0,0 +1,410 @@ +#include "amsm.h" + +typedef struct { + TCHAR *name; + HANDLE process_handle; + unsigned long pid; + unsigned long deadline; + FILETIME creation_time; + kill_t k; +} hook_t; + +const TCHAR *hook_event_strings[] = { AMSM_HOOK_EVENT_START, AMSM_HOOK_EVENT_STOP, AMSM_HOOK_EVENT_EXIT, AMSM_HOOK_EVENT_POWER, AMSM_HOOK_EVENT_ROTATE, NULL }; +const TCHAR *hook_action_strings[] = { AMSM_HOOK_ACTION_PRE, AMSM_HOOK_ACTION_POST, AMSM_HOOK_ACTION_CHANGE, AMSM_HOOK_ACTION_RESUME, NULL }; + +static unsigned long WINAPI await_hook(void *arg) { + hook_t *hook = (hook_t *) arg; + if (! hook) return AMSM_HOOK_STATUS_ERROR; + + int ret = 0; + if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = AMSM_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 = AMSM_KILL_CONSOLE_GRACE_PERIOD; + hook->k.kill_window_delay = AMSM_KILL_WINDOW_GRACE_PERIOD; + hook->k.kill_threads_delay = AMSM_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 == AMSM_HOOK_STATUS_ABORT) return AMSM_HOOK_STATUS_ABORT; + if (exitcode) return AMSM_HOOK_STATUS_FAILED; + + return AMSM_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, AMSM_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, AMSM_HOOK_EVENT_EXIT)) { + if (str_equiv(hook_action, AMSM_HOOK_ACTION_POST)) return true; + if (quiet) return false; + print_message(stderr, AMSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_POST); + return false; + } + + /* Power/{Change,Resume} */ + if (str_equiv(hook_event, AMSM_HOOK_EVENT_POWER)) { + if (str_equiv(hook_action, AMSM_HOOK_ACTION_CHANGE)) return true; + if (str_equiv(hook_action, AMSM_HOOK_ACTION_RESUME)) return true; + if (quiet) return false; + print_message(stderr, AMSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_CHANGE); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_RESUME); + return false; + } + + /* Rotate/{Pre,Post} */ + if (str_equiv(hook_event, AMSM_HOOK_EVENT_ROTATE)) { + if (str_equiv(hook_action, AMSM_HOOK_ACTION_PRE)) return true; + if (str_equiv(hook_action, AMSM_HOOK_ACTION_POST)) return true; + if (quiet) return false; + print_message(stderr, AMSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_PRE); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_POST); + return false; + } + + /* Start/{Pre,Post} */ + if (str_equiv(hook_event, AMSM_HOOK_EVENT_START)) { + if (str_equiv(hook_action, AMSM_HOOK_ACTION_PRE)) return true; + if (str_equiv(hook_action, AMSM_HOOK_ACTION_POST)) return true; + if (quiet) return false; + print_message(stderr, AMSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_PRE); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_POST); + return false; + } + + /* Stop/Pre */ + if (str_equiv(hook_event, AMSM_HOOK_EVENT_STOP)) { + if (str_equiv(hook_action, AMSM_HOOK_ACTION_PRE)) return true; + if (quiet) return false; + print_message(stderr, AMSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_ACTION_PRE); + return false; + } + + if (quiet) return false; + print_message(stderr, AMSM_MESSAGE_INVALID_HOOK_EVENT); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_EVENT_EXIT); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_EVENT_POWER); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_EVENT_ROTATE); + _ftprintf(stderr, _T("%s\n"), AMSM_HOOK_EVENT_START); + _ftprintf(stderr, _T("%s\n"), AMSM_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, AMSM_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, AMSM_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: + AMSM_HOOK_STATUS_SUCCESS if the hook ran successfully. + AMSM_HOOK_STATUS_NOTFOUND if no hook was found. + AMSM_HOOK_STATUS_ABORT if the hook failed and we should cancel service start. + AMSM_HOOK_STATUS_ERROR on error. + AMSM_HOOK_STATUS_NOTRUN if the hook didn't run. + AMSM_HOOK_STATUS_TIMEOUT if the hook timed out. + AMSM_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, AMSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0); + return AMSM_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"), AMSM_HOOK_VERSION); + SetEnvironmentVariable(AMSM_HOOK_ENV_VERSION, number); + + /* Event triggering this action. */ + SetEnvironmentVariable(AMSM_HOOK_ENV_EVENT, hook_event); + + /* Hook action. */ + SetEnvironmentVariable(AMSM_HOOK_ENV_ACTION, hook_action); + + /* Control triggering this action. May be empty. */ + if (hook_control) SetEnvironmentVariable(AMSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control)); + else SetEnvironmentVariable(AMSM_HOOK_ENV_TRIGGER, _T("")); + + /* Last control handled. */ + SetEnvironmentVariable(AMSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control)); + + /* Path to AMSM, unquoted for the environment. */ + SetEnvironmentVariable(AMSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath()); + + /* AMSM version. */ + SetEnvironmentVariable(AMSM_HOOK_ENV_AMSM_CONFIGURATION, AMSM_CONFIGURATION); + SetEnvironmentVariable(AMSM_HOOK_ENV_AMSM_VERSION, AMSM_VERSION); + SetEnvironmentVariable(AMSM_HOOK_ENV_BUILD_DATE, AMSM_DATE); + + /* AMSM PID. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId()); + SetEnvironmentVariable(AMSM_HOOK_ENV_PID, number); + + /* AMSM runtime. */ + set_hook_runtime(AMSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now); + + /* Application PID. */ + if (service->pid) { + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid); + SetEnvironmentVariable(AMSM_HOOK_ENV_APPLICATION_PID, number); + /* Application runtime. */ + set_hook_runtime(AMSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now); + /* Exit code. */ + SetEnvironmentVariable(AMSM_HOOK_ENV_EXITCODE, _T("")); + } + else { + SetEnvironmentVariable(AMSM_HOOK_ENV_APPLICATION_PID, _T("")); + if (str_equiv(hook_event, AMSM_HOOK_EVENT_START) && str_equiv(hook_action, AMSM_HOOK_ACTION_PRE)) { + SetEnvironmentVariable(AMSM_HOOK_ENV_APPLICATION_RUNTIME, _T("")); + SetEnvironmentVariable(AMSM_HOOK_ENV_EXITCODE, _T("")); + } + else { + set_hook_runtime(AMSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time); + /* Exit code. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode); + SetEnvironmentVariable(AMSM_HOOK_ENV_EXITCODE, number); + } + } + + /* Deadline for this script. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline); + SetEnvironmentVariable(AMSM_HOOK_ENV_DEADLINE, number); + + /* Service name. */ + SetEnvironmentVariable(AMSM_HOOK_ENV_SERVICE_NAME, service->name); + SetEnvironmentVariable(AMSM_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(AMSM_HOOK_ENV_START_REQUESTED_COUNT, number); + + /* Times the service actually did start. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count); + SetEnvironmentVariable(AMSM_HOOK_ENV_START_COUNT, number); + + /* Times the service exited. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count); + SetEnvironmentVariable(AMSM_HOOK_ENV_EXIT_COUNT, number); + + /* Throttled count. */ + _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle); + SetEnvironmentVariable(AMSM_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(AMSM_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, AMSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0); + unset_service_environment(service); + LeaveCriticalSection(&service->hook_section); + HeapFree(GetProcessHeap(), 0, hook); + return AMSM_HOOK_STATUS_ERROR; + } + + /* No hook. */ + if (! _tcslen(cmd)) { + unset_service_environment(service); + LeaveCriticalSection(&service->hook_section); + HeapFree(GetProcessHeap(), 0, hook); + return AMSM_HOOK_STATUS_NOTFOUND; + } + + /* Run the command. */ + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + if (service->hook_share_output_handles) (void) use_output_handles(service, &si); + bool inherit_handles = false; + if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true; + unsigned long flags = 0; +#ifdef UNICODE + flags |= CREATE_UNICODE_ENVIRONMENT; +#endif + ret = AMSM_HOOK_STATUS_NOTRUN; + if (CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) { + close_output_handles(&si); + 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 + AMSM_SERVICE_STATUS_DEADLINE); + unsigned long exitcode; + GetExitCodeThread(thread_handle, &exitcode); + ret = (int) exitcode; + CloseHandle(thread_handle); + } + } + else { + log_event(EVENTLOG_ERROR_TYPE, AMSM_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, AMSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0); + HeapFree(GetProcessHeap(), 0, hook); + close_output_handles(&si); + } + + /* 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, AMSM_HOOK_DEADLINE); +} diff --git a/hook.h b/hook.h new file mode 100644 index 0000000..cffb1c3 --- /dev/null +++ b/hook.h @@ -0,0 +1,75 @@ +#ifndef HOOK_H +#define HOOK_H + +#define AMSM_HOOK_EVENT_START _T("Start") +#define AMSM_HOOK_EVENT_STOP _T("Stop") +#define AMSM_HOOK_EVENT_EXIT _T("Exit") +#define AMSM_HOOK_EVENT_POWER _T("Power") +#define AMSM_HOOK_EVENT_ROTATE _T("Rotate") + +#define AMSM_HOOK_ACTION_PRE _T("Pre") +#define AMSM_HOOK_ACTION_POST _T("Post") +#define AMSM_HOOK_ACTION_CHANGE _T("Change") +#define AMSM_HOOK_ACTION_RESUME _T("Resume") + +/* Hook name will be " (/)" */ +#define HOOK_NAME_LENGTH SERVICE_NAME_LENGTH * 2 + +#define AMSM_HOOK_VERSION 1 + +/* Hook ran successfully. */ +#define AMSM_HOOK_STATUS_SUCCESS 0 +/* No hook configured. */ +#define AMSM_HOOK_STATUS_NOTFOUND 1 +/* Hook requested abort. */ +#define AMSM_HOOK_STATUS_ABORT 99 +/* Internal error launching hook. */ +#define AMSM_HOOK_STATUS_ERROR 100 +/* Hook was not run. */ +#define AMSM_HOOK_STATUS_NOTRUN 101 +/* Hook timed out. */ +#define AMSM_HOOK_STATUS_TIMEOUT 102 +/* Hook returned non-zero. */ +#define AMSM_HOOK_STATUS_FAILED 111 + +/* Version 1. */ +#define AMSM_HOOK_ENV_VERSION _T("AMSM_HOOK_VERSION") +#define AMSM_HOOK_ENV_IMAGE_PATH _T("AMSM_EXE") +#define AMSM_HOOK_ENV_AMSM_CONFIGURATION _T("AMSM_CONFIGURATION") +#define AMSM_HOOK_ENV_AMSM_VERSION _T("AMSM_VERSION") +#define AMSM_HOOK_ENV_BUILD_DATE _T("AMSM_BUILD_DATE") +#define AMSM_HOOK_ENV_PID _T("AMSM_PID") +#define AMSM_HOOK_ENV_DEADLINE _T("AMSM_DEADLINE") +#define AMSM_HOOK_ENV_SERVICE_NAME _T("AMSM_SERVICE_NAME") +#define AMSM_HOOK_ENV_SERVICE_DISPLAYNAME _T("AMSM_SERVICE_DISPLAYNAME") +#define AMSM_HOOK_ENV_COMMAND_LINE _T("AMSM_COMMAND_LINE") +#define AMSM_HOOK_ENV_APPLICATION_PID _T("AMSM_APPLICATION_PID") +#define AMSM_HOOK_ENV_EVENT _T("AMSM_EVENT") +#define AMSM_HOOK_ENV_ACTION _T("AMSM_ACTION") +#define AMSM_HOOK_ENV_TRIGGER _T("AMSM_TRIGGER") +#define AMSM_HOOK_ENV_LAST_CONTROL _T("AMSM_LAST_CONTROL") +#define AMSM_HOOK_ENV_START_REQUESTED_COUNT _T("AMSM_START_REQUESTED_COUNT") +#define AMSM_HOOK_ENV_START_COUNT _T("AMSM_START_COUNT") +#define AMSM_HOOK_ENV_THROTTLE_COUNT _T("AMSM_THROTTLE_COUNT") +#define AMSM_HOOK_ENV_EXIT_COUNT _T("AMSM_EXIT_COUNT") +#define AMSM_HOOK_ENV_EXITCODE _T("AMSM_EXITCODE") +#define AMSM_HOOK_ENV_RUNTIME _T("AMSM_RUNTIME") +#define AMSM_HOOK_ENV_APPLICATION_RUNTIME _T("AMSM_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 new file mode 100644 index 0000000..31e701e --- /dev/null +++ b/imports.cpp @@ -0,0 +1,91 @@ +#include "amsm.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(); + if (*error != ERROR_PROC_NOT_FOUND) log_event(EVENTLOG_WARNING_TYPE, AMSM_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(); + if (*error != ERROR_PROC_NOT_FOUND) { + TCHAR *function_name; + if (! from_utf8(function, &function_name, 0)) { + log_event(EVENTLOG_WARNING_TYPE, AMSM_EVENT_GETPROCADDRESS_FAILED, function_name, error_string(*error), 0); + HeapFree(GetProcessHeap(), 0, function_name); + } + } + } + + 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.QueryFullProcessImageName = (QueryFullProcessImageName_ptr) get_import(imports.kernel32, QUERYFULLPROCESSIMAGENAME_EXPORT, &error); + if (! imports.QueryFullProcessImageName) { + if (error != ERROR_PROC_NOT_FOUND) return 3; + } + + imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error); + if (! imports.SleepConditionVariableCS) { + if (error != ERROR_PROC_NOT_FOUND) return 4; + } + + imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error); + if (! imports.WakeConditionVariable) { + if (error != ERROR_PROC_NOT_FOUND) return 5; + } + } + 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 7; + } + imports.IsWellKnownSid = (IsWellKnownSid_ptr) get_import(imports.advapi32, "IsWellKnownSid", &error); + if (! imports.IsWellKnownSid) { + if (error != ERROR_PROC_NOT_FOUND) return 8; + } + } + else if (error != ERROR_MOD_NOT_FOUND) return 6; + + 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 new file mode 100644 index 0000000..7a8d5e0 --- /dev/null +++ b/imports.h @@ -0,0 +1,34 @@ +#ifndef IMPORTS_H +#define IMPORTS_H + +/* Some functions don't have decorated versions. */ +#ifdef UNICODE +#define QUERYFULLPROCESSIMAGENAME_EXPORT "QueryFullProcessImageNameW" +#else +#define QUERYFULLPROCESSIMAGENAME_EXPORT "QueryFullProcessImageNameA" +#endif + +typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD); +typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD); +typedef BOOL (WINAPI *QueryFullProcessImageName_ptr)(HANDLE, unsigned long, LPTSTR, unsigned long *); +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; + QueryFullProcessImageName_ptr QueryFullProcessImageName; + 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/io.cpp b/io.cpp new file mode 100644 index 0000000..b30c134 --- /dev/null +++ b/io.cpp @@ -0,0 +1,741 @@ +#include "amsm.h" + +#define COMPLAINED_READ (1 << 0) +#define COMPLAINED_WRITE (1 << 1) +#define COMPLAINED_ROTATE (1 << 2) +#define TIMESTAMP_FORMAT "%04u-%02u-%02u %02u:%02u:%02u.%03u: " +#define TIMESTAMP_LEN 25 + +static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *source_description, TCHAR *dest_description, unsigned long flags) { + if (! dest_handle_ptr) return 1; + + if (! DuplicateHandle(GetCurrentProcess(), source_handle, GetCurrentProcess(), dest_handle_ptr, 0, true, flags)) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_DUPLICATEHANDLE_FAILED, source_description, dest_description, error_string(GetLastError()), 0); + return 2; + } + return 0; +} + +static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *source_description, TCHAR *dest_description) { + return dup_handle(source_handle, dest_handle_ptr, source_description, dest_description, DUPLICATE_SAME_ACCESS); +} + +/* + read_handle: read from application + pipe_handle: stdout of application + write_handle: to file +*/ +static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long rotate_delay, unsigned long *tid_ptr, unsigned long *rotate_online, bool timestamp_log, bool copy_and_truncate) { + *tid_ptr = 0; + + /* Pipe between application's stdout/stderr and our logging handle. */ + if (read_handle_ptr && ! *read_handle_ptr) { + if (pipe_handle_ptr && ! *pipe_handle_ptr) { + if (CreatePipe(read_handle_ptr, pipe_handle_ptr, 0, 0)) { + SetHandleInformation(*pipe_handle_ptr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + } + else { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_CREATEPIPE_FAILED, service_name, path, error_string(GetLastError())); + return (HANDLE) 0; + } + } + } + + logger_t *logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t)); + if (! logger) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, _T("logger"), _T("create_logging_thread()"), 0); + return (HANDLE) 0; + } + + ULARGE_INTEGER size; + size.LowPart = rotate_bytes_low; + size.HighPart = rotate_bytes_high; + + logger->service_name = service_name; + logger->path = path; + logger->sharing = sharing; + logger->disposition = disposition; + logger->flags = flags; + logger->read_handle = *read_handle_ptr; + logger->write_handle = *write_handle_ptr; + logger->size = (__int64) size.QuadPart; + logger->tid_ptr = tid_ptr; + logger->timestamp_log = timestamp_log; + logger->line_length = 0; + logger->rotate_online = rotate_online; + logger->rotate_delay = rotate_delay; + logger->copy_and_truncate = copy_and_truncate; + + HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr); + if (! thread_handle) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0); + HeapFree(GetProcessHeap(), 0, logger); + } + + return thread_handle; +} + +static inline unsigned long guess_charsize(void *address, unsigned long bufsize) { + if (IsTextUnicode(address, bufsize, 0)) return (unsigned long) sizeof(wchar_t); + else return (unsigned long) sizeof(char); +} + +static inline void write_bom(logger_t *logger, unsigned long *out) { + wchar_t bom = L'\ufeff'; + if (! WriteFile(logger->write_handle, (void *) &bom, sizeof(bom), out, 0)) { + log_event(EVENTLOG_WARNING_TYPE, AMSM_EVENT_SOMEBODY_SET_UP_US_THE_BOM, logger->service_name, logger->path, error_string(GetLastError()), 0); + } +} + +void close_handle(HANDLE *handle, HANDLE *remember) { + if (remember) *remember = INVALID_HANDLE_VALUE; + if (! handle) return; + if (! *handle) return; + CloseHandle(*handle); + if (remember) *remember = *handle; + *handle = 0; +} + +void close_handle(HANDLE *handle) { + close_handle(handle, NULL); +} + +/* Get path, share mode, creation disposition and flags for a stream. */ +int get_createfile_parameters(HKEY key, TCHAR *prefix, TCHAR *path, unsigned long *sharing, unsigned long default_sharing, unsigned long *disposition, unsigned long default_disposition, unsigned long *flags, unsigned long default_flags, bool *copy_and_truncate) { + TCHAR value[AMSM_STDIO_LENGTH]; + + /* Path. */ + if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s"), prefix) < 0) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, prefix, _T("get_createfile_parameters()"), 0); + return 1; + } + switch (expand_parameter(key, value, path, PATH_LENGTH, true, false)) { + case 0: if (! path[0]) return 0; break; /* OK. */ + default: return 2; /* Error. */ + } + + /* ShareMode. */ + if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, AMSM_REG_STDIO_SHARING) < 0) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, AMSM_REG_STDIO_SHARING, _T("get_createfile_parameters()"), 0); + return 3; + } + switch (get_number(key, value, sharing, false)) { + case 0: *sharing = default_sharing; break; /* Missing. */ + case 1: break; /* Found. */ + case -2: return 4; /* Error. */ + } + + /* CreationDisposition. */ + if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, AMSM_REG_STDIO_DISPOSITION) < 0) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, AMSM_REG_STDIO_DISPOSITION, _T("get_createfile_parameters()"), 0); + return 5; + } + switch (get_number(key, value, disposition, false)) { + case 0: *disposition = default_disposition; break; /* Missing. */ + case 1: break; /* Found. */ + case -2: return 6; /* Error. */ + } + + /* Flags. */ + if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, AMSM_REG_STDIO_FLAGS) < 0) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, AMSM_REG_STDIO_FLAGS, _T("get_createfile_parameters()"), 0); + return 7; + } + switch (get_number(key, value, flags, false)) { + case 0: *flags = default_flags; break; /* Missing. */ + case 1: break; /* Found. */ + case -2: return 8; /* Error. */ + } + + /* Rotate with CopyFile() and SetEndOfFile(). */ + if (copy_and_truncate) { + unsigned long data; + if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, AMSM_REG_STDIO_COPY_AND_TRUNCATE) < 0) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, AMSM_REG_STDIO_COPY_AND_TRUNCATE, _T("get_createfile_parameters()"), 0); + return 9; + } + switch (get_number(key, value, &data, false)) { + case 0: *copy_and_truncate = false; break; /* Missing. */ + case 1: /* Found. */ + if (data) *copy_and_truncate = true; + else *copy_and_truncate = false; + break; + case -2: return 9; /* Error. */ + } + } + + return 0; +} + +int set_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix, unsigned long number) { + TCHAR value[AMSM_STDIO_LENGTH]; + + if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, suffix, _T("set_createfile_parameter()"), 0); + return 1; + } + + return set_number(key, value, number); +} + +int delete_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix) { + TCHAR value[AMSM_STDIO_LENGTH]; + + if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_OUT_OF_MEMORY, suffix, _T("delete_createfile_parameter()"), 0); + return 1; + } + + if (RegDeleteValue(key, value)) return 0; + return 1; +} + +HANDLE write_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *attributes, unsigned long disposition, unsigned long flags) { + static LARGE_INTEGER offset = { 0 }; + HANDLE ret = CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0); + if (ret != INVALID_HANDLE_VALUE) { + if (SetFilePointerEx(ret, offset, 0, FILE_END)) SetEndOfFile(ret); + return ret; + } + + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_CREATEFILE_FAILED, path, error_string(GetLastError()), 0); + return ret; +} + +static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_len, SYSTEMTIME *st) { + if (! st) { + SYSTEMTIME now; + st = &now; + GetSystemTime(st); + } + + TCHAR buffer[PATH_LENGTH]; + memmove(buffer, path, sizeof(buffer)); + TCHAR *ext = PathFindExtension(buffer); + TCHAR extension[PATH_LENGTH]; + _sntprintf_s(extension, _countof(extension), _TRUNCATE, _T("-%04u%02u%02uT%02u%02u%02u.%03u%s"), st->wYear, st->wMonth, st->wDay, st->wHour, st->wMinute, st->wSecond, st->wMilliseconds, ext); + *ext = _T('\0'); + _sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension); +} + +void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long delay, unsigned long low, unsigned long high, bool copy_and_truncate) { + unsigned long error; + + /* Now. */ + SYSTEMTIME st; + GetSystemTime(&st); + + BY_HANDLE_FILE_INFORMATION info; + + /* Try to open the file to check if it exists and to get attributes. */ + HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (file != INVALID_HANDLE_VALUE) { + /* Get file attributes. */ + if (! GetFileInformationByHandle(file, &info)) { + /* Reuse current time for rotation timestamp. */ + seconds = low = high = 0; + SystemTimeToFileTime(&st, &info.ftLastWriteTime); + } + + CloseHandle(file); + } + else { + error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND) return; + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0); + /* Reuse current time for rotation timestamp. */ + seconds = low = high = 0; + SystemTimeToFileTime(&st, &info.ftLastWriteTime); + } + + /* Check file age. */ + if (seconds) { + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + + ULARGE_INTEGER s; + s.LowPart = ft.dwLowDateTime; + s.HighPart = ft.dwHighDateTime; + s.QuadPart -= seconds * 10000000LL; + ft.dwLowDateTime = s.LowPart; + ft.dwHighDateTime = s.HighPart; + if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return; + } + + /* Check file size. */ + if (low || high) { + if (info.nFileSizeHigh < high) return; + if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return; + } + + /* Get new filename. */ + FileTimeToSystemTime(&info.ftLastWriteTime, &st); + + TCHAR rotated[PATH_LENGTH]; + rotated_filename(path, rotated, _countof(rotated), &st); + + /* Rotate. */ + bool ok = true; + TCHAR *function; + if (copy_and_truncate) { + function = _T("CopyFile()"); + if (CopyFile(path, rotated, TRUE)) { + file = write_to_file(path, AMSM_STDOUT_SHARING, 0, AMSM_STDOUT_DISPOSITION, AMSM_STDOUT_FLAGS); + Sleep(delay); + SetFilePointer(file, 0, 0, FILE_BEGIN); + SetEndOfFile(file); + CloseHandle(file); + } + else ok = false; + } + else { + function = _T("MoveFile()"); + if (! MoveFile(path, rotated)) ok = false; + } + if (ok) { + log_event(EVENTLOG_INFORMATION_TYPE, AMSM_EVENT_ROTATED, service_name, path, rotated, 0); + return; + } + error = GetLastError(); + + if (error == ERROR_FILE_NOT_FOUND) return; + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_ROTATE_FILE_FAILED, service_name, path, function, rotated, error_string(error), 0); + return; +} + +int get_output_handles(nssm_service_t *service, STARTUPINFO *si) { + if (! si) return 1; + bool inherit_handles = false; + + /* Allocate a new console so we get a fresh stdin, stdout and stderr. */ + alloc_console(service); + + /* stdin */ + if (service->stdin_path[0]) { + si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, 0, service->stdin_disposition, service->stdin_flags, 0); + if (si->hStdInput == INVALID_HANDLE_VALUE) { + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0); + return 2; + } + + inherit_handles = true; + + } + + /* stdout */ + if (service->stdout_path[0]) { + if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stdout_copy_and_truncate); + HANDLE stdout_handle = write_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags); + if (stdout_handle == INVALID_HANDLE_VALUE) return 4; + service->stdout_si = 0; + + if (service->use_stdout_pipe) { + service->stdout_pipe = si->hStdOutput = 0; + service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &service->stdout_si, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stdout_tid, &service->rotate_stdout_online, service->timestamp_log, service->stdout_copy_and_truncate); + if (! service->stdout_thread) { + CloseHandle(service->stdout_pipe); + CloseHandle(service->stdout_si); + } + } + else service->stdout_thread = 0; + + if (! service->stdout_thread) { + if (dup_handle(stdout_handle, &service->stdout_si, AMSM_REG_STDOUT, _T("stdout"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 4; + service->rotate_stdout_online = AMSM_ROTATE_OFFLINE; + } + + if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_si"), _T("stdout"))) close_handle(&service->stdout_thread); + + inherit_handles = true; + + } + + /* stderr */ + if (service->stderr_path[0]) { + /* Same as stdout? */ + if (str_equiv(service->stderr_path, service->stdout_path)) { + service->stderr_sharing = service->stdout_sharing; + service->stderr_disposition = service->stdout_disposition; + service->stderr_flags = service->stdout_flags; + service->rotate_stderr_online = AMSM_ROTATE_OFFLINE; + + /* Two handles to the same file will create a race. */ + /* XXX: Here we assume that either both or neither handle must be a pipe. */ + if (dup_handle(service->stdout_si, &service->stderr_si, _T("stdout"), _T("stderr"))) return 6; + } + else { + if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stderr_copy_and_truncate); + HANDLE stderr_handle = write_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags); + if (stderr_handle == INVALID_HANDLE_VALUE) return 7; + service->stderr_si = 0; + + if (service->use_stderr_pipe) { + service->stderr_pipe = si->hStdError = 0; + service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &service->stderr_si, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stderr_tid, &service->rotate_stderr_online, service->timestamp_log, service->stderr_copy_and_truncate); + if (! service->stderr_thread) { + CloseHandle(service->stderr_pipe); + CloseHandle(service->stderr_si); + } + } + else service->stderr_thread = 0; + + if (! service->stderr_thread) { + if (dup_handle(stderr_handle, &service->stderr_si, AMSM_REG_STDERR, _T("stderr"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 7; + service->rotate_stderr_online = AMSM_ROTATE_OFFLINE; + } + } + + if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_si"), _T("stderr"))) close_handle(&service->stderr_thread); + + inherit_handles = true; + + } + + /* + We need to set the startup_info flags to make the new handles + inheritable by the new process. + */ + if (inherit_handles) si->dwFlags |= STARTF_USESTDHANDLES; + + return 0; +} + +/* Reuse output handles for a hook. */ +int use_output_handles(nssm_service_t *service, STARTUPINFO *si) { + si->dwFlags &= ~STARTF_USESTDHANDLES; + + if (service->stdout_si) { + if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_pipe"), _T("hStdOutput"))) return 1; + si->dwFlags |= STARTF_USESTDHANDLES; + } + + if (service->stderr_si) { + if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_pipe"), _T("hStdError"))) { + if (si->hStdOutput) { + si->dwFlags &= ~STARTF_USESTDHANDLES; + CloseHandle(si->hStdOutput); + } + return 2; + } + si->dwFlags |= STARTF_USESTDHANDLES; + } + + return 0; +} + +void close_output_handles(STARTUPINFO *si) { + if (si->hStdInput) CloseHandle(si->hStdInput); + if (si->hStdOutput) CloseHandle(si->hStdOutput); + if (si->hStdError) CloseHandle(si->hStdError); +} + +void cleanup_loggers(nssm_service_t *service) { + unsigned long interval = AMSM_CLEANUP_LOGGERS_DEADLINE; + HANDLE thread_handle = INVALID_HANDLE_VALUE; + + close_handle(&service->stdout_thread, &thread_handle); + /* Close write end of the data pipe so logging thread can finalise read. */ + close_handle(&service->stdout_si); + /* Await logging thread then close read end. */ + if (thread_handle != INVALID_HANDLE_VALUE) WaitForSingleObject(thread_handle, interval); + close_handle(&service->stdout_pipe); + + thread_handle = INVALID_HANDLE_VALUE; + close_handle(&service->stderr_thread, &thread_handle); + close_handle(&service->stderr_si); + if (thread_handle != INVALID_HANDLE_VALUE) WaitForSingleObject(thread_handle, interval); + close_handle(&service->stderr_pipe); +} + +/* + Try multiple times to read from a file. + Returns: 0 on success. + 1 on non-fatal error. + -1 on fatal error. +*/ +static int try_read(logger_t *logger, void *address, unsigned long bufsize, unsigned long *in, int *complained) { + int ret = 1; + unsigned long error; + for (int tries = 0; tries < 5; tries++) { + if (ReadFile(logger->read_handle, address, bufsize, in, 0)) return 0; + + error = GetLastError(); + switch (error) { + /* Other end closed the pipe. */ + case ERROR_BROKEN_PIPE: + ret = -1; + goto complain_read; + + /* Couldn't lock the buffer. */ + case ERROR_NOT_ENOUGH_QUOTA: + Sleep(2000 + tries * 3000); + ret = 1; + continue; + + /* Write was cancelled by the other end. */ + case ERROR_OPERATION_ABORTED: + ret = 1; + goto complain_read; + + default: + ret = -1; + } + } + +complain_read: + /* Ignore the error if we've been requested to exit anyway. */ + if (*logger->rotate_online != AMSM_ROTATE_ONLINE) return ret; + if (! (*complained & COMPLAINED_READ)) log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_READFILE_FAILED, logger->service_name, logger->path, error_string(error), 0); + *complained |= COMPLAINED_READ; + return ret; +} + +/* + Try multiple times to write to a file. + Returns: 0 on success. + 1 on non-fatal error. + -1 on fatal error. +*/ +static int try_write(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained) { + int ret = 1; + unsigned long error; + for (int tries = 0; tries < 5; tries++) { + if (WriteFile(logger->write_handle, address, bufsize, out, 0)) return 0; + + error = GetLastError(); + if (error == ERROR_IO_PENDING) { + /* Operation was successful pending flush to disk. */ + return 0; + } + + switch (error) { + /* Other end closed the pipe. */ + case ERROR_BROKEN_PIPE: + ret = -1; + goto complain_write; + + /* Couldn't lock the buffer. */ + case ERROR_NOT_ENOUGH_QUOTA: + /* Out of disk space. */ + case ERROR_DISK_FULL: + Sleep(2000 + tries * 3000); + ret = 1; + continue; + + default: + /* We'll lose this line but try to read and write subsequent ones. */ + ret = 1; + } + } + +complain_write: + if (! (*complained & COMPLAINED_WRITE)) log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(error), 0); + *complained |= COMPLAINED_WRITE; + return ret; +} + +/* Note that the timestamp is created in UTF-8. */ +static inline int write_timestamp(logger_t *logger, unsigned long charsize, unsigned long *out, int *complained) { + char timestamp[TIMESTAMP_LEN + 1]; + + SYSTEMTIME now; + GetSystemTime(&now); + _snprintf_s(timestamp, _countof(timestamp), _TRUNCATE, TIMESTAMP_FORMAT, now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, now.wMilliseconds); + + if (charsize == sizeof(char)) return try_write(logger, (void *) timestamp, TIMESTAMP_LEN, out, complained); + + wchar_t *utf16; + unsigned long utf16len; + if (to_utf16(timestamp, &utf16, &utf16len)) return -1; + int ret = try_write(logger, (void *) *utf16, utf16len * sizeof(wchar_t), out, complained); + HeapFree(GetProcessHeap(), 0, utf16); + return ret; +} + +static int write_with_timestamp(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained, unsigned long charsize) { + if (logger->timestamp_log) { + unsigned long log_out; + int log_complained; + unsigned long timestamp_out = 0; + int timestamp_complained; + if (! logger->line_length) { + write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained); + logger->line_length += (__int64) timestamp_out; + *out += timestamp_out; + *complained |= timestamp_complained; + } + + unsigned long i; + void *line = address; + unsigned long offset = 0; + int ret; + for (i = 0; i < bufsize; i++) { + if (((char *) address)[i] == '\n') { + ret = try_write(logger, line, i - offset + 1, &log_out, &log_complained); + line = (void *) ((char *) line + i - offset + 1); + logger->line_length = 0LL; + *out += log_out; + *complained |= log_complained; + offset = i + 1; + if (offset < bufsize) { + write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained); + logger->line_length += (__int64) timestamp_out; + *out += timestamp_out; + *complained |= timestamp_complained; + } + } + } + + if (offset < bufsize) { + ret = try_write(logger, line, bufsize - offset, &log_out, &log_complained); + *out += log_out; + *complained |= log_complained; + } + + return ret; + } + else return try_write(logger, address, bufsize, out, complained); +} + +/* Wrapper to be called in a new thread for logging. */ +unsigned long WINAPI log_and_rotate(void *arg) { + logger_t *logger = (logger_t *) arg; + if (! logger) return 1; + + __int64 size; + BY_HANDLE_FILE_INFORMATION info; + + /* Find initial file size. */ + if (! GetFileInformationByHandle(logger->write_handle, &info)) logger->size = 0LL; + else { + ULARGE_INTEGER l; + l.HighPart = info.nFileSizeHigh; + l.LowPart = info.nFileSizeLow; + size = l.QuadPart; + } + + char buffer[1024]; + void *address; + unsigned long in, out; + unsigned long charsize = 0; + unsigned long error; + int ret; + int complained = 0; + + while (true) { + /* Read data from the pipe. */ + address = &buffer; + ret = try_read(logger, address, sizeof(buffer), &in, &complained); + if (ret < 0) { + close_handle(&logger->read_handle); + close_handle(&logger->write_handle); + HeapFree(GetProcessHeap(), 0, logger); + return 2; + } + else if (ret) continue; + + if (*logger->rotate_online == AMSM_ROTATE_ONLINE_ASAP || (logger->size && size + (__int64) in >= logger->size)) { + /* Look for newline. */ + unsigned long i; + for (i = 0; i < in; i++) { + if (buffer[i] == '\n') { + if (! charsize) charsize = guess_charsize(address, in); + i += charsize; + + /* Write up to the newline. */ + ret = try_write(logger, address, i, &out, &complained); + if (ret < 0) { + close_handle(&logger->read_handle); + close_handle(&logger->write_handle); + HeapFree(GetProcessHeap(), 0, logger); + return 3; + } + size += (__int64) out; + + /* Rotate. */ + *logger->rotate_online = AMSM_ROTATE_ONLINE; + TCHAR rotated[PATH_LENGTH]; + rotated_filename(logger->path, rotated, _countof(rotated), 0); + + /* + Ideally we'd try the rename first then close the handle but + MoveFile() will fail if the handle is still open so we must + risk losing everything. + */ + if (logger->copy_and_truncate) FlushFileBuffers(logger->write_handle); + close_handle(&logger->write_handle); + bool ok = true; + TCHAR *function; + if (logger->copy_and_truncate) { + function = _T("CopyFile()"); + if (CopyFile(logger->path, rotated, TRUE)) { + HANDLE file = write_to_file(logger->path, AMSM_STDOUT_SHARING, 0, AMSM_STDOUT_DISPOSITION, AMSM_STDOUT_FLAGS); + Sleep(logger->rotate_delay); + SetFilePointer(file, 0, 0, FILE_BEGIN); + SetEndOfFile(file); + CloseHandle(file); + } + else ok = false; + } + else { + function = _T("MoveFile()"); + if (! MoveFile(logger->path, rotated)) ok = false; + } + if (ok) { + log_event(EVENTLOG_INFORMATION_TYPE, AMSM_EVENT_ROTATED, logger->service_name, logger->path, rotated, 0); + size = 0LL; + } + else { + error = GetLastError(); + if (error != ERROR_FILE_NOT_FOUND) { + if (! (complained & COMPLAINED_ROTATE)) log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, function, rotated, error_string(error), 0); + complained |= COMPLAINED_ROTATE; + /* We can at least try to re-open the existing file. */ + logger->disposition = OPEN_ALWAYS; + } + } + + /* Reopen. */ + logger->write_handle = write_to_file(logger->path, logger->sharing, 0, logger->disposition, logger->flags); + if (logger->write_handle == INVALID_HANDLE_VALUE) { + error = GetLastError(); + log_event(EVENTLOG_ERROR_TYPE, AMSM_EVENT_CREATEFILE_FAILED, logger->path, error_string(error), 0); + /* Oh dear. Now we can't log anything further. */ + close_handle(&logger->read_handle); + close_handle(&logger->write_handle); + HeapFree(GetProcessHeap(), 0, logger); + return 4; + } + + /* Resume writing after the newline. */ + address = (void *) ((char *) address + i); + in -= i; + } + } + } + + if (! size || logger->timestamp_log) if (! charsize) charsize = guess_charsize(address, in); + if (! size) { + /* Write a BOM to the new file. */ + if (charsize == sizeof(wchar_t)) write_bom(logger, &out); + size += (__int64) out; + } + + /* Write the data, if any. */ + if (! in) continue; + + ret = write_with_timestamp(logger, address, in, &out, &complained, charsize); + size += (__int64) out; + if (ret < 0) { + close_handle(&logger->read_handle); + close_handle(&logger->write_handle); + HeapFree(GetProcessHeap(), 0, logger); + return 3; + } + } + + close_handle(&logger->read_handle); + close_handle(&logger->write_handle); + HeapFree(GetProcessHeap(), 0, logger); + return 0; +} diff --git a/io.h b/io.h new file mode 100644 index 0000000..6b29ee7 --- /dev/null +++ b/io.h @@ -0,0 +1,44 @@ +#ifndef IO_H +#define IO_H + +#define AMSM_STDIN_SHARING FILE_SHARE_WRITE +#define AMSM_STDIN_DISPOSITION OPEN_EXISTING +#define AMSM_STDIN_FLAGS FILE_ATTRIBUTE_NORMAL +#define AMSM_STDOUT_SHARING (FILE_SHARE_READ | FILE_SHARE_WRITE) +#define AMSM_STDOUT_DISPOSITION OPEN_ALWAYS +#define AMSM_STDOUT_FLAGS FILE_ATTRIBUTE_NORMAL +#define AMSM_STDERR_SHARING (FILE_SHARE_READ | FILE_SHARE_WRITE) +#define AMSM_STDERR_DISPOSITION OPEN_ALWAYS +#define AMSM_STDERR_FLAGS FILE_ATTRIBUTE_NORMAL + +typedef struct { + TCHAR *service_name; + TCHAR *path; + unsigned long sharing; + unsigned long disposition; + unsigned long flags; + HANDLE read_handle; + HANDLE write_handle; + __int64 size; + unsigned long *tid_ptr; + unsigned long *rotate_online; + bool timestamp_log; + __int64 line_length; + bool copy_and_truncate; + unsigned long rotate_delay; +} logger_t; + +void close_handle(HANDLE *, HANDLE *); +void close_handle(HANDLE *); +int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long, bool *); +int set_createfile_parameter(HKEY, TCHAR *, TCHAR *, unsigned long); +int delete_createfile_parameter(HKEY, TCHAR *, TCHAR *); +HANDLE write_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long); +void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long, unsigned long, bool); +int get_output_handles(nssm_service_t *, STARTUPINFO *); +int use_output_handles(nssm_service_t *, STARTUPINFO *); +void close_output_handles(STARTUPINFO *); +void cleanup_loggers(nssm_service_t *); +unsigned long WINAPI log_and_rotate(void *); + +#endif diff --git a/messages.aps b/messages.aps new file mode 100644 index 0000000..3a7f630 Binary files /dev/null and b/messages.aps differ diff --git a/messages.h b/messages.h new file mode 100644 index 0000000..b302edb --- /dev/null +++ b/messages.h @@ -0,0 +1,2047 @@ +// +// Values are 32 bit values laid out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// + + +// +// Define the severity codes +// + + +// +// MessageId: AMSM_MESSAGE_USAGE +// +// MessageText: +// +// AMSM: The AppKu Manifest Service Manager. +// Version %s %s, %s +// Usage: amsm