VirtualBox

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

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

IPRT/RTProcCreateEx/posix: Use 'ASCII' instead of 'C' as the default child codeset, iconv_open doesn't grok 'C' and fails. The dynamic loading of libpam must take shared object versioning into account and not expect the dev library libpam.so to be installed, so use RTLdr for loading it and keep it loaded. Ubuntu needs the session to be opened before we get any locale enviornment bits, so do pam_open_session too. [build fix, logging] bugref:10153

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