VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/posix/process-creation-posix.cpp@ 95007

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

IPRT/Process creation: A bit more logging, added a hint. bugref:10225

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 87.1 KB
Line 
1/* $Id: process-creation-posix.cpp 95007 2022-05-13 15:36:54Z vboxsync $ */
2/** @file
3 * IPRT - Process Creation, POSIX.
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/cdefs.h>
33#ifdef RT_OS_LINUX
34# define IPRT_WITH_DYNAMIC_CRYPT_R
35#endif
36#if (defined(RT_OS_LINUX) || defined(RT_OS_OS2)) && !defined(_GNU_SOURCE)
37# define _GNU_SOURCE
38#endif
39#if defined(RT_OS_LINUX) && !defined(_XOPEN_SOURCE)
40# define _XOPEN_SOURCE 700 /* for newlocale */
41#endif
42
43#ifdef RT_OS_OS2
44# define crypt unistd_crypt
45# define setkey unistd_setkey
46# define encrypt unistd_encrypt
47# include <unistd.h>
48# undef crypt
49# undef setkey
50# undef encrypt
51#else
52# include <unistd.h>
53#endif
54#include <stdlib.h>
55#include <errno.h>
56#include <langinfo.h>
57#include <locale.h>
58#include <sys/types.h>
59#include <sys/stat.h>
60#include <sys/wait.h>
61#include <fcntl.h>
62#include <signal.h>
63#include <grp.h>
64#include <pwd.h>
65#if defined(RT_OS_LINUX) || defined(RT_OS_OS2) || defined(RT_OS_SOLARIS)
66# include <crypt.h>
67#endif
68#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
69# include <shadow.h>
70#endif
71
72#if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
73/* While Solaris has posix_spawn() of course we don't want to use it as
74 * we need to have the child in a different process contract, no matter
75 * whether it is started detached or not. */
76# define HAVE_POSIX_SPAWN 1
77#endif
78#if defined(RT_OS_DARWIN) && defined(MAC_OS_X_VERSION_MIN_REQUIRED)
79# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
80# define HAVE_POSIX_SPAWN 1
81# endif
82#endif
83#ifdef HAVE_POSIX_SPAWN
84# include <spawn.h>
85#endif
86
87#if !defined(IPRT_USE_PAM) \
88 && !defined(IPRT_WITHOUT_PAM) \
89 && ( defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) )
90# define IPRT_USE_PAM
91#endif
92#ifdef IPRT_USE_PAM
93# include <security/pam_appl.h>
94# include <stdlib.h>
95# include <dlfcn.h>
96# include <iprt/asm.h>
97#endif
98
99#ifdef RT_OS_SOLARIS
100# include <limits.h>
101# include <sys/ctfs.h>
102# include <sys/contract/process.h>
103# include <libcontract.h>
104#endif
105
106#ifndef RT_OS_SOLARIS
107# include <paths.h>
108#else
109# define _PATH_MAILDIR "/var/mail"
110# define _PATH_DEFPATH "/usr/bin:/bin"
111# define _PATH_STDPATH "/sbin:/usr/sbin:/bin:/usr/bin"
112#endif
113#ifndef _PATH_BSHELL
114# define _PATH_BSHELL "/bin/sh"
115#endif
116
117
118#include <iprt/process.h>
119#include "internal/iprt.h"
120
121#include <iprt/alloca.h>
122#include <iprt/assert.h>
123#include <iprt/ctype.h>
124#include <iprt/env.h>
125#include <iprt/err.h>
126#include <iprt/file.h>
127#if defined(IPRT_WITH_DYNAMIC_CRYPT_R) || defined(IPRT_USE_PAM)
128# include <iprt/ldr.h>
129#endif
130#include <iprt/log.h>
131#include <iprt/path.h>
132#include <iprt/pipe.h>
133#include <iprt/socket.h>
134#include <iprt/string.h>
135#include <iprt/mem.h>
136#include "internal/process.h"
137#include "internal/path.h"
138#include "internal/string.h"
139
140
141/*********************************************************************************************************************************
142* Defined Constants And Macros *
143*********************************************************************************************************************************/
144#ifdef IPRT_USE_PAM
145/*
146 * The PAM library names and version ranges to try.
147 */
148# ifdef RT_OS_DARWIN
149# include <mach-o/dyld.h>
150/** @node libpam.2.dylib was introduced with 10.6.x (OpenPAM); we use
151 * libpam.dylib as that's a symlink to the latest and greatest. */
152# define IPRT_LIBPAM_FILE_1 "libpam.dylib"
153# define IPRT_LIBPAM_FILE_1_FIRST_VER 0
154# define IPRT_LIBPAM_FILE_1_END_VER 0
155# define IPRT_LIBPAM_FILE_2 "libpam.2.dylib"
156# define IPRT_LIBPAM_FILE_2_FIRST_VER 0
157# define IPRT_LIBPAM_FILE_2_END_VER 0
158# define IPRT_LIBPAM_FILE_3 "libpam.1.dylib"
159# define IPRT_LIBPAM_FILE_3_FIRST_VER 0
160# define IPRT_LIBPAM_FILE_3_END_VER 0
161# elif RT_OS_LINUX
162# define IPRT_LIBPAM_FILE_1 "libpam.so.0"
163# define IPRT_LIBPAM_FILE_1_FIRST_VER 0
164# define IPRT_LIBPAM_FILE_1_END_VER 0
165# define IPRT_LIBPAM_FILE_2 "libpam.so"
166# define IPRT_LIBPAM_FILE_2_FIRST_VER 16
167# define IPRT_LIBPAM_FILE_2_END_VER 1
168# else
169# define IPRT_LIBPAM_FILE_1 "libpam.so"
170# define IPRT_LIBPAM_FILE_1_FIRST_VER 16
171# define IPRT_LIBPAM_FILE_1_END_VER 0
172# endif
173#endif
174
175
176/*********************************************************************************************************************************
177* Structures and Typedefs *
178*********************************************************************************************************************************/
179#ifdef IPRT_USE_PAM
180/** For passing info between rtCheckCredentials and rtPamConv. */
181typedef struct RTPROCPAMARGS
182{
183 const char *pszUser;
184 const char *pszPassword;
185} RTPROCPAMARGS;
186/** Pointer to rtPamConv argument package. */
187typedef RTPROCPAMARGS *PRTPROCPAMARGS;
188#endif
189
190
191/*********************************************************************************************************************************
192* Global Variables *
193*********************************************************************************************************************************/
194/** Environment dump marker used with CSH. */
195static const char g_szEnvMarkerBegin[] = "IPRT_EnvEnvEnv_Begin_EnvEnvEnv";
196/** Environment dump marker used with CSH. */
197static const char g_szEnvMarkerEnd[] = "IPRT_EnvEnvEnv_End_EnvEnvEnv";
198
199
200/*********************************************************************************************************************************
201* Internal Functions *
202*********************************************************************************************************************************/
203static int rtProcPosixCreateInner(const char *pszExec, const char * const *papszArgs, RTENV hEnv, RTENV hEnvToUse,
204 uint32_t fFlags, const char *pszAsUser, uid_t uid, gid_t gid,
205 unsigned cRedirFds, int *paRedirFds, PRTPROCESS phProcess);
206
207
208#ifdef IPRT_USE_PAM
209/**
210 * Worker for rtCheckCredentials that feeds password and maybe username to PAM.
211 *
212 * @returns PAM status.
213 * @param cMessages Number of messages.
214 * @param papMessages Message vector.
215 * @param ppaResponses Where to put our responses.
216 * @param pvAppData Pointer to RTPROCPAMARGS.
217 */
218static int rtPamConv(int cMessages, const struct pam_message **papMessages, struct pam_response **ppaResponses, void *pvAppData)
219{
220 LogFlow(("rtPamConv: cMessages=%d\n", cMessages));
221 PRTPROCPAMARGS pArgs = (PRTPROCPAMARGS)pvAppData;
222 AssertPtrReturn(pArgs, PAM_CONV_ERR);
223
224 struct pam_response *paResponses = (struct pam_response *)calloc(cMessages, sizeof(paResponses[0]));
225 AssertReturn(paResponses, PAM_CONV_ERR);
226 for (int i = 0; i < cMessages; i++)
227 {
228 LogFlow(("rtPamConv: #%d: msg_style=%d msg=%s\n", i, papMessages[i]->msg_style, papMessages[i]->msg));
229
230 paResponses[i].resp_retcode = 0;
231 if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_OFF)
232 paResponses[i].resp = strdup(pArgs->pszPassword);
233 else if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_ON)
234 paResponses[i].resp = strdup(pArgs->pszUser);
235 else
236 {
237 paResponses[i].resp = NULL;
238 continue;
239 }
240 if (paResponses[i].resp == NULL)
241 {
242 while (i-- > 0)
243 free(paResponses[i].resp);
244 free(paResponses);
245 LogFlow(("rtPamConv: out of memory\n"));
246 return PAM_CONV_ERR;
247 }
248 }
249
250 *ppaResponses = paResponses;
251 return PAM_SUCCESS;
252}
253
254
255/**
256 * Common PAM driver for rtCheckCredentials and the case where pszAsUser is NULL
257 * but RTPROC_FLAGS_PROFILE is set.
258 *
259 * @returns IPRT status code.
260 * @param pszPamService The PAM service to use for the run.
261 * @param pszUser The user.
262 * @param pszPassword The password.
263 * @param ppapszEnv Where to return PAM environment variables, NULL is
264 * fine if no variables to return. Call
265 * rtProcPosixFreePamEnv to free. Optional, so NULL
266 * can be passed in.
267 * @param pfMayFallBack Where to return whether a fallback to crypt is
268 * acceptable or if the failure result is due to
269 * authentication failing. Optional.
270 */
271static int rtProcPosixAuthenticateUsingPam(const char *pszPamService, const char *pszUser, const char *pszPassword,
272 char ***ppapszEnv, bool *pfMayFallBack)
273{
274 if (pfMayFallBack)
275 *pfMayFallBack = true;
276
277 /*
278 * Dynamically load pam the first time we go thru here.
279 */
280 static int (*s_pfnPamStart)(const char *, const char *, struct pam_conv *, pam_handle_t **);
281 static int (*s_pfnPamAuthenticate)(pam_handle_t *, int);
282 static int (*s_pfnPamAcctMgmt)(pam_handle_t *, int);
283 static int (*s_pfnPamSetItem)(pam_handle_t *, int, const void *);
284 static int (*s_pfnPamSetCred)(pam_handle_t *, int);
285 static char ** (*s_pfnPamGetEnvList)(pam_handle_t *);
286 static int (*s_pfnPamOpenSession)(pam_handle_t *, int);
287 static int (*s_pfnPamCloseSession)(pam_handle_t *, int);
288 static int (*s_pfnPamEnd)(pam_handle_t *, int);
289 if ( s_pfnPamStart == NULL
290 || s_pfnPamAuthenticate == NULL
291 || s_pfnPamAcctMgmt == NULL
292 || s_pfnPamSetItem == NULL
293 || s_pfnPamEnd == NULL)
294 {
295 RTLDRMOD hModPam = NIL_RTLDRMOD;
296 const char *pszLast;
297 int rc = RTLdrLoadSystemEx(pszLast = IPRT_LIBPAM_FILE_1, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD
298 | RTLDRLOAD_FLAGS_SO_VER_RANGE(IPRT_LIBPAM_FILE_1_FIRST_VER, IPRT_LIBPAM_FILE_1_END_VER),
299 &hModPam);
300# ifdef IPRT_LIBPAM_FILE_2
301 if (RT_FAILURE(rc))
302 rc = RTLdrLoadSystemEx(pszLast = IPRT_LIBPAM_FILE_2, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD
303 | RTLDRLOAD_FLAGS_SO_VER_RANGE(IPRT_LIBPAM_FILE_2_FIRST_VER, IPRT_LIBPAM_FILE_2_END_VER),
304 &hModPam);
305# endif
306# ifdef IPRT_LIBPAM_FILE_3
307 if (RT_FAILURE(rc))
308 rc = RTLdrLoadSystemEx(pszLast = IPRT_LIBPAM_FILE_3, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD
309 | RTLDRLOAD_FLAGS_SO_VER_RANGE(IPRT_LIBPAM_FILE_3_FIRST_VER, IPRT_LIBPAM_FILE_3_END_VER),
310 &hModPam);
311# endif
312 if (RT_FAILURE(rc))
313 {
314 LogRelMax(10, ("failed to load %s: %Rrc\n", pszLast, rc));
315 return VERR_AUTHENTICATION_FAILURE;
316 }
317
318 *(uintptr_t *)&s_pfnPamStart = (uintptr_t)RTLdrGetFunction(hModPam, "pam_start");
319 *(uintptr_t *)&s_pfnPamAuthenticate = (uintptr_t)RTLdrGetFunction(hModPam, "pam_authenticate");
320 *(uintptr_t *)&s_pfnPamAcctMgmt = (uintptr_t)RTLdrGetFunction(hModPam, "pam_acct_mgmt");
321 *(uintptr_t *)&s_pfnPamSetItem = (uintptr_t)RTLdrGetFunction(hModPam, "pam_set_item");
322 *(uintptr_t *)&s_pfnPamSetCred = (uintptr_t)RTLdrGetFunction(hModPam, "pam_setcred");
323 *(uintptr_t *)&s_pfnPamGetEnvList = (uintptr_t)RTLdrGetFunction(hModPam, "pam_getenvlist");
324 *(uintptr_t *)&s_pfnPamOpenSession = (uintptr_t)RTLdrGetFunction(hModPam, "pam_open_session");
325 *(uintptr_t *)&s_pfnPamCloseSession = (uintptr_t)RTLdrGetFunction(hModPam, "pam_close_session");
326 *(uintptr_t *)&s_pfnPamEnd = (uintptr_t)RTLdrGetFunction(hModPam, "pam_end");
327 ASMCompilerBarrier();
328
329 RTLdrClose(hModPam);
330
331 if ( s_pfnPamStart == NULL
332 || s_pfnPamAuthenticate == NULL
333 || s_pfnPamAcctMgmt == NULL
334 || s_pfnPamSetItem == NULL
335 || s_pfnPamEnd == NULL)
336 {
337 LogRelMax(10, ("failed to resolve symbols: %p %p %p %p %p\n",
338 s_pfnPamStart, s_pfnPamAuthenticate, s_pfnPamAcctMgmt, s_pfnPamSetItem, s_pfnPamEnd));
339 return VERR_AUTHENTICATION_FAILURE;
340 }
341 }
342
343# define pam_start s_pfnPamStart
344# define pam_authenticate s_pfnPamAuthenticate
345# define pam_acct_mgmt s_pfnPamAcctMgmt
346# define pam_set_item s_pfnPamSetItem
347# define pam_setcred s_pfnPamSetCred
348# define pam_getenvlist s_pfnPamGetEnvList
349# define pam_open_session s_pfnPamOpenSession
350# define pam_close_session s_pfnPamCloseSession
351# define pam_end s_pfnPamEnd
352
353 /*
354 * Do the PAM stuff.
355 */
356 pam_handle_t *hPam = NULL;
357 RTPROCPAMARGS PamConvArgs = { pszUser, pszPassword };
358 struct pam_conv PamConversation;
359 RT_ZERO(PamConversation);
360 PamConversation.appdata_ptr = &PamConvArgs;
361 PamConversation.conv = rtPamConv;
362 int rc = pam_start(pszPamService, pszUser, &PamConversation, &hPam);
363 if (rc == PAM_SUCCESS)
364 {
365 rc = pam_set_item(hPam, PAM_RUSER, pszUser);
366 LogRel2(("rtProcPosixAuthenticateUsingPam(%s): pam_setitem/PAM_RUSER: %s\n", pszPamService, pszUser));
367 if (rc == PAM_SUCCESS)
368 {
369 RTENV hEnv = RTENV_DEFAULT;
370
371 /*
372 * Secure TTY fun ahead (for pam_securetty).
373 *
374 * We also need to set PAM_TTY (if available) to make PAM stacks work which
375 * require a secure TTY via pam_securetty (Debian 10 + 11, for example). See @bugref{10225}.
376 *
377 * Note! We only can try (or better: guess) to a certain amount, as it really depends on the
378 * distribution or Administrator which has set up the system which (and how) things are allowed
379 * (see /etc/securetty).
380 */
381 char szTTY[64] = { 0 };
382 int rc2 = RTEnvGetEx(hEnv, "DISPLAY", szTTY, sizeof(szTTY), NULL);
383 if (RT_FAILURE(rc2))
384 {
385 char szTTYNr[4];
386 rc2 = RTEnvGetEx(hEnv, "XDG_VTNR", szTTYNr, sizeof(szTTYNr), NULL); /* Virtual terminal hint given? */
387 if (RT_SUCCESS(rc2))
388 {
389 if (RTStrPrintf2(szTTY, sizeof(szTTY), "tty%s", szTTYNr) <= 0)
390 rc2 = VERR_BUFFER_OVERFLOW;
391 }
392 }
393
394 /* As a last resort, try the TTY's name instead. */
395 if (RT_FAILURE(rc2))
396 {
397 if (RTStrPrintf2(szTTY, sizeof(szTTY), "%s", ttyname(0)) <= 0)
398 rc2 = VERR_BUFFER_OVERFLOW;
399 }
400
401 LogRel2(("rtProcPosixAuthenticateUsingPam(%s): pam_setitem/PAM_TTY: %s, rc=%Rrc\n", pszPamService, szTTY, rc2));
402 if (!strlen(szTTY))
403 LogRel2(("rtProcPosixAuthenticateUsingPam(%s): Hint: Looks like running as a non-interactive user (no TTY/PTY).\n"
404 "Authentication requiring a secure terminal might fail.\n",
405 pszPamService));
406
407 if ( RT_SUCCESS(rc2)
408 && strlen(szTTY)) /* Only try using PAM_TTY if we have something to set. */
409 {
410 rc = pam_set_item(hPam, PAM_TTY, szTTY);
411 }
412
413 if (rc == PAM_SUCCESS)
414 {
415 /* From this point on we don't allow falling back to other auth methods. */
416 if (pfMayFallBack)
417 *pfMayFallBack = false;
418
419 rc = pam_authenticate(hPam, 0);
420 if (rc == PAM_SUCCESS)
421 {
422 rc = pam_acct_mgmt(hPam, 0);
423 if ( rc == PAM_SUCCESS
424 || rc == PAM_AUTHINFO_UNAVAIL /*??*/)
425 {
426 if ( ppapszEnv
427 && s_pfnPamGetEnvList
428 && s_pfnPamSetCred)
429 {
430 /* pam_env.so creates the environment when pam_setcred is called,. */
431 int rcSetCred = pam_setcred(hPam, PAM_ESTABLISH_CRED | PAM_SILENT);
432 /** @todo check pam_setcred status code? */
433
434 /* Unless it does it during session opening (Ubuntu 21.10). This
435 unfortunately means we might mount user dir and other crap: */
436 /** @todo do session handling properly */
437 int rcOpenSession = PAM_ABORT;
438 if ( s_pfnPamOpenSession
439 && s_pfnPamCloseSession)
440 rcOpenSession = pam_open_session(hPam, PAM_SILENT);
441
442 *ppapszEnv = pam_getenvlist(hPam);
443 LogFlowFunc(("pam_getenvlist -> %p ([0]=%p); rcSetCred=%d rcOpenSession=%d\n",
444 *ppapszEnv, *ppapszEnv ? **ppapszEnv : NULL, rcSetCred, rcOpenSession)); RT_NOREF(rcSetCred);
445
446 if (rcOpenSession == PAM_SUCCESS)
447 pam_close_session(hPam, PAM_SILENT);
448 pam_setcred(hPam, PAM_DELETE_CRED);
449 }
450
451 pam_end(hPam, PAM_SUCCESS);
452 LogFlowFunc(("pam auth (for %s) successful\n", pszPamService));
453 return VINF_SUCCESS;
454 }
455 LogFunc(("pam_acct_mgmt -> %d\n", rc));
456 }
457 else
458 LogFunc(("pam_authenticate -> %d\n", rc));
459 }
460 else
461 LogFunc(("pam_setitem/PAM_TTY -> %d\n", rc));
462 }
463 else
464 LogFunc(("pam_set_item/PAM_RUSER -> %d\n", rc));
465 pam_end(hPam, rc);
466 }
467 else
468 LogFunc(("pam_start(%s) -> %d\n", pszPamService, rc));
469
470 LogRel2(("rtProcPosixAuthenticateUsingPam(%s): Failed authenticating user '%s' with %d\n", pszPamService, pszUser, rc));
471 return VERR_AUTHENTICATION_FAILURE;
472}
473
474
475/**
476 * Checks if the given service file is present in any of the pam.d directories.
477 */
478static bool rtProcPosixPamServiceExists(const char *pszService)
479{
480 char szPath[256];
481
482 /* PAM_CONFIG_D: */
483 int rc = RTPathJoin(szPath, sizeof(szPath), "/etc/pam.d/", pszService); AssertRC(rc);
484 if (RTFileExists(szPath))
485 return true;
486
487 /* PAM_CONFIG_DIST_D: */
488 rc = RTPathJoin(szPath, sizeof(szPath), "/usr/lib/pam.d/", pszService); AssertRC(rc);
489 if (RTFileExists(szPath))
490 return true;
491
492 /* No support for PAM_CONFIG_DIST2_D. */
493 return false;
494}
495
496#endif /* IPRT_USE_PAM */
497
498
499#if defined(IPRT_WITH_DYNAMIC_CRYPT_R)
500/** Pointer to crypt_r(). */
501typedef char *(*PFNCRYPTR)(const char *, const char *, struct crypt_data *);
502
503/**
504 * Wrapper for resolving and calling crypt_r dynamically.
505 *
506 * The reason for this is that fedora 30+ wants to use libxcrypt rather than the
507 * glibc libcrypt. The two libraries has different crypt_data sizes and layout,
508 * so we allocate a 256KB data block to be on the safe size (caller does this).
509 */
510static char *rtProcDynamicCryptR(const char *pszKey, const char *pszSalt, struct crypt_data *pData)
511{
512 static PFNCRYPTR volatile s_pfnCryptR = NULL;
513 PFNCRYPTR pfnCryptR = s_pfnCryptR;
514 if (pfnCryptR)
515 return pfnCryptR(pszKey, pszSalt, pData);
516
517 pfnCryptR = (PFNCRYPTR)(uintptr_t)RTLdrGetSystemSymbolEx("libcrypt.so", "crypt_r", RTLDRLOAD_FLAGS_SO_VER_RANGE(1, 6));
518 if (!pfnCryptR)
519 pfnCryptR = (PFNCRYPTR)(uintptr_t)RTLdrGetSystemSymbolEx("libxcrypt.so", "crypt_r", RTLDRLOAD_FLAGS_SO_VER_RANGE(1, 32));
520 if (pfnCryptR)
521 {
522 s_pfnCryptR = pfnCryptR;
523 return pfnCryptR(pszKey, pszSalt, pData);
524 }
525
526 LogRel(("IPRT/RTProc: Unable to locate crypt_r!\n"));
527 return NULL;
528}
529#endif /* IPRT_WITH_DYNAMIC_CRYPT_R */
530
531
532/** Free the environment list returned by rtCheckCredentials. */
533static void rtProcPosixFreePamEnv(char **papszEnv)
534{
535 if (papszEnv)
536 {
537 for (size_t i = 0; papszEnv[i] != NULL; i++)
538 free(papszEnv[i]);
539 free(papszEnv);
540 }
541}
542
543
544/**
545 * Check the credentials and return the gid/uid of user.
546 *
547 * @param pszUser The username.
548 * @param pszPasswd The password to authenticate with.
549 * @param gid Where to store the GID of the user.
550 * @param uid Where to store the UID of the user.
551 * @param ppapszEnv Where to return PAM environment variables, NULL is fine
552 * if no variables to return. Call rtProcPosixFreePamEnv to
553 * free. Optional, so NULL can be passed in.
554 * @returns IPRT status code
555 */
556static int rtCheckCredentials(const char *pszUser, const char *pszPasswd, gid_t *pGid, uid_t *pUid, char ***ppapszEnv)
557{
558 Log(("rtCheckCredentials: pszUser=%s\n", pszUser));
559 int rc;
560
561 if (ppapszEnv)
562 *ppapszEnv = NULL;
563
564 /*
565 * Resolve user to UID and GID.
566 */
567 char achBuf[_4K];
568 struct passwd Pw;
569 struct passwd *pPw;
570 if (getpwnam_r(pszUser, &Pw, achBuf, sizeof(achBuf), &pPw) != 0)
571 return VERR_AUTHENTICATION_FAILURE;
572 if (!pPw)
573 return VERR_AUTHENTICATION_FAILURE;
574
575 *pUid = pPw->pw_uid;
576 *pGid = pPw->pw_gid;
577
578#ifdef IPRT_USE_PAM
579 /*
580 * Try authenticate using PAM, and falling back on crypto if allowed.
581 */
582 const char *pszService = "iprt-as-user";
583 if (!rtProcPosixPamServiceExists("iprt-as-user"))
584# ifdef IPRT_PAM_NATIVE_SERVICE_NAME_AS_USER
585 pszService = IPRT_PAM_NATIVE_SERVICE_NAME_AS_USER;
586# else
587 pszService = "login";
588# endif
589 bool fMayFallBack = false;
590 rc = rtProcPosixAuthenticateUsingPam(pszService, pszUser, pszPasswd, ppapszEnv, &fMayFallBack);
591 if (RT_SUCCESS(rc) || !fMayFallBack)
592 {
593 RTMemWipeThoroughly(achBuf, sizeof(achBuf), 3);
594 return rc;
595 }
596#endif
597
598#if !defined(IPRT_USE_PAM) || defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) || defined(RT_OS_OS2)
599# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
600 /*
601 * Ditto for /etc/shadow and replace pw_passwd from above if we can access it:
602 *
603 * Note! On FreeBSD and OS/2 the root user will open /etc/shadow above, so
604 * this getspnam_r step is not necessary.
605 */
606 struct spwd ShwPwd;
607 char achBuf2[_4K];
608# if defined(RT_OS_LINUX)
609 struct spwd *pShwPwd = NULL;
610 if (getspnam_r(pszUser, &ShwPwd, achBuf2, sizeof(achBuf2), &pShwPwd) != 0)
611 pShwPwd = NULL;
612# else
613 struct spwd *pShwPwd = getspnam_r(pszUser, &ShwPwd, achBuf2, sizeof(achBuf2));
614# endif
615 if (pShwPwd != NULL)
616 pPw->pw_passwd = pShwPwd->sp_pwdp;
617# endif
618
619 /*
620 * Encrypt the passed in password and see if it matches.
621 */
622# if defined(RT_OS_LINUX)
623 /* Default fCorrect=true if no password specified. In that case, pPw->pw_passwd
624 must be NULL (no password set for this user). Fail if a password is specified
625 but the user does not have one assigned. */
626 rc = !pszPasswd || !*pszPasswd ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
627 if (pPw->pw_passwd && *pPw->pw_passwd)
628# endif
629 {
630# if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
631# ifdef IPRT_WITH_DYNAMIC_CRYPT_R
632 size_t const cbCryptData = RT_MAX(sizeof(struct crypt_data) * 2, _256K);
633# else
634 size_t const cbCryptData = sizeof(struct crypt_data);
635# endif
636 struct crypt_data *pCryptData = (struct crypt_data *)RTMemTmpAllocZ(cbCryptData);
637 if (pCryptData)
638 {
639# ifdef IPRT_WITH_DYNAMIC_CRYPT_R
640 char *pszEncPasswd = rtProcDynamicCryptR(pszPasswd, pPw->pw_passwd, pCryptData);
641# else
642 char *pszEncPasswd = crypt_r(pszPasswd, pPw->pw_passwd, pCryptData);
643# endif
644 rc = pszEncPasswd && !strcmp(pszEncPasswd, pPw->pw_passwd) ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
645 RTMemWipeThoroughly(pCryptData, cbCryptData, 3);
646 RTMemTmpFree(pCryptData);
647 }
648 else
649 rc = VERR_NO_TMP_MEMORY;
650# else
651 char *pszEncPasswd = crypt(pszPasswd, pPw->pw_passwd);
652 rc = strcmp(pszEncPasswd, pPw->pw_passwd) == 0 ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
653# endif
654 }
655
656 /*
657 * Return GID and UID on success. Always wipe stack buffers.
658 */
659 if (RT_SUCCESS(rc))
660 {
661 *pGid = pPw->pw_gid;
662 *pUid = pPw->pw_uid;
663 }
664# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
665 RTMemWipeThoroughly(achBuf2, sizeof(achBuf2), 3);
666# endif
667#endif
668 RTMemWipeThoroughly(achBuf, sizeof(achBuf), 3);
669 return rc;
670}
671
672#ifdef RT_OS_SOLARIS
673
674/** @todo the error reporting of the Solaris process contract code could be
675 * a lot better, but essentially it is not meant to run into errors after
676 * the debugging phase. */
677static int rtSolarisContractPreFork(void)
678{
679 int templateFd = open64(CTFS_ROOT "/process/template", O_RDWR);
680 if (templateFd < 0)
681 return -1;
682
683 /* Set template parameters and event sets. */
684 if (ct_pr_tmpl_set_param(templateFd, CT_PR_PGRPONLY))
685 {
686 close(templateFd);
687 return -1;
688 }
689 if (ct_pr_tmpl_set_fatal(templateFd, CT_PR_EV_HWERR))
690 {
691 close(templateFd);
692 return -1;
693 }
694 if (ct_tmpl_set_critical(templateFd, 0))
695 {
696 close(templateFd);
697 return -1;
698 }
699 if (ct_tmpl_set_informative(templateFd, CT_PR_EV_HWERR))
700 {
701 close(templateFd);
702 return -1;
703 }
704
705 /* Make this the active template for the process. */
706 if (ct_tmpl_activate(templateFd))
707 {
708 close(templateFd);
709 return -1;
710 }
711
712 return templateFd;
713}
714
715static void rtSolarisContractPostForkChild(int templateFd)
716{
717 if (templateFd == -1)
718 return;
719
720 /* Clear the active template. */
721 ct_tmpl_clear(templateFd);
722 close(templateFd);
723}
724
725static void rtSolarisContractPostForkParent(int templateFd, pid_t pid)
726{
727 if (templateFd == -1)
728 return;
729
730 /* Clear the active template. */
731 int cleared = ct_tmpl_clear(templateFd);
732 close(templateFd);
733
734 /* If the clearing failed or the fork failed there's nothing more to do. */
735 if (cleared || pid <= 0)
736 return;
737
738 /* Look up the contract which was created by this thread. */
739 int statFd = open64(CTFS_ROOT "/process/latest", O_RDONLY);
740 if (statFd == -1)
741 return;
742 ct_stathdl_t statHdl;
743 if (ct_status_read(statFd, CTD_COMMON, &statHdl))
744 {
745 close(statFd);
746 return;
747 }
748 ctid_t ctId = ct_status_get_id(statHdl);
749 ct_status_free(statHdl);
750 close(statFd);
751 if (ctId < 0)
752 return;
753
754 /* Abandon this contract we just created. */
755 char ctlPath[PATH_MAX];
756 size_t len = snprintf(ctlPath, sizeof(ctlPath),
757 CTFS_ROOT "/process/%ld/ctl", (long)ctId);
758 if (len >= sizeof(ctlPath))
759 return;
760 int ctlFd = open64(ctlPath, O_WRONLY);
761 if (statFd == -1)
762 return;
763 if (ct_ctl_abandon(ctlFd) < 0)
764 {
765 close(ctlFd);
766 return;
767 }
768 close(ctlFd);
769}
770
771#endif /* RT_OS_SOLARIS */
772
773
774RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
775{
776 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
777 NULL, NULL, NULL, /* standard handles */
778 NULL /*pszAsUser*/, NULL /* pszPassword*/, NULL /*pvExtraData*/,
779 pProcess);
780}
781
782
783/**
784 * Adjust the profile environment after forking the child process and changing
785 * the UID.
786 *
787 * @returns IRPT status code.
788 * @param hEnvToUse The environment we're going to use with execve.
789 * @param fFlags The process creation flags.
790 * @param hEnv The environment passed in by the user.
791 */
792static int rtProcPosixAdjustProfileEnvFromChild(RTENV hEnvToUse, uint32_t fFlags, RTENV hEnv)
793{
794 int rc = VINF_SUCCESS;
795#ifdef RT_OS_DARWIN
796 if ( RT_SUCCESS(rc)
797 && (!(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || RTEnvExistEx(hEnv, "TMPDIR")) )
798 {
799 char szValue[RTPATH_MAX];
800 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szValue, sizeof(szValue));
801 if (cbNeeded > 0 && cbNeeded < sizeof(szValue))
802 {
803 char *pszTmp;
804 rc = RTStrCurrentCPToUtf8(&pszTmp, szValue);
805 if (RT_SUCCESS(rc))
806 {
807 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
808 RTStrFree(pszTmp);
809 }
810 }
811 else
812 rc = VERR_BUFFER_OVERFLOW;
813 }
814#else
815 RT_NOREF_PV(hEnvToUse); RT_NOREF_PV(fFlags); RT_NOREF_PV(hEnv);
816#endif
817 return rc;
818}
819
820
821/**
822 * Undos quoting and escape sequences and looks for stop characters.
823 *
824 * @returns Where to continue scanning in @a pszString. This points to the
825 * next character after the stop character, but for the zero terminator
826 * it points to the terminator character.
827 * @param pszString The string to undo quoting and escaping for.
828 * This is both input and output as the work is
829 * done in place.
830 * @param pfStoppedOnEqual Where to return whether we stopped work on a
831 * plain equal characater or not. If this is NULL,
832 * then the equal character is not a stop
833 * character, then only newline and the zero
834 * terminator are.
835 */
836static char *rtProcPosixProfileEnvUnquoteAndUnescapeString(char *pszString, bool *pfStoppedOnEqual)
837{
838 if (pfStoppedOnEqual)
839 *pfStoppedOnEqual = false;
840
841 enum { kPlain, kSingleQ, kDoubleQ } enmState = kPlain;
842 char *pszDst = pszString;
843 for (;;)
844 {
845 char ch = *pszString++;
846 switch (ch)
847 {
848 default:
849 *pszDst++ = ch;
850 break;
851
852 case '\\':
853 {
854 char ch2;
855 if ( enmState == kSingleQ
856 || (ch2 = *pszString) == '\0'
857 || (enmState == kDoubleQ && strchr("\\$`\"\n", ch2) == NULL) )
858 *pszDst++ = ch;
859 else
860 {
861 *pszDst++ = ch2;
862 pszString++;
863 }
864 break;
865 }
866
867 case '"':
868 if (enmState == kSingleQ)
869 *pszDst++ = ch;
870 else
871 enmState = enmState == kPlain ? kDoubleQ : kPlain;
872 break;
873
874 case '\'':
875 if (enmState == kDoubleQ)
876 *pszDst++ = ch;
877 else
878 enmState = enmState == kPlain ? kSingleQ : kPlain;
879 break;
880
881 case '\n':
882 if (enmState == kPlain)
883 {
884 *pszDst = '\0';
885 return pszString;
886 }
887 *pszDst++ = ch;
888 break;
889
890 case '=':
891 if (enmState == kPlain && pfStoppedOnEqual)
892 {
893 *pszDst = '\0';
894 *pfStoppedOnEqual = true;
895 return pszString;
896 }
897 *pszDst++ = ch;
898 break;
899
900 case '\0':
901 Assert(enmState == kPlain);
902 *pszDst = '\0';
903 return pszString - 1;
904 }
905 }
906}
907
908
909/**
910 * Worker for rtProcPosixProfileEnvRunAndHarvest that parses the environment
911 * dump and loads it into hEnvToUse.
912 *
913 * @note This isn't entirely correct should any of the profile setup scripts
914 * unset any of the environment variables in the basic initial
915 * enviornment, but since that's unlikely and it's very convenient to
916 * have something half sensible as a basis if don't don't grok the dump
917 * entirely and would skip central stuff like PATH or HOME.
918 *
919 * @returns IPRT status code.
920 * @retval -VERR_PARSE_ERROR (positive, e.g. warning) if we run into trouble.
921 * @retval -VERR_INVALID_UTF8_ENCODING (positive, e.g. warning) if there are
922 * invalid UTF-8 in the environment. This isn't unlikely if the
923 * profile doesn't use UTF-8. This is unfortunately not something we
924 * can guess to accurately up front, so we don't do any guessing and
925 * hope everyone is sensible and use UTF-8.
926 *
927 * @param hEnvToUse The basic environment to extend with what we manage
928 * to parse here.
929 * @param pszEnvDump The environment dump to parse. Nominally in Bourne
930 * shell 'export -p' format.
931 * @param fWithMarkers Whether there are markers around the dump (C shell,
932 * tmux) or not.
933 */
934static int rtProcPosixProfileEnvHarvest(RTENV hEnvToUse, char *pszEnvDump, bool fWithMarkers)
935{
936 LogRel3(("**** pszEnvDump start ****\n%s**** pszEnvDump end ****\n", pszEnvDump));
937 if (!LogIs3Enabled())
938 LogFunc(("**** pszEnvDump start ****\n%s**** pszEnvDump end ****\n", pszEnvDump));
939
940 /*
941 * Clip dump at markers if we're using them (C shell).
942 */
943 if (fWithMarkers)
944 {
945 char *pszStart = strstr(pszEnvDump, g_szEnvMarkerBegin);
946 AssertReturn(pszStart, -VERR_PARSE_ERROR);
947 pszStart += sizeof(g_szEnvMarkerBegin) - 1;
948 if (*pszStart == '\n')
949 pszStart++;
950 pszEnvDump = pszStart;
951
952 char *pszEnd = strstr(pszStart, g_szEnvMarkerEnd);
953 AssertReturn(pszEnd, -VERR_PARSE_ERROR);
954 *pszEnd = '\0';
955 }
956
957 /*
958 * Since we're using /bin/sh -c "export -p" for all the dumping, we should
959 * always get lines on the format:
960 * export VAR1="Value 1"
961 * export VAR2=Value2
962 *
963 * However, just in case something goes wrong, like bash doesn't think it
964 * needs to be posixly correct, try deal with the alternative where
965 * "declare -x " replaces the "export".
966 */
967 const char *pszPrefix;
968 if ( strncmp(pszEnvDump, RT_STR_TUPLE("export")) == 0
969 && RT_C_IS_BLANK(pszEnvDump[6]))
970 pszPrefix = "export ";
971 else if ( strncmp(pszEnvDump, RT_STR_TUPLE("declare")) == 0
972 && RT_C_IS_BLANK(pszEnvDump[7])
973 && pszEnvDump[8] == '-')
974 pszPrefix = "declare -x "; /* We only need to care about the non-array, non-function lines. */
975 else
976 AssertFailedReturn(-VERR_PARSE_ERROR);
977 size_t const cchPrefix = strlen(pszPrefix);
978
979 /*
980 * Process the lines, ignoring stuff which we don't grok.
981 * The shell should quote problematic characters. Bash double quotes stuff
982 * by default, whereas almquist's shell does it as needed and only the value
983 * side.
984 */
985 int rc = VINF_SUCCESS;
986 while (pszEnvDump && *pszEnvDump != '\0')
987 {
988 /*
989 * Skip the prefixing command.
990 */
991 if ( cchPrefix == 0
992 || strncmp(pszEnvDump, pszPrefix, cchPrefix) == 0)
993 {
994 pszEnvDump += cchPrefix;
995 while (RT_C_IS_BLANK(*pszEnvDump))
996 pszEnvDump++;
997 }
998 else
999 {
1000 /* Oops, must find our bearings for some reason... */
1001 pszEnvDump = strchr(pszEnvDump, '\n');
1002 rc = -VERR_PARSE_ERROR;
1003 continue;
1004 }
1005
1006 /*
1007 * Parse out the variable name using typical bourne shell escaping
1008 * and quoting rules.
1009 */
1010 /** @todo We should throw away lines that aren't propertly quoted, now we
1011 * just continue and use what we found. */
1012 const char *pszVar = pszEnvDump;
1013 bool fStoppedOnPlainEqual = false;
1014 pszEnvDump = rtProcPosixProfileEnvUnquoteAndUnescapeString(pszEnvDump, &fStoppedOnPlainEqual);
1015 const char *pszValue = pszEnvDump;
1016 if (fStoppedOnPlainEqual)
1017 pszEnvDump = rtProcPosixProfileEnvUnquoteAndUnescapeString(pszEnvDump, NULL /*pfStoppedOnPlainEqual*/);
1018 else
1019 pszValue = "";
1020
1021 /*
1022 * Add them if valid UTF-8, otherwise we simply drop them for now.
1023 * The whole codeset stuff goes seriously wonky here as the environment
1024 * we're harvesting probably contains it's own LC_CTYPE or LANG variables,
1025 * so ignore the problem for now.
1026 */
1027 if ( RTStrIsValidEncoding(pszVar)
1028 && RTStrIsValidEncoding(pszValue))
1029 {
1030 int rc2 = RTEnvSetEx(hEnvToUse, pszVar, pszValue);
1031 AssertRCReturn(rc2, rc2);
1032 }
1033 else if (rc == VINF_SUCCESS)
1034 rc = -VERR_INVALID_UTF8_ENCODING;
1035 }
1036
1037 return rc;
1038}
1039
1040
1041/**
1042 * Runs the user's shell in login mode with some environment dumping logic and
1043 * harvests the dump, putting it into hEnvToUse.
1044 *
1045 * This is a bit hairy, esp. with regards to codesets.
1046 *
1047 * @returns IPRT status code. Not all error statuses will be returned and the
1048 * caller should just continue with whatever is in hEnvToUse.
1049 *
1050 * @param hEnvToUse On input this is the basic user environment, on success
1051 * in is fleshed out with stuff from the login shell dump.
1052 * @param pszAsUser The user name for the profile.
1053 * @param uid The UID corrsponding to @a pszAsUser, ~0 if current user.
1054 * @param gid The GID corrsponding to @a pszAsUser, ~0 if current user.
1055 * @param pszShell The login shell. This is a writable string to avoid
1056 * needing to make a copy of it when examining the path
1057 * part, instead we make a temporary change to it which is
1058 * always reverted before returning.
1059 */
1060static int rtProcPosixProfileEnvRunAndHarvest(RTENV hEnvToUse, const char *pszAsUser, uid_t uid, gid_t gid, char *pszShell)
1061{
1062 LogFlowFunc(("pszAsUser=%s uid=%u gid=%u pszShell=%s; hEnvToUse contains %u variables on entry\n",
1063 pszAsUser, uid, gid, pszShell, RTEnvCountEx(hEnvToUse) ));
1064
1065 /*
1066 * The three standard handles should be pointed to /dev/null, the 3rd handle
1067 * is used to dump the environment.
1068 */
1069 RTPIPE hPipeR, hPipeW;
1070 int rc = RTPipeCreate(&hPipeR, &hPipeW, 0);
1071 if (RT_SUCCESS(rc))
1072 {
1073 RTFILE hFileNull;
1074 rc = RTFileOpenBitBucket(&hFileNull, RTFILE_O_READWRITE);
1075 if (RT_SUCCESS(rc))
1076 {
1077 int aRedirFds[4];
1078 aRedirFds[0] = aRedirFds[1] = aRedirFds[2] = RTFileToNative(hFileNull);
1079 aRedirFds[3] = RTPipeToNative(hPipeW);
1080
1081 /*
1082 * Allocate a buffer for receiving the environment dump.
1083 *
1084 * This is fixed sized for simplicity and safety (creative user script
1085 * shouldn't be allowed to exhaust our memory or such, after all we're
1086 * most likely running with root privileges in this code path).
1087 */
1088 size_t const cbEnvDump = _64K;
1089 char * const pszEnvDump = (char *)RTMemTmpAllocZ(cbEnvDump);
1090 if (pszEnvDump)
1091 {
1092 /*
1093 * Our default approach is using /bin/sh:
1094 */
1095 const char *pszExec = _PATH_BSHELL;
1096 const char *apszArgs[8];
1097 apszArgs[0] = "-sh"; /* First arg must start with a dash for login shells. */
1098 apszArgs[1] = "-c";
1099 apszArgs[2] = "POSIXLY_CORRECT=1;export -p >&3";
1100 apszArgs[3] = NULL;
1101
1102 /*
1103 * But see if we can trust the shell to be a real usable shell.
1104 * This would be great as different shell typically has different profile setup
1105 * files and we'll endup with the wrong enviornment if we use a different shell.
1106 */
1107 char szDashShell[32];
1108 char szExportArg[128];
1109 bool fWithMarkers = false;
1110 const char *pszShellNm = RTPathFilename(pszShell);
1111 if ( pszShellNm
1112 && access(pszShellNm, X_OK))
1113 {
1114 /*
1115 * First the check that it's a known bin directory:
1116 */
1117 size_t const cchShellPath = pszShellNm - pszShell;
1118 char const chSaved = pszShell[cchShellPath - 1];
1119 pszShell[cchShellPath - 1] = '\0';
1120 if ( RTPathCompare(pszShell, "/bin") == 0
1121 || RTPathCompare(pszShell, "/usr/bin") == 0
1122 || RTPathCompare(pszShell, "/usr/local/bin") == 0)
1123 {
1124 /*
1125 * Then see if we recognize the shell name.
1126 */
1127 RTStrCopy(&szDashShell[1], sizeof(szDashShell) - 1, pszShellNm);
1128 szDashShell[0] = '-';
1129 if ( strcmp(pszShellNm, "bash") == 0
1130 || strcmp(pszShellNm, "ksh") == 0
1131 || strcmp(pszShellNm, "ksh93") == 0
1132 || strcmp(pszShellNm, "zsh") == 0
1133 || strcmp(pszShellNm, "fish") == 0)
1134 {
1135 pszExec = pszShell;
1136 apszArgs[0] = szDashShell;
1137
1138 /* Use /bin/sh for doing the environment dumping so we get the same kind
1139 of output from everyone and can limit our parsing + testing efforts. */
1140 RTStrPrintf(szExportArg, sizeof(szExportArg),
1141 "%s -c 'POSIXLY_CORRECT=1;export -p >&3'", _PATH_BSHELL);
1142 apszArgs[2] = szExportArg;
1143 }
1144 /* C shell is very annoying in that it closes fd 3 without regard to what
1145 we might have put there, so we must use stdout here but with markers so
1146 we can find the dump.
1147 Seems tmux have similar issues as it doesn't work above, but works fine here. */
1148 else if ( strcmp(pszShellNm, "csh") == 0
1149 || strcmp(pszShellNm, "tcsh") == 0
1150 || strcmp(pszShellNm, "tmux") == 0)
1151 {
1152 pszExec = pszShell;
1153 apszArgs[0] = szDashShell;
1154
1155 fWithMarkers = true;
1156 size_t cch = RTStrPrintf(szExportArg, sizeof(szExportArg),
1157 "%s -c 'set -e;POSIXLY_CORRECT=1;echo %s;export -p;echo %s'",
1158 _PATH_BSHELL, g_szEnvMarkerBegin, g_szEnvMarkerEnd);
1159 Assert(cch < sizeof(szExportArg) - 1); RT_NOREF(cch);
1160 apszArgs[2] = szExportArg;
1161
1162 aRedirFds[1] = aRedirFds[3];
1163 aRedirFds[3] = -1;
1164 }
1165 }
1166 pszShell[cchShellPath - 1] = chSaved;
1167 }
1168
1169 /*
1170 * Create the process and wait for the output.
1171 */
1172 LogFunc(("Executing '%s': '%s', '%s', '%s'\n", pszExec, apszArgs[0], apszArgs[1], apszArgs[2]));
1173 RTPROCESS hProcess = NIL_RTPROCESS;
1174 rc = rtProcPosixCreateInner(pszExec, apszArgs, hEnvToUse, hEnvToUse, 0 /*fFlags*/,
1175 pszAsUser, uid, gid, RT_ELEMENTS(aRedirFds), aRedirFds, &hProcess);
1176 if (RT_SUCCESS(rc))
1177 {
1178 RTPipeClose(hPipeW);
1179 hPipeW = NIL_RTPIPE;
1180
1181 size_t offEnvDump = 0;
1182 uint64_t const msStart = RTTimeMilliTS();
1183 for (;;)
1184 {
1185 size_t cbRead = 0;
1186 if (offEnvDump < cbEnvDump - 1)
1187 {
1188 rc = RTPipeRead(hPipeR, &pszEnvDump[offEnvDump], cbEnvDump - 1 - offEnvDump, &cbRead);
1189 if (RT_SUCCESS(rc))
1190 offEnvDump += cbRead;
1191 else
1192 {
1193 LogFlowFunc(("Breaking out of read loop: %Rrc\n", rc));
1194 if (rc == VERR_BROKEN_PIPE)
1195 rc = VINF_SUCCESS;
1196 break;
1197 }
1198 pszEnvDump[offEnvDump] = '\0';
1199 }
1200 else
1201 {
1202 LogFunc(("Too much data in environment dump, dropping it\n"));
1203 rc = VERR_TOO_MUCH_DATA;
1204 break;
1205 }
1206
1207 /* Do the timout check. */
1208 uint64_t const cMsElapsed = RTTimeMilliTS() - msStart;
1209 if (cMsElapsed >= RT_MS_15SEC)
1210 {
1211 LogFunc(("Timed out after %RU64 ms\n", cMsElapsed));
1212 rc = VERR_TIMEOUT;
1213 break;
1214 }
1215
1216 /* If we got no data in above wait for more to become ready. */
1217 if (!cbRead)
1218 RTPipeSelectOne(hPipeR, RT_MS_15SEC - cMsElapsed);
1219 }
1220
1221 /*
1222 * Kill the process and wait for it to avoid leaving zombies behind.
1223 */
1224 /** @todo do we check the exit code? */
1225 int rc2 = RTProcWait(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, NULL);
1226 if (RT_SUCCESS(rc2))
1227 LogFlowFunc(("First RTProcWait succeeded\n"));
1228 else
1229 {
1230 LogFunc(("First RTProcWait failed (%Rrc), terminating and doing a blocking wait\n", rc2));
1231 RTProcTerminate(hProcess);
1232 RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, NULL);
1233 }
1234
1235 /*
1236 * Parse the result.
1237 */
1238 if (RT_SUCCESS(rc))
1239 rc = rtProcPosixProfileEnvHarvest(hEnvToUse, pszEnvDump, fWithMarkers);
1240 else
1241 {
1242 LogFunc(("Ignoring rc=%Rrc from the pipe read loop and continues with basic environment\n", rc));
1243 rc = -rc;
1244 }
1245 }
1246 else
1247 LogFunc(("Failed to create process '%s': %Rrc\n", pszExec, rc));
1248 RTMemTmpFree(pszEnvDump);
1249 }
1250 else
1251 {
1252 LogFunc(("Failed to allocate %#zx bytes for the dump\n", cbEnvDump));
1253 rc = VERR_NO_TMP_MEMORY;
1254 }
1255 RTFileClose(hFileNull);
1256 }
1257 else
1258 LogFunc(("Failed to open /dev/null: %Rrc\n", rc));
1259 RTPipeClose(hPipeR);
1260 RTPipeClose(hPipeW);
1261 }
1262 else
1263 LogFunc(("Failed to create pipe: %Rrc\n", rc));
1264 LogFlowFunc(("returns %Rrc (hEnvToUse contains %u variables now)\n", rc, RTEnvCountEx(hEnvToUse)));
1265 return rc;
1266}
1267
1268
1269/**
1270 * Create an environment for the given user.
1271 *
1272 * This starts by creating a very basic environment and then tries to do it
1273 * properly by running the user's shell in login mode with some environment
1274 * dumping attached. The latter may fail and we'll ignore that for now and move
1275 * ahead with the very basic environment.
1276 *
1277 * @returns IPRT status code.
1278 * @param phEnvToUse Where to return the created environment.
1279 * @param pszAsUser The user name for the profile. NULL if the current
1280 * user.
1281 * @param uid The UID corrsponding to @a pszAsUser, ~0 if NULL.
1282 * @param gid The GID corrsponding to @a pszAsUser, ~0 if NULL.
1283 * @param fFlags RTPROC_FLAGS_XXX
1284 * @param papszPamEnv Array of environment variables returned by PAM, if
1285 * it was used for authentication and produced anything.
1286 * Otherwise NULL.
1287 */
1288static int rtProcPosixCreateProfileEnv(PRTENV phEnvToUse, const char *pszAsUser, uid_t uid, gid_t gid,
1289 uint32_t fFlags, char **papszPamEnv)
1290{
1291 /*
1292 * Get the passwd entry for the user.
1293 */
1294 struct passwd Pwd;
1295 struct passwd *pPwd = NULL;
1296 char achBuf[_4K];
1297 int rc;
1298 errno = 0;
1299 if (pszAsUser)
1300 rc = getpwnam_r(pszAsUser, &Pwd, achBuf, sizeof(achBuf), &pPwd);
1301 else
1302 rc = getpwuid_r(getuid(), &Pwd, achBuf, sizeof(achBuf), &pPwd);
1303 if (rc == 0 && pPwd)
1304 {
1305 /*
1306 * Convert stuff to UTF-8 since the environment is UTF-8.
1307 */
1308 char *pszDir;
1309 rc = RTStrCurrentCPToUtf8(&pszDir, pPwd->pw_dir);
1310 if (RT_SUCCESS(rc))
1311 {
1312#if 0 /* Enable and modify this to test shells other that your login shell. */
1313 pPwd->pw_shell = (char *)"/bin/tmux";
1314#endif
1315 char *pszShell;
1316 rc = RTStrCurrentCPToUtf8(&pszShell, pPwd->pw_shell);
1317 if (RT_SUCCESS(rc))
1318 {
1319 char *pszAsUserFree = NULL;
1320 if (!pszAsUser)
1321 {
1322 rc = RTStrCurrentCPToUtf8(&pszAsUserFree, pPwd->pw_name);
1323 if (RT_SUCCESS(rc))
1324 pszAsUser = pszAsUserFree;
1325 }
1326 if (RT_SUCCESS(rc))
1327 {
1328 /*
1329 * Create and populate the environment.
1330 */
1331 rc = RTEnvCreate(phEnvToUse);
1332 if (RT_SUCCESS(rc))
1333 {
1334 RTENV hEnvToUse = *phEnvToUse;
1335 rc = RTEnvSetEx(hEnvToUse, "HOME", pszDir);
1336 if (RT_SUCCESS(rc))
1337 rc = RTEnvSetEx(hEnvToUse, "SHELL", pszShell);
1338 if (RT_SUCCESS(rc))
1339 rc = RTEnvSetEx(hEnvToUse, "USER", pszAsUser);
1340 if (RT_SUCCESS(rc))
1341 rc = RTEnvSetEx(hEnvToUse, "LOGNAME", pszAsUser);
1342 if (RT_SUCCESS(rc))
1343 rc = RTEnvSetEx(hEnvToUse, "PATH", pPwd->pw_uid == 0 ? _PATH_STDPATH : _PATH_DEFPATH);
1344 char szTmpPath[RTPATH_MAX];
1345 if (RT_SUCCESS(rc))
1346 {
1347 RTStrPrintf(szTmpPath, sizeof(szTmpPath), "%s/%s", _PATH_MAILDIR, pszAsUser);
1348 rc = RTEnvSetEx(hEnvToUse, "MAIL", szTmpPath);
1349 }
1350#ifdef RT_OS_DARWIN
1351 if (RT_SUCCESS(rc))
1352 {
1353 /* TMPDIR is some unique per user directory under /var/folders on darwin,
1354 so get the one for the current user. If we're launching the process as
1355 a different user, rtProcPosixAdjustProfileEnvFromChild will update it
1356 again for the actual child process user (provided we set it here). See
1357 https://opensource.apple.com/source/Libc/Libc-997.1.1/darwin/_dirhelper.c
1358 for the implementation of this query. */
1359 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szTmpPath, sizeof(szTmpPath));
1360 if (cbNeeded > 0 && cbNeeded < sizeof(szTmpPath))
1361 {
1362 char *pszTmp;
1363 rc = RTStrCurrentCPToUtf8(&pszTmp, szTmpPath);
1364 if (RT_SUCCESS(rc))
1365 {
1366 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
1367 RTStrFree(pszTmp);
1368 }
1369 }
1370 else
1371 rc = VERR_BUFFER_OVERFLOW;
1372 }
1373#endif
1374 /*
1375 * Add everything from the PAM environment.
1376 */
1377 if (RT_SUCCESS(rc) && papszPamEnv != NULL)
1378 for (size_t i = 0; papszPamEnv[i] != NULL && RT_SUCCESS(rc); i++)
1379 {
1380 char *pszEnvVar;
1381 rc = RTStrCurrentCPToUtf8(&pszEnvVar, papszPamEnv[i]);
1382 if (RT_SUCCESS(rc))
1383 {
1384 char *pszValue = strchr(pszEnvVar, '=');
1385 if (pszValue)
1386 *pszValue++ = '\0';
1387 rc = RTEnvSetEx(hEnvToUse, pszEnvVar, pszValue ? pszValue : "");
1388 RTStrFree(pszEnvVar);
1389 }
1390 /* Ignore conversion issue, though LogRel them. */
1391 else if (rc != VERR_NO_STR_MEMORY && rc != VERR_NO_MEMORY)
1392 {
1393 LogRelMax(256, ("RTStrCurrentCPToUtf8(,%.*Rhxs) -> %Rrc\n", strlen(pszEnvVar), pszEnvVar, rc));
1394 rc = -rc;
1395 }
1396 }
1397 if (RT_SUCCESS(rc))
1398 {
1399 /*
1400 * Now comes the fun part where we need to try run a shell in login mode
1401 * and harvest its final environment to get the proper environment for
1402 * the user. We ignore some failures here so buggy login scrips and
1403 * other weird stuff won't trip us up too badly.
1404 */
1405 if (!(fFlags & RTPROC_FLAGS_ONLY_BASIC_PROFILE))
1406 rc = rtProcPosixProfileEnvRunAndHarvest(hEnvToUse, pszAsUser, uid, gid, pszShell);
1407 }
1408
1409 if (RT_FAILURE(rc))
1410 RTEnvDestroy(hEnvToUse);
1411 }
1412 RTStrFree(pszAsUserFree);
1413 }
1414 RTStrFree(pszShell);
1415 }
1416 RTStrFree(pszDir);
1417 }
1418 }
1419 else
1420 rc = errno ? RTErrConvertFromErrno(errno) : VERR_ACCESS_DENIED;
1421 return rc;
1422}
1423
1424
1425/**
1426 * Converts the arguments to the child's LC_CTYPE charset if necessary.
1427 *
1428 * @returns IPRT status code.
1429 * @param papszArgs The arguments (UTF-8).
1430 * @param hEnvToUse The child process environment.
1431 * @param ppapszArgs Where to return the converted arguments. The array
1432 * entries must be freed by RTStrFree and the array itself
1433 * by RTMemFree.
1434 */
1435static int rtProcPosixConvertArgv(const char * const *papszArgs, RTENV hEnvToUse, char ***ppapszArgs)
1436{
1437 *ppapszArgs = (char **)papszArgs;
1438
1439 /*
1440 * The first thing we need to do here is to try guess the codeset of the
1441 * child process and check if it's UTF-8 or not.
1442 */
1443 const char *pszEncoding;
1444 char szEncoding[512];
1445 if (hEnvToUse == RTENV_DEFAULT)
1446 {
1447 /* Same environment as us, assume setlocale is up to date: */
1448 pszEncoding = rtStrGetLocaleCodeset();
1449 }
1450 else
1451 {
1452 /* LC_ALL overrides everything else.*/
1453 /** @todo I don't recall now if this can do LC_XXX= inside it's value, like
1454 * what setlocale returns on some systems. It's been 15-16 years
1455 * since I last worked on an setlocale implementation... */
1456 const char *pszVar;
1457 int rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_ALL", szEncoding, sizeof(szEncoding), NULL);
1458 if (rc == VERR_ENV_VAR_NOT_FOUND)
1459 rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_CTYPE", szEncoding, sizeof(szEncoding), NULL);
1460 if (rc == VERR_ENV_VAR_NOT_FOUND)
1461 rc = RTEnvGetEx(hEnvToUse, pszVar = "LANG", szEncoding, sizeof(szEncoding), NULL);
1462 if (RT_SUCCESS(rc))
1463 {
1464 const char *pszDot = strchr(szEncoding, '.');
1465 if (pszDot)
1466 pszDot = RTStrStripL(pszDot + 1);
1467 if (pszDot && *pszDot)
1468 {
1469 pszEncoding = pszDot;
1470 Log2Func(("%s=%s -> %s (simple)\n", pszVar, szEncoding, pszEncoding));
1471 }
1472 else
1473 {
1474 /* No charset is given, so the default of the locale should be
1475 used. To get at that we have to use newlocale and nl_langinfo_l,
1476 which is there since ancient days on linux but no necessarily else
1477 where. */
1478#ifdef LC_CTYPE_MASK
1479 locale_t hLocale = newlocale(LC_CTYPE_MASK, szEncoding, (locale_t)0);
1480 if (hLocale != (locale_t)0)
1481 {
1482 const char *pszCodeset = nl_langinfo_l(CODESET, hLocale);
1483 Log2Func(("nl_langinfo_l(CODESET, %s=%s) -> %s\n", pszVar, szEncoding, pszCodeset));
1484 Assert(pszCodeset && *pszCodeset != '\0');
1485
1486 rc = RTStrCopy(szEncoding, sizeof(szEncoding), pszCodeset);
1487 AssertRC(rc); /* cannot possibly overflow */
1488
1489 freelocale(hLocale);
1490 pszEncoding = szEncoding;
1491 }
1492 else
1493#endif
1494 {
1495 /* This is mostly wrong, but I cannot think of anything better now: */
1496 pszEncoding = rtStrGetLocaleCodeset();
1497 LogFunc(("No newlocale or it failed (on '%s=%s', errno=%d), falling back on %s that we're using...\n",
1498 pszVar, szEncoding, errno, pszEncoding));
1499 }
1500 }
1501 RT_NOREF_PV(pszVar);
1502 }
1503 else
1504#ifdef RT_OS_DARWIN /* @bugref{10153}: Darwin defaults to UTF-8. */
1505 pszEncoding = "UTF-8";
1506#else
1507 pszEncoding = "ASCII";
1508#endif
1509 }
1510
1511 /*
1512 * Do nothing if it's UTF-8.
1513 */
1514 if (rtStrIsCodesetUtf8(pszEncoding))
1515 {
1516 LogFlowFunc(("No conversion needed (%s)\n", pszEncoding));
1517 return VINF_SUCCESS;
1518 }
1519
1520
1521 /*
1522 * Do the conversion.
1523 */
1524 size_t cArgs = 0;
1525 while (papszArgs[cArgs] != NULL)
1526 cArgs++;
1527 LogFunc(("Converting #%u arguments to %s...\n", cArgs, pszEncoding));
1528
1529 char **papszArgsConverted = (char **)RTMemAllocZ(sizeof(papszArgsConverted[0]) * (cArgs + 2));
1530 AssertReturn(papszArgsConverted, VERR_NO_MEMORY);
1531
1532 void *pvConversionCache = NULL;
1533 rtStrLocalCacheInit(&pvConversionCache);
1534 for (size_t i = 0; i < cArgs; i++)
1535 {
1536 int rc = rtStrLocalCacheConvert(papszArgs[i], strlen(papszArgs[i]), "UTF-8",
1537 &papszArgsConverted[i], 0, pszEncoding, &pvConversionCache);
1538 if (RT_SUCCESS(rc) && rc != VWRN_NO_TRANSLATION)
1539 { /* likely */ }
1540 else
1541 {
1542 LogRelMax(100, ("Failed to convert argument #%u '%s' to '%s': %Rrc\n", i, papszArgs[i], pszEncoding, rc));
1543 while (i-- > 0)
1544 RTStrFree(papszArgsConverted[i]);
1545 RTMemFree(papszArgsConverted);
1546 rtStrLocalCacheDelete(&pvConversionCache);
1547 return rc == VWRN_NO_TRANSLATION || rc == VERR_NO_TRANSLATION ? VERR_PROC_NO_ARG_TRANSLATION : rc;
1548 }
1549 }
1550
1551 rtStrLocalCacheDelete(&pvConversionCache);
1552 *ppapszArgs = papszArgsConverted;
1553 return VINF_SUCCESS;
1554}
1555
1556
1557/**
1558 * The result structure for rtPathFindExec/RTPathTraverseList.
1559 * @todo move to common path code?
1560 */
1561typedef struct RTPATHINTSEARCH
1562{
1563 /** For EACCES or EPERM errors that we continued on.
1564 * @note Must be initialized to VINF_SUCCESS. */
1565 int rcSticky;
1566 /** Buffer containing the filename. */
1567 char szFound[RTPATH_MAX];
1568} RTPATHINTSEARCH;
1569/** Pointer to a rtPathFindExec/RTPathTraverseList result. */
1570typedef RTPATHINTSEARCH *PRTPATHINTSEARCH;
1571
1572
1573/**
1574 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
1575 */
1576static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
1577{
1578 const char *pszExec = (const char *)pvUser1;
1579 PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)pvUser2;
1580 int rc = RTPathJoinEx(pResult->szFound, sizeof(pResult->szFound), pchPath, cchPath, pszExec, RTSTR_MAX);
1581 if (RT_SUCCESS(rc))
1582 {
1583 const char *pszNativeExec = NULL;
1584 rc = rtPathToNative(&pszNativeExec, pResult->szFound, NULL);
1585 if (RT_SUCCESS(rc))
1586 {
1587 if (!access(pszNativeExec, X_OK))
1588 rc = VINF_SUCCESS;
1589 else
1590 {
1591 if ( errno == EACCES
1592 || errno == EPERM)
1593 pResult->rcSticky = RTErrConvertFromErrno(errno);
1594 rc = VERR_TRY_AGAIN;
1595 }
1596 rtPathFreeNative(pszNativeExec, pResult->szFound);
1597 }
1598 else
1599 AssertRCStmt(rc, rc = VERR_TRY_AGAIN /* don't stop on this, whatever it is */);
1600 }
1601 return rc;
1602}
1603
1604
1605RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
1606 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
1607 const char *pszPassword, void *pvExtraData, PRTPROCESS phProcess)
1608{
1609 int rc;
1610 LogFlow(("RTProcCreateEx: pszExec=%s pszAsUser=%s fFlags=%#x phStdIn=%p phStdOut=%p phStdErr=%p\n",
1611 pszExec, pszAsUser, fFlags, phStdIn, phStdOut, phStdErr));
1612
1613 /*
1614 * Input validation
1615 */
1616 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
1617 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
1618 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
1619 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
1620 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
1621 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
1622 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
1623 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
1624 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
1625 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
1626#if defined(RT_OS_OS2)
1627 if (fFlags & RTPROC_FLAGS_DETACHED)
1628 return VERR_PROC_DETACH_NOT_SUPPORTED;
1629#endif
1630 AssertReturn(pvExtraData == NULL || (fFlags & RTPROC_FLAGS_DESIRED_SESSION_ID), VERR_INVALID_PARAMETER);
1631
1632 /*
1633 * Get the file descriptors for the handles we've been passed.
1634 */
1635 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
1636 int aStdFds[3] = { -1, -1, -1 };
1637 for (int i = 0; i < 3; i++)
1638 {
1639 if (paHandles[i])
1640 {
1641 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
1642 switch (paHandles[i]->enmType)
1643 {
1644 case RTHANDLETYPE_FILE:
1645 aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE
1646 ? (int)RTFileToNative(paHandles[i]->u.hFile)
1647 : -2 /* close it */;
1648 break;
1649
1650 case RTHANDLETYPE_PIPE:
1651 aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
1652 ? (int)RTPipeToNative(paHandles[i]->u.hPipe)
1653 : -2 /* close it */;
1654 break;
1655
1656 case RTHANDLETYPE_SOCKET:
1657 aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
1658 ? (int)RTSocketToNative(paHandles[i]->u.hSocket)
1659 : -2 /* close it */;
1660 break;
1661
1662 default:
1663 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
1664 }
1665 /** @todo check the close-on-execness of these handles? */
1666 }
1667 }
1668
1669 for (int i = 0; i < 3; i++)
1670 if (aStdFds[i] == i)
1671 aStdFds[i] = -1;
1672 LogFlowFunc(("aStdFds={%d, %d, %d}\n", aStdFds[0], aStdFds[1], aStdFds[2]));
1673
1674 for (int i = 0; i < 3; i++)
1675 AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i,
1676 ("%i := %i not possible because we're lazy\n", i, aStdFds[i]),
1677 VERR_NOT_SUPPORTED);
1678
1679 /*
1680 * Validate the credentials if a user is specified.
1681 */
1682 bool const fNeedLoginEnv = (fFlags & RTPROC_FLAGS_PROFILE)
1683 && ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || hEnv == RTENV_DEFAULT);
1684 uid_t uid = ~(uid_t)0;
1685 gid_t gid = ~(gid_t)0;
1686 char **papszPamEnv = NULL;
1687 if (pszAsUser)
1688 {
1689 rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid, fNeedLoginEnv ? &papszPamEnv : NULL);
1690 if (RT_FAILURE(rc))
1691 return rc;
1692 }
1693#ifdef IPRT_USE_PAM
1694 /*
1695 * User unchanged, but if PROFILE is request we must try get the PAM
1696 * environmnet variables.
1697 *
1698 * For this to work, we'll need a special PAM service profile which doesn't
1699 * actually do any authentication, only concerns itself with the enviornment
1700 * setup. gdm-launch-environment is such one, and we use it if we haven't
1701 * got an IPRT specific one there.
1702 */
1703 else if (fNeedLoginEnv)
1704 {
1705 const char *pszService;
1706 if (rtProcPosixPamServiceExists("iprt-environment"))
1707 pszService = "iprt-environment";
1708# ifdef IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT
1709 else if (rtProcPosixPamServiceExists(IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT))
1710 pszService = IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT;
1711# endif
1712 else if (rtProcPosixPamServiceExists("gdm-launch-environment"))
1713 pszService = "gdm-launch-environment";
1714 else
1715 pszService = NULL;
1716 if (pszService)
1717 {
1718 char szLoginName[512];
1719 rc = getlogin_r(szLoginName, sizeof(szLoginName));
1720 if (rc == 0)
1721 rc = rtProcPosixAuthenticateUsingPam(pszService, szLoginName, "xxx", &papszPamEnv, NULL);
1722 }
1723 }
1724#endif
1725
1726 /*
1727 * Create the child environment if either RTPROC_FLAGS_PROFILE or
1728 * RTPROC_FLAGS_ENV_CHANGE_RECORD are in effect.
1729 */
1730 RTENV hEnvToUse = hEnv;
1731 if ( (fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_PROFILE))
1732 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
1733 || hEnv == RTENV_DEFAULT) )
1734 {
1735 if (fFlags & RTPROC_FLAGS_PROFILE)
1736 rc = rtProcPosixCreateProfileEnv(&hEnvToUse, pszAsUser, uid, gid, fFlags, papszPamEnv);
1737 else
1738 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
1739 rtProcPosixFreePamEnv(papszPamEnv);
1740 papszPamEnv = NULL;
1741 if (RT_FAILURE(rc))
1742 return rc;
1743
1744 if ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) && hEnv != RTENV_DEFAULT)
1745 {
1746 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
1747 if (RT_FAILURE(rc))
1748 {
1749 RTEnvDestroy(hEnvToUse);
1750 return rc;
1751 }
1752 }
1753 }
1754 Assert(papszPamEnv == NULL);
1755
1756 /*
1757 * Check for execute access to the file, searching the PATH if needed.
1758 */
1759 const char *pszNativeExec = NULL;
1760 rc = rtPathToNative(&pszNativeExec, pszExec, NULL);
1761 if (RT_SUCCESS(rc))
1762 {
1763 if (access(pszNativeExec, X_OK) == 0)
1764 rc = VINF_SUCCESS;
1765 else
1766 {
1767 rc = errno;
1768 rtPathFreeNative(pszNativeExec, pszExec);
1769
1770 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
1771 || rc != ENOENT
1772 || RTPathHavePath(pszExec) )
1773 rc = RTErrConvertFromErrno(rc);
1774 else
1775 {
1776 /* Search the PATH for it: */
1777 char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
1778 if (pszPath)
1779 {
1780 PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)alloca(sizeof(*pResult));
1781 pResult->rcSticky = VINF_SUCCESS;
1782 rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, pResult);
1783 RTStrFree(pszPath);
1784 if (RT_SUCCESS(rc))
1785 {
1786 /* Found it. Now, convert to native path: */
1787 pszExec = pResult->szFound;
1788 rc = rtPathToNative(&pszNativeExec, pszExec, NULL);
1789 }
1790 else
1791 rc = rc != VERR_END_OF_STRING ? rc
1792 : pResult->rcSticky == VINF_SUCCESS ? VERR_FILE_NOT_FOUND : pResult->rcSticky;
1793 }
1794 else
1795 rc = VERR_NO_STR_MEMORY;
1796 }
1797 }
1798 if (RT_SUCCESS(rc))
1799 {
1800 /*
1801 * Convert arguments to child codeset if necessary.
1802 */
1803 char **papszArgsConverted = (char **)papszArgs;
1804 if (!(fFlags & RTPROC_FLAGS_UTF8_ARGV))
1805 rc = rtProcPosixConvertArgv(papszArgs, hEnvToUse, &papszArgsConverted);
1806 if (RT_SUCCESS(rc))
1807 {
1808 /*
1809 * The rest of the process creation is reused internally by rtProcPosixCreateProfileEnv.
1810 */
1811 rc = rtProcPosixCreateInner(pszNativeExec, papszArgsConverted, hEnv, hEnvToUse, fFlags, pszAsUser, uid, gid,
1812 RT_ELEMENTS(aStdFds), aStdFds, phProcess);
1813
1814 }
1815
1816 /* Free the translated argv copy, if needed. */
1817 if (papszArgsConverted != (char **)papszArgs)
1818 {
1819 for (size_t i = 0; papszArgsConverted[i] != NULL; i++)
1820 RTStrFree(papszArgsConverted[i]);
1821 RTMemFree(papszArgsConverted);
1822 }
1823 rtPathFreeNative(pszNativeExec, pszExec);
1824 }
1825 }
1826 if (hEnvToUse != hEnv)
1827 RTEnvDestroy(hEnvToUse);
1828 return rc;
1829}
1830
1831
1832/**
1833 * The inner 2nd half of RTProcCreateEx.
1834 *
1835 * This is also used by rtProcPosixCreateProfileEnv().
1836 *
1837 * @returns IPRT status code.
1838 * @param pszNativeExec The executable to run (absolute path, X_OK).
1839 * Native path.
1840 * @param papszArgs The arguments. Caller has done codeset conversions.
1841 * @param hEnv The original enviornment request, needed for
1842 * adjustments if starting as different user.
1843 * @param hEnvToUse The environment we should use.
1844 * @param fFlags The process creation flags, RTPROC_FLAGS_XXX.
1845 * @param pszAsUser The user to start the process as, if requested.
1846 * @param uid The UID corrsponding to @a pszAsUser, ~0 if NULL.
1847 * @param gid The GID corrsponding to @a pszAsUser, ~0 if NULL.
1848 * @param cRedirFds Number of redirection file descriptors.
1849 * @param paRedirFds Pointer to redirection file descriptors. Entries
1850 * containing -1 are not modified (inherit from parent),
1851 * -2 indicates that the descriptor should be closed in the
1852 * child.
1853 * @param phProcess Where to return the process ID on success.
1854 */
1855static int rtProcPosixCreateInner(const char *pszNativeExec, const char * const *papszArgs, RTENV hEnv, RTENV hEnvToUse,
1856 uint32_t fFlags, const char *pszAsUser, uid_t uid, gid_t gid,
1857 unsigned cRedirFds, int *paRedirFds, PRTPROCESS phProcess)
1858{
1859 /*
1860 * Get the environment block.
1861 */
1862 const char * const *papszEnv = RTEnvGetExecEnvP(hEnvToUse);
1863 AssertPtrReturn(papszEnv, VERR_INVALID_HANDLE);
1864
1865 /*
1866 * Optimize the redirections.
1867 */
1868 while (cRedirFds > 0 && paRedirFds[cRedirFds - 1] == -1)
1869 cRedirFds--;
1870
1871 /*
1872 * Child PID.
1873 */
1874 pid_t pid = -1;
1875
1876 /*
1877 * Take care of detaching the process.
1878 *
1879 * HACK ALERT! Put the process into a new process group with pgid = pid
1880 * to make sure it differs from that of the parent process to ensure that
1881 * the IPRT waitpid call doesn't race anyone (read XPCOM) doing group wide
1882 * waits. setsid() includes the setpgid() functionality.
1883 * 2010-10-11 XPCOM no longer waits for anything, but it cannot hurt.
1884 */
1885#ifndef RT_OS_OS2
1886 if (fFlags & RTPROC_FLAGS_DETACHED)
1887 {
1888# ifdef RT_OS_SOLARIS
1889 int templateFd = -1;
1890 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1891 {
1892 templateFd = rtSolarisContractPreFork();
1893 if (templateFd == -1)
1894 return VERR_OPEN_FAILED;
1895 }
1896# endif /* RT_OS_SOLARIS */
1897 pid = fork();
1898 if (!pid)
1899 {
1900# ifdef RT_OS_SOLARIS
1901 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1902 rtSolarisContractPostForkChild(templateFd);
1903# endif
1904 setsid(); /* see comment above */
1905
1906 pid = -1;
1907 /* Child falls through to the actual spawn code below. */
1908 }
1909 else
1910 {
1911# ifdef RT_OS_SOLARIS
1912 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1913 rtSolarisContractPostForkParent(templateFd, pid);
1914# endif
1915 if (pid > 0)
1916 {
1917 /* Must wait for the temporary process to avoid a zombie. */
1918 int status = 0;
1919 pid_t pidChild = 0;
1920
1921 /* Restart if we get interrupted. */
1922 do
1923 {
1924 pidChild = waitpid(pid, &status, 0);
1925 } while ( pidChild == -1
1926 && errno == EINTR);
1927
1928 /* Assume that something wasn't found. No detailed info. */
1929 if (status)
1930 return VERR_PROCESS_NOT_FOUND;
1931 if (phProcess)
1932 *phProcess = 0;
1933 return VINF_SUCCESS;
1934 }
1935 return RTErrConvertFromErrno(errno);
1936 }
1937 }
1938#endif
1939
1940 /*
1941 * Spawn the child.
1942 *
1943 * Any spawn code MUST not execute any atexit functions if it is for a
1944 * detached process. It would lead to running the atexit functions which
1945 * make only sense for the parent. libORBit e.g. gets confused by multiple
1946 * execution. Remember, there was only a fork() so far, and until exec()
1947 * is successfully run there is nothing which would prevent doing anything
1948 * silly with the (duplicated) file descriptors.
1949 */
1950 int rc;
1951#ifdef HAVE_POSIX_SPAWN
1952 /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */
1953 if ( uid == ~(uid_t)0
1954 && gid == ~(gid_t)0)
1955 {
1956 /* Spawn attributes. */
1957 posix_spawnattr_t Attr;
1958 rc = posix_spawnattr_init(&Attr);
1959 if (!rc)
1960 {
1961 /* Indicate that process group and signal mask are to be changed,
1962 and that the child should use default signal actions. */
1963 rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
1964 Assert(rc == 0);
1965
1966 /* The child starts in its own process group. */
1967 if (!rc)
1968 {
1969 rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */);
1970 Assert(rc == 0);
1971 }
1972
1973 /* Unmask all signals. */
1974 if (!rc)
1975 {
1976 sigset_t SigMask;
1977 sigemptyset(&SigMask);
1978 rc = posix_spawnattr_setsigmask(&Attr, &SigMask); Assert(rc == 0);
1979 }
1980
1981 /* File changes. */
1982 posix_spawn_file_actions_t FileActions;
1983 posix_spawn_file_actions_t *pFileActions = NULL;
1984 if (!rc && cRedirFds > 0)
1985 {
1986 rc = posix_spawn_file_actions_init(&FileActions);
1987 if (!rc)
1988 {
1989 pFileActions = &FileActions;
1990 for (unsigned i = 0; i < cRedirFds; i++)
1991 {
1992 int fd = paRedirFds[i];
1993 if (fd == -2)
1994 rc = posix_spawn_file_actions_addclose(&FileActions, i);
1995 else if (fd >= 0 && fd != (int)i)
1996 {
1997 rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i);
1998 if (!rc)
1999 {
2000 for (unsigned j = i + 1; j < cRedirFds; j++)
2001 if (paRedirFds[j] == fd)
2002 {
2003 fd = -1;
2004 break;
2005 }
2006 if (fd >= 0)
2007 rc = posix_spawn_file_actions_addclose(&FileActions, fd);
2008 }
2009 }
2010 if (rc)
2011 break;
2012 }
2013 }
2014 }
2015
2016 if (!rc)
2017 rc = posix_spawn(&pid, pszNativeExec, pFileActions, &Attr, (char * const *)papszArgs,
2018 (char * const *)papszEnv);
2019
2020 /* cleanup */
2021 int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2);
2022 if (pFileActions)
2023 {
2024 rc2 = posix_spawn_file_actions_destroy(pFileActions);
2025 Assert(rc2 == 0);
2026 }
2027
2028 /* return on success.*/
2029 if (!rc)
2030 {
2031 /* For a detached process this happens in the temp process, so
2032 * it's not worth doing anything as this process must exit. */
2033 if (fFlags & RTPROC_FLAGS_DETACHED)
2034 _Exit(0);
2035 if (phProcess)
2036 *phProcess = pid;
2037 return VINF_SUCCESS;
2038 }
2039 }
2040 /* For a detached process this happens in the temp process, so
2041 * it's not worth doing anything as this process must exit. */
2042 if (fFlags & RTPROC_FLAGS_DETACHED)
2043 _Exit(124);
2044 }
2045 else
2046#endif
2047 {
2048#ifdef RT_OS_SOLARIS
2049 int templateFd = -1;
2050 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
2051 {
2052 templateFd = rtSolarisContractPreFork();
2053 if (templateFd == -1)
2054 return VERR_OPEN_FAILED;
2055 }
2056#endif /* RT_OS_SOLARIS */
2057 pid = fork();
2058 if (!pid)
2059 {
2060#ifdef RT_OS_SOLARIS
2061 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
2062 rtSolarisContractPostForkChild(templateFd);
2063#endif /* RT_OS_SOLARIS */
2064 if (!(fFlags & RTPROC_FLAGS_DETACHED))
2065 setpgid(0, 0); /* see comment above */
2066
2067 /*
2068 * Change group and user if requested.
2069 */
2070#if 1 /** @todo This needs more work, see suplib/hardening. */
2071 if (pszAsUser)
2072 {
2073 int ret = initgroups(pszAsUser, gid);
2074 if (ret)
2075 {
2076 if (fFlags & RTPROC_FLAGS_DETACHED)
2077 _Exit(126);
2078 else
2079 exit(126);
2080 }
2081 }
2082 if (gid != ~(gid_t)0)
2083 {
2084 if (setgid(gid))
2085 {
2086 if (fFlags & RTPROC_FLAGS_DETACHED)
2087 _Exit(126);
2088 else
2089 exit(126);
2090 }
2091 }
2092
2093 if (uid != ~(uid_t)0)
2094 {
2095 if (setuid(uid))
2096 {
2097 if (fFlags & RTPROC_FLAGS_DETACHED)
2098 _Exit(126);
2099 else
2100 exit(126);
2101 }
2102 }
2103#endif
2104
2105 /*
2106 * Some final profile environment tweaks, if running as user.
2107 */
2108 if ( (fFlags & RTPROC_FLAGS_PROFILE)
2109 && pszAsUser
2110 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
2111 || hEnv == RTENV_DEFAULT) )
2112 {
2113 rc = rtProcPosixAdjustProfileEnvFromChild(hEnvToUse, fFlags, hEnv);
2114 papszEnv = RTEnvGetExecEnvP(hEnvToUse);
2115 if (RT_FAILURE(rc) || !papszEnv)
2116 {
2117 if (fFlags & RTPROC_FLAGS_DETACHED)
2118 _Exit(126);
2119 else
2120 exit(126);
2121 }
2122 }
2123
2124 /*
2125 * Unset the signal mask.
2126 */
2127 sigset_t SigMask;
2128 sigemptyset(&SigMask);
2129 rc = sigprocmask(SIG_SETMASK, &SigMask, NULL);
2130 Assert(rc == 0);
2131
2132 /*
2133 * Apply changes to the standard file descriptor and stuff.
2134 */
2135 for (unsigned i = 0; i < cRedirFds; i++)
2136 {
2137 int fd = paRedirFds[i];
2138 if (fd == -2)
2139 close(i);
2140 else if (fd >= 0)
2141 {
2142 int rc2 = dup2(fd, i);
2143 if (rc2 != (int)i)
2144 {
2145 if (fFlags & RTPROC_FLAGS_DETACHED)
2146 _Exit(125);
2147 else
2148 exit(125);
2149 }
2150 for (unsigned j = i + 1; j < cRedirFds; j++)
2151 if (paRedirFds[j] == fd)
2152 {
2153 fd = -1;
2154 break;
2155 }
2156 if (fd >= 0)
2157 close(fd);
2158 }
2159 }
2160
2161 /*
2162 * Finally, execute the requested program.
2163 */
2164 rc = execve(pszNativeExec, (char * const *)papszArgs, (char * const *)papszEnv);
2165 if (errno == ENOEXEC)
2166 {
2167 /* This can happen when trying to start a shell script without the magic #!/bin/sh */
2168 RTAssertMsg2Weak("Cannot execute this binary format!\n");
2169 }
2170 else
2171 RTAssertMsg2Weak("execve returns %d errno=%d (%s)\n", rc, errno, pszNativeExec);
2172 RTAssertReleasePanic();
2173 if (fFlags & RTPROC_FLAGS_DETACHED)
2174 _Exit(127);
2175 else
2176 exit(127);
2177 }
2178#ifdef RT_OS_SOLARIS
2179 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
2180 rtSolarisContractPostForkParent(templateFd, pid);
2181#endif /* RT_OS_SOLARIS */
2182 if (pid > 0)
2183 {
2184 /* For a detached process this happens in the temp process, so
2185 * it's not worth doing anything as this process must exit. */
2186 if (fFlags & RTPROC_FLAGS_DETACHED)
2187 _Exit(0);
2188 if (phProcess)
2189 *phProcess = pid;
2190 return VINF_SUCCESS;
2191 }
2192 /* For a detached process this happens in the temp process, so
2193 * it's not worth doing anything as this process must exit. */
2194 if (fFlags & RTPROC_FLAGS_DETACHED)
2195 _Exit(124);
2196 return RTErrConvertFromErrno(errno);
2197 }
2198
2199 return VERR_NOT_IMPLEMENTED;
2200}
2201
2202
2203RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
2204{
2205 /*
2206 * Fork the child process in a new session and quit the parent.
2207 *
2208 * - fork once and create a new session (setsid). This will detach us
2209 * from the controlling tty meaning that we won't receive the SIGHUP
2210 * (or any other signal) sent to that session.
2211 * - The SIGHUP signal is ignored because the session/parent may throw
2212 * us one before we get to the setsid.
2213 * - When the parent exit(0) we will become an orphan and re-parented to
2214 * the init process.
2215 * - Because of the sometimes unexpected semantics of assigning the
2216 * controlling tty automagically when a session leader first opens a tty,
2217 * we will fork() once more to get rid of the session leadership role.
2218 */
2219
2220 /* We start off by opening the pidfile, so that we can fail straight away
2221 * if it already exists. */
2222 int fdPidfile = -1;
2223 if (pszPidfile != NULL)
2224 {
2225 /* @note the exclusive create is not guaranteed on all file
2226 * systems (e.g. NFSv2) */
2227 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
2228 return RTErrConvertFromErrno(errno);
2229 }
2230
2231 /* Ignore SIGHUP straight away. */
2232 struct sigaction OldSigAct;
2233 struct sigaction SigAct;
2234 memset(&SigAct, 0, sizeof(SigAct));
2235 SigAct.sa_handler = SIG_IGN;
2236 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
2237
2238 /* First fork, to become independent process. */
2239 pid_t pid = fork();
2240 if (pid == -1)
2241 {
2242 if (fdPidfile != -1)
2243 close(fdPidfile);
2244 return RTErrConvertFromErrno(errno);
2245 }
2246 if (pid != 0)
2247 {
2248 /* Parent exits, no longer necessary. The child gets reparented
2249 * to the init process. */
2250 exit(0);
2251 }
2252
2253 /* Create new session, fix up the standard file descriptors and the
2254 * current working directory. */
2255 /** @todo r=klaus the webservice uses this function and assumes that the
2256 * contract id of the daemon is the same as that of the original process.
2257 * Whenever this code is changed this must still remain possible. */
2258 pid_t newpgid = setsid();
2259 int SavedErrno = errno;
2260 if (rcSigAct != -1)
2261 sigaction(SIGHUP, &OldSigAct, NULL);
2262 if (newpgid == -1)
2263 {
2264 if (fdPidfile != -1)
2265 close(fdPidfile);
2266 return RTErrConvertFromErrno(SavedErrno);
2267 }
2268
2269 if (!fNoClose)
2270 {
2271 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
2272 int fd = open("/dev/null", O_RDWR);
2273 if (fd == -1) /* paranoia */
2274 {
2275 close(STDIN_FILENO);
2276 close(STDOUT_FILENO);
2277 close(STDERR_FILENO);
2278 fd = open("/dev/null", O_RDWR);
2279 }
2280 if (fd != -1)
2281 {
2282 dup2(fd, STDIN_FILENO);
2283 dup2(fd, STDOUT_FILENO);
2284 dup2(fd, STDERR_FILENO);
2285 if (fd > 2)
2286 close(fd);
2287 }
2288 }
2289
2290 if (!fNoChDir)
2291 {
2292 int rcIgnored = chdir("/");
2293 NOREF(rcIgnored);
2294 }
2295
2296 /* Second fork to lose session leader status. */
2297 pid = fork();
2298 if (pid == -1)
2299 {
2300 if (fdPidfile != -1)
2301 close(fdPidfile);
2302 return RTErrConvertFromErrno(errno);
2303 }
2304
2305 if (pid != 0)
2306 {
2307 /* Write the pid file, this is done in the parent, before exiting. */
2308 if (fdPidfile != -1)
2309 {
2310 char szBuf[256];
2311 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
2312 ssize_t cbIgnored = write(fdPidfile, szBuf, cbPid); NOREF(cbIgnored);
2313 close(fdPidfile);
2314 }
2315 exit(0);
2316 }
2317
2318 if (fdPidfile != -1)
2319 close(fdPidfile);
2320
2321 return VINF_SUCCESS;
2322}
2323
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