[comp.sources.unix] v20i025: Deliver, flexible email delivery system, Part03/04

rsalz@uunet.uu.net (Rich Salz) (10/16/89)

Submitted-by: Chip Salzenberg <chip@ateng.com>
Posting-number: Volume 20, Issue 25
Archive-name: deliver2.0/part03

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  debug.c dest.c dfile.c lock.c main.c
# Wrapped by network@ateng on Fri Jun  9 13:21:01 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'debug.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'debug.c'\"
else
echo shar: Extracting \"'debug.c'\" \(1063 characters\)
sed "s/^X//" >'debug.c' <<'END_OF_FILE'
X/* $Header: debug.c,v 2.1 89/06/09 12:25:17 network Exp $
X *
X * Debugging output.
X *
X * $Log:	debug.c,v $
X * Revision 2.1  89/06/09  12:25:17  network
X * Update RCS revisions.
X * 
X * Revision 1.4  89/06/09  12:23:42  network
X * Baseline for 2.0 release.
X * 
X */
X
X#include "deliver.h"
X
X/*----------------------------------------------------------------------
X * Print out a complete dump of all destinations
X */
X
Xdumpdests(when)
Xchar    *when;
X{
X	DEST    *d;
X
X	message("Destinations %s:\n", when);
X	for (d = first_dest(); d; d = next_dest(d))
X	{
X		message("\t%s", d->d_name);
X
X		switch (d->d_class)
X		{
X		case CL_USER:
X			/* it's understood */
X			break;
X		case CL_MBOX:
X			message(", mailbox='%s'", d->d_mailbox);
X			break;
X		case CL_UUCP:
X			message(" (UUCP)");
X			break;
X		}
X		message("; ");
X		switch (d->d_state)
X		{
X		case ST_WORKING:
X			message("Working");
X			break;
X		case ST_HOLD:
X			message("Hold");
X			break;
X		case ST_DONE:
X			message("Done");
X			break;
X		case ST_ERROR:
X			message("Error (%s)", derrmsg(d->d_error));
X			break;
X		}
X		message("\n");
X	}
X}
END_OF_FILE
if test 1063 -ne `wc -c <'debug.c'`; then
    echo shar: \"'debug.c'\" unpacked with wrong size!
fi
# end of 'debug.c'
fi
if test -f 'dest.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'dest.c'\"
else
echo shar: Extracting \"'dest.c'\" \(2938 characters\)
sed "s/^X//" >'dest.c' <<'END_OF_FILE'
X/* $Header: dest.c,v 2.1 89/06/09 12:25:22 network Exp $
X *
X * Operations on the list of mail destinations.
X *
X * $Log:	dest.c,v $
X * Revision 2.1  89/06/09  12:25:22  network
X * Update RCS revisions.
X * 
X * Revision 1.5  89/06/09  12:23:45  network
X * Baseline for 2.0 release.
X * 
X */
X
X#include "deliver.h"
X
X/*
X * Local data.
X */
X
Xstatic  DEST    deadhead = { &deadhead, &deadhead };
X#define HEADPTR	(&deadhead)
X
X/*----------------------------------------------------------------------
X * Add a new destination to the list (unless it already exists).
X * Return pointer to DEST.
X */
X
XDEST *
Xdest(name, mailbox)
Xchar    *name;
Xchar    *mailbox;
X{
X	DEST    *d;
X	DCLASS   class;
X
X	if (strchr(name, '!'))
X		class = CL_UUCP;
X	else if (mailbox)
X		class = CL_MBOX;
X	else
X		class = CL_USER;
X
X	for (d = HEADPTR->d_next; d != HEADPTR; d = d->d_next)
X	{
X		if (d->d_class != class)
X			continue;
X
X		if (strcmp(d->d_name, name) != 0)
X			continue;
X
X		/*
X		 * If this destination has a named mailbox, then
X		 * test it for equality as well.
X		 */
X
X		if (class == CL_MBOX
X		 && strcmp(d->d_mailbox, mailbox) != 0)
X			continue;
X
X		/*
X		 * Like, gnarly, dude!  It's already in the chain!
X		 */
X
X		return d;
X	}
X
X	/*
X	 * The given dest isn't in the list, so we have to add it.
X	 */
X
X	d = (DEST *) zalloc(sizeof(DEST));
X	d->d_class = class;
X	d->d_state = ST_WORKING;
X	d->d_name = copystr(name);
X	if (class == CL_MBOX)
X		d->d_mailbox = copystr(mailbox);
X
X	/*
X	 * Check address for validity.
X	 */
X
X	if (!valid_address(name))
X		dest_err(d, E_IVADDR);
X	else if (class != CL_UUCP && name_context(name) == NULL)
X		dest_err(d, E_NSUSER);
X
X	/*
X	 * Put new address at the end of of the chain.
X	 * (This is important!  Other code depends on it.)
X	 */
X
X	d->d_prev = HEADPTR->d_prev;
X	d->d_next = HEADPTR;
X	d->d_prev->d_next = d;
X	d->d_next->d_prev = d;
X
X	return d;
X}
X
X/*----------------------------------------------------------------------
X * Return pointer to first DEST in the list.
X */
X
XDEST *
Xfirst_dest()
X{
X	if (HEADPTR->d_next != HEADPTR)
X		return HEADPTR->d_next;
X
X	return NULL;
X}
X
X/*----------------------------------------------------------------------
X * Return pointer to next DEST in the list, or NULL.
X */
X
XDEST *
Xnext_dest(d)
XDEST    *d;
X{
X	if (d && (d = d->d_next) != HEADPTR)
X		return d;
X
X	return NULL;
X}
X
X/*----------------------------------------------------------------------
X * Return an error message given a DERROR.
X */
X
Xchar *
Xderrmsg(e)
XDERROR  e;
X{
X	static  char    unknown_buf[40];
X
X	switch (e)
X	{
X	case E_IVADDR:
X		return "Invalid address string";
X	case E_NSUSER:
X		return "No such user";
X	case E_NSHOST:
X		return "No such host (UUCP addresses)";
X	case E_CTPERM:
X		return "No permissions for that context";
X	case E_CTLOST:
X		return "Context lost (should never happen)";
X	case E_MBOX:
X		return "Can't write to mailbox";
X	case E_UUX:
X		return "Can't pipe to uux";
X	}
X
X	(void) sprintf(unknown_buf, "Unknown error %d", e);
X	return unknown_buf;
X}
END_OF_FILE
if test 2938 -ne `wc -c <'dest.c'`; then
    echo shar: \"'dest.c'\" unpacked with wrong size!
