VirtualBox

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

Last change on this file since 95084 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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