/* $Header: mbox.c,v 2.5 90/02/23 16:35:55 chip Exp $
 *
 * Finally!  Put the message in the specified mailbox(es).
 *
 * $Log:	mbox.c,v $
 * Revision 2.5  90/02/23  16:35:55  chip
 * \Fix problems determining legality of user references.
 * 
 * Revision 2.4  90/02/23  14:16:50  chip
 * Support "#!" in delivery files.
 * Support "user|program" and "user?error" from delivery files.
 * Improve debugging and error message formatting.
 * Rearrange code for clarity.
 * 
 * Revision 2.3  90/02/06  11:56:42  chip
 * Enforce MBX_MODE regardless of UMASK.
 * Enforce ordered logging with a log lockfile.
 * Revise log format.
 * 
 * Revision 2.2  89/11/01  11:51:05  network
 * Fix error code; unknown user is now reported correctly.
 * 
 * Revision 2.1  89/06/09  12:25:33  network
 * Update RCS revisions.
 * 
 * Revision 1.6  89/06/09  12:23:55  network
 * Baseline for 2.0 release.
 * 
 */

#include "deliver.h"
#include <sys/stat.h>
#include <errno.h>

/*
 * External data.
 */

extern  int     errno;

/*
 * Local functions.
 */

static		prog_one();
static          mbox_one();
static  int     mbox_write();

/*----------------------------------------------------------------------
 * Deliver mail to all valid program (pipe) destinations.
 * Return count of programs to which we tried to write.
 */

int
prog_deliver()
{
	DEST    *d;
	int	progcount;

	progcount = 0;
	for (d = first_dest(); d; d = next_dest(d))
	{
		if (d->d_state != ST_WORKING)
			continue;

		if (d->d_class == CL_PROG)
		{
			prog_one(d);
			++progcount;
		}
	}

	return progcount;
}

/*----------------------------------------------------------------------
 * Deliver mail to one program.
 */

static
prog_one(d)
DEST    *d;
{
	CONTEXT *ct;
	char	*av[4];
	int	fd;

	if (printaddrs)
		(void) printf("%s|%s\n", d->d_name, d->d_param);

	if (dryrun)
	{
		d->d_state = ST_DONE;
		return;
	}

	if ((ct = name_context(d->d_name)) == NULL)
	{
		dest_err(d, E_NSUSER);
		return;
	}

	if (! ok_context(eff_uid, real_uid, real_gid, ct))
	{
		dest_err(d, E_CTPERM);
		return;
	}

	av[0] = SHELL;
	av[1] = "-c";
	av[2] = d->d_param;
	av[3] = NULL;
	if ((fd = ct_openv(ct, av[0], av, O_WRONLY)) == -1)
	{
		dest_err(d, E_PIPE);
		return;
	}

	(void) lseek(tfd[T_HDR], 0L, 0);
	(void) lseek(tfd[T_BODY], 0L, 0);
	if (copyfd(tfd[T_HDR], fd) < 0 || copyfd(tfd[T_BODY], fd) < 0)
		dest_err(d, E_PIPE);

	if (ct_close(fd))
		dest_err(d, E_PROG);
	else
		d->d_state = ST_DONE;
}

/*----------------------------------------------------------------------
 * Deliver mail to all valid mailbox destinations.
 * Return count of mailboxes to which we tried to write.
 */

int
mbox_deliver()
{
	DEST    *d;
	int	mboxcount;

	mboxcount = 0;
	for (d = first_dest(); d; d = next_dest(d))
	{
		if (d->d_state != ST_WORKING)
			continue;

		if (d->d_class == CL_USER || d->d_class == CL_MBOX)
		{
			mbox_one(d);
			++mboxcount;
		}
	}

	return mboxcount;
}

/*----------------------------------------------------------------------
 * Deliver mail to one mailbox destination.
 */