fi
# end of 'dest.c'
fi
if test -f 'dfile.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'dfile.c'\"
else
echo shar: Extracting \"'dfile.c'\" \(8068 characters\)
sed "s/^X//" >'dfile.c' <<'END_OF_FILE'
X/* $Header: dfile.c,v 2.1 89/06/09 12:25:24 network Exp $
X *
X * Filter destination(s) through delivery file(s).
X *
X * $Log:	dfile.c,v $
X * Revision 2.1  89/06/09  12:25:24  network
X * Update RCS revisions.
X * 
X * Revision 1.10  89/06/09  12:23:49  network
X * Baseline for 2.0 release.
X * 
X */
X
X#include "deliver.h"
X#include <sys/stat.h>
X
X/*----------------------------------------------------------------------
X * Filter all valid destinations through the global delivery file.
X */
X
Xsys_dfile(dac, dav)
Xint     dac;
Xchar    **dav;
X{
X	char    **fav;
X	int     fac, a;
X	struct stat st;
X
X	/*
X	 * If there is no global delivery file, forget it.
X	 */
X
X	if (stat(sys_deliver, &st) == -1)
X	{
X		if (verbose)
X			message("no system delivery file\n");
X		return -1;
X	}
X
X	/*
X	 * If we've been asked not to run delivery files, forget it.
X	 */
X
X	if (!rundfiles)
X	{
X		if (verbose)
X			message("system delivery file disabled\n");
X		return -1;
X	}
X
X	/*
X	 * Collect the arguments for the delivery file.
X	 */
X
X	fav = (char **) zalloc((dac + 3) * sizeof(char **));
X	fav[0] = shell;
X	fav[1] = sys_deliver;
X	fac = 2;
X
X	for (a = 0; a < dac; ++a)
X	{
X		char    *addr;
X
X		addr = dav[a];
X		if (valid_address(addr))
X		{
X			/* Let the delivery file handle valid addresses. */
X
X			fav[fac++] = addr;
X		}
X		else
X		{
X			/* Note invalid address(es); report them later. */
X
X			(void) dest(addr, (char *) NULL);
X		}
X	}
X
X	fav[fac] = NULL;
X
X	/*
X	 * If there were any good names found, let loose the delivery
X	 * file.  Note the meaning of "good" is "well-formed", not "valid".
X	 * Thus the system delivery file has control over the handling of
X	 * all local deliveries, not just those to valid users.
X	 */
X
X	if (fac > 2)
X		(void) do_dfile(eff_ct, fav, (DEST *)NULL);
X
X	free((char *) fav);
X
X	return 0;
X}
X
X/*----------------------------------------------------------------------
X * Filter some undelivered destinations through the post-user
X * delivery file.
X */
X
Xpost_dfile()
X{
X	DEST    *d;
X	char    **fav;
X	int     num_dests, fac;
X	struct stat st;
X
X	/*
X	 * If there is no post-user delivery file, forget it.
X	 */
X
X	if (stat(post_deliver, &st) == -1)
X	{
X		if (verbose)
X			message("no post-user delivery file\n");
X		return -1;
X	}
X
X	/*
X	 * If we've been asked not to run delivery files, forget it.
X	 */
X
X	if (!rundfiles)
X	{
X		if (verbose)
X			message("post-user delivery file disabled\n");
X		return -1;
X	}
X
X	/*
X	 * Generate the delivery file argument list.
X	 */
X
X	num_dests = 0;
X	for (d = first_dest(); d; d = next_dest(d))
X		++num_dests;
X
X	fav = (char **) zalloc((num_dests + 3) * sizeof(char **));
X	fav[0] = shell;
X	fav[1] = post_deliver;
X	fac = 2;
X
X	for (d = first_dest(); d; d = next_dest(d))
X	{
X		if ((d->d_class == CL_USER || d->d_class == CL_UUCP)
X		 && (d->d_state == ST_WORKING
X		  || (d->d_state == ST_ERROR && d->d_error == E_NSUSER)))
X		{
X			fav[fac++] = d->d_name;
X			d->d_state = ST_HOLD;
X		}
X	}
X
X	fav[fac] = NULL;
X
X	if (fac > 2)
X		(void) do_dfile(eff_ct, fav, (DEST *)NULL);
X
X	free((char *) fav);
X
X	return 0;
X}
X
X/*----------------------------------------------------------------------
X * Filter all user destinations through their local delivery files.
X */
X
Xuser_dfiles()
X{
X	DEST    *d;
X	int     nfound;
X
X	/*
X	 * If we've been asked not to run delivery files, forget it.
X	 */
X
X	if (!rundfiles)
X	{
X		if (verbose)
X			message("user delivery files disabled\n");
X
X		return -1;
X	}
X
X
X	/*
X	 * Continue to loop through all addresses until no destination
X	 * that needs expanding can be found.
X	 */
X
X	do {
X		nfound = 0;
X		for (d = first_dest(); d; d = next_dest(d))
X		{
X			if (d->d_class == CL_USER
X			 && d->d_state == ST_WORKING
X			 && !d->d_dfdone)
X			{
X				one_dfile(d);
X				d->d_dfdone = TRUE;
X			}
X		}
X	} while (nfound > 0);
X
X	return 0;
X}
X
X/*----------------------------------------------------------------------
X * Run the delivery file (if any) for the specified destination.
X */
X
Xone_dfile(d)
XDEST    *d;
X{
X	CONTEXT *ct;
X	char    *fav[4];
X	char    udel_path[100];
X	struct stat st;
X
X	if ((ct = name_context(d->d_name)) == NULL)
X	{
X		dest_err(d, E_CTLOST);
X		return;
X	}
X
X	/*
X	 * If user's home directory is missing, forget it.
X	 * If user's home directory is writable to the world,
X	 * executing the delivery file would allow a security breach!
X	 * Thanks to Jon Zeeff for this hint...
X	 */
X
X	if (stat(ct->ct_home, &st) == -1
X	 || (st.st_mode & S_IFMT) != S_IFDIR)
X	{
X		if (verbose)
X			message("user %s: home directory %s is missing!\n",
X				ct->ct_name, ct->ct_home);
X		return;
X	}
X
X	if (st.st_mode & 02)
X	{
X		if (verbose)
X			message("user %s: home directory is writable to the world!\n",
X				ct->ct_name);
X		return;
X	}
X
X	/*
X	 * If there is no delivery file to execute, just return.
X	 */
X
X	(void) sprintf(udel_path, "%s/%s", ct->ct_home, user_deliver);
X	if (stat(udel_path, &st) == -1)
X	{
X		if (verbose)
X			message("%s has no delivery file\n", d->d_name);
X		return;
X	}
X
X	/*
X	 * Time to run the file!
X	 * We put this dest on hold, so that it will be ignored unless
X	 * the delivery file names it.
X	 */
X
X	d->d_state = ST_HOLD;
X
X	fav[0] = shell;
X	fav[1] = udel_path;
X	fav[2] = d->d_name;
X	fav[3] = NULL;
X	(void) do_dfile(ct, fav, d);
X}
X
X/*----------------------------------------------------------------------
X * Process a delivery file.
X */
X
Xint
Xdo_dfile(ct, av, d)
XCONTEXT *ct;
Xchar    **av;
XDEST    *d;
X{
X	FILE    *fp;
X	char    *name, *mailbox;
X
X	if (!ct)
X		return -1;
X
X	if (! ok_context(ct))
X	{
X		if (d)
X			dest_err(d, E_CTPERM);
X		else
X			message("No permissions to run as %s\n", ct->ct_name);
X
X		return -1;
X	}
X
X	/* Copy the temp files again */
X
X	if (copy_again() < 0)
X		return -1;
X
X	/* Allow the given user to own and read the copies */
X
X	if (give_temps(ct) < 0)
X		return -1;
X
X	/* Here we go! */
X
X	if (verbose)
X		message("Processing delivery file as %s\n", ct->ct_name);
X
X	if ((fp = ct_popenv(ct, shell, av, "r")) == NULL)
X	{
X		error("can't execute delivery file as %s\n", ct->ct_name);
X		return -1;
X	}
X
X	/*
X	 * Read the standard output of the delivery file.
X	 */
X
X	while (dfile_gets(fp, &name, &mailbox) >= 0)
X	{
X		DEST    *nd;
X
X		nd = dest(name, mailbox);
X		if (nd->d_state == ST_HOLD)
X			nd->d_state = ST_WORKING;
X
X		/*
X		 * If the delivery file specified a mailbox, verify
X		 * that the user whose delivery file is running has
X		 * permissions for the requested context.
X		 */
X
X		if ((nd->d_state == ST_WORKING) && (mailbox != NULL))
X		{
X			CONTEXT *nct;
X
X			if ((nct = name_context(name)) == NULL)
X				dest_err(nd, E_CTLOST);
X			else if (! ok_context(nct))
X				dest_err(nd, E_CTPERM);
X		}
X	}
X
X	return ct_pclose(fp);
X}
X
X/*----------------------------------------------------------------------
X * Get and parse a single delivery file output line.
X */
X
Xint
Xdfile_gets(fp, namep, mailboxp)
XFILE    *fp;
Xchar    **namep;
Xchar    **mailboxp;
X{
X	char    *p, *q;
X	static  char    buf[BUFSIZ];
X
X	if (fgets(buf, GETSIZE(buf), fp) == NULL)
X		return -1;
X
X	if ((p = strchr(buf, '\n')) != NULL)
X		*p = 0;
X	else
X	{
X		int c;
X
X		while ((c = fgetc(fp)) != '\n' && c != EOF)
X			; /* keep reading */
X
X		error("invalid line from delivery file: '%s'\n", buf);
X		return -1;
X	}
X
X	/* Strip out all whitespace and eliminate duplicated slashes */
X
X	p = q = buf;
X	while (*p)
X	{
X		if (isspace(*p))
X			++p;
X		else if ((*q++ = *p++) == '/')
X		{
X			while (*p == '/')
X				++p;
X		}
X	}
X	*q = 0;
X
X	/* Debugging message: display input line */
X
X	if (verbose)
X		message("\t'%s'\n", buf);
X
X	if ((p = strchr(buf, ':')) != NULL)
X	{
X		*p++ = 0;
X		if ((q = strchr(p, ':')) != NULL)
X			*q = 0;
X	}
X
X	*namep = buf;
X	*mailboxp = p;
X	return 0;
X}
X
X/*----------------------------------------------------------------------
X * Make the temp files readable in the given context.
X * This is needed because the temps are readable by owner only.
X */
X
Xint
Xgive_temps(ct)
XCONTEXT *ct;
X{
X	int     err, t;
X
X	if (!ct)
X		return -1;
X
X	err = 0;
X	for (t = T_HDRCOPY; t <= T_BODYCOPY; ++t)
X	{
X		if (chmod(tfile[t], 0600) == -1)
X		{
X			syserr("can't chmod %s", tfile[t]);
X			++err;
X		}
X		if (chown(tfile[t], ct->ct_uid, ct->ct_gid) == -1)
X		{
X			syserr("can't chown %s to %d/%d",
X				tfile[t], ct->ct_uid, ct->ct_gid);
X			++err;
X		}
X	}
X
X	return err ? -1 : 0;
X}
END_OF_FILE
if test 8068 -ne `wc -c <'dfile.c'`; then
    echo shar: \"'dfile.c'\" unpacked with wrong size!
