Index: bin/pg_ctl/pg_ctl.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/pg_ctl/pg_ctl.c,v retrieving revision 1.63 diff -c -r1.63 pg_ctl.c *** bin/pg_ctl/pg_ctl.c 5 Jan 2006 03:01:37 -0000 1.63 --- bin/pg_ctl/pg_ctl.c 14 Jan 2006 16:45:31 -0000 *************** *** 9,14 **** --- 9,21 ---- *------------------------------------------------------------------------- */ + #ifdef WIN32 + /* Need this to get defines for restricted tokens and jobs. And it + * needs to be set before any header from the Win32 API is loaded. + */ + #define _WIN32_WINNT 0x0500 + #endif + #include "postgres_fe.h" #include "libpq-fe.h" *************** *** 111,116 **** --- 118,124 ---- 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); #endif static pgpid_t get_pgpid(void); static char **readfile(const char *path); *************** *** 325,366 **** static int start_postmaster(void) { /* * Since there might be quotes to handle here, it is easier simply to pass * everything to a shell to process them. */ - char cmd[MAXPGPATH]; - - /* - * Win32 needs START /B rather than "&". - * - * Win32 has a problem with START and quoted executable names. You must - * add a "" as the title at the beginning so you can quote the executable - * name: http://www.winnetmag.com/Article/ArticleID/14589/14589.html - * http://dev.remotenetworktechnology.com/cmd/cmdfaq.htm - */ if (log_file != NULL) - #ifndef WIN32 /* Cygwin doesn't have START */ snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1 &%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, log_file, SYSTEMQUOTE); ! #else ! snprintf(cmd, MAXPGPATH, "%sSTART /B \"\" \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s", ! SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, ! DEVNULL, log_file, SYSTEMQUOTE); ! #endif ! else ! #ifndef WIN32 /* Cygwin doesn't have START */ snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" 2>&1 &%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, SYSTEMQUOTE); ! #else ! snprintf(cmd, MAXPGPATH, "%sSTART /B \"\" \"%s\" %s%s < \"%s\" 2>&1%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, SYSTEMQUOTE); - #endif ! return system(cmd); } --- 333,378 ---- static int start_postmaster(void) { + char cmd[MAXPGPATH]; + #ifndef WIN32 /* * Since there might be quotes to handle here, it is easier simply to pass * everything to a shell to process them. */ if (log_file != NULL) snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1 &%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, log_file, SYSTEMQUOTE); ! else snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" 2>&1 &%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, SYSTEMQUOTE); ! ! return system(cmd); ! ! #else /* WIN32 */ ! /* ! * On win32 we don't use system(). So we don't need to use & ! * (which would be START /B on win32). However, we still call the shell ! * (CMD.EXE) with it to handle redirection etc. ! */ ! PROCESS_INFORMATION pi; ! ! if (log_file != NULL) ! snprintf(cmd, MAXPGPATH, "CMD /C %s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s", ! SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, ! DEVNULL, log_file, SYSTEMQUOTE); ! else ! snprintf(cmd, MAXPGPATH, "CMD /C %s\"%s\" %s%s < \"%s\" 2>&1%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, SYSTEMQUOTE); ! if (!CreateRestrictedProcess(cmd, &pi)) ! return GetLastError(); ! CloseHandle(pi.hProcess); ! CloseHandle(pi.hThread); ! return 0; ! #endif /* WIN32 */ } *************** *** 1063,1069 **** static void WINAPI pgwin32_ServiceMain(DWORD argc, LPTSTR * argv) { - STARTUPINFO si; PROCESS_INFORMATION pi; DWORD ret; --- 1075,1080 ---- *************** *** 1077,1084 **** status.dwCurrentState = SERVICE_START_PENDING; memset(&pi, 0, sizeof(pi)); - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); /* Register the control request handler */ if ((hStatus = RegisterServiceCtrlHandler(register_servicename, pgwin32_ServiceHandler)) == (SERVICE_STATUS_HANDLE) 0) --- 1088,1093 ---- *************** *** 1089,1095 **** /* Start the postmaster */ pgwin32_SetServiceStatus(SERVICE_START_PENDING); ! if (!CreateProcess(NULL, pgwin32_CommandLine(false), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { pgwin32_SetServiceStatus(SERVICE_STOPPED); return; --- 1098,1104 ---- /* Start the postmaster */ pgwin32_SetServiceStatus(SERVICE_START_PENDING); ! if (!CreateRestrictedProcess(pgwin32_CommandLine(false), &pi)) { pgwin32_SetServiceStatus(SERVICE_STOPPED); return; *************** *** 1141,1146 **** --- 1150,1334 ---- exit(1); } } + + + /* Mingw headers are incomplete, and so are the libraries. So we have to load + * a whole lot of API functions dynamically. Since we have to do this anyway, + * also load the couple of functions that *do* exist in minwg headers but not + * on NT4. That way, we don't break on NT4. + */ + typedef WINAPI BOOL (*__CreateRestrictedToken)(HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE); + typedef WINAPI BOOL (*__IsProcessInJob)(HANDLE, HANDLE, PBOOL); + typedef WINAPI HANDLE (*__CreateJobObject)(LPSECURITY_ATTRIBUTES, LPCTSTR); + typedef WINAPI BOOL (*__SetInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD); + typedef WINAPI BOOL (*__AssignProcessToJobObject)(HANDLE, HANDLE); + typedef WINAPI BOOL (*__QueryInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD, LPDWORD); + + /* Windows API define missing from MingW headers */ + #define DISABLE_MAX_PRIVILEGE 0x1 + + /* + * Create a restricted token, a job object sandbox, and exceute the specified + * process with it. + * + * Returns 0 on success, non-zero on failure, same as CreateProcess(). + * + * On NT4, or any other system not containing the required functions, will launch the + * process under the current token without doing any modifications. + * + * NOTE! Job object will only work when running as a service, because it's automatically + * destroyed when pg_ctl exits. + */ + static int + CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo) + { + int r; + BOOL b; + STARTUPINFO si; + HANDLE origToken; + HANDLE restrictedToken; + SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY}; + SID_AND_ATTRIBUTES dropSids[2]; + + /* Functions loaded dynamically */ + __CreateRestrictedToken _CreateRestrictedToken = NULL; + __IsProcessInJob _IsProcessInJob = NULL; + __CreateJobObject _CreateJobObject = NULL; + __SetInformationJobObject _SetInformationJobObject = NULL; + __AssignProcessToJobObject _AssignProcessToJobObject = NULL; + __QueryInformationJobObject _QueryInformationJobObject = NULL; + HANDLE Kernel32Handle; + HANDLE Advapi32Handle; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + Advapi32Handle = LoadLibrary("ADVAPI32.DLL"); + if (Advapi32Handle != NULL) + { + _CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken"); + } + + if (_CreateRestrictedToken == NULL) + { + /* NT4 doesn't have CreateRestrictedToken, so just call ordinary CreateProcess */ + write_stderr("WARNING: Unable to create restricted tokens on this platform\n"); + if (Advapi32Handle != NULL) + FreeLibrary(Advapi32Handle); + return CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, processInfo); + } + + /* Open the current token to use as a base for the restricted one */ + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken)) + { + write_stderr("Failed to open process token: %lu\n", GetLastError()); + return 0; + } + + /* Allocate list of SIDs to remove */ + ZeroMemory(&dropSids, sizeof(dropSids)); + if (!AllocateAndInitializeSid(&NtAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0,0,0,0,0, + 0, &dropSids[0].Sid) || + !AllocateAndInitializeSid(&NtAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0,0,0,0,0, + 0, &dropSids[1].Sid)) + { + write_stderr("Failed to allocate SIDs: %lu\n", GetLastError()); + return 0; + } + + b = _CreateRestrictedToken(origToken, + DISABLE_MAX_PRIVILEGE, + sizeof(dropSids)/sizeof(dropSids[0]), + dropSids, + 0, NULL, + 0, NULL, + &restrictedToken); + + FreeSid(dropSids[1].Sid); + FreeSid(dropSids[0].Sid); + CloseHandle(origToken); + FreeLibrary(Advapi32Handle); + + if (!b) + { + write_stderr("Failed to create restricted token: %lu\n", GetLastError()); + return 0; + } + + r = CreateProcessAsUser(restrictedToken, NULL, cmd, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, processInfo); + + Kernel32Handle = LoadLibrary("KERNEL32.DLL"); + if (Kernel32Handle != NULL) + { + _IsProcessInJob = (__IsProcessInJob) GetProcAddress(Kernel32Handle, "IsProcessInJob"); + _CreateJobObject = (__CreateJobObject) GetProcAddress(Kernel32Handle, "CreateJobObjectA"); + _SetInformationJobObject = (__SetInformationJobObject) GetProcAddress(Kernel32Handle, "SetInformationJobObject"); + _AssignProcessToJobObject = (__AssignProcessToJobObject) GetProcAddress(Kernel32Handle, "AssignProcessToJobObject"); + _QueryInformationJobObject = (__QueryInformationJobObject) GetProcAddress(Kernel32Handle, "QueryInformationJobObject"); + } + + /* Verify that we found all functions */ + if (_IsProcessInJob == NULL || _CreateJobObject == NULL || _SetInformationJobObject == NULL || _AssignProcessToJobObject == NULL || _QueryInformationJobObject == NULL) + { + write_stderr("WARNING: Unable to locate all job object functions in system API!\n"); + } + else + { + BOOL inJob; + if (_IsProcessInJob(processInfo->hProcess, NULL, &inJob)) + { + if (!inJob) + { + /* Job objects are working, and the new process isn't in one, so we can create one safely. + If any problems show up when setting it, we're going to ignore them. */ + HANDLE job; + char jobname[128]; + + sprintf(jobname,"PostgreSQL_%lu", processInfo->dwProcessId); + + job = _CreateJobObject(NULL, jobname); + if (job) + { + JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimit; + JOBOBJECT_BASIC_UI_RESTRICTIONS uiRestrictions; + JOBOBJECT_SECURITY_LIMIT_INFORMATION securityLimit; + + ZeroMemory(&basicLimit, sizeof(basicLimit)); + ZeroMemory(&uiRestrictions, sizeof(uiRestrictions)); + ZeroMemory(&securityLimit, sizeof(securityLimit)); + + basicLimit.LimitFlags = JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | JOB_OBJECT_LIMIT_PRIORITY_CLASS; + basicLimit.PriorityClass = NORMAL_PRIORITY_CLASS; + _SetInformationJobObject(job, JobObjectBasicLimitInformation, &basicLimit, sizeof(basicLimit)); + + uiRestrictions.UIRestrictionsClass = JOB_OBJECT_UILIMIT_DESKTOP | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | + JOB_OBJECT_UILIMIT_EXITWINDOWS | JOB_OBJECT_UILIMIT_HANDLES | JOB_OBJECT_UILIMIT_READCLIPBOARD | + JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_WRITECLIPBOARD; + _SetInformationJobObject(job, JobObjectBasicUIRestrictions, &uiRestrictions, sizeof(uiRestrictions)); + + securityLimit.SecurityLimitFlags = JOB_OBJECT_SECURITY_NO_ADMIN | JOB_OBJECT_SECURITY_ONLY_TOKEN; + securityLimit.JobToken = restrictedToken; + _SetInformationJobObject(job, JobObjectSecurityLimitInformation, &securityLimit, sizeof(securityLimit)); + + _AssignProcessToJobObject(job, processInfo->hProcess); + } + } + } + } + + CloseHandle(restrictedToken); + + ResumeThread(processInfo->hThread); + + FreeLibrary(Kernel32Handle); + + /* We intentionally don't close the job object handle, because we want the object to + live on until pg_ctl shuts down. */ + return r; + } + #endif static void