VirtualBox

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

Last change on this file was 103581, checked in by vboxsync, 3 months ago

IPRT/process-creation-posix: Try hardcoded paths for macOS if loading the PAM .so files fail if SIP is enabled when authenticating via rtProcPosixAuthenticateUsingPam(). This commits patch v1 of bugref:9946

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

© 2023 Oracle
ContactPrivacy policyTerms of Use