VirtualBox

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

Last change on this file since 85112 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 103.2 KB
Line 
1/* $Id: process-win.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
2/** @file
3 * IPRT - Process, Windows.
4 */
5
6/*
7 * Copyright (C) 2006-2020 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 (%p) failed: dwErr=%u (%#x), rc=%Rrc\n",
2079 g_pfnCreateProcessWithLogonW, dwErr, dwErr, rc));
2080 }
2081 if (pwszzBlock)
2082 RTEnvFreeUtf16Block(pwszzBlock);
2083 }
2084 return rc;
2085}
2086
2087
2088static int rtProcWinCreateAsUser(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 *ppwszExec, PRTUTF16 pwszCmdLine,
2089 RTENV hEnv, DWORD dwCreationFlags,
2090 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo,
2091 uint32_t fFlags, const char *pszExec, uint32_t idDesiredSession)
2092{
2093 /*
2094 * If we run as a service CreateProcessWithLogon will fail, so don't even
2095 * try it (because of Local System context). If we got an impersonated token
2096 * we should use, we also have to have to skip over this approach.
2097 * Note! This method is very slow on W2K.
2098 */
2099 if (!(fFlags & (RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_AS_IMPERSONATED_TOKEN)))
2100 {
2101 AssertPtr(pwszUser);
2102 int rc = rtProcWinCreateAsUser1(pwszUser, pwszPassword, ppwszExec, pwszCmdLine,
2103 hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags, pszExec);
2104 if (RT_SUCCESS(rc))
2105 return rc;
2106 }
2107 return rtProcWinCreateAsUser2(pwszUser, pwszPassword, ppwszExec, pwszCmdLine,
2108 hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags, pszExec, idDesiredSession);
2109}
2110
2111
2112/**
2113 * RTPathTraverseList callback used by rtProcWinFindExe to locate the
2114 * executable.
2115 */
2116static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
2117{
2118 const char *pszExec = (const char *)pvUser1;
2119 char *pszRealExec = (char *)pvUser2;
2120 int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
2121 if (RT_FAILURE(rc))
2122 return rc;
2123 if (RTFileExists(pszRealExec))
2124 return VINF_SUCCESS;
2125 return VERR_TRY_AGAIN;
2126}
2127
2128
2129/**
2130 * Locate the executable file if necessary.
2131 *
2132 * @returns IPRT status code.
2133 * @param pszExec The UTF-8 executable string passed in by the user.
2134 * @param fFlags The process creation flags pass in by the user.
2135 * @param hEnv The environment to get the path variabel from.
2136 * @param ppwszExec Pointer to the variable pointing to the UTF-16
2137 * converted string. If we find something, the current
2138 * pointer will be free (RTUtf16Free) and
2139 * replaced by a new one.
2140 */
2141static int rtProcWinFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec, PRTUTF16 *ppwszExec)
2142{
2143 /*
2144 * Return immediately if we're not asked to search, or if the file has a
2145 * path already or if it actually exists in the current directory.
2146 */
2147 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
2148 || RTPathHavePath(pszExec)
2149 || RTPathExists(pszExec) )
2150 return VINF_SUCCESS;
2151
2152 /*
2153 * Search the Path or PATH variable for the file.
2154 */
2155 char *pszPath;
2156 if (RTEnvExistEx(hEnv, "PATH"))
2157 pszPath = RTEnvDupEx(hEnv, "PATH");
2158 else if (RTEnvExistEx(hEnv, "Path"))
2159 pszPath = RTEnvDupEx(hEnv, "Path");
2160 else
2161 return VERR_FILE_NOT_FOUND;
2162
2163 char szRealExec[RTPATH_MAX];
2164 int rc = RTPathTraverseList(pszPath, ';', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
2165 RTStrFree(pszPath);
2166 if (RT_SUCCESS(rc))
2167 {
2168 /*
2169 * Replace the executable string.
2170 */
2171 RTPathWinFree(*ppwszExec);
2172 *ppwszExec = NULL;
2173 rc = RTPathWinFromUtf8(ppwszExec, szRealExec, 0 /*fFlags*/);
2174 }
2175 else if (rc == VERR_END_OF_STRING)
2176 rc = VERR_FILE_NOT_FOUND;
2177 return rc;
2178}
2179
2180
2181/**
2182 * Creates the UTF-16 environment block and, if necessary, find the executable.
2183 *
2184 * @returns IPRT status code.
2185 * @param fFlags The process creation flags pass in by the user.
2186 * @param hEnv The environment handle passed by the user.
2187 * @param pszExec See rtProcWinFindExe.
2188 * @param ppwszzBlock Where RTEnvQueryUtf16Block returns the block.
2189 * @param ppwszExec See rtProcWinFindExe.
2190 */
2191static int rtProcWinCreateEnvBlockAndFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec,
2192 PRTUTF16 *ppwszzBlock, PRTUTF16 *ppwszExec)
2193{
2194 int rc;
2195
2196 /*
2197 * In most cases, we just need to convert the incoming enviornment to a
2198 * UTF-16 environment block.
2199 */
2200 RTENV hEnvToUse = NIL_RTENV; /* (MSC maybe used uninitialized) */
2201 if ( !(fFlags & (RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_ENV_CHANGE_RECORD))
2202 || (hEnv == RTENV_DEFAULT && !(fFlags & RTPROC_FLAGS_PROFILE))
2203 || (hEnv != RTENV_DEFAULT && !(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)) )
2204 {
2205 hEnvToUse = hEnv;
2206 rc = VINF_SUCCESS;
2207 }
2208 else if (fFlags & RTPROC_FLAGS_PROFILE)
2209 {
2210 /*
2211 * We need to get the profile environment for the current user.
2212 */
2213 Assert((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || hEnv == RTENV_DEFAULT);
2214 AssertReturn(g_pfnCreateEnvironmentBlock && g_pfnDestroyEnvironmentBlock, VERR_SYMBOL_NOT_FOUND);
2215 AssertReturn(g_pfnLoadUserProfileW && g_pfnUnloadUserProfile, VERR_SYMBOL_NOT_FOUND);
2216 HANDLE hToken;
2217 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &hToken))
2218 {
2219 rc = rtProcWinCreateEnvFromToken(hToken, hEnv, fFlags, &hEnvToUse);
2220 CloseHandle(hToken);
2221 }
2222 else
2223 rc = RTErrConvertFromWin32(GetLastError());
2224 }
2225 else
2226 {
2227 /*
2228 * Apply hEnv as a change record on top of the default environment.
2229 */
2230 Assert(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD);
2231 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
2232 if (RT_SUCCESS(rc))
2233 {
2234 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
2235 if (RT_FAILURE(rc))
2236 RTEnvDestroy(hEnvToUse);
2237 }
2238 }
2239 if (RT_SUCCESS(rc))
2240 {
2241 /*
2242 * Query the UTF-16 environment block and locate the executable (if needed).
2243 */
2244 rc = RTEnvQueryUtf16Block(hEnvToUse, ppwszzBlock);
2245 if (RT_SUCCESS(rc))
2246 rc = rtProcWinFindExe(fFlags, hEnvToUse, pszExec, ppwszExec);
2247
2248 if (hEnvToUse != hEnv)
2249 RTEnvDestroy(hEnvToUse);
2250 }
2251
2252 return rc;
2253}
2254
2255
2256RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
2257 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
2258 const char *pszPassword, void *pvExtraData, PRTPROCESS phProcess)
2259{
2260 /*
2261 * Input validation
2262 */
2263 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
2264 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
2265 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
2266 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
2267 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
2268 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
2269 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
2270 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
2271 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
2272 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
2273
2274 /* Extra data: */
2275 uint32_t idDesiredSession = UINT32_MAX;
2276 if ( (fFlags & (RTPROC_FLAGS_DESIRED_SESSION_ID | RTPROC_FLAGS_SERVICE))
2277 == (RTPROC_FLAGS_DESIRED_SESSION_ID | RTPROC_FLAGS_SERVICE))
2278 {
2279 AssertPtrReturn(pvExtraData, VERR_INVALID_POINTER);
2280 idDesiredSession = *(uint32_t *)pvExtraData;
2281 }
2282 else
2283 AssertReturn(!(fFlags & RTPROC_FLAGS_DESIRED_SESSION_ID), VERR_INVALID_FLAGS);
2284
2285 /*
2286 * Initialize the globals.
2287 */
2288 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
2289 AssertRCReturn(rc, rc);
2290 if (pszAsUser || (fFlags & (RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_AS_IMPERSONATED_TOKEN)))
2291 {
2292 rc = RTOnce(&g_rtProcWinResolveOnce, rtProcWinResolveOnce, NULL);
2293 AssertRCReturn(rc, rc);
2294 }
2295
2296 /*
2297 * Get the file descriptors for the handles we've been passed.
2298 *
2299 * It seems there is no point in trying to convince a child process's CRT
2300 * that any of the standard file handles is non-TEXT. So, we don't...
2301 */
2302 STARTUPINFOW StartupInfo;
2303 RT_ZERO(StartupInfo);
2304 StartupInfo.cb = sizeof(StartupInfo);
2305 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
2306#if 1 /* The CRT should keep the standard handles up to date. */
2307 StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
2308 StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
2309 StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
2310#else
2311 StartupInfo.hStdInput = _get_osfhandle(0);
2312 StartupInfo.hStdOutput = _get_osfhandle(1);
2313 StartupInfo.hStdError = _get_osfhandle(2);
2314#endif
2315 /* If we want to have a hidden process (e.g. not visible to
2316 * to the user) use the STARTUPINFO flags. */
2317 if (fFlags & RTPROC_FLAGS_HIDDEN)
2318 {
2319 StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
2320 StartupInfo.wShowWindow = SW_HIDE;
2321 }
2322
2323 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
2324 HANDLE *aphStds[3] = { &StartupInfo.hStdInput, &StartupInfo.hStdOutput, &StartupInfo.hStdError };
2325 DWORD afInhStds[3] = { 0xffffffff, 0xffffffff, 0xffffffff };
2326 HANDLE ahStdDups[3] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
2327 for (int i = 0; i < 3; i++)
2328 {
2329 if (paHandles[i])
2330 {
2331 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
2332 switch (paHandles[i]->enmType)
2333 {
2334 case RTHANDLETYPE_FILE:
2335 {
2336 HANDLE hNativeFile = paHandles[i]->u.hFile != NIL_RTFILE
2337 ? (HANDLE)RTFileToNative(paHandles[i]->u.hFile)
2338 : INVALID_HANDLE_VALUE;
2339 if ( hNativeFile == *aphStds[i]
2340 && g_enmWinVer == kRTWinOSType_NT310)
2341 continue;
2342 *aphStds[i] = hNativeFile;
2343 break;
2344 }
2345
2346 case RTHANDLETYPE_PIPE:
2347 *aphStds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
2348 ? (HANDLE)RTPipeToNative(paHandles[i]->u.hPipe)
2349 : INVALID_HANDLE_VALUE;
2350 if ( g_enmWinVer == kRTWinOSType_NT310
2351 && *aphStds[i] == INVALID_HANDLE_VALUE)
2352 {
2353 AssertMsgReturn(RTPipeGetCreationInheritability(paHandles[i]->u.hPipe), ("%Rrc %p\n", rc, *aphStds[i]),
2354 VERR_INVALID_STATE);
2355 continue;
2356 }
2357 break;
2358
2359 case RTHANDLETYPE_SOCKET:
2360 *aphStds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
2361 ? (HANDLE)RTSocketToNative(paHandles[i]->u.hSocket)
2362 : INVALID_HANDLE_VALUE;
2363 break;
2364
2365 default:
2366 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
2367 }
2368
2369 /* Get the inheritability of the handle. */
2370 if (*aphStds[i] != INVALID_HANDLE_VALUE)
2371 {
2372 if (g_enmWinVer == kRTWinOSType_NT310)
2373 afInhStds[i] = 0; /* No handle info on NT 3.1, so ASSUME it is not inheritable. */
2374 else if (!GetHandleInformation(*aphStds[i], &afInhStds[i]))
2375 {
2376 rc = RTErrConvertFromWin32(GetLastError());
2377 AssertMsgFailedReturn(("%Rrc aphStds[%d] => %p paHandles[%d]={%d,%p}\n",
2378 rc, i, *aphStds[i], i, paHandles[i]->enmType, paHandles[i]->u.uInt),
2379 rc);
2380 }
2381 }
2382 }
2383 }
2384
2385 /*
2386 * Set the inheritability any handles we're handing the child.
2387 *
2388 * Note! On NT 3.1 there is no SetHandleInformation, so we have to duplicate
2389 * the handles to make sure they are inherited by the child.
2390 */
2391 rc = VINF_SUCCESS;
2392 for (int i = 0; i < 3; i++)
2393 if ( (afInhStds[i] != 0xffffffff)
2394 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
2395 {
2396 if (g_enmWinVer == kRTWinOSType_NT310)
2397 {
2398 if (DuplicateHandle(GetCurrentProcess(), *aphStds[i], GetCurrentProcess(), &ahStdDups[i],
2399 i == 0 ? GENERIC_READ : GENERIC_WRITE, TRUE /*fInheritHandle*/, DUPLICATE_SAME_ACCESS))
2400 *aphStds[i] = ahStdDups[i];
2401 else
2402 {
2403 rc = RTErrConvertFromWin32(GetLastError());
2404 AssertMsgFailedBreak(("%Rrc aphStds[%u] => %p\n", rc, i, *aphStds[i]));
2405 }
2406 }
2407 else if (!SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
2408 {
2409 rc = RTErrConvertFromWin32(GetLastError());
2410 if (rc == VERR_INVALID_FUNCTION && g_enmWinVer == kRTWinOSType_NT310)
2411 rc = VINF_SUCCESS;
2412 else
2413 AssertMsgFailedBreak(("%Rrc aphStds[%u] => %p\n", rc, i, *aphStds[i]));
2414 }
2415 }
2416
2417 /*
2418 * Create the command line and convert the executable name.
2419 */
2420 PRTUTF16 pwszCmdLine = NULL; /* Shut up, MSC! */
2421 if (RT_SUCCESS(rc))
2422 rc = RTGetOptArgvToUtf16String(&pwszCmdLine, papszArgs,
2423 !(fFlags & RTPROC_FLAGS_UNQUOTED_ARGS)
2424 ? RTGETOPTARGV_CNV_QUOTE_MS_CRT : RTGETOPTARGV_CNV_UNQUOTED);
2425 if (RT_SUCCESS(rc))
2426 {
2427 PRTUTF16 pwszExec;
2428 rc = RTPathWinFromUtf8(&pwszExec, pszExec, 0 /*fFlags*/);
2429 if (RT_SUCCESS(rc))
2430 {
2431 /*
2432 * Get going...
2433 */
2434 PROCESS_INFORMATION ProcInfo;
2435 RT_ZERO(ProcInfo);
2436 DWORD dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
2437 if (fFlags & RTPROC_FLAGS_DETACHED)
2438 dwCreationFlags |= DETACHED_PROCESS;
2439 if (fFlags & RTPROC_FLAGS_NO_WINDOW)
2440 dwCreationFlags |= CREATE_NO_WINDOW;
2441
2442 /*
2443 * Only use the normal CreateProcess stuff if we have no user name
2444 * and we are not running from a (Windows) service. Otherwise use
2445 * the more advanced version in rtProcWinCreateAsUser().
2446 */
2447 if ( pszAsUser == NULL
2448 && !(fFlags & (RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_AS_IMPERSONATED_TOKEN)))
2449 {
2450 /* Create the environment block first. */
2451 PRTUTF16 pwszzBlock;
2452 rc = rtProcWinCreateEnvBlockAndFindExe(fFlags, hEnv, pszExec, &pwszzBlock, &pwszExec);
2453 if (RT_SUCCESS(rc))
2454 {
2455 if (CreateProcessW(pwszExec,
2456 pwszCmdLine,
2457 NULL, /* pProcessAttributes */
2458 NULL, /* pThreadAttributes */
2459 TRUE, /* fInheritHandles */
2460 dwCreationFlags,
2461 pwszzBlock,
2462 NULL, /* pCurrentDirectory */
2463 &StartupInfo,
2464 &ProcInfo))
2465 rc = VINF_SUCCESS;
2466 else
2467 rc = RTErrConvertFromWin32(GetLastError());
2468 RTEnvFreeUtf16Block(pwszzBlock);
2469 }
2470 }
2471 else
2472 {
2473 /*
2474 * Convert the additional parameters and use a helper
2475 * function to do the actual work.
2476 */
2477 PRTUTF16 pwszUser = NULL;
2478 if (pszAsUser)
2479 rc = RTStrToUtf16(pszAsUser, &pwszUser);
2480 if (RT_SUCCESS(rc))
2481 {
2482 PRTUTF16 pwszPassword;
2483 rc = RTStrToUtf16(pszPassword ? pszPassword : "", &pwszPassword);
2484 if (RT_SUCCESS(rc))
2485 {
2486 rc = rtProcWinCreateAsUser(pwszUser, pwszPassword,
2487 &pwszExec, pwszCmdLine, hEnv, dwCreationFlags,
2488 &StartupInfo, &ProcInfo, fFlags, pszExec, idDesiredSession);
2489
2490 if (pwszPassword && *pwszPassword)
2491 RTMemWipeThoroughly(pwszPassword, RTUtf16Len(pwszPassword), 5);
2492 RTUtf16Free(pwszPassword);
2493 }
2494 RTUtf16Free(pwszUser);
2495 }
2496 }
2497 if (RT_SUCCESS(rc))
2498 {
2499 CloseHandle(ProcInfo.hThread);
2500 if (phProcess)
2501 {
2502 /*
2503 * Add the process to the child process list so RTProcWait can reuse and close
2504 * the process handle, unless, of course, the caller has no intention waiting.
2505 */
2506 if (!(fFlags & RTPROC_FLAGS_NO_WAIT))
2507 rtProcWinAddPid(ProcInfo.dwProcessId, ProcInfo.hProcess);
2508 else
2509 CloseHandle(ProcInfo.hProcess);
2510 *phProcess = ProcInfo.dwProcessId;
2511 }
2512 else
2513 CloseHandle(ProcInfo.hProcess);
2514 rc = VINF_SUCCESS;
2515 }
2516 RTPathWinFree(pwszExec);
2517 }
2518 RTUtf16Free(pwszCmdLine);
2519 }
2520
2521 if (g_enmWinVer != kRTWinOSType_NT310)
2522 {
2523 /* Undo any handle inherit changes. */
2524 for (int i = 0; i < 3; i++)
2525 if ( (afInhStds[i] != 0xffffffff)
2526 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
2527 {
2528 if ( !SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, 0)
2529 && ( GetLastError() != ERROR_INVALID_FUNCTION
2530 || g_enmWinVer != kRTWinOSType_NT310) )
2531 AssertMsgFailed(("%Rrc %p\n", RTErrConvertFromWin32(GetLastError()), *aphStds[i]));
2532 }
2533 }
2534 else
2535 {
2536 /* Close handles duplicated for correct inheritance. */
2537 for (int i = 0; i < 3; i++)
2538 if (ahStdDups[i] != INVALID_HANDLE_VALUE)
2539 CloseHandle(ahStdDups[i]);
2540 }
2541
2542 return rc;
2543}
2544
2545
2546
2547RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
2548{
2549 AssertReturn(!(fFlags & ~(RTPROCWAIT_FLAGS_BLOCK | RTPROCWAIT_FLAGS_NOBLOCK)), VERR_INVALID_PARAMETER);
2550 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
2551 AssertRCReturn(rc, rc);
2552
2553 /*
2554 * Try find the process among the ones we've spawned, otherwise, attempt
2555 * opening the specified process.
2556 */
2557 HANDLE hOpenedProc = NULL;
2558 HANDLE hProcess = rtProcWinFindPid(Process);
2559 if (hProcess == NULL)
2560 {
2561 hProcess = hOpenedProc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Process);
2562 if (hProcess == NULL)
2563 {
2564 DWORD dwErr = GetLastError();
2565 if (dwErr == ERROR_INVALID_PARAMETER)
2566 return VERR_PROCESS_NOT_FOUND;
2567 return RTErrConvertFromWin32(dwErr);
2568 }
2569 }
2570
2571 /*
2572 * Wait for it to terminate.
2573 */
2574 DWORD Millies = fFlags == RTPROCWAIT_FLAGS_BLOCK ? INFINITE : 0;
2575 DWORD WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
2576 while (WaitRc == WAIT_IO_COMPLETION)
2577 WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
2578 switch (WaitRc)
2579 {
2580 /*
2581 * It has terminated.
2582 */
2583 case WAIT_OBJECT_0:
2584 {
2585 DWORD dwExitCode;
2586 if (GetExitCodeProcess(hProcess, &dwExitCode))
2587 {
2588 /** @todo the exit code can be special statuses. */
2589 if (pProcStatus)
2590 {
2591 pProcStatus->enmReason = RTPROCEXITREASON_NORMAL;
2592 pProcStatus->iStatus = (int)dwExitCode;
2593 }
2594 if (hOpenedProc == NULL)
2595 rtProcWinRemovePid(Process);
2596 rc = VINF_SUCCESS;
2597 }
2598 else
2599 rc = RTErrConvertFromWin32(GetLastError());
2600 break;
2601 }
2602
2603 /*
2604 * It hasn't terminated just yet.
2605 */
2606 case WAIT_TIMEOUT:
2607 rc = VERR_PROCESS_RUNNING;
2608 break;
2609
2610 /*
2611 * Something went wrong...
2612 */
2613 case WAIT_FAILED:
2614 rc = RTErrConvertFromWin32(GetLastError());
2615 break;
2616
2617 case WAIT_ABANDONED:
2618 AssertFailed();
2619 rc = VERR_GENERAL_FAILURE;
2620 break;
2621
2622 default:
2623 AssertMsgFailed(("WaitRc=%RU32\n", WaitRc));
2624 rc = VERR_GENERAL_FAILURE;
2625 break;
2626 }
2627
2628 if (hOpenedProc != NULL)
2629 CloseHandle(hOpenedProc);
2630 return rc;
2631}
2632
2633
2634RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
2635{
2636 /** @todo this isn't quite right. */
2637 return RTProcWait(Process, fFlags, pProcStatus);
2638}
2639
2640
2641RTR3DECL(int) RTProcTerminate(RTPROCESS Process)
2642{
2643 if (Process == NIL_RTPROCESS)
2644 return VINF_SUCCESS;
2645
2646 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
2647 AssertRCReturn(rc, rc);
2648
2649 /*
2650 * Try find the process among the ones we've spawned, otherwise, attempt
2651 * opening the specified process.
2652 */
2653 HANDLE hProcess = rtProcWinFindPid(Process);
2654 if (hProcess != NULL)
2655 {
2656 if (!TerminateProcess(hProcess, 127))
2657 rc = RTErrConvertFromWin32(GetLastError());
2658 }
2659 else
2660 {
2661 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, Process);
2662 if (hProcess != NULL)
2663 {
2664 BOOL fRc = TerminateProcess(hProcess, 127);
2665 DWORD dwErr = GetLastError();
2666 CloseHandle(hProcess);
2667 if (!fRc)
2668 rc = RTErrConvertFromWin32(dwErr);
2669 }
2670 }
2671 return rc;
2672}
2673
2674
2675RTR3DECL(uint64_t) RTProcGetAffinityMask(void)
2676{
2677 DWORD_PTR dwProcessAffinityMask = 0xffffffff;
2678 DWORD_PTR dwSystemAffinityMask;
2679
2680 BOOL fRc = GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinityMask, &dwSystemAffinityMask);
2681 Assert(fRc); NOREF(fRc);
2682
2683 return dwProcessAffinityMask;
2684}
2685
2686
2687RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, size_t *pcbUser)
2688{
2689 AssertReturn( (pszUser && cbUser > 0)
2690 || (!pszUser && !cbUser), VERR_INVALID_PARAMETER);
2691 AssertReturn(pcbUser || pszUser, VERR_INVALID_PARAMETER);
2692
2693 int rc;
2694 if ( hProcess == NIL_RTPROCESS
2695 || hProcess == RTProcSelf())
2696 {
2697 RTUTF16 wszUsername[UNLEN + 1];
2698 DWORD cwcUsername = RT_ELEMENTS(wszUsername);
2699 if (GetUserNameW(&wszUsername[0], &cwcUsername))
2700 {
2701 if (pszUser)
2702 {
2703 rc = RTUtf16ToUtf8Ex(wszUsername, cwcUsername, &pszUser, cbUser, pcbUser);
2704 if (pcbUser)
2705 *pcbUser += 1;
2706 }
2707 else
2708 {
2709 *pcbUser = RTUtf16CalcUtf8Len(wszUsername) + 1;
2710 rc = VERR_BUFFER_OVERFLOW;
2711 }
2712 }
2713 else
2714 rc = RTErrConvertFromWin32(GetLastError());
2715 }
2716 else
2717 rc = VERR_NOT_SUPPORTED;
2718 return rc;
2719}
2720
2721
2722RTR3DECL(int) RTProcQueryUsernameA(RTPROCESS hProcess, char **ppszUser)
2723{
2724 AssertPtrReturn(ppszUser, VERR_INVALID_POINTER);
2725 int rc;
2726 if ( hProcess == NIL_RTPROCESS
2727 || hProcess == RTProcSelf())
2728 {
2729 RTUTF16 wszUsername[UNLEN + 1];
2730 DWORD cwcUsername = RT_ELEMENTS(wszUsername);
2731 if (GetUserNameW(&wszUsername[0], &cwcUsername))
2732 rc = RTUtf16ToUtf8(wszUsername, ppszUser);
2733 else
2734 rc = RTErrConvertFromWin32(GetLastError());
2735 }
2736 else
2737 rc = VERR_NOT_SUPPORTED;
2738 return rc;
2739}
2740
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