VirtualBox

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

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

IPRT,RTProcCreatEx/posix: Do *not* use achBuf for temporary stuff, just use a dedicated buffer of RTPATH_MAX size for that. Corrected incorrect rtProcPosixProfileEnvRunAndHarvest docs (pszAsUser won't be NULL). bugref:10153

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