VirtualBox

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

Last change on this file since 79007 was 79007, checked in by vboxsync, 6 years ago

IPRT/process-creation-posix.cpp: Use reentrant functions for accessing /etc/passwd and /etc/shadow on linux. Tried to combine the code for all non-darwin platforms. ticketref:18682

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 39.6 KB
Line 
1/* $Id: process-creation-posix.cpp 79007 2019-06-05 17:24:03Z vboxsync $ */
2/** @file
3 * IPRT - Process Creation, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2019 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
34#include <unistd.h>
35#include <stdlib.h>
36#include <errno.h>
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <sys/wait.h>
40#include <fcntl.h>
41#include <signal.h>
42#include <grp.h>
43#include <pwd.h>
44#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
45# include <crypt.h>
46# include <shadow.h>
47#endif
48
49#if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
50/* While Solaris has posix_spawn() of course we don't want to use it as
51 * we need to have the child in a different process contract, no matter
52 * whether it is started detached or not. */
53# define HAVE_POSIX_SPAWN 1
54#endif
55#if defined(RT_OS_DARWIN) && defined(MAC_OS_X_VERSION_MIN_REQUIRED)
56# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
57# define HAVE_POSIX_SPAWN 1
58# endif
59#endif
60#ifdef HAVE_POSIX_SPAWN
61# include <spawn.h>
62#endif
63
64#ifdef RT_OS_DARWIN
65# include <mach-o/dyld.h>
66# include <security/pam_appl.h>
67# include <stdlib.h>
68# include <dlfcn.h>
69# include <iprt/asm.h>
70#endif
71
72#ifdef RT_OS_SOLARIS
73# include <limits.h>
74# include <sys/ctfs.h>
75# include <sys/contract/process.h>
76# include <libcontract.h>
77#endif
78
79#ifndef RT_OS_SOLARIS
80# include <paths.h>
81#else
82# define _PATH_MAILDIR "/var/mail"
83# define _PATH_DEFPATH "/usr/bin:/bin"
84# define _PATH_STDPATH "/sbin:/usr/sbin:/bin:/usr/bin"
85#endif
86
87
88#include <iprt/process.h>
89#include "internal/iprt.h"
90
91#include <iprt/assert.h>
92#include <iprt/env.h>
93#include <iprt/err.h>
94#include <iprt/file.h>
95#include <iprt/log.h>
96#include <iprt/path.h>
97#include <iprt/pipe.h>
98#include <iprt/socket.h>
99#include <iprt/string.h>
100#include <iprt/mem.h>
101#include "internal/process.h"
102
103
104/*********************************************************************************************************************************
105* Structures and Typedefs *
106*********************************************************************************************************************************/
107#ifdef RT_OS_DARWIN
108/** For passing info between rtCheckCredentials and rtPamConv. */
109typedef struct RTPROCPAMARGS
110{
111 const char *pszUser;
112 const char *pszPassword;
113} RTPROCPAMARGS;
114/** Pointer to rtPamConv argument package. */
115typedef RTPROCPAMARGS *PRTPROCPAMARGS;
116#endif
117
118
119#ifdef RT_OS_DARWIN
120/**
121 * Worker for rtCheckCredentials that feeds password and maybe username to PAM.
122 *
123 * @returns PAM status.
124 * @param cMessages Number of messages.
125 * @param papMessages Message vector.
126 * @param ppaResponses Where to put our responses.
127 * @param pvAppData Pointer to RTPROCPAMARGS.
128 */
129static int rtPamConv(int cMessages, const struct pam_message **papMessages, struct pam_response **ppaResponses, void *pvAppData)
130{
131 LogFlow(("rtPamConv: cMessages=%d\n", cMessages));
132 PRTPROCPAMARGS pArgs = (PRTPROCPAMARGS)pvAppData;
133 AssertPtrReturn(pArgs, PAM_CONV_ERR);
134
135 struct pam_response *paResponses = (struct pam_response *)calloc(cMessages, sizeof(paResponses[0]));
136 AssertReturn(paResponses, PAM_CONV_ERR);
137 for (int i = 0; i < cMessages; i++)
138 {
139 LogFlow(("rtPamConv: #%d: msg_style=%d msg=%s\n", i, papMessages[i]->msg_style, papMessages[i]->msg));
140
141 paResponses[i].resp_retcode = 0;
142 if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_OFF)
143 paResponses[i].resp = strdup(pArgs->pszPassword);
144 else if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_ON)
145 paResponses[i].resp = strdup(pArgs->pszUser);
146 else
147 {
148 paResponses[i].resp = NULL;
149 continue;
150 }
151 if (paResponses[i].resp == NULL)
152 {
153 while (i-- > 0)
154 free(paResponses[i].resp);
155 free(paResponses);
156 LogFlow(("rtPamConv: out of memory\n"));
157 return PAM_CONV_ERR;
158 }
159 }
160
161 *ppaResponses = paResponses;
162 return PAM_SUCCESS;
163}
164#endif /* RT_OS_DARWIN */
165
166
167/**
168 * Check the credentials and return the gid/uid of user.
169 *
170 * @param pszUser username
171 * @param pszPasswd password
172 * @param gid where to store the GID of the user
173 * @param uid where to store the UID of the user
174 * @returns IPRT status code
175 */
176static int rtCheckCredentials(const char *pszUser, const char *pszPasswd, gid_t *pGid, uid_t *pUid)
177{
178#if defined(RT_OS_DARWIN)
179 RTLogPrintf("rtCheckCredentials\n");
180
181 /*
182 * Resolve user to UID and GID.
183 */
184 char szBuf[_4K];
185 struct passwd Pw;
186 struct passwd *pPw;
187 if (getpwnam_r(pszUser, &Pw, szBuf, sizeof(szBuf), &pPw) != 0)
188 return VERR_AUTHENTICATION_FAILURE;
189 if (!pPw)
190 return VERR_AUTHENTICATION_FAILURE;
191
192 *pUid = pPw->pw_uid;
193 *pGid = pPw->pw_gid;
194
195 /*
196 * Use PAM for the authentication.
197 * Note! libpam.2.dylib was introduced with 10.6.x (OpenPAM).
198 */
199 void *hModPam = dlopen("libpam.dylib", RTLD_LAZY | RTLD_GLOBAL);
200 if (hModPam)
201 {
202 int (*pfnPamStart)(const char *, const char *, struct pam_conv *, pam_handle_t **);
203 int (*pfnPamAuthenticate)(pam_handle_t *, int);
204 int (*pfnPamAcctMgmt)(pam_handle_t *, int);
205 int (*pfnPamSetItem)(pam_handle_t *, int, const void *);
206 int (*pfnPamEnd)(pam_handle_t *, int);
207 *(void **)&pfnPamStart = dlsym(hModPam, "pam_start");
208 *(void **)&pfnPamAuthenticate = dlsym(hModPam, "pam_authenticate");
209 *(void **)&pfnPamAcctMgmt = dlsym(hModPam, "pam_acct_mgmt");
210 *(void **)&pfnPamSetItem = dlsym(hModPam, "pam_set_item");
211 *(void **)&pfnPamEnd = dlsym(hModPam, "pam_end");
212 ASMCompilerBarrier();
213 if ( pfnPamStart
214 && pfnPamAuthenticate
215 && pfnPamAcctMgmt
216 && pfnPamSetItem
217 && pfnPamEnd)
218 {
219#define pam_start pfnPamStart
220#define pam_authenticate pfnPamAuthenticate
221#define pam_acct_mgmt pfnPamAcctMgmt
222#define pam_set_item pfnPamSetItem
223#define pam_end pfnPamEnd
224
225 /* Do the PAM stuff.
226 Note! Abusing 'login' here for now... */
227 pam_handle_t *hPam = NULL;
228 RTPROCPAMARGS PamConvArgs = { pszUser, pszPasswd };
229 struct pam_conv PamConversation;
230 RT_ZERO(PamConversation);
231 PamConversation.appdata_ptr = &PamConvArgs;
232 PamConversation.conv = rtPamConv;
233 int rc = pam_start("login", pszUser, &PamConversation, &hPam);
234 if (rc == PAM_SUCCESS)
235 {
236 rc = pam_set_item(hPam, PAM_RUSER, pszUser);
237 if (rc == PAM_SUCCESS)
238 rc = pam_authenticate(hPam, 0);
239 if (rc == PAM_SUCCESS)
240 {
241 rc = pam_acct_mgmt(hPam, 0);
242 if ( rc == PAM_SUCCESS
243 || rc == PAM_AUTHINFO_UNAVAIL /*??*/)
244 {
245 pam_end(hPam, PAM_SUCCESS);
246 dlclose(hModPam);
247 return VINF_SUCCESS;
248 }
249 Log(("rtCheckCredentials: pam_acct_mgmt -> %d\n", rc));
250 }
251 else
252 Log(("rtCheckCredentials: pam_authenticate -> %d\n", rc));
253 pam_end(hPam, rc);
254 }
255 else
256 Log(("rtCheckCredentials: pam_start -> %d\n", rc));
257 }
258 else
259 Log(("rtCheckCredentials: failed to resolve symbols: %p %p %p %p %p\n",
260 pfnPamStart, pfnPamAuthenticate, pfnPamAcctMgmt, pfnPamSetItem, pfnPamEnd));
261 dlclose(hModPam);
262 }
263 else
264 Log(("rtCheckCredentials: Loading libpam.dylib failed\n"));
265 return VERR_AUTHENTICATION_FAILURE;
266
267#else
268 /*
269 * Lookup the user in /etc/passwd first.
270 *
271 * Note! On FreeBSD and OS/2 the root user will open /etc/shadow here, so
272 * the getspnam_r step is not necessary.
273 */
274 struct passwd Pwd;
275 char szBuf[_4K];
276 struct passwd *pPwd = NULL;
277 if (getpwnam_r(pszUser, &Pwd, szBuf, sizeof(szBuf), &pPwd) != 0)
278 return VERR_AUTHENTICATION_FAILURE;
279 if (pPwd == NULL)
280 return VERR_AUTHENTICATION_FAILURE;
281
282# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
283 /*
284 * Ditto for /etc/shadow and replace pw_passwd from above if we can access it:
285 */
286 struct spwd ShwPwd;
287 char szBuf2[_4K];
288# if defined(RT_OS_LINUX)
289 struct spwd *pShwPwd = NULL;
290 if (getspnam_r(pszUser, &ShwPwd, szBuf2, sizeof(szBuf2), &pShwPwd) != 0)
291 pShwPwd = NULL;
292# else
293 struct spwd *pShwPwd = getspnam_r(pszUser, &ShwPwd, szBuf2, sizeof(szBuf2));
294# endif
295 if (pShwPwd != NULL)
296 pPwd->pw_passwd = pShwPwd->sp_pwdp;
297# endif
298
299 /*
300 * Encrypt the passed in password and see if it matches.
301 */
302# if !defined(RT_OS_LINUX)
303 int rc;
304# else
305 /* Default fCorrect=true if no password specified. In that case, pPwd->pw_passwd
306 must be NULL (no password set for this user). Fail if a password is specified
307 but the user does not have one assigned. */
308 int rc = !pszPasswd || !*pszPasswd ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
309 if (pPwd->pw_passwd && *pPwd->pw_passwd)
310# endif
311 {
312# if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
313 struct crypt_data CryptData;
314 RT_ZERO(CryptData);
315 char *pszEncPasswd = crypt_r(pszPasswd, pPwd->pw_passwd, &CryptData);
316 rc = pszEncPasswd && !strcmp(pszEncPasswd, pPwd->pw_passwd) ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
317 RTMemWipeThoroughly(&CryptData, sizeof(CryptData), 3);
318# else
319 char *pszEncPasswd = crypt(pszPasswd, ppw->pw_passwd);
320 rc = strcmp(pszEncPasswd, ppw->pw_passwd) == 0 ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
321# endif
322 }
323
324 /*
325 * Return GID and UID on success. Always wipe stack buffers.
326 */
327 if (RT_SUCCESS(rc))
328 {
329 *pGid = pPwd->pw_gid;
330 *pUid = pPwd->pw_uid;
331 }
332 RTMemWipeThoroughly(szBuf, sizeof(szBuf), 3);
333# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
334 RTMemWipeThoroughly(szBuf2, sizeof(szBuf2), 3);
335# endif
336 return rc;
337#endif
338}
339
340#ifdef RT_OS_SOLARIS
341
342/** @todo the error reporting of the Solaris process contract code could be
343 * a lot better, but essentially it is not meant to run into errors after
344 * the debugging phase. */
345static int rtSolarisContractPreFork(void)
346{
347 int templateFd = open64(CTFS_ROOT "/process/template", O_RDWR);
348 if (templateFd < 0)
349 return -1;
350
351 /* Set template parameters and event sets. */
352 if (ct_pr_tmpl_set_param(templateFd, CT_PR_PGRPONLY))
353 {
354 close(templateFd);
355 return -1;
356 }
357 if (ct_pr_tmpl_set_fatal(templateFd, CT_PR_EV_HWERR))
358 {
359 close(templateFd);
360 return -1;
361 }
362 if (ct_tmpl_set_critical(templateFd, 0))
363 {
364 close(templateFd);
365 return -1;
366 }
367 if (ct_tmpl_set_informative(templateFd, CT_PR_EV_HWERR))
368 {
369 close(templateFd);
370 return -1;
371 }
372
373 /* Make this the active template for the process. */
374 if (ct_tmpl_activate(templateFd))
375 {
376 close(templateFd);
377 return -1;
378 }
379
380 return templateFd;
381}
382
383static void rtSolarisContractPostForkChild(int templateFd)
384{
385 if (templateFd == -1)
386 return;
387
388 /* Clear the active template. */
389 ct_tmpl_clear(templateFd);
390 close(templateFd);
391}
392
393static void rtSolarisContractPostForkParent(int templateFd, pid_t pid)
394{
395 if (templateFd == -1)
396 return;
397
398 /* Clear the active template. */
399 int cleared = ct_tmpl_clear(templateFd);
400 close(templateFd);
401
402 /* If the clearing failed or the fork failed there's nothing more to do. */
403 if (cleared || pid <= 0)
404 return;
405
406 /* Look up the contract which was created by this thread. */
407 int statFd = open64(CTFS_ROOT "/process/latest", O_RDONLY);
408 if (statFd == -1)
409 return;
410 ct_stathdl_t statHdl;
411 if (ct_status_read(statFd, CTD_COMMON, &statHdl))
412 {
413 close(statFd);
414 return;
415 }
416 ctid_t ctId = ct_status_get_id(statHdl);
417 ct_status_free(statHdl);
418 close(statFd);
419 if (ctId < 0)
420 return;
421
422 /* Abandon this contract we just created. */
423 char ctlPath[PATH_MAX];
424 size_t len = snprintf(ctlPath, sizeof(ctlPath),
425 CTFS_ROOT "/process/%ld/ctl", (long)ctId);
426 if (len >= sizeof(ctlPath))
427 return;
428 int ctlFd = open64(ctlPath, O_WRONLY);
429 if (statFd == -1)
430 return;
431 if (ct_ctl_abandon(ctlFd) < 0)
432 {
433 close(ctlFd);
434 return;
435 }
436 close(ctlFd);
437}
438
439#endif /* RT_OS_SOLARIS */
440
441
442RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
443{
444 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
445 NULL, NULL, NULL, /* standard handles */
446 NULL /*pszAsUser*/, NULL /* pszPassword*/,
447 pProcess);
448}
449
450
451/**
452 * Adjust the profile environment after forking the child process and changing
453 * the UID.
454 *
455 * @returns IRPT status code.
456 * @param hEnvToUse The environment we're going to use with execve.
457 * @param fFlags The process creation flags.
458 * @param hEnv The environment passed in by the user.
459 */
460static int rtProcPosixAdjustProfileEnvFromChild(RTENV hEnvToUse, uint32_t fFlags, RTENV hEnv)
461{
462 int rc = VINF_SUCCESS;
463#ifdef RT_OS_DARWIN
464 if ( RT_SUCCESS(rc)
465 && (!(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || RTEnvExistEx(hEnv, "TMPDIR")) )
466 {
467 char szValue[_4K];
468 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szValue, sizeof(szValue));
469 if (cbNeeded > 0 && cbNeeded < sizeof(szValue))
470 {
471 char *pszTmp;
472 rc = RTStrCurrentCPToUtf8(&pszTmp, szValue);
473 if (RT_SUCCESS(rc))
474 {
475 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
476 RTStrFree(pszTmp);
477 }
478 }
479 else
480 rc = VERR_BUFFER_OVERFLOW;
481 }
482#else
483 RT_NOREF_PV(hEnvToUse); RT_NOREF_PV(fFlags); RT_NOREF_PV(hEnv);
484#endif
485 return rc;
486}
487
488
489/**
490 * Create a very very basic environment for a user.
491 *
492 * @returns IPRT status code.
493 * @param phEnvToUse Where to return the created environment.
494 * @param pszUser The user name for the profile.
495 */
496static int rtProcPosixCreateProfileEnv(PRTENV phEnvToUse, const char *pszUser)
497{
498 struct passwd Pwd;
499 struct passwd *pPwd = NULL;
500 char achBuf[_4K];
501 int rc;
502 errno = 0;
503 if (pszUser)
504 rc = getpwnam_r(pszUser, &Pwd, achBuf, sizeof(achBuf), &pPwd);
505 else
506 rc = getpwuid_r(getuid(), &Pwd, achBuf, sizeof(achBuf), &pPwd);
507 if (rc == 0 && pPwd)
508 {
509 char *pszDir;
510 rc = RTStrCurrentCPToUtf8(&pszDir, pPwd->pw_dir);
511 if (RT_SUCCESS(rc))
512 {
513 char *pszShell;
514 rc = RTStrCurrentCPToUtf8(&pszShell, pPwd->pw_shell);
515 if (RT_SUCCESS(rc))
516 {
517 char *pszUserFree = NULL;
518 if (!pszUser)
519 {
520 rc = RTStrCurrentCPToUtf8(&pszUserFree, pPwd->pw_name);
521 if (RT_SUCCESS(rc))
522 pszUser = pszUserFree;
523 }
524 if (RT_SUCCESS(rc))
525 {
526 rc = RTEnvCreate(phEnvToUse);
527 if (RT_SUCCESS(rc))
528 {
529 RTENV hEnvToUse = *phEnvToUse;
530
531 rc = RTEnvSetEx(hEnvToUse, "HOME", pszDir);
532 if (RT_SUCCESS(rc))
533 rc = RTEnvSetEx(hEnvToUse, "SHELL", pszShell);
534 if (RT_SUCCESS(rc))
535 rc = RTEnvSetEx(hEnvToUse, "USER", pszUser);
536 if (RT_SUCCESS(rc))
537 rc = RTEnvSetEx(hEnvToUse, "LOGNAME", pszUser);
538
539 if (RT_SUCCESS(rc))
540 rc = RTEnvSetEx(hEnvToUse, "PATH", pPwd->pw_uid == 0 ? _PATH_STDPATH : _PATH_DEFPATH);
541
542 if (RT_SUCCESS(rc))
543 {
544 RTStrPrintf(achBuf, sizeof(achBuf), "%s/%s", _PATH_MAILDIR, pszUser);
545 rc = RTEnvSetEx(hEnvToUse, "MAIL", achBuf);
546 }
547
548#ifdef RT_OS_DARWIN
549 if (RT_SUCCESS(rc) && !pszUserFree)
550 {
551 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, achBuf, sizeof(achBuf));
552 if (cbNeeded > 0 && cbNeeded < sizeof(achBuf))
553 {
554 char *pszTmp;
555 rc = RTStrCurrentCPToUtf8(&pszTmp, achBuf);
556 if (RT_SUCCESS(rc))
557 {
558 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
559 RTStrFree(pszTmp);
560 }
561 }
562 else
563 rc = VERR_BUFFER_OVERFLOW;
564 }
565#endif
566
567 /** @todo load /etc/environment, /etc/profile.env and ~/.pam_environment? */
568
569 if (RT_FAILURE(rc))
570 RTEnvDestroy(hEnvToUse);
571 }
572 RTStrFree(pszUserFree);
573 }
574 RTStrFree(pszShell);
575 }
576 RTStrFree(pszDir);
577 }
578 }
579 else
580 rc = errno ? RTErrConvertFromErrno(errno) : VERR_ACCESS_DENIED;
581 return rc;
582}
583
584
585/**
586 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
587 */
588static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
589{
590 const char *pszExec = (const char *)pvUser1;
591 char *pszRealExec = (char *)pvUser2;
592 int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
593 if (RT_FAILURE(rc))
594 return rc;
595 if (!access(pszRealExec, X_OK))
596 return VINF_SUCCESS;
597 if ( errno == EACCES
598 || errno == EPERM)
599 return RTErrConvertFromErrno(errno);
600 return VERR_TRY_AGAIN;
601}
602
603/**
604 * Cleans up the environment on the way out.
605 */
606static int rtProcPosixCreateReturn(int rc, RTENV hEnvToUse, RTENV hEnv)
607{
608 if (hEnvToUse != hEnv)
609 RTEnvDestroy(hEnvToUse);
610 return rc;
611}
612
613
614RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
615 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
616 const char *pszPassword, PRTPROCESS phProcess)
617{
618 int rc;
619 LogFlow(("RTProcCreateEx: pszExec=%s pszAsUser=%s\n", pszExec, pszAsUser));
620
621 /*
622 * Input validation
623 */
624 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
625 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
626 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
627 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
628 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
629 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
630 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
631 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
632 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
633 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
634#if defined(RT_OS_OS2)
635 if (fFlags & RTPROC_FLAGS_DETACHED)
636 return VERR_PROC_DETACH_NOT_SUPPORTED;
637#endif
638
639 /*
640 * Get the file descriptors for the handles we've been passed.
641 */
642 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
643 int aStdFds[3] = { -1, -1, -1 };
644 for (int i = 0; i < 3; i++)
645 {
646 if (paHandles[i])
647 {
648 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
649 switch (paHandles[i]->enmType)
650 {
651 case RTHANDLETYPE_FILE:
652 aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE
653 ? (int)RTFileToNative(paHandles[i]->u.hFile)
654 : -2 /* close it */;
655 break;
656
657 case RTHANDLETYPE_PIPE:
658 aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
659 ? (int)RTPipeToNative(paHandles[i]->u.hPipe)
660 : -2 /* close it */;
661 break;
662
663 case RTHANDLETYPE_SOCKET:
664 aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
665 ? (int)RTSocketToNative(paHandles[i]->u.hSocket)
666 : -2 /* close it */;
667 break;
668
669 default:
670 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
671 }
672 /** @todo check the close-on-execness of these handles? */
673 }
674 }
675
676 for (int i = 0; i < 3; i++)
677 if (aStdFds[i] == i)
678 aStdFds[i] = -1;
679
680 for (int i = 0; i < 3; i++)
681 AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i,
682 ("%i := %i not possible because we're lazy\n", i, aStdFds[i]),
683 VERR_NOT_SUPPORTED);
684
685 /*
686 * Resolve the user id if specified.
687 */
688 uid_t uid = ~(uid_t)0;
689 gid_t gid = ~(gid_t)0;
690 if (pszAsUser)
691 {
692 rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid);
693 if (RT_FAILURE(rc))
694 return rc;
695 }
696
697 /*
698 * Create the child environment if either RTPROC_FLAGS_PROFILE or
699 * RTPROC_FLAGS_ENV_CHANGE_RECORD are in effect.
700 */
701 RTENV hEnvToUse = hEnv;
702 if ( (fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_PROFILE))
703 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
704 || hEnv == RTENV_DEFAULT) )
705 {
706 if (fFlags & RTPROC_FLAGS_PROFILE)
707 rc = rtProcPosixCreateProfileEnv(&hEnvToUse, pszAsUser);
708 else
709 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
710 if (RT_SUCCESS(rc))
711 {
712 if ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) && hEnv != RTENV_DEFAULT)
713 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
714 if (RT_FAILURE(rc))
715 RTEnvDestroy(hEnvToUse);
716 }
717 if (RT_FAILURE(rc))
718 return rc;
719 }
720
721 /*
722 * Check for execute access to the file.
723 */
724 char szRealExec[RTPATH_MAX];
725 if (access(pszExec, X_OK))
726 {
727 rc = errno;
728 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
729 || rc != ENOENT
730 || RTPathHavePath(pszExec) )
731 rc = RTErrConvertFromErrno(rc);
732 else
733 {
734 /* search */
735 char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
736 rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
737 RTStrFree(pszPath);
738 if (RT_SUCCESS(rc))
739 pszExec = szRealExec;
740 else
741 rc = rc == VERR_END_OF_STRING ? VERR_FILE_NOT_FOUND : rc;
742 }
743
744 if (RT_FAILURE(rc))
745 return rtProcPosixCreateReturn(rc, hEnvToUse, hEnv);
746 }
747
748 pid_t pid = -1;
749 const char * const *papszEnv = RTEnvGetExecEnvP(hEnvToUse);
750 AssertPtrReturn(papszEnv, rtProcPosixCreateReturn(VERR_INVALID_HANDLE, hEnvToUse, hEnv));
751
752
753 /*
754 * Take care of detaching the process.
755 *
756 * HACK ALERT! Put the process into a new process group with pgid = pid
757 * to make sure it differs from that of the parent process to ensure that
758 * the IPRT waitpid call doesn't race anyone (read XPCOM) doing group wide
759 * waits. setsid() includes the setpgid() functionality.
760 * 2010-10-11 XPCOM no longer waits for anything, but it cannot hurt.
761 */
762#ifndef RT_OS_OS2
763 if (fFlags & RTPROC_FLAGS_DETACHED)
764 {
765# ifdef RT_OS_SOLARIS
766 int templateFd = -1;
767 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
768 {
769 templateFd = rtSolarisContractPreFork();
770 if (templateFd == -1)
771 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
772 }
773# endif /* RT_OS_SOLARIS */
774 pid = fork();
775 if (!pid)
776 {
777# ifdef RT_OS_SOLARIS
778 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
779 rtSolarisContractPostForkChild(templateFd);
780# endif
781 setsid(); /* see comment above */
782
783 pid = -1;
784 /* Child falls through to the actual spawn code below. */
785 }
786 else
787 {
788# ifdef RT_OS_SOLARIS
789 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
790 rtSolarisContractPostForkParent(templateFd, pid);
791# endif
792 if (pid > 0)
793 {
794 /* Must wait for the temporary process to avoid a zombie. */
795 int status = 0;
796 pid_t pidChild = 0;
797
798 /* Restart if we get interrupted. */
799 do
800 {
801 pidChild = waitpid(pid, &status, 0);
802 } while ( pidChild == -1
803 && errno == EINTR);
804
805 /* Assume that something wasn't found. No detailed info. */
806 if (status)
807 return rtProcPosixCreateReturn(VERR_PROCESS_NOT_FOUND, hEnvToUse, hEnv);
808 if (phProcess)
809 *phProcess = 0;
810 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
811 }
812 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
813 }
814 }
815#endif
816
817 /*
818 * Spawn the child.
819 *
820 * Any spawn code MUST not execute any atexit functions if it is for a
821 * detached process. It would lead to running the atexit functions which
822 * make only sense for the parent. libORBit e.g. gets confused by multiple
823 * execution. Remember, there was only a fork() so far, and until exec()
824 * is successfully run there is nothing which would prevent doing anything
825 * silly with the (duplicated) file descriptors.
826 */
827#ifdef HAVE_POSIX_SPAWN
828 /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */
829 if ( uid == ~(uid_t)0
830 && gid == ~(gid_t)0)
831 {
832 /* Spawn attributes. */
833 posix_spawnattr_t Attr;
834 rc = posix_spawnattr_init(&Attr);
835 if (!rc)
836 {
837 /* Indicate that process group and signal mask are to be changed,
838 and that the child should use default signal actions. */
839 rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
840 Assert(rc == 0);
841
842 /* The child starts in its own process group. */
843 if (!rc)
844 {
845 rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */);
846 Assert(rc == 0);
847 }
848
849 /* Unmask all signals. */
850 if (!rc)
851 {
852 sigset_t SigMask;
853 sigemptyset(&SigMask);
854 rc = posix_spawnattr_setsigmask(&Attr, &SigMask); Assert(rc == 0);
855 }
856
857 /* File changes. */
858 posix_spawn_file_actions_t FileActions;
859 posix_spawn_file_actions_t *pFileActions = NULL;
860 if ((aStdFds[0] != -1 || aStdFds[1] != -1 || aStdFds[2] != -1) && !rc)
861 {
862 rc = posix_spawn_file_actions_init(&FileActions);
863 if (!rc)
864 {
865 pFileActions = &FileActions;
866 for (int i = 0; i < 3; i++)
867 {
868 int fd = aStdFds[i];
869 if (fd == -2)
870 rc = posix_spawn_file_actions_addclose(&FileActions, i);
871 else if (fd >= 0 && fd != i)
872 {
873 rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i);
874 if (!rc)
875 {
876 for (int j = i + 1; j < 3; j++)
877 if (aStdFds[j] == fd)
878 {
879 fd = -1;
880 break;
881 }
882 if (fd >= 0)
883 rc = posix_spawn_file_actions_addclose(&FileActions, fd);
884 }
885 }
886 if (rc)
887 break;
888 }
889 }
890 }
891
892 if (!rc)
893 rc = posix_spawn(&pid, pszExec, pFileActions, &Attr, (char * const *)papszArgs,
894 (char * const *)papszEnv);
895
896 /* cleanup */
897 int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2);
898 if (pFileActions)
899 {
900 rc2 = posix_spawn_file_actions_destroy(pFileActions);
901 Assert(rc2 == 0);
902 }
903
904 /* return on success.*/
905 if (!rc)
906 {
907 /* For a detached process this happens in the temp process, so
908 * it's not worth doing anything as this process must exit. */
909 if (fFlags & RTPROC_FLAGS_DETACHED)
910 _Exit(0);
911 if (phProcess)
912 *phProcess = pid;
913 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
914 }
915 }
916 /* For a detached process this happens in the temp process, so
917 * it's not worth doing anything as this process must exit. */
918 if (fFlags & RTPROC_FLAGS_DETACHED)
919 _Exit(124);
920 }
921 else
922#endif
923 {
924#ifdef RT_OS_SOLARIS
925 int templateFd = -1;
926 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
927 {
928 templateFd = rtSolarisContractPreFork();
929 if (templateFd == -1)
930 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
931 }
932#endif /* RT_OS_SOLARIS */
933 pid = fork();
934 if (!pid)
935 {
936#ifdef RT_OS_SOLARIS
937 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
938 rtSolarisContractPostForkChild(templateFd);
939#endif /* RT_OS_SOLARIS */
940 if (!(fFlags & RTPROC_FLAGS_DETACHED))
941 setpgid(0, 0); /* see comment above */
942
943 /*
944 * Change group and user if requested.
945 */
946#if 1 /** @todo This needs more work, see suplib/hardening. */
947 if (pszAsUser)
948 {
949 int ret = initgroups(pszAsUser, gid);
950 if (ret)
951 {
952 if (fFlags & RTPROC_FLAGS_DETACHED)
953 _Exit(126);
954 else
955 exit(126);
956 }
957 }
958 if (gid != ~(gid_t)0)
959 {
960 if (setgid(gid))
961 {
962 if (fFlags & RTPROC_FLAGS_DETACHED)
963 _Exit(126);
964 else
965 exit(126);
966 }
967 }
968
969 if (uid != ~(uid_t)0)
970 {
971 if (setuid(uid))
972 {
973 if (fFlags & RTPROC_FLAGS_DETACHED)
974 _Exit(126);
975 else
976 exit(126);
977 }
978 }
979#endif
980
981 /*
982 * Some final profile environment tweaks, if running as user.
983 */
984 if ( (fFlags & RTPROC_FLAGS_PROFILE)
985 && pszAsUser
986 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
987 || hEnv == RTENV_DEFAULT) )
988 {
989 rc = rtProcPosixAdjustProfileEnvFromChild(hEnvToUse, fFlags, hEnv);
990 papszEnv = RTEnvGetExecEnvP(hEnvToUse);
991 if (RT_FAILURE(rc) || !papszEnv)
992 {
993 if (fFlags & RTPROC_FLAGS_DETACHED)
994 _Exit(126);
995 else
996 exit(126);
997 }
998 }
999
1000 /*
1001 * Unset the signal mask.
1002 */
1003 sigset_t SigMask;
1004 sigemptyset(&SigMask);
1005 rc = sigprocmask(SIG_SETMASK, &SigMask, NULL);
1006 Assert(rc == 0);
1007
1008 /*
1009 * Apply changes to the standard file descriptor and stuff.
1010 */
1011 for (int i = 0; i < 3; i++)
1012 {
1013 int fd = aStdFds[i];
1014 if (fd == -2)
1015 close(i);
1016 else if (fd >= 0)
1017 {
1018 int rc2 = dup2(fd, i);
1019 if (rc2 != i)
1020 {
1021 if (fFlags & RTPROC_FLAGS_DETACHED)
1022 _Exit(125);
1023 else
1024 exit(125);
1025 }
1026 for (int j = i + 1; j < 3; j++)
1027 if (aStdFds[j] == fd)
1028 {
1029 fd = -1;
1030 break;
1031 }
1032 if (fd >= 0)
1033 close(fd);
1034 }
1035 }
1036
1037 /*
1038 * Finally, execute the requested program.
1039 */
1040 rc = execve(pszExec, (char * const *)papszArgs, (char * const *)papszEnv);
1041 if (errno == ENOEXEC)
1042 {
1043 /* This can happen when trying to start a shell script without the magic #!/bin/sh */
1044 RTAssertMsg2Weak("Cannot execute this binary format!\n");
1045 }
1046 else
1047 RTAssertMsg2Weak("execve returns %d errno=%d\n", rc, errno);
1048 RTAssertReleasePanic();
1049 if (fFlags & RTPROC_FLAGS_DETACHED)
1050 _Exit(127);
1051 else
1052 exit(127);
1053 }
1054#ifdef RT_OS_SOLARIS
1055 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1056 rtSolarisContractPostForkParent(templateFd, pid);
1057#endif /* RT_OS_SOLARIS */
1058 if (pid > 0)
1059 {
1060 /* For a detached process this happens in the temp process, so
1061 * it's not worth doing anything as this process must exit. */
1062 if (fFlags & RTPROC_FLAGS_DETACHED)
1063 _Exit(0);
1064 if (phProcess)
1065 *phProcess = pid;
1066 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
1067 }
1068 /* For a detached process this happens in the temp process, so
1069 * it's not worth doing anything as this process must exit. */
1070 if (fFlags & RTPROC_FLAGS_DETACHED)
1071 _Exit(124);
1072 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
1073 }
1074
1075 return rtProcPosixCreateReturn(VERR_NOT_IMPLEMENTED, hEnvToUse, hEnv);
1076}
1077
1078
1079RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
1080{
1081 /*
1082 * Fork the child process in a new session and quit the parent.
1083 *
1084 * - fork once and create a new session (setsid). This will detach us
1085 * from the controlling tty meaning that we won't receive the SIGHUP
1086 * (or any other signal) sent to that session.
1087 * - The SIGHUP signal is ignored because the session/parent may throw
1088 * us one before we get to the setsid.
1089 * - When the parent exit(0) we will become an orphan and re-parented to
1090 * the init process.
1091 * - Because of the sometimes unexpected semantics of assigning the
1092 * controlling tty automagically when a session leader first opens a tty,
1093 * we will fork() once more to get rid of the session leadership role.
1094 */
1095
1096 /* We start off by opening the pidfile, so that we can fail straight away
1097 * if it already exists. */
1098 int fdPidfile = -1;
1099 if (pszPidfile != NULL)
1100 {
1101 /* @note the exclusive create is not guaranteed on all file
1102 * systems (e.g. NFSv2) */
1103 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
1104 return RTErrConvertFromErrno(errno);
1105 }
1106
1107 /* Ignore SIGHUP straight away. */
1108 struct sigaction OldSigAct;
1109 struct sigaction SigAct;
1110 memset(&SigAct, 0, sizeof(SigAct));
1111 SigAct.sa_handler = SIG_IGN;
1112 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
1113
1114 /* First fork, to become independent process. */
1115 pid_t pid = fork();
1116 if (pid == -1)
1117 {
1118 if (fdPidfile != -1)
1119 close(fdPidfile);
1120 return RTErrConvertFromErrno(errno);
1121 }
1122 if (pid != 0)
1123 {
1124 /* Parent exits, no longer necessary. The child gets reparented
1125 * to the init process. */
1126 exit(0);
1127 }
1128
1129 /* Create new session, fix up the standard file descriptors and the
1130 * current working directory. */
1131 /** @todo r=klaus the webservice uses this function and assumes that the
1132 * contract id of the daemon is the same as that of the original process.
1133 * Whenever this code is changed this must still remain possible. */
1134 pid_t newpgid = setsid();
1135 int SavedErrno = errno;
1136 if (rcSigAct != -1)
1137 sigaction(SIGHUP, &OldSigAct, NULL);
1138 if (newpgid == -1)
1139 {
1140 if (fdPidfile != -1)
1141 close(fdPidfile);
1142 return RTErrConvertFromErrno(SavedErrno);
1143 }
1144
1145 if (!fNoClose)
1146 {
1147 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
1148 int fd = open("/dev/null", O_RDWR);
1149 if (fd == -1) /* paranoia */
1150 {
1151 close(STDIN_FILENO);
1152 close(STDOUT_FILENO);
1153 close(STDERR_FILENO);
1154 fd = open("/dev/null", O_RDWR);
1155 }
1156 if (fd != -1)
1157 {
1158 dup2(fd, STDIN_FILENO);
1159 dup2(fd, STDOUT_FILENO);
1160 dup2(fd, STDERR_FILENO);
1161 if (fd > 2)
1162 close(fd);
1163 }
1164 }
1165
1166 if (!fNoChDir)
1167 {
1168 int rcIgnored = chdir("/");
1169 NOREF(rcIgnored);
1170 }
1171
1172 /* Second fork to lose session leader status. */
1173 pid = fork();
1174 if (pid == -1)
1175 {
1176 if (fdPidfile != -1)
1177 close(fdPidfile);
1178 return RTErrConvertFromErrno(errno);
1179 }
1180
1181 if (pid != 0)
1182 {
1183 /* Write the pid file, this is done in the parent, before exiting. */
1184 if (fdPidfile != -1)
1185 {
1186 char szBuf[256];
1187 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
1188 ssize_t cbIgnored = write(fdPidfile, szBuf, cbPid); NOREF(cbIgnored);
1189 close(fdPidfile);
1190 }
1191 exit(0);
1192 }
1193
1194 if (fdPidfile != -1)
1195 close(fdPidfile);
1196
1197 return VINF_SUCCESS;
1198}
1199
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