VirtualBox

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

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

IPRT/RTProcCreateEx: Quick darwin implementation using PAM. bugref:4802

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 39.1 KB
Line 
1/* $Id: process-creation-posix.cpp 75725 2018-11-25 19:18:32Z vboxsync $ */
2/** @file
3 * IPRT - Process Creation, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2017 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#elif defined(RT_OS_LINUX)
268 struct passwd *pw;
269
270 pw = getpwnam(pszUser);
271 if (!pw)
272 return VERR_AUTHENTICATION_FAILURE;
273
274 if (!pszPasswd)
275 pszPasswd = "";
276
277 struct spwd *spwd;
278 /* works only if /etc/shadow is accessible */
279 spwd = getspnam(pszUser);
280 if (spwd)
281 pw->pw_passwd = spwd->sp_pwdp;
282
283 /* Default fCorrect=true if no password specified. In that case, pw->pw_passwd
284 * must be NULL (no password set for this user). Fail if a password is specified
285 * but the user does not have one assigned. */
286 int fCorrect = !pszPasswd || !*pszPasswd;
287 if (pw->pw_passwd && *pw->pw_passwd)
288 {
289 struct crypt_data *data = (struct crypt_data*)RTMemTmpAllocZ(sizeof(*data));
290 /* be reentrant */
291 char *pszEncPasswd = crypt_r(pszPasswd, pw->pw_passwd, data);
292 fCorrect = pszEncPasswd && !strcmp(pszEncPasswd, pw->pw_passwd);
293 RTMemTmpFree(data);
294 }
295 if (!fCorrect)
296 return VERR_AUTHENTICATION_FAILURE;
297
298 *pGid = pw->pw_gid;
299 *pUid = pw->pw_uid;
300 return VINF_SUCCESS;
301
302#elif defined(RT_OS_SOLARIS)
303 struct passwd *ppw, pw;
304 char szBuf[1024];
305
306 if (getpwnam_r(pszUser, &pw, szBuf, sizeof(szBuf), &ppw) != 0 || ppw == NULL)
307 return VERR_AUTHENTICATION_FAILURE;
308
309 if (!pszPasswd)
310 pszPasswd = "";
311
312 struct spwd spwd;
313 char szPwdBuf[1024];
314 /* works only if /etc/shadow is accessible */
315 if (getspnam_r(pszUser, &spwd, szPwdBuf, sizeof(szPwdBuf)) != NULL)
316 ppw->pw_passwd = spwd.sp_pwdp;
317
318 char *pszEncPasswd = crypt(pszPasswd, ppw->pw_passwd);
319 if (strcmp(pszEncPasswd, ppw->pw_passwd))
320 return VERR_AUTHENTICATION_FAILURE;
321
322 *pGid = ppw->pw_gid;
323 *pUid = ppw->pw_uid;
324 return VINF_SUCCESS;
325
326#else
327 NOREF(pszUser); NOREF(pszPasswd); NOREF(pGid); NOREF(pUid);
328 return VERR_AUTHENTICATION_FAILURE;
329#endif
330}
331
332#ifdef RT_OS_SOLARIS
333
334/** @todo the error reporting of the Solaris process contract code could be
335 * a lot better, but essentially it is not meant to run into errors after
336 * the debugging phase. */
337static int rtSolarisContractPreFork(void)
338{
339 int templateFd = open64(CTFS_ROOT "/process/template", O_RDWR);
340 if (templateFd < 0)
341 return -1;
342
343 /* Set template parameters and event sets. */
344 if (ct_pr_tmpl_set_param(templateFd, CT_PR_PGRPONLY))
345 {
346 close(templateFd);
347 return -1;
348 }
349 if (ct_pr_tmpl_set_fatal(templateFd, CT_PR_EV_HWERR))
350 {
351 close(templateFd);
352 return -1;
353 }
354 if (ct_tmpl_set_critical(templateFd, 0))
355 {
356 close(templateFd);
357 return -1;
358 }
359 if (ct_tmpl_set_informative(templateFd, CT_PR_EV_HWERR))
360 {
361 close(templateFd);
362 return -1;
363 }
364
365 /* Make this the active template for the process. */
366 if (ct_tmpl_activate(templateFd))
367 {
368 close(templateFd);
369 return -1;
370 }
371
372 return templateFd;
373}
374
375static void rtSolarisContractPostForkChild(int templateFd)
376{
377 if (templateFd == -1)
378 return;
379
380 /* Clear the active template. */
381 ct_tmpl_clear(templateFd);
382 close(templateFd);
383}
384
385static void rtSolarisContractPostForkParent(int templateFd, pid_t pid)
386{
387 if (templateFd == -1)
388 return;
389
390 /* Clear the active template. */
391 int cleared = ct_tmpl_clear(templateFd);
392 close(templateFd);
393
394 /* If the clearing failed or the fork failed there's nothing more to do. */
395 if (cleared || pid <= 0)
396 return;
397
398 /* Look up the contract which was created by this thread. */
399 int statFd = open64(CTFS_ROOT "/process/latest", O_RDONLY);
400 if (statFd == -1)
401 return;
402 ct_stathdl_t statHdl;
403 if (ct_status_read(statFd, CTD_COMMON, &statHdl))
404 {
405 close(statFd);
406 return;
407 }
408 ctid_t ctId = ct_status_get_id(statHdl);
409 ct_status_free(statHdl);
410 close(statFd);
411 if (ctId < 0)
412 return;
413
414 /* Abandon this contract we just created. */
415 char ctlPath[PATH_MAX];
416 size_t len = snprintf(ctlPath, sizeof(ctlPath),
417 CTFS_ROOT "/process/%ld/ctl", (long)ctId);
418 if (len >= sizeof(ctlPath))
419 return;
420 int ctlFd = open64(ctlPath, O_WRONLY);
421 if (statFd == -1)
422 return;
423 if (ct_ctl_abandon(ctlFd) < 0)
424 {
425 close(ctlFd);
426 return;
427 }
428 close(ctlFd);
429}
430
431#endif /* RT_OS_SOLARIS */
432
433
434RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
435{
436 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
437 NULL, NULL, NULL, /* standard handles */
438 NULL /*pszAsUser*/, NULL /* pszPassword*/,
439 pProcess);
440}
441
442
443/**
444 * Adjust the profile environment after forking the child process and changing
445 * the UID.
446 *
447 * @returns IRPT status code.
448 * @param hEnvToUse The environment we're going to use with execve.
449 * @param fFlags The process creation flags.
450 * @param hEnv The environment passed in by the user.
451 */
452static int rtProcPosixAdjustProfileEnvFromChild(RTENV hEnvToUse, uint32_t fFlags, RTENV hEnv)
453{
454 int rc = VINF_SUCCESS;
455#ifdef RT_OS_DARWIN
456 if ( RT_SUCCESS(rc)
457 && (!(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || RTEnvExistEx(hEnv, "TMPDIR")) )
458 {
459 char szValue[_4K];
460 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szValue, sizeof(szValue));
461 if (cbNeeded > 0 && cbNeeded < sizeof(szValue))
462 {
463 char *pszTmp;
464 rc = RTStrCurrentCPToUtf8(&pszTmp, szValue);
465 if (RT_SUCCESS(rc))
466 {
467 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
468 RTStrFree(pszTmp);
469 }
470 }
471 else
472 rc = VERR_BUFFER_OVERFLOW;
473 }
474#else
475 RT_NOREF_PV(hEnvToUse); RT_NOREF_PV(fFlags); RT_NOREF_PV(hEnv);
476#endif
477 return rc;
478}
479
480
481/**
482 * Create a very very basic environment for a user.
483 *
484 * @returns IPRT status code.
485 * @param phEnvToUse Where to return the created environment.
486 * @param pszUser The user name for the profile.
487 */
488static int rtProcPosixCreateProfileEnv(PRTENV phEnvToUse, const char *pszUser)
489{
490 struct passwd Pwd;
491 struct passwd *pPwd = NULL;
492 char achBuf[_4K];
493 int rc;
494 errno = 0;
495 if (pszUser)
496 rc = getpwnam_r(pszUser, &Pwd, achBuf, sizeof(achBuf), &pPwd);
497 else
498 rc = getpwuid_r(getuid(), &Pwd, achBuf, sizeof(achBuf), &pPwd);
499 if (rc == 0 && pPwd)
500 {
501 char *pszDir;
502 rc = RTStrCurrentCPToUtf8(&pszDir, pPwd->pw_dir);
503 if (RT_SUCCESS(rc))
504 {
505 char *pszShell;
506 rc = RTStrCurrentCPToUtf8(&pszShell, pPwd->pw_shell);
507 if (RT_SUCCESS(rc))
508 {
509 char *pszUserFree = NULL;
510 if (!pszUser)
511 {
512 rc = RTStrCurrentCPToUtf8(&pszUserFree, pPwd->pw_name);
513 if (RT_SUCCESS(rc))
514 pszUser = pszUserFree;
515 }
516 if (RT_SUCCESS(rc))
517 {
518 rc = RTEnvCreate(phEnvToUse);
519 if (RT_SUCCESS(rc))
520 {
521 RTENV hEnvToUse = *phEnvToUse;
522
523 rc = RTEnvSetEx(hEnvToUse, "HOME", pszDir);
524 if (RT_SUCCESS(rc))
525 rc = RTEnvSetEx(hEnvToUse, "SHELL", pszShell);
526 if (RT_SUCCESS(rc))
527 rc = RTEnvSetEx(hEnvToUse, "USER", pszUser);
528 if (RT_SUCCESS(rc))
529 rc = RTEnvSetEx(hEnvToUse, "LOGNAME", pszUser);
530
531 if (RT_SUCCESS(rc))
532 rc = RTEnvSetEx(hEnvToUse, "PATH", pPwd->pw_uid == 0 ? _PATH_STDPATH : _PATH_DEFPATH);
533
534 if (RT_SUCCESS(rc))
535 {
536 RTStrPrintf(achBuf, sizeof(achBuf), "%s/%s", _PATH_MAILDIR, pszUser);
537 rc = RTEnvSetEx(hEnvToUse, "MAIL", achBuf);
538 }
539
540#ifdef RT_OS_DARWIN
541 if (RT_SUCCESS(rc) && !pszUserFree)
542 {
543 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, achBuf, sizeof(achBuf));
544 if (cbNeeded > 0 && cbNeeded < sizeof(achBuf))
545 {
546 char *pszTmp;
547 rc = RTStrCurrentCPToUtf8(&pszTmp, achBuf);
548 if (RT_SUCCESS(rc))
549 {
550 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
551 RTStrFree(pszTmp);
552 }
553 }
554 else
555 rc = VERR_BUFFER_OVERFLOW;
556 }
557#endif
558
559 /** @todo load /etc/environment, /etc/profile.env and ~/.pam_environment? */
560
561 if (RT_FAILURE(rc))
562 RTEnvDestroy(hEnvToUse);
563 }
564 RTStrFree(pszUserFree);
565 }
566 RTStrFree(pszShell);
567 }
568 RTStrFree(pszDir);
569 }
570 }
571 else
572 rc = errno ? RTErrConvertFromErrno(errno) : VERR_ACCESS_DENIED;
573 return rc;
574}
575
576
577/**
578 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
579 */
580static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
581{
582 const char *pszExec = (const char *)pvUser1;
583 char *pszRealExec = (char *)pvUser2;
584 int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
585 if (RT_FAILURE(rc))
586 return rc;
587 if (!access(pszRealExec, X_OK))
588 return VINF_SUCCESS;
589 if ( errno == EACCES
590 || errno == EPERM)
591 return RTErrConvertFromErrno(errno);
592 return VERR_TRY_AGAIN;
593}
594
595/**
596 * Cleans up the environment on the way out.
597 */
598static int rtProcPosixCreateReturn(int rc, RTENV hEnvToUse, RTENV hEnv)
599{
600 if (hEnvToUse != hEnv)
601 RTEnvDestroy(hEnvToUse);
602 return rc;
603}
604
605
606RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
607 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
608 const char *pszPassword, PRTPROCESS phProcess)
609{
610 int rc;
611 LogFlow(("RTProcCreateEx: pszExec=%s pszAsUser=%s\n", pszExec, pszAsUser));
612
613 /*
614 * Input validation
615 */
616 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
617 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
618 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
619 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
620 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
621 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
622 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
623 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
624 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
625 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
626#if defined(RT_OS_OS2)
627 if (fFlags & RTPROC_FLAGS_DETACHED)
628 return VERR_PROC_DETACH_NOT_SUPPORTED;
629#endif
630
631 /*
632 * Get the file descriptors for the handles we've been passed.
633 */
634 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
635 int aStdFds[3] = { -1, -1, -1 };
636 for (int i = 0; i < 3; i++)
637 {
638 if (paHandles[i])
639 {
640 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
641 switch (paHandles[i]->enmType)
642 {
643 case RTHANDLETYPE_FILE:
644 aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE
645 ? (int)RTFileToNative(paHandles[i]->u.hFile)
646 : -2 /* close it */;
647 break;
648
649 case RTHANDLETYPE_PIPE:
650 aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
651 ? (int)RTPipeToNative(paHandles[i]->u.hPipe)
652 : -2 /* close it */;
653 break;
654
655 case RTHANDLETYPE_SOCKET:
656 aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
657 ? (int)RTSocketToNative(paHandles[i]->u.hSocket)
658 : -2 /* close it */;
659 break;
660
661 default:
662 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
663 }
664 /** @todo check the close-on-execness of these handles? */
665 }
666 }
667
668 for (int i = 0; i < 3; i++)
669 if (aStdFds[i] == i)
670 aStdFds[i] = -1;
671
672 for (int i = 0; i < 3; i++)
673 AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i,
674 ("%i := %i not possible because we're lazy\n", i, aStdFds[i]),
675 VERR_NOT_SUPPORTED);
676
677 /*
678 * Resolve the user id if specified.
679 */
680 uid_t uid = ~(uid_t)0;
681 gid_t gid = ~(gid_t)0;
682 if (pszAsUser)
683 {
684 rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid);
685 if (RT_FAILURE(rc))
686 return rc;
687 }
688
689 /*
690 * Create the child environment if either RTPROC_FLAGS_PROFILE or
691 * RTPROC_FLAGS_ENV_CHANGE_RECORD are in effect.
692 */
693 RTENV hEnvToUse = hEnv;
694 if ( (fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_PROFILE))
695 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
696 || hEnv == RTENV_DEFAULT) )
697 {
698 if (fFlags & RTPROC_FLAGS_PROFILE)
699 rc = rtProcPosixCreateProfileEnv(&hEnvToUse, pszAsUser);
700 else
701 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
702 if (RT_SUCCESS(rc))
703 {
704 if ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) && hEnv != RTENV_DEFAULT)
705 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
706 if (RT_FAILURE(rc))
707 RTEnvDestroy(hEnvToUse);
708 }
709 if (RT_FAILURE(rc))
710 return rc;
711 }
712
713 /*
714 * Check for execute access to the file.
715 */
716 char szRealExec[RTPATH_MAX];
717 if (access(pszExec, X_OK))
718 {
719 rc = errno;
720 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
721 || rc != ENOENT
722 || RTPathHavePath(pszExec) )
723 rc = RTErrConvertFromErrno(rc);
724 else
725 {
726 /* search */
727 char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
728 rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
729 RTStrFree(pszPath);
730 if (RT_SUCCESS(rc))
731 pszExec = szRealExec;
732 else
733 rc = rc == VERR_END_OF_STRING ? VERR_FILE_NOT_FOUND : rc;
734 }
735
736 if (RT_FAILURE(rc))
737 return rtProcPosixCreateReturn(rc, hEnvToUse, hEnv);
738 }
739
740 pid_t pid = -1;
741 const char * const *papszEnv = RTEnvGetExecEnvP(hEnvToUse);
742 AssertPtrReturn(papszEnv, rtProcPosixCreateReturn(VERR_INVALID_HANDLE, hEnvToUse, hEnv));
743
744
745 /*
746 * Take care of detaching the process.
747 *
748 * HACK ALERT! Put the process into a new process group with pgid = pid
749 * to make sure it differs from that of the parent process to ensure that
750 * the IPRT waitpid call doesn't race anyone (read XPCOM) doing group wide
751 * waits. setsid() includes the setpgid() functionality.
752 * 2010-10-11 XPCOM no longer waits for anything, but it cannot hurt.
753 */
754#ifndef RT_OS_OS2
755 if (fFlags & RTPROC_FLAGS_DETACHED)
756 {
757# ifdef RT_OS_SOLARIS
758 int templateFd = -1;
759 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
760 {
761 templateFd = rtSolarisContractPreFork();
762 if (templateFd == -1)
763 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
764 }
765# endif /* RT_OS_SOLARIS */
766 pid = fork();
767 if (!pid)
768 {
769# ifdef RT_OS_SOLARIS
770 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
771 rtSolarisContractPostForkChild(templateFd);
772# endif
773 setsid(); /* see comment above */
774
775 pid = -1;
776 /* Child falls through to the actual spawn code below. */
777 }
778 else
779 {
780# ifdef RT_OS_SOLARIS
781 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
782 rtSolarisContractPostForkParent(templateFd, pid);
783# endif
784 if (pid > 0)
785 {
786 /* Must wait for the temporary process to avoid a zombie. */
787 int status = 0;
788 pid_t pidChild = 0;
789
790 /* Restart if we get interrupted. */
791 do
792 {
793 pidChild = waitpid(pid, &status, 0);
794 } while ( pidChild == -1
795 && errno == EINTR);
796
797 /* Assume that something wasn't found. No detailed info. */
798 if (status)
799 return rtProcPosixCreateReturn(VERR_PROCESS_NOT_FOUND, hEnvToUse, hEnv);
800 if (phProcess)
801 *phProcess = 0;
802 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
803 }
804 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
805 }
806 }
807#endif
808
809 /*
810 * Spawn the child.
811 *
812 * Any spawn code MUST not execute any atexit functions if it is for a
813 * detached process. It would lead to running the atexit functions which
814 * make only sense for the parent. libORBit e.g. gets confused by multiple
815 * execution. Remember, there was only a fork() so far, and until exec()
816 * is successfully run there is nothing which would prevent doing anything
817 * silly with the (duplicated) file descriptors.
818 */
819#ifdef HAVE_POSIX_SPAWN
820 /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */
821 if ( uid == ~(uid_t)0
822 && gid == ~(gid_t)0)
823 {
824 /* Spawn attributes. */
825 posix_spawnattr_t Attr;
826 rc = posix_spawnattr_init(&Attr);
827 if (!rc)
828 {
829 /* Indicate that process group and signal mask are to be changed,
830 and that the child should use default signal actions. */
831 rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
832 Assert(rc == 0);
833
834 /* The child starts in its own process group. */
835 if (!rc)
836 {
837 rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */);
838 Assert(rc == 0);
839 }
840
841 /* Unmask all signals. */
842 if (!rc)
843 {
844 sigset_t SigMask;
845 sigemptyset(&SigMask);
846 rc = posix_spawnattr_setsigmask(&Attr, &SigMask); Assert(rc == 0);
847 }
848
849 /* File changes. */
850 posix_spawn_file_actions_t FileActions;
851 posix_spawn_file_actions_t *pFileActions = NULL;
852 if ((aStdFds[0] != -1 || aStdFds[1] != -1 || aStdFds[2] != -1) && !rc)
853 {
854 rc = posix_spawn_file_actions_init(&FileActions);
855 if (!rc)
856 {
857 pFileActions = &FileActions;
858 for (int i = 0; i < 3; i++)
859 {
860 int fd = aStdFds[i];
861 if (fd == -2)
862 rc = posix_spawn_file_actions_addclose(&FileActions, i);
863 else if (fd >= 0 && fd != i)
864 {
865 rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i);
866 if (!rc)
867 {
868 for (int j = i + 1; j < 3; j++)
869 if (aStdFds[j] == fd)
870 {
871 fd = -1;
872 break;
873 }
874 if (fd >= 0)
875 rc = posix_spawn_file_actions_addclose(&FileActions, fd);
876 }
877 }
878 if (rc)
879 break;
880 }
881 }
882 }
883
884 if (!rc)
885 rc = posix_spawn(&pid, pszExec, pFileActions, &Attr, (char * const *)papszArgs,
886 (char * const *)papszEnv);
887
888 /* cleanup */
889 int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2);
890 if (pFileActions)
891 {
892 rc2 = posix_spawn_file_actions_destroy(pFileActions);
893 Assert(rc2 == 0);
894 }
895
896 /* return on success.*/
897 if (!rc)
898 {
899 /* For a detached process this happens in the temp process, so
900 * it's not worth doing anything as this process must exit. */
901 if (fFlags & RTPROC_FLAGS_DETACHED)
902 _Exit(0);
903 if (phProcess)
904 *phProcess = pid;
905 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
906 }
907 }
908 /* For a detached process this happens in the temp process, so
909 * it's not worth doing anything as this process must exit. */
910 if (fFlags & RTPROC_FLAGS_DETACHED)
911 _Exit(124);
912 }
913 else
914#endif
915 {
916#ifdef RT_OS_SOLARIS
917 int templateFd = -1;
918 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
919 {
920 templateFd = rtSolarisContractPreFork();
921 if (templateFd == -1)
922 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
923 }
924#endif /* RT_OS_SOLARIS */
925 pid = fork();
926 if (!pid)
927 {
928#ifdef RT_OS_SOLARIS
929 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
930 rtSolarisContractPostForkChild(templateFd);
931#endif /* RT_OS_SOLARIS */
932 if (!(fFlags & RTPROC_FLAGS_DETACHED))
933 setpgid(0, 0); /* see comment above */
934
935 /*
936 * Change group and user if requested.
937 */
938#if 1 /** @todo This needs more work, see suplib/hardening. */
939 if (pszAsUser)
940 {
941 int ret = initgroups(pszAsUser, gid);
942 if (ret)
943 {
944 if (fFlags & RTPROC_FLAGS_DETACHED)
945 _Exit(126);
946 else
947 exit(126);
948 }
949 }
950 if (gid != ~(gid_t)0)
951 {
952 if (setgid(gid))
953 {
954 if (fFlags & RTPROC_FLAGS_DETACHED)
955 _Exit(126);
956 else
957 exit(126);
958 }
959 }
960
961 if (uid != ~(uid_t)0)
962 {
963 if (setuid(uid))
964 {
965 if (fFlags & RTPROC_FLAGS_DETACHED)
966 _Exit(126);
967 else
968 exit(126);
969 }
970 }
971#endif
972
973 /*
974 * Some final profile environment tweaks, if running as user.
975 */
976 if ( (fFlags & RTPROC_FLAGS_PROFILE)
977 && pszAsUser
978 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
979 || hEnv == RTENV_DEFAULT) )
980 {
981 rc = rtProcPosixAdjustProfileEnvFromChild(hEnvToUse, fFlags, hEnv);
982 papszEnv = RTEnvGetExecEnvP(hEnvToUse);
983 if (RT_FAILURE(rc) || !papszEnv)
984 {
985 if (fFlags & RTPROC_FLAGS_DETACHED)
986 _Exit(126);
987 else
988 exit(126);
989 }
990 }
991
992 /*
993 * Unset the signal mask.
994 */
995 sigset_t SigMask;
996 sigemptyset(&SigMask);
997 rc = sigprocmask(SIG_SETMASK, &SigMask, NULL);
998 Assert(rc == 0);
999
1000 /*
1001 * Apply changes to the standard file descriptor and stuff.
1002 */
1003 for (int i = 0; i < 3; i++)
1004 {
1005 int fd = aStdFds[i];
1006 if (fd == -2)
1007 close(i);
1008 else if (fd >= 0)
1009 {
1010 int rc2 = dup2(fd, i);
1011 if (rc2 != i)
1012 {
1013 if (fFlags & RTPROC_FLAGS_DETACHED)
1014 _Exit(125);
1015 else
1016 exit(125);
1017 }
1018 for (int j = i + 1; j < 3; j++)
1019 if (aStdFds[j] == fd)
1020 {
1021 fd = -1;
1022 break;
1023 }
1024 if (fd >= 0)
1025 close(fd);
1026 }
1027 }
1028
1029 /*
1030 * Finally, execute the requested program.
1031 */
1032 rc = execve(pszExec, (char * const *)papszArgs, (char * const *)papszEnv);
1033 if (errno == ENOEXEC)
1034 {
1035 /* This can happen when trying to start a shell script without the magic #!/bin/sh */
1036 RTAssertMsg2Weak("Cannot execute this binary format!\n");
1037 }
1038 else
1039 RTAssertMsg2Weak("execve returns %d errno=%d\n", rc, errno);
1040 RTAssertReleasePanic();
1041 if (fFlags & RTPROC_FLAGS_DETACHED)
1042 _Exit(127);
1043 else
1044 exit(127);
1045 }
1046#ifdef RT_OS_SOLARIS
1047 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1048 rtSolarisContractPostForkParent(templateFd, pid);
1049#endif /* RT_OS_SOLARIS */
1050 if (pid > 0)
1051 {
1052 /* For a detached process this happens in the temp process, so
1053 * it's not worth doing anything as this process must exit. */
1054 if (fFlags & RTPROC_FLAGS_DETACHED)
1055 _Exit(0);
1056 if (phProcess)
1057 *phProcess = pid;
1058 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
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(124);
1064 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
1065 }
1066
1067 return rtProcPosixCreateReturn(VERR_NOT_IMPLEMENTED, hEnvToUse, hEnv);
1068}
1069
1070
1071RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
1072{
1073 /*
1074 * Fork the child process in a new session and quit the parent.
1075 *
1076 * - fork once and create a new session (setsid). This will detach us
1077 * from the controlling tty meaning that we won't receive the SIGHUP
1078 * (or any other signal) sent to that session.
1079 * - The SIGHUP signal is ignored because the session/parent may throw
1080 * us one before we get to the setsid.
1081 * - When the parent exit(0) we will become an orphan and re-parented to
1082 * the init process.
1083 * - Because of the sometimes unexpected semantics of assigning the
1084 * controlling tty automagically when a session leader first opens a tty,
1085 * we will fork() once more to get rid of the session leadership role.
1086 */
1087
1088 /* We start off by opening the pidfile, so that we can fail straight away
1089 * if it already exists. */
1090 int fdPidfile = -1;
1091 if (pszPidfile != NULL)
1092 {
1093 /* @note the exclusive create is not guaranteed on all file
1094 * systems (e.g. NFSv2) */
1095 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
1096 return RTErrConvertFromErrno(errno);
1097 }
1098
1099 /* Ignore SIGHUP straight away. */
1100 struct sigaction OldSigAct;
1101 struct sigaction SigAct;
1102 memset(&SigAct, 0, sizeof(SigAct));
1103 SigAct.sa_handler = SIG_IGN;
1104 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
1105
1106 /* First fork, to become independent process. */
1107 pid_t pid = fork();
1108 if (pid == -1)
1109 {
1110 if (fdPidfile != -1)
1111 close(fdPidfile);
1112 return RTErrConvertFromErrno(errno);
1113 }
1114 if (pid != 0)
1115 {
1116 /* Parent exits, no longer necessary. The child gets reparented
1117 * to the init process. */
1118 exit(0);
1119 }
1120
1121 /* Create new session, fix up the standard file descriptors and the
1122 * current working directory. */
1123 /** @todo r=klaus the webservice uses this function and assumes that the
1124 * contract id of the daemon is the same as that of the original process.
1125 * Whenever this code is changed this must still remain possible. */
1126 pid_t newpgid = setsid();
1127 int SavedErrno = errno;
1128 if (rcSigAct != -1)
1129 sigaction(SIGHUP, &OldSigAct, NULL);
1130 if (newpgid == -1)
1131 {
1132 if (fdPidfile != -1)
1133 close(fdPidfile);
1134 return RTErrConvertFromErrno(SavedErrno);
1135 }
1136
1137 if (!fNoClose)
1138 {
1139 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
1140 int fd = open("/dev/null", O_RDWR);
1141 if (fd == -1) /* paranoia */
1142 {
1143 close(STDIN_FILENO);
1144 close(STDOUT_FILENO);
1145 close(STDERR_FILENO);
1146 fd = open("/dev/null", O_RDWR);
1147 }
1148 if (fd != -1)
1149 {
1150 dup2(fd, STDIN_FILENO);
1151 dup2(fd, STDOUT_FILENO);
1152 dup2(fd, STDERR_FILENO);
1153 if (fd > 2)
1154 close(fd);
1155 }
1156 }
1157
1158 if (!fNoChDir)
1159 {
1160 int rcIgnored = chdir("/");
1161 NOREF(rcIgnored);
1162 }
1163
1164 /* Second fork to lose session leader status. */
1165 pid = fork();
1166 if (pid == -1)
1167 {
1168 if (fdPidfile != -1)
1169 close(fdPidfile);
1170 return RTErrConvertFromErrno(errno);
1171 }
1172
1173 if (pid != 0)
1174 {
1175 /* Write the pid file, this is done in the parent, before exiting. */
1176 if (fdPidfile != -1)
1177 {
1178 char szBuf[256];
1179 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
1180 ssize_t cbIgnored = write(fdPidfile, szBuf, cbPid); NOREF(cbIgnored);
1181 close(fdPidfile);
1182 }
1183 exit(0);
1184 }
1185
1186 if (fdPidfile != -1)
1187 close(fdPidfile);
1188
1189 return VINF_SUCCESS;
1190}
1191
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