VirtualBox

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

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

IPRT/RTProcCreateEx/posix: Use PAM when authenticating to Solaris guests
for guest control activities. bugref:10233

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

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