VirtualBox

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

Last change on this file since 92723 was 92672, checked in by vboxsync, 3 years ago

IPRT/RTProcCreateEx/posix: Added VERR_PROC_NO_ARG_TRANSLATION for argument translation trouble. bugref:10153

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

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