VirtualBox

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

Last change on this file since 74460 was 74460, checked in by vboxsync, 6 years ago

IPRT: Implemented long filename support for windows (except for LoadLibrary). bugref:9248

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