diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c index 8a5c8cf2a86..a8a8564599d 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -151,6 +151,10 @@ static void WINAPI pgwin32_ServiceHandler(DWORD); static void WINAPI pgwin32_ServiceMain(DWORD, LPTSTR *); static void pgwin32_doRunAsService(void); static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_service); +static bool pgwin32_get_dynamic_tokeninfo(HANDLE token, + TOKEN_INFORMATION_CLASS class, + char **InfoBuffer, char *errbuf, int errsize); +static int pgwin32_is_service(void); #endif static pgpid_t get_pgpid(void); @@ -216,7 +220,7 @@ write_stderr(const char *fmt,...) * On Win32, we print to stderr if running on a console, or write to * eventlog if running as a service */ - if (!isatty(fileno(stderr))) /* Running as a service */ + if (!pgwin32_is_service()) /* Running as a service */ { char errbuf[2048]; /* Arbitrary size? */ @@ -1604,6 +1608,160 @@ pgwin32_doRunAsService(void) } } +/* + * Call GetTokenInformation() on a token and return a dynamically sized + * buffer with the information in it. This buffer must be free():d by + * the calling function! + */ +static bool +pgwin32_get_dynamic_tokeninfo(HANDLE token, TOKEN_INFORMATION_CLASS class, + char **InfoBuffer, char *errbuf, int errsize) +{ + DWORD InfoBufferSize; + + if (GetTokenInformation(token, class, NULL, 0, &InfoBufferSize)) + { + snprintf(errbuf, errsize, "could not get token information: got zero size\n"); + return false; + } + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + snprintf(errbuf, errsize, "could not get token information: error code %lu\n", + GetLastError()); + return false; + } + + *InfoBuffer = malloc(InfoBufferSize); + if (*InfoBuffer == NULL) + { + snprintf(errbuf, errsize, "could not allocate %d bytes for token information\n", + (int) InfoBufferSize); + return false; + } + + if (!GetTokenInformation(token, class, *InfoBuffer, + InfoBufferSize, &InfoBufferSize)) + { + snprintf(errbuf, errsize, "could not get token information: error code %lu\n", + GetLastError()); + return false; + } + + return true; +} + +/* + * We consider ourselves running as a service if one of the following is + * true: + * + * 1) We are running as Local System (only used by services) + * 2) Our token contains SECURITY_SERVICE_RID (automatically added to the + * process token by the SCM when starting a service) + * + * Return values: + * 0 = Not service + * 1 = Service + * -1 = Error + * + * Note: we can't report errors via write_stderr (because that calls this) + * We are therefore reduced to writing directly on stderr, which sucks, but + * we have few alternatives. + */ +int +pgwin32_is_service(void) +{ + static int _is_service = -1; + HANDLE AccessToken; + char *InfoBuffer = NULL; + char errbuf[256]; + PTOKEN_GROUPS Groups; + PTOKEN_USER User; + PSID ServiceSid; + PSID LocalSystemSid; + SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY}; + UINT x; + + /* Only check the first time */ + if (_is_service != -1) + return _is_service; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &AccessToken)) + { + fprintf(stderr, "could not open process token: error code %lu\n", + GetLastError()); + return -1; + } + + /* First check for local system */ + if (!pgwin32_get_dynamic_tokeninfo(AccessToken, TokenUser, &InfoBuffer, + errbuf, sizeof(errbuf))) + { + fprintf(stderr, "%s", errbuf); + return -1; + } + + User = (PTOKEN_USER) InfoBuffer; + + if (!AllocateAndInitializeSid(&NtAuthority, 1, + SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, + &LocalSystemSid)) + { + fprintf(stderr, "could not get SID for local system account\n"); + CloseHandle(AccessToken); + return -1; + } + + if (EqualSid(LocalSystemSid, User->User.Sid)) + { + FreeSid(LocalSystemSid); + free(InfoBuffer); + CloseHandle(AccessToken); + _is_service = 1; + return _is_service; + } + + FreeSid(LocalSystemSid); + free(InfoBuffer); + + /* Now check for group SID */ + if (!pgwin32_get_dynamic_tokeninfo(AccessToken, TokenGroups, &InfoBuffer, + errbuf, sizeof(errbuf))) + { + fprintf(stderr, "%s", errbuf); + return -1; + } + + Groups = (PTOKEN_GROUPS) InfoBuffer; + + if (!AllocateAndInitializeSid(&NtAuthority, 1, + SECURITY_SERVICE_RID, 0, 0, 0, 0, 0, 0, 0, + &ServiceSid)) + { + fprintf(stderr, "could not get SID for service group\n"); + free(InfoBuffer); + CloseHandle(AccessToken); + return -1; + } + + _is_service = 0; + for (x = 0; x < Groups->GroupCount; x++) + { + if (EqualSid(ServiceSid, Groups->Groups[x].Sid)) + { + _is_service = 1; + break; + } + } + + free(InfoBuffer); + FreeSid(ServiceSid); + + CloseHandle(AccessToken); + + return _is_service; +} + /* * Mingw headers are incomplete, and so are the libraries. So we have to load