/***************************************************************************/
/*  Module:      $Id: deliver.c,v 1.5 1995/08/28 02:00:34 maf Exp $
/*  Description: message Delivery functions for sendpage
/*  Author:      maf
/*
/* Copyright (c) 1995 Mark Fullmer and The Ohio State University
/***************************************************************************/

/*
$Log: deliver.c,v $
 * Revision 1.5  1995/08/28  02:00:34  maf
 * QueueTime thing, VERBOSE_MAIL_REPLY
 *
 * Revision 1.4  1995/05/24  00:47:24  maf
 * all unknown string sizes use %.512s for report()
 *
 * Revision 1.3  1995/03/17  04:15:23  maf
 * added error message for STATUS_EXPIRED
 *
 * Revision 1.2  1995/03/15  04:40:52  maf
 * *** empty log message ***
 *
 * Revision 1.1  1995/01/10  01:42:52  maf
 * Initial revision
 *
*/


#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <syslog.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <db.h>
#include <sys/uio.h>
#include "report.h"
#include "sendpage.h"

#ifdef NEED_MALLOC_H
#include <malloc.h>
#endif

/*********************************************************************/
/* Function: Deliver()
/*	upper most layer of delivery code.
/*
/*	Returns 0	good
/*			!0	bad
/*
/*	Implements code to handle signals, reads alias database, starts
/*	delivery run when appropiate
/*
/*	This will most likely return either on a SIGQUIT (clean exit)
/*	in which case the return code will be 0, or if an error occurs
/*	such as starting the daemon with an error on the config file.
/*
/*	errors are reported via report()
/*
/*********************************************************************/
Deliver()
{
	u_int   ncentral;
	DB *aliasdb, *pcdb;
	extern int need_readconfig, need_runqueue, need_quit;
	extern int debug, errno;
	int err, rerun, have_config;
	

	aliasdb = pcdb = (DB*)0;
	err = 1; /* bad */
	have_config = 0;	/* don't have a config struct read */

	for (;;) {

		/* quit? */
		if (need_quit) {
			report (LOG_INFO, "sendpage exiting on SIGQUIT");
			err = 0;
			goto Deliverout;
		}

		/* read config file (aliases, etc) */
		if (need_readconfig) {

			need_readconfig = 0;

			if (ReadConfig(&aliasdb, &pcdb, &ncentral, PATH_SENDPAGE_CONFIG)) {
				report (LOG_ERR, "ReadConfig() failed, exiting");
				if (!have_config)
					goto Deliverout;
				else
					report (LOG_INFO, "config file change ignored");
			} else
				have_config = 1;
		}
		

		/* run the queue */
		if (need_runqueue) {

			need_runqueue = 0;

			if (QueueRun(aliasdb, pcdb, ncentral, &rerun)) {
				report (LOG_ERR, "QueueRun() failed, exiting");
				goto Deliverout;
			}
		}

		/* if need to re-run the queue because something couldn't be
		delivered, sleep and try again else, sleep the longer QUEUE_CHECKTIME.

		This is not relying on the USR1 signal to wake us up, which may
		not be necessary, but this is safer */

		/* if we got the signal... */
		if (need_runqueue)
			continue;

		if (rerun) {
#ifdef DEBUG
			if (debug > 1)
				report (LOG_INFO, "sleeping for %u", (u_int)QUEUE_RUNTIME);
#endif /* DEBUG */
			alarm((u_int)QUEUE_RUNTIME);
			pause();
		}
		else {
#ifdef DEBUG
			if (debug > 1)
				report (LOG_INFO, "sleeping for %u", (u_int)QUEUE_CHECKTIME);
#endif /* DEBUG */
			alarm((u_int)QUEUE_CHECKTIME);
			pause();
		}
		alarm((u_int)0);

	} /* forever */

Deliverout:

	if (aliasdb)
		err |= aliasdb->close(aliasdb);
	if (pcdb)
		err |= pcdb->close(pcdb);

	return err;

} /* Deliver */

