VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/win/process-win.cpp@ 70195

Last change on this file since 70195 was 70195, checked in by vboxsync, 7 years ago

IPRT/R3: Made the core work on NT 3.51 (still experimental).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 97.4 KB
Line 
1/* $Id: process-win.cpp 70195 2017-12-18 13:40:26Z vboxsync $ */
2/** @file
3 * IPRT - Process, Windows.
4 */
5
6/*
7 * Copyright (C) 2006-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP RTLOGGROUP_PROCESS
32#include <iprt/asm.h> /* hack */
33
34#include <iprt/nt/nt-and-windows.h>
35#include <Userenv.h>
36#include <tlhelp32.h>
37#include <process.h>
38#include <errno.h>
39#include <Strsafe.h>
40#include <LsaLookup.h>
41#include <Lmcons.h>
42
43#define _NTDEF_ /* Prevents redefining (P)UNICODE_STRING. */
44#include <Ntsecapi.h>
45
46#include <iprt/process.h>
47#include "internal-r3-win.h"
48
49#include <iprt/assert.h>
50#include <iprt/critsect.h>
51#include <iprt/file.h>
52#include <iprt/err.h>
53#include <iprt/env.h>
54#include <iprt/getopt.h>
55#include <iprt/initterm.h>
56#include <iprt/ldr.h>
57#include <iprt/log.h>
58#include <iprt/mem.h>
59#include <iprt/once.h>
60#include <iprt/path.h>
61#include <iprt/pipe.h>
62#include <iprt/string.h>
63#include <iprt/socket.h>
64
65
66
67/*********************************************************************************************************************************
68* Structures and Typedefs *
69*********************************************************************************************************************************/
70/* kernel32.dll: */
71//typedef DWORD (WINAPI *PFNWTSGETACTIVECONSOLESESSIONID)(VOID);
72typedef HANDLE (WINAPI *PFNCREATETOOLHELP32SNAPSHOT)(DWORD, DWORD);
73typedef BOOL (WINAPI *PFNPROCESS32FIRST)(HANDLE, LPPROCESSENTRY32);
74typedef BOOL (WINAPI *PFNPROCESS32FIRSTW)(HANDLE, LPPROCESSENTRY32W);
75typedef BOOL (WINAPI *PFNPROCESS32NEXT)(HANDLE, LPPROCESSENTRY32);
76typedef BOOL (WINAPI *PFNPROCESS32NEXTW)(HANDLE, LPPROCESSENTRY32W);
77
78/* psapi.dll: */
79typedef BOOL (WINAPI *PFNENUMPROCESSES)(LPDWORD, DWORD, LPDWORD);
80typedef DWORD (WINAPI *PFNGETMODULEBASENAME)(HANDLE, HMODULE, LPTSTR, DWORD);
81
82/* advapi32.dll: */
83typedef BOOL (WINAPI *PFNCREATEPROCESSWITHLOGON)(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPCWSTR, LPWSTR, DWORD,
84 LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
85typedef NTSTATUS (NTAPI *PFNLSALOOKUPNAMES2)(LSA_HANDLE, ULONG, ULONG, PLSA_UNICODE_STRING,
86 PLSA_REFERENCED_DOMAIN_LIST*, PLSA_TRANSLATED_SID2*);
87
88/* userenv.dll: */
89typedef BOOL (WINAPI *PFNCREATEENVIRONMENTBLOCK)(LPVOID *, HANDLE, BOOL);
90typedef BOOL (WINAPI *PFNPFNDESTROYENVIRONMENTBLOCK)(LPVOID);
91typedef BOOL (WINAPI *PFNLOADUSERPROFILEW)(HANDLE, LPPROFILEINFOW);
92typedef BOOL (WINAPI *PFNUNLOADUSERPROFILE)(HANDLE, HANDLE);
93
94
95/*********************************************************************************************************************************
96* Global Variables *
97*********************************************************************************************************************************/
98/** Init once structure. */
99static RTONCE g_rtProcWinInitOnce = RTONCE_INITIALIZER;
100/** Critical section protecting the process array. */
101static RTCRITSECT g_CritSect;
102/** The number of processes in the array. */
103static uint32_t g_cProcesses;
104/** The current allocation size. */
105static uint32_t g_cProcessesAlloc;
106/** Array containing the live or non-reaped child processes. */
107static struct RTPROCWINENTRY
108{
109 /** The process ID. */
110 ULONG_PTR pid;
111 /** The process handle. */
112 HANDLE hProcess;
113} *g_paProcesses;
114
115/** Structure for storing a user's account info.
116 * Must be free'd with rtProcWinFreeAccountInfo(). */
117typedef struct RTPROCWINACCOUNTINFO
118{
119 /** User name. */
120 PRTUTF16 pwszUserName;
121 /** Domain this account is tied to. Can be NULL if no domain is being used. */
122 PRTUTF16 pwszDomain;
123} RTPROCWINACCOUNTINFO, *PRTPROCWINACCOUNTINFO;
124
125/** @name userenv.dll imports (we don't unload it).
126 * They're all optional. So in addition to using g_rtProcWinResolveOnce, the
127 * caller must also check if any of the necessary APIs are NULL pointers.
128 * @{ */
129/** Init once structure for run-as-user functions we need. */
130static RTONCE g_rtProcWinResolveOnce = RTONCE_INITIALIZER;
131/* kernel32.dll: */
132static PFNCREATETOOLHELP32SNAPSHOT g_pfnCreateToolhelp32Snapshot = NULL;
133static PFNPROCESS32FIRST g_pfnProcess32First = NULL;
134static PFNPROCESS32NEXT g_pfnProcess32Next = NULL;
135static PFNPROCESS32FIRSTW g_pfnProcess32FirstW = NULL;
136static PFNPROCESS32NEXTW g_pfnProcess32NextW = NULL;
137/* psapi.dll: */
138static PFNGETMODULEBASENAME g_pfnGetModuleBaseName = NULL;
139static PFNENUMPROCESSES g_pfnEnumProcesses = NULL;
140/* advapi32.dll: */
141static PFNCREATEPROCESSWITHLOGON g_pfnCreateProcessWithLogonW = NULL;
142static PFNLSALOOKUPNAMES2 g_pfnLsaLookupNames2 = NULL;
143/* userenv.dll: */
144static PFNCREATEENVIRONMENTBLOCK g_pfnCreateEnvironmentBlock = NULL;
145static PFNPFNDESTROYENVIRONMENTBLOCK g_pfnDestroyEnvironmentBlock = NULL;
146static PFNLOADUSERPROFILEW g_pfnLoadUserProfileW = NULL;
147static PFNUNLOADUSERPROFILE g_pfnUnloadUserProfile = NULL;
148/** @} */
149
150
151/*********************************************************************************************************************************
152* Internal Functions *
153*********************************************************************************************************************************/
154static int rtProcWinFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec, PRTUTF16 *ppwszExec);
155static int rtProcWinCreateEnvBlockAndFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec,
156 PRTUTF16 *ppwszzBlock, PRTUTF16 *ppwszExec);
157
158
159/**
160 * Clean up the globals.
161 *
162 * @param enmReason Ignored.
163 * @param iStatus Ignored.
164 * @param pvUser Ignored.
165 */
166static DECLCALLBACK(void) rtProcWinTerm(RTTERMREASON enmReason, int32_t iStatus, void *pvUser)
167{
168 NOREF(pvUser); NOREF(iStatus); NOREF(enmReason);
169
170 RTCritSectDelete(&g_CritSect);
171
172 size_t i = g_cProcesses;
173 while (i-- > 0)
174 {
175 CloseHandle(g_paProcesses[i].hProcess);
176 g_paProcesses[i].hProcess = NULL;
177 }
178 RTMemFree(g_paProcesses);
179
180 g_paProcesses = NULL;
181 g_cProcesses = 0;
182 g_cProcessesAlloc = 0;
183}
184
185
186/**
187 * Initialize the globals.
188 *
189 * @returns IPRT status code.
190 * @param pvUser Ignored.
191 */
192static DECLCALLBACK(int32_t) rtProcWinInitOnce(void *pvUser)
193{
194 NOREF(pvUser);
195
196 g_cProcesses = 0;
197 g_cProcessesAlloc = 0;
198 g_paProcesses = NULL;
199 int rc = RTCritSectInit(&g_CritSect);
200 if (RT_SUCCESS(rc))
201 {
202 /** @todo init once, terminate once - this is a generic thing which should
203 * have some kind of static and simpler setup! */
204 rc = RTTermRegisterCallback(rtProcWinTerm, NULL);
205 if (RT_SUCCESS(rc))
206 return rc;
207 RTCritSectDelete(&g_CritSect);
208 }
209 return rc;
210}
211
212
213/**
214 * Gets the process handle for a process from g_paProcesses.
215 *
216 * @returns Process handle if found, NULL if not.
217 * @param pid The process to remove (pid).
218 */
219static HANDLE rtProcWinFindPid(RTPROCESS pid)
220{
221 HANDLE hProcess = NULL;
222
223 RTCritSectEnter(&g_CritSect);
224 uint32_t i = g_cProcesses;
225 while (i-- > 0)
226 if (g_paProcesses[i].pid == pid)
227 {
228 hProcess = g_paProcesses[i].hProcess;
229 break;
230 }
231 RTCritSectLeave(&g_CritSect);
232
233 return hProcess;
234}
235
236
237/**
238 * Removes a process from g_paProcesses and closes the process handle.
239 *
240 * @param pid The process to remove (pid).
241 */
242static void rtProcWinRemovePid(RTPROCESS pid)
243{
244 RTCritSectEnter(&g_CritSect);
245 uint32_t i = g_cProcesses;
246 while (i-- > 0)
247 if (g_paProcesses[i].pid == pid)
248 {
249 HANDLE hProcess = g_paProcesses[i].hProcess;
250
251 g_cProcesses--;
252 uint32_t cToMove = g_cProcesses - i;
253 if (cToMove)
254 memmove(&g_paProcesses[i], &g_paProcesses[i + 1], cToMove * sizeof(g_paProcesses[0]));
255
256 RTCritSectLeave(&g_CritSect);
257 CloseHandle(hProcess);
258 return;
259 }
260 RTCritSectLeave(&g_CritSect);
261}
262
263
264/**
265 * Adds a process to g_paProcesses.
266 *
267 * @returns IPRT status code.
268 * @param pid The process id.
269 * @param hProcess The process handle.
270 */
271static int rtProcWinAddPid(RTPROCESS pid, HANDLE hProcess)
272{
273 RTCritSectEnter(&g_CritSect);
274
275 uint32_t i = g_cProcesses;
276 if (i >= g_cProcessesAlloc)
277 {
278 void *pvNew = RTMemRealloc(g_paProcesses, (i + 16) * sizeof(g_paProcesses[0]));
279 if (RT_UNLIKELY(!pvNew))
280 {
281 RTCritSectLeave(&g_CritSect);
282 return VERR_NO_MEMORY;
283 }
284 g_paProcesses = (struct RTPROCWINENTRY *)pvNew;
285 g_cProcessesAlloc = i + 16;
286 }
287
288 g_paProcesses[i].pid = pid;
289 g_paProcesses[i].hProcess = hProcess;
290 g_cProcesses = i + 1;
291
292 RTCritSectLeave(&g_CritSect);
293 return VINF_SUCCESS;
294}
295
296
297/**
298 * Initialize the import APIs for run-as-user and special environment support.
299 *
300 * @returns IPRT status code.
301 * @param pvUser Ignored.
302 */
303static DECLCALLBACK(int) rtProcWinResolveOnce(void *pvUser)
304{
305 int rc;
306 RTLDRMOD hMod;
307 RT_NOREF_PV(pvUser);
308
309 /*
310 * kernel32.dll APIs introduced after NT4.
311 */
312 g_pfnCreateToolhelp32Snapshot = (PFNCREATETOOLHELP32SNAPSHOT)GetProcAddress(g_hModKernel32, "CreateToolhelp32Snapshot");
313 g_pfnProcess32First = (PFNPROCESS32FIRST )GetProcAddress(g_hModKernel32, "Process32First");
314 g_pfnProcess32FirstW = (PFNPROCESS32FIRSTW )GetProcAddress(g_hModKernel32, "Process32FirstW");
315 g_pfnProcess32Next = (PFNPROCESS32NEXT )GetProcAddress(g_hModKernel32, "Process32Next");
316 g_pfnProcess32NextW = (PFNPROCESS32NEXTW )GetProcAddress(g_hModKernel32, "Process32NextW");
317
318 /*
319 * psapi.dll APIs, if none of the above are available.
320 */
321 if ( !g_pfnCreateToolhelp32Snapshot
322 || !g_pfnProcess32First
323 || !g_pfnProcess32Next)
324 {
325 Assert(!g_pfnCreateToolhelp32Snapshot && !g_pfnProcess32First && !g_pfnProcess32Next);
326
327 rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hMod);
328 if (RT_SUCCESS(rc))
329 {
330 rc = RTLdrGetSymbol(hMod, "GetModuleBaseName", (void **)&g_pfnGetModuleBaseName);
331 AssertStmt(RT_SUCCESS(rc), g_pfnGetModuleBaseName = NULL);
332
333 rc = RTLdrGetSymbol(hMod, "EnumProcesses", (void **)&g_pfnEnumProcesses);
334 AssertStmt(RT_SUCCESS(rc), g_pfnEnumProcesses = NULL);
335
336 RTLdrClose(hMod);
337 }
338 }
339
340 /*
341 * advapi32.dll APIs.
342 */
343 rc = RTLdrLoadSystem("advapi32.dll", true /*fNoUnload*/, &hMod);
344 if (RT_SUCCESS(rc))
345 {
346 rc = RTLdrGetSymbol(hMod, "CreateProcessWithLogonW", (void **)&g_pfnCreateProcessWithLogonW);
347 AssertStmt(RT_SUCCESS(rc), g_pfnCreateProcessWithLogonW = NULL);
348
349 rc = RTLdrGetSymbol(hMod, "LsaLookupNames2", (void **)&g_pfnLsaLookupNames2);
350 AssertStmt(RT_SUCCESS(rc), g_pfnLsaLookupNames2 = NULL);
351
352 RTLdrClose(hMod);
353 }
354
355 /*
356 * userenv.dll APIs.
357 */
358 rc = RTLdrLoadSystem("userenv.dll", true /*fNoUnload*/, &hMod);
359 if (RT_SUCCESS(rc))
360 {
361 rc = RTLdrGetSymbol(hMod, "LoadUserProfileW", (void **)&g_pfnLoadUserProfileW);
362 AssertStmt(RT_SUCCESS(rc), g_pfnLoadUserProfileW = NULL);
363
364 rc = RTLdrGetSymbol(hMod, "UnloadUserProfile", (void **)&g_pfnUnloadUserProfile);
365 AssertStmt(RT_SUCCESS(rc), g_pfnUnloadUserProfile = NULL);
366
367 rc = RTLdrGetSymbol(hMod, "CreateEnvironmentBlock", (void **)&g_pfnCreateEnvironmentBlock);
368 AssertStmt(RT_SUCCESS(rc), g_pfnCreateEnvironmentBlock = NULL);
369
370 rc = RTLdrGetSymbol(hMod, "DestroyEnvironmentBlock", (void **)&g_pfnDestroyEnvironmentBlock);
371 AssertStmt(RT_SUCCESS(rc), g_pfnDestroyEnvironmentBlock = NULL);
372
373 RTLdrClose(hMod);
374 }
375
376 return VINF_SUCCESS;
377}
378
379
380RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
381{
382 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
383 NULL, NULL, NULL, /* standard handles */
384 NULL /*pszAsUser*/, NULL /* pszPassword*/,
385 pProcess);
386}
387
388
389/**
390 * Get the process token of the process indicated by @a dwPID if the @a pSid
391 * matches.
392 *
393 * @returns IPRT status code.
394 * @param dwPid The process identifier.
395 * @param pSid The secure identifier of the user.
396 * @param phToken Where to return the a duplicate of the process token
397 * handle on success. (The caller closes it.)
398 */
399static int rtProcWinGetProcessTokenHandle(DWORD dwPid, PSID pSid, PHANDLE phToken)
400{
401 AssertPtr(pSid);
402 AssertPtr(phToken);
403
404 int rc;
405 HANDLE hProc = OpenProcess(MAXIMUM_ALLOWED, TRUE, dwPid);
406 if (hProc != NULL)
407 {
408 HANDLE hTokenProc;
409 if (OpenProcessToken(hProc,
410 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE
411 | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE,
412 &hTokenProc))
413 {
414 SetLastError(NO_ERROR);
415 DWORD dwSize = 0;
416 BOOL fRc = GetTokenInformation(hTokenProc, TokenUser, NULL, 0, &dwSize);
417 DWORD dwErr = GetLastError();
418 if ( !fRc
419 && dwErr == ERROR_INSUFFICIENT_BUFFER
420 && dwSize > 0)
421 {
422 PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemTmpAllocZ(dwSize);
423 if (pTokenUser)
424 {
425 if (GetTokenInformation(hTokenProc, TokenUser, pTokenUser, dwSize, &dwSize))
426 {
427 if ( IsValidSid(pTokenUser->User.Sid)
428 && EqualSid(pTokenUser->User.Sid, pSid))
429 {
430 /*
431 * The following NT call is for v3.51 and does the equivalent of:
432 * DuplicateTokenEx(hTokenProc, MAXIMUM_ALLOWED, NULL,
433 * SecurityIdentification, TokenPrimary, phToken);
434 */
435 if (g_pfnNtDuplicateToken)
436 {
437 SECURITY_QUALITY_OF_SERVICE SecQoS;
438 SecQoS.Length = sizeof(SecQoS);
439 SecQoS.ImpersonationLevel = SecurityIdentification;
440 SecQoS.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
441 SecQoS.EffectiveOnly = FALSE;
442
443 OBJECT_ATTRIBUTES ObjAttr;
444 InitializeObjectAttributes(&ObjAttr, NULL /*Name*/, 0 /*OBJ_XXX*/, NULL /*Root*/, NULL /*SecDesc*/);
445 ObjAttr.SecurityQualityOfService = &SecQoS;
446
447 NTSTATUS rcNt = g_pfnNtDuplicateToken(hTokenProc, MAXIMUM_ALLOWED, &ObjAttr, FALSE,
448 TokenPrimary, phToken);
449 if (NT_SUCCESS(rcNt))
450 {
451 /*
452 * So we found the process instance which belongs to the user we want to
453 * to run our new process under. This duplicated token will be used for
454 * the actual CreateProcessAsUserW() call then.
455 */
456 rc = VINF_SUCCESS;
457 }
458 else
459 rc = RTErrConvertFromNtStatus(rcNt);
460
461 }
462 else
463 rc = VERR_SYMBOL_NOT_FOUND; /** @todo do we really need to duplicate the token? */
464 }
465 else
466 rc = VERR_NOT_FOUND;
467 }
468 else
469 rc = RTErrConvertFromWin32(GetLastError());
470 RTMemTmpFree(pTokenUser);
471 }
472 else
473 rc = VERR_NO_MEMORY;
474 }
475 else if (fRc || dwErr == NO_ERROR)
476 rc = VERR_IPE_UNEXPECTED_STATUS;
477 else
478 rc = RTErrConvertFromWin32(dwErr);
479 CloseHandle(hTokenProc);
480 }
481 else
482 rc = RTErrConvertFromWin32(GetLastError());
483 CloseHandle(hProc);
484 }
485 else
486 rc = RTErrConvertFromWin32(GetLastError());
487 return rc;
488}
489
490
491/**
492 * Fallback method for rtProcWinFindTokenByProcess that uses the older NT4
493 * PSAPI.DLL API.
494 *
495 * @returns Success indicator.
496 * @param papszNames The process candidates, in prioritized order.
497 * @param pSid The secure identifier of the user.
498 * @param phToken Where to return the token handle - duplicate,
499 * caller closes it on success.
500 *
501 * @remarks NT4 needs a copy of "PSAPI.dll" (redistributed by Microsoft and not
502 * part of the OS) in order to get a lookup. If we don't have this DLL
503 * we are not able to get a token and therefore no UI will be visible.
504 */
505static bool rtProcWinFindTokenByProcessAndPsApi(const char * const *papszNames, PSID pSid, PHANDLE phToken)
506{
507 /*
508 * Load PSAPI.DLL and resolve the two symbols we need.
509 */
510 if ( !g_pfnGetModuleBaseName
511 || !g_pfnEnumProcesses)
512 return false;
513
514 /*
515 * Get a list of PID. We retry if it looks like there are more PIDs
516 * to be returned than what we supplied buffer space for.
517 */
518 bool fFound = false;
519 int rc = VINF_SUCCESS;
520 DWORD cbPidsAllocated = 4096;
521 DWORD cbPidsReturned = 0; /* (MSC maybe used uninitialized) */
522 DWORD *paPids;
523 for (;;)
524 {
525 paPids = (DWORD *)RTMemTmpAlloc(cbPidsAllocated);
526 AssertBreakStmt(paPids, rc = VERR_NO_TMP_MEMORY);
527 cbPidsReturned = 0;
528 if (!g_pfnEnumProcesses(paPids, cbPidsAllocated, &cbPidsReturned))
529 {
530 rc = RTErrConvertFromWin32(GetLastError());
531 AssertMsgFailedBreak(("%Rrc\n", rc));
532 }
533 if ( cbPidsReturned < cbPidsAllocated
534 || cbPidsAllocated >= _512K)
535 break;
536 RTMemTmpFree(paPids);
537 cbPidsAllocated *= 2;
538 }
539 if (RT_SUCCESS(rc))
540 {
541 /*
542 * Search for the process.
543 *
544 * We ASSUME that the caller won't be specifying any names longer
545 * than RTPATH_MAX.
546 */
547 DWORD cbProcName = RTPATH_MAX;
548 char *pszProcName = (char *)RTMemTmpAlloc(RTPATH_MAX);
549 if (pszProcName)
550 {
551 for (size_t i = 0; papszNames[i] && !fFound; i++)
552 {
553 const DWORD cPids = cbPidsReturned / sizeof(DWORD);
554 for (DWORD iPid = 0; iPid < cPids && !fFound; iPid++)
555 {
556 HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, paPids[iPid]);
557 if (hProc)
558 {
559 *pszProcName = '\0';
560 DWORD cbRet = g_pfnGetModuleBaseName(hProc, 0 /*hModule = exe */, pszProcName, cbProcName);
561 if ( cbRet > 0
562 && _stricmp(pszProcName, papszNames[i]) == 0
563 && RT_SUCCESS(rtProcWinGetProcessTokenHandle(paPids[iPid], pSid, phToken)))
564 fFound = true;
565 CloseHandle(hProc);
566 }
567 }
568 }
569 RTMemTmpFree(pszProcName);
570 }
571 else
572 rc = VERR_NO_TMP_MEMORY;
573 }
574 RTMemTmpFree(paPids);
575
576 return fFound;
577}
578
579
580/**
581 * Finds a one of the processes in @a papszNames running with user @a pSid and
582 * returns a duplicate handle to its token.
583 *
584 * @returns Success indicator.
585 * @param papszNames The process candidates, in prioritized order.
586 * @param pSid The secure identifier of the user.
587 * @param phToken Where to return the token handle - duplicate,
588 * caller closes it on success.
589 */
590static bool rtProcWinFindTokenByProcess(const char * const *papszNames, PSID pSid, PHANDLE phToken)
591{
592 AssertPtr(papszNames);
593 AssertPtr(pSid);
594 AssertPtr(phToken);
595
596 bool fFound = false;
597
598 /*
599 * On modern systems (W2K+) try the Toolhelp32 API first; this is more stable
600 * and reliable. Fallback to EnumProcess on NT4.
601 */
602 bool fFallback = true;
603 if (g_pfnProcess32Next && g_pfnProcess32First && g_pfnCreateToolhelp32Snapshot)
604 {
605 HANDLE hSnap = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
606 Assert(hSnap != INVALID_HANDLE_VALUE);
607 if (hSnap != INVALID_HANDLE_VALUE)
608 {
609 fFallback = false;
610 for (size_t i = 0; papszNames[i] && !fFound; i++)
611 {
612 PROCESSENTRY32 ProcEntry;
613 ProcEntry.dwSize = sizeof(PROCESSENTRY32);
614/** @todo use W APIs here. */
615 if (g_pfnProcess32First(hSnap, &ProcEntry))
616 {
617 do
618 {
619 if (_stricmp(ProcEntry.szExeFile, papszNames[i]) == 0)
620 {
621 int rc = rtProcWinGetProcessTokenHandle(ProcEntry.th32ProcessID, pSid, phToken);
622 if (RT_SUCCESS(rc))
623 {
624 fFound = true;
625 break;
626 }
627 }
628 } while (g_pfnProcess32Next(hSnap, &ProcEntry));
629 }
630#ifdef RT_STRICT
631 else
632 {
633 DWORD dwErr = GetLastError();
634 AssertMsgFailed(("dwErr=%u (%x)\n", dwErr, dwErr));
635 }
636#endif
637 }
638 CloseHandle(hSnap);
639 }
640 }
641
642 /* If we couldn't take a process snapshot for some reason or another, fall
643 back on the NT4 compatible API. */
644 if (fFallback)
645 fFound = rtProcWinFindTokenByProcessAndPsApi(papszNames, pSid, phToken);
646 return fFound;
647}
648
649
650/**
651 * Logs on a specified user and returns its primary token.
652 *
653 * @returns IPRT status code.
654 * @param pwszUser User name. A domain name can be specified (as part of a UPN, User Principal Name),
655 * e.g. "[email protected]".
656 * @param pwszPassword Password.
657 * @param phToken Pointer to store the logon token.
658 */
659static int rtProcWinUserLogon(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, HANDLE *phToken)
660{
661 AssertPtrReturn(pwszUser, VERR_INVALID_POINTER);
662 AssertPtrReturn(pwszPassword, VERR_INVALID_POINTER);
663 AssertPtrReturn(phToken, VERR_INVALID_POINTER);
664
665 /*
666 * Because we have to deal with http://support.microsoft.com/kb/245683
667 * for NULL domain names when running on NT4 here, pass an empty string if so.
668 * However, passing FQDNs should work!
669 *
670 * The SE_TCB_NAME (Policy: Act as part of the operating system) right
671 * is required on older windows versions (NT4, W2K, possibly XP).
672 */
673 PCRTUTF16 pwszDomainNone = g_enmWinVer < kRTWinOSType_2K ? L"" /* NT4 and older */ : NULL /* Windows 2000 and up */;
674 BOOL fRc = LogonUserW(pwszUser,
675 /* The domain always is passed as part of the UPN (user name). */
676 pwszDomainNone,
677 pwszPassword,
678 LOGON32_LOGON_INTERACTIVE,
679 LOGON32_PROVIDER_DEFAULT,
680 phToken);
681 if (fRc)
682 return VINF_SUCCESS;
683
684 DWORD dwErr = GetLastError();
685 int rc = dwErr == ERROR_PRIVILEGE_NOT_HELD ? VERR_PROC_TCB_PRIV_NOT_HELD : RTErrConvertFromWin32(dwErr);
686 if (rc == VERR_UNRESOLVED_ERROR)
687 LogRelFunc(("dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc));
688 return rc;
689}
690
691
692/**
693 * Returns the environment to use for the child process.
694 *
695 * This implements the RTPROC_FLAGS_ENV_CHANGE_RECORD and environment related
696 * parts of RTPROC_FLAGS_PROFILE.
697 *
698 * @returns IPRT status code.
699 * @param hToken The user token to use if RTPROC_FLAGS_PROFILE is given.
700 * The caller must have loaded profile for this.
701 * @param hEnv The environment passed in by the RTProcCreateEx caller.
702 * @param fFlags The process creation flags passed in by the
703 * RTProcCreateEx caller (RTPROC_FLAGS_XXX).
704 * @param phEnv Where to return the environment to use. This can either
705 * be a newly created environment block or @a hEnv. In the
706 * former case, the caller must destroy it.
707 */
708static int rtProcWinCreateEnvFromToken(HANDLE hToken, RTENV hEnv, uint32_t fFlags, PRTENV phEnv)
709{
710 int rc;
711
712 /*
713 * Query the environment from the user profile associated with the token if
714 * the caller has specified it directly or indirectly.
715 */
716 if ( (fFlags & RTPROC_FLAGS_PROFILE)
717 && ( hEnv == RTENV_DEFAULT
718 || (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)) )
719 {
720 if (g_pfnCreateEnvironmentBlock && g_pfnDestroyEnvironmentBlock)
721 {
722 LPVOID pvEnvBlockProfile = NULL;
723 if (g_pfnCreateEnvironmentBlock(&pvEnvBlockProfile, hToken, FALSE /* Don't inherit from parent. */))
724 {
725 rc = RTEnvCloneUtf16Block(phEnv, (PCRTUTF16)pvEnvBlockProfile, 0 /*fFlags*/);
726 if ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
727 && RT_SUCCESS(rc)
728 && hEnv != RTENV_DEFAULT)
729 {
730 rc = RTEnvApplyChanges(*phEnv, hEnv);
731 if (RT_FAILURE(rc))
732 RTEnvDestroy(*phEnv);
733 }
734 g_pfnDestroyEnvironmentBlock(pvEnvBlockProfile);
735 }
736 else
737 rc = RTErrConvertFromWin32(GetLastError());
738 }
739 else
740 rc = VERR_SYMBOL_NOT_FOUND;
741 }
742 /*
743 * We we've got an incoming change record, combine it with the default environment.
744 */
745 else if (hEnv != RTENV_DEFAULT && (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD))
746 {
747 rc = RTEnvClone(phEnv, RTENV_DEFAULT);
748 if (RT_SUCCESS(rc))
749 {
750 rc = RTEnvApplyChanges(*phEnv, hEnv);
751 if (RT_FAILURE(rc))
752 RTEnvDestroy(*phEnv);
753 }
754 }
755 /*
756 * Otherwise we can return the incoming environment directly.
757 */
758 else
759 {
760 *phEnv = hEnv;
761 rc = VINF_SUCCESS;
762 }
763
764 return rc;
765}
766
767
768/**
769 * Figures which privilege we're missing for success application of
770 * CreateProcessAsUserW.
771 *
772 * @returns IPRT error status.
773 */
774static int rtProcWinFigureWhichPrivilegeNotHeld2(void)
775{
776 HANDLE hToken;
777 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
778 {
779 static struct
780 {
781 const char *pszName;
782 int rc;
783 } const s_aPrivileges[] =
784 {
785 { SE_TCB_NAME, VERR_PROC_TCB_PRIV_NOT_HELD },
786 { SE_ASSIGNPRIMARYTOKEN_NAME, VERR_PROC_APT_PRIV_NOT_HELD },
787 { SE_INCREASE_QUOTA_NAME, VERR_PROC_IQ_PRIV_NOT_HELD },
788 };
789 for (uint32_t i = 0; i < RT_ELEMENTS(s_aPrivileges); i++)
790 {
791 union
792 {
793 TOKEN_PRIVILEGES TokPriv;
794 char abAlloced[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
795 } uNew, uOld;
796 uNew.TokPriv.PrivilegeCount = 1;
797 uNew.TokPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
798 AssertContinue(LookupPrivilegeValue(NULL, s_aPrivileges[i].pszName, &uNew.TokPriv.Privileges[0].Luid));
799 uOld = uNew;
800 SetLastError(NO_ERROR);
801 DWORD cbActual = RT_OFFSETOF(TOKEN_PRIVILEGES, Privileges[1]);
802 AdjustTokenPrivileges(hToken, FALSE /*fDisableAllPrivileges*/, &uNew.TokPriv, cbActual, &uOld.TokPriv, &cbActual);
803 if (GetLastError() != NO_ERROR)
804 {
805 CloseHandle(hToken);
806 return s_aPrivileges[i].rc;
807 }
808 if (uOld.TokPriv.Privileges[0].Attributes == 0)
809 AdjustTokenPrivileges(hToken, FALSE /*fDisableAllPrivileges*/, &uOld.TokPriv, 0, NULL, NULL);
810 }
811 AssertFailed();
812 CloseHandle(hToken);
813 }
814 else
815 AssertFailed();
816 return VERR_PRIVILEGE_NOT_HELD;
817}
818
819#if 0 /* debug code */
820
821static char *rtProcWinSidToString(char *psz, PSID pSid)
822{
823 char *pszRet = psz;
824
825 *psz++ = 'S';
826 *psz++ = '-';
827 *psz++ = '1';
828 *psz++ = '-';
829
830 PISID pISid = (PISID)pSid;
831
832 psz += RTStrFormatU32(psz, 32, RT_MAKE_U32_FROM_U8(pISid->IdentifierAuthority.Value[5],
833 pISid->IdentifierAuthority.Value[4],
834 pISid->IdentifierAuthority.Value[3],
835 pISid->IdentifierAuthority.Value[2]),
836 10, 0, 0, 0);
837 for (unsigned i = 0; i < pISid->SubAuthorityCount; i++)
838 {
839 *psz++ = '-';
840 psz += RTStrFormatU32(psz, 32, pISid->SubAuthority[i], 10, 0, 0, 0);
841 }
842 *psz++ = '\0';
843 return pszRet;
844}
845
846static void rtProcWinLogAcl(PACL pAcl)
847{
848 if (!pAcl)
849 RTAssertMsg2("ACL is NULL\n");
850 else
851 {
852 RTAssertMsg2("AceCount=%d AclSize=%#x AclRevision=%d\n", pAcl->AceCount, pAcl->AclSize, pAcl->AclRevision);
853 for (uint32_t i = 0; i < pAcl->AceCount; i++)
854 {
855 PACE_HEADER pAceHdr = NULL;
856 if (GetAce(pAcl, i, (PVOID *)&pAceHdr))
857 {
858 RTAssertMsg2(" ACE[%u]: Flags=%#x Type=%#x Size=%#x", i, pAceHdr->AceFlags, pAceHdr->AceType, pAceHdr->AceSize);
859 char szTmp[256];
860 if (pAceHdr->AceType == ACCESS_ALLOWED_ACE_TYPE)
861 RTAssertMsg2(" Mask=%#x %s\n", ((ACCESS_ALLOWED_ACE *)pAceHdr)->Mask,
862 rtProcWinSidToString(szTmp, &((ACCESS_ALLOWED_ACE *)pAceHdr)->SidStart));
863 else
864 RTAssertMsg2(" ACE[%u]: Flags=%#x Type=%#x Size=%#x\n", i, pAceHdr->AceFlags, pAceHdr->AceType, pAceHdr->AceSize);
865 }
866 }
867 }
868}
869
870static bool rtProcWinLogSecAttr(HANDLE hUserObj)
871{
872 /*
873 * Get the security descriptor for the user interface object.
874 */
875 uint32_t cbSecDesc = _64K;
876 PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc);
877 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION;
878 DWORD cbNeeded;
879 AssertReturn(pSecDesc, false);
880 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded))
881 {
882 RTMemTmpFree(pSecDesc);
883 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, false);
884 cbSecDesc = cbNeeded + 128;
885 pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc);
886 AssertReturn(pSecDesc, false);
887 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded))
888 {
889 RTMemTmpFree(pSecDesc);
890 AssertFailedReturn(false);
891 }
892 }
893
894 /*
895 * Get the discretionary access control list (if we have one).
896 */
897 BOOL fDaclDefaulted;
898 BOOL fDaclPresent;
899 PACL pDacl;
900 if (GetSecurityDescriptorDacl(pSecDesc, &fDaclPresent, &pDacl, &fDaclDefaulted))
901 rtProcWinLogAcl(pDacl);
902 else
903 RTAssertMsg2("GetSecurityDescriptorDacl failed\n");
904
905 RTMemFree(pSecDesc);
906 return true;
907}
908
909#endif /* debug */
910
911/**
912 * Get the user SID from a token.
913 *
914 * @returns Pointer to the SID on success. Free by calling RTMemFree.
915 * @param hToken The token..
916 */
917static PSID rtProcWinGetTokenUserSid(HANDLE hToken)
918{
919 /*
920 * Get the groups associated with the token. We just try a size first then
921 * reallocates if it's insufficient.
922 */
923 DWORD cbUser = _1K;
924 PTOKEN_USER pUser = (PTOKEN_USER)RTMemTmpAlloc(cbUser);
925 AssertReturn(pUser, NULL);
926 DWORD cbNeeded = 0;
927 if (!GetTokenInformation(hToken, TokenUser, pUser, cbUser, &cbNeeded))
928 {
929 RTMemTmpFree(pUser);
930 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL);
931 cbUser = cbNeeded + 128;
932 pUser = (PTOKEN_USER)RTMemTmpAlloc(cbUser);
933 AssertReturn(pUser, NULL);
934 if (!GetTokenInformation(hToken, TokenUser, pUser, cbUser, &cbNeeded))
935 {
936 RTMemTmpFree(pUser);
937 AssertFailedReturn(NULL);
938 }
939 }
940
941 DWORD cbSid = GetLengthSid(pUser->User.Sid);
942 PSID pSidRet = RTMemDup(pUser->User.Sid, cbSid);
943 Assert(pSidRet);
944 RTMemTmpFree(pUser);
945 return pSidRet;
946}
947
948
949#if 0 /* not used */
950/**
951 * Get the login SID from a token.
952 *
953 * @returns Pointer to the SID on success. Free by calling RTMemFree.
954 * @param hToken The token..
955 */
956static PSID rtProcWinGetTokenLogonSid(HANDLE hToken)
957{
958 /*
959 * Get the groups associated with the token. We just try a size first then
960 * reallocates if it's insufficient.
961 */
962 DWORD cbGroups = _1K;
963 PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbGroups);
964 AssertReturn(pGroups, NULL);
965 DWORD cbNeeded = 0;
966 if (!GetTokenInformation(hToken, TokenGroups, pGroups, cbGroups, &cbNeeded))
967 {
968 RTMemTmpFree(pGroups);
969 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL);
970 cbGroups = cbNeeded + 128;
971 pGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbGroups);
972 AssertReturn(pGroups, NULL);
973 if (!GetTokenInformation(hToken, TokenGroups, pGroups, cbGroups, &cbNeeded))
974 {
975 RTMemTmpFree(pGroups);
976 AssertFailedReturn(NULL);
977 }
978 }
979
980 /*
981 * Locate the logon sid.
982 */
983 PSID pSidRet = NULL;
984 uint32_t i = pGroups->GroupCount;
985 while (i-- > 0)
986 if ((pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
987 {
988 DWORD cbSid = GetLengthSid(pGroups->Groups[i].Sid);
989 pSidRet = RTMemDup(pGroups->Groups[i].Sid, cbSid);
990 break;
991 }
992
993 RTMemTmpFree(pGroups);
994 Assert(pSidRet);
995 return pSidRet;
996}
997#endif /* unused */
998
999
1000/**
1001 * Retrieves the DACL security descriptor of the give GUI object.
1002 *
1003 * @returns Pointer to the security descriptor.
1004 * @param hUserObj The GUI object handle.
1005 * @param pcbSecDesc Where to return the size of the security descriptor.
1006 * @param ppDacl Where to return the DACL pointer.
1007 * @param pfDaclPresent Where to return the DACL-present indicator.
1008 * @param pDaclSizeInfo Where to return the DACL size information.
1009 */
1010static PSECURITY_DESCRIPTOR rtProcWinGetUserObjDacl(HANDLE hUserObj, uint32_t *pcbSecDesc, PACL *ppDacl,
1011 BOOL *pfDaclPresent, ACL_SIZE_INFORMATION *pDaclSizeInfo)
1012{
1013 /*
1014 * Get the security descriptor for the user interface object.
1015 */
1016 uint32_t cbSecDesc = _1K;
1017 PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc);
1018 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION;
1019 DWORD cbNeeded;
1020 AssertReturn(pSecDesc, NULL);
1021 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded))
1022 {
1023 RTMemTmpFree(pSecDesc);
1024 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL);
1025 cbSecDesc = cbNeeded + 128;
1026 pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc);
1027 AssertReturn(pSecDesc, NULL);
1028 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded))
1029 {
1030 RTMemTmpFree(pSecDesc);
1031 AssertFailedReturn(NULL);
1032 }
1033 }
1034 *pcbSecDesc = cbNeeded;
1035
1036 /*
1037 * Get the discretionary access control list (if we have one).
1038 */
1039 BOOL fDaclDefaulted;
1040 if (GetSecurityDescriptorDacl(pSecDesc, pfDaclPresent, ppDacl, &fDaclDefaulted))
1041 {
1042 RT_ZERO(*pDaclSizeInfo);
1043 pDaclSizeInfo->AclBytesInUse = sizeof(ACL);
1044 if ( !*ppDacl
1045 || GetAclInformation(*ppDacl, pDaclSizeInfo, sizeof(*pDaclSizeInfo), AclSizeInformation))
1046 return pSecDesc;
1047 AssertFailed();
1048 }
1049 else
1050 AssertFailed();
1051 RTMemTmpFree(pSecDesc);
1052 return NULL;
1053}
1054
1055
1056/**
1057 * Copy ACEs from one ACL to another.
1058 *
1059 * @returns true on success, false on failure.
1060 * @param pDst The destination ACL.
1061 * @param pSrc The source ACL.
1062 * @param cAces The number of ACEs to copy.
1063 */
1064static bool rtProcWinCopyAces(PACL pDst, PACL pSrc, uint32_t cAces)
1065{
1066 for (uint32_t i = 0; i < cAces; i++)
1067 {
1068 PACE_HEADER pAceHdr;
1069 AssertReturn(GetAce(pSrc, i, (PVOID *)&pAceHdr), false);
1070 AssertReturn(AddAce(pDst, ACL_REVISION, MAXDWORD, pAceHdr, pAceHdr->AceSize), false);
1071 }
1072 return true;
1073}
1074
1075
1076/**
1077 * Adds an access-allowed access control entry to an ACL.
1078 *
1079 * @returns true on success, false on failure.
1080 * @param pDstAcl The ACL.
1081 * @param fAceFlags The ACE flags.
1082 * @param fMask The ACE access mask.
1083 * @param pSid The SID to go with the ACE.
1084 * @param cbSid The size of the SID.
1085 */
1086static bool rtProcWinAddAccessAllowedAce(PACL pDstAcl, uint32_t fAceFlags, uint32_t fMask, PSID pSid, uint32_t cbSid)
1087{
1088 struct
1089 {
1090 ACCESS_ALLOWED_ACE Core;
1091 DWORD abPadding[128]; /* More than enough, AFAIK. */
1092 } AceBuf;
1093 RT_ZERO(AceBuf);
1094 uint32_t const cbAllowedAce = RT_OFFSETOF(ACCESS_ALLOWED_ACE, SidStart) + cbSid;
1095 AssertReturn(cbAllowedAce <= sizeof(AceBuf), false);
1096
1097 AceBuf.Core.Header.AceSize = cbAllowedAce;
1098 AceBuf.Core.Header.AceType = ACCESS_ALLOWED_ACE_TYPE;
1099 AceBuf.Core.Header.AceFlags = fAceFlags;
1100 AceBuf.Core.Mask = fMask;
1101 AssertReturn(CopySid(cbSid, &AceBuf.Core.SidStart, pSid), false);
1102
1103 uint32_t i = pDstAcl->AceCount;
1104 while (i-- > 0)
1105 {
1106 PACE_HEADER pAceHdr;
1107 AssertContinue(GetAce(pDstAcl, i, (PVOID *)&pAceHdr));
1108 if ( pAceHdr->AceSize == cbAllowedAce
1109 && memcmp(pAceHdr, &AceBuf.Core, cbAllowedAce) == 0)
1110 return true;
1111
1112 }
1113 AssertMsgReturn(AddAce(pDstAcl, ACL_REVISION, MAXDWORD, &AceBuf.Core, cbAllowedAce), ("%u\n", GetLastError()), false);
1114 return true;
1115}
1116
1117
1118/** All window station rights we know about */
1119#define MY_WINSTATION_ALL_RIGHTS ( WINSTA_ACCESSCLIPBOARD | WINSTA_ACCESSGLOBALATOMS | WINSTA_CREATEDESKTOP \
1120 | WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE | WINSTA_EXITWINDOWS | WINSTA_READATTRIBUTES \
1121 | WINSTA_READSCREEN | WINSTA_WRITEATTRIBUTES | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER )
1122/** All desktop rights we know about */
1123#define MY_DESKTOP_ALL_RIGHTS ( DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL \
1124 | DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | DESKTOP_READOBJECTS \
1125 | DESKTOP_SWITCHDESKTOP | DESKTOP_WRITEOBJECTS | DELETE | READ_CONTROL | WRITE_DAC \
1126 | WRITE_OWNER )
1127/** Generic rights. */
1128#define MY_GENERIC_ALL_RIGHTS ( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL )
1129
1130
1131/**
1132 * Grants the given SID full access to the given window station.
1133 *
1134 * @returns true on success, false on failure.
1135 * @param hWinStation The window station.
1136 * @param pSid The SID.
1137 */
1138static bool rtProcWinAddSidToWinStation(HWINSTA hWinStation, PSID pSid)
1139{
1140 bool fRet = false;
1141
1142 /*
1143 * Get the current DACL.
1144 */
1145 uint32_t cbSecDesc;
1146 PACL pDacl;
1147 ACL_SIZE_INFORMATION DaclSizeInfo;
1148 BOOL fDaclPresent;
1149 PSECURITY_DESCRIPTOR pSecDesc = rtProcWinGetUserObjDacl(hWinStation, &cbSecDesc, &pDacl, &fDaclPresent, &DaclSizeInfo);
1150 if (pSecDesc)
1151 {
1152 /*
1153 * Create a new DACL. This will contain two extra ACEs.
1154 */
1155 PSECURITY_DESCRIPTOR pNewSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc);
1156 if ( pNewSecDesc
1157 && InitializeSecurityDescriptor(pNewSecDesc, SECURITY_DESCRIPTOR_REVISION))
1158 {
1159 uint32_t const cbSid = GetLengthSid(pSid);
1160 uint32_t const cbNewDacl = DaclSizeInfo.AclBytesInUse + (sizeof(ACCESS_ALLOWED_ACE) + cbSid) * 2;
1161 PACL pNewDacl = (PACL)RTMemTmpAlloc(cbNewDacl);
1162 if ( pNewDacl
1163 && InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION)
1164 && rtProcWinCopyAces(pNewDacl, pDacl, fDaclPresent ? DaclSizeInfo.AceCount : 0))
1165 {
1166 /*
1167 * Add the two new SID ACEs.
1168 */
1169 if ( rtProcWinAddAccessAllowedAce(pNewDacl, CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE,
1170 MY_GENERIC_ALL_RIGHTS, pSid, cbSid)
1171 && rtProcWinAddAccessAllowedAce(pNewDacl, NO_PROPAGATE_INHERIT_ACE, MY_WINSTATION_ALL_RIGHTS, pSid, cbSid))
1172 {
1173 /*
1174 * Now mate the new DECL with the security descriptor and set it.
1175 */
1176 if (SetSecurityDescriptorDacl(pNewSecDesc, TRUE /*fDaclPresent*/, pNewDacl, FALSE /*fDaclDefaulted*/))
1177 {
1178 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION;
1179 if (SetUserObjectSecurity(hWinStation, &SecInfo, pNewSecDesc))
1180 fRet = true;
1181 else
1182 AssertFailed();
1183 }
1184 else
1185 AssertFailed();
1186 }
1187 else
1188 AssertFailed();
1189 }
1190 else
1191 AssertFailed();
1192 RTMemTmpFree(pNewDacl);
1193 }
1194 else
1195 AssertFailed();
1196 RTMemTmpFree(pNewSecDesc);
1197 RTMemTmpFree(pSecDesc);
1198 }
1199 return fRet;
1200}
1201
1202
1203/**
1204 * Grants the given SID full access to the given desktop.
1205 *
1206 * @returns true on success, false on failure.
1207 * @param hDesktop The desktop handle.
1208 * @param pSid The SID.
1209 */
1210static bool rtProcWinAddSidToDesktop(HDESK hDesktop, PSID pSid)
1211{
1212 bool fRet = false;
1213
1214 /*
1215 * Get the current DACL.
1216 */
1217 uint32_t cbSecDesc;
1218 PACL pDacl;
1219 ACL_SIZE_INFORMATION DaclSizeInfo;
1220 BOOL fDaclPresent;
1221 PSECURITY_DESCRIPTOR pSecDesc = rtProcWinGetUserObjDacl(hDesktop, &cbSecDesc, &pDacl, &fDaclPresent, &DaclSizeInfo);
1222 if (pSecDesc)
1223 {
1224 /*
1225 * Create a new DACL. This will contain one extra ACE.
1226 */
1227 PSECURITY_DESCRIPTOR pNewSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc);
1228 if ( pNewSecDesc
1229 && InitializeSecurityDescriptor(pNewSecDesc, SECURITY_DESCRIPTOR_REVISION))
1230 {
1231 uint32_t const cbSid = GetLengthSid(pSid);
1232 uint32_t const cbNewDacl = DaclSizeInfo.AclBytesInUse + (sizeof(ACCESS_ALLOWED_ACE) + cbSid) * 1;
1233 PACL pNewDacl = (PACL)RTMemTmpAlloc(cbNewDacl);
1234 if ( pNewDacl
1235 && InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION)
1236 && rtProcWinCopyAces(pNewDacl, pDacl, fDaclPresent ? DaclSizeInfo.AceCount : 0))
1237 {
1238 /*
1239 * Add the new SID ACE.
1240 */
1241 if (rtProcWinAddAccessAllowedAce(pNewDacl, 0 /*fAceFlags*/, MY_DESKTOP_ALL_RIGHTS, pSid, cbSid))
1242 {
1243 /*
1244 * Now mate the new DECL with the security descriptor and set it.
1245 */
1246 if (SetSecurityDescriptorDacl(pNewSecDesc, TRUE /*fDaclPresent*/, pNewDacl, FALSE /*fDaclDefaulted*/))
1247 {
1248 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION;
1249 if (SetUserObjectSecurity(hDesktop, &SecInfo, pNewSecDesc))
1250 fRet = true;
1251 else
1252 AssertFailed();
1253 }
1254 else
1255 AssertFailed();
1256 }
1257 else
1258 AssertFailed();
1259 }
1260 else
1261 AssertFailed();
1262 RTMemTmpFree(pNewDacl);
1263 }
1264 else
1265 AssertFailed();
1266 RTMemTmpFree(pNewSecDesc);
1267 RTMemTmpFree(pSecDesc);
1268 }
1269 return fRet;
1270}
1271
1272
1273/**
1274 * Preps the window station and desktop for the new app.
1275 *
1276 * EXPERIMENTAL. Thus no return code.
1277 *
1278 * @param hTokenToUse The access token of the new process.
1279 * @param pStartupInfo The startup info (we'll change lpDesktop, maybe).
1280 * @param phWinStationOld Where to return an window station handle to restore.
1281 * Pass this to SetProcessWindowStation if not NULL.
1282 */
1283static void rtProcWinStationPrep(HANDLE hTokenToUse, STARTUPINFOW *pStartupInfo, HWINSTA *phWinStationOld)
1284{
1285 /** @todo Always mess with the interactive one? Maybe it's not there... */
1286 *phWinStationOld = GetProcessWindowStation();
1287 HWINSTA hWinStation0 = OpenWindowStationW(L"winsta0", FALSE /*fInherit*/, READ_CONTROL | WRITE_DAC);
1288 if (hWinStation0)
1289 {
1290 if (SetProcessWindowStation(hWinStation0))
1291 {
1292 HDESK hDesktop = OpenDesktop("default", 0 /*fFlags*/, FALSE /*fInherit*/,
1293 READ_CONTROL | WRITE_DAC | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS);
1294 if (hDesktop)
1295 {
1296 /*PSID pSid = rtProcWinGetTokenLogonSid(hTokenToUse); - Better to use the user SID. Avoid overflowing the ACL. */
1297 PSID pSid = rtProcWinGetTokenUserSid(hTokenToUse);
1298 if (pSid)
1299 {
1300 if ( rtProcWinAddSidToWinStation(hWinStation0, pSid)
1301 && rtProcWinAddSidToDesktop(hDesktop, pSid))
1302 {
1303 pStartupInfo->lpDesktop = L"winsta0\\default";
1304 }
1305 RTMemFree(pSid);
1306 }
1307 CloseDesktop(hDesktop);
1308 }
1309 else
1310 AssertFailed();
1311 }
1312 else
1313 AssertFailed();
1314 CloseWindowStation(hWinStation0);
1315 }
1316 else
1317 AssertFailed();
1318}
1319
1320
1321/**
1322 * Extracts the user name + domain from a given UPN (User Principal Name, "[email protected]") or
1323 * Down-Level Logon Name format ("example.com\\joedoe") string.
1324 *
1325 * @return IPRT status code.
1326 * @param pwszString Pointer to string to extract the account info from.
1327 * @param pAccountInfo Where to store the parsed account info.
1328 * Must be free'd with rtProcWinFreeAccountInfo().
1329 */
1330static int rtProcWinParseAccountInfo(PRTUTF16 pwszString, PRTPROCWINACCOUNTINFO pAccountInfo)
1331{
1332 AssertPtrReturn(pwszString, VERR_INVALID_POINTER);
1333 AssertPtrReturn(pAccountInfo, VERR_INVALID_POINTER);
1334
1335 /*
1336 * Note: UPN handling is defined in RFC 822. We only implement very rudimentary parsing for the user
1337 * name and domain fields though.
1338 */
1339 char *pszString;
1340 int rc = RTUtf16ToUtf8(pwszString, &pszString);
1341 if (RT_SUCCESS(rc))
1342 {
1343 do
1344 {
1345 /* UPN or FQDN handling needed? */
1346 /** @todo Add more validation here as needed. Regular expressions would be nice. */
1347 char *pszDelim = strchr(pszString, '@');
1348 if (pszDelim) /* UPN name? */
1349 {
1350 rc = RTStrToUtf16Ex(pszString, pszDelim - pszString, &pAccountInfo->pwszUserName, 0, NULL);
1351 if (RT_FAILURE(rc))
1352 break;
1353
1354 rc = RTStrToUtf16Ex(pszDelim + 1, RTSTR_MAX, &pAccountInfo->pwszDomain, 0, NULL);
1355 if (RT_FAILURE(rc))
1356 break;
1357 }
1358 else if (pszDelim = strchr(pszString, '\\')) /* FQDN name? */
1359 {
1360 rc = RTStrToUtf16Ex(pszString, pszDelim - pszString, &pAccountInfo->pwszDomain, 0, NULL);
1361 if (RT_FAILURE(rc))
1362 break;
1363
1364 rc = RTStrToUtf16Ex(pszDelim + 1, RTSTR_MAX, &pAccountInfo->pwszUserName, 0, NULL);
1365 if (RT_FAILURE(rc))
1366 break;
1367 }
1368 else
1369 rc = VERR_NOT_SUPPORTED;
1370
1371 } while (0);
1372
1373 RTStrFree(pszString);
1374 }
1375
1376#ifdef DEBUG
1377 LogRelFunc(("Name : %ls\n", pAccountInfo->pwszUserName));
1378 LogRelFunc(("Domain: %ls\n", pAccountInfo->pwszDomain));
1379#endif
1380
1381 if (RT_FAILURE(rc))
1382 LogRelFunc(("Parsing \"%ls\" failed with rc=%Rrc\n", pwszString, rc));
1383 return rc;
1384}
1385
1386
1387static void rtProcWinFreeAccountInfo(PRTPROCWINACCOUNTINFO pAccountInfo)
1388{
1389 if (!pAccountInfo)
1390 return;
1391
1392 if (pAccountInfo->pwszUserName)
1393 {
1394 RTUtf16Free(pAccountInfo->pwszUserName);
1395 pAccountInfo->pwszUserName = NULL;
1396 }
1397
1398 if (pAccountInfo->pwszDomain)
1399 {
1400 RTUtf16Free(pAccountInfo->pwszDomain);
1401 pAccountInfo->pwszDomain = NULL;
1402 }
1403}
1404
1405
1406/**
1407 * Method \#2.
1408 */
1409static int rtProcWinCreateAsUser2(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 *ppwszExec, PRTUTF16 pwszCmdLine,
1410 RTENV hEnv, DWORD dwCreationFlags,
1411 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo,
1412 uint32_t fFlags, const char *pszExec)
1413{
1414 /*
1415 * So if we want to start a process from a service (RTPROC_FLAGS_SERVICE),
1416 * we have to do the following:
1417 * - Check the credentials supplied and get the user SID.
1418 * - If valid get the correct Explorer/VBoxTray instance corresponding to that
1419 * user. This of course is only possible if that user is logged in (over
1420 * physical console or terminal services).
1421 * - If we found the user's Explorer/VBoxTray app, use and modify the token to
1422 * use it in order to allow the newly started process to access the user's
1423 * desktop. If there's no Explorer/VBoxTray app we cannot display the started
1424 * process (but run it without UI).
1425 *
1426 * The following restrictions apply:
1427 * - A process only can show its UI when the user the process should run
1428 * under is logged in (has a desktop).
1429 * - We do not want to display a process of user A run on the desktop
1430 * of user B on multi session systems.
1431 *
1432 * The following rights are needed in order to use LogonUserW and
1433 * CreateProcessAsUserW, so the local policy has to be modified to:
1434 * - SE_TCB_NAME = Act as part of the operating system
1435 * - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a (process) token object
1436 * - SE_INCREASE_QUOTA_NAME = Increase quotas
1437 *
1438 * We may fail here with ERROR_PRIVILEGE_NOT_HELD.
1439 */
1440 DWORD dwErr = NO_ERROR;
1441 HANDLE hTokenLogon = INVALID_HANDLE_VALUE;
1442 int rc = rtProcWinUserLogon(pwszUser, pwszPassword, &hTokenLogon);
1443 if (RT_SUCCESS(rc))
1444 {
1445 DWORD fRc;
1446 bool fFound = false;
1447 HANDLE hTokenUserDesktop = INVALID_HANDLE_VALUE;
1448
1449 /*
1450 * If the SERVICE flag is specified, we do something rather ugly to
1451 * make things work at all. We search for a known desktop process
1452 * belonging to the user, grab its token and use it for launching
1453 * the new process. That way the process will have desktop access.
1454 */
1455 if (fFlags & RTPROC_FLAGS_SERVICE)
1456 {
1457 PSID pSid = NULL;
1458
1459 /* Try query the SID and domain sizes first. */
1460 DWORD cbSid = 0; /* Must be zero to query size! */
1461 DWORD cwcDomain = 0;
1462 SID_NAME_USE SidNameUse = SidTypeUser;
1463 fRc = LookupAccountNameW(NULL, pwszUser, NULL, &cbSid, NULL, &cwcDomain, &SidNameUse);
1464 if (!fRc)
1465 {
1466 dwErr = GetLastError();
1467
1468 /*
1469 * The errors ERROR_TRUSTED_DOMAIN_FAILURE and ERROR_TRUSTED_RELATIONSHIP_FAILURE
1470 * can happen if an ADC (Active Domain Controller) is offline or not reachable.
1471 *
1472 * Try to handle these errors gracefully by asking the local LSA cache of the
1473 * client OS instead then. For this to work, the desired user must have at
1474 * least logged in once at that client -- otherwise there will be no cached
1475 * authentication available and this fallback will fail.
1476 */
1477 if ( g_pfnLsaLookupNames2 /* >= Windows XP */
1478 && ( dwErr == ERROR_TRUSTED_DOMAIN_FAILURE
1479 || dwErr == ERROR_TRUSTED_RELATIONSHIP_FAILURE))
1480 {
1481 LSA_OBJECT_ATTRIBUTES objAttr;
1482 RT_ZERO(objAttr);
1483 objAttr.Length = sizeof(LSA_OBJECT_ATTRIBUTES);
1484
1485 LSA_HANDLE lsahPolicy;
1486 NTSTATUS ntSts = LsaOpenPolicy(NULL, &objAttr, POLICY_LOOKUP_NAMES, &lsahPolicy);
1487 if (ntSts == STATUS_SUCCESS)
1488 {
1489 RTPROCWINACCOUNTINFO accountInfo;
1490 RT_ZERO(accountInfo);
1491 rc = rtProcWinParseAccountInfo(pwszUser, &accountInfo);
1492 AssertRC(rc);
1493 AssertPtr(accountInfo.pwszUserName);
1494
1495 LSA_UNICODE_STRING lsaUser;
1496 lsaUser.Buffer = accountInfo.pwszUserName;
1497 lsaUser.Length = (USHORT)(RTUtf16Len(accountInfo.pwszUserName) * sizeof(WCHAR));
1498 lsaUser.MaximumLength = lsaUser.Length;
1499
1500 PLSA_REFERENCED_DOMAIN_LIST pDomainList = NULL;
1501 PLSA_TRANSLATED_SID2 pTranslatedSids = NULL;
1502 ntSts = g_pfnLsaLookupNames2(lsahPolicy, 0 /* Flags */,
1503 1 /* Number of users to lookup */,
1504 &lsaUser, &pDomainList, &pTranslatedSids);
1505 if (ntSts == STATUS_SUCCESS)
1506 {
1507 AssertPtr(pDomainList);
1508 AssertPtr(pTranslatedSids);
1509# ifdef DEBUG
1510 LogRelFunc(("LsaLookupNames2: cDomains=%u, DomainIndex=%ld, SidUse=%ld\n",
1511 pDomainList->Entries, pTranslatedSids[0].DomainIndex, pTranslatedSids[0].Use));
1512# endif
1513 Assert(pTranslatedSids[0].Use == SidTypeUser);
1514
1515 if (pDomainList->Entries)
1516 {
1517 AssertPtr(pDomainList->Domains);
1518 LogRelFunc(("LsaLookupNames2: Domain=%ls\n",
1519 pDomainList->Domains[pTranslatedSids[0].DomainIndex].Name.Buffer));
1520 }
1521
1522 cbSid = GetLengthSid(pTranslatedSids->Sid) + 16;
1523 Assert(cbSid);
1524 pSid = (PSID)RTMemAllocZ(cbSid);
1525 if (!CopySid(cbSid, pSid, pTranslatedSids->Sid))
1526 {
1527 dwErr = GetLastError();
1528 LogRelFunc(("CopySid failed with: %ld\n", dwErr));
1529 rc = dwErr != NO_ERROR ? RTErrConvertFromWin32(dwErr) : VERR_INTERNAL_ERROR_2;
1530 }
1531 }
1532 else
1533 {
1534 dwErr = LsaNtStatusToWinError(ntSts);
1535 LogRelFunc(("LsaLookupNames2 failed with: %ld\n", dwErr));
1536 rc = dwErr != NO_ERROR ? RTErrConvertFromWin32(dwErr) : VERR_INTERNAL_ERROR_2;
1537 }
1538
1539 if (pDomainList)
1540 {
1541 LsaFreeMemory(pDomainList);
1542 pDomainList = NULL;
1543 }
1544 if (pTranslatedSids)
1545 {
1546 LsaFreeMemory(pTranslatedSids);
1547 pTranslatedSids = NULL;
1548 }
1549
1550 rtProcWinFreeAccountInfo(&accountInfo);
1551 LsaClose(lsahPolicy);
1552 }
1553 else
1554 {
1555 dwErr = LsaNtStatusToWinError(ntSts);
1556 LogRelFunc(("LsaOpenPolicy failed with: %ld\n", dwErr));
1557 rc = dwErr != NO_ERROR ? RTErrConvertFromWin32(dwErr) : VERR_INTERNAL_ERROR_3;
1558 }
1559
1560 /* Note: pSid will be free'd down below. */
1561 }
1562 else if (dwErr == ERROR_INSUFFICIENT_BUFFER)
1563 {
1564 /* Allocate memory for the LookupAccountNameW output buffers and do it for real. */
1565 cbSid = fRc && cbSid != 0 ? cbSid + 16 : _1K;
1566 pSid = (PSID)RTMemAllocZ(cbSid);
1567 if (pSid)
1568 {
1569 cwcDomain = fRc ? cwcDomain + 2 : _4K;
1570 PRTUTF16 pwszDomain = (PRTUTF16)RTMemAllocZ(cwcDomain * sizeof(RTUTF16));
1571 if (pwszDomain)
1572 {
1573 /* Note: Just pass in the UPN (User Principal Name), e.g. [email protected] */
1574 if (!LookupAccountNameW(NULL /*lpSystemName*/, pwszUser, pSid, &cbSid, pwszDomain, &cwcDomain,
1575 &SidNameUse))
1576 {
1577 dwErr = GetLastError();
1578 LogRelFunc(("LookupAccountNameW(2) failed with: %ld\n", dwErr));
1579 rc = dwErr != NO_ERROR ? RTErrConvertFromWin32(dwErr) : VERR_INTERNAL_ERROR_4;
1580 }
1581
1582 RTMemFree(pwszDomain);
1583 }
1584 else
1585 rc = VERR_NO_MEMORY;
1586
1587 /* Note: pSid will be free'd down below. */
1588 }
1589 else
1590 rc = VERR_NO_MEMORY;
1591 }
1592 else
1593 {
1594 LogRelFunc(("LookupAccountNameW(1) failed with: %ld\n", dwErr));
1595 rc = dwErr != NO_ERROR ? RTErrConvertFromWin32(dwErr) : VERR_INTERNAL_ERROR_2;
1596 }
1597 }
1598
1599 if (pSid)
1600 {
1601 if (IsValidSid(pSid))
1602 {
1603 /* Array of process names we want to look for. */
1604 static const char * const s_papszProcNames[] =
1605 {
1606#ifdef VBOX /* The explorer entry is a fallback in case GA aren't installed. */
1607 { "VBoxTray.exe" },
1608#endif
1609 { "explorer.exe" },
1610 NULL
1611 };
1612 fFound = rtProcWinFindTokenByProcess(s_papszProcNames, pSid, &hTokenUserDesktop);
1613 dwErr = 0;
1614 }
1615 else
1616 {
1617 dwErr = GetLastError();
1618 LogRelFunc(("SID is invalid: %ld\n", dwErr));
1619 rc = dwErr != NO_ERROR ? RTErrConvertFromWin32(dwErr) : VERR_INTERNAL_ERROR_3;
1620 }
1621
1622 RTMemFree(pSid);
1623 }
1624 }
1625 /* else: !RTPROC_FLAGS_SERVICE: Nothing to do here right now. */
1626
1627#if 0
1628 /*
1629 * If we make LogonUserW to return an impersonation token, enable this
1630 * to convert it into a primary token.
1631 */
1632 if (!fFound && detect-impersonation-token)
1633 {
1634 HANDLE hNewToken;
1635 if (DuplicateTokenEx(hTokenLogon, MAXIMUM_ALLOWED, NULL /*SecurityAttribs*/,
1636 SecurityIdentification, TokenPrimary, &hNewToken))
1637 {
1638 CloseHandle(hTokenLogon);
1639 hTokenLogon = hNewToken;
1640 }
1641 else
1642 AssertMsgFailed(("%d\n", GetLastError()));
1643 }
1644#endif
1645
1646 if (RT_SUCCESS(rc))
1647 {
1648 /*
1649 * If we didn't find a matching VBoxTray, just use the token we got
1650 * above from LogonUserW(). This enables us to at least run processes
1651 * with desktop interaction without UI.
1652 */
1653 HANDLE hTokenToUse = fFound ? hTokenUserDesktop : hTokenLogon;
1654 if ( !(fFlags & RTPROC_FLAGS_PROFILE)
1655 || (g_pfnUnloadUserProfile && g_pfnLoadUserProfileW) )
1656 {
1657 /*
1658 * Load the profile, if requested. (Must be done prior to
1659 * creating the enviornment.)
1660 */
1661 PROFILEINFOW ProfileInfo;
1662 RT_ZERO(ProfileInfo);
1663 if (fFlags & RTPROC_FLAGS_PROFILE)
1664 {
1665 ProfileInfo.dwSize = sizeof(ProfileInfo);
1666 ProfileInfo.lpUserName = pwszUser;
1667 ProfileInfo.dwFlags = PI_NOUI; /* Prevents the display of profile error messages. */
1668
1669 if (!g_pfnLoadUserProfileW(hTokenToUse, &ProfileInfo))
1670 rc = RTErrConvertFromWin32(GetLastError());
1671 }
1672 if (RT_SUCCESS(rc))
1673 {
1674 /*
1675 * Create the environment.
1676 */
1677 RTENV hEnvFinal;
1678 rc = rtProcWinCreateEnvFromToken(hTokenToUse, hEnv, fFlags, &hEnvFinal);
1679 if (RT_SUCCESS(rc))
1680 {
1681 PRTUTF16 pwszzBlock;
1682 rc = RTEnvQueryUtf16Block(hEnvFinal, &pwszzBlock);
1683 if (RT_SUCCESS(rc))
1684 {
1685 rc = rtProcWinFindExe(fFlags, hEnv, pszExec, ppwszExec);
1686 if (RT_SUCCESS(rc))
1687 {
1688 HWINSTA hOldWinStation = NULL;
1689 if (!fFound && g_enmWinVer <= kRTWinOSType_NT4) /** @todo test newer versions... */
1690 rtProcWinStationPrep(hTokenToUse, pStartupInfo, &hOldWinStation);
1691
1692 /*
1693 * Useful KB articles:
1694 * http://support.microsoft.com/kb/165194/
1695 * http://support.microsoft.com/kb/184802/
1696 * http://support.microsoft.com/kb/327618/
1697 */
1698 fRc = CreateProcessAsUserW(hTokenToUse,
1699 *ppwszExec,
1700 pwszCmdLine,
1701 NULL, /* pProcessAttributes */
1702 NULL, /* pThreadAttributes */
1703 TRUE, /* fInheritHandles */
1704 dwCreationFlags,
1705 /** @todo Warn about exceeding 8192 bytes
1706 * on XP and up. */
1707 pwszzBlock, /* lpEnvironment */
1708 NULL, /* pCurrentDirectory */
1709 pStartupInfo,
1710 pProcInfo);
1711 if (fRc)
1712 rc = VINF_SUCCESS;
1713 else
1714 {
1715 dwErr = GetLastError();
1716 if (dwErr == ERROR_PRIVILEGE_NOT_HELD)
1717 rc = rtProcWinFigureWhichPrivilegeNotHeld2();
1718 else
1719 rc = RTErrConvertFromWin32(dwErr);
1720 }
1721
1722 if (hOldWinStation)
1723 SetProcessWindowStation(hOldWinStation);
1724 }
1725 RTEnvFreeUtf16Block(pwszzBlock);
1726 }
1727
1728 if (hEnvFinal != hEnv)
1729 RTEnvDestroy(hEnvFinal);
1730 }
1731
1732 if ((fFlags & RTPROC_FLAGS_PROFILE) && ProfileInfo.hProfile)
1733 {
1734 fRc = g_pfnUnloadUserProfile(hTokenToUse, ProfileInfo.hProfile);
1735#ifdef RT_STRICT
1736 if (!fRc)
1737 {
1738 DWORD dwErr2 = GetLastError();
1739 AssertMsgFailed(("Unloading user profile failed with error %u (%#x) - Are all handles closed? (dwErr=%u)",
1740 dwErr2, dwErr2, dwErr));
1741 }
1742#endif
1743 }
1744 }
1745 }
1746 else
1747 rc = VERR_SYMBOL_NOT_FOUND;
1748 } /* Account lookup succeeded? */
1749
1750 if (hTokenUserDesktop != INVALID_HANDLE_VALUE)
1751 CloseHandle(hTokenUserDesktop);
1752 if (hTokenLogon != INVALID_HANDLE_VALUE)
1753 CloseHandle(hTokenLogon);
1754
1755 if (rc == VERR_UNRESOLVED_ERROR)
1756 LogRelFunc(("dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc));
1757 }
1758
1759 return rc;
1760}
1761
1762
1763/**
1764 * Plants a standard handle into a child process on older windows versions.
1765 *
1766 * This is only needed when using CreateProcessWithLogonW on older windows
1767 * versions. It would appear that newer versions of windows does this for us.
1768 *
1769 * @param hSrcHandle The source handle.
1770 * @param hDstProcess The child process handle.
1771 * @param offProcParamMember The offset to RTL_USER_PROCESS_PARAMETERS.
1772 * @param ppvDstProcParamCache Where where cached the address of
1773 * RTL_USER_PROCESS_PARAMETERS in the child.
1774 */
1775static void rtProcWinDupStdHandleIntoChild(HANDLE hSrcHandle, HANDLE hDstProcess, uint32_t offProcParamMember,
1776 PVOID *ppvDstProcParamCache)
1777{
1778 if (hSrcHandle != NULL && hSrcHandle != INVALID_HANDLE_VALUE)
1779 {
1780 HANDLE hDstHandle;
1781 if (DuplicateHandle(GetCurrentProcess(), hSrcHandle, hDstProcess, &hDstHandle,
1782 0 /*IgnoredDesiredAccess*/, FALSE /*fInherit*/, DUPLICATE_SAME_ACCESS))
1783 {
1784 if (hSrcHandle == hDstHandle)
1785 return;
1786
1787 if (!*ppvDstProcParamCache)
1788 {
1789 PROCESS_BASIC_INFORMATION BasicInfo;
1790 ULONG cbIgn;
1791 NTSTATUS rcNt = NtQueryInformationProcess(hDstProcess, ProcessBasicInformation,
1792 &BasicInfo, sizeof(BasicInfo), &cbIgn);
1793 if (NT_SUCCESS(rcNt))
1794 {
1795 SIZE_T cbCopied = 0;
1796 if (!ReadProcessMemory(hDstProcess,
1797 (char *)BasicInfo.PebBaseAddress + RT_OFFSETOF(PEB_COMMON, ProcessParameters),
1798 ppvDstProcParamCache, sizeof(*ppvDstProcParamCache), &cbCopied))
1799 {
1800 AssertMsgFailed(("PebBaseAddress=%p %d\n", BasicInfo.PebBaseAddress, GetLastError()));
1801 *ppvDstProcParamCache = NULL;
1802 }
1803 }
1804 else
1805 AssertMsgFailed(("rcNt=%#x\n", rcNt));
1806 }
1807 if (*ppvDstProcParamCache)
1808 {
1809 if (WriteProcessMemory(hDstProcess, (char *)*ppvDstProcParamCache + offProcParamMember,
1810 &hDstHandle, sizeof(hDstHandle), NULL))
1811 return;
1812 }
1813
1814 /*
1815 * Close the handle.
1816 */
1817 HANDLE hSrcHandle2;
1818 if (DuplicateHandle(hDstProcess, hDstHandle, GetCurrentProcess(), &hSrcHandle2,
1819 0 /*IgnoredDesiredAccess*/, FALSE /*fInherit*/, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
1820 CloseHandle(hSrcHandle2);
1821 else
1822 AssertMsgFailed(("hDstHandle=%p %u\n", hDstHandle, GetLastError()));
1823 }
1824 else
1825 AssertMsg(GetLastError() == ERROR_INVALID_PARAMETER, ("%u\n", GetLastError()));
1826 }
1827}
1828
1829
1830/**
1831 * Method \#1.
1832 *
1833 * This method requires Windows 2000 or later. It may fail if the process is
1834 * running under the SYSTEM account (like a service, ERROR_ACCESS_DENIED) on
1835 * newer platforms (however, this works on W2K!).
1836 */
1837static int rtProcWinCreateAsUser1(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 *ppwszExec, PRTUTF16 pwszCmdLine,
1838 RTENV hEnv, DWORD dwCreationFlags,
1839 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo,
1840 uint32_t fFlags, const char *pszExec)
1841{
1842 /* The CreateProcessWithLogonW API was introduced with W2K and later. It uses a service
1843 for launching the process. */
1844 if (!g_pfnCreateProcessWithLogonW)
1845 return VERR_SYMBOL_NOT_FOUND;
1846
1847 /*
1848 * Create the environment block and find the executable first.
1849 *
1850 * We try to skip this when RTPROC_FLAGS_PROFILE is set so we can sidestep
1851 * potential missing TCB privilege issues when calling UserLogonW. At least
1852 * NT4 and W2K requires the trusted code base (TCB) privilege for logon use.
1853 * Passing pwszzBlock=NULL and LOGON_WITH_PROFILE means the child process
1854 * gets the environment specified by the user profile.
1855 */
1856 int rc;
1857 PRTUTF16 pwszzBlock = NULL;
1858
1859 /* Eliminating the path search flags simplifies things a little. */
1860 if ( (fFlags & RTPROC_FLAGS_SEARCH_PATH)
1861 && (RTPathHasPath(pszExec) || RTPathExists(pszExec)))
1862 fFlags &= ~RTPROC_FLAGS_SEARCH_PATH;
1863
1864 /*
1865 * No profile is simple, as is a user specified environment (no change record).
1866 */
1867 if ( !(fFlags & RTPROC_FLAGS_PROFILE)
1868 || ( !(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
1869 && hEnv != RTENV_DEFAULT))
1870 rc = rtProcWinCreateEnvBlockAndFindExe(fFlags, hEnv, pszExec, &pwszzBlock, ppwszExec);
1871 /*
1872 * Default profile environment without changes or path searching we leave
1873 * to the service that implements the API.
1874 */
1875 else if ( hEnv == RTENV_DEFAULT
1876 && !(fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_SEARCH_PATH)))
1877 {
1878 pwszzBlock = NULL;
1879 rc = VINF_SUCCESS;
1880 }
1881 /*
1882 * Otherwise, we need to get the user profile environment.
1883 */
1884 else
1885 {
1886 RTENV hEnvToUse = NIL_RTENV;
1887 HANDLE hTokenLogon = INVALID_HANDLE_VALUE;
1888 rc = rtProcWinUserLogon(pwszUser, pwszPassword, &hTokenLogon);
1889 if (RT_SUCCESS(rc))
1890 {
1891 /* CreateEnvFromToken docs says we should load the profile, though
1892 we haven't observed any difference when not doing it. Maybe it's
1893 only an issue with roaming profiles or something similar... */
1894 PROFILEINFOW ProfileInfo;
1895 RT_ZERO(ProfileInfo);
1896 ProfileInfo.dwSize = sizeof(ProfileInfo);
1897 ProfileInfo.lpUserName = pwszUser;
1898 ProfileInfo.dwFlags = PI_NOUI; /* Prevents the display of profile error messages. */
1899
1900 if (g_pfnLoadUserProfileW(hTokenLogon, &ProfileInfo))
1901 {
1902 /*
1903 * Do what we need to do. Don't keep any temp environment object.
1904 */
1905 rc = rtProcWinCreateEnvFromToken(hTokenLogon, hEnv, fFlags, &hEnvToUse);
1906 if (RT_SUCCESS(rc))
1907 {
1908 rc = rtProcWinFindExe(fFlags, hEnv, pszExec, ppwszExec);
1909 if (RT_SUCCESS(rc))
1910 rc = RTEnvQueryUtf16Block(hEnvToUse, &pwszzBlock);
1911 if (hEnvToUse != hEnv)
1912 RTEnvDestroy(hEnvToUse);
1913 }
1914
1915 if (!g_pfnUnloadUserProfile(hTokenLogon, ProfileInfo.hProfile))
1916 AssertFailed();
1917 }
1918 else
1919 rc = RTErrConvertFromWin32(GetLastError());
1920
1921 if (hTokenLogon != INVALID_HANDLE_VALUE)
1922 CloseHandle(hTokenLogon);
1923 }
1924 }
1925 if (RT_SUCCESS(rc))
1926 {
1927 /*
1928 * Create the process.
1929 */
1930 Assert(!(dwCreationFlags & CREATE_SUSPENDED));
1931 bool const fCreatedSuspended = g_enmWinVer < kRTWinOSType_XP;
1932 BOOL fRc = g_pfnCreateProcessWithLogonW(pwszUser,
1933 NULL, /* lpDomain*/
1934 pwszPassword,
1935 fFlags & RTPROC_FLAGS_PROFILE ? 1 /*LOGON_WITH_ PROFILE*/ : 0,
1936 *ppwszExec,
1937 pwszCmdLine,
1938 dwCreationFlags | (fCreatedSuspended ? CREATE_SUSPENDED : 0),
1939 pwszzBlock,
1940 NULL, /* pCurrentDirectory */
1941 pStartupInfo,
1942 pProcInfo);
1943 if (fRc)
1944 {
1945 if (!fCreatedSuspended)
1946 rc = VINF_SUCCESS;
1947 else
1948 {
1949 /*
1950 * Duplicate standard handles into the child process, we ignore failures here as it's
1951 * legal to have bad standard handle values and we cannot dup console I/O handles.*
1952 */
1953 PVOID pvDstProcParamCache = NULL;
1954 rtProcWinDupStdHandleIntoChild(pStartupInfo->hStdInput, pProcInfo->hProcess,
1955 RT_OFFSETOF(RTL_USER_PROCESS_PARAMETERS, StandardInput), &pvDstProcParamCache);
1956 rtProcWinDupStdHandleIntoChild(pStartupInfo->hStdOutput, pProcInfo->hProcess,
1957 RT_OFFSETOF(RTL_USER_PROCESS_PARAMETERS, StandardOutput), &pvDstProcParamCache);
1958 rtProcWinDupStdHandleIntoChild(pStartupInfo->hStdError, pProcInfo->hProcess,
1959 RT_OFFSETOF(RTL_USER_PROCESS_PARAMETERS, StandardError), &pvDstProcParamCache);
1960
1961 if (ResumeThread(pProcInfo->hThread) != ~(DWORD)0)
1962 rc = VINF_SUCCESS;
1963 else
1964 rc = RTErrConvertFromWin32(GetLastError());
1965 if (RT_FAILURE(rc))
1966 {
1967 TerminateProcess(pProcInfo->hProcess, 127);
1968 CloseHandle(pProcInfo->hThread);
1969 CloseHandle(pProcInfo->hProcess);
1970 }
1971 }
1972 }
1973 else
1974 {
1975 DWORD dwErr = GetLastError();
1976 rc = RTErrConvertFromWin32(dwErr);
1977 if (rc == VERR_UNRESOLVED_ERROR)
1978 LogRelFunc(("CreateProcessWithLogonW (%p) failed: dwErr=%u (%#x), rc=%Rrc\n",
1979 g_pfnCreateProcessWithLogonW, dwErr, dwErr, rc));
1980 }
1981 if (pwszzBlock)
1982 RTEnvFreeUtf16Block(pwszzBlock);
1983 }
1984 return rc;
1985}
1986
1987
1988static int rtProcWinCreateAsUser(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 *ppwszExec, PRTUTF16 pwszCmdLine,
1989 RTENV hEnv, DWORD dwCreationFlags,
1990 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo,
1991 uint32_t fFlags, const char *pszExec)
1992{
1993 /*
1994 * If we run as a service CreateProcessWithLogon will fail, so don't even
1995 * try it (because of Local System context). This method is very slow on W2K.
1996 */
1997 if (!(fFlags & RTPROC_FLAGS_SERVICE))
1998 {
1999 int rc = rtProcWinCreateAsUser1(pwszUser, pwszPassword, ppwszExec, pwszCmdLine,
2000 hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags, pszExec);
2001 if (RT_SUCCESS(rc))
2002 return rc;
2003 }
2004 return rtProcWinCreateAsUser2(pwszUser, pwszPassword, ppwszExec, pwszCmdLine,
2005 hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags, pszExec);
2006}
2007
2008
2009/**
2010 * RTPathTraverseList callback used by rtProcWinFindExe to locate the
2011 * executable.
2012 */
2013static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
2014{
2015 const char *pszExec = (const char *)pvUser1;
2016 char *pszRealExec = (char *)pvUser2;
2017 int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
2018 if (RT_FAILURE(rc))
2019 return rc;
2020 if (RTFileExists(pszRealExec))
2021 return VINF_SUCCESS;
2022 return VERR_TRY_AGAIN;
2023}
2024
2025
2026/**
2027 * Locate the executable file if necessary.
2028 *
2029 * @returns IPRT status code.
2030 * @param pszExec The UTF-8 executable string passed in by the user.
2031 * @param fFlags The process creation flags pass in by the user.
2032 * @param hEnv The environment to get the path variabel from.
2033 * @param ppwszExec Pointer to the variable pointing to the UTF-16
2034 * converted string. If we find something, the current
2035 * pointer will be free (RTUtf16Free) and
2036 * replaced by a new one.
2037 */
2038static int rtProcWinFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec, PRTUTF16 *ppwszExec)
2039{
2040 /*
2041 * Return immediately if we're not asked to search, or if the file has a
2042 * path already or if it actually exists in the current directory.
2043 */
2044 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
2045 || RTPathHavePath(pszExec)
2046 || RTPathExists(pszExec) )
2047 return VINF_SUCCESS;
2048
2049 /*
2050 * Search the Path or PATH variable for the file.
2051 */
2052 char *pszPath;
2053 if (RTEnvExistEx(hEnv, "PATH"))
2054 pszPath = RTEnvDupEx(hEnv, "PATH");
2055 else if (RTEnvExistEx(hEnv, "Path"))
2056 pszPath = RTEnvDupEx(hEnv, "Path");
2057 else
2058 return VERR_FILE_NOT_FOUND;
2059
2060 char szRealExec[RTPATH_MAX];
2061 int rc = RTPathTraverseList(pszPath, ';', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
2062 RTStrFree(pszPath);
2063 if (RT_SUCCESS(rc))
2064 {
2065 /*
2066 * Replace the executable string.
2067 */
2068 RTUtf16Free(*ppwszExec);
2069 *ppwszExec = NULL;
2070 rc = RTStrToUtf16(szRealExec, ppwszExec);
2071 }
2072 else if (rc == VERR_END_OF_STRING)
2073 rc = VERR_FILE_NOT_FOUND;
2074 return rc;
2075}
2076
2077
2078/**
2079 * Creates the UTF-16 environment block and, if necessary, find the executable.
2080 *
2081 * @returns IPRT status code.
2082 * @param fFlags The process creation flags pass in by the user.
2083 * @param hEnv The environment handle passed by the user.
2084 * @param pszExec See rtProcWinFindExe.
2085 * @param ppwszzBlock Where RTEnvQueryUtf16Block returns the block.
2086 * @param ppwszExec See rtProcWinFindExe.
2087 */
2088static int rtProcWinCreateEnvBlockAndFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec,
2089 PRTUTF16 *ppwszzBlock, PRTUTF16 *ppwszExec)
2090{
2091 int rc;
2092
2093 /*
2094 * In most cases, we just need to convert the incoming enviornment to a
2095 * UTF-16 environment block.
2096 */
2097 RTENV hEnvToUse = NIL_RTENV; /* (MSC maybe used uninitialized) */
2098 if ( !(fFlags & (RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_ENV_CHANGE_RECORD))
2099 || (hEnv == RTENV_DEFAULT && !(fFlags & RTPROC_FLAGS_PROFILE))
2100 || (hEnv != RTENV_DEFAULT && !(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)) )
2101 {
2102 hEnvToUse = hEnv;
2103 rc = VINF_SUCCESS;
2104 }
2105 else if (fFlags & RTPROC_FLAGS_PROFILE)
2106 {
2107 /*
2108 * We need to get the profile environment for the current user.
2109 */
2110 Assert((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || hEnv == RTENV_DEFAULT);
2111 AssertReturn(g_pfnCreateEnvironmentBlock && g_pfnDestroyEnvironmentBlock, VERR_SYMBOL_NOT_FOUND);
2112 AssertReturn(g_pfnLoadUserProfileW && g_pfnUnloadUserProfile, VERR_SYMBOL_NOT_FOUND);
2113 HANDLE hToken;
2114 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &hToken))
2115 {
2116 rc = rtProcWinCreateEnvFromToken(hToken, hEnv, fFlags, &hEnvToUse);
2117 CloseHandle(hToken);
2118 }
2119 else
2120 rc = RTErrConvertFromWin32(GetLastError());
2121 }
2122 else
2123 {
2124 /*
2125 * Apply hEnv as a change record on top of the default environment.
2126 */
2127 Assert(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD);
2128 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
2129 if (RT_SUCCESS(rc))
2130 {
2131 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
2132 if (RT_FAILURE(rc))
2133 RTEnvDestroy(hEnvToUse);
2134 }
2135 }
2136 if (RT_SUCCESS(rc))
2137 {
2138 /*
2139 * Query the UTF-16 environment block and locate the executable (if needed).
2140 */
2141 rc = RTEnvQueryUtf16Block(hEnvToUse, ppwszzBlock);
2142 if (RT_SUCCESS(rc))
2143 rc = rtProcWinFindExe(fFlags, hEnvToUse, pszExec, ppwszExec);
2144
2145 if (hEnvToUse != hEnv)
2146 RTEnvDestroy(hEnvToUse);
2147 }
2148
2149 return rc;
2150}
2151
2152
2153RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
2154 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
2155 const char *pszPassword, PRTPROCESS phProcess)
2156{
2157 /*
2158 * Input validation
2159 */
2160 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
2161 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
2162 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
2163 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
2164 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
2165 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
2166 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
2167 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
2168 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
2169 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
2170
2171 /*
2172 * Initialize the globals.
2173 */
2174 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
2175 AssertRCReturn(rc, rc);
2176 if (pszAsUser || (fFlags & (RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_SERVICE)))
2177 {
2178 rc = RTOnce(&g_rtProcWinResolveOnce, rtProcWinResolveOnce, NULL);
2179 AssertRCReturn(rc, rc);
2180 }
2181
2182 /*
2183 * Get the file descriptors for the handles we've been passed.
2184 *
2185 * It seems there is no point in trying to convince a child process's CRT
2186 * that any of the standard file handles is non-TEXT. So, we don't...
2187 */
2188 STARTUPINFOW StartupInfo;
2189 RT_ZERO(StartupInfo);
2190 StartupInfo.cb = sizeof(StartupInfo);
2191 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
2192#if 1 /* The CRT should keep the standard handles up to date. */
2193 StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
2194 StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
2195 StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
2196#else
2197 StartupInfo.hStdInput = _get_osfhandle(0);
2198 StartupInfo.hStdOutput = _get_osfhandle(1);
2199 StartupInfo.hStdError = _get_osfhandle(2);
2200#endif
2201 /* If we want to have a hidden process (e.g. not visible to
2202 * to the user) use the STARTUPINFO flags. */
2203 if (fFlags & RTPROC_FLAGS_HIDDEN)
2204 {
2205 StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
2206 StartupInfo.wShowWindow = SW_HIDE;
2207 }
2208
2209 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
2210 HANDLE *aphStds[3] = { &StartupInfo.hStdInput, &StartupInfo.hStdOutput, &StartupInfo.hStdError };
2211 DWORD afInhStds[3] = { 0xffffffff, 0xffffffff, 0xffffffff };
2212 for (int i = 0; i < 3; i++)
2213 {
2214 if (paHandles[i])
2215 {
2216 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
2217 switch (paHandles[i]->enmType)
2218 {
2219 case RTHANDLETYPE_FILE:
2220 *aphStds[i] = paHandles[i]->u.hFile != NIL_RTFILE
2221 ? (HANDLE)RTFileToNative(paHandles[i]->u.hFile)
2222 : INVALID_HANDLE_VALUE;
2223 break;
2224
2225 case RTHANDLETYPE_PIPE:
2226 *aphStds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
2227 ? (HANDLE)RTPipeToNative(paHandles[i]->u.hPipe)
2228 : INVALID_HANDLE_VALUE;
2229 break;
2230
2231 case RTHANDLETYPE_SOCKET:
2232 *aphStds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
2233 ? (HANDLE)RTSocketToNative(paHandles[i]->u.hSocket)
2234 : INVALID_HANDLE_VALUE;
2235 break;
2236
2237 default:
2238 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
2239 }
2240
2241 /* Get the inheritability of the handle. */
2242 if (*aphStds[i] != INVALID_HANDLE_VALUE)
2243 {
2244 if (!GetHandleInformation(*aphStds[i], &afInhStds[i]))
2245 {
2246 rc = RTErrConvertFromWin32(GetLastError());
2247 AssertMsgFailedReturn(("%Rrc %p\n", rc, *aphStds[i]), rc);
2248 }
2249 }
2250 }
2251 }
2252
2253 /*
2254 * Set the inheritability any handles we're handing the child.
2255 */
2256 rc = VINF_SUCCESS;
2257 for (int i = 0; i < 3; i++)
2258 if ( (afInhStds[i] != 0xffffffff)
2259 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
2260 {
2261 if (!SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
2262 {
2263 rc = RTErrConvertFromWin32(GetLastError());
2264 AssertMsgFailedBreak(("%Rrc %p\n", rc, *aphStds[i]));
2265 }
2266 }
2267
2268 /*
2269 * Create the command line and convert the executable name.
2270 */
2271 PRTUTF16 pwszCmdLine = NULL; /* Shut up, MSC! */
2272 if (RT_SUCCESS(rc))
2273 rc = RTGetOptArgvToUtf16String(&pwszCmdLine, papszArgs,
2274 !(fFlags & RTPROC_FLAGS_UNQUOTED_ARGS)
2275 ? RTGETOPTARGV_CNV_QUOTE_MS_CRT : RTGETOPTARGV_CNV_UNQUOTED);
2276 if (RT_SUCCESS(rc))
2277 {
2278 PRTUTF16 pwszExec;
2279 rc = RTStrToUtf16(pszExec, &pwszExec);
2280 if (RT_SUCCESS(rc))
2281 {
2282 /*
2283 * Get going...
2284 */
2285 PROCESS_INFORMATION ProcInfo;
2286 RT_ZERO(ProcInfo);
2287 DWORD dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
2288 if (fFlags & RTPROC_FLAGS_DETACHED)
2289 dwCreationFlags |= DETACHED_PROCESS;
2290 if (fFlags & RTPROC_FLAGS_NO_WINDOW)
2291 dwCreationFlags |= CREATE_NO_WINDOW;
2292
2293 /*
2294 * Only use the normal CreateProcess stuff if we have no user name
2295 * and we are not running from a (Windows) service. Otherwise use
2296 * the more advanced version in rtProcWinCreateAsUser().
2297 */
2298 if ( pszAsUser == NULL
2299 && !(fFlags & RTPROC_FLAGS_SERVICE))
2300 {
2301 /* Create the environment block first. */
2302 PRTUTF16 pwszzBlock;
2303 rc = rtProcWinCreateEnvBlockAndFindExe(fFlags, hEnv, pszExec, &pwszzBlock, &pwszExec);
2304 if (RT_SUCCESS(rc))
2305 {
2306 if (CreateProcessW(pwszExec,
2307 pwszCmdLine,
2308 NULL, /* pProcessAttributes */
2309 NULL, /* pThreadAttributes */
2310 TRUE, /* fInheritHandles */
2311 dwCreationFlags,
2312 pwszzBlock,
2313 NULL, /* pCurrentDirectory */
2314 &StartupInfo,
2315 &ProcInfo))
2316 rc = VINF_SUCCESS;
2317 else
2318 rc = RTErrConvertFromWin32(GetLastError());
2319 RTEnvFreeUtf16Block(pwszzBlock);
2320 }
2321 }
2322 else
2323 {
2324 /*
2325 * Convert the additional parameters and use a helper
2326 * function to do the actual work.
2327 */
2328 PRTUTF16 pwszUser;
2329 rc = RTStrToUtf16(pszAsUser, &pwszUser);
2330 if (RT_SUCCESS(rc))
2331 {
2332 PRTUTF16 pwszPassword;
2333 rc = RTStrToUtf16(pszPassword ? pszPassword : "", &pwszPassword);
2334 if (RT_SUCCESS(rc))
2335 {
2336 rc = rtProcWinCreateAsUser(pwszUser, pwszPassword,
2337 &pwszExec, pwszCmdLine, hEnv, dwCreationFlags,
2338 &StartupInfo, &ProcInfo, fFlags, pszExec);
2339
2340 if (pwszPassword && *pwszPassword)
2341 RTMemWipeThoroughly(pwszPassword, RTUtf16Len(pwszPassword), 5);
2342 RTUtf16Free(pwszPassword);
2343 }
2344 RTUtf16Free(pwszUser);
2345 }
2346 }
2347 if (RT_SUCCESS(rc))
2348 {
2349 CloseHandle(ProcInfo.hThread);
2350 if (phProcess)
2351 {
2352 /*
2353 * Add the process to the child process list so
2354 * RTProcWait can reuse and close the process handle.
2355 */
2356 rtProcWinAddPid(ProcInfo.dwProcessId, ProcInfo.hProcess);
2357 *phProcess = ProcInfo.dwProcessId;
2358 }
2359 else
2360 CloseHandle(ProcInfo.hProcess);
2361 rc = VINF_SUCCESS;
2362 }
2363 RTUtf16Free(pwszExec);
2364 }
2365 RTUtf16Free(pwszCmdLine);
2366 }
2367
2368 /* Undo any handle inherit changes. */
2369 for (int i = 0; i < 3; i++)
2370 if ( (afInhStds[i] != 0xffffffff)
2371 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
2372 {
2373 if (!SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, 0))
2374 AssertMsgFailed(("%Rrc %p\n", RTErrConvertFromWin32(GetLastError()), *aphStds[i]));
2375 }
2376
2377 return rc;
2378}
2379
2380
2381
2382RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
2383{
2384 AssertReturn(!(fFlags & ~(RTPROCWAIT_FLAGS_BLOCK | RTPROCWAIT_FLAGS_NOBLOCK)), VERR_INVALID_PARAMETER);
2385 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
2386 AssertRCReturn(rc, rc);
2387
2388 /*
2389 * Try find the process among the ones we've spawned, otherwise, attempt
2390 * opening the specified process.
2391 */
2392 HANDLE hOpenedProc = NULL;
2393 HANDLE hProcess = rtProcWinFindPid(Process);
2394 if (hProcess == NULL)
2395 {
2396 hProcess = hOpenedProc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Process);
2397 if (hProcess == NULL)
2398 {
2399 DWORD dwErr = GetLastError();
2400 if (dwErr == ERROR_INVALID_PARAMETER)
2401 return VERR_PROCESS_NOT_FOUND;
2402 return RTErrConvertFromWin32(dwErr);
2403 }
2404 }
2405
2406 /*
2407 * Wait for it to terminate.
2408 */
2409 DWORD Millies = fFlags == RTPROCWAIT_FLAGS_BLOCK ? INFINITE : 0;
2410 DWORD WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
2411 while (WaitRc == WAIT_IO_COMPLETION)
2412 WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
2413 switch (WaitRc)
2414 {
2415 /*
2416 * It has terminated.
2417 */
2418 case WAIT_OBJECT_0:
2419 {
2420 DWORD dwExitCode;
2421 if (GetExitCodeProcess(hProcess, &dwExitCode))
2422 {
2423 /** @todo the exit code can be special statuses. */
2424 if (pProcStatus)
2425 {
2426 pProcStatus->enmReason = RTPROCEXITREASON_NORMAL;
2427 pProcStatus->iStatus = (int)dwExitCode;
2428 }
2429 if (hOpenedProc == NULL)
2430 rtProcWinRemovePid(Process);
2431 rc = VINF_SUCCESS;
2432 }
2433 else
2434 rc = RTErrConvertFromWin32(GetLastError());
2435 break;
2436 }
2437
2438 /*
2439 * It hasn't terminated just yet.
2440 */
2441 case WAIT_TIMEOUT:
2442 rc = VERR_PROCESS_RUNNING;
2443 break;
2444
2445 /*
2446 * Something went wrong...
2447 */
2448 case WAIT_FAILED:
2449 rc = RTErrConvertFromWin32(GetLastError());
2450 break;
2451
2452 case WAIT_ABANDONED:
2453 AssertFailed();
2454 rc = VERR_GENERAL_FAILURE;
2455 break;
2456
2457 default:
2458 AssertMsgFailed(("WaitRc=%RU32\n", WaitRc));
2459 rc = VERR_GENERAL_FAILURE;
2460 break;
2461 }
2462
2463 if (hOpenedProc != NULL)
2464 CloseHandle(hOpenedProc);
2465 return rc;
2466}
2467
2468
2469RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
2470{
2471 /** @todo this isn't quite right. */
2472 return RTProcWait(Process, fFlags, pProcStatus);
2473}
2474
2475
2476RTR3DECL(int) RTProcTerminate(RTPROCESS Process)
2477{
2478 if (Process == NIL_RTPROCESS)
2479 return VINF_SUCCESS;
2480
2481 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
2482 AssertRCReturn(rc, rc);
2483
2484 /*
2485 * Try find the process among the ones we've spawned, otherwise, attempt
2486 * opening the specified process.
2487 */
2488 HANDLE hProcess = rtProcWinFindPid(Process);
2489 if (hProcess != NULL)
2490 {
2491 if (!TerminateProcess(hProcess, 127))
2492 rc = RTErrConvertFromWin32(GetLastError());
2493 }
2494 else
2495 {
2496 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, Process);
2497 if (hProcess != NULL)
2498 {
2499 BOOL fRc = TerminateProcess(hProcess, 127);
2500 DWORD dwErr = GetLastError();
2501 CloseHandle(hProcess);
2502 if (!fRc)
2503 rc = RTErrConvertFromWin32(dwErr);
2504 }
2505 }
2506 return rc;
2507}
2508
2509
2510RTR3DECL(uint64_t) RTProcGetAffinityMask(void)
2511{
2512 DWORD_PTR dwProcessAffinityMask = 0xffffffff;
2513 DWORD_PTR dwSystemAffinityMask;
2514
2515 BOOL fRc = GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinityMask, &dwSystemAffinityMask);
2516 Assert(fRc); NOREF(fRc);
2517
2518 return dwProcessAffinityMask;
2519}
2520
2521
2522RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, size_t *pcbUser)
2523{
2524 AssertReturn( (pszUser && cbUser > 0)
2525 || (!pszUser && !cbUser), VERR_INVALID_PARAMETER);
2526 AssertReturn(pcbUser || pszUser, VERR_INVALID_PARAMETER);
2527
2528 int rc;
2529 if ( hProcess == NIL_RTPROCESS
2530 || hProcess == RTProcSelf())
2531 {
2532 RTUTF16 wszUsername[UNLEN + 1];
2533 DWORD cwcUsername = RT_ELEMENTS(wszUsername);
2534 if (GetUserNameW(&wszUsername[0], &cwcUsername))
2535 {
2536 if (pszUser)
2537 {
2538 rc = RTUtf16ToUtf8Ex(wszUsername, cwcUsername, &pszUser, cbUser, pcbUser);
2539 if (pcbUser)
2540 *pcbUser += 1;
2541 }
2542 else
2543 {
2544 *pcbUser = RTUtf16CalcUtf8Len(wszUsername) + 1;
2545 rc = VERR_BUFFER_OVERFLOW;
2546 }
2547 }
2548 else
2549 rc = RTErrConvertFromWin32(GetLastError());
2550 }
2551 else
2552 rc = VERR_NOT_SUPPORTED;
2553 return rc;
2554}
2555
2556
2557RTR3DECL(int) RTProcQueryUsernameA(RTPROCESS hProcess, char **ppszUser)
2558{
2559 AssertPtrReturn(ppszUser, VERR_INVALID_POINTER);
2560 int rc;
2561 if ( hProcess == NIL_RTPROCESS
2562 || hProcess == RTProcSelf())
2563 {
2564 RTUTF16 wszUsername[UNLEN + 1];
2565 DWORD cwcUsername = RT_ELEMENTS(wszUsername);
2566 if (GetUserNameW(&wszUsername[0], &cwcUsername))
2567 rc = RTUtf16ToUtf8(wszUsername, ppszUser);
2568 else
2569 rc = RTErrConvertFromWin32(GetLastError());
2570 }
2571 else
2572 rc = VERR_NOT_SUPPORTED;
2573 return rc;
2574}
2575
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette