44 *
55 * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
66 *
7- * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.65 2006/02/07 11:36:36 petere Exp $
7+ * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.66 2006/02/10 22:00:59 tgl Exp $
88 *
99 *-------------------------------------------------------------------------
1010 */
1111
12+ #ifdef WIN32
13+ /*
14+ * Need this to get defines for restricted tokens and jobs. And it
15+ * has to be set before any header from the Win32 API is loaded.
16+ */
17+ #define _WIN32_WINNT 0x0500
18+ #endif
19+
1220#include "postgres_fe.h"
1321#include "libpq-fe.h"
1422
@@ -111,6 +119,7 @@ static void pgwin32_SetServiceStatus(DWORD);
111119static void WINAPI pgwin32_ServiceHandler (DWORD );
112120static void WINAPI pgwin32_ServiceMain (DWORD ,LPTSTR * );
113121static void pgwin32_doRunAsService (void );
122+ static int CreateRestrictedProcess (char * cmd ,PROCESS_INFORMATION * processInfo );
114123#endif
115124static pgpid_t get_pgpid (void );
116125static char * * readfile (const char * path );
@@ -325,42 +334,46 @@ readfile(const char *path)
325334static int
326335start_postmaster (void )
327336{
337+ char cmd [MAXPGPATH ];
338+ #ifndef WIN32
328339/*
329340 * Since there might be quotes to handle here, it is easier simply to pass
330341 * everything to a shell to process them.
331342 */
332- char cmd [MAXPGPATH ];
333-
334- /*
335- * Win32 needs START /B rather than "&".
336- *
337- * Win32 has a problem with START and quoted executable names. You must
338- * add a "" as the title at the beginning so you can quote the executable
339- * name: http://www.winnetmag.com/Article/ArticleID/14589/14589.html
340- * http://dev.remotenetworktechnology.com/cmd/cmdfaq.htm
341- */
342343if (log_file != NULL )
343- #ifndef WIN32 /* Cygwin doesn't have START */
344344snprintf (cmd ,MAXPGPATH ,"%s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1 &%s" ,
345345SYSTEMQUOTE ,postgres_path ,pgdata_opt ,post_opts ,
346346DEVNULL ,log_file ,SYSTEMQUOTE );
347- #else
348- snprintf (cmd ,MAXPGPATH ,"%sSTART /B \"\" \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s" ,
349- SYSTEMQUOTE ,postgres_path ,pgdata_opt ,post_opts ,
350- DEVNULL ,log_file ,SYSTEMQUOTE );
351- #endif
352- else
353- #ifndef WIN32 /* Cygwin doesn't have START */
347+ else
354348snprintf (cmd ,MAXPGPATH ,"%s\"%s\" %s%s < \"%s\" 2>&1 &%s" ,
355349SYSTEMQUOTE ,postgres_path ,pgdata_opt ,post_opts ,
356350DEVNULL ,SYSTEMQUOTE );
357- #else
358- snprintf (cmd ,MAXPGPATH ,"%sSTART /B \"\" \"%s\" %s%s < \"%s\" 2>&1%s" ,
351+
352+ return system (cmd );
353+
354+ #else /* WIN32 */
355+ /*
356+ * On win32 we don't use system(). So we don't need to use &
357+ * (which would be START /B on win32). However, we still call the shell
358+ * (CMD.EXE) with it to handle redirection etc.
359+ */
360+ PROCESS_INFORMATION pi ;
361+
362+ if (log_file != NULL )
363+ snprintf (cmd ,MAXPGPATH ,"CMD /C %s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s" ,
364+ SYSTEMQUOTE ,postgres_path ,pgdata_opt ,post_opts ,
365+ DEVNULL ,log_file ,SYSTEMQUOTE );
366+ else
367+ snprintf (cmd ,MAXPGPATH ,"CMD /C %s\"%s\" %s%s < \"%s\" 2>&1%s" ,
359368SYSTEMQUOTE ,postgres_path ,pgdata_opt ,post_opts ,
360369DEVNULL ,SYSTEMQUOTE );
361- #endif
362370
363- return system (cmd );
371+ if (!CreateRestrictedProcess (cmd ,& pi ))
372+ return GetLastError ();
373+ CloseHandle (pi .hProcess );
374+ CloseHandle (pi .hThread );
375+ return 0 ;
376+ #endif /* WIN32 */
364377}
365378
366379
@@ -1063,7 +1076,6 @@ pgwin32_ServiceHandler(DWORD request)
10631076static void WINAPI
10641077pgwin32_ServiceMain (DWORD argc ,LPTSTR * argv )
10651078{
1066- STARTUPINFO si ;
10671079PROCESS_INFORMATION pi ;
10681080DWORD ret ;
10691081
@@ -1077,8 +1089,6 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv)
10771089status .dwCurrentState = SERVICE_START_PENDING ;
10781090
10791091memset (& pi ,0 ,sizeof (pi ));
1080- memset (& si ,0 ,sizeof (si ));
1081- si .cb = sizeof (si );
10821092
10831093/* Register the control request handler */
10841094if ((hStatus = RegisterServiceCtrlHandler (register_servicename ,pgwin32_ServiceHandler ))== (SERVICE_STATUS_HANDLE )0 )
@@ -1089,7 +1099,7 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv)
10891099
10901100/* Start the postmaster */
10911101pgwin32_SetServiceStatus (SERVICE_START_PENDING );
1092- if (!CreateProcess ( NULL , pgwin32_CommandLine (false), NULL , NULL , TRUE, 0 , NULL , NULL , & si ,& pi ))
1102+ if (!CreateRestrictedProcess ( pgwin32_CommandLine (false),& pi ))
10931103{
10941104pgwin32_SetServiceStatus (SERVICE_STOPPED );
10951105return ;
@@ -1141,6 +1151,188 @@ pgwin32_doRunAsService(void)
11411151exit (1 );
11421152}
11431153}
1154+
1155+
1156+ /*
1157+ * Mingw headers are incomplete, and so are the libraries. So we have to load
1158+ * a whole lot of API functions dynamically. Since we have to do this anyway,
1159+ * also load the couple of functions that *do* exist in minwg headers but not
1160+ * on NT4. That way, we don't break on NT4.
1161+ */
1162+ typedef WINAPI BOOL (* __CreateRestrictedToken )(HANDLE ,DWORD ,DWORD ,PSID_AND_ATTRIBUTES ,DWORD ,PLUID_AND_ATTRIBUTES ,DWORD ,PSID_AND_ATTRIBUTES ,PHANDLE );
1163+ typedef WINAPI BOOL (* __IsProcessInJob )(HANDLE ,HANDLE ,PBOOL );
1164+ typedef WINAPI HANDLE (* __CreateJobObject )(LPSECURITY_ATTRIBUTES ,LPCTSTR );
1165+ typedef WINAPI BOOL (* __SetInformationJobObject )(HANDLE ,JOBOBJECTINFOCLASS ,LPVOID ,DWORD );
1166+ typedef WINAPI BOOL (* __AssignProcessToJobObject )(HANDLE ,HANDLE );
1167+ typedef WINAPI BOOL (* __QueryInformationJobObject )(HANDLE ,JOBOBJECTINFOCLASS ,LPVOID ,DWORD ,LPDWORD );
1168+
1169+ /* Windows API define missing from MingW headers */
1170+ #define DISABLE_MAX_PRIVILEGE 0x1
1171+
1172+ /*
1173+ * Create a restricted token, a job object sandbox, and execute the specified
1174+ * process with it.
1175+ *
1176+ * Returns 0 on success, non-zero on failure, same as CreateProcess().
1177+ *
1178+ * On NT4, or any other system not containing the required functions, will
1179+ * launch the process under the current token without doing any modifications.
1180+ *
1181+ * NOTE! Job object will only work when running as a service, because it's
1182+ * automatically destroyed when pg_ctl exits.
1183+ */
1184+ static int
1185+ CreateRestrictedProcess (char * cmd ,PROCESS_INFORMATION * processInfo )
1186+ {
1187+ int r ;
1188+ BOOL b ;
1189+ STARTUPINFO si ;
1190+ HANDLE origToken ;
1191+ HANDLE restrictedToken ;
1192+ SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY };
1193+ SID_AND_ATTRIBUTES dropSids [2 ];
1194+
1195+ /* Functions loaded dynamically */
1196+ __CreateRestrictedToken _CreateRestrictedToken = NULL ;
1197+ __IsProcessInJob _IsProcessInJob = NULL ;
1198+ __CreateJobObject _CreateJobObject = NULL ;
1199+ __SetInformationJobObject _SetInformationJobObject = NULL ;
1200+ __AssignProcessToJobObject _AssignProcessToJobObject = NULL ;
1201+ __QueryInformationJobObject _QueryInformationJobObject = NULL ;
1202+ HANDLE Kernel32Handle ;
1203+ HANDLE Advapi32Handle ;
1204+
1205+ ZeroMemory (& si ,sizeof (si ));
1206+ si .cb = sizeof (si );
1207+
1208+ Advapi32Handle = LoadLibrary ("ADVAPI32.DLL" );
1209+ if (Advapi32Handle != NULL )
1210+ {
1211+ _CreateRestrictedToken = (__CreateRestrictedToken )GetProcAddress (Advapi32Handle ,"CreateRestrictedToken" );
1212+ }
1213+
1214+ if (_CreateRestrictedToken == NULL )
1215+ {
1216+ /* NT4 doesn't have CreateRestrictedToken, so just call ordinary CreateProcess */
1217+ write_stderr ("WARNING: Unable to create restricted tokens on this platform\n" );
1218+ if (Advapi32Handle != NULL )
1219+ FreeLibrary (Advapi32Handle );
1220+ return CreateProcess (NULL ,cmd ,NULL ,NULL , FALSE,0 ,NULL ,NULL ,& si ,processInfo );
1221+ }
1222+
1223+ /* Open the current token to use as a base for the restricted one */
1224+ if (!OpenProcessToken (GetCurrentProcess (),TOKEN_ALL_ACCESS ,& origToken ))
1225+ {
1226+ write_stderr ("Failed to open process token: %lu\n" ,GetLastError ());
1227+ return 0 ;
1228+ }
1229+
1230+ /* Allocate list of SIDs to remove */
1231+ ZeroMemory (& dropSids ,sizeof (dropSids ));
1232+ if (!AllocateAndInitializeSid (& NtAuthority ,2 ,
1233+ SECURITY_BUILTIN_DOMAIN_RID ,DOMAIN_ALIAS_RID_ADMINS ,0 ,0 ,0 ,0 ,0 ,
1234+ 0 ,& dropSids [0 ].Sid )||
1235+ !AllocateAndInitializeSid (& NtAuthority ,2 ,
1236+ SECURITY_BUILTIN_DOMAIN_RID ,DOMAIN_ALIAS_RID_POWER_USERS ,0 ,0 ,0 ,0 ,0 ,
1237+ 0 ,& dropSids [1 ].Sid ))
1238+ {
1239+ write_stderr ("Failed to allocate SIDs: %lu\n" ,GetLastError ());
1240+ return 0 ;
1241+ }
1242+
1243+ b = _CreateRestrictedToken (origToken ,
1244+ DISABLE_MAX_PRIVILEGE ,
1245+ sizeof (dropSids )/sizeof (dropSids [0 ]),
1246+ dropSids ,
1247+ 0 ,NULL ,
1248+ 0 ,NULL ,
1249+ & restrictedToken );
1250+
1251+ FreeSid (dropSids [1 ].Sid );
1252+ FreeSid (dropSids [0 ].Sid );
1253+ CloseHandle (origToken );
1254+ FreeLibrary (Advapi32Handle );
1255+
1256+ if (!b )
1257+ {
1258+ write_stderr ("Failed to create restricted token: %lu\n" ,GetLastError ());
1259+ return 0 ;
1260+ }
1261+
1262+ r = CreateProcessAsUser (restrictedToken ,NULL ,cmd ,NULL ,NULL , TRUE,CREATE_SUSPENDED ,NULL ,NULL ,& si ,processInfo );
1263+
1264+ Kernel32Handle = LoadLibrary ("KERNEL32.DLL" );
1265+ if (Kernel32Handle != NULL )
1266+ {
1267+ _IsProcessInJob = (__IsProcessInJob )GetProcAddress (Kernel32Handle ,"IsProcessInJob" );
1268+ _CreateJobObject = (__CreateJobObject )GetProcAddress (Kernel32Handle ,"CreateJobObjectA" );
1269+ _SetInformationJobObject = (__SetInformationJobObject )GetProcAddress (Kernel32Handle ,"SetInformationJobObject" );
1270+ _AssignProcessToJobObject = (__AssignProcessToJobObject )GetProcAddress (Kernel32Handle ,"AssignProcessToJobObject" );
1271+ _QueryInformationJobObject = (__QueryInformationJobObject )GetProcAddress (Kernel32Handle ,"QueryInformationJobObject" );
1272+ }
1273+
1274+ /* Verify that we found all functions */
1275+ if (_IsProcessInJob == NULL || _CreateJobObject == NULL || _SetInformationJobObject == NULL || _AssignProcessToJobObject == NULL || _QueryInformationJobObject == NULL )
1276+ {
1277+ write_stderr ("WARNING: Unable to locate all job object functions in system API!\n" );
1278+ }
1279+ else
1280+ {
1281+ BOOL inJob ;
1282+ if (_IsProcessInJob (processInfo -> hProcess ,NULL ,& inJob ))
1283+ {
1284+ if (!inJob )
1285+ {
1286+ /* Job objects are working, and the new process isn't in one, so we can create one safely.
1287+ If any problems show up when setting it, we're going to ignore them. */
1288+ HANDLE job ;
1289+ char jobname [128 ];
1290+
1291+ sprintf (jobname ,"PostgreSQL_%lu" ,processInfo -> dwProcessId );
1292+
1293+ job = _CreateJobObject (NULL ,jobname );
1294+ if (job )
1295+ {
1296+ JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimit ;
1297+ JOBOBJECT_BASIC_UI_RESTRICTIONS uiRestrictions ;
1298+ JOBOBJECT_SECURITY_LIMIT_INFORMATION securityLimit ;
1299+
1300+ ZeroMemory (& basicLimit ,sizeof (basicLimit ));
1301+ ZeroMemory (& uiRestrictions ,sizeof (uiRestrictions ));
1302+ ZeroMemory (& securityLimit ,sizeof (securityLimit ));
1303+
1304+ basicLimit .LimitFlags = JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION |JOB_OBJECT_LIMIT_PRIORITY_CLASS ;
1305+ basicLimit .PriorityClass = NORMAL_PRIORITY_CLASS ;
1306+ _SetInformationJobObject (job ,JobObjectBasicLimitInformation ,& basicLimit ,sizeof (basicLimit ));
1307+
1308+ uiRestrictions .UIRestrictionsClass = JOB_OBJECT_UILIMIT_DESKTOP |JOB_OBJECT_UILIMIT_DISPLAYSETTINGS |
1309+ JOB_OBJECT_UILIMIT_EXITWINDOWS |JOB_OBJECT_UILIMIT_HANDLES |JOB_OBJECT_UILIMIT_READCLIPBOARD |
1310+ JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS |JOB_OBJECT_UILIMIT_WRITECLIPBOARD ;
1311+ _SetInformationJobObject (job ,JobObjectBasicUIRestrictions ,& uiRestrictions ,sizeof (uiRestrictions ));
1312+
1313+ securityLimit .SecurityLimitFlags = JOB_OBJECT_SECURITY_NO_ADMIN |JOB_OBJECT_SECURITY_ONLY_TOKEN ;
1314+ securityLimit .JobToken = restrictedToken ;
1315+ _SetInformationJobObject (job ,JobObjectSecurityLimitInformation ,& securityLimit ,sizeof (securityLimit ));
1316+
1317+ _AssignProcessToJobObject (job ,processInfo -> hProcess );
1318+ }
1319+ }
1320+ }
1321+ }
1322+
1323+ CloseHandle (restrictedToken );
1324+
1325+ ResumeThread (processInfo -> hThread );
1326+
1327+ FreeLibrary (Kernel32Handle );
1328+
1329+ /*
1330+ * We intentionally don't close the job object handle, because we want the
1331+ * object to live on until pg_ctl shuts down.
1332+ */
1333+ return r ;
1334+ }
1335+
11441336#endif
11451337
11461338static void