VirtualBox

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

Last change on this file since 96609 was 96609, checked in by vboxsync, 2 years ago

IPRT/path: Added fFlags parameter to RTPathAppendEx and RTPathJoinEx to make it possible to select the path style. bugref:10286

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

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