1/* spawn.c 2 Spawn a program securely. 3 4 Copyright (C) 1992, 1993, 1994, 1995 Ian Lance Taylor 5 6 This file is part of the Taylor UUCP package. 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License as 10 published by the Free Software Foundation; either version 2 of the 11 License, or (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, but 14 WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 21 22 The author of the program may be contacted at ian@airs.com. 23 */ 24 25#include "uucp.h" 26 27#include "uudefs.h" 28#include "sysdep.h" 29 30#include <errno.h> 31 32#if HAVE_FCNTL_H 33#include <fcntl.h> 34#else 35#if HAVE_SYS_FILE_H 36#include <sys/file.h> 37#endif 38#endif 39 40#ifndef O_RDONLY 41#define O_RDONLY 0 42#define O_WRONLY 1 43#define O_RDWR 2 44#endif 45 46#ifndef FD_CLOEXEC 47#define FD_CLOEXEC 1 48#endif 49 50#ifndef environ 51extern char **environ; 52#endif 53 54/* Spawn a child in a fairly secure fashion. This returns the process 55 ID of the child or -1 on error. It takes far too many arguments: 56 57 pazargs -- arguments (element 0 is command) 58 aidescs -- file descriptors for stdin, stdout and stderr 59 fkeepuid -- TRUE if euid should be left unchanged 60 fkeepenv -- TRUE if environment should be left unmodified 61 zchdir -- directory to chdir to 62 fnosigs -- TRUE if child should ignore SIGHUP, SIGINT and SIGQUIT 63 fshell -- TRUE if should try /bin/sh if execve gets ENOEXEC 64 zpath -- value for environment variable PATH 65 zuu_machine -- value for environment variable UU_MACHINE 66 zuu_user -- value for environment variable UU_USER 67 68 The aidescs array is three elements long. 0 is stdin, 1 is stdout 69 and 2 is stderr. The array may contain either file descriptor 70 numbers to dup appropriately, or one of the following: 71 72 SPAWN_NULL -- set descriptor to /dev/null 73 SPAWN_READ_PIPE -- set aidescs element to pipe for parent to read 74 SPAWN_WRITE_PIPE -- set aidescs element to pipe for parent to write 75 76 If fkeepenv is FALSE, a standard environment is created. The 77 environment arguments (zpath, zuu_machine and zuu_user) are only 78 used if fkeepenv is FALSE; any of them may be NULL. 79 80 This routine expects that all file descriptors have been set to 81 close-on-exec, so it doesn't have to worry about closing them 82 explicitly. It sets the close-on-exec flag for the new pipe 83 descriptors it returns. */ 84 85pid_t 86ixsspawn (pazargs, aidescs, fkeepuid, fkeepenv, zchdir, fnosigs, fshell, 87 zpath, zuu_machine, zuu_user) 88 const char **pazargs; 89 int aidescs[3]; 90 boolean fkeepuid; 91 boolean fkeepenv; 92 const char *zchdir; 93 boolean fnosigs; 94 boolean fshell; 95 const char *zpath; 96 const char *zuu_machine; 97 const char *zuu_user; 98{ 99 char *zshcmd; 100 int i; 101 char *azenv[9]; 102 char **pazenv; 103 boolean ferr; 104#if HAVE_FULLDUPLEX_PIPES 105 boolean ffullduplex; 106#endif 107 int ierr = 0; 108 int onull; 109 int aichild_descs[3]; 110 int cpar_close; 111 int aipar_close[4]; 112 int cchild_close; 113 int aichild_close[3]; 114 pid_t iret = 0; 115 const char *zcmd; 116 117 /* If we might have to use the shell, allocate enough space for the 118 quoted command before forking. Otherwise the allocation would 119 modify the data segment and we could not safely use vfork. */ 120 zshcmd = NULL; 121 if (fshell) 122 { 123 size_t clen; 124 125 clen = 0; 126 for (i = 0; pazargs[i] != NULL; i++) 127 clen += strlen (pazargs[i]); 128 zshcmd = zbufalc (2 * clen + i); 129 } 130 131 /* Set up a standard environment. This is again done before forking 132 because it will modify the data segment. */ 133 if (fkeepenv) 134 pazenv = environ; 135 else 136 { 137 const char *zterm, *ztz; 138 char *zspace; 139 int ienv; 140 141 if (zpath == NULL) 142 zpath = CMDPATH; 143 144 azenv[0] = zbufalc (sizeof "PATH=" + strlen (zpath)); 145 sprintf (azenv[0], "PATH=%s", zpath); 146 zspace = azenv[0] + sizeof "PATH=" - 1; 147 while ((zspace = strchr (zspace, ' ')) != NULL) 148 *zspace = ':'; 149 150 azenv[1] = zbufalc (sizeof "HOME=" + strlen (zSspooldir)); 151 sprintf (azenv[1], "HOME=%s", zSspooldir); 152 153 zterm = getenv ("TERM"); 154 if (zterm == NULL) 155 zterm = "unknown"; 156 azenv[2] = zbufalc (sizeof "TERM=" + strlen (zterm)); 157 sprintf (azenv[2], "TERM=%s", zterm); 158 159 azenv[3] = zbufcpy ("SHELL=/bin/sh"); 160 161 azenv[4] = zbufalc (sizeof "USER=" + strlen (OWNER)); 162 sprintf (azenv[4], "USER=%s", OWNER); 163 164 ienv = 5; 165 166 ztz = getenv ("TZ"); 167 if (ztz != NULL) 168 { 169 azenv[ienv] = zbufalc (sizeof "TZ=" + strlen (ztz)); 170 sprintf (azenv[ienv], "TZ=%s", ztz); 171 ++ienv; 172 } 173 174 if (zuu_machine != NULL) 175 { 176 azenv[ienv] = zbufalc (sizeof "UU_MACHINE=" 177 + strlen (zuu_machine)); 178 sprintf (azenv[ienv], "UU_MACHINE=%s", zuu_machine); 179 ++ienv; 180 } 181 182 if (zuu_user != NULL) 183 { 184 azenv[ienv] = zbufalc (sizeof "UU_USER=" 185 + strlen (zuu_user)); 186 sprintf (azenv[ienv], "UU_USER=%s", zuu_user); 187 ++ienv; 188 } 189 190 azenv[ienv] = NULL; 191 pazenv = azenv; 192 } 193 194 /* Set up any needed pipes. */ 195 196 ferr = FALSE; 197 onull = -1; 198 cpar_close = 0; 199 cchild_close = 0; 200 201#if HAVE_FULLDUPLEX_PIPES 202 ffullduplex = (aidescs[0] == SPAWN_WRITE_PIPE 203 && aidescs[1] == SPAWN_READ_PIPE); 204#endif 205 206 for (i = 0; i < 3; i++) 207 { 208 if (aidescs[i] == SPAWN_NULL) 209 { 210 if (onull < 0) 211 { 212 onull = open ((char *) "/dev/null", O_RDWR); 213 if (onull < 0 214 || fcntl (onull, F_SETFD, 215 fcntl (onull, F_GETFD, 0) | FD_CLOEXEC) < 0) 216 { 217 ierr = errno; 218 (void) close (onull); 219 ferr = TRUE; 220 break; 221 } 222 aipar_close[cpar_close] = onull; 223 ++cpar_close; 224 } 225 aichild_descs[i] = onull; 226 } 227 else if (aidescs[i] != SPAWN_READ_PIPE 228 && aidescs[i] != SPAWN_WRITE_PIPE) 229 aichild_descs[i] = aidescs[i]; 230 else 231 { 232 int aipipe[2]; 233 234#if HAVE_FULLDUPLEX_PIPES 235 if (ffullduplex && i == 1) 236 { 237 /* Just use the fullduplex pipe. */ 238 aidescs[i] = aidescs[0]; 239 aichild_descs[i] = aichild_descs[0]; 240 continue; 241 } 242#endif 243 244 if (pipe (aipipe) < 0) 245 { 246 ierr = errno; 247 ferr = TRUE; 248 break; 249 } 250 251 if (aidescs[i] == SPAWN_READ_PIPE) 252 { 253 aidescs[i] = aipipe[0]; 254 aichild_close[cchild_close] = aipipe[0]; 255 aichild_descs[i] = aipipe[1]; 256 aipar_close[cpar_close] = aipipe[1]; 257 } 258 else 259 { 260 aidescs[i] = aipipe[1]; 261 aichild_close[cchild_close] = aipipe[1]; 262 aichild_descs[i] = aipipe[0]; 263 aipar_close[cpar_close] = aipipe[0]; 264 } 265 266 ++cpar_close; 267 ++cchild_close; 268 269 if (fcntl (aipipe[0], F_SETFD, 270 fcntl (aipipe[0], F_GETFD, 0) | FD_CLOEXEC) < 0 271 || fcntl (aipipe[1], F_SETFD, 272 fcntl (aipipe[1], F_GETFD, 0) | FD_CLOEXEC) < 0) 273 { 274 ierr = errno; 275 ferr = TRUE; 276 break; 277 } 278 } 279 } 280 281#if DEBUG > 1 282 if (! ferr && FDEBUGGING (DEBUG_EXECUTE)) 283 { 284 ulog (LOG_DEBUG_START, "Forking %s", pazargs[0]); 285 for (i = 1; pazargs[i] != NULL; i++) 286 ulog (LOG_DEBUG_CONTINUE, " %s", pazargs[i]); 287 ulog (LOG_DEBUG_END, "%s", ""); 288 } 289#endif 290 291 if (! ferr) 292 { 293 /* This should really be vfork if available. */ 294 iret = ixsfork (); 295 if (iret < 0) 296 { 297 ferr = TRUE; 298 ierr = errno; 299 } 300 } 301 302 if (ferr) 303 { 304 for (i = 0; i < cchild_close; i++) 305 (void) close (aichild_close[i]); 306 iret = -1; 307 } 308 309 if (iret != 0) 310 { 311 /* The parent. Close the child's ends of the pipes and return 312 the process ID, or an error. */ 313 for (i = 0; i < cpar_close; i++) 314 (void) close (aipar_close[i]); 315 ubuffree (zshcmd); 316 if (! fkeepenv) 317 { 318 char **pz; 319 320 for (pz = azenv; *pz != NULL; pz++) 321 ubuffree (*pz); 322 } 323 errno = ierr; 324 return iret; 325 } 326 327 /* The child. */ 328 329#ifdef STDIN_FILENO 330#if STDIN_FILENO != 0 || STDOUT_FILENO != 1 || STDERR_FILENO != 2 331 #error The following code makes invalid assumptions 332#endif 333#endif 334 335 for (i = 0; i < 3; i++) 336 { 337 if (aichild_descs[i] != i) 338 (void) dup2 (aichild_descs[i], i); 339 /* This should only be necessary if aichild_descs[i] == i, but 340 some systems copy the close-on-exec flag for a dupped 341 descriptor, which is wrong according to POSIX. */ 342 (void) fcntl (i, F_SETFD, fcntl (i, F_GETFD, 0) &~ FD_CLOEXEC); 343 } 344 345 zcmd = pazargs[0]; 346 pazargs[0] = strrchr (zcmd, '/'); 347 if (pazargs[0] == NULL) 348 pazargs[0] = zcmd; 349 else 350 ++pazargs[0]; 351 352 if (! fkeepuid) 353 { 354 /* Return to the uid of the invoking user. */ 355 (void) setuid (getuid ()); 356 (void) setgid (getgid ()); 357 } 358 else 359 { 360 /* Try to force the UUCP uid to be both real and effective user 361 ID, in order to present a consistent environment regardless 362 of the invoking user. This won't work on older System V 363 based systems, where it can cause trouble if ordinary users 364 wind up executing uuxqt, perhaps via uucico; any program 365 which uuxqt executes will have an arbitrary real user ID, so 366 if the program is itself a setuid program, any security 367 checks it does based on the real user ID will be incorrect. 368 Fixing this problem would seem to require a special setuid 369 root program; I have not used this approach because 370 modern systems should not suffer from it. */ 371#if HAVE_SETREUID 372 (void) setreuid (geteuid (), -1); 373 (void) setregid (getegid (), -1); 374#else 375 (void) setuid (geteuid ()); 376 (void) setgid (getegid ()); 377#endif 378 } 379 380 if (zchdir != NULL) { 381 (void) chdir (zchdir); 382 } 383 384 if (fnosigs) 385 { 386#ifdef SIGHUP 387 (void) signal (SIGHUP, SIG_IGN); 388#endif 389#ifdef SIGINT 390 (void) signal (SIGINT, SIG_IGN); 391#endif 392#ifdef SIGQUIT 393 (void) signal (SIGQUIT, SIG_IGN); 394#endif 395 } 396 397#ifdef isc386 398#ifdef _POSIX_SOURCE 399 /* ISC has a remarkably stupid notion of environments. If a program 400 is compiled in the POSIX environment, it sets a process state. 401 If you then exec a program which expects the USG environment, the 402 process state is not reset, so the execed program fails. The 403 __setostype call is required to change back to the USG 404 environment. This ought to be a switch in policy.h, but it seems 405 too trivial, so I will leave this code here and wait for it to 406 break in some fashion in the next version of ISC. */ 407 __setostype (0); 408#endif 409#endif 410 411 (void) execve ((char *) zcmd, (char **) pazargs, pazenv); 412 413 /* The exec failed. If permitted, try using /bin/sh to execute a 414 shell script. */ 415 if (errno == ENOEXEC && fshell) 416 { 417 char *zto; 418 const char *azshargs[4]; 419 420 pazargs[0] = zcmd; 421 zto = zshcmd; 422 for (i = 0; pazargs[i] != NULL; i++) 423 { 424 const char *zfrom; 425 426 for (zfrom = pazargs[i]; *zfrom != '\0'; zfrom++) 427 { 428 /* Some versions of /bin/sh appear to have a bug such 429 that quoting a '/' sometimes causes an error. I 430 don't know exactly when this happens (I can recreate 431 it on Ultrix 4.0), but in any case it is harmless to 432 not quote a '/'. */ 433 if (*zfrom != '/') 434 *zto++ = '\\'; 435 *zto++ = *zfrom; 436 } 437 *zto++ = ' '; 438 } 439 *(zto - 1) = '\0'; 440 441 azshargs[0] = "sh"; 442 azshargs[1] = "-c"; 443 azshargs[2] = zshcmd; 444 azshargs[3] = NULL; 445 446 (void) execve ((char *) "/bin/sh", (char **) azshargs, pazenv); 447 } 448 449 _exit (EXIT_FAILURE); 450 451 /* Avoid compiler warning. */ 452 return -1; 453} 454