VirtualBox

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

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

Runtime: Make it possible to override PAM usage with IPRT_WITHOUT_PAM (addendum for r148553), bugref:10153

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