/*
#ident	"@(#)smail/src:RELEASE-3_2_0_112:sysdep.c,v 1.82 2000/09/29 21:20:08 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, hstrerror
 */
#define NEED_SOCKETS
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include "defs.h"
#include "smail.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "child.h"
#include "alloc.h"
#include "dys.h"
#include "log.h"
#include "route.h"
#include "bindlib.h"
#include "exitcodes.h"

#ifndef DEPEND
# include "extern.h"
# include "debug.h"
#endif

#if	defined(UNIX_SYS5) || defined(INCLUDE_SYS_PARAM)
# include <sys/param.h>
#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 HAVE_STDLIB_H
# include <stdlib.h>
#endif
#if defined(POSIX_OS)
# include <limits.h>
# include <unistd.h>
#else /* not POSIX_OS */
# if defined (HAVE_LIMITS_H)
#  include <limits.h>
# endif
# if defined (HAVE_UNISTD_H)
#  include <unistd.h>
# endif
#endif	/* not POSIX_OS */

#ifndef STDERR_FILENO
# define STDIN_FILENO	0	/* standard input file descriptor */
#endif
#ifndef STDOUT_FILENO
# define STDOUT_FILENO	1	/* standard output file descriptor */
#endif
#ifndef STDERR_FILENO
# define STDERR_FILENO	2	/* standard error file descriptor */
#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(UNIX_BSD)) && !defined(ISC_SOCKET_TIME_BUG)
# include <time.h>
#else
# include <sys/time.h>
#endif

#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

#if defined(INCLUDE_VFORK_H)
# include <vfork.h>
#endif

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

#if (defined(UNIX_BSD) || defined(WAIT_USE_UNION)) && !defined(NO_WAIT_USE_UNION)
# define STATUS_TYPE	union wait
#else
# define STATUS_TYPE	int
#endif

/* XXX maybe these should each be wrapped with #ifndef ? */
#if !defined(UNIX_BSD) && !defined(WAIT_USE_UNION) && !defined(POSIX_OS)
# ifdef UNIX_AIX3				/* XXX is this true for non-rs6k? */
#  define WIFSTOPPED(w)	((w) & 0100)		/* borrowed from GNU GDB */
# else
#  define WIFSTOPPED(w)	(((w) & 0377) == 0177)
# endif
# define WIFSIGNALED(w)	(((w) & 0377) != 0177 && ((w) & ~0377) == 0)
# define WIFEXITED(w)	(((w) & 0377) == 0)

# define WEXITSTATUS(w)	(((w) >> 8) & 0377)
# define WRETCODE(w)	WEXITSTATUS(w)
# define WSTOPSIG(w)	(((w) >> 8) & 0377)
# define WTERMSIG(w)	((w) & 0x177)
#endif
#if !defined(WCOREDUMP)
#  define WCOREDUMP(w)	(((w) & 0200) != 0)	/* not required by POSIX.1 */
#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

#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 passwd *getpwnam();
extern struct passwd *getpwuid();
extern struct tm *gmtime();
extern struct tm *localtime();
#endif

#ifndef __STDC__
/* variables imported from libc */
extern int errno;
#endif

/* define these, if they aren't already */
#ifndef O_RDONLY
# define O_RDONLY	0
#endif
#ifndef	O_WRONLY
# define O_WRONLY	1
#endif
#ifndef O_RDWR
# define O_RDWR		2
#endif

/* whence values for lseek(2) */
#ifndef SEEK_SET
# define SEEK_SET	0	/* set file offset to offset */
#endif
#ifndef SEEK_CUR
# define SEEK_CUR	1	/* set file offset to current plus offset */
#endif
#ifndef SEEK_END
# define SEEK_END	2	/* set file offset to EOF plus offset */
#endif

/* mode values for access(2) */
#ifndef F_OK
# define F_OK		0	/* does file exist */
#endif
#ifndef X_OK
# define X_OK		1	/* is it executable by caller */
#endif
#ifndef W_OK
# define W_OK		2	/* is it writable by caller */
#endif
#ifndef R_OK
# define R_OK		4	/* is it readable by caller */
#endif



/*
 * time_stamp - return a time stamp string in the form:
 *
 *	mm/dd/yyyy hh:mm:ss
 */
char *
time_stamp()
{
    time_t curtime = time((time_t *) NULL);
    struct tm *ltm = localtime(&curtime);
    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);
    return timebuf;
}

