/*
#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 <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#if defined(POSIX_OS) || defined(UNIX_BSD) || defined(WAIT_USE_UNION)
# include <sys/wait.h>
#endif

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL)
# include <fcntl.h>
#else
# if defined(UNIX_BSD)
#  include <sys/file.h>
# endif
#endif
#ifdef __GLIBC__
# include <sys/file.h>
#endif
#ifdef HAVE_SYS_LOCKING_H
# include <sys/locking.h>
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#if defined(HAVE_SETUSERCONTEXT) && defined(HAVE_LOGIN_CAP_H)
# include <login_cap.h>
#endif

#ifdef HAVE_UNAME
# include <sys/utsname.h>
#endif
#if defined(UNIX_BSD) && !defined(POSIX_OS)
# include <sys/dir.h>
#else
# if defined(UNIX_XENIX) && !defined(POSIX_OS)
#  include <sys/ndir.h>
# else
#  if defined(HAVE_READDIR)
#   include <dirent.h>
#   define direct dirent
#  else
#   include <sys/dir.h>
#  endif /* HAVE_READDIR */
# endif	/* UNIX_XENIX && !POSIX_OS */
#endif	/* UNIX_BSD && !POSIX_OS */

#if defined(POSIX_OS) || defined(HAVE_UTIME_H)
# include <utime.h>
#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 <sys/timeb.h>
#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(&gtm, ltm, sizeof(gtm));
    ltm = localtime(&sometime);
    tzinfo = get_time_zone(&gtm, 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 *) &times, '\0', sizeof(times));
    cur_time = time((time_t *) NULL);
    times.actime  = atime ? atime : cur_time;
    times.modtime = mtime ? mtime : cur_time;
    return utime(fname, &times);
}

/* 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:
 */


syntax highlighted by Code2HTML, v. 0.9.1