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