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