fi
# end of 'dfile.c'
fi
if test -f 'lock.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lock.c'\"
else
echo shar: Extracting \"'lock.c'\" \(7155 characters\)
sed "s/^X//" >'lock.c' <<'END_OF_FILE'
X/* $Header: lock.c,v 2.1 89/06/09 12:25:30 network Exp $
X *
X * Mailbox locking.
X * Local hacks for mailbox access should be grafted here.
X *
X * $Log:	lock.c,v $
X * Revision 2.1  89/06/09  12:25:30  network
X * Update RCS revisions.
X * 
X * Revision 1.6  89/06/09  12:23:52  network
X * Baseline for 2.0 release.
X * 
X */
X
X#include "deliver.h"
X
X/*
X * Validate the locking configuration.
X */
X
X#if (defined(ML_FCNTL) + defined(ML_LOCKF) + defined(ML_LOCKING)) > 1
X  lose! "Only one of ML_FCNTL, ML_LOCKF and ML_LOCKING may be defined.";
X#endif
X
X/*
X * Support for the lockf() system call.
X */
X
X#ifdef ML_LOCKF
X#include <unistd.h>
X#define SIMPLE_LOCK "lockf"
X#define LOCKFD(fd, size)    lockf(fd, F_LOCK, size)
X#define UNLOCKFD(fd, size)  lockf(fd, F_ULOCK, size)
X#endif /* ML_LOCKF */
X
X/*
X * Setup for the locking() system call.
X */
X
X#ifdef ML_LOCKING
X#include <sys/locking.h>
X#define SIMPLE_LOCK "locking"
X#define LOCKFD(fd, size)    locking(fd, LK_LOCK, size)
X#define UNLOCKFD(fd, size)  locking(fd, LK_UNLOCK, size)
X#endif
X
X/*
X * Local functions.
X */
X
X#ifdef ML_DOTLOCK
Xstatic  char    *dotlock_name();
X#endif
X#ifdef ML_DOTMLK
Xstatic  char    *dotmlk_name();
X#endif
X
X/*----------------------------------------------------------------------
X * Lock a mailbox by name.
X *
X * This code looks quite hairy with all the ifdefs.  In fact, the only
X * somewhat strange thing here is that neither, either, or both of
X * ML_DOTLOCK and ML_DOTMLK may be defined, and we have to allow for it.
X */
X
Xint
Xname_lock(name)
Xchar    *name;
X{
X#ifdef ML_DOTLOCK
X	char    *dotlock;
X#endif
X#ifdef ML_DOTMLK
X	char    *dotmlk;
X#endif
X
X#ifdef ML_DOTLOCK
X	if ((dotlock = dotlock_name(name)) == NULL
X	 || create_lockfile(dotlock) < 0)
X		return -1;
X#endif /* ML_DOTLOCK */
X
X#ifdef ML_DOTMLK
X	if ((dotmlk = dotmlk_name(name)) == NULL
X	 || create_lockfile(dotmlk) < 0)
X	{
X#ifdef ML_DOTLOCK
X		(void) remove_lockfile(dotlock); /* don't leave me hanging */
X#endif
X		return -1;
X	}
X#endif /* ML_DOTMLK */
X
X	return 0;
X}
X
X/*----------------------------------------------------------------------
X * Unlock a mailbox by name.
X */
X
Xint
Xname_unlock(name)
Xchar    *name;
X{
X	int     ret = 0;
X
X#ifdef ML_DOTLOCK
X	char    *dotlock;
X#endif
X#ifdef ML_DOTMLK
X	char    *dotmlk;
X#endif
X
X#ifdef ML_DOTLOCK
X	if ((dotlock = dotlock_name(name)) == NULL
X	 || remove_lockfile(dotlock) < 0)
X		ret = -1;
X#endif /* ML_DOTLOCK */
X
X#ifdef ML_DOTMLK
X	if ((dotmlk = dotmlk_name(name)) == NULL
X	 || remove_lockfile(dotmlk) < 0)
X		ret = -1;
X#endif /* ML_DOTMLK */
X
X	return ret;
X}
X
X/*----------------------------------------------------------------------
X * Lock a file descriptor.
X */
X
Xint
Xfd_lock(fd)
Xint     fd;
X{
X#ifdef ML_FCNTL
X	struct flock fl;
X
X	fl.l_type = F_WRLCK;
X	fl.l_whence = 0;
X	fl.l_start = 0L;
X	fl.l_len = 0L;
X
X	if (fcntl(fd, F_SETLKW, &fl) == -1)
X	{
X		syserr("can't lock with fcntl()");
X		return -1;
X	}
X
X	if (verbose)
X		message("locked mailbox with fcntl()\n");
X#endif /* ML_FCNTL */
X
X#ifdef SIMPLE_LOCK
X	long    pos;
X
X	if ((pos = lseek(fd, 0L, 0)) == -1)
X	{
X		syserr("can't seek in mailbox");
X		return -1;
X	}
X	if (LOCKFD(fd, 0L) == -1)
X	{
X		syserr("can't lock with %s()", SIMPLE_LOCK);
X		return -1;
X	}
X	if (lseek(fd, pos, 0) == -1)
X	{
X		syserr("can't seek in mailbox");
X		return -1;
X	}
X
X	if (verbose)
X		message("locked mailbox with %s()\n", SIMPLE_LOCK);
X#endif /* SIMPLE_LOCK */
X
X	/* Default: success */
X	return 0;
X}
X
X/*----------------------------------------------------------------------
X * Unlock a file descriptor.
X */
X
Xint
Xfd_unlock(fd)
Xint     fd;
X{
X#ifdef ML_FCNTL
X	struct flock fl;
X
X	fl.l_type = F_UNLCK;
X	fl.l_whence = 0;
X	fl.l_start = 0L;
X	fl.l_len = 0L;
X
X	if (fcntl(fd, F_SETLKW, &fl) == -1)
X	{
X		syserr("can't unlock with fcntl()");
X		return -1;
X	}
X
X	if (verbose)
X		message("unlocked mailbox with fcntl()\n");
X#endif /* ML_FCNTL */
X
X#ifdef SIMPLE_LOCK
X	long    pos;
X
X	if ((pos = lseek(fd, 0L, 0)) == -1)
X	{
X		syserr("can't seek in mailbox");
X		return -1;
X	}
X	if (LOCKFD(fd, 0L) == -1)
X	{
X		syserr("can't unlock with %s()", SIMPLE_LOCK);
X		return -1;
X	}
X	if (lseek(fd, pos, 0) == -1)
X	{
X		syserr("can't seek in mailbox");
X		return -1;
X	}
X
X	if (verbose)
X		message("unlocked mailbox with %s()\n", SIMPLE_LOCK);
X#endif /* SIMPLE_LOCK */
X
X	/* Default: success */
X	return 0;
X}
X
X/*----------------------------------------------------------------------
X * Return the name of the appropriate ".lock" file for a mailbox.
X */
X
X#ifdef ML_DOTLOCK
X
Xstatic char *
Xdotlock_name(name)
Xchar    *name;
X{
X	static char *lname = NULL;
X	static int lsize = 0;
X	char    *p;
X	int     n, i;
X
X	n = strlen(name);
X	if (lsize < n + 8)
X	{
X		if (lname)
X			free(lname);
X		lsize = n + 32;
X		lname = zalloc(lsize);
X	}
X
X	(void) strcpy(lname, name);
X
X	/*
X	 * We want as much of `basename.lock' as will fit in a string
X	 * MAX_NAMESIZE long.
X	 */
X	for (i = 0, p = basename(lname); (i < MAX_NAMESIZE - 5) && (*p); ++i)
X		++p;
X	(void) strcpy(p, ".lock");
X
X	return lname;
X}
X
X#endif /* ML_DOTLOCK */
X
X/*----------------------------------------------------------------------
X * Return the name of the appropriate ".mlk" file for a mailbox.
X */
X
X#ifdef ML_DOTMLK
X
Xstatic char *
Xdotmlk_name(name)
Xchar    *name;
X{
X	static char lname[MAX_NAMESIZE + 16];
X	char    *p, *d;
X	int     i;
X
X	/*
X	 * To explain the below:  If we ass_u_me that MAX_NAMESIZE is 14,
X	 * then this code is like `printf(lname, "/tmp/%.10s.mlk", ...)'.
X	 * In other words, we want as much of `basename.mlk' as will fit
X	 * in a string MAX_NAMESIZE long.
X	 */
X	d = lname;
X	for (p = "/tmp/"; *p; )
X		*d++ = *p++;
X	for (i = 0, p = basename(name); (i < MAX_NAMESIZE - 4) && (*p); ++i)
X		*d++ = *p++;
X	(void) strcpy(d, ".mlk");
X
X	return lname;
X}
X
X#endif /* ML_DOTMLK */
X
X/*----------------------------------------------------------------------
X * Create a lockfile.
X */
X
Xint
Xcreate_lockfile(name)
Xchar    *name;
X{
X#ifndef O_CREAT
X	char    *othername, *p;
X#endif
X	int     fd, tries;
X
X#ifndef O_CREAT
X	othername = zalloc(strlen(name) + 20);  /* fudge (???) */
X	(void) strcpy(othername, name);
X	(void) sprintf(basename(othername), ".dl.%d", getpid());
X	if ((fd = creat(othername, 0)) == -1)
X	{
X		syserr("can't create %s", othername);
X		return -1;
X	}
X	(void) close(fd);
X	if (verbose)
X		message("created pre-lockfile %s\n", name);
X#endif
X
X	for (tries = 0; tries < 10; ++tries)
X	{
X		if (tries)
X			snooze(3);
X
X#ifdef O_CREAT
X
X		if ((fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0)) >= 0)
X		{
X			(void) close(fd);
X			if (verbose)
X				message("created lockfile %s\n", name);
X			return 0;
X		}
X
X#else /* not O_CREAT */
X
X		if (link(othername, name) == 0)
X		{
X			if (unlink(othername) == -1)
X				syserr("can't remove %s", othername);
X			free(othername);
X			if (verbose)
X				message("created lockfile %s\n", name);
X			return 0;
X		}
X
X#endif /* not O_CREAT */
X
X		if (verbose && (tries == 0))
X			message("Waiting to create %s\n", name);
X	}
X
X	syserr("can't create lockfile %s", name);
X	return -1;
X}
X
X/*----------------------------------------------------------------------
X * Remove a lockfile.
X */
X
Xint
Xremove_lockfile(name)
Xchar    *name;
X{
X	if (unlink(name) == -1)
X	{
X		syserr("can't remove lockfile %s", name);
X		return -1;
X	}
X
X	if (verbose)
X		message("removed lockfile %s\n", name);
X
X	return 0;
X}
END_OF_FILE
if test 7155 -ne `wc -c <'lock.c'`; then
    echo shar: \"'lock.c'\" unpacked with wrong size!