/*
 * strings used by the date and time routines below
 */
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_local_year - get the year according to local time
 */
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);
}

/*
 * get_arpa_date - return date in RFC822 format
 */
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
 */
/* 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.
 */
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_local_sender - compute the user running this program.
 */
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 = getpwnam(p);
	    if (pw != NULL && pw->pw_uid == real_uid) {
		local_sender = xmalloc(strlen(p) + 1);
		(void) strcpy(local_sender, p);
		return;
	    }
	}
    }

    if ((pw = getpwuid(real_uid)) != NULL) {
	local_sender = xmalloc((unsigned)(strlen(pw->pw_name) + 1));
	(void) strcpy(local_sender, pw->pw_name);
	return;
    }

    local_sender = "postmaster";
    return;
}

/*
 * getfullname - get the full name of the sender
 *
 * The full name comes from the GCOS field in the password entry.
 */
void
getfullname()
{
    struct passwd *pw;

    if (islocal) {
	/* only possible for local senders */
	pw = getpwnam(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
 */
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(*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, uppercase((unsigned char) sender[0]));
	    STR_CAT(sp, sender+1);
	} else {
	    STR_NEXT(sp, *gecos);
	}
	gecos++;
    }
    STR_NEXT(sp, '\0');
    STR_DONE(sp);
    sender_name = sp->p;
}

