VirtualBox

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

最後變更 在這個檔案從95190是 95147,由 vboxsync 提交於 3 年 前

IPRT/Process creation: Fixed fallback handling for using stdin's TTY as a last resort, added IPRT_WITH_PAM_TTY_KLUDGE (disabled by default) along with a @todo. bugref:10225

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

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