fi
# end of 'lock.c'
fi
if test -f 'main.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'main.c'\"
else
echo shar: Extracting \"'main.c'\" \(12025 characters\)
sed "s/^X//" >'main.c' <<'END_OF_FILE'
X/* $Header: main.c,v 2.1 89/06/09 12:25:32 network Exp $
X *
X * A program to deliver local mail with some flexibility.
X *
X * $Log:	main.c,v $
X * Revision 2.1  89/06/09  12:25:32  network
X * Update RCS revisions.
X * 
X * Revision 1.13  89/06/09  12:23:53  network
X * Baseline for 2.0 release.
X * 
X */
X
X#include "deliver.h"
X#include "patchlevel.h"
X#include <signal.h>
X
X/*
X * External data.
X */
X
X/* Variables set by getopt() [blech] */
X
Xextern  int     optind, opterr;
Xextern  char    *optarg;
X
X/*
X * Local data
X */
X
Xstatic  char    sys_dfl[] = SYS_DELIVER;
Xstatic  char    post_dfl[] = POST_DELIVER;
Xstatic  char    user_dfl[] = USER_DELIVER;
X
X/*
X * Global data
X */
X
Xint     verbose         = FALSE;
Xint     dryrun          = FALSE;
Xint     rundfiles       = TRUE;
Xint     printaddrs      = FALSE;
Xint     leavetemps      = FALSE;
Xint     boxdelivery     = FALSE;
X
Xchar    *progname       = "deliver";
Xchar    version[32]     = "2.0";
Xchar    *shell          = SHELL;
X
Xchar    *sys_deliver    = sys_dfl;
Xchar    *post_deliver   = post_dfl;
Xchar    *user_deliver   = user_dfl;
Xchar    *sender         = NULL;
Xchar    *hostname       = NULL;
X
Xint     eff_uid         = -1;
Xint     eff_gid         = -1;
Xint     real_uid        = -1;
Xint     real_gid        = -1;
X
XCONTEXT *eff_ct         = NULL;
XCONTEXT *real_ct        = NULL;
X
Xint     tty_input       = FALSE;
XSIGFLAG got_sig         = FALSE;
X
Xint     trust_user      = FALSE;
Xint     trust_delfiles  = FALSE;
X
Xchar    *ttype[T_MAX]   = { "header", "body", "header copy", "body copy" };
Xchar    *tfile[T_MAX]   = { NULL, NULL, NULL, NULL };
Xchar    *tenv[T_MAX]    = { NULL, NULL, ENV_HEADER, ENV_BODY };
Xint     tfd[T_MAX]      = { -1, -1, -1, -1 };
X
X/*
X * Local functions.
X */
X
Xstatic  SIGTYPE sighup(), sigint(), sigquit();
X
X/*----------------------------------------------------------------------
X * The Program.
X */
X
Xmain(argc, argv)
Xint     argc;
Xchar    **argv;
X{
X	char    *p;
X	int     u, c, errcount, copy;
X
X	/* Make sure that stdout and stderr are interleaved correctly */
X
X	Linebuf(stdout);
X	Linebuf(stderr);
X
X	/* Figure out the name used to invoke this program. */
X
X	progname = basename(argv[0]);
X
X	/* What version of the program is this? */
X
X	(void) sprintf(version + strlen(version), ".%02d", PATCHLEVEL);
X
X	/* Figure out the name of this host */
X
X	if ((hostname = gethost()) == NULL)
X	{
X		hostname = "unknown";
X		error("unable to determine host name; using \"%s\"\n",
X		      hostname);
X	}
X
X	/* Find effective and real uids and gids. */
X
X	eff_uid = geteuid();
X	eff_gid = getegid();
X	real_uid = getuid();
X	real_gid = getgid();
X
X	if (eff_uid != real_uid && eff_uid != 0)
X	{
X		error("if setuid, must be setuid root\n");
X		leave(1);
X	}
X
X	/* Process environment: handle recursive invocation */
X
X	if ((p = getenv(ENV_DFLAGS)) != NULL)
X	{
X		while (*p)
X		{
X			switch (*p++)
X			{
X			case 'v':
X				verbose = TRUE;
X				break;
X			case 'd':
X				verbose = TRUE;
X				dryrun = TRUE;
X				break;
X			case 'A':
X				printaddrs = TRUE;
X				dryrun = TRUE;
X				break;
X			case 'n':
X				rundfiles = FALSE;
X				break;
X			case 't':
X				leavetemps = TRUE;
X				break;
X			}
X		}
X	}
X
X	if ((p = getenv(ENV_SYSDEL)) != NULL && *p)
X		sys_deliver = p;
X	if ((p = getenv(ENV_POSTDEL)) != NULL && *p)
X		post_deliver = p;
X	if ((p = getenv(ENV_USERDEL)) != NULL && *p)
X		user_deliver = p;
X	if ((p = getenv(ENV_SENDER)) != NULL && *p)
X		sender = p;
X	if ((p = getenv(ENV_HOSTNAME)) != NULL && *p)
X		hostname = p;
X
X	/* Parse command line arguments */
X
X	while ((c = getopt(argc, argv, "vdAntbs:p:u:r:h:")) != EOF)
X	{
X		switch (c)
X		{
X		case 'v':
X			verbose = TRUE;
X			break;
X		case 'd':
X			verbose = TRUE;
X			dryrun = TRUE;
X			break;
X		case 'A':
X			printaddrs = TRUE;
X			dryrun = TRUE;
X			break;
X		case 'n':
X			rundfiles = FALSE;
X			break;
X		case 't':
X			leavetemps = TRUE;
X			break;
X		case 'b':
X			boxdelivery = TRUE;
X			break;
X		case 's':
X			if (*optarg)
X				sys_deliver = optarg;
X			break;
X		case 'p':
X			if (*optarg)
X				post_deliver = optarg;
X			break;
X		case 'u':
X			if (*optarg)
X				user_deliver = optarg;
X			break;
X		case 'r':
X			if (*optarg)
X				sender = optarg;
X			break;
X		case 'h':
X			if (*optarg)
X				hostname = optarg;
X			break;
X		case '?':
X			usage();
X		}
X	}
X
X	/* If no destinations were given, forget it. */
X
X	if (optind >= argc)
X	{
X		error("no recipients specified\n");
X		usage();
X	}
X
X	/* Print a debugging message */
X
X	if (verbose)
X	{
X		message("%s %s running on host %s\n",
X			progname, version, hostname);
X	}
X
X	/* Do we trust our caller? */
X
X	if (trusted_uid(real_uid))
X		trust_user = TRUE;
X
X	/* Do we trust our delivery files? */
X
X	if (strcmp(sys_dfl, sys_deliver) == 0
X	 && strcmp(post_dfl, post_deliver) == 0
X	 && strcmp(user_dfl, user_deliver) == 0)
X		trust_delfiles = TRUE;
X
X	/* Renounce special privileges if something insecure was requested. */
X
X	if (!trust_user && !trust_delfiles)
X	{
X		if (setgid(eff_gid = real_gid) == -1
X		 || setuid(eff_uid = real_uid) == -1)
X		{
X			syserr("can't renounce setuid privileges");
X			leave(1);
X		}
X	}
X
X	/* Get the contexts of our effective and real uids. */
X
X	if ((eff_ct = uid_context(eff_uid)) == NULL)
X		error("invalid effective uid %d!?\n", eff_uid);
X
X	if ((real_ct = uid_context(real_uid)) == NULL)
X		error("invalid real uid %d!?\n", real_uid);
X
X	if (!eff_ct || !real_ct)
X		leave(1);
X
X	if (verbose)
X	{
X		message("effective uid = %s (%d/%d); real uid = %s (%d/%d)\n",
X			eff_ct->ct_name, eff_ct->ct_uid, eff_ct->ct_gid,
X			real_ct->ct_name, real_ct->ct_uid, real_ct->ct_gid);
X	}
X
X	/* Let's be sane about the file creation mask. */
X
X	u = umask(0);
X	u &= ~0700;     /* Let's not deprive ourselves of permissions.  */
X	u |= 022;       /* Let's be reasonably paranoid about writing.  */
X	(void) umask(u);
X
X	/*
X	 * Where is the message coming from?
X	 */
X
X	if (isatty(0))
X		tty_input = TRUE;
X
X	/*
X	 * If we are not going to deliver, or if we are receiving the
X	 * message from a tty, catch signals so we can remove temp files.
X	 * Otherwise, ignore signals.
X	 */
X
X	if (dryrun || tty_input)
X		catch_sigs();
X	else
X		ignore_sigs();
X
X	/*
X	 * Create the temporary files and write the message to them.
X	 */
X
X	copy = copy_message();
X
X	/*
X	 * No more signals...
X	 */
X
X	ignore_sigs();
X
X	/*
X	 * ... but if we had already caught a signal,
X	 *     or if copy_msg() had a problem, leave.
X	 */
X
X	if ((copy < 0) || got_sig)
X	{
X		if (got_sig)
X			error("caught signal - exiting\n");
X		leave(1);
X	}
X
X	/*
X	 * Set up useful environment variables.
X	 * Note that this must be done _after_ copy_message(),
X	 * since that's where the temp files are created.
X	 */
X
X	setup_environ();
X
X	/*
X	 * Perhaps we should consider all arguments as mailbox names...
X	 */
X
X	if (boxdelivery)
X	{
X		int     a;
X
X		if (verbose)
X			message("mailbox delivery as %s\n", real_ct->ct_name);
X
X		/*
X		 * Consider all arguments as mailbox filenames.
X		 */
X
X		for (a = optind; a < argc; ++a)
X			(void) dest(real_ct->ct_name, argv[a]);
X
X		if (verbose)
X			dumpdests("(should all be mailboxes)");
X	}
X
X	/*
X	 * They're not mailbox names, so they should be mail addresses.
X	 */
X
X	else
X	{
X		/* Run all destinations though the system delivery file. */
X
X		if (sys_dfile(argc - optind, argv + optind) >= 0)
X		{
X			if (verbose)
X				dumpdests("after running system delivery file");
X		}
X		else
X		{
X			int     a;
X
X			/*
X			 * System delivery file is missing or ignored.
X			 * Use the argument list verbatim.
X			 */
X
X			for (a = optind; a < argc; ++a)
X				(void) dest(argv[a], (char *) NULL);
X
X			if (verbose)
X				dumpdests("as taken from argument list");
X		}
X
X		/*
X		 * Run each user destination through his delivery file.
X		 */
X
X		if (user_dfiles() >= 0)
X		{
X			if (verbose)
X				dumpdests("after running user delivery files");
X		}
X
X		/*
X		 * Run each remaining destination though the post-user
X		 * delivery file.
X		 */
X
X		if (post_dfile() >= 0)
X		{
X			if (verbose)
X				dumpdests("after running post-user delivery file");
X		}
X	}
X
X	/*
X	 * Drop mail in mailbox(es).
X	 */
X
X	mbox_deliver();
X
X	if (verbose)
X		dumpdests("after delivery to all mailboxes");
X
X	/*
X	 * Send mail to UUCP address(es).
X	 */
X
X	uucp_deliver();
X
X	if (verbose)
X		dumpdests("after delivery to UUCP addresses");
X
X	/*
X	 * Report any errors, and leave.
X	 */
X
X	errcount = report_errors();
X
X	/*
X	 * All done.
X	 */
X
X	leave(errcount ? 1 : 0);
X	/* NOTREACHED */
X}
X
X/*----------------------------------------------------------------------
X * Print a usage message and exit.
X */
X
Xusage()
X{
X	message("Usage: %s [-b][-A][-d][-v][-n][-t][-r from][-h host] args\n", progname);
X	message("-b       All arguments are mailbox filenames.\n");
X	message("         (Default: arguments are user names.)\n");
X	message("-A       Resolve addresses but do not deliver.\n");
X	message("-d       Be verbose but do not deliver.\n");
X	message("-v       Be verbose and deliver.\n");
X	message("-n       Do not run any delivery files.\n");
X	message("-t       Do not remote temp files before exiting.\n");
X	message("-s file  Specify the system delivery filename.\n");
X	message("-p file  Specify the post-user delivery filename.\n");
X	message("-u file  Specify the user delivery filename.\n");
X	message("-r from  Specify the address to appear in the \"From \" line.\n");
X	message("-h host  Specify the host name.\n");
X	message("args     Either user addresses or mailboxes (-b).\n");
X	leave(1);
X}
X
X/*----------------------------------------------------------------------
X * Clean up and exit.
X */
X
Xleave(code)
Xint     code;
X{
X	if (! leavetemps)
X	{
X		int     t;
X
X		for (t = 0; t < T_MAX; ++t)
X		{
X			if (tfd[t] != -1)
X				(void) close(tfd[t]);
X			if (tfile[t] && unlink(tfile[t]) == -1)
X				syserr("can't unlink %s", tfile[t]);
X		}
X	}
X
X	exit(code);
X}
X
X/*----------------------------------------------------------------------
X * Catch signals.
X */
X
Xcatch_sigs()
X{
X	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
X		(void) signal(SIGHUP, sighup);
X	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
X		(void) signal(SIGINT, sigint);
X	if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
X		(void) signal(SIGQUIT, sigquit);
X}
X
X/*----------------------------------------------------------------------
X * Ignore signals.
X */
X
Xignore_sigs()
X{
X	(void) signal(SIGHUP, SIG_IGN);
X	(void) signal(SIGINT, SIG_IGN);
X	(void) signal(SIGQUIT, SIG_IGN);
X}
X
Xstatic SIGTYPE
Xsighup()
X{
X	(void) signal(SIGHUP, sighup);
X	got_sig = TRUE;
X}
X
Xstatic SIGTYPE
Xsigint()
X{
X	(void) signal(SIGINT, sigint);
X	got_sig = TRUE;
X}
X
Xstatic SIGTYPE
Xsigquit()
X{
X	(void) signal(SIGQUIT, sigquit);
X	got_sig = TRUE;
X}
X
X/*----------------------------------------------------------------------
X * Report any errors to stderr.
X * Return an error count.
X */
X
Xint
Xreport_errors()
X{
X	DEST    *d;
X	int     count = 0;
X
X	for (d = first_dest(); d; d = next_dest(d))
X	{
X		if (d->d_state != ST_ERROR)
X			continue;
X
X		if (++count == 1)
X		{
X			error(
X		    "delivery to the following address(es) failed on host %s\n",
X				hostname);
X		}
X
X		message("\t\"%s\"", d->d_name);
X		if (d->d_class == CL_MBOX)
X			message(", mailbox \"%s\"", d->d_mailbox);
X		message(": %s\n", derrmsg(d->d_error));
X	}
X
X	return count;
X}
X
X/*----------------------------------------------------------------------
X * Is the given uid trusted?
X */
X
Xint
Xtrusted_uid(uid)
Xint     uid;
X{
X	CONTEXT *ct;
X	char    **n;
X	static char *t[] = { TRUSTED_USERS, 0 };
X
X	for (n = t; *n; ++n)
X	{
X		if ((ct = name_context(*n)) != NULL && uid == ct->ct_uid)
X			return TRUE;
X	}
X
X	return FALSE;
X}
X
X/*----------------------------------------------------------------------
X * Set up useful environment variables.
X */
X
Xsetup_environ()
X{
X	char    flags[8];
X	int     f = 0;
X
X	flags[f++] = '-';
X	if (verbose)
X		flags[f++] = (dryrun ? 'd' : 'v');
X	if (printaddrs)
X		flags[f++] = 'A';
X	if (leavetemps)
X		flags[f++] = 't';
X	flags[f] = 0;
X
X	alloc_env(ENV_DFLAGS, (f > 1) ? flags : "");
X	if (sys_deliver && *sys_deliver)
X		alloc_env(ENV_SYSDEL, sys_deliver);
X	if (user_deliver && *user_deliver)
X		alloc_env(ENV_USERDEL, user_deliver);
X	if (hostname && *hostname)
X		alloc_env(ENV_HOSTNAME, hostname);
X	if (sender && *sender)
X		alloc_env(ENV_SENDER, sender);
X
X	alloc_env("IFS", " \t\n");
X	del_env("ENV");         /* in case SHELL is ksh */
X}
END_OF_FILE
if test 12025 -ne `wc -c <'main.c'`; then
    echo shar: \"'main.c'\" unpacked with wrong size!
fi
# end of 'main.c'
fi
echo shar: End of shell archive.
exit 0


-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.