/* sys1.unx The basic system dependent routines for UNIX. Copyright (C) 1991, 1992 Ian Lance Taylor This file is part of the Taylor UUCP package. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The author of the program may be contacted at ian@airs.com or c/o AIRS, P.O. Box 520, Waltham, MA 02254. $Log: sys1.unx,v $ Revision 1.1.1.1 1993/03/21 09:45:37 cgd initial import of 386bsd-0.1 sources # Revision 1.2 92/05/13 05:42:07 rich # ported to 386bsd # # Revision 1.1 1992/05/10 17:36:33 rich # Initial revision # Revision 1.68 1992/04/03 05:37:11 ian Minor cleanups for gcc 2.1 Revision 1.67 1992/03/31 23:53:34 ian Use $PWD to get the current directory if it's defined and correct Revision 1.66 1992/03/31 23:42:59 ian Brian W. Antoine: use name from getpwnam rather than getlogin Revision 1.65 1992/03/26 17:17:25 ian Gerben Wierda: various cleanups Revision 1.64 1992/03/18 06:00:25 ian Open the controlling terminal in non delay mode Revision 1.63 1992/03/17 15:35:28 ian Log signals when they happen, even if we continue looping Revision 1.62 1992/03/17 01:28:18 ian Undefine remove in uucp.h if ! HAVE_REMOVE Revision 1.61 1992/03/16 22:40:01 ian Undefine remove before function definition Revision 1.60 1992/03/16 22:22:35 ian Adjusted external declarations Revision 1.59 1992/03/16 22:01:58 ian Don't declare sigemptyset Revision 1.58 1992/03/16 01:23:08 ian Make blocking writes optional Revision 1.57 1992/03/15 04:51:17 ian Keep an array of signals we've received rather than a single variable Revision 1.56 1992/03/15 01:54:46 ian All execs are now done in isspawn, all waits are done in iswait Revision 1.55 1992/03/12 19:54:43 ian Debugging based on types rather than number Revision 1.54 1992/03/11 02:09:57 ian Franc,ois Pinard: retry fork several times before giving up Revision 1.53 1992/03/11 00:18:50 ian Save temporary file if file send fails Revision 1.52 1992/03/08 02:06:28 ian Let setpgrp fail silently Revision 1.51 1992/03/04 01:40:51 ian Thomas Fischer: tweaked a bit for the NeXT Revision 1.50 1992/03/03 21:01:20 ian Use strict timeout in fsserial_read, eliminate all race conditions Revision 1.49 1992/02/29 01:06:59 ian Chip Salzenberg: recheck file permissions before sending Revision 1.48 1992/02/28 05:06:15 ian T. William Wells: fsysdep_catch must be a macro Revision 1.47 1992/02/27 19:51:09 ian Added some new extern definitions Revision 1.46 1992/02/27 05:40:54 ian T. William Wells: detach from controlling terminal, handle signals safely Revision 1.45 1992/02/24 20:07:43 ian John Theus: some systems don't have Revision 1.44 1992/02/24 04:58:47 ian Only permit files to be received into directories that are world-writeable Revision 1.43 1992/02/23 03:26:51 ian Overhaul to use automatic configure shell script Revision 1.42 1992/02/19 19:36:07 ian Rearranged time functions Revision 1.41 1992/02/09 05:10:50 ian Added HAVE_MKDIR configuration parameter and mkdir emulation Revision 1.40 1992/02/09 03:14:48 ian Added HAVE_OLD_DIRECTORIES for systems without readdir routines Revision 1.39 1992/02/09 02:41:58 ian Added HAVE_DUP2 configuration parameter and dup2 emulation function Revision 1.38 1992/02/08 23:38:17 ian Put utsname on stack rather than making it static Revision 1.37 1992/02/08 23:34:41 ian If we have neither getcwd nor getwd, fork /bin/pwd to get the cwd Revision 1.36 1992/02/08 22:33:32 ian Only get the current working directory if it's going to be needed Revision 1.35 1992/02/08 03:54:18 ian Include only in , added 1992 copyright Revision 1.34 1992/01/29 04:27:11 ian Jay Vassos-Libove: removed some conflicting declarations Revision 1.33 1992/01/22 05:08:21 ian Call execl with correct first argument Revision 1.32 1992/01/21 19:39:12 ian Chip Salzenberg: uucp and uux start uucico for right system, not any Revision 1.31 1992/01/21 00:30:48 ian Don't try to create a directory with no name Revision 1.30 1992/01/16 03:38:20 ian Put \n at end of fsysdep_run error message Revision 1.29 1992/01/15 21:06:11 ian Mike Park: some systems can't include and together Revision 1.28 1992/01/13 19:38:16 ian Chip Salzenberg: can't declare execl, since it is varadic Revision 1.27 1992/01/13 06:11:39 ian David Nugent: can't declare open or fcntl Revision 1.26 1992/01/11 17:30:10 ian John Antypas: use memcpy instead of relying on structure assignment Revision 1.25 1992/01/04 23:23:57 ian usysdep_localtime can't use usysdep_full_time if HAVE_TIMES Revision 1.24 1992/01/04 22:56:22 ian Added extern definition Revision 1.23 1991/12/29 15:45:46 ian Don't take the address of a cast value Revision 1.22 1991/12/29 04:04:18 ian Added a bunch of extern definitions Revision 1.21 1991/12/29 02:59:50 ian Lele Gaifax: put full year in log file Revision 1.20 1991/12/28 17:08:47 ian John Theus: offer HAVE_GETWD as an alternative to using getcwd Revision 1.19 1991/12/28 07:01:15 ian Added HAVE_FTIME configuration option Revision 1.18 1991/12/22 22:14:19 ian Monty Solomon: added HAVE_UNISTD_H configuration parameter Revision 1.17 1991/12/21 21:34:14 ian Moved fsysdep_file_exists from sys5.unx to sys1.unx Revision 1.16 1991/12/21 21:04:42 ian Use real program name in fsysdep_run error messages Revision 1.15 1991/12/17 07:09:58 ian Record statistics in fractions of a second Revision 1.14 1991/12/12 18:35:47 ian Do locking with link to avoid races and to permit running as root Revision 1.13 1991/12/12 17:39:40 ian Set the GID as well as the UID for extra safety Revision 1.12 1991/12/11 03:59:19 ian Create directories when necessary; don't just assume they exist Revision 1.11 1991/12/06 22:50:01 ian Franc,ois Pinard: getcwd may legitimately fail in usysdep_initialize Revision 1.10 1991/12/01 02:23:12 ian Niels Baggesen: don't multiply include Revision 1.9 1991/11/21 20:59:32 ian Brian Campbell: ttyname takes an argument Revision 1.8 1991/11/14 19:11:25 ian Add extern for ttyname Revision 1.7 1991/11/14 03:40:10 ian Try to figure out whether stdin is a TCP port Revision 1.6 1991/11/11 18:55:52 ian Get protocol parameters from port and dialer for incoming calls Revision 1.5 1991/09/19 17:49:39 ian Chip Salzenberg: the log file has been closed before calling fsysdep_run Revision 1.4 1991/09/19 15:46:48 ian Chip Salzenberg: Make sure getlogin () uid matches process uid Revision 1.3 1991/09/19 03:23:34 ian Chip Salzenberg: append to private debugging file, don't overwrite it Revision 1.2 1991/09/11 02:33:14 ian Added ffork argument to fsysdep_run Revision 1.1 1991/09/10 19:45:50 ian Initial revision */ #include "uucp.h" #if USE_RCS_ID char sys1_unx_rcsid[] = "$Id: sys1.unx,v 1.1.1.1 1993/03/21 09:45:37 cgd Exp $"; #endif #include #if USE_STDIO && HAVE_UNISTD_H #include #endif #include "system.h" #include "sysdep.h" #include #if HAVE_GETGRENT #include extern struct group *getgrent (); #endif #if HAVE_LIMITS_H #include #endif #if HAVE_SYS_PARAM_H #include #endif #if HAVE_FCNTL_H #include #else #if HAVE_SYS_FILE_H #include #endif #endif #ifndef O_RDONLY #define O_RDONLY 0 #define O_WRONLY 1 #define O_RDWR 2 #endif #ifndef O_APPEND #ifdef FAPPEND #define O_APPEND FAPPEND #endif #endif #ifndef O_NOCTTY #define O_NOCTTY 0 #endif #if ! HAVE_GETHOSTNAME && HAVE_UNAME #include extern int uname (); #endif #if HAVE_TIME_H && (HAVE_SYS_TIME_AND_TIME_H || ! HAVE_GETTIMEOFDAY) #include #endif #if HAVE_SYS_IOCTL_H #include #endif /* If we have getcwd, forget about getwd. */ #if HAVE_GETCWD #undef HAVE_GETWD #define HAVE_GETWD 0 #endif #if HAVE_GETWD /* If we didn't get MAXPATHLEN, make it up. */ #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif extern char *getwd (); #endif /* HAVE_GETWD */ /* Prefer gettimeofday to ftime to times. */ #if HAVE_GETTIMEOFDAY || HAVE_FTIME #undef HAVE_TIMES #define HAVE_TIMES 0 #endif #if HAVE_GETTIMEOFDAY #undef HAVE_FTIME #define HAVE_FTIME 0 #endif #if HAVE_GETTIMEOFDAY #include extern int gettimeofday (); #endif #if HAVE_FTIME #include extern int ftime (); #endif #if HAVE_TIMES #include #if TIMES_DECLARATION_OK /* We use a macro to protect this because times really returns clock_t and on some systems, such as Ultrix 4.0, clock_t is int. We don't leave it out entirely because on some systems, such as System III, the declaration is necessary for correct compilation. */ extern long times (); #endif #if TIMES_TICK == 0 /* We don't have a value for TIMES_TICK. Look for one. */ #ifdef CLK_TCK #undef TIMES_TICK #define TIMES_TICK CLK_TCK #else /* ! defined (CLK_TCK) */ #ifdef HZ #undef TIMES_TICK #define TIMES_TICK HZ #endif /* defined (HZ) */ #endif /* ! defined (CLK_TCK) */ #endif /* TIMES_TICK == 0 */ #endif /* HAVE_TIMES */ /* We need the access macros. */ #ifndef R_OK #define R_OK 4 #define W_OK 2 #define X_OK 1 #define F_OK 0 #endif /* ! defined (R_OK) */ /* We need wait status information. */ #if HAVE_SYS_WAIT_H #include #endif /* We use a typedef wait_status for wait and related functions to put results into. We define the POSIX examination functions we need if they are not already defined (if they aren't defined, I assume that we have a standard wait status). */ #if HAVE_UNION_WAIT typedef union wait wait_status; #ifndef WIFEXITED #define WIFEXITED(u) ((u).w_termsig == 0) #endif #ifndef WEXITSTATUS #define WEXITSTATUS(u) ((u).w_retcode) #endif #ifndef WTERMSIG #define WTERMSIG(u) ((u).w_termsig) #endif #else /* ! HAVE_UNION_WAIT */ typedef int wait_status; #ifndef WIFEXITED #define WIFEXITED(i) (((i) & 0xff) == 0) #endif #ifndef WEXITSTATUS #define WEXITSTATUS(i) (((i) >> 8) & 0xff) #endif #ifndef WTERMSIG #define WTERMSIG(i) ((i) & 0x7f) #endif #endif /* ! HAVE_UNION_WAIT */ /* External variables. */ extern char **environ; /* External functions. */ #ifndef __386BSD__ extern int kill (); #endif __386BSD__ extern int chdir (), access (), stat (), unlink (), execve (); extern int close (), pipe (), dup2 (); extern int fputs (); extern void _exit (); extern time_t time (); extern char *getlogin (), *ttyname (); extern pid_t getpid (), getppid (), fork (), getpgrp (); extern uid_t getuid (), geteuid (), getgid (), getegid (); extern struct tm *localtime (); #ifndef __386BSD__ extern struct passwd *getpwuid (); #endif __386BSD__ extern struct passwd *getpwnam (); #if HAVE_GETHOSTNAME extern int gethostname (); #endif #if HAVE_GETCWD extern char *getcwd (); #else #if ! HAVE_GETWD static char *getcwd P((char *zbuf, int cbuf)); #endif /* ! HAVE_GETWD */ #endif /* ! HAVE_GETCWD */ #if HAVE_GETDTABLESIZE extern int getdtablesize (); #endif #if HAVE_SYSCONF extern long sysconf (); #endif #if HAVE_SETPGRP #ifndef __386BSD__ extern int setpgrp (); #endif __386BSD__ #endif #if HAVE_SETSID #ifndef __386BSD__ extern int setsid (); #endif __386BSD__ #endif #if HAVE_SIGACTION extern int sigaction (); #endif #if HAVE_SIGVEC extern int sigvec (); #endif #if HAVE_TCP extern int getsockname (); #endif /* Initialize the system dependent routines. We will probably be running suid to uucp, so we make sure that nothing is obviously wrong. We save the login name since we will be losing the real uid. */ static char *zSlogin; /* We save the current directory since we will do a chdir to the spool directory. */ char *zScwd; /* The maximum length of a system name is controlled by the type of spool directory we use. */ #if SPOOLDIR_V2 | SPOOLDIR_BSD42 | SPOOLDIR_BSD43 | SPOOLDIR_ULTRIX int cSysdep_max_name_len = 7; #endif /* SPOOLDIR_V2 | SPOOLDIR_BSD42 | SPOOLDIR_BSD43 | SPOOLDIR_ULTRIX */ #if SPOOLDIR_BNU int cSysdep_max_name_len = 14; #endif /* SPOOLDIR_BNU */ #if SPOOLDIR_TAYLOR #if HAVE_LONG_NAMES int cSysdep_max_name_len = 255; #else /* ! HAVE_LONG_NAMES */ int cSysdep_max_name_len = 14; #endif /* ! HAVE_LONG_NAMES */ #endif /* SPOOLDIR_TAYLOR */ /* The number of available file descriptors. */ static int cSdescriptors; /* Local functions. */ static void xmkdir P((const char *zdir)); static void usmake_spool_dir P((void)); void usysdep_initialize (fdaemon, fgetcwd) boolean fdaemon; boolean fgetcwd; { int o; char *z; struct passwd *q; ulog_id (getpid ()); #if HAVE_GETDTABLESIZE cSdescriptors = getdtablesize (); #else #if HAVE_SYSCONF cSdescriptors = sysconf (_SC_OPEN_MAX); #else #ifdef OPEN_MAX cSdescriptors = OPEN_MAX; #else #ifdef NOFILE cSdescriptors = NOFILE; #else cSdescriptors = 20; #endif /* ! defined (NOFILE) */ #endif /* ! defined (OPEN_MAX) */ #endif /* ! HAVE_SYSCONF */ #endif /* ! HAVE_GETDTABLESIZE */ /* Close everything but stdin, stdout and stderr. */ for (o = 3; o < cSdescriptors; o++) (void) close (o); /* Make sure stdin, stdout and stderr are open. Otherwise, newly opened files will appear to be them and confusion will result. */ if (fcntl (0, F_GETFD, 0) == -1 && open ("/dev/null", O_RDONLY, 0) != 0) exit (EXIT_FAILURE); if (fcntl (1, F_GETFD, 0) == -1 && open ("/dev/null", O_WRONLY, 0) != 1) exit (EXIT_FAILURE); if (fcntl (2, F_GETFD, 0) == -1 && open ("/dev/null", O_WRONLY, 0) != 2) exit (EXIT_FAILURE); /* We always set our file modes to exactly what we want. */ umask (0); /* Get the login name, making sure that it matches the uid. Many systems truncate the getlogin return value to 8 characters, but keep the full name in the password file, so we prefer the name in the password file. */ z = getlogin (); if (z == NULL) q = NULL; else { q = getpwnam (z); if (q != NULL) z = q->pw_name; } if (q == NULL || q->pw_uid != getuid ()) { q = getpwuid (getuid ()); if (q == NULL) ulog (LOG_FATAL, "Can't get login name"); z = q->pw_name; } zSlogin = xstrdup (z); if (fdaemon) { /* Set our uid to our effective uid. There is no point in remembering who originally ran the program. This won't work on System V, but there's nothing to be done about that and it doesn't make all that much difference. */ (void) setuid (geteuid ()); (void) setgid (getegid ()); } if (fgetcwd) { const char *zenv; struct stat senv, sdot; /* Get the current working directory. We have to get it now, since we're about to do a chdir. We use PWD if it's defined and if it really names the working directory, since if it's not the same as whatever getcwd returns it's probably more appropriate. */ zenv = getenv ("PWD"); if (zenv != NULL && stat (zenv, &senv) == 0 && stat (".", &sdot) == 0 && senv.st_ino == sdot.st_ino && senv.st_dev == sdot.st_dev) zScwd = xstrdup (zenv); else { #if HAVE_GETCWD || ! HAVE_GETWD { int c; c = 128; while (TRUE) { zScwd = (char *) xmalloc (c); if (getcwd (zScwd, c) != NULL) break; if (errno != ERANGE) ulog (LOG_FATAL, "getcwd: %s", strerror (errno)); xfree ((pointer) zScwd); c <<= 1; } } #endif /* HAVE_GETCWD */ #if HAVE_GETWD zScwd = (char *) xmalloc (MAXPATHLEN); /* The getwd function puts in an error message in the buffer, rather than setting errno. */ if (getwd (zScwd) == NULL) ulog (LOG_FATAL, "getwd: %s", zScwd); #endif /* HAVE_GETWD */ zScwd = (char *) xrealloc ((pointer) zScwd, strlen (zScwd) + 1); } } /* Connect to the spool directory, and create it if is doesn't exist. */ if (chdir (zSpooldir) < 0) { if (errno != ENOENT) ulog (LOG_FATAL, "chdir (%s): %s", zSpooldir, strerror (errno)); usmake_spool_dir (); } } /* Exit the program. */ void usysdep_exit (fsuccess) boolean fsuccess; { exit (fsuccess ? EXIT_SUCCESS : EXIT_FAILURE); } /* This is called when a non-standard configuration file is used, to make sure the program doesn't hand out privileged file access. This means that to test non-standard configuration files, you should be logged in as uucp. This is called before usysdep_initialize. It ensures that someone can't simply use an alternate configuration file to steal UUCP transfers from other systems. This will still permit people to set up their own configuration file and pretend to be whatever system they choose. The only real security is to use a high level of protection on the modem ports. */ /*ARGSUSED*/ boolean fsysdep_other_config (z) const char *z; { (void) setuid (getuid ()); (void) setgid (getgid ()); return TRUE; } /* Detach from the controlling terminal. This is called by uucico if it is calling out to another system, so that it can receive SIGHUP signals from the port it calls out on. It is also called by uucico just before it starts uuxqt, so that uuxqt is completely independent of the terminal. */ #ifdef TIOCNOTTY #define HAVE_TIOCNOTTY 1 #else #define HAVE_TIOCNOTTY 0 #endif void usysdep_detach () { int o; #if ! HAVE_BSD_PGRP || ! HAVE_TIOCNOTTY pid_t igrp; /* First make sure we are not a process group leader. If we have TIOCNOTTY, this doesn't matter, since TIOCNOTTY sets our process group to 0 anyhow. */ #if HAVE_BSD_PGRP igrp = getpgrp (0); #else igrp = getpgrp (); #endif if (igrp == getpid ()) { boolean fignored; pid_t ipid; /* Ignore SIGHUP, since our process group leader is about to die. */ usset_signal (SIGHUP, SIG_IGN, FALSE, &fignored); ipid = isfork (); if (ipid < 0) ulog (LOG_FATAL, "fork: %s", strerror (errno)); if (ipid != 0) _exit (EXIT_SUCCESS); /* We'll always wind up as a child of process number 1, right? Right? We have to wait for our parent to die before reenabling SIGHUP. */ while (getppid () != 1) sleep (1); /* Restore SIGHUP catcher if it wasn't being ignored. */ if (! fignored) usset_signal (SIGHUP, ussignal, TRUE, (boolean *) NULL); } #endif /* ! HAVE_BSD_PGRP || ! HAVE_TIOCNOTTY */ /* Close all open files. */ ulog_close (); for (o = 0; o < cSdescriptors; o++) (void) close (o); /* Reopen stdin, stdout and stderr. */ if (open ("/dev/null", O_RDONLY) != 0 || open ("/dev/null", O_WRONLY) != 1 || open ("/dev/null", O_WRONLY) != 2) ulog (LOG_FATAL, "open (/dev/null): %s", strerror (errno)); #if HAVE_BSD_PGRP #if HAVE_TIOCNOTTY /* Lose our controlling terminal. */ #ifndef O_NDELAY #define O_NDELAY FNDELAY #endif o = open ("/dev/tty", O_RDWR | O_NDELAY, 0); if (o >= 0) { (void) ioctl (o, TIOCNOTTY, (char *) NULL); (void) close (o); } #endif /* HAVE_TIOCNOTTY */ /* Make sure our process group ID is set to 0. On BSD TIOCNOTTY should already have set it 0, so this will do no harm. On System V we presumably did not execute the TIOCNOTTY call, but the System V setpgrp will detach the controlling terminal anyhow. This lets us use the same code on both BSD and System V, provided it compiles correctly, which life easier for the configure script. We don't output an error if we got EPERM because some BSD variants don't permit this usage of setpgrp (which means they don't provide any way to pick up a new controlling terminal). */ if (setpgrp (0, 0) < 0) { if (errno != EPERM) ulog (LOG_ERROR, "setpgrp: %s", strerror (errno)); } #else /* ! HAVE_BSD_PGRP */ #if HAVE_SETSID /* Under POSIX the setsid call creates a new session for which we are the process group leader. It also detaches us from our controlling terminal. I'm using the BSD setpgrp call first because they should be equivalent for my purposes, but it turns out that on Ultrix 4.0 setsid prevents us from ever acquiring another controlling terminal (it does not change our process group, and Ultrix 4.0 prevents us from setting our process group to 0). */ if (setsid () < 0) ulog (LOG_ERROR, "setsid: %s", strerror (errno)); #else /* ! HAVE_SETSID */ #if HAVE_SETPGRP /* Now we assume we have the System V setpgrp, which takes no arguments, and we couldn't compile the HAVE_BSD_PGRP code above because there was a prototype somewhere in scope. On System V setpgrp makes us the leader of a new process group and also detaches the controlling terminal. */ if (setpgrp () < 0) ulog (LOG_ERROR, "setpgrp: %s", strerror (errno)); #else /* ! HAVE_SETPGRP */ #error Must detach from controlling terminal #endif /* HAVE_SETPGRP */ #endif /* ! HAVE_SETSID */ #endif /* ! HAVE_BSD_PGRP */ /* At this point we have completely detached from our controlling terminal. The next terminal device we open will probably become our controlling terminal. */ } /* Get the node name to use if it was not specified in the configuration file. */ const char * zsysdep_local_name () { #if HAVE_GETHOSTNAME char ab[256]; if (gethostname (ab, sizeof ab) < 0) { ulog (LOG_ERROR, "gethostname: %s", strerror (errno)); return NULL; } ab[sizeof ab - 1] = '\0'; ab[strcspn (ab, ".")] = '\0'; return xstrdup (ab); #else /* ! HAVE_GETHOSTNAME */ #if HAVE_UNAME struct utsname s; if (uname (&s) < 0) { ulog (LOG_ERROR, "uname: %s", strerror (errno)); return NULL; } return xstrdup (s.nodename); #else /* ! HAVE_UNAME */ return NULL; #endif /* ! HAVE_UNAME */ #endif /* ! HAVE_GETHOSTNAME */ } /* Get the login name. We actually get the login name in usysdep_initialize, because after that we will lost the real uid. */ const char * zsysdep_login_name () { return zSlogin; } /* Get the port name of standard input. I assume that Unix systems generally support ttyname. If they don't, this function can just return NULL. It uses getsockname to see whether standard input is a TCP connection. */ const char * zsysdep_port_name (ftcp_port) boolean *ftcp_port; { const char *z; *ftcp_port = FALSE; #if HAVE_TCP { int clen; clen = 0; if (getsockname (0, (struct sockaddr *) NULL, &clen) == 0) *ftcp_port = TRUE; } #endif /* HAVE_TCP */ z = ttyname (0); if (z == NULL) return NULL; if (strncmp (z, "/dev/", 5) == 0) return z + 5; else return z; } /* Signal handling routines. When we catch a signal, we want to set the appropriate elements of afSignal and afLog_signal to TRUE. If we are on a system which restarts system calls, we may also want to longjmp out. On a system which does not restart system calls, these signal handling routines are well-defined by ANSI C. */ #if HAVE_RESTARTABLE_SYSCALLS volatile sig_atomic_t fSjmp; volatile jmp_buf sSjmp_buf; #endif /* HAVE_RESTARTABLE_SYSCALLS */ /* The SVR3 sigset function can be called just like signal, unless system calls are restarted which is extremely unlikely; we prevent this case in sysh.unx. */ #if HAVE_SIGSET && ! HAVE_SIGACTION && ! HAVE_SIGVEC #define signal sigset #endif /* Catch a signal. Reinstall the signal handler if necessary, set the appropriate variables, and do a longjmp if necessary. */ SIGtype ussignal (isig) int isig; { int iindex; #if ! HAVE_SIGACTION && ! HAVE_SIGVEC && ! HAVE_SIGSET (void) signal (isig, ussignal); #endif switch (isig) { default: iindex = INDEXSIG_SIGHUP; break; #ifdef SIGINT case SIGINT: iindex = INDEXSIG_SIGINT; break; #endif #ifdef SIGQUIT case SIGQUIT: iindex = INDEXSIG_SIGQUIT; break; #endif #ifdef SIGTERM case SIGTERM: iindex = INDEXSIG_SIGTERM; break; #endif #ifdef SIGPIPE case SIGPIPE: iindex = INDEXSIG_SIGPIPE; break; #endif } afSignal[iindex] = TRUE; afLog_signal[iindex] = TRUE; #if HAVE_RESTARTABLE_SYSCALLS if (fSjmp) longjmp (sSjmp_buf, 1); #endif /* HAVE_RESTARTABLE_SYSCALLS */ } /* Prepare to catch a signal. This is basically the ANSI C routine signal, but it uses sigaction or sigvec instead if they are available. If fforce is FALSE, we do not set the signal if it is currently being ignored. If pfignored is not NULL and fforce is FALSE, then *pfignored will be set to TRUE if the signal was previously being ignored (if fforce is TRUE the value of *pfignored is meaningless). If we can't change the signal handler we give a fatal error. */ void usset_signal (isig, pfn, fforce, pfignored) int isig; SIGtype (*pfn) P((int)); boolean fforce; boolean *pfignored; { #if HAVE_SIGACTION struct sigaction s; if (! fforce) { sigemptyset (&s.sa_mask); if (sigaction (isig, (struct sigaction *) NULL, &s) != 0) ulog (LOG_FATAL, "sigaction (%d): %s", isig, strerror (errno)); if (s.sa_handler == SIG_IGN) { if (pfignored != NULL) *pfignored = TRUE; return; } if (pfignored != NULL) *pfignored = FALSE; } s.sa_handler = pfn; sigemptyset (&s.sa_mask); s.sa_flags = 0; if (sigaction (isig, &s, (struct sigaction *) NULL) != 0) ulog (LOG_FATAL, "sigaction (%d): %s", isig, strerror (errno)); #else /* ! HAVE_SIGACTION */ #if HAVE_SIGVEC struct sigvec s; if (! fforce) { if (sigvec (isig, (struct sigvec *) NULL, &s) != 0) ulog (LOG_FATAL, "sigvec (%d): %s", isig, strerror (errno)); if (s.sv_handler == SIG_IGN) { if (pfignored != NULL) *pfignored = TRUE; return; } if (pfignored != NULL) *pfignored = FALSE; } s.sv_handler = pfn; s.sv_mask = 0; #ifdef SV_INTERRUPT s.sv_flags = SV_INTERRUPT; #else s.sv_flags = 0; #endif if (sigvec (isig, &s, (struct sigvec *) NULL) != 0) ulog (LOG_FATAL, "sigvec (%d): %s", isig, strerror (errno)); #else /* ! HAVE_SIGVEC */ if (! fforce) { if (signal (isig, SIG_IGN) == SIG_IGN) { if (pfignored != NULL) *pfignored = TRUE; return; } if (pfignored != NULL) *pfignored = FALSE; } (void) signal (isig, pfn); #endif /* ! HAVE_SIGVEC */ #endif /* ! HAVE_SIGACTION */ } /* The routine called by the system independent code, which always uses the same signal handler. */ void usysdep_signal (isig) int isig; { usset_signal (isig, ussignal, FALSE, (boolean *) NULL); } /* Get the time in seconds since the epoch, with optional microseconds. We use usysdep_process_time to get the microseconds if it will work (it won't is it uses times, since that returns a time based only on the process). */ long isysdep_time (pimicros) long *pimicros; { #if HAVE_GETTIMEOFDAY || HAVE_FTIME return isysdep_process_time (pimicros); #else if (pimicros != NULL) *pimicros = 0; return time ((time_t *) NULL); #endif } /* Get the time in seconds and microseconds; this need only work within the process when called from the system independent code. It is also called by isysdep_time, above. */ long isysdep_process_time (pimicros) long *pimicros; { #if HAVE_GETTIMEOFDAY struct timeval stime; struct timezone stz; (void) gettimeofday (&stime, &stz); if (pimicros != NULL) *pimicros = stime.tv_usec; return stime.tv_sec; #endif /* HAVE_GETTIMEOFDAY */ #if HAVE_FTIME struct timeb stime; (void) ftime (&stime); if (pimicros != NULL) *pimicros = stime.millitm * 1000; return stime.time; #endif /* HAVE_FTIME */ #if HAVE_TIMES struct tms s; long i; static int itick; if (itick == 0) { #if TIMES_TICK != 0 itick = TIMES_TICK; #else /* TIMES_TICK == 0 */ const char *z; z = getenv ("HZ"); if (z != NULL) itick = atoi (z); /* If we really couldn't get anything, just use 60. */ if (itick == 0) itick = 60; #endif /* TIMES_TICK == 0 */ } i = (long) times (&s); if (pimicros != NULL) *pimicros = (i % (long) itick) * ((long) 1000000 / (long) itick); return i / (long) itick; #endif /* HAVE_TIMES */ #if ! HAVE_GETTIMEOFDAY && ! HAVE_FTIME && ! HAVE_TIMES if (pimicros != NULL) *pimicros = 0; return time ((time_t *) NULL); #endif /* ! HAVE_GETTIMEOFDAY && ! HAVE_FTIME && ! HAVE_TIMES */ } /* Fill in a struct tm. */ void usysdep_localtime (itime, q) long itime; struct tm *q; { time_t i; i = (time_t) itime; memcpy (q, localtime (&i), sizeof (struct tm)); } /* Sleep for a number of seconds. */ void usysdep_sleep (c) int c; { sleep (c); } /* Check whether a file exists. */ boolean fsysdep_file_exists (zfile) const char *zfile; { struct stat s; return stat (zfile, &s) == 0; } /* Open a stdio file with appropriate permissions. */ FILE * esysdep_fopen (zfile, fpublic, fappend, fmkdirs) const char *zfile; boolean fpublic; boolean fappend; boolean fmkdirs; { int imode; int o; FILE *e; if (fpublic) imode = IPUBLIC_FILE_MODE; else imode = IPRIVATE_FILE_MODE; if (! fappend) o = creat (zfile, imode); else { #ifdef O_CREAT o = open (zfile, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, imode); #else o = open (zfile, O_WRONLY | O_NOCTTY); if (o < 0 && errno == ENOENT) o = creat (zfile, imode); #endif /* ! defined (O_CREAT) */ } if (o < 0) { if (errno == ENOENT && fmkdirs) { if (! fsysdep_make_dirs (zfile, fpublic)) return NULL; if (! fappend) o = creat (zfile, imode); else { #ifdef O_CREAT o = open (zfile, O_WRONLY | O_APPEND | O_CREAT, imode); #else o = creat (zfile, imode); #endif } } if (o < 0) { ulog (LOG_ERROR, "open (%s): %s", zfile, strerror (errno)); return NULL; } } #ifndef O_CREAT #ifdef O_APPEND if (fappend) { if (fcntl (o, F_SETFL, O_APPEND) != 0) { ulog (LOG_ERROR, "fcntl (%s, O_APPEND): %s", zfile, strerror (errno)); (void) close (o); return NULL; } } #endif /* defined (O_APPEND) */ #endif /* ! defined (O_CREAT) */ if (fappend) e = fdopen (o, (char *) "a"); else e = fdopen (o, (char *) "w"); if (e == NULL) { ulog (LOG_ERROR, "fdopen: %s", strerror (errno)); (void) close (o); } return e; } /* See whether a directory exists. */ boolean fsdirectory_exists (z) const char *z; { struct stat s; if (stat (z, &s) < 0) return FALSE; return S_ISDIR (s.st_mode); } /* Create any directories needed for a file name. */ boolean fsysdep_make_dirs (zfile, fpublic) const char *zfile; boolean fpublic; { char *zcopy, *z; int imode; zcopy = (char *) alloca (strlen (zfile) + 1); strcpy (zcopy, zfile); if (fpublic) imode = IPUBLIC_DIRECTORY_MODE; else imode = IDIRECTORY_MODE; for (z = zcopy; *z != '\0'; z++) { if (*z == '/' && z != zcopy) { *z = '\0'; if (! fsdirectory_exists (zcopy)) { if (mkdir (zcopy, imode) != 0) { ulog (LOG_ERROR, "mkdir (%s): %s", zcopy, strerror (errno)); return FALSE; } } *z = '/'; } } return TRUE; } /* Tilde expand a file or directory name. */ /*ARGSUSED*/ const char * zstilde_expand (qsys, zfile) const struct ssysteminfo *qsys; const char *zfile; { static int calc; static char *zalc; const char *zdir; int clen; if (zfile[0] != '~') return zfile; else if (zfile[1] == '\0' || zfile[1] == '/') { const char *zpub; if (qsys->zpubdir == NULL) zpub = zPubdir; else zpub = qsys->zpubdir; if (zfile[1] == '\0') return zpub; else { zdir = zpub; zfile += 2; } } else { int cuserlen; char *zcopy; struct passwd *q; ++zfile; cuserlen = strcspn (zfile, "/"); zcopy = (char *) alloca (cuserlen + 1); strncpy (zcopy, zfile, cuserlen); zcopy[cuserlen] = '\0'; q = getpwnam (zcopy); if (q == NULL) { ulog (LOG_ERROR, "User %s not found", zcopy); return NULL; } if (zfile[cuserlen] == '\0') return q->pw_dir; else { zdir = q->pw_dir; zfile += cuserlen + 1; } } clen = strlen (zdir) + strlen (zfile) + 2; if (clen > calc) { zalc = (char *) xrealloc ((pointer) zalc, clen); calc = clen; } sprintf (zalc, "%s/%s", zdir, zfile); return zalc; } /* Do access(2) on a stat structure, except that the user name is provided. If the user name in zuser is NULL, require the file to be accessible to the world. Return TRUE if access is permitted, FALSE otherwise. This does not log an error message. */ boolean fsuser_access (q, imode, zuser) const struct stat *q; int imode; const char *zuser; { static char *zuser_hold; static uid_t iuid_hold; static gid_t igid_hold; static int cgroups_hold; static gid_t *paigroups_hold; int ir, iw, ix, iand; if (imode == F_OK) return TRUE; if (zuser != NULL) { /* We keep static variables around for the last user we did, to avoid looking up a user multiple times. */ if (zuser_hold == NULL || strcmp (zuser_hold, zuser) != 0) { struct passwd *qpwd; if (zuser_hold != NULL) { xfree ((pointer) zuser_hold); zuser_hold = NULL; cgroups_hold = 0; xfree ((pointer) paigroups_hold); paigroups_hold = NULL; } qpwd = getpwnam ((char *) zuser); if (qpwd == NULL) { /* Check this as a remote request. */ zuser = NULL; } else { #if HAVE_GETGRENT struct group *qg; #endif zuser_hold = xstrdup (zuser); iuid_hold = qpwd->pw_uid; igid_hold = qpwd->pw_gid; #if HAVE_GETGRENT /* Get the list of groups for this user. This is definitely more appropriate for BSD than for System V. It may just be a waste of time, and perhaps it should be configurable. */ setgrent (); while ((qg = getgrent ()) != NULL) { const char **pz; if (qg->gr_gid == igid_hold) continue; for (pz = (const char **) qg->gr_mem; *pz != NULL; pz++) { if ((*pz)[0] == *zuser && strcmp (*pz, zuser) == 0) { paigroups_hold = ((gid_t *) (xrealloc ((pointer) paigroups_hold, ((cgroups_hold + 1) * sizeof (gid_t))))); paigroups_hold[cgroups_hold] = qg->gr_gid; ++cgroups_hold; break; } } } endgrent (); #endif } } } /* Now do the actual access check. */ if (zuser != NULL) { /* The superuser can do anything. */ if (iuid_hold == 0) return TRUE; /* If this is the uid we're running under, there's no point to checking access further, because when we actually try the operation the system will do the checking for us. */ if (iuid_hold == geteuid ()) return TRUE; } ir = S_IROTH; iw = S_IWOTH; ix = S_IXOTH; if (zuser != NULL) { if (iuid_hold == q->st_uid) { ir = S_IRUSR; iw = S_IWUSR; ix = S_IXUSR; } else { boolean fgroup; fgroup = FALSE; if (igid_hold == q->st_gid) fgroup = TRUE; else { int i; for (i = 0; i < cgroups_hold; i++) { if (paigroups_hold[i] == q->st_gid) { fgroup = TRUE; break; } } } if (fgroup) { ir = S_IRGRP; iw = S_IWGRP; ix = S_IXGRP; } } } iand = 0; if ((imode & R_OK) != 0) iand |= ir; if ((imode & W_OK) != 0) iand |= iw; if ((imode & X_OK) != 0) iand |= ix; return (q->st_mode & iand) == iand; } /* See whether a file is in a directory, and optionally check access. */ boolean fsysdep_in_directory (qsys, zfile, zdir, fcheck, freadable, zuser) const struct ssysteminfo *qsys; const char *zfile; const char *zdir; boolean fcheck; boolean freadable; const char *zuser; { int c; char *zcopy, *zslash; struct stat s; if (*zdir == '~') { zdir = zstilde_expand (qsys, zdir); if (zdir == NULL) return FALSE; } c = strlen (zdir); if (zdir[c - 1] == '/') c--; if (strncmp (zfile, zdir, c) != 0 || (zfile[c] != '/' && zfile[c] != '\0')) return FALSE; if (strstr (zfile + c, "/../") != NULL) return FALSE; /* If we're not checking access, get out now. */ if (! fcheck) return TRUE; zcopy = (char *) alloca (strlen (zfile) + 1); strcpy (zcopy, zfile); /* Start checking directories after zdir. Otherwise, we would require that all directories down to /usr/spool/uucppublic be publically searchable; they probably are but it should not be requirement. */ zslash = zcopy + c; do { char b; struct stat shold; b = *zslash; *zslash = '\0'; shold = s; if (stat (zcopy, &s) != 0) { if (errno != ENOENT) { ulog (LOG_ERROR, "stat (%s): %s", zcopy, strerror (errno)); return FALSE; } /* If this is the top directory, any problems will be caught later when we try to open it. */ if (zslash == zcopy + c) return TRUE; /* Go back and check the last directory for read or write access. */ s = shold; break; } /* If this is not a directory, get out of the loop. */ if (! S_ISDIR (s.st_mode)) break; /* Make sure the directory is searchable. */ if (! fsuser_access (&s, X_OK, zuser)) { ulog (LOG_ERROR, "%s: %s", zcopy, strerror (EACCES)); return FALSE; } /* If we've reached the end of the string, get out. */ if (b == '\0') break; *zslash = b; } while ((zslash = strchr (zslash + 1, '/')) != NULL); /* At this point s holds a stat on the last component of the path. We must check it for readability or writeability. */ if (! fsuser_access (&s, freadable ? R_OK : W_OK, zuser)) { ulog (LOG_ERROR, "%s: %s", zcopy, strerror (EACCES)); return FALSE; } return TRUE; } /* Start up a new program and end the current one. We always go through isspawn, and never exec directly. We don't have to worry about SIGHUP because the current process is either not a process group leader (uucp, uux) or it does not have a controlling terminal (uucico). */ boolean fsysdep_run (ffork, zprogram, zarg1, zarg2) boolean ffork; const char *zprogram; const char *zarg1; const char *zarg2; { char *zlib; const char *azargs[4]; int aidescs[3]; pid_t ipid; zlib = (char *) alloca (sizeof LIBDIR + sizeof "/" + strlen (zprogram)); sprintf (zlib, "%s/%s", LIBDIR, zprogram); azargs[0] = zlib; azargs[1] = zarg1; azargs[2] = zarg2; azargs[3] = NULL; aidescs[0] = SPAWN_NULL; aidescs[1] = SPAWN_NULL; aidescs[2] = SPAWN_NULL; /* We pass fshell as TRUE, which permits uucico and uuxqt to be replaced by shell scripts. */ ipid = isspawn (azargs, aidescs, FALSE, FALSE, (const char *) NULL, FALSE, TRUE, (const char *) NULL, (const char *) NULL, (const char *) NULL); if (ipid < 0) { ulog (LOG_ERROR, "isspawn: %s", strerror (errno)); return FALSE; } if (ffork) return TRUE; exit (EXIT_SUCCESS); } /* Mail a message to a user. */ boolean fsysdep_mail (zto, zsubject, cstrs, paz) const char *zto; const char *zsubject; int cstrs; const char **paz; { const char *az[3]; FILE *e; pid_t ipid; time_t itime; int i; az[0] = MAIL_PROGRAM; az[1] = zto; az[2] = NULL; e = espopen (az, FALSE, &ipid); if (e == NULL) { ulog (LOG_ERROR, "espopen (%s): %s", MAIL_PROGRAM, strerror (errno)); return FALSE; } fprintf (e, "Subject: %s\n", zsubject); fprintf (e, "To: %s\n", zto); /* We should probably put in a Date: header as well. */ fprintf (e, "\n"); (void) time (&itime); /* Remember that ctime includes a \n, so this skips a line. */ fprintf (e, "Message from UUCP on %s %s\n", zLocalname, ctime (&itime)); for (i = 0; i < cstrs; i++) fputs (paz[i], e); (void) fclose (e); return iswait ((unsigned long) ipid, MAIL_PROGRAM) == 0; } /* Make a directory with error checking. */ static void xmkdir (zdir) const char *zdir; { if (mkdir ((char *) zdir, IDIRECTORY_MODE) < 0) ulog (LOG_FATAL, "mkdir (%s): %s", zdir, strerror (errno)); } /* Make the spool directory. */ static void usmake_spool_dir () { xmkdir (zSpooldir); if (chdir (zSpooldir) < 0) ulog (LOG_FATAL, "chdir (%s): %s", zSpooldir, strerror (errno)); #if SPOOLDIR_BSD42 | SPOOLDIR_BSD43 xmkdir ("C."); xmkdir ("D."); #if SPOOLDIR_BSD43 xmkdir ("X."); #endif /* SPOOLDIR_BSD43 */ { char ab[sizeof "D.1234567X"]; sprintf (ab, "D.%.7s", zLocalname); xmkdir (ab); #if SPOOLDIR_BSD43 strcat (ab, "X"); xmkdir (ab); #endif /* SPOOLDIR_BSD43 */ } #endif /* SPOOLDIR_BSD42 | SPOOLDIR_BSD43 */ #if SPOOLDIR_ULTRIX xmkdir ("sys"); xmkdir ("sys/DEFAULT"); xmkdir ("sys/DEFAULT/C."); xmkdir ("sys/DEFAULT/D."); xmkdir ("sys/DEFAULT/X."); { char ab[sizeof "sys/DEFAULT/D.1234567X"]; sprintf (ab, "sys/DEFAULT/D.%.7s", zLocalname); xmkdir (ab); strcat (ab, "X"); xmkdir (ab); } #endif /* SPOOLDIR_ULTRIX */ #if SPOOLDIR_BSD43 | SPOOLDIR_ULTRIX | SPOOLDIR_TAYLOR xmkdir (".Temp"); #endif /* SPOOLDIR_BSD43 | SPOOLDIR_ULTRIX | SPOOLDIR_TAYLOR */ xmkdir (".Status"); xmkdir (".Sequence"); xmkdir (XQTDIR); xmkdir (PRESERVEDIR); } /* Retry fork several times before giving up. */ pid_t isfork () { int i; pid_t iret; for (i = 0; i < 10; i++) { iret = fork (); if (iret >= 0 || errno != EAGAIN) return iret; sleep (5); } return iret; } /* Spawn a child in a fairly secure fashion. This returns the process ID of the child or -1 on error. It takes far too many arguments: pazargs -- arguments (element 0 is command) aidescs -- file descriptors for stdin, stdout and stderr fkeepuid -- TRUE if euid should be left unchanged fkeepenv -- TRUE if environment should be left unmodified zchdir -- directory to chdir to fnosigs -- TRUE if child should ignore SIGHUP, SIGINT and SIGQUIT fshell -- TRUE if should try /bin/sh if execve gets ENOEXEC zpath -- value for environment variable PATH zuu_machine -- value for environment variable UU_MACHINE zuu_user -- value for environment variable UU_USER The aidescs array is three elements long. 0 is stdin, 1 is stdout and 2 is stderr. The array may contain either file descriptor numbers to dup appropriately, or one of the following: SPAWN_NULL -- set descriptor to /dev/null SPAWN_READ_PIPE -- set aidescs element to pipe for parent to read SPAWN_WRITE_PIPE -- set aidescs element to pipe for parent to write If fkeepenv is FALSE, a standard environment is created. The environment arguments (zpath, zuu_machine and zuu_user) are only used if fkeepenv is FALSE; any of them may be NULL. */ pid_t isspawn (pazargs, aidescs, fkeepuid, fkeepenv, zchdir, fnosigs, fshell, zpath, zuu_machine, zuu_user) const char **pazargs; int aidescs[3]; boolean fkeepuid; boolean fkeepenv; const char *zchdir; boolean fnosigs; boolean fshell; const char *zpath; const char *zuu_machine; const char *zuu_user; { char *zshcmd = NULL; int i; char *azenv[9]; char **pazenv; boolean ferr; int ierr = 0; int onull; int aichild_descs[3]; int cpar_close; int aipar_close[4]; int cchild_close; int aichild_close[3]; pid_t iret = 0; const char *zcmd; /* If we might have to use the shell, allocate enough space for the quoted command before forking. Otherwise the allocation might modify the data segment and we could not safely use vfork. */ if (fshell) { int clen; clen = 0; for (i = 0; pazargs[i] != NULL; i++) clen += strlen (pazargs[i]); zshcmd = (char *) alloca (2 * clen + i); } /* Set up a standard environment. This is again done before forking because it might modify the data segment. */ if (fkeepenv) pazenv = environ; else { const char *zterm, *ztz; char *zspace; int ienv; if (zpath == NULL) zpath = CMDPATH; azenv[0] = (char *) alloca (sizeof "PATH=" + strlen (zpath)); sprintf (azenv[0], "PATH=%s", zpath); zspace = azenv[0] + sizeof "PATH=" - 1; while ((zspace = strchr (zspace, ' ')) != NULL) *zspace = ':'; azenv[1] = (char *) alloca (sizeof "HOME=" + strlen (zSpooldir)); sprintf (azenv[1], "HOME=%s", zSpooldir); zterm = getenv ("TERM"); if (zterm == NULL) zterm = "unknown"; azenv[2] = (char *) alloca (sizeof "TERM=" + strlen (zterm)); sprintf (azenv[2], "TERM=%s", zterm); azenv[3] = (char *) "SHELL=/bin/sh"; azenv[4] = (char *) alloca (sizeof "USER=" + strlen (OWNER)); sprintf (azenv[4], "USER=%s", OWNER); ienv = 5; ztz = getenv ("TZ"); if (ztz != NULL) { azenv[ienv] = (char *) alloca (sizeof "TZ=" + strlen (ztz)); sprintf (azenv[ienv], "TZ=%s", ztz); ++ienv; } if (zuu_machine != NULL) { azenv[ienv] = (char *) alloca (sizeof "UU_MACHINE=" + strlen (zuu_machine)); sprintf (azenv[ienv], "UU_MACHINE=%s", zuu_machine); ++ienv; } if (zuu_user != NULL) { azenv[ienv] = (char *) alloca (sizeof "UU_USER=" + strlen (zuu_user)); sprintf (azenv[ienv], "UU_USER=%s", zuu_user); ++ienv; } azenv[ienv] = NULL; pazenv = azenv; } /* Set up any needed pipes. */ ferr = FALSE; onull = -1; cpar_close = 0; cchild_close = 0; for (i = 0; i < 3; i++) { if (aidescs[i] == SPAWN_NULL) { if (onull < 0) { onull = open ("/dev/null", O_RDWR); if (onull < 0) { ierr = errno; ferr = TRUE; break; } aipar_close[cpar_close] = onull; ++cpar_close; } aichild_descs[i] = onull; } else if (aidescs[i] != SPAWN_READ_PIPE && aidescs[i] != SPAWN_WRITE_PIPE) aichild_descs[i] = aidescs[i]; else { int aipipe[2]; if (pipe (aipipe) < 0) { ierr = errno; ferr = TRUE; break; } if (aidescs[i] == SPAWN_READ_PIPE) { aidescs[i] = aipipe[0]; aichild_close[cchild_close] = aipipe[0]; aichild_descs[i] = aipipe[1]; aipar_close[cpar_close] = aipipe[1]; } else { aidescs[i] = aipipe[1]; aichild_close[cchild_close] = aipipe[1]; aichild_descs[i] = aipipe[0]; aipar_close[cpar_close] = aipipe[0]; } ++cpar_close; ++cchild_close; } } #if DEBUG > 1 if (! ferr && FDEBUGGING (DEBUG_EXECUTE)) { ulog (LOG_DEBUG_START, "Forking %s", pazargs[0]); for (i = 1; pazargs[i] != NULL; i++) ulog (LOG_DEBUG_CONTINUE, " %s", pazargs[i]); ulog (LOG_DEBUG_END, "%s", ""); } #endif if (! ferr) { /* This should really be vfork if available. */ iret = isfork (); if (iret < 0) { ferr = TRUE; ierr = errno; } } if (ferr) { for (i = 0; i < cpar_close; i++) (void) close (aipar_close[i]); for (i = 0; i < cchild_close; i++) (void) close (aichild_close[i]); errno = ierr; return -1; } /* Here the fork has succeeded and all the pipes have been done. */ if (iret != 0) { /* The parent. Close the child's ends of the pipes and return the process ID. */ for (i = 0; i < cpar_close; i++) (void) close (aipar_close[i]); return iret; } /* The child. */ #ifdef STDIN_FILENO #if STDIN_FILENO != 0 || STDOUT_FILENO != 1 || STDERR_FILENO != 2 #error The following code makes invalid assumptions #endif #endif for (i = 0; i < 3; i++) if (aichild_descs[i] != i) (void) dup2 (aichild_descs[i], i); for (i = 3; i < cSdescriptors; i++) (void) close (i); zcmd = pazargs[0]; pazargs[0] = strrchr (zcmd, '/'); if (pazargs[0] == NULL) pazargs[0] = zcmd; else ++pazargs[0]; if (! fkeepuid) { (void) setuid (getuid ()); (void) setgid (getgid ()); } if (zchdir != NULL) (void) chdir (zchdir); if (fnosigs) { #ifdef SIGHUP (void) signal (SIGHUP, SIG_IGN); #endif #ifdef SIGINT (void) signal (SIGINT, SIG_IGN); #endif #ifdef SIGQUIT (void) signal (SIGQUIT, SIG_IGN); #endif } (void) execve ((char *) zcmd, (char **) pazargs, pazenv); /* The exec failed. If permitted, try using /bin/sh to execute a shell script. */ if (errno == ENOEXEC && fshell) { char *zto; const char *azshargs[4]; pazargs[0] = zcmd; zto = zshcmd; for (i = 0; pazargs[i] != NULL; i++) { const char *zfrom; for (zfrom = pazargs[i]; *zfrom != '\0'; zfrom++) { /* Some versions of /bin/sh appear to have a bug such that quoting a '/' sometimes causes an error. I don't know exactly when this happens (I can recreate it on Ultrix 4.0), but in any case it is harmless to not quote a '/'. */ if (*zfrom != '/') *zto++ = '\\'; *zto++ = *zfrom; } *zto++ = ' '; } *(zto - 1) = '\0'; azshargs[0] = "sh"; azshargs[1] = "-c"; azshargs[2] = zshcmd; azshargs[3] = NULL; (void) execve ((char *) "/bin/sh", (char **) azshargs, pazenv); } _exit (EXIT_FAILURE); /* Avoid compiler warning. */ return -1; } /* A version of popen that goes through isspawn. This actually takes an array of arguments rather than a string, and takes a boolean read/write value rather than a string. It sets *pipid to the process ID of the child. */ FILE * espopen (pazargs, frd, pipid) const char **pazargs; boolean frd; pid_t *pipid; { int aidescs[3]; pid_t ipid; FILE *eret; if (frd) { aidescs[0] = SPAWN_NULL; aidescs[1] = SPAWN_READ_PIPE; } else { aidescs[0] = SPAWN_WRITE_PIPE; aidescs[1] = SPAWN_NULL; } aidescs[2] = SPAWN_NULL; ipid = isspawn (pazargs, aidescs, FALSE, FALSE, (const char *) NULL, FALSE, TRUE, (const char *) NULL, (const char *) NULL, (const char *) NULL); if (ipid < 0) return NULL; if (frd) eret = fdopen (aidescs[1], (char *) "r"); else eret = fdopen (aidescs[0], (char *) "w"); if (eret == NULL) { int ierr; ierr = errno; (void) close (frd ? aidescs[1] : aidescs[0]); (void) kill (ipid, SIGKILL); (void) iswait ((unsigned long) ipid, (const char *) NULL); errno = ierr; return NULL; } *pipid = ipid; return eret; } /* Wait for a particular process to finish. The ipid argument should be pid_t, but then we couldn't have a prototype. If the zreport argument is not NULL, then a wait error will be logged, and if the exit status is non-zero it will be logged with zreport as the header of the log message. If the zreport argument is NULL, no errors will be logged. This function returns the exit status if the process exited normally, or -1 on error or if the process was killed by a signal (I don't just always return the exit status because then the calling code would have to prepared to handle union wait status vs. int status, and none of the callers care which signal killed the program anyhow). This functions keeps waiting until the process finished, even if it is interrupted by a signal. I think this is right for all uses. The controversial one would be when called from uuxqt to wait for a requested process. Hitting uuxqt with SIGKILL will approximate the actions taken if we return from here with an error anyhow. If we do get a signal, we call ulog with a NULL argument to get it in the log file at about the right time. */ int iswait (ipid, zreport) unsigned long ipid; const char *zreport; { wait_status istat; #if HAVE_WAITPID while (waitpid ((pid_t) ipid, &istat, 0) < 0) { if (errno != EINTR) { if (zreport != NULL) ulog (LOG_ERROR, "waitpid: %s", strerror (errno)); return -1; } ulog (LOG_ERROR, (const char *) NULL); } #else /* ! HAVE_WAITPID */ #if HAVE_WAIT4 while (wait4 ((pid_t) ipid, &istat, 0, (struct rusage *) NULL) < 0) { if (errno != EINTR) { if (zreport != NULL) ulog (LOG_ERROR, "wait4: %s", strerror (errno)); return -1; } ulog (LOG_ERROR, (const char *) NULL); } #else /* ! HAVE_WAIT4 */ pid_t igot; /* We could theoretically get the wrong child here if we're in some kind of weird pipeline, so we don't give any error messages for it. */ while ((igot = wait (&istat)) != (pid_t) ipid) { if (igot < 0) { if (errno != EINTR) { if (zreport != NULL) ulog (LOG_ERROR, "wait: %s", strerror (errno)); return -1; } ulog (LOG_ERROR, (const char *) NULL); } } #endif /* ! HAVE_WAIT4 */ #endif /* ! HAVE_WAITPID */ DEBUG_MESSAGE2 (DEBUG_EXECUTE, "%s %d", WIFEXITED (istat) ? "Exit status" : "Signal", WIFEXITED (istat) ? WEXITSTATUS (istat) : WTERMSIG (istat)); if (WIFEXITED (istat) && WEXITSTATUS (istat) == 0) return 0; if (zreport != NULL) { if (! WIFEXITED (istat)) ulog (LOG_ERROR, "%s: Got signal %d", zreport, WTERMSIG (istat)); else ulog (LOG_ERROR, "%s: Exit status %d", zreport, WEXITSTATUS (istat)); } if (WIFEXITED (istat)) return WEXITSTATUS (istat); else return -1; } #if ! HAVE_REMOVE /* Remove a file. */ int remove (z) const char *z; { return unlink (z); } #endif /* ! HAVE_REMOVE */ #if ! HAVE_STRERROR /* Some systems don't have a strerror definition, so we provide one. This function is, of course, system dependent. */ char * strerror (ierr) int ierr; { extern int sys_nerr; extern char *sys_errlist[]; if (ierr >= 0 && ierr < sys_nerr) return sys_errlist[ierr]; return (char *) "unknown error"; } #endif /* ! HAVE_STRERROR */ #if ! HAVE_GETCWD && ! HAVE_GETWD /* Implement a simple getcwd that just calls /bin/pwd. I probably should include Roland McGrath's getcwd implementation here, since it doesn't fork, but it requires readdir support that I don't feel like contemplating just now. */ static char * getcwd (zbuf, cbuf) char *zbuf; int cbuf; { const char *azargs[2]; FILE *e; pid_t ipid; int cread; int ierr; azargs[0] = PWD_PROGRAM; azargs[1] = NULL; e = espopen (azargs, TRUE, &ipid); if (e == NULL) return NULL; ierr = 0; cread = fread (zbuf, sizeof (char), cbuf, e); if (cread == 0) ierr = errno; (void) fclose (e); if (iswait ((unsigned long) ipid, (const char *) NULL) != 0) { ierr = EACCES; cread = 0; } if (cread != 0) { if (zbuf[cread - 1] == '\n') zbuf[cread - 1] = '\0'; else { ierr = ERANGE; cread = 0; } } if (cread == 0) { errno = ierr; return NULL; } return zbuf; } #endif /* ! HAVE_GETCWD && ! HAVE_GETWD */ #if ! HAVE_DUP2 /* Emulate the dup2 call. I basically took this from the emacs 18.57 distribution, although I cleaned it up a bit and made it POSIX compliant. */ int dup2 (oold, onew) int oold; int onew; { if (oold == onew) return onew; (void) close (onew); #ifdef F_DUPFD return fcntl (oold, F_DUPFD, onew); #else { int onext, oret, isave; onext = dup (oold); if (onext == onew) return onext; if (onext < 0) return -1; oret = dup2 (oold, onew); isave = errno; (void) close (onext); errno = isave; return oret; } #endif } #endif /* ! HAVE_DUP2 */ #if ! HAVE_OPENDIR /* Simple emulations of opendir/readdir/closedir for systems which have the original format of Unix directories. It's probably better to get Doug Gwyn's public domain set of emulation functions. */ DIR * opendir (zdir) const char *zdir; { int o; struct stat s; DIR *qret; o = open (zdir, O_RDONLY | O_NOCTTY, 0); if (o == -1) return NULL; if (fstat (o, &s) < 0) { (void) close (o); return NULL; } if (! S_ISDIR (s.st_mode)) { (void) close (o); errno = ENOTDIR; return NULL; } qret = (DIR *) xmalloc (sizeof (DIR)); qret->o = o; return qret; } struct dirent * readdir (q) DIR *q; { struct direct sdir; int cgot; do { cgot = read (q->o, &sdir, sizeof (struct direct)); if (cgot <= 0) return NULL; if (cgot != sizeof (struct direct)) { errno = ENOENT; return NULL; } } while (sdir.d_ino == 0); strncpy (q->s.d_name, sdir.d_name, DIRSIZ); q->s.d_name[DIRSIZ] = '\0'; return &q->s; } int closedir (q) DIR *q; { int iret, isave; iret = close (q->o); isave = errno; xfree (q); errno = isave; return iret; } #endif /* ! HAVE_OPENDIR */ #if ! HAVE_MKDIR /* We don't have the mkdir system call, so we invoke the suid program uudir to create the directory with the correct owner. */ int mkdir (zdir, imode) const char *zdir; int imode; { const char *azargs[2]; int aidescs[3]; pid_t ipid; /* /bin/mkdir will create the directory with mode 777, so we set our umask to get the mode we want. */ (void) umask ((~ imode) & (S_IRWXU | S_IRWXG | S_IRWXO)); azargs[0] = UUDIR_PROGRAM; azargs[1] = NULL; aidescs[0] = SPAWN_NULL; aidescs[1] = SPAWN_NULL; aidescs[2] = SPAWN_NULL; ipid = isspawn (azargs, aidescs, FALSE, FALSE, (const char *) NULL, TRUE, FALSE, (const char *) NULL, (const char *) NULL, (const char *) NULL); (void) umask (0); if (ipid < 0) return -1; if (iswait ((unsigned long) ipid, (const char *) NULL) != 0) { /* Make up an errno value. */ errno = EACCES; return -1; } return 0; } #endif /* ! HAVE_MKDIR */ /* Local variables: mode:c End: */