/*
 * 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? */
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") */
    unsigned int uid;			/* user id */
    unsigned int gid;			/* group id */
    unsigned int mode;			/* mode for creat() */
{
    FILE *fp;				/* opened file */
    int fd = EOF;			/* file descriptor for opened file */
    int cfd = EOF;			/* file descriptor for file in child */
    int 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, %d, %d, 0%o) called\n",
	   fn, type, uid, 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 %d).\n",
			       gid, strerror(errno), uid);
			exit(errno + 1);
			/* NOTREACHED */
		    }
		}
		if (setuid(uid) != 0) {
		    DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child setuid(%d): %s.\n",
			   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 %d: %s.\n",
			       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 %d.\n",
			   pid, WTERMSIG(status));
		    errno = EINTR;
		    return NULL;
		} else if (WIFSTOPPED(status)) {
		    DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child[%d] stopped %d.\n",
			   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(%d): %s (uid=%d now, to become %d).\n",
			   gid, strerror(errno), getuid(), uid);
		    exit(errno + 1);
		    /* NOTREACHED */
		}
	    }
	    if (setuid(uid) != 0) {
		DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child setuid(%d): %s.\n",
		       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) {
		    DEBUG2(DBG_DRIVER_MID, "fopen_as_user(): test child wait() for %d: %s.\n",
			   pid, strerror(errno));
		    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);
		    errno = WEXITSTATUS(status) - 1;
		    return NULL;
		}
	    } else if (WIFSIGNALED(status)) {
		DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child[%d] signalled %d.\n",
		       pid, WTERMSIG(status));
		errno = EINTR;
		return NULL;
	    } else if (WIFSTOPPED(status)) {
		DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child[%d] stopped %d.\n",
		       pid, WSTOPSIG(status));
		errno = EINTR;
		return NULL;
	    }
        }	
    }
    if (!(fp = fdopen(fd, type))) {
	DEBUG3(DBG_DRIVER_LO, "fopen_as_user(): fdopen(%s, %s): %s.\n",
	       fn, type, strerror(errno));
	return NULL;
    }

    DEBUG5(DBG_DRIVER_HI, "fopen_as_user(%s, %s, %d, %d, 0%o) returning\n",
	   fn, type, uid, 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, "%lu", (long unsigned 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[50];			/* for contents of lock file (a PID) */
    int ct;
    int fd;
    int pid = 0;
    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, "%d", &pid);
	    /* see if the pid exists */
	    if ((kill(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((unsigned int) ((p - fn) + 1));
	    (void) memcpy(dir, fn, (unsigned int) (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
    int len;
# endif

    /*
     * My man page says that 255 chars (plus nul byte) is the limit
     * on length of the local host name.  There appears to be no
     * #define for it in 4.2BSD.
     */
    hostname = xmalloc(256);
# ifdef GETHOSTNAME_USE_PTR
    len = 256;
    (void) gethostname(hostname, &len);
# else
    (void) gethostname(hostname, 256);
# endif

    hostname = xrealloc(hostname, 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;
{
    char * s = NULL;
    char * p;
#ifndef NO_USEGETHOSTBYNAME
    struct hostent *host_info;
    char * * ptr;
#endif /* NO_USEGETHOSTBYNAME */

#ifdef HAVE_BIND
    s = bind_compute_domain();
#endif /* HAVE_BIND */

#ifndef NO_USEGETHOSTBYNAME
    if (!s || !*s) {
	if ((host_info = gethostbyname(hostname))) {
	    /* find the first alias with a '.' in it */
	    for (ptr = host_info->h_aliases, p = *ptr; (*ptr); p = *++ptr) {
		if ((s = strchr(*ptr, '.')) && 
		    (*s++ = '\0', EQIC(*ptr, hostname))) {
		    s = COPY_STRING(p);
		    break;
		}
	    }
	}
    }
#endif /* NO_USEGETHOSTBYNAME */

    /* 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.  In this case as well,
 *		  never to a vfork(), always use fork().
 *	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 thre 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 sepecifies 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.
 */
int
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 */
    int flags;				/* flags affecting operation */
    unsigned int uid;			/* user ID for child process */
    unsigned int 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 */
    int pid;				/* pid from fork() */
    int oerrno;				/* errno from execv*() */
    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 (!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, %d, %d) called\n",
	   argv[0], errfd >= 0 ? "errlog" : "noerrlog", flags, uid, 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 fork until we succeed or retries == 0 */
    while (
#if (defined(UNIX_BSD) || defined(HAVE_VFORK)) && !defined(DONT_USE_VFORK)
	   (pid = vfork()) < 0 &&
#else	/* not UNIX_BSD && not HAVE_VFORK */
	   (pid = fork()) < 0 &&
#endif	/* UNIX_BSD || HAVE_VFORK */
	   errno == EAGAIN &&
	   --retries >= 0)
    {
	(void) sleep(FORK_INTERVAL);	/* sleep between retries */
    }
    if (pid < 0) {
	write_log(WRITE_LOG_TTY | 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_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_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);
	    }
	}

	/* close all unnecessary file descriptors */
	if ( !(flags & CHILD_NOCLOSE) ) {
	    close_all();
	}

	/* change uid and gid */
	if (setgid(gid) == -1) {
	    write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_PANIC : 0),
		      "open_child(%s): setgid(%d) failed (possible local configuration error): %s", argv[0], gid, strerror(errno));
	    _exit(EX_NOPERM);
	}
	if (setuid(uid) == -1) {
	    write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_PANIC : 0),
		      "open_child(%s): setuid(%d) failed (possible local configuration error): %s", argv[0], uid, strerror(errno));
	    _exit(EX_NOPERM);
	}

	/* execute the program */
	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_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 handle
 *	  the performing the waits 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 */
    int 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 POSIX_OS
    max_nfd = sysconf(_SC_OPEN_MAX);
#else	/* not POSIX_OS */
# 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
#endif

    for (i = 3; i  < max_nfd; i++)
	(void) close(i);
}

#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)
#ifdef ANSI_C
    const char *from;			/* old file name */
    const char *to;			/* new file name */
#else
    char *from;				/* old file name */
    char *to;				/* new file name */
#endif
{
    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 */
    int oumask = umask((~mode)&0777);
    int 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_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;

    bzero((char *)&times, sizeof(times));
    cur_time = time(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 UNUSED ifndef F_FREESP */
{
    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, "", 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;
}

#ifdef NEED_HSTRERROR
const char *
hstrerror(h_err)
    int h_err;
{
    /* XXX Should use h_errlist if it's available */
    if (h_err < 0)
	return "Resolver internal error";
    switch (h_err) {
    case 0:
	return "Resolver Error 0 (no error)";

    case HOST_NOT_FOUND:
	return "Unknown host";

    case TRY_AGAIN:
	return "Hostname lookup failure";

    case NO_RECOVERY:
	return "Unknown server error";

    case NO_DATA:
	return "No DNS data associated with name";

#if defined(NO_ADDRESS) && ((NO_ADDRESS - 0) != (NO_DATA))
    case NO_ADDRESS:
	return "No address associated with name";
#endif

    default:
	return "Unknown resolver error";
    }
    /* NOTREACHED */
}
#endif
