/* #ident "@(#)smail/src:RELEASE-3_2_0_121:sysdep.c,v 1.128 2005/08/26 19:59:39 woods Exp" */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * Copyright (C) 1992 Ronald S. Karr * * See the file COPYING, distributed with smail, for restriction * and warranty information. */ /* * sysdep.c: * functions which may be operating system dependent. * * external functions: time_stamp, get_arpa_date, get_local_year, * unix_date, compute_local_sender, getfullname, * fopen_as_user, lock_file, unlock_file, * compute_hostname, compute_domain, * open_child, close_child, * close_all, scan_dir, fcntl_rd_lock, touch, fsetsize, * vfprintf, strerror, strysexit, strsignal, sig2str, * set_sigchld_handler */ #include "defs.h" #include #include #include #include #include #include #include #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef __STDC__ # include #else # include #endif #if defined(POSIX_OS) || defined(UNIX_BSD) || defined(WAIT_USE_UNION) # include #endif #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # endif #endif #if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL) # include #else # if defined(UNIX_BSD) # include # endif #endif #ifdef __GLIBC__ # include #endif #ifdef HAVE_SYS_LOCKING_H # include #endif #if defined(HAVE_UNISTD_H) # include #endif #if defined(HAVE_SETUSERCONTEXT) && defined(HAVE_LOGIN_CAP_H) # include #endif #ifdef HAVE_UNAME # include #endif #if defined(UNIX_BSD) && !defined(POSIX_OS) # include #else # if defined(UNIX_XENIX) && !defined(POSIX_OS) # include # else # if defined(HAVE_READDIR) # include # define direct dirent # else # include # endif /* HAVE_READDIR */ # endif /* UNIX_XENIX && !POSIX_OS */ #endif /* UNIX_BSD && !POSIX_OS */ #if defined(POSIX_OS) || defined(HAVE_UTIME_H) # include #else # if defined(DECLARE_UTIMBUF) && !defined(NO_DECLARE_UTIMBUF) struct utimbuf { time_t actime; time_t modtime; }; # endif /* DECLARE_UTIMBUF */ #endif /* POSIX_OS || HAVE_UTIME_H */ #if !defined(UNIX_BSD) && !defined(UNIX_SYS5) && !defined(POSIX_OS) /* use this for ancient ftime() system call, as a last resort */ # include #endif #include "smail.h" #include "smailarch.h" #include "smailsock.h" #include "main.h" #include "parse.h" #include "addr.h" #include "child.h" #include "alloc.h" #include "list.h" #include "smailstring.h" #include "smailwait.h" #include "dys.h" #include "log.h" #include "route.h" #include "bindlib.h" #include "exitcodes.h" #include "extern.h" #include "debug.h" #include "smailport.h" /* * double-check some assumptions.... * * XXX can CPP deal with "the usual conversions"? */ #if (MAXINT_B10_DIGITS == MAXLONG_B10_DIGITS && UINT_MAX != ULONG_MAX) # include "ERROR: ARCH_TYPE assumptions about MAX*_B10_DIGITS are wrong!" #endif #if (__STDC__ - 0) > 0 # if (MAXINT_B10_DIGITS <= 5 && UINT_MAX > 0xffffU) # include "ERROR: ARCH_TYPE (16bit?) assumptions about MAXINT_B10_DIGITS are wrong!" # endif # if (MAXINT_B10_DIGITS <= 10 && UINT_MAX > 0xffffffffU) # include "ERROR: ARCH_TYPE (32bit?) assumptions about MAXINT_B10_DIGITS are wrong!" # endif # if (MAXLONG_B10_DIGITS <= 10 && ULONG_MAX > 0xffffffffU) # include "ERROR: ARCH_TYPE assumptions about MAXLONG_B10_DIGITS are wrong!" # endif # ifdef HAVE_LONG_LONG # if (MAXLONG_B10_DIGITS <= 20 && 0xffffffffffffffffULL) # include "ERROR: ARCH_TYPE assumptions about MAXLONG_B10_DIGITS are wrong!" # endif # endif #endif /* * XXX This is a most horrible work-around to a bug in the setuid(3V) * XXX call in the SunOS-4.1.x /usr/5lib/libc.a. The manual page lies, * XXX and in fact returns EINVAL for uid values greater than 65533. * XXX The implementation in /usr/lib/libc.a works as documented and * XXX allows uid values up to and including 65535. Same for setgid(3V). * * XXX WARNING: these should really be implemented as it is in * XXX /usr/5lib/libc.a{setuid.o} to ensure maximum bug compatability. * XXX So far as I can tell from disassembling /usr/lib/libc.a{setuid.o}, * XXX the setuid() macro is a faithful re-implementation of that routine: * * XXX We do need to be sure we pretend to fail if we're trying to setuid to * XXX -1 (the common value for BOGUS_USER), since setre?id() won't fail in * XXX this case and we'd still be running as root when we thought we'd * XXX changed to nobody_uid or some such less privileged user or group. * XXX This will prevent use of uid 65535, but will allow -2 (65534). */ #if defined(sun) && defined(UNIX_SUN_OS_4_1) && defined(UNIX_SYS5) # define setuid(n) ((n) == (uid_t) -1 ? (errno = EINVAL, -1) : setreuid((-1), (n))) # define setgid(n) ((n) == (gid_t) -1 ? (errno = EINVAL, -1) : setregid((-1), (n))) #endif #ifndef UID_MAX # ifdef MAXUID # define UID_MAX MAXUID # else # ifdef SHRT_MAX # define UID_MAX SHRT_MAX /* XXX SHORT_MAX, LONG_MAX on 4.4bsd???? */ # else # define UID_MAX 32767 # endif # endif #endif #ifndef GID_MAX # define GID_MAX UID_MAX #endif #ifndef UID_NOBODY # define UID_NOBODY UID_MAX #endif #ifndef GID_NOBODY # define GID_NOBODY GID_MAX #endif #if defined(FORCE_DIRSIZ_VALUE) # undef DIRSIZ # define DIRSIZ FORCE_DIRSIZ_VALUE #endif /* functions local to this file */ static char *get_time_zone __P((struct tm *, struct tm *)); static char *get_time_zone_name __P((struct tm *)); static void fullname_from_gcos __P((char *)); static void check_stale __P((char *)); static int lock_file_by_name __P((char *)); static void unlock_file_by_name __P((char *)); static int lockfile_name __P((char *, char **)); #ifndef __STDC__ /* imported library functions */ extern off_t lseek(); extern time_t time(); extern char *getenv(); extern char *getlogin(); extern char *ctime(); # if !defined(USE_TM_TZNAME) && \ !defined(UNIX_SYS5) && !defined(POSIX_OS) && !defined(USE_TZNAME) extern char *timezone(); /* only bsd or v6/v7 */ # endif /* UNIX_SYS5 */ extern struct tm *gmtime(); extern struct tm *localtime(); #endif #ifndef __STDC__ /* variables imported from libc */ extern int errno; #endif /* * time_stamp - return a time stamp string in the form: * * yyyy/dd/dd hh:mm:ss * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ char * time_stamp() { time_t curtime = time((time_t *) NULL); struct tm *ltm = localtime(&curtime); #ifndef USE_OLD_TIMESTAMP /* * not exactly ISO-8601 but close, the T becomes a space so as to preserve * field counts & offsets in logs, etc. */ static char timebuf[sizeof("yyyy/mm/dd hh:mm:ss")]; (void) sprintf(timebuf, "%04d/%02d/%02d %02d:%02d:%02d", ltm->tm_year + 1900, ltm->tm_mon + 1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min, ltm->tm_sec); #else /* old stupid format */ static char timebuf[sizeof("mm/dd/yyyy hh:mm:ss")]; (void) sprintf(timebuf, "%02d/%02d/%04d %02d:%02d:%02d", ltm->tm_mon + 1, ltm->tm_mday, ltm->tm_year + 1900, ltm->tm_hour, ltm->tm_min, ltm->tm_sec); #endif return timebuf; } /* * get_local_year - get the year according to local time * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ int get_local_year() { time_t stamp; /* local time stamp in seconds form Epoch */ /* return year */ stamp = time((time_t *) NULL); return ((localtime(&stamp))->tm_year + 1900); } /* * strings used by get_arpa_date() */ static char *week_day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; /* * get_arpa_date - return date in RFC822 format * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ char * get_arpa_date(sometime) time_t sometime; { struct tm gtm, *ltm; char *tzinfo; static char timebuf[sizeof("ddd, dd mmm yyyy hh:mm:ss +tzof (tzn dst) [extra]")]; /* * We need to do this since most implementations will use the same static * buffer for both localtime and gmtime */ ltm = gmtime(&sometime); (void) memcpy(>m, ltm, sizeof(gtm)); ltm = localtime(&sometime); tzinfo = get_time_zone(>m, ltm); (void) sprintf(timebuf, "%s, %d %s %04d %02d:%02d:%02d %s", week_day[ltm->tm_wday], ltm->tm_mday, months[ltm->tm_mon], ltm->tm_year + 1900, ltm->tm_hour, ltm->tm_min, ltm->tm_sec, tzinfo); return timebuf; } /* * get_time_zone - get the current timezone * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ /* ARGSUSED */ static char * get_time_zone(gtm, ltm) struct tm *gtm, *ltm; { int tzoff, tzoff_hour, tzoff_min; char sign; char * ltzname = NULL; static char tzbuf[20]; /* This code works on all systems to get timezone */ tzoff = ((ltm->tm_min - gtm->tm_min) + (60 * (ltm->tm_hour - gtm->tm_hour))); if (ltm->tm_year != gtm->tm_year) { tzoff += (ltm->tm_year > gtm->tm_year) ? 1440 : -1440; } else { if (ltm->tm_yday != gtm->tm_yday) { tzoff += (ltm->tm_yday > gtm->tm_yday) ? 1440 : -1440; } } if (tzoff >= 0) { sign = '+'; } else { sign = '-'; tzoff = -tzoff; } tzoff_min = tzoff % 60; tzoff_hour = tzoff / 60; ltzname = get_time_zone_name(ltm); if (ltzname) { sprintf(tzbuf, "%c%02d%02d (%s)", sign, tzoff_hour, tzoff_min, ltzname); } else { sprintf(tzbuf, "%c%02d%02d", sign, tzoff_hour, tzoff_min); } return (tzbuf); } static char * get_time_zone_name(ltm) struct tm *ltm; { #if defined(USE_TM_ZONE) return ltm->tm_zone; /* SunOS-4, 4.3BSD */ #else # if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_TZNAME) extern char *tzname[2]; return tzname[ltm->tm_isdst]; # else # if (defined(UNIX_BSD) || defined(USE_GETTIMEOFDAY)) struct timeval tv; struct timezone tz; (void) gettimeofday(&tv, &tz); return timezone(tz.tz_minuteswest, ltm->tm_isdst); # else struct timeb timeb; /* try the V6/V7 system call for getting the time zone */ (void) ftime(&timeb); return timezone(timeb.timezone, ltm->tm_isdst); # endif # endif #endif } /* * unix_date - get the current date as suitable for a From_ line * * Use ctime format. * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ char * unix_date() { time_t curtime = time((time_t *) NULL); char *date = ctime(&curtime); /* * XXX - ctime format is quite standard, so just put the nul in * the proper spot. */ date[24] = '\0'; /* get rid of that \n */ return date; } /* * compute_nobody - figure out the nobody uid/gid * * if `nobody_uid' and `nobody_gid' are defined, use them, otherwise * use the login name in `nobody' to determine nobody_uid/gid. * * XXX assumes nobody's gid == group "nobody" */ void compute_nobody() { if (nobody_uid != BOGUS_USER && nobody_gid != BOGUS_GROUP) { return; } if (nobody == NULL || nobody[0] == '\0') { /* * nobody uid/gid not defined. use something likely to not be * in use */ nobody_uid = UID_NOBODY; nobody_gid = GID_NOBODY; } else { struct passwd *pw; /* passwd entry for `nobody' */ pw = getpwbyname(FALSE, nobody); if (pw == NULL) { nobody_uid = UID_NOBODY; /* XXX may still be == BOGUS_USER */ nobody_gid = GID_NOBODY; } else { nobody_uid = pw->pw_uid; nobody_gid = pw->pw_gid; } } } /* * compute_local_sender - compute the user running this program. * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ void compute_local_sender() { struct passwd *pw; if (local_sender) { return; } islocal = TRUE; /* only local sender determined here */ if (sender_env_variable) { char *p = getenv(sender_env_variable); if (p && *p) { pw = getpwbyname(FALSE, p); if (pw != NULL && pw->pw_uid == real_uid) { local_sender = COPY_STRING(p); return; } } } if ((pw = getpwbyuid(real_uid)) != NULL) { local_sender = COPY_STRING(pw->pw_name); return; } write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "no username found for UID=%u", (unsigned int) real_uid); local_sender = xprintf("%u", (unsigned int) real_uid); return; } /* * getfullname - get the full name of the sender * * The full name comes from the GCOS field in the password entry. * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ void getfullname() { struct passwd *pw; if (islocal) { /* only possible for local senders */ pw = getpwbyname(FALSE, sender); if (pw) { fullname_from_gcos(pw->pw_gecos); } } return; } /* * fullname_from_gcos - fill in sender_name from the given gcos field * * (Modified to strip 0000-name(0000) USG junk - hargen@pdn 8/20/88 * * XXX this isn't really a system dependent function, and was not even before * the days of POSIX.... */ static void fullname_from_gcos(gecos) register char *gecos; { struct str str; register struct str *sp = &str; /* put full name here */ char *cp; STR_INIT(sp); if (isdigit((int) *gecos) && (cp = strchr(gecos, '-')) != NULL) gecos = cp + 1; /* skip USG-style 0000-Name junk */ while (*gecos && *gecos != ',' && *gecos != '(') { /* name may end with a comma or USG-style (0000) junk */ if (*gecos == '&') { /* & means copy sender, capitalized */ STR_NEXT(sp, toupper((int) sender[0])); STR_CAT(sp, sender+1); } else { STR_NEXT(sp, *gecos); } gecos++; } STR_NEXT(sp, '\0'); STR_DONE(sp); sender_name = STR(sp); } #ifdef HAVE_BSD_NETWORKING /* * get_inet_addr - call inet_addr() * * believe it or not, but inet_addr() is not portable!!!! :-( */ unsigned long get_inet_addr(ipaddr) char *ipaddr; { unsigned long inet; /* internet address */ # if defined(INET_ADDR_USE_STRUCT) struct in_addr s_inet; /* Ultrix(?) internet address */ # endif # ifdef INET_ADDR_USE_STRUCT s_inet = inet_addr(ipaddr); inet = s_inet.s_addr; # else inet = inet_addr(ipaddr); # endif return inet; } #endif /* HAVE_BSD_NETWORKING */ /* * fopen_as_user - call fopen(3s) within the context of a specific uid/gid * * given a uid and gid, fopen a file only if the given uid/gid would be * able to do so. Also, for "a" and "w" types: if the file did not * previously exist, the file will be owned by the uid/gid after having * being opened (if the mailer is running as root or as uid/gid). * * return value and errno are set as a call to fopen(3s) would set them. * * NOTE: the mode argument is only required if type is "a" or "w". */ /* XXX FIXME: this doesn't belong in sysdep.c, does it? [yes, if we add the socketpair() file-descriptor passing version!] */ FILE * fopen_as_user(fn, type, uid, gid, mode) char *fn; /* name of file to open */ char *type; /* mode for fopen ("r", "w" or "a") */ uid_t uid; /* user id */ gid_t gid; /* group id */ unsigned int mode; /* mode for creat() (XXX mode_t) */ { FILE *fp; /* opened file */ int fd = EOF; /* file descriptor for opened file */ int cfd = EOF; /* file descriptor for file in child */ pid_t pid; /* PID of forked process */ int i; /* Used to iternate through processes */ STATUS_TYPE status; /* status of exited forked process */ struct stat stb_root, stb_user; /* stat of file in root/user contexts */ DEBUG5(DBG_DRIVER_HI, "fopen_as_user(%s, %s, %u, %u, 0%o) called\n", fn, type, (unsigned int) uid, (unsigned int) gid, mode); /* reject non-root-based paths out of hand, though we shouldn't get any */ if (!fn || fn[0] != '/') { write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, "fopen_as_user: given non-root anchored path: %s", fn); errno = EACCES; return NULL; } /* * This is the classic way for a setuid program to use a file safely as a * user without worrying about race conditions. If the file doesn't exist * we first must create it as the user. In all cases we then open the file * first as root (no create, no truncation unless the file is to be owned * by root), then as the user in a child process, and we compare st_dev and * st_ino for both files, failing if they are not identical. */ if (geteuid() == 0 && uid != 0) { /* * If writing (incl. appending), check whether the file already exists * as we need it to do the access check properly. */ if (((type[0] == 'w') || (type[0] == 'a')) && (access(fn, F_OK) == EOF)) { /* * OK, we need to create the file as the user, so we fork().... If * access() failed we don't care why, as open() will discover the * reason soon enough. */ switch ((pid = fork())) { case EOF: DEBUG1(DBG_DRIVER_LO, "fopen_as_user(): create child fork(): %s.\n", strerror(errno)); return NULL; /* NOTREACHED */ case 0: if (getegid() != gid) { /* this is probably silly */ if (setgid(gid) != 0) { DEBUG3(DBG_DRIVER_LO, "fopen_as_user(): create child setgid(%d): %s (uid was %u).\n", gid, strerror(errno), (unsigned int) uid); exit(errno + 1); /* NOTREACHED */ } } if (setuid(uid) != 0) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child setuid(%u): %s.\n", (unsigned int) uid, strerror(errno)); exit(errno + 1); /* NOTREACHED */ } if ((cfd = open(fn, O_WRONLY | O_CREAT, mode)) < 0) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create open(%s): %s.\n", fn, strerror(errno)); exit(errno + 1); /* NOTREACHED */ } close(cfd); exit(0); /* NOTREACHED */ default: /* wait for the child process to die */ while ((i = wait((STATUS_TYPE *) &status)) != pid) { if (i < 0) { DEBUG2(DBG_DRIVER_MID, "fopen_as_user(): create child wait() for PID %ld: %s.\n", (long int) pid, strerror(errno)); return NULL; } } if (WIFEXITED(status)) { if (WEXITSTATUS(status)) { DEBUG1(WEXITSTATUS(status) - 1 ? DBG_DRIVER_LO : DBG_DRIVER_HI, "fopen_as_user(): create child exited with %d.\n", WEXITSTATUS(status) - 1); errno = WEXITSTATUS(status) - 1; return NULL; } } else if (WIFSIGNALED(status)) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child[%d] signalled PID %ld.\n", (long int) pid, WTERMSIG(status)); errno = EINTR; return NULL; } else if (WIFSTOPPED(status)) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child[%d] stopped PID %ld.\n", (long int) pid, WSTOPSIG(status)); errno = EINTR; return NULL; } } } } /* * If we're not running as euid==0, then we're assuming the file does exist * and has group write permission aka SysV policy. There is a race * condition here where the user might remove the file just after we've * created it above, but if so, too bad for the user -- mailer readers * should truncate the empty mailbox, not remove it. */ #define CREAT_TRUNC_ARE_OK ((geteuid() == 0 && uid == 0) || \ (geteuid() != 0 && access(fn, F_OK) != EOF)) if (type[0] == 'w') { fd = open(fn, O_WRONLY | (CREAT_TRUNC_ARE_OK ? (O_CREAT | O_TRUNC) : 0), mode); } else if (type[0] == 'a') { fd = open(fn, O_WRONLY | O_APPEND | (CREAT_TRUNC_ARE_OK ? (O_CREAT) : 0), mode); } else { fd = open(fn, O_RDONLY, mode); } if (fd < 0) { DEBUG2(DBG_DRIVER_MID, "fopen_as_user(): open(%s): %s.\n", fn, strerror(errno)); return(NULL); } if (geteuid() == 0 && uid != 0) { if (fstat(fd, &stb_root) != 0) { int oerrno = errno; DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): root fstat(%s): %s.\n", fn, strerror(errno)); close(fd); errno = oerrno; return NULL; } switch ((pid = fork())) { case EOF: { int oerrno = errno; DEBUG1(DBG_DRIVER_LO, "fopen_as_user(): test child fork(): %s.\n", strerror(errno)); close(fd); errno = oerrno; } return NULL; /* NOTREACHED */ case 0: if (getegid() != gid) { /* this is probably silly */ if (setgid(gid) != 0) { DEBUG4(DBG_DRIVER_LO, "fopen_as_user(): test child setgid(%u): %s (uid=%u now, to become %u).\n", (unsigned int) gid, strerror(errno), (unsigned int) getuid(), (unsigned int) uid); exit(errno + 1); /* NOTREACHED */ } } if (setuid(uid) != 0) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child setuid(%u): %s.\n", (unsigned int) uid, strerror(errno)); exit(errno + 1); /* NOTREACHED */ } if (type[0] == 'w') { /* * NB: Truncate only here to prevent people being able to * wipe files under the open() done as root. */ cfd = open(fn, O_WRONLY | O_TRUNC, mode); } else if (type[0] == 'a') { cfd = open(fn, O_WRONLY | O_APPEND, mode); } else { cfd = open(fn, O_RDONLY, mode); } if (cfd < 0) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child open(%s): %s.\n", fn, strerror(errno)); exit(errno + 1); /* NOTREACHED */ } if (fstat(cfd, &stb_user) < 0) { int oerrno = errno; DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child fstat(%s): %s.\n", fn, strerror(errno)); close(cfd); exit(oerrno + 1); } close(cfd); if ((stb_root.st_dev != stb_user.st_dev) || (stb_root.st_ino != stb_user.st_ino)) { write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, /* XXX child writing to log here!!! */ "fopen_as_user: parent/child file mismatch!: %s.\n", fn); exit(EACCES + 1); /* got a better idea? */ } exit(0); /* NOTREACHED */ default: /* wait for the child process to die */ while ((i = wait((STATUS_TYPE *) &status)) != pid) { if (i < 0) { int oerrno = errno; DEBUG2(DBG_DRIVER_MID, "fopen_as_user(): test child wait() for PID %ld: %s.\n", (long int) pid, strerror(errno)); close(fd); errno = oerrno; return NULL; } } if (WIFEXITED(status)) { if (WEXITSTATUS(status)) { DEBUG1(WEXITSTATUS(status) - 1 ? DBG_DRIVER_LO : DBG_DRIVER_HI, "fopen_as_user(): test child exited with %d.\n", WEXITSTATUS(status) - 1); close(fd); errno = WEXITSTATUS(status) - 1; return NULL; } } else if (WIFSIGNALED(status)) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child[%d] signalled PID %ld.\n", (long int) pid, WTERMSIG(status)); close(fd); errno = EINTR; return NULL; } else if (WIFSTOPPED(status)) { DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child[%d] stopped PID %d.\n", (long int) pid, WSTOPSIG(status)); close(fd); errno = EINTR; return NULL; } } } if (!(fp = fdopen(fd, type))) { int oerrno = errno; DEBUG3(DBG_DRIVER_LO, "fopen_as_user(): fdopen(%s, %s): %s.\n", fn, type, strerror(errno)); close(fd); errno = oerrno; return NULL; } DEBUG5(DBG_DRIVER_HI, "fopen_as_user(%s, %s, %u, %u, 0%o) returning\n", fn, type, (unsigned int) uid, (unsigned int) gid, mode); return fp; } /* * lock_file - lock a user's mailbox or other mail file * * return SUCCEED or FAIL. */ #ifdef lock_fd_wait /* * under 4.3BSD and some 4.2+ operating systems, flock(2) is used * to lock mailboxes or other mail files. FLOCK_MAILBOX should * be defined in os.h in this case. We assume that the file * descriptor points to the beginning of the file. */ int lock_file(fn, f) char *fn; /* name of file */ FILE *f; /* open file */ { off_t offset; int success; if (! flock_mailbox) { /* use filename-based locking for mailbox files */ return lock_file_by_name(fn); } offset = lseek(fileno(f), (off_t) 0L, SEEK_CUR); (void) lseek(fileno(f), (off_t) 0L, SEEK_SET); success = lock_fd_wait(fileno(f)); (void) lseek(fileno(f), offset, SEEK_SET); if (success < 0) { /* * This should never fail, but if it does, we will retry delivery * at a later time. */ return FAIL; } return SUCCEED; } #else /* not lock_fd_wait */ /* * if the lock_fd_wait macro is not available then we must always * use the V6-style file-locking. */ /*ARGSUSED*/ int lock_file(fn, f) char *fn; /* name of file */ FILE *f; /* open file */ { return lock_file_by_name(fn); } #endif /* not lock_fd_wait */ /* * V7-style or Xenix-style locking. * As far as I know, all flavors of UN*X that do not use 4.3BSD style * locking use this method. * * To lock a file named fn, create a file named fn.lock (or for Xenix, * /tmp/basename.mlk) and stuff the pid into it. If the file already * exisits, see if it is stale and remove it if so. If it was stale, * sleep for FNLOCK_INTERVAL and try to lock it again. Retry at most * FNLOCK_RETRIES times. On non-BSD systems, locking will not be done * if the basename for the file is more than 12 chars. The lenth * restriction does not apply to Xenix, because the basename is always * truncated to 10 chars, allowing sufficient space for the .mlk * suffix. * * on systems without O_EXCL for the open(2) call, creat is used * with a mode that does not allow writing. */ static int lock_file_by_name(fn) char *fn; /* name of file */ { int l_fd = EOF; /* open lock file */ char *l_fn; /* lock file name */ int retries; /* remaining retries */ char apid[BITS_PER_INT/3 + 1]; /* ASCII representation of pid */ /* generate lockfile name */ if (lockfile_name(fn, &l_fn) == FAIL) return FAIL; /* if no lockfile required, that's it */ if (l_fn == NULL) return SUCCEED; /* * try to create the lock file at most FNLOCK_RETRIES+1 times. * each time a lock fails, read a pid from the lock file and try * to remove the lock if that pid does not exist then sleep for * FNLOCK_INTERVAL and try again. */ for (retries = fnlock_retries; retries-- >= 0; sleep(fnlock_interval)) { #ifdef O_EXCL l_fd = open(l_fn, O_WRONLY|O_CREAT|O_EXCL, fnlock_mode); #else /* O_EXCL */ /* * if we can't open for exclusive creat, we will have to * use the creat call. */ l_fd = creat(l_fn, fnlock_mode & (~0222)); #endif /* O_EXCL */ if (l_fd < 0) { /* * examine why this failed */ #ifdef O_EXCL /* * open with O_EXCL returns this error if file exists */ if (errno == EEXIST) { check_stale(l_fn); /* remove stale lock files */ continue; } #else /* O_EXCL */ /* * creat returns the ambiguous EACCES if the file exists * and is not writable (the correct condition for a lock) */ if (errno == EACCES) { check_stale(l_fn); continue; } #endif /* O_EXCL */ /* * some other reason is preventing us from writing * the lock file, thus we won't bother retrying. */ xfree(l_fn); #ifdef UNIX_BSD if (errno == ENOENT) { /* * this is what BSD seems to return on basename too * long, so return SUCCEED in this case because * locking is not possible. Unfortunately this * error does not uniquely identify the problem. */ return SUCCEED; } #endif /* UNIX_BSD */ /* * for anything else, something strange is preventing us * creating the lock file. FAIL for now, we will try * again later. */ return FAIL; } break; } xfree(l_fn); /* don't need this any more */ /* * none of the attempts to create the lock file succeeded */ if (l_fd < 0) { return FAIL; } (void) sprintf(apid, "%ld", (long int) getpid()); (void) write(l_fd, apid, strlen(apid)); (void) close(l_fd); return SUCCEED; } /* * check_stale - see if a lock file is stale. If so, remove it. */ static void check_stale(l_fn) char *l_fn; /* name of lock file */ { char buf[MAXLONG_B10_DIGITS + 1]; /* for contents of lock file (a PID) */ int ct; int fd; long int pid = 0; /* not pid_t -- we're reading it from a file */ struct stat statbuf; fd = open(l_fn, O_RDONLY); if (fd < 0) { DEBUG2(DBG_DRIVER_MID, "%s: cannot open lock file: %s\n", l_fn, strerror(errno)); return; /* doesn't exist? */ } for (;;) { ct = read(fd, buf, sizeof(buf) - 1); if (ct == 0) { DEBUG1(DBG_DRIVER_MID, "%s: zero-length lock file ... ", l_fn); /* sleep 30 seconds and see if new data comes in */ (void) sleep(30); /* fine if it went away */ if ((fstat(fd, &statbuf) < 0) || (statbuf.st_nlink == 0)) { DEBUG(DBG_DRIVER_MID, "lock file went away\n"); (void) close(fd); return; } /* stale if still empty */ if (statbuf.st_size == 0) { if (statbuf.st_nlink > 0) { /* unlink if it still exists */ (void) unlink(l_fn); } DEBUG(DBG_DRIVER_MID, "still empty, removed\n"); (void) close(fd); return; } } else { #ifdef UNIX_XENIX /* For Xenix, simple existence is quite enough. */ DEBUG1(DBG_DRIVER_MID, "%s: valid lock file\n", l_fn); #else /* !Xenix */ buf[ct] = '\0'; (void) sscanf(buf, "%ld", &pid); /* see if the pid exists */ if ((kill((pid_t) pid, 0) < 0) && (errno == ESRCH)) { /* process does not exist, wait 5 seconds before removing */ (void) sleep(5); if ((fstat(fd, &statbuf) == 0) && (statbuf.st_nlink > 0)) { /* remove the file if it still exists */ (void) unlink(l_fn); DEBUG1(DBG_DRIVER_MID, "%s: stale lock file, removed\n", l_fn); } else { DEBUG1(DBG_DRIVER_MID, "%s: stale lock file vanished\n", l_fn); } } else { DEBUG1(DBG_DRIVER_MID, "%s: valid lock file\n", l_fn); } #endif /* !Xenix */ (void) close(fd); return; } } } /* * unlock_file - unlock a user's mailbox or other mail file * * we do not check to make sure we unlocked the file. What * could we do if an unlock failed? */ #ifdef lock_fd_wait /* * for 4.3BSD-style locking, just call flock to unlock the file */ void unlock_file(fn, f) char *fn; /* name of file */ FILE *f; /* open file */ { off_t offset; if (! flock_mailbox) { /* use the V6 locking protocol */ unlock_file_by_name(fn); return; } offset = lseek(fileno(f), (off_t) 0L, SEEK_CUR); (void) lseek(fileno(f), (off_t) 0L, SEEK_SET); /* unlock from beginning to end */ unlock_fd_wait(fileno(f)); (void) lseek(fileno(f), offset, SEEK_SET); } #else /* not lock_fd_wait */ /* * flock not available, so always call the V6-style unlock function. */ /*ARGSUSED*/ void unlock_file(fn, f) char *fn; /* name of file */ FILE *f; /* open file */ { unlock_file_by_name(fn); } #endif /* not lock_fd_wait */ /* * for V6-style locking remove the lock file. Be careful to * make sure that the name passed to unlink(2) won't unlink * the main file (i.e., use the same checks for basename size * used in creating the lock file) */ static void unlock_file_by_name(fn) char *fn; /* name of file */ { char *l_fn; /* lock file name */ /* generate lockfile name */ if (lockfile_name(fn, &l_fn) == FAIL) return; /* if no lockfile required, that's it */ if (l_fn == NULL) return; /* * remove the lock file and clean up */ (void) unlink(l_fn); xfree(l_fn); } /* * generate a mailbox lockfile name into the given char **plfn. * if returned pointer is NULL, no lockfile is required. * return SUCCEED or FAIL. */ static int lockfile_name(fn, plfn) char *fn; char **plfn; { char *base; if (fn[0] != '/') { /* * there should not be a way for the software to generate * a filename that does not begin with / */ panic(EX_SOFTWARE, "lock_by_name: non-rooted filename: %s", fn); /*NOTREACHED*/ } /* find basename */ base = strrchr(fn, '/') + 1; #ifdef lint base = base + 1; /* lint says not used (SunOS) */ #endif /* assume that no lockfile is required */ *plfn = NULL; #ifdef UNIX_XENIX *plfn = xmalloc(sizeof("/tmp/.mlk") + 10); (void) sprintf(*plfn, "/tmp/%.10s.mlk", base); return SUCCEED; #else /* not UNIX_XENIX */ /* * will it be possible to create the lock file? On BSD systems, * the open call will tell us if the filename is too long. On * other systems, we need to check this ourselves before hand. * * On POSIX systems, the only way to know whether truncation * would occur is to call pathconf(). */ # if defined(POSIX_OS) { int no_trunc_flag; int name_max = 0; char *p; if ((int)strlen(base) > _POSIX_NAME_MAX - 2) { char *dir; p = base; while (p > fn && *(p - 1) == '/') { --p; } dir = xmalloc((size_t) ((p - fn) + 1)); (void) memcpy(dir, fn, (size_t) (p - fn)); dir[p - fn] = '\0'; no_trunc_flag = pathconf(dir, _PC_NO_TRUNC); if (! no_trunc_flag) { name_max = pathconf(dir, _PC_NAME_MAX); } xfree(dir); if (no_trunc_flag < 0 || name_max < 0) return FAIL; if (! no_trunc_flag && (int)strlen(base) > name_max - 2) { /* * If there is not enough room to append a .l, then * always succeed the lock operation. This is dangerous * but there is probably nothing better to do. */ return SUCCEED; } } } # else /* not POSIX_OS */ # if !defined(UNIX_BSD) && !defined(UNIX_XENIX) /* allow at least enough room for a trailing .l */ if (strlen(base) > DIRSIZ - 2) { /* * always succeed */ return SUCCEED; } # endif /* not UNIX_BSD and not UNIX_XENIX */ # endif /* POSIX_OS */ /* generate the lock filename */ *plfn = xmalloc(strlen(fn) + sizeof(".lock")); (void) sprintf(*plfn, "%s.lock", fn); return SUCCEED; #endif /* not UNIX_XENIX */ } /* * compute_hostname - compute the name of the local host * * return NULL if we are on an operating system that does not know about * hostnames. */ #ifdef HAVE_GETHOSTNAME char * compute_hostname() { static char *hostname; # ifdef GETHOSTNAME_USE_PTR size_t len; # endif hostname = xmalloc((size_t) (MAXHOSTNAMELEN + 1)); *hostname = '\0'; # ifdef GETHOSTNAME_USE_PTR len = MAXHOSTNAMELEN; (void) gethostname(hostname, &len); # else (void) gethostname(hostname, (size_t) MAXHOSTNAMELEN); # endif hostname[MAXHOSTNAMELEN] = '\0'; hostname = xrealloc(hostname, (size_t) strlen(hostname) + 1); return hostname; } #else /* not HAVE_GETHOSTNAME */ # ifdef HAVE_UNAME char * compute_hostname() { static struct utsname utsnm; (void) uname(&utsnm); /* Is the sysname tag used for something interesting? */ return utsnm.nodename; } # else /* not HAVE_UNAME */ # ifdef SITENAME_FILE /* sitename is stored in a file */ char * compute_hostname() { static struct str hostname; static int inited = FALSE; register int c; FILE *f; if (inited) { return STR(&hostname); } inited = TRUE; STR_INIT(&hostname); f = fopen(SITENAME_FILE, "r"); while ((c = getc(f)) != EOF && c != '\n') { STR_NEXT(&hostname, c); } STR_NEXT(&hostname, '\0'); STR_DONE(&hostname); return STR(&hostname); } # else /* not SITENAME_FILE */ char * compute_hostname() { /* * perhaps we should call uuname -l rather than punting. */ panic(EX_SOFTWARE, "the local host name is not computable and is not configured"); /*NOTREACHED*/ } # endif /* not SITENAME_FILE */ # endif /* not HAVE_UNAME */ #endif /* not HAVE_GETHOSTNAME */ char * compute_domain(hostname) char *hostname #ifndef HAVE_BSD_NETWORKING __attribute__((unused)) #endif ; { char *s = NULL; char *p; #if defined(HAVE_BSD_NETWORKING) && !defined(NO_USEGETHOSTBYNAME) struct hostent *host_info; #endif /* HAVE_BSD_NETWORKING && !NO_USEGETHOSTBYNAME */ #ifdef HAVE_BIND s = bind_compute_domain(); #endif /* HAVE_BIND */ #if defined(HAVE_BSD_NETWORKING) && !defined(NO_USEGETHOSTBYNAME) if (!s || !*s) { /* * gethostbyname() will usually expand an unqualified name, though on * BIND systems it should be the same as what bind_compute_domain() * will find, i.e. the value of "domain" from /etc/resolv.conf */ if ((host_info = gethostbyname(hostname))) { if (strchr(host_info->h_name, '.')) { char *hn = COPY_STRING(host_info->h_name); p = strchr(hn, '.'); *p++ = '\0'; if (EQIC(hn, hostname)) { /* XXX is this check necessary? */ s = COPY_STRING(p); } xfree(hn); } } } #endif /* HAVE_BSD_NETWORKING && !NO_USEGETHOSTBYNAME */ #if defined(HAVE_GETDOMAINNAME) if (!s || !*s) { char *dn = xmalloc((size_t) MAXHOSTNAMELEN + 1); *dn = '\0'; (void) getdomainname(dn, (size_t) MAXHOSTNAMELEN); dn[MAXHOSTNAMELEN] = '\0'; s = xrealloc(dn, (size_t) strlen(dn) + 1); } #endif /* HAVE_GETDOMAINNAME */ /* Next bit of code removes trailing dots */ if (s && *s) { p = s + strlen(s) - 1; while (*p == '.') { *p-- = '\0'; } return(s); } return (NULL); } /* * open_child - create a child process and open pipes to it and exec * * open_child creates a child process, with possible read and write * pipes to the child process's stdin and stdout/stderr. Setuid and * setgid are called in the child process to change the uid/gid to * whichever uid/gid are given to open_child. * * open_child does nothing with signals in the parent process. * Only signal behaviors modified by exec will be changed in the * child process. If more complex signal behavior desired, call * with argv == NULL and handle signals and the exec in the caller * routine within the child process. * * inputs: * argv - a vector suitable for passing to execve. argv[0] is * given as the program to exec. If argv is NULL, then * do a return instead of an exec. * envp - a vector of environment variables suitable for passing * to execve. If envp is null and CHILD_MINENV is not * set in flags, then the parents environment will be * passed to the child. * in - a pointer to a FILE* variable. If non-NULL, a pipe * is created, with the write-end returned in this * variable and with the read-end tied to the stdin of * the child process. * out - a pointer to a FILE* variable. If non-NULL, a pipe * is created, with the read-end returned in this * variable and with the write-end tied to the stdout * of the child process. * errfd - if errfd >= 0 then it specifies a file descriptor * to duplicate to the stdout and/or stderr of the * child. If out != NULL, errfd is only dup'd to the * stderr. * flags - a bitmask of flags affecting open_child's operation. * Flags are: * * CHILD_DUPERR - duplicate stdout to stderr in the * child process. * CHILD_CLSERR - close stderr in the child process. * CHILD_DEVNULL - open "/dev/null" and associate it * with stdin and/or stdout in the * child process, if no in and/or out * variable is specified. * CHILD_RETRY - retry fork() operation up to FORK_RETRIES * times at FORK_INTERVAL second intervals, * if fork returns EAGAIN. * CHILD_MINENV - give the child process a very minimal * environment. This is overridden by giving * an explicit environment in envp. * CHILD_NOCLOSE - don't close file descriptors. If this is * not set, close all file descriptors * other than stdin/stdout/stderr. * * uid - do a setuid(uid) in the child * gid - do a setgid(gid) in the child * * output: * pid of child process, or 0 if in child process (only if argv==NULL). * Return EOF on critical errors, such as if pipe() or fork() failed. */ pid_t open_child(argv, envp, in, out, errfd, flags, uid, gid) char **argv; /* arg vector for execve */ char **envp; /* environment vector for execve */ FILE **in; /* make a stdin file pointer */ FILE **out; /* make a stdout file pointer */ volatile int errfd; /* fd to use for stdout/stderr */ unsigned int flags; /* flags affecting operation */ uid_t uid; /* user ID for child process */ gid_t gid; /* group ID for child process */ { int stdin_fd[2]; /* fds from pipe(2) for child stdin */ int stdout_fd[2]; /* fds from pipe(2) for child stdout */ /* remember, fd[0] is the read descriptor, fd[1] is the write descriptor */ volatile int retries = FORK_RETRIES; /* countdown of retries */ pid_t pid; /* pid from fork() (XXX pid_t) */ int oerrno; /* errno from execv*(), etc. */ static char *min_env[] = { #ifdef CHILD_ENVIRON CHILD_ENVIRON, #else /* CHILD_ENVIRON */ "HOME=/", NULL, /* leave space for path */ NULL, /* leave space for shell */ #endif /* CHILD_ENVIRON */ NULL, /* leave space for timezone */ NULL, /* end of environment */ }; static char **minp = NULL; #if defined(HAVE_SETUSERCONTEXT) || defined(HAVE_SETLOGIN) struct passwd *upwd; #endif if (!argv) { write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child() called with NULL argv!"); return EOF; } DEBUG5(DBG_DRIVER_HI, "open_child('%s' ... , env, in, out, %s, 0%o, %u, %u) called\n", argv[0], errfd >= 0 ? "errlog" : "noerrlog", flags, (unsigned int) uid, (unsigned int) gid); if (in && pipe(stdin_fd) < 0) { write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): pipe(stdin_fd) failed: %s", argv[0], strerror(errno)); return EOF; } if (out && pipe(stdout_fd) < 0) { write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): pipe(stdout_fd) failed: %s", argv[0], strerror(errno)); /* free resources */ if (in) { (void) close(stdin_fd[0]); (void) close(stdin_fd[1]); } return EOF; } /* * keep trying to create a new child process until either we succeed, or we * run out of retries * * Do not use vfork() -- we muck with far too much in here! */ while ((pid = fork()) < 0 && errno == EAGAIN && --retries >= 0) { (void) sleep(FORK_INTERVAL); /* sleep a bit between retries */ } if (pid < 0) { write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC, "open_child(%s): fork() failed after %d: %s", argv[0], FORK_RETRIES, strerror(errno)); /* free resources */ if (in) { (void) close(stdin_fd[0]); (void) close(stdin_fd[1]); } if (out) { (void) close(stdout_fd[0]); (void) close(stdout_fd[1]); } return EOF; } if (pid == 0) { /* in child process */ /* close unnecessary file descriptors */ if (in) { (void) close(stdin_fd[1]); } if (out) { (void) close(stdout_fd[0]); } /* if errfd == 0, watch out for the code below */ if (errfd == STDIN_FILENO || errfd == STDOUT_FILENO) { int new_errfd; /* search for a descriptor we don't care about */ for (new_errfd = (STDERR_FILENO + 1); new_errfd < 10; new_errfd++) { if ((in && new_errfd == stdin_fd[0]) || (out && new_errfd == stdout_fd[1])) { continue; } break; } if (dup2(errfd, new_errfd) == -1) { /* XXX this debug may not even print!!! */ write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): dup2(errfd, new_errfd) failed: %s", argv[0], strerror(errno)); _exit(EX_OSERR); } (void) close(errfd); errfd = new_errfd; } if (in || (flags & CHILD_DEVNULL)) { /* * setup the child's stdin */ if (in) { /* pipe from parent process */ if (stdin_fd[0] != STDIN_FILENO) { if (dup2(stdin_fd[0], STDIN_FILENO) == -1) { /* XXX this debug may not even print!!! */ write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): dup2(stdin_fd[0], 0) failed: %s", argv[0], strerror(errno)); _exit(EX_OSERR); } (void) close(stdin_fd[0]); } } else if (flags & CHILD_DEVNULL) { /* * XXX - we rely on pipe() never returning 0 as the * write file descriptor, or this could close * stdout_fd[1], which would not be nice. */ /* open /dev/null as the stdin */ (void) close(STDIN_FILENO); if (open("/dev/null", O_RDONLY) < 0) { /* XXX assumes will re-open on just closed fd!!!! */ /* uggh! failed to open /dev/null, quit without flush */ write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC, "open_child(%s): open(/dev/null, O_RDONLY) failed: %s", argv[0], strerror(errno)); _exit(EX_OSFILE); } } } if (out || errfd >= 0 || (flags & CHILD_DEVNULL)) { /* * setup the child's stdout */ if (out) { /* pipe to parent process */ if (stdout_fd[1] != STDOUT_FILENO) { if (dup2(stdout_fd[1], STDOUT_FILENO) == -1) { /* XXX this debug may not even print!!! */ write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): dup2(stdin_fd[1], 1) failed: %s", argv[0], strerror(errno)); _exit(EX_OSERR); } (void) close(stdout_fd[1]); } } else if (errfd >= 0) { /* use the given fd for stdout */ if (dup2(errfd, STDOUT_FILENO) == -1) { /* XXX this debug may not even print!!! */ write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): dup2(errfd, 1) failed: %s", argv[0], strerror(errno)); _exit(EX_OSERR); } } else if (flags & CHILD_DEVNULL) { /* open /dev/null as stdout */ (void) close(STDOUT_FILENO); if (open("/dev/null", O_WRONLY) < 0) { /* XXX assumes will re-open on just closed fd!!!! */ /* uggh! failed to open /dev/null, quit without flush */ write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC, "open_child(%s): open(/dev/null, O_WRONLY) failed: %s", argv[0], strerror(errno)); _exit(EX_OSFILE); } } } if (errfd >= 0) { /* use this fd as the stderr */ if (errfd != STDERR_FILENO) { if (dup2(errfd, STDERR_FILENO) == -1) { /* XXX this debug may not even print!!! */ write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): dup2(errfd, 2) failed: %s", argv[0], strerror(errno)); _exit(EX_OSERR); } (void) close(errfd); } } else if (flags & CHILD_DUPERR) { /* duplicate stdout to get the stderr */ if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) { /* XXX this debug may not even print!!! */ write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): dup2(1, 2) failed: %s", argv[0], strerror(errno)); _exit(EX_OSERR); } } else if (flags & CHILD_CLSERR) { (void) close(STDERR_FILENO); if (open("/dev/null", O_WRONLY) < 0) { /* XXX assumes will re-open on just closed fd!!!! */ /* uggh! failed to open /dev/null, quit without flush */ write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): open(/dev/null) failed: %s", argv[0], strerror(errno)); _exit(EX_OSFILE); } } #ifdef POSIX_OS /* NOTE: we must do this before using setlogin(2) [via setusercontext()] */ (void) setsid(); #else (void) setpgrp(0, getpid()); #endif #ifdef HAVE_SETUSERCONTEXT if (!(upwd = getpwbyuid(uid))) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0), "open_child(%s): getpwbyuid(%u) for setusercontext() failed (possible local configuration error): %s", argv[0], (unsigned int) uid, strerror(errno)); _exit(EX_OSERR); } /* setusercontext() doesn't set GID the way we want it set */ if (setgid(gid) == -1) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0), "open_child(%s): setgid(%u) failed (possible local configuration error): %s", argv[0], (unsigned int) gid, strerror(errno)); _exit(EX_NOPERM); } /* * every system with setusercontext(3) pretty much must have * setgroups(2) and it's almost certain that it'll have a working * version too -- i.e. one that knows what to do with a count of 0. */ if (setgroups(0, (gid_t *) NULL) != 0) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_PANIC : 0), "setgroups(0, NULL) failed: %s", gid, strerror(errno)); } /* * i.e. everything but LOGIN_SETGROUP and LOGIN_SETENV (the latter * would be overridden by our own environment setup and use of * execve(). * * Note that without the pwd parameter setusercontext() turns off * LOGIN_SETGROUP and LOGIN_SETLOGIN regardless, but it's easier, * safer, and more portable for us to use getpwuid() than it is to do * both that and then use login_getclass() and then use only the flags * we know we want. */ if (setusercontext((login_cap_t *) NULL, upwd, uid, (LOGIN_SETRESOURCES | LOGIN_SETPRIORITY | LOGIN_SETUMASK | LOGIN_SETLOGIN | LOGIN_SETUSER | LOGIN_SETPATH)) == -1) { oerrno = errno; write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0), "open_child(%s): setusercontext() failed (possible local configuration error): %s", argv[0], strerror(errno)); _exit((oerrno == EPERM) ? EX_NOPERM : EX_OSERR); } #else /* !HAVE_SETUSERCONTEXT */ # ifdef HAVE_SETLOGIN if (!(upwd = getpwbyuid(uid))) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0), "open_child(%s): getpwbyuid(%u) for setlogin() failed (possible local configuration error): %s", argv[0], (unsigned int) uid, strerror(errno)); _exit(EX_OSERR); } if (setlogin(upwd->pw_name) == -1) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0), "open_child(%s): setlogin(%s) failed (possible local configuration error): %s", argv[0], upwd->pw_name, strerror(errno)); _exit(EX_NOPERM); } # endif /* change uid and gid */ if (setgid(gid) == -1) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0), "open_child(%s): setgid(%u) failed (possible local configuration error): %s", argv[0], (unsigned int) gid, strerror(errno)); _exit(EX_NOPERM); } # ifdef HAVE_SETGROUPS /* clear out all extra groups. We don't want to have to deal with them */ { gid_t dummy_grouplist[1]; dummy_grouplist[0] = gid; if (setgroups(0, (gid_t *) NULL) != 0) { if (setgroups(1, dummy_grouplist) != 0) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_PANIC : 0), "setgroups(0, NULL) and setgroups(1, [%d]) failed: %s", gid, strerror(errno)); } } } # endif if (setuid(uid) == -1) { write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0), "open_child(%s): setuid(%u) failed (possible local configuration error): %s", argv[0], (unsigned int) uid, strerror(errno)); _exit(EX_NOPERM); } #endif /* !HAVE_SETUSERCONTEXT */ /* close all unnecessary file descriptors */ if ( !(flags & CHILD_NOCLOSE) ) { close_all(); } /* run the program in the current process' context */ if (envp) { (void) execve(argv[0], argv, envp); oerrno = errno; DEBUG2(DBG_DRIVER_LO, "open_child(): execve(%s, envp): %s.\n", argv[0], strerror(errno)); } else if (flags & CHILD_MINENV) { if (minp == NULL) { /* pass through the TZ environment variable */ char *tz; for (minp = min_env; *minp; minp++) { ; } #ifndef CHILD_ENVIRON # ifdef SHELL_EXEC_PATH *minp++ = xprintf("SHELL=%s", SHELL_EXEC_PATH); # else *minp++ = "SHELL=/bin/sh"; # endif *minp++ = xprintf("PATH=%s", SECURE_PATH); #endif /* CHILD_ENVIRON */ if ((tz = getenv("TZ"))) { *minp++ = xprintf("TZ=%s", tz); } } (void) execve(argv[0], argv, min_env); oerrno = errno; DEBUG2(DBG_DRIVER_LO, "open_child(): execve(%s, minenv): %s.\n", argv[0], strerror(errno)); } else { (void) execv(argv[0], argv); oerrno = errno; DEBUG2(DBG_DRIVER_LO, "open_child(): execv(%s): %s.\n", argv[0], strerror(errno)); } /* Oh well... all this work and we can't even exec the file! */ write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC, "open_child(): exec(%s) failed (possible local configuration error): %s", argv[0], strerror(oerrno)); _exit(EX_OSFILE); } else { /* in parent process */ if (in) { /* close the child stdin, return the write-channel */ (void) close(stdin_fd[0]); if (!(*in = fdopen(stdin_fd[1], "w"))) { write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): fdopen(stdin_fd[1], w) failed: %s", argv[0], strerror(errno)); /* free resources */ (void) close(stdin_fd[1]); if (out) { (void) close(stdout_fd[0]); (void) close(stdout_fd[1]); } return EOF; } } if (out) { /* close the child stdout, return the read-channel */ (void) close(stdout_fd[1]); if (!(*out = fdopen(stdout_fd[0], "r"))) { write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "open_child(%s): fdopen(stdout_fd[1], r) failed: %s", argv[0], strerror(errno)); /* free remaining resources */ (void) close(stdout_fd[0]); return EOF; } } } return pid; } /* * close_child - close down the child process started with open_child. * * if non-zero file pointers are passed, the given files are closed. * Then, wait for the specified pid to exit and return its status. * * NOTE: we do not keep the exit status of processes other than the * one we are waiting. Thus, only one process should be open * at a time by open_child, unless the caller wishes to do * the wait() calls itself. * * return EOF, on error, or exit status. */ int close_child(in, out, pid) FILE *in; /* pipe to child's stdin */ FILE *out; /* pipe to child's stdout */ pid_t pid; /* pid of child process */ { int i; /* return value from wait */ int status; /* status from wait */ if (in) { (void) fclose(in); /* close the pipe to stdin */ } if (out) { (void) fclose(out); /* close the pipe to stdout */ } /* wait for the child process to die */ while ((i = wait((STATUS_TYPE *) &status)) != pid && i >= 0) { ; } if (i < 0) { /* failed to find the child process */ return FAIL; } return status; /* everything went okay */ } /* * close_all - close all file descriptors other than 0, 1 and 2 * * At several key points smail needs to know that no extra file descriptors * being used, such as when execing other processes (to ensure that * child processes cannot read or write protected information left open in * smail) and at startup (to prevent smail running out of file descriptors). */ void close_all() { register int i; int max_nfd; /* * so, just how many file descriptors do we have? */ #ifdef OPEN_MAX max_nfd = OPEN_MAX; #else /* not POSIX_OS && not OPEN_MAX */ # ifdef NOFILE max_nfd = NOFILE; # else /* not POSIX_OS && not OPEN_MAX && not NOFILE */ max_nfd = 20; # endif #endif for (i = 3; i < max_nfd; i++) { if (errfile && i == fileno(errfile)) { continue; } if (logfile && i == fileno(logfile)) { continue; } if (panicfile && i == fileno(panicfile)) { continue; } (void) close(i); } return; } #ifndef HAVE_RENAME /* * rename - emulate the BSD/System V.3 rename(2) system call * * This subroutine is not atomic, so functions which are worried about * consistency across system failures or in the face of multiple processes * should consider the situation when HAVE_RENAME is not defined. * * The errno returned will not always correspond to what would have * been produced by a true rename(2) system call. */ int rename(from, to) const char *from; /* old file name */ const char *to; /* new file name */ { struct stat statbuf; if (stat(from, &statbuf) < 0) { /* `from' is inaccessible, so forget it */ return -1; } if (unlink(to) && errno != ENOENT) { /* could not unlink `to', though it may exist */ return -1; } if (link(from, to) < 0) { /* failed to link, however, an existing `to' file was removed */ return -1; } if (unlink(from) < 0) { /* could not unlink, the file exists under two names */ return -1; } return 0; } #endif /* HAVE_RENAME */ #if !defined(HAVE_MKDIR) /* * for OS's that lack a mkdir system call, call the mkdir program */ int mkdir(dir, mode) char *dir; int mode; { static char *mkdir_argv[] = { "/bin/mkdir", NULL, /* space for directory name */ NULL, }; /* set the umask so that the directory will have the correct perms */ unsigned int oumask = umask((~mode) & 0777); pid_t pid; mkdir_argv[1] = dir; /* start up the mkdir program */ pid = open_child(mkdir_argv, (char **)NULL, (FILE **)NULL, (FILE **)NULL, -1, CHILD_DEVNULL, 0, 0); (void) umask(oumask); return close_child((FILE *)NULL, (FILE *)NULL, pid); } #endif /* !defined(HAVE_MKDIR) */ #ifndef HAVE_DUP2 /* * dup2 - this provides the dup2 function as described by the Posix Draft * for those sites which do not have it. * * Mark Colburn (mark@jhereg.mn.org) */ int dup2(fildes, fildes2) int fildes, fildes2; { int fid; if (fcntl(fildes, F_GETFL, 0) == -1) return(EBADF); if (fildes != fildes2) close(fildes2); fid = fcntl(fildes, F_DUPFD, fildes2); return(fid); } #endif /* HAVE_DUP2 */ #ifndef HAVE_READDIR /* * for OS's that lack the directory facilities, emulate */ struct direct_with_null { struct direct d; int trailing_null; /* make sure name ends in a nul byte */ }; typedef FILE DIR; #define opendir(dn) (fopen(dn, "r")) #define closedir(dn) (fclose(dn)) static struct direct * readdir(dirp) DIR *dirp; { static struct direct_with_null ret; ret.trailing_null = 0; do { if (fread(&ret.d, sizeof(ret.d), 1, (FILE *)dirp) < 1) { return NULL; } } while (ret.d.d_ino == 0); return &ret.d; } #endif /* HAVE_READDIR */ /* * scan_dir - scan through a directory, looking for filenames * * if given a non-NULL directory argument, opens the directory and * returns the first file, or NULL if it contains no files. * successive calls, with a NULL argument, return successive files * in the directory. On the call after the last directory, a NULL * is returned and the directory is closed. * * scan_dir returns static data which is reused on subsequent calls. */ char * scan_dir(dn) char *dn; /* directory name, or NULL */ { static DIR *dirp = NULL; /* opened directory */ struct direct *dp; /* current directory entry */ if (dn) { if (dirp) { (void) closedir(dirp); dirp = NULL; } dirp = opendir(dn); } if (dirp == NULL) { return NULL; /* no directory, so no entries */ } dp = readdir(dirp); if (dp == NULL) { (void) closedir(dirp); dirp = NULL; return NULL; } return dp->d_name; } #ifdef USE_FCNTL_RD_LOCK /* * fcntl_rd_lock - get a shared lock using a System V-style fcntl. */ int fcntl_rd_lock(fd) int fd; { struct flock l; l.l_type = F_RDLCK; l.l_whence = 0; l.l_start = 0L; l.l_len = 0L; if (fcntl(fd, F_SETLK, &l) < 0) { return FAIL; } return SUCCEED; } /* * fcntl_rd_lock_wait - get a shared lock with blocking using a System V-style fcntl. */ int fcntl_rd_lock_wait(fd) int fd; { struct flock l; l.l_type = F_RDLCK; l.l_whence = 0; l.l_start = 0L; l.l_len = 0L; if (fcntl(fd, F_SETLKW, &l) < 0) { return FAIL; } return SUCCEED; } #endif /* touch - update the access and modified times of the given path; * if both values are zero, the current time is used for both */ int touch(fname, atime, mtime) char *fname; time_t atime, mtime; { struct utimbuf times; time_t cur_time; (void) memset((char *) ×, '\0', sizeof(times)); cur_time = time((time_t *) NULL); times.actime = atime ? atime : cur_time; times.modtime = mtime ? mtime : cur_time; return utime(fname, ×); } /* fsetsize - set file size * * If truncation is possible, set file to wantsize. * Otherwise, write NULs after oversize. */ /* ARGSUSED */ int fsetsize(fd, wantsize, oversize) int fd; /* fd to adjust */ off_t wantsize; /* desired size */ off_t oversize /* bytes not to overwrite */ #ifndef F_FREESP __attribute__((unused)) #endif ; { off_t cursize; /* determine current file size */ if ((cursize = lseek(fd, (off_t) 0L, SEEK_END)) == -1) { return -1; } /* maybe lengthen... */ if (cursize < wantsize) { if (lseek(fd, wantsize - 1, 0) == -1 || write(fd, "", (size_t) 1) == -1) { return -1; } return 0; } /* maybe shorten... */ if (wantsize < cursize) { #ifdef HAVE_FTRUNCATE return ftruncate(fd, wantsize); #else # ifdef HAVE_CHSIZE return chsize(fd, wantsize); # else # ifdef F_FREESP /* * Thanks to Larry Wall for this hack. * It relies on the UNDOCUMENTED F_FREESP argument to * fcntl(2), which truncates the file so that it ends * at the position indicated by fl.l_start. */ struct flock fl; fl.l_whence = 0; fl.l_len = 0; fl.l_start = wantsize; fl.l_type = F_WRLCK; /* write lock on file space */ return fcntl(fd, F_FREESP, &fl); # else /* !F_FREESP */ /* * No truncation features are available. * Zero out unwanted bytes (i.e. those past oversize). */ if (oversize < cursize) { off_t left; if (lseek(fd, oversize, SEEK_SET) == -1) { return -1; } left = cursize - oversize; while (left > 0) { static char x[64]; size_t n; if ((n = sizeof(x)) > left) { n = left; } if (write(fd, x, n) == -1) { return -1; } left -= n; } } return 0; # endif /* F_FREESP */ # endif /* HAVE_CHSIZE */ #endif /* HAVE_FTRUNCATE */ } /* no adjustment required */ return 0; } #ifndef HAVE_VFPRINTF /* * vfprintf - a hacked version of vfprintf() for sites that don't have it * * XXX - will _doprnt() work here? */ int vfprintf(file, fmt, ap) FILE *file; char *fmt; va_list ap; { int a,b,c,d,e,f,g,h,i,j,k,l,m,n,o; a = va_arg(ap, int); b = va_arg(ap, int); c = va_arg(ap, int); d = va_arg(ap, int); e = va_arg(ap, int); f = va_arg(ap, int); g = va_arg(ap, int); h = va_arg(ap, int); i = va_arg(ap, int); j = va_arg(ap, int); k = va_arg(ap, int); l = va_arg(ap, int); m = va_arg(ap, int); n = va_arg(ap, int); o = va_arg(ap, int); return fprintf(file, fmt, a,b,c,d,e,f,g,h,i,j,k,l,m,n,o); } #endif /* HAVE_VFPRINTF */ #ifndef HAVE_STRERROR /* * strerror - return a string representing the error given in errnum. */ char * strerror(errnum) int errnum; { static char misc_err[sizeof("Unknown error: ") + MAXINT_B10_DIGITS + 1]; /* used when sprintf must be used */ #if 0 extern const char *const sys_errlist[]; /* 4.4BSD */ #else extern char *sys_errlist[]; /* list of error strings */ #endif extern int sys_nerr; /* number of entries in sys_errlist */ if (errnum > sys_nerr || errnum < 0) { /* there is no entry for it in sys_errlist, build one */ (void) sprintf(misc_err, "Unknown error: %d", errnum); return misc_err; } else { /* there is an entry in sys_errlist, use it */ return sys_errlist[errnum]; } } #endif /* HAVE_STRERROR */ /* * strsysexit - return a string corresponding to an exit code. * * XXX as-is this should be sysexit2name() or something */ char * strsysexit(exitcode) int exitcode; { static char buf[sizeof("EX_") + MAXINT_B10_DIGITS + 1]; /* buffer for generating message */ switch (exitcode) { case EX_USAGE: return "EX_USAGE"; case EX_DATAERR: return "EX_DATAERR"; case EX_NOINPUT: return "EX_NOINPUT"; case EX_NOUSER: return "EX_NOUSER"; case EX_NOHOST: return "EX_NOHOST"; case EX_UNAVAILABLE: return "EX_UNAVAILABLE"; case EX_SOFTWARE: return "EX_SOFTWARE"; case EX_OSERR: return "EX_OSERR"; case EX_OSFILE: return "EX_OSFILE"; case EX_CANTCREAT: return "EX_CANTCREAT"; case EX_IOERR: return "EX_IOERR"; case EX_TEMPFAIL: return "EX_TEMPFAIL"; case EX_PROTOCOL: return "EX_PROTOCOL"; case EX_NOPERM: return "EX_NOPERM"; default: (void) sprintf(buf, "EX_%d", exitcode); return buf; } } /* NetBSD gained sys_nsig sometime just prior to 1.4 */ #if defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 104000000) # define HAVE_SYS_NSIG 1 # define SYS_NSIG_DECLARED 1 #endif /* FreeBSD gained sys_nsig sometime just prior to 4.0 */ #if defined(__FreeBSD_version) && (__FreeBSD_version >= 400017) # define HAVE_SYS_NSIG 1 # define SYS_NSIG_DECLARED 1 #endif #if !defined(HAVE_STRSIGNAL) || (!defined(HAVE_SIG2STR) && defined(HAVE_SYS_SIGNAME)) # if !defined(SYS_NSIG_DECLARED) && !defined(__SYS_NSIG_DECLARED) # if defined(HAVE_SYS_NSIG) extern int sys_nsig; # else # define sys_nsig NSIG # endif # endif #endif #ifndef HAVE_STRSIGNAL /* * strsignal - return a pointer to a string describing the signal given in signum. */ char * strsignal(signum) int signum; { # if !defined(SYS_SIGLIST_DECLARED) && !defined(__SYS_SIGLIST_DECLARED) # if 0 /* 4.4BSD, GNU/Linux, SCO5, SunOS-5, if they needed it */ extern const char *const sys_siglist[]; # else extern char *sys_siglist[]; # endif # endif #if defined(SIGRTMIN) && defined(SIGRTMAX) if (signum >= sys_nsig && signum >= SIGRTMIN && signum <= SIGRTMAX) { static char rt_descr[sizeof("Real time signal: ") + MAXINT_B10_DIGITS + 1]; /* * Signum is out of range but is within SIGRTMIN..SIGRTMAX so we build * our own description for it. */ (void) sprintf(rt_descr, "Real time signal: %d", signum); return rt_descr; } else #endif if (signum >= sys_nsig || signum < 0) { char signame[SIG2STR_MAX]; static char misc_err[sizeof("Unknown signal: SIG []") + MAXINT_B10_DIGITS + SIG2STR_MAX + 1]; /* * Signum is out of range so there's no entry for it in sys_siglist, so * we build our own description. * * Even though the SunOS-5 manual says "The strsignal() function * returns NULL if sig is not a valid signal number.", it's easy enough * to assume signum is valid and instead that sys_siglist is * incomplete. The behaviour of this implementation matches that of * 4.4BSD. */ if (sig2str(signum, signame) == -1) { (void) sprintf(signame, "#%d", signum); } (void) sprintf(misc_err, "Unknown signal: SIG%s [%d]", signame, signum); return misc_err; } /* there is an entry in sys_siglist, use it */ return sys_siglist[signum]; } #endif /* HAVE_STRSIGNAL */ #ifndef HAVE_SIG2STR # if defined(HAVE_SYS_SIGNAME) # define SYS_NSIGNAME sys_nsig # else # define SYS_NSIGNAME sys_nsigname /* can't be sure gen'ed one has sys_nsig entries */ extern const unsigned int sys_nsigname; /* from ../compat/signame.c */ # endif /* * sig2str - pass back via signame a string naming the signal given by signum. * * Returns -1 if no name is known for the signal. * * NOTE: Unfortunately this most horrible API is "standard" in SunOS-5.x */ int sig2str(signum, signame) int signum; char *signame; /* ptr to at least SIG2STR_MAX bytes */ { # if !defined(SYS_SIGNAME_DECLARED) && !defined(__SYS_SIGNAME_DECLARED) # if 0 /* 4.4BSD, Linux, etc. */ extern const char *const sys_signame[]; # else extern char *sys_signame[]; # endif # endif # if defined(SIGRTMIN) && defined(SIGRTMAX) if (signum >= (int) SYS_NSIGNAME && signum >= SIGRTMIN && signum <= SIGRTMAX) { /* * Signum is out of range but is within SIGRTMIN..SIGRTMAX so we build * our own description for it. * * XXX any str2sig() implementation should try to account for this * hack, just as the SunOS-5 implementation does, though perhaps not * with the "RTMAX-1" weirdness. */ # if (SIG2STR_MAX < (MAXINT_B10_DIGITS + 7)) # include "ERROR: SIG2STR_MAX is too small!" # endif (void) sprintf(signame, "RTMIN+%d", (signum - SIGRTMIN)); return 0; } else # endif if (signum >= (int) SYS_NSIGNAME || signum < 0) { /* XXX Maybe we dould format "#%d" in here anyway? */ return (-1); } strncpy(signame, sys_signame[signum], (size_t) SIG2STR_MAX); signame[SIG2STR_MAX - 1] = '\0'; return 0; } #endif /* HAVE_SIG2STR */ /* * Unless the target system has POSIX.4 queued signals we really need reliable * signals in order to safely handle SIGCHLD. This is because only with * reliable signals are we free to ignore problems with lost signals (which * would adversely affect our ability to reliably count remaining active child * processes) and free to avoid worrying about creating a loop should we * re-instate the signal handler before all exited child processes have been * reaped. * * (except under old SysVr2.x with signal(SIGCLD) where the right thing * always happens -- the signal hander is always re-entered if any SIGCLD * signals arrive while it is executing, and with only wait(2) the signal * handler must reap dead child processes). * * XXX it is basically impossible to deal with SIGCLD on SysVr3.x because it's * not queued, and it is reset, and there's no non-blocking alternative to * wait(2). * * XXX on BSD4.3 or BSD4.2 we will need to use yucky old sigvec(2). * * For all others (i.e. for POSIX_OS) we use sigaction(2). * * Rumour (in the form of code in Postfix) has it that on HP/UX (and perhaps * others) the handler will have to muck with the signal context in order to * get select() or accept() to return EINTR instead of restarting. */ #ifdef SA_NOCLDSTOP /* aka HAVE_SIGACTION aka POSIX_OS */ int set_sigchld_handler(handler) void (*handler) __P((int)); { struct sigaction action; sigemptyset(&action.sa_mask); action.sa_flags = 0; # ifdef SA_RESTART /* NOTE: we do not intend accept() or select() to restart! */ action.sa_flags |= SA_RESTART; # endif action.sa_flags |= SA_NOCLDSTOP; action.sa_handler = handler; return sigaction(SIGCHLD, &action, (struct sigaction *) NULL); } #else /* SA_NOCLDSTOP */ # ifdef UNIX_SYS5_2 int set_sigchld_handler(handler) void (*handler)(int); { return (signal(SIGCLD, handler) == SIG_ERR) ? -1 : 0; } # else # include "ERROR: smail not currently supported on this platform without sigaction(2)" # endif /* UNIX_SYS5_2 */ #endif /* SA_NOCLDSTOP */ /* * Local Variables: * c-file-style: "smail" * End: */