/*********************************************************************/
/* Function: DeliverMAIL()
/*	sends mail via sendmail
/*
/*		Returns	0	good
/*				!0	bad
/*
/*	args:
/*		to		recipient
/*		qfname	queue file name
/*		qbuf	pointer to queue entry
/*
/*	This creates a pipe, forks a child sendmail process, and writes
/*	an appropiate message based upon the contents of the queue entry, 
/*	normally looking at the status bits.
/*
/*	Returning good doesn't necessarly mean the mail was sent, although
/*	it does mean an error wasn't detected.
/*
/*	errors are reported via report()
/*
/*********************************************************************/
DeliverMAIL(to, qfname, qbuf)
char *to, *qfname, *qbuf;
{

	pid_t	pid;
	extern int errno;
	int err, pipefd[2], statloc;
	FILE *FP;
	char *recipient, *recipient2, *sender, *message, *statusline;
	struct tm *ptm, tm, tm2;
	struct messagequeue *entry;
	extern int got_sigpipe;
	extern char *versionID;
	extern char *sendpage_env[];
	char *c;

	err = 1;
	FP = (FILE*)NULL;
	pipefd[0] = pipefd[1] = -1;
	got_sigpipe = 0;

	if (!to || !to[0]) {
		report (LOG_INFO, "DeliverMAIL: no recipient");
		return 0;
	}

#ifdef PARANOID_REPLIES
	for (c = to; *c; ++c) 

		if (*c == '|' || *c == '/') {
			report (LOG_WARNING,
				"DeliverMAIL: POSSIBLE BREAKIN ATTEMPT - %.512s, ignoring", to);
				goto DeliverMAILout;
		}

		/* like there aren't a million others that I can't test for */
		if (*c == '\n' || *c == '\r') {
			report (LOG_WARNING,
		"DeliverMAIL: ATTEMPT TO EXPLOIT OLD SENDMAIL BUG - %.512s, ignoring",
				to);
				goto DeliverMAILout;
		}

#endif /* PARANOID_REPLIES */

	if (pipe(pipefd) == -1) {
		report (LOG_ERR, "DeliverMAIL: pipe(): %s", strerror(errno));
		goto DeliverMAILout;
	}

	if ((pid = fork()) == -1) {
		report (LOG_ERR, "DeliverMAIL: fork(): %s", strerror(errno));
		goto DeliverMAILout;
	}

	if (pid) { /* parent */

 		/* close read end */
		if (close (pipefd[0])) {
			report (LOG_ERR, "pipe read close(): %s", strerror(errno));
			goto DeliverMAILout;
		}
		pipefd[0] = -1;
			
		/* associate a stream with the write end */
		if (!(FP = fdopen(pipefd[1], "w"))) {
			report (LOG_ERR, "can't fdopen() pipe");
			goto DeliverMAILout;
		}

		entry = (struct messagequeue*)qbuf;
		recipient = (char*)qbuf + sizeof(struct messagequeue);
		recipient2 = recipient + entry->recipientlen + 1;
		sender = recipient2 + entry->recipient2len + 1;
		message = sender + entry->senderlen + 1;

		if (!(ptm = localtime(&(entry->queuetime)))) {
			report(LOG_ERR, "localtime() failed");
			goto DeliverMAILout;
		} else 
			bcopy(ptm, &tm, sizeof(tm));

		if (!(ptm = localtime(&(entry->delivertime)))) {
			report(LOG_ERR, "localtime() failed");
			goto DeliverMAILout;
		} else
			bcopy(ptm, &tm2, sizeof(tm2));

		/* note STATUS_DELIVERED also set for errors, so check the
		/* error conditions first */
		if (entry->status & STATUS_EXPIRED)
			statusline = 
			"*** Message not delivered -- too many retries.";
		else if (entry->status & STATUS_NODELIVER1)
			statusline = 
			"*** Message not delivered because recipient could not be found.";
		else if (entry->status & STATUS_NODELIVER2)
			statusline = 
			"*** Message not delivered due to fatal internal error.";
		else if (entry->status & STATUS_NODELIVER3)
			statusline = 
			"*** Message not delivered Page Rejected";
		else if (entry->status & STATUS_MSGTRUNCATE)
			statusline = 
			"*** Message(s) not delivered due to administrative limit.";
		else if (entry->status & STATUS_DELIVERED)
			statusline =  "*** Page accepted.";
		else if (entry->status & STATUS_TMPFAIL) 
			statusline = 
				"*** Temporary error while delivering page, will keep trying.";
		else
			statusline = "*** unknown status, help!";

		/* Header lines */
		fprintf(FP, "From: %s\n", FROM_HEADER);
		fprintf(FP, "To: %s\n", to);
		fprintf(FP, "Subject: %s\n", statusline);
		fprintf(FP, "Precedence: bulk\n\n");

		/* Body lines */
#ifdef VERBOSE_MAIL_REPLY
		fprintf(FP, "QueueID:     %s\n", qfname);
#endif /* VERBOSE_MAIL_REPLY */
		fprintf(FP, "QueueTime:   %d/%d/%d %d:%02d:%02d\n", tm.tm_mon+1,
			tm.tm_mday, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
		if ((entry->status & STATUS_DELIVERED) && (!(entry->status &
				(STATUS_NODELIVER1|STATUS_NODELIVER2|STATUS_NODELIVER3|
				STATUS_MSGTRUNCATE))))
			fprintf(FP,
				"DeliverTime: %d/%d/%d %02d:%02d:%02d (%lu second delay)\n",
				tm2.tm_mon+1, tm2.tm_mday, tm2.tm_year+1900, tm2.tm_hour,
				tm2.tm_min, tm2.tm_sec, (u_long)((u_long)entry->delivertime -
				(u_long)entry->queuetime));
		fprintf(FP, "Retries:     %d\n", entry->retries);
#ifdef VERBOSE_MAIL_REPLY
		fprintf(FP, "Status:      0x%X\n", entry->status);
#endif /* VERBOSE_MAIL_REPLY */
		fprintf(FP, "Sender:      %s\n", sender);
		fprintf(FP, "Recipient:   %s\n", recipient);
		fprintf(FP, "Message:     %s\n\n", message);

		fprintf(FP, "%s\n\n", statusline);

		if (entry->status & STATUS_MSGTRUNCATE) {
			fprintf(FP,
"Note: any messages following and including this have been silently dropped.\n");
			fprintf(FP,
"  Each line is considered a separate page.  Lines before this will be\n");
			fprintf(FP, 
"  processed normally.  This limit is imposed to prevent accidental page\n");
			fprintf(FP, "  floods.\n\n");
		}

		if (entry->status & STATUS_SIZETRUNCATE) 
			fprintf(FP,
"Note: message size reduced to conform to paging central configuration\n\n");

		fprintf(FP, "-- \nsendpage v. %s\n\n", versionID);

		if (fclose(FP)) {
			report (LOG_ERR, "pipe stream fclose() failed");
			goto DeliverMAILout;
		}
		FP = (FILE*)NULL;
		pipefd[1] = -1; 

		/* no zombies */
		if (waitpid(pid, &statloc, 0) == -1) 
			report (LOG_ERR, "waitpid(): %s", strerror(errno));

		/* expect exit status 0 */
		if (statloc)
			report (LOG_WARNING, "sendmail child exited with status %d",
				statloc);

		/* could of got a sigpipe too */
		if (got_sigpipe)
			report (LOG_WARNING, "got SIGPIPE while sending mail");


	} /* parent */ else {

		/* child */

		if (close (pipefd[1])) {
			report (LOG_ERR, "child: pipe write close(): %s", strerror(errno));
			exit (1);
		}

		/* pipe becomes stdin */
		if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
			report (LOG_ERR, "child: dup2() to stdin failed");
			exit (1);
		}

		if (close (pipefd[0])) {
			report (LOG_ERR, "child: pipe read close(): %s", strerror(errno));
			exit (1);
		}
		pipefd[0] = -1;

		/* invoke sendmail  */
		/* -t get recipient from body */
		/* -o em set error mode to mail to sender */
		if (execle(PATH_SENDMAIL, "sendmail", "-t", "-oem", (char*)0L,
			sendpage_env) == -1) {
			report (LOG_ERR, "child: execl() sendmail: %s", strerror(errno));
			exit (1);
		} 
	} /* child */
		
	err = 0; /* good */

	report (LOG_INFO, "Mail sent to %.512s for %.512s", to, qfname);

DeliverMAILout:

	if (FP)
		err |= fclose(FP);

	if (pipefd[0] != -1)
		err |= close(pipefd[0]);

	if (pipefd[1] != -1)
		err |= close(pipefd[1]);

	return err;

}