static
mbox_one(d)
DEST    *d;
{
	CONTEXT *ct;
	int     ret = 0;

	if (printaddrs)
	{
		(void) printf("%s", d->d_name);
		if (d->d_class == CL_MBOX)
			(void) printf(":%s", d->d_param);
		(void) printf("\n");
	}

	if (dryrun)
	{
		d->d_state = ST_DONE;
		return;
	}

	if ((ct = name_context(d->d_name)) == NULL)
	{
		dest_err(d, E_NSUSER);
		return;
	}

	if (! ok_context(eff_uid, real_uid, real_gid, ct))
	{
		dest_err(d, E_CTPERM);
		return;
	}

	if (d->d_class == CL_MBOX)
	{
		if (sfork() == 0)
		{
			if (become(ct, !boxdelivery) < 0)
				exit(1);
			if (mbox_write(d->d_param, ct, FALSE) < 0)
				exit(1);
			exit(0);
		}

		if (await_child() != 0)
			ret = -1;
	}
	else
	{
		char    mailbox[100];

		(void) sprintf(mailbox, "%s/%s",
#ifdef MBX_DIR
			MBX_DIR, d->d_name
#else
			d->d_home, MBX_NAME
#endif
			);

		if (mbox_write(mailbox, ct, TRUE) < 0)
			ret = -1;
	}

	if (ret >= 0)
		d->d_state = ST_DONE;
	else
		dest_err(d, E_MBOX);
}

/*----------------------------------------------------------------------
 * Write mail to the named mailbox.
 * If we have to create the mailbox, give it to the specified user.
 * If "is_sys" is true, then we're writing to a system mailbox.
 */

static int
mbox_write(mailbox, ct, is_sys)
char    *mailbox;
CONTEXT *ct;
int     is_sys;
{
	struct stat st;
	int     fd, t, mbox_uid, mbox_gid;
	int     ret = 0;

	if (verbose)
	{
		message("As %s, delivering to %s mailbox %s\n",
			ct->ct_name, (is_sys ? "system" : "user"), mailbox);
	}

	if (name_lock(mailbox) < 0)
		return -1;

	while ((fd = open(mailbox, O_WRONLY)) == -1)
	{
		int	um;

		if (errno != ENOENT)
		{
			syserr("can't open %s", mailbox);
			break;
		}

		um = umask(0);    /* save old umask; set it to zero */
#ifdef O_CREAT
		fd = open(mailbox, O_WRONLY|O_CREAT|O_EXCL, MBX_MODE);
#else
		fd = creat(mailbox, MBX_MODE);
#endif
		(void) umask(um); /* restore umask; ass_u_me errno unchanged */

		if (fd == -1)
		{
			/* If the error is "file already there", try again. */
			if (errno == EEXIST)
				continue;

			/* Something is very wrong... */
			syserr("can't create %s", mailbox);
			break;
		}

		/* Make sure the mailbox receives the correct modes */

		mbox_uid = ct->ct_uid;
		mbox_gid = ct->ct_gid;

#ifdef MBX_GROUP
		if (is_sys)
		{
			static int mbox_sv_gid = -2;

			if (mbox_sv_gid == -2)
				mbox_sv_gid = group_id(MBX_GROUP);

			if (mbox_sv_gid < 0)
				message("%s: no such group\n", MBX_GROUP);
			else
				mbox_gid = mbox_sv_gid;
		}
#endif /* MBX_GROUP */

		if (fstat(fd, &st) == -1)
		{
			syserr("can't fstat open mailbox?!");
			(void) close(fd);
			fd = -1;
			break;
		}

		/* Change mailbox ownership if it's not already correct. */

		if ((st.st_uid != mbox_uid || st.st_gid != mbox_gid)
		 && chown(mailbox, mbox_uid, mbox_gid) == -1)
		{
			/* print a message, but that's all. (???) */
			syserr("can't chown %s to %d,%d",
				mailbox, mbox_uid, mbox_gid);
		}

		/* It's open now, so we can stop looping now. */

		break;
	}

	if (fd == -1)
	{
		(void) name_unlock(mailbox);
		return -1;
	}

	if (fd_lock(fd) < 0)
	{
		(void) close(fd);
		(void) name_unlock(mailbox);
		return -1;
	}

	(void) lseek(fd, 0L, 2); /* No error check: may be a special file */

	for (t = T_HDR; t <= T_BODY; ++t)
	{
		if (lseek(tfd[t], 0L, 0) == -1)
		{
			syserr("lseek in %s file %s", ttype[t], tfile[t]);
			ret = -1;
			break;
		}

		if (copyfd(tfd[t], fd) < 0)
		{
			ret = -1;
			break;
		}
	}

	if (verbose)
	{
		if (ret >= 0)
			message("wrote message to %s\n", mailbox);
	}

	if (fd_unlock(fd) < 0)
		ret = -1;
	(void) close(fd);
	if (name_unlock(mailbox) < 0)
		ret = -1;

	return ret;
}
