VirtualBox

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

Last change on this file since 99483 was 99483, checked in by vboxsync, 14 months ago

IPRT/RTProcCreateEx: More fixes for the initial patch supplied; also tweaked the testcases a little. bugref:8053

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 92.3 KB
Line 
1/* $Id: process-creation-posix.cpp 99483 2023-04-20 10:16:10Z vboxsync $ */
2/** @file
3 * IPRT - Process Creation, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2023 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, void *pvExtraData, 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, NULL /*pvExtraData*/, &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 (that's
1482 * what setlocale in glibc does).
1483 */
1484 const char *pszVar;
1485 int rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_ALL", szEncoding, sizeof(szEncoding), NULL);
1486 if (rc == VERR_ENV_VAR_NOT_FOUND || (RT_SUCCESS(rc) && szEncoding[0] == '\0'))
1487 rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_CTYPE", szEncoding, sizeof(szEncoding), NULL);
1488 if (rc == VERR_ENV_VAR_NOT_FOUND || (RT_SUCCESS(rc) && szEncoding[0] == '\0'))
1489 rc = RTEnvGetEx(hEnvToUse, pszVar = "LANG", szEncoding, sizeof(szEncoding), NULL);
1490 if (RT_SUCCESS(rc) && szEncoding[0] != '\0')
1491 {
1492 /*
1493 * LC_ALL can contain a composite locale consisting of the locales of each of the
1494 * categories in two different formats depending on the OS. On Solaris, macOS, and
1495 * *BSD composite locale names use slash ('/') as the separator and the following
1496 * order for the categories:
1497 * LC_CTYPE/LC_NUMERIC/LC_TIME/LC_COLLATE/LC_MONETARY/LC_MESSAGES
1498 * e.g.:
1499 * en_US.UTF-8/POSIX/el_GR.UTF-8/el_CY.UTF-8/en_GB.UTF-8/es_ES.UTF-8
1500 *
1501 * On Solaris there is also a leading slash.
1502 *
1503 * On Linux and OS/2 the composite locale format is made up of key-value pairs
1504 * of category names and locales of the form 'name=value' with each element
1505 * separated by a semicolon in the same order as above with following additional
1506 * categories included as well:
1507 * LC_PAPER/LC_NAME/LC_ADDRESS/LC_TELEPHONE/LC_MEASUREMENT/LC_IDENTIFICATION
1508 * e.g.
1509 * LC_CTYPE=fr_BE;LC_NUMERIC=fr_BE@euro;LC_TIME=fr_BE.utf8;LC_COLLATE=fr_CA;\
1510 * LC_MONETARY=fr_CA.utf8;LC_MESSAGES=fr_CH;LC_PAPER=fr_CH.utf8;LC_NAME=fr_FR;\
1511 * LC_ADDRESS=fr_FR.utf8;LC_TELEPHONE=fr_LU;LC_MEASUREMENT=fr_LU@euro;\
1512 * LC_IDENTIFICATION=fr_LU.utf8
1513 */
1514 char *pszEncodingStart = szEncoding;
1515#if !defined(RT_OS_LINUX) && !defined(RT_OS_OS2)
1516 if (*pszEncodingStart == '/')
1517 pszEncodingStart++;
1518 char *pszSlash = strchr(pszEncodingStart, '/');
1519 if (pszSlash)
1520 *pszSlash = '\0'; /* This ASSUMES the first one is LC_CTYPE! */
1521#else
1522 char *pszCType = strstr(pszEncodingStart, "LC_CTYPE=");
1523 if (pszCType)
1524 {
1525 pszEncodingStart = pszCType + sizeof("LC_CTYPE=") - 1;
1526
1527 char *pszSemiColon = strchr(pszEncodingStart, ';');
1528 if (pszSemiColon)
1529 *pszSemiColon = '\0';
1530 }
1531#endif
1532
1533 /*
1534 * Use newlocale and nl_langinfo_l to determine the default codeset for the locale
1535 * specified in the child's environment. These routines have been around since
1536 * ancient days on Linux and for quite a long time on macOS, Solaris, and *BSD but
1537 * to ensure their availability check that LC_CTYPE_MASK is defined.
1538 *
1539 * Note! The macOS nl_langinfo(3)/nl_langinfo_l(3) routines return a pointer to an
1540 * empty string for "short" locale names like en_NZ, it_IT, el_GR, etc. so use
1541 * UTF-8 in those cases as it is the default for short name locales on macOS
1542 * (see also rtStrGetLocaleCodeset).
1543 */
1544#ifdef LC_CTYPE_MASK
1545 locale_t hLocale = newlocale(LC_CTYPE_MASK, pszEncodingStart, (locale_t)0);
1546 if (hLocale != (locale_t)0)
1547 {
1548 const char *pszCodeset = nl_langinfo_l(CODESET, hLocale);
1549 Log2Func(("nl_langinfo_l(CODESET, %s=%s) -> %s\n", pszVar, pszEncodingStart, pszCodeset));
1550 if (!pszCodeset || *pszCodeset == '\0')
1551# ifdef RT_OS_DARWIN
1552 pszEncoding = "UTF-8";
1553# else
1554 pszEncoding = "ASCII";
1555# endif
1556 else
1557 {
1558 rc = RTStrCopy(szEncoding, sizeof(szEncoding), pszCodeset);
1559 AssertRC(rc); /* cannot possibly overflow */
1560 }
1561
1562 freelocale(hLocale);
1563 pszEncoding = szEncoding;
1564 }
1565 else
1566#endif
1567 {
1568 /* If there is something that ought to be a character set encoding, try use it: */
1569 const char *pszDot = strchr(pszEncodingStart, '.');
1570 if (pszDot)
1571 pszDot = RTStrStripL(pszDot + 1);
1572 if (pszDot && *pszDot != '\0')
1573 {
1574 pszEncoding = pszDot;
1575 Log2Func(("%s=%s -> %s (simple)\n", pszVar, szEncoding, pszEncoding));
1576 }
1577 else
1578 {
1579 /* This is mostly wrong, but I cannot think of anything better now: */
1580 pszEncoding = rtStrGetLocaleCodeset();
1581 LogFunc(("No newlocale or it failed (on '%s=%s', errno=%d), falling back on %s that we're using...\n",
1582 pszVar, pszEncodingStart, errno, pszEncoding));
1583 }
1584 }
1585 RT_NOREF_PV(pszVar);
1586 }
1587 else
1588#ifdef RT_OS_DARWIN /* @bugref{10153}: Darwin defaults to UTF-8. */
1589 pszEncoding = "UTF-8";
1590#else
1591 pszEncoding = "ASCII";
1592#endif
1593 }
1594
1595 /*
1596 * Do nothing if it's UTF-8.
1597 */
1598 if (rtStrIsCodesetUtf8(pszEncoding))
1599 {
1600 LogFlowFunc(("No conversion needed (%s)\n", pszEncoding));
1601 return VINF_SUCCESS;
1602 }
1603
1604
1605 /*
1606 * Do the conversion.
1607 */
1608 size_t cArgs = 0;
1609 while (papszArgs[cArgs] != NULL)
1610 cArgs++;
1611 LogFunc(("Converting #%u arguments to %s...\n", cArgs, pszEncoding));
1612
1613 char **papszArgsConverted = (char **)RTMemAllocZ(sizeof(papszArgsConverted[0]) * (cArgs + 2));
1614 AssertReturn(papszArgsConverted, VERR_NO_MEMORY);
1615
1616 void *pvConversionCache = NULL;
1617 rtStrLocalCacheInit(&pvConversionCache);
1618 for (size_t i = 0; i < cArgs; i++)
1619 {
1620 int rc = rtStrLocalCacheConvert(papszArgs[i], strlen(papszArgs[i]), "UTF-8",
1621 &papszArgsConverted[i], 0, pszEncoding, &pvConversionCache);
1622 if (RT_SUCCESS(rc) && rc != VWRN_NO_TRANSLATION)
1623 { /* likely */ }
1624 else
1625 {
1626 LogRelMax(100, ("Failed to convert argument #%u '%s' to '%s': %Rrc\n", i, papszArgs[i], pszEncoding, rc));
1627 while (i-- > 0)
1628 RTStrFree(papszArgsConverted[i]);
1629 RTMemFree(papszArgsConverted);
1630 rtStrLocalCacheDelete(&pvConversionCache);
1631 return rc == VWRN_NO_TRANSLATION || rc == VERR_NO_TRANSLATION ? VERR_PROC_NO_ARG_TRANSLATION : rc;
1632 }
1633 }
1634
1635 rtStrLocalCacheDelete(&pvConversionCache);
1636 *ppapszArgs = papszArgsConverted;
1637 return VINF_SUCCESS;
1638}
1639
1640
1641/**
1642 * The result structure for rtPathFindExec/RTPathTraverseList.
1643 * @todo move to common path code?
1644 */
1645typedef struct RTPATHINTSEARCH
1646{
1647 /** For EACCES or EPERM errors that we continued on.
1648 * @note Must be initialized to VINF_SUCCESS. */
1649 int rcSticky;
1650 /** Buffer containing the filename. */
1651 char szFound[RTPATH_MAX];
1652} RTPATHINTSEARCH;
1653/** Pointer to a rtPathFindExec/RTPathTraverseList result. */
1654typedef RTPATHINTSEARCH *PRTPATHINTSEARCH;
1655
1656
1657/**
1658 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
1659 */
1660static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
1661{
1662 const char *pszExec = (const char *)pvUser1;
1663 PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)pvUser2;
1664 int rc = RTPathJoinEx(pResult->szFound, sizeof(pResult->szFound), pchPath, cchPath, pszExec, RTSTR_MAX,
1665 RTPATH_STR_F_STYLE_HOST);
1666 if (RT_SUCCESS(rc))
1667 {
1668 const char *pszNativeExec = NULL;
1669 rc = rtPathToNative(&pszNativeExec, pResult->szFound, NULL);
1670 if (RT_SUCCESS(rc))
1671 {
1672 if (!access(pszNativeExec, X_OK))
1673 rc = VINF_SUCCESS;
1674 else
1675 {
1676 if ( errno == EACCES
1677 || errno == EPERM)
1678 pResult->rcSticky = RTErrConvertFromErrno(errno);
1679 rc = VERR_TRY_AGAIN;
1680 }
1681 rtPathFreeNative(pszNativeExec, pResult->szFound);
1682 }
1683 else
1684 AssertRCStmt(rc, rc = VERR_TRY_AGAIN /* don't stop on this, whatever it is */);
1685 }
1686 return rc;
1687}
1688
1689
1690RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
1691 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
1692 const char *pszPassword, void *pvExtraData, PRTPROCESS phProcess)
1693{
1694 int rc;
1695 LogFlow(("RTProcCreateEx: pszExec=%s pszAsUser=%s fFlags=%#x phStdIn=%p phStdOut=%p phStdErr=%p\n",
1696 pszExec, pszAsUser, fFlags, phStdIn, phStdOut, phStdErr));
1697
1698 /*
1699 * Input validation
1700 */
1701 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
1702 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
1703 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
1704 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
1705 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
1706 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
1707 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
1708 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
1709 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
1710 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
1711#if defined(RT_OS_OS2)
1712 if (fFlags & RTPROC_FLAGS_DETACHED)
1713 return VERR_PROC_DETACH_NOT_SUPPORTED;
1714#endif
1715
1716 /* Extra data: */
1717 if (fFlags & RTPROC_FLAGS_CWD)
1718 {
1719 AssertPtrReturn(pvExtraData, VERR_INVALID_POINTER);
1720 }
1721 else
1722 AssertReturn(pvExtraData == NULL, VERR_INVALID_PARAMETER);
1723 /* Note: Windows-specific flags will be quietly ignored. */
1724
1725 /*
1726 * Get the file descriptors for the handles we've been passed.
1727 */
1728 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
1729 int aStdFds[3] = { -1, -1, -1 };
1730 for (int i = 0; i < 3; i++)
1731 {
1732 if (paHandles[i])
1733 {
1734 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
1735 switch (paHandles[i]->enmType)
1736 {
1737 case RTHANDLETYPE_FILE:
1738 aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE
1739 ? (int)RTFileToNative(paHandles[i]->u.hFile)
1740 : -2 /* close it */;
1741 break;
1742
1743 case RTHANDLETYPE_PIPE:
1744 aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
1745 ? (int)RTPipeToNative(paHandles[i]->u.hPipe)
1746 : -2 /* close it */;
1747 break;
1748
1749 case RTHANDLETYPE_SOCKET:
1750 aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
1751 ? (int)RTSocketToNative(paHandles[i]->u.hSocket)
1752 : -2 /* close it */;
1753 break;
1754
1755 default:
1756 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
1757 }
1758 /** @todo check the close-on-execness of these handles? */
1759 }
1760 }
1761
1762 for (int i = 0; i < 3; i++)
1763 if (aStdFds[i] == i)
1764 aStdFds[i] = -1;
1765 LogFlowFunc(("aStdFds={%d, %d, %d}\n", aStdFds[0], aStdFds[1], aStdFds[2]));
1766
1767 for (int i = 0; i < 3; i++)
1768 AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i,
1769 ("%i := %i not possible because we're lazy\n", i, aStdFds[i]),
1770 VERR_NOT_SUPPORTED);
1771
1772 /*
1773 * Validate the credentials if a user is specified.
1774 */
1775 bool const fNeedLoginEnv = (fFlags & RTPROC_FLAGS_PROFILE)
1776 && ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || hEnv == RTENV_DEFAULT);
1777 uid_t uid = ~(uid_t)0;
1778 gid_t gid = ~(gid_t)0;
1779 char **papszPamEnv = NULL;
1780 if (pszAsUser)
1781 {
1782 rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid, fNeedLoginEnv ? &papszPamEnv : NULL);
1783 if (RT_FAILURE(rc))
1784 return rc;
1785 }
1786#ifdef IPRT_USE_PAM
1787 /*
1788 * User unchanged, but if PROFILE is request we must try get the PAM
1789 * environmnet variables.
1790 *
1791 * For this to work, we'll need a special PAM service profile which doesn't
1792 * actually do any authentication, only concerns itself with the enviornment
1793 * setup. gdm-launch-environment is such one, and we use it if we haven't
1794 * got an IPRT specific one there.
1795 */
1796 else if (fNeedLoginEnv)
1797 {
1798 const char *pszService;
1799 if (rtProcPosixPamServiceExists("iprt-environment"))
1800 pszService = "iprt-environment";
1801# ifdef IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT
1802 else if (rtProcPosixPamServiceExists(IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT))
1803 pszService = IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT;
1804# endif
1805 else if (rtProcPosixPamServiceExists("gdm-launch-environment"))
1806 pszService = "gdm-launch-environment";
1807 else
1808 pszService = NULL;
1809 if (pszService)
1810 {
1811 char szLoginName[512];
1812 rc = getlogin_r(szLoginName, sizeof(szLoginName));
1813 if (rc == 0)
1814 rc = rtProcPosixAuthenticateUsingPam(pszService, szLoginName, "xxx", &papszPamEnv, NULL);
1815 }
1816 }
1817#endif
1818
1819 /*
1820 * Create the child environment if either RTPROC_FLAGS_PROFILE or
1821 * RTPROC_FLAGS_ENV_CHANGE_RECORD are in effect.
1822 */
1823 RTENV hEnvToUse = hEnv;
1824 if ( (fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_PROFILE))
1825 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
1826 || hEnv == RTENV_DEFAULT) )
1827 {
1828 if (fFlags & RTPROC_FLAGS_PROFILE)
1829 rc = rtProcPosixCreateProfileEnv(&hEnvToUse, pszAsUser, uid, gid, fFlags, papszPamEnv);
1830 else
1831 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
1832 rtProcPosixFreePamEnv(papszPamEnv);
1833 papszPamEnv = NULL;
1834 if (RT_FAILURE(rc))
1835 return rc;
1836
1837 if ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) && hEnv != RTENV_DEFAULT)
1838 {
1839 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
1840 if (RT_FAILURE(rc))
1841 {
1842 RTEnvDestroy(hEnvToUse);
1843 return rc;
1844 }
1845 }
1846 }
1847 Assert(papszPamEnv == NULL);
1848
1849 /*
1850 * Check for execute access to the file, searching the PATH if needed.
1851 */
1852 const char *pszNativeExec = NULL;
1853 rc = rtPathToNative(&pszNativeExec, pszExec, NULL);
1854 if (RT_SUCCESS(rc))
1855 {
1856 if (access(pszNativeExec, X_OK) == 0)
1857 rc = VINF_SUCCESS;
1858 else
1859 {
1860 rc = errno;
1861 rtPathFreeNative(pszNativeExec, pszExec);
1862
1863 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
1864 || rc != ENOENT
1865 || RTPathHavePath(pszExec) )
1866 rc = RTErrConvertFromErrno(rc);
1867 else
1868 {
1869 /* Search the PATH for it: */
1870 char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
1871 if (pszPath)
1872 {
1873 PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)alloca(sizeof(*pResult));
1874 pResult->rcSticky = VINF_SUCCESS;
1875 rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, pResult);
1876 RTStrFree(pszPath);
1877 if (RT_SUCCESS(rc))
1878 {
1879 /* Found it. Now, convert to native path: */
1880 pszExec = pResult->szFound;
1881 rc = rtPathToNative(&pszNativeExec, pszExec, NULL);
1882 }
1883 else
1884 rc = rc != VERR_END_OF_STRING ? rc
1885 : pResult->rcSticky == VINF_SUCCESS ? VERR_FILE_NOT_FOUND : pResult->rcSticky;
1886 }
1887 else
1888 rc = VERR_NO_STR_MEMORY;
1889 }
1890 }
1891 if (RT_SUCCESS(rc))
1892 {
1893 /*
1894 * Convert arguments to child codeset if necessary.
1895 */
1896 char **papszArgsConverted = (char **)papszArgs;
1897 if (!(fFlags & RTPROC_FLAGS_UTF8_ARGV))
1898 rc = rtProcPosixConvertArgv(papszArgs, hEnvToUse, &papszArgsConverted);
1899 if (RT_SUCCESS(rc))
1900 {
1901 /*
1902 * The rest of the process creation is reused internally by rtProcPosixCreateProfileEnv.
1903 */
1904 rc = rtProcPosixCreateInner(pszNativeExec, papszArgsConverted, hEnv, hEnvToUse, fFlags, pszAsUser, uid, gid,
1905 RT_ELEMENTS(aStdFds), aStdFds, pvExtraData, phProcess);
1906
1907 }
1908
1909 /* Free the translated argv copy, if needed. */
1910 if (papszArgsConverted != (char **)papszArgs)
1911 {
1912 for (size_t i = 0; papszArgsConverted[i] != NULL; i++)
1913 RTStrFree(papszArgsConverted[i]);
1914 RTMemFree(papszArgsConverted);
1915 }
1916 rtPathFreeNative(pszNativeExec, pszExec);
1917 }
1918 }
1919 if (hEnvToUse != hEnv)
1920 RTEnvDestroy(hEnvToUse);
1921 return rc;
1922}
1923
1924
1925/**
1926 * The inner 2nd half of RTProcCreateEx.
1927 *
1928 * This is also used by rtProcPosixCreateProfileEnv().
1929 *
1930 * @returns IPRT status code.
1931 * @param pszNativeExec The executable to run (absolute path, X_OK).
1932 * Native path.
1933 * @param papszArgs The arguments. Caller has done codeset conversions.
1934 * @param hEnv The original enviornment request, needed for
1935 * adjustments if starting as different user.
1936 * @param hEnvToUse The environment we should use.
1937 * @param fFlags The process creation flags, RTPROC_FLAGS_XXX.
1938 * @param pszAsUser The user to start the process as, if requested.
1939 * @param uid The UID corrsponding to @a pszAsUser, ~0 if NULL.
1940 * @param gid The GID corrsponding to @a pszAsUser, ~0 if NULL.
1941 * @param cRedirFds Number of redirection file descriptors.
1942 * @param paRedirFds Pointer to redirection file descriptors. Entries
1943 * containing -1 are not modified (inherit from parent),
1944 * -2 indicates that the descriptor should be closed in the
1945 * child.
1946 * @param phProcess Where to return the process ID on success.
1947 */
1948static int rtProcPosixCreateInner(const char *pszNativeExec, const char * const *papszArgs, RTENV hEnv, RTENV hEnvToUse,
1949 uint32_t fFlags, const char *pszAsUser, uid_t uid, gid_t gid,
1950 unsigned cRedirFds, int *paRedirFds, void *pvExtraData, PRTPROCESS phProcess)
1951{
1952 /*
1953 * Get the environment block.
1954 */
1955 const char * const *papszEnv = RTEnvGetExecEnvP(hEnvToUse);
1956 AssertPtrReturn(papszEnv, VERR_INVALID_HANDLE);
1957
1958 /*
1959 * Optimize the redirections.
1960 */
1961 while (cRedirFds > 0 && paRedirFds[cRedirFds - 1] == -1)
1962 cRedirFds--;
1963
1964 /*
1965 * Child PID.
1966 */
1967 pid_t pid = -1;
1968
1969 /*
1970 * Take care of detaching the process.
1971 *
1972 * HACK ALERT! Put the process into a new process group with pgid = pid
1973 * to make sure it differs from that of the parent process to ensure that
1974 * the IPRT waitpid call doesn't race anyone (read XPCOM) doing group wide
1975 * waits. setsid() includes the setpgid() functionality.
1976 * 2010-10-11 XPCOM no longer waits for anything, but it cannot hurt.
1977 */
1978#ifndef RT_OS_OS2
1979 if (fFlags & RTPROC_FLAGS_DETACHED)
1980 {
1981# ifdef RT_OS_SOLARIS
1982 int templateFd = -1;
1983 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1984 {
1985 templateFd = rtSolarisContractPreFork();
1986 if (templateFd == -1)
1987 return VERR_OPEN_FAILED;
1988 }
1989# endif /* RT_OS_SOLARIS */
1990 pid = fork();
1991 if (!pid)
1992 {
1993# ifdef RT_OS_SOLARIS
1994 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1995 rtSolarisContractPostForkChild(templateFd);
1996# endif
1997 setsid(); /* see comment above */
1998
1999 pid = -1;
2000 /* Child falls through to the actual spawn code below. */
2001 }
2002 else
2003 {
2004# ifdef RT_OS_SOLARIS
2005 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
2006 rtSolarisContractPostForkParent(templateFd, pid);
2007# endif
2008 if (pid > 0)
2009 {
2010 /* Must wait for the temporary process to avoid a zombie. */
2011 int status = 0;
2012 pid_t pidChild = 0;
2013
2014 /* Restart if we get interrupted. */
2015 do
2016 {
2017 pidChild = waitpid(pid, &status, 0);
2018 } while ( pidChild == -1
2019 && errno == EINTR);
2020
2021 /* Assume that something wasn't found. No detailed info. */
2022 if (status)
2023 return VERR_PROCESS_NOT_FOUND;
2024 if (phProcess)
2025 *phProcess = 0;
2026 return VINF_SUCCESS;
2027 }
2028 return RTErrConvertFromErrno(errno);
2029 }
2030 }
2031#endif
2032
2033 /*
2034 * Spawn the child.
2035 *
2036 * Any spawn code MUST not execute any atexit functions if it is for a
2037 * detached process. It would lead to running the atexit functions which
2038 * make only sense for the parent. libORBit e.g. gets confused by multiple
2039 * execution. Remember, there was only a fork() so far, and until exec()
2040 * is successfully run there is nothing which would prevent doing anything
2041 * silly with the (duplicated) file descriptors.
2042 */
2043 int rc;
2044#ifdef HAVE_POSIX_SPAWN
2045 /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */
2046 if ( uid == ~(uid_t)0
2047 && gid == ~(gid_t)0
2048 && !(fFlags & RTPROC_FLAGS_CWD))
2049 {
2050 /* Spawn attributes. */
2051 posix_spawnattr_t Attr;
2052 rc = posix_spawnattr_init(&Attr);
2053 if (!rc)
2054 {
2055 /* Indicate that process group and signal mask are to be changed,
2056 and that the child should use default signal actions. */
2057 rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
2058 Assert(rc == 0);
2059
2060 /* The child starts in its own process group. */
2061 if (!rc)
2062 {
2063 rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */);
2064 Assert(rc == 0);
2065 }
2066
2067 /* Unmask all signals. */
2068 if (!rc)
2069 {
2070 sigset_t SigMask;
2071 sigemptyset(&SigMask);
2072 rc = posix_spawnattr_setsigmask(&Attr, &SigMask); Assert(rc == 0);
2073 }
2074
2075 /* File changes. */
2076 posix_spawn_file_actions_t FileActions;
2077 posix_spawn_file_actions_t *pFileActions = NULL;
2078 if (!rc && cRedirFds > 0)
2079 {
2080 rc = posix_spawn_file_actions_init(&FileActions);
2081 if (!rc)
2082 {
2083 pFileActions = &FileActions;
2084 for (unsigned i = 0; i < cRedirFds; i++)
2085 {
2086 int fd = paRedirFds[i];
2087 if (fd == -2)
2088 rc = posix_spawn_file_actions_addclose(&FileActions, i);
2089 else if (fd >= 0 && fd != (int)i)
2090 {
2091 rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i);
2092 if (!rc)
2093 {
2094 for (unsigned j = i + 1; j < cRedirFds; j++)
2095 if (paRedirFds[j] == fd)
2096 {
2097 fd = -1;
2098 break;
2099 }
2100 if (fd >= 0)
2101 rc = posix_spawn_file_actions_addclose(&FileActions, fd);
2102 }
2103 }
2104 if (rc)
2105 break;
2106 }
2107 }
2108 }
2109
2110 if (!rc)
2111 rc = posix_spawn(&pid, pszNativeExec, pFileActions, &Attr, (char * const *)papszArgs,
2112 (char * const *)papszEnv);
2113
2114 /* cleanup */
2115 int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2);
2116 if (pFileActions)
2117 {
2118 rc2 = posix_spawn_file_actions_destroy(pFileActions);
2119 Assert(rc2 == 0);
2120 }
2121
2122 /* return on success.*/
2123 if (!rc)
2124 {
2125 /* For a detached process this happens in the temp process, so
2126 * it's not worth doing anything as this process must exit. */
2127 if (fFlags & RTPROC_FLAGS_DETACHED)
2128 _Exit(0);
2129 if (phProcess)
2130 *phProcess = pid;
2131 return VINF_SUCCESS;
2132 }
2133 }
2134 /* For a detached process this happens in the temp process, so
2135 * it's not worth doing anything as this process must exit. */
2136 if (fFlags & RTPROC_FLAGS_DETACHED)
2137 _Exit(124);
2138 }
2139 else
2140#endif
2141 {
2142#ifdef RT_OS_SOLARIS
2143 int templateFd = -1;
2144 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
2145 {
2146 templateFd = rtSolarisContractPreFork();
2147 if (templateFd == -1)
2148 return VERR_OPEN_FAILED;
2149 }
2150#endif /* RT_OS_SOLARIS */
2151 pid = fork();
2152 if (!pid)
2153 {
2154#ifdef RT_OS_SOLARIS
2155 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
2156 rtSolarisContractPostForkChild(templateFd);
2157#endif /* RT_OS_SOLARIS */
2158 if (!(fFlags & RTPROC_FLAGS_DETACHED))
2159 setpgid(0, 0); /* see comment above */
2160
2161 /*
2162 * Change group and user if requested.
2163 */
2164#if 1 /** @todo This needs more work, see suplib/hardening. */
2165 if (pszAsUser)
2166 {
2167 int ret = initgroups(pszAsUser, gid);
2168 if (ret)
2169 {
2170 if (fFlags & RTPROC_FLAGS_DETACHED)
2171 _Exit(126);
2172 else
2173 exit(126);
2174 }
2175 }
2176 if (gid != ~(gid_t)0)
2177 {
2178 if (setgid(gid))
2179 {
2180 if (fFlags & RTPROC_FLAGS_DETACHED)
2181 _Exit(126);
2182 else
2183 exit(126);
2184 }
2185 }
2186
2187 if (uid != ~(uid_t)0)
2188 {
2189 if (setuid(uid))
2190 {
2191 if (fFlags & RTPROC_FLAGS_DETACHED)
2192 _Exit(126);
2193 else
2194 exit(126);
2195 }
2196 }
2197#endif
2198 if (fFlags & RTPROC_FLAGS_CWD)
2199 {
2200 const char *pszCwd = (const char *)pvExtraData;
2201 if (pszCwd && *pszCwd)
2202 {
2203 rc = RTPathSetCurrent(pszCwd);
2204 if (RT_FAILURE(rc))
2205 {
2206 /** @todo r=bela What is the right exit code here?? */
2207 if (fFlags & RTPROC_FLAGS_DETACHED)
2208 _Exit(126);
2209 else
2210 /*exit*/_Exit(126);
2211 }
2212 }
2213 }
2214
2215 /*
2216 * Some final profile environment tweaks, if running as user.
2217 */
2218 if ( (fFlags & RTPROC_FLAGS_PROFILE)
2219 && pszAsUser
2220 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
2221 || hEnv == RTENV_DEFAULT) )
2222 {
2223 rc = rtProcPosixAdjustProfileEnvFromChild(hEnvToUse, fFlags, hEnv);
2224 papszEnv = RTEnvGetExecEnvP(hEnvToUse);
2225 if (RT_FAILURE(rc) || !papszEnv)
2226 {
2227 if (fFlags & RTPROC_FLAGS_DETACHED)
2228 _Exit(126);
2229 else
2230 exit(126);
2231 }
2232 }
2233
2234 /*
2235 * Unset the signal mask.
2236 */
2237 sigset_t SigMask;
2238 sigemptyset(&SigMask);
2239 rc = sigprocmask(SIG_SETMASK, &SigMask, NULL);
2240 Assert(rc == 0);
2241
2242 /*
2243 * Apply changes to the standard file descriptor and stuff.
2244 */
2245 for (unsigned i = 0; i < cRedirFds; i++)
2246 {
2247 int fd = paRedirFds[i];
2248 if (fd == -2)
2249 close(i);
2250 else if (fd >= 0)
2251 {
2252 int rc2 = dup2(fd, i);
2253 if (rc2 != (int)i)
2254 {
2255 if (fFlags & RTPROC_FLAGS_DETACHED)
2256 _Exit(125);
2257 else
2258 exit(125);
2259 }
2260 for (unsigned j = i + 1; j < cRedirFds; j++)
2261 if (paRedirFds[j] == fd)
2262 {
2263 fd = -1;
2264 break;
2265 }
2266 if (fd >= 0)
2267 close(fd);
2268 }
2269 }
2270
2271 /*
2272 * Finally, execute the requested program.
2273 */
2274 rc = execve(pszNativeExec, (char * const *)papszArgs, (char * const *)papszEnv);
2275 if (errno == ENOEXEC)
2276 {
2277 /* This can happen when trying to start a shell script without the magic #!/bin/sh */
2278 RTAssertMsg2Weak("Cannot execute this binary format!\n");
2279 }
2280 else
2281 RTAssertMsg2Weak("execve returns %d errno=%d (%s)\n", rc, errno, pszNativeExec);
2282 RTAssertReleasePanic();
2283 if (fFlags & RTPROC_FLAGS_DETACHED)
2284 _Exit(127);
2285 else
2286 exit(127);
2287 }
2288#ifdef RT_OS_SOLARIS
2289 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
2290 rtSolarisContractPostForkParent(templateFd, pid);
2291#endif /* RT_OS_SOLARIS */
2292 if (pid > 0)
2293 {
2294 /* For a detached process this happens in the temp process, so
2295 * it's not worth doing anything as this process must exit. */
2296 if (fFlags & RTPROC_FLAGS_DETACHED)
2297 _Exit(0);
2298 if (phProcess)
2299 *phProcess = pid;
2300 return VINF_SUCCESS;
2301 }
2302 /* For a detached process this happens in the temp process, so
2303 * it's not worth doing anything as this process must exit. */
2304 if (fFlags & RTPROC_FLAGS_DETACHED)
2305 _Exit(124);
2306 return RTErrConvertFromErrno(errno);
2307 }
2308
2309 return VERR_NOT_IMPLEMENTED;
2310}
2311
2312
2313RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
2314{
2315 /*
2316 * Fork the child process in a new session and quit the parent.
2317 *
2318 * - fork once and create a new session (setsid). This will detach us
2319 * from the controlling tty meaning that we won't receive the SIGHUP
2320 * (or any other signal) sent to that session.
2321 * - The SIGHUP signal is ignored because the session/parent may throw
2322 * us one before we get to the setsid.
2323 * - When the parent exit(0) we will become an orphan and re-parented to
2324 * the init process.
2325 * - Because of the sometimes unexpected semantics of assigning the
2326 * controlling tty automagically when a session leader first opens a tty,
2327 * we will fork() once more to get rid of the session leadership role.
2328 */
2329
2330 /* We start off by opening the pidfile, so that we can fail straight away
2331 * if it already exists. */
2332 int fdPidfile = -1;
2333 if (pszPidfile != NULL)
2334 {
2335 /* @note the exclusive create is not guaranteed on all file
2336 * systems (e.g. NFSv2) */
2337 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
2338 return RTErrConvertFromErrno(errno);
2339 }
2340
2341 /* Ignore SIGHUP straight away. */
2342 struct sigaction OldSigAct;
2343 struct sigaction SigAct;
2344 memset(&SigAct, 0, sizeof(SigAct));
2345 SigAct.sa_handler = SIG_IGN;
2346 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
2347
2348 /* First fork, to become independent process. */
2349 pid_t pid = fork();
2350 if (pid == -1)
2351 {
2352 if (fdPidfile != -1)
2353 close(fdPidfile);
2354 return RTErrConvertFromErrno(errno);
2355 }
2356 if (pid != 0)
2357 {
2358 /* Parent exits, no longer necessary. The child gets reparented
2359 * to the init process. */
2360 exit(0);
2361 }
2362
2363 /* Create new session, fix up the standard file descriptors and the
2364 * current working directory. */
2365 /** @todo r=klaus the webservice uses this function and assumes that the
2366 * contract id of the daemon is the same as that of the original process.
2367 * Whenever this code is changed this must still remain possible. */
2368 pid_t newpgid = setsid();
2369 int SavedErrno = errno;
2370 if (rcSigAct != -1)
2371 sigaction(SIGHUP, &OldSigAct, NULL);
2372 if (newpgid == -1)
2373 {
2374 if (fdPidfile != -1)
2375 close(fdPidfile);
2376 return RTErrConvertFromErrno(SavedErrno);
2377 }
2378
2379 if (!fNoClose)
2380 {
2381 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
2382 int fd = open("/dev/null", O_RDWR);
2383 if (fd == -1) /* paranoia */
2384 {
2385 close(STDIN_FILENO);
2386 close(STDOUT_FILENO);
2387 close(STDERR_FILENO);
2388 fd = open("/dev/null", O_RDWR);
2389 }
2390 if (fd != -1)
2391 {
2392 dup2(fd, STDIN_FILENO);
2393 dup2(fd, STDOUT_FILENO);
2394 dup2(fd, STDERR_FILENO);
2395 if (fd > 2)
2396 close(fd);
2397 }
2398 }
2399
2400 if (!fNoChDir)
2401 {
2402 int rcIgnored = chdir("/");
2403 NOREF(rcIgnored);
2404 }
2405
2406 /* Second fork to lose session leader status. */
2407 pid = fork();
2408 if (pid == -1)
2409 {
2410 if (fdPidfile != -1)
2411 close(fdPidfile);
2412 return RTErrConvertFromErrno(errno);
2413 }
2414
2415 if (pid != 0)
2416 {
2417 /* Write the pid file, this is done in the parent, before exiting. */
2418 if (fdPidfile != -1)
2419 {
2420 char szBuf[256];
2421 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
2422 ssize_t cbIgnored = write(fdPidfile, szBuf, cbPid); NOREF(cbIgnored);
2423 close(fdPidfile);
2424 }
2425 exit(0);
2426 }
2427
2428 if (fdPidfile != -1)
2429 close(fdPidfile);
2430
2431 return VINF_SUCCESS;
2432}
2433
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use