/*
**  Deliver.c
**
**  Routines to effect delivery of mail for rmail/smail. 
**
*/

#ifndef lint
static char 	*sccsid="@(#)deliver.c	2.5 (smail) 9/15/87";
#endif

# include	<stdio.h>
# include	<sys/types.h>
# include	<sys/stat.h>
# include	<ctype.h>
# include	<signal.h>
# include	"defs.h"

extern int  exitstat;		/* set if a forked mailer fails */
extern enum edebug debug;	/* how verbose we are 		*/ 
extern char hostname[];		/* our uucp hostname 		*/
extern char hostdomain[];	/* our host's domain 		*/
extern enum ehandle handle;	/* what we handle		*/
extern enum erouting routing;	/* how we're routing addresses  */
extern char *uuxargs;		/* arguments given to uux       */
extern int  queuecost;		/* threshold for queueing mail  */
extern int  maxnoqueue;		/* max number of uucico's       */
extern char *spoolfile;		/* file name of spooled message */
extern FILE *spoolfp;		/* file ptr  to spooled message */
extern int spoolmaster;		/* set if creator of spoolfile  */
extern char nows[];		/* local time in ctime(3) format*/
extern char arpanows[];		/* local time in arpadate format*/
char stderrfile[20];		/* error file for stderr traping*/

/*
**
**  deliver():  hand the letter to the proper mail programs.
**
**  Issues one command for each different host of <hostv>,
**  constructing the proper command for LOCAL or UUCP mail.
**  Note that LOCAL mail has blank host names.
**
**  The <userv> names for each host are arguments to the command.
** 
**  Prepends a "From" line to the letter just before going 
**  out, with a "remote from <hostname>" if it is a UUCP letter.
**
*/

deliver(argc, hostv, userv, formv, costv)
int argc;				/* number of addresses		*/
char *hostv[];				/* host names			*/
char *userv[];				/* user names			*/
enum eform formv[];			/* form for each address	*/
int costv[];				/* cost vector 			*/
{
	FILE *out;			/* pipe to mailer		*/
	FILE *popen();			/* to fork a mailer 		*/
#ifdef RECORD
	void record();			/* record all transactions	*/
#endif
#ifdef LOG
	void log();
#endif
	char *mktemp();
	char from[SMLBUF];		/* accumulated from argument 	*/
	char lcommand[SMLBUF];		/* local command issued 	*/
	char rcommand[SMLBUF];		/* remote command issued	*/
	char scommand[SMLBUF];		/* retry  command issued	*/
	char *command;			/* actual command		*/
	char buf[SMLBUF];		/* copying rest of the letter   */
	enum eform form;		/* holds form[i] for speed 	*/
	long size;			/* number of bytes of message 	*/
	char *flags;			/* flags for uux		*/
	char *sflag;			/* flag  for smail		*/
	int i, j, status, retrying;
	char *c, *postmaster();
	int failcount = 0;
	int noqcnt = 0;			/* number of uucico's started   */
	char *uux_noqueue = UUX_NOQUEUE;/* uucico starts immediately    */
	char *uux_queue   = UUX_QUEUE;	/* uucico job gets queued       */
	off_t message;
	struct stat st;

/*
** rewind the spool file and read the collapsed From_ line
*/
	(void) fseek(spoolfp, 0L, 0);
	(void) fgets(from, sizeof(from), spoolfp);
	if((c = index(from, '\n')) != 0) *c = '\0';
	message = ftell(spoolfp);

/*
**  We pass through the list of addresses.
*/
	stderrfile[0] = '\0';
	for(i = 0; i < argc; i++) {
		char *lend = lcommand;
		char *rend = rcommand;
		char *send = scommand;

/*
**  If we don't have sendmail, arrange to trap standard error
**  for inclusion in the message that is returned with failed mail.
*/
		(void) unlink(stderrfile);
		(void) strcpy(stderrfile, "/tmp/stderrXXXXXX");
		(void) mktemp(stderrfile);
		(void) freopen(stderrfile, "w", stderr);
		if(debug != YES) {
			(void) freopen(stderrfile, "w", stdout);
		}

		*lend = *rend = *send = '\0';

/*
**  If form == ERROR, the address was bad 
**  If form == SENT, it has been sent on a  previous pass.
*/
		form = formv[i];
		if (form == SENT) {
			continue;
		}
/*
**  Build the command based on whether this is local mail or uucp mail.
**  By default, don't allow more than 'maxnoqueue' uucico commands to
**  be started by a single invocation of 'smail'.
*/
		if(uuxargs == NULL) {	/* flags not set on command line */
			if(noqcnt < maxnoqueue && costv[i] <= queuecost) {
				flags = uux_noqueue;
			} else {
				flags = uux_queue;
			}
		} else {
			flags = uuxargs;
		}

		retrying = 0;
		if(routing == JUSTDOMAIN) {
			sflag = "-r";
		} else if(routing == ALWAYS) {
			sflag = "-R";
		} else {
			sflag = "";
		}

		(void) sprintf(lcommand, LMAIL(from, hostv[i]));
		(void) sprintf(rcommand, RMAIL(flags, from, hostv[i]));

/*
**  For each address with the same host name and form, append the user
**  name to the command line, and set form = ERROR so we skip this address
**  on later passes. 
*/
		/* we initialized lend (rend) to point at the
		 * beginning of its buffer, so that at
		 * least one address will be used regardless
		 * of the length of lcommand (rcommand).
		 */
		for (j = i; j < argc; j++) {
			if ((formv[j] != form)
			 || (strcmpic(hostv[i], hostv[j]) != 0)
			 || ((lend - lcommand) > MAXCLEN)
			 || ((rend - rcommand) > MAXCLEN)) {
				continue;
			}

			/*
			** seek to the end of scommand
			** and add on a 'smail' command
			** multiple commands are separated by ';'
			*/

			send += strlen(send);
			if(send != scommand) {
				*send++ = ';' ;
			}

			(void) sprintf(send, RETRY(sflag));
			send += strlen(send);

			lend += strlen(lend);
			rend += strlen(rend);

			if (form == LOCAL) {
				(void) sprintf(lend, LARG(userv[j]));
				(void) sprintf(send, LARG(userv[j]));
			} else {
				(void) sprintf(lend, RLARG(hostv[i], userv[j]));
				(void) sprintf(send, RLARG(hostv[i], userv[j]));
			}

			(void) sprintf(rend, RARG(userv[j]));
			formv[j] = SENT;
		}
retry:
/*
** rewind the spool file and read the collapsed From_ line
*/
		(void) fseek(spoolfp, message, 0);

		/* if the address was in a bogus form (usually DOMAIN),
		** then don't bother trying the uux.
		**
		** Rather, go straight to the next smail routing level.
		*/
		if(form == ERROR) {
			static char errbuf[SMLBUF];
			(void) sprintf(errbuf,
				"address resolution ('%s' @ '%s') failed",
					userv[i], hostv[i]);
			command = errbuf;
			size    = 0;
			goto form_error;
		}

		if (retrying) {
			command = scommand;
		} else if (form == LOCAL) {
			command = lcommand;
		} else {
			command = rcommand;
			if(flags == uux_noqueue) {
				noqcnt++;
			}
		}
		ADVISE("COMMAND: %s\n", command);

/*
** Fork the mailer and set it up for writing so we can send the mail to it,
** or for debugging divert the output to stdout.
*/

/*
** We may try to write on a broken pipe, if the uux'd host
** is unknown to us.  Ignore this signal, since we can use the
** return value of the pclose() as our indication of failure.
*/
		(void) signal(SIGPIPE, SIG_IGN);

		if (debug == YES) {
			out = stdout;
		} else {
			failcount = 0;
			do {
				out = popen(command, "w");
				if (out) break;
				/*
				 * Fork failed.  System probably overloaded.
				 * Wait awhile and try again 10 times.
				 * If it keeps failing, probably some
				 * other problem, like no uux or smail.
				 */
				(void) sleep(60);
			} while (++failcount < 10);
		}
		if(out == NULL) {
			exitstat = EX_UNAVAILABLE;
			(void) printf("couldn't execute %s.\n", command);
			continue;
		}

		size = 0;
		if(fstat(fileno(spoolfp), &st) >= 0) {
			size = st.st_size - message;
		}
/*
**  Output our From_ line.
*/
		if (form == LOCAL) {
#ifdef SENDMAIL
			(void) sprintf(buf, LFROM(from, nows, hostname));
			size += strlen(buf);
			(void) fputs(buf, out);
#else
			char *p;
			if((p=index(from, '!')) == NULL) {
				(void) sprintf(buf,
					LFROM(from, nows, hostname));
				size += strlen(buf);
				(void) fputs(buf, out);
			} else {
				*p = NULL;
				(void) sprintf(buf, RFROM(p+1, nows, from));
				size += strlen(buf);
				(void) fputs(buf, out);
				*p = '!';
			}
#endif
		} else {
			(void) sprintf(buf, RFROM(from, nows, hostname));
			size += strlen(buf);
			(void) fputs(buf, out);
		}

#ifdef SENDMAIL
/*
**  If using sendmail, insert a Received: line only for mail
**  that is being passed to uux.  If not using sendmail, always
**  insert the received line, since sendmail isn't there to do it.
*/
		if(command == rcommand && handle != ALL)
#endif
		{
			(void) sprintf(buf,
				"Received: by %s (%s)\n\tid AA%05d; %s\n",
					hostdomain, VERSION,
					getpid(), arpanows);
			size += strlen(buf);
			(void) fputs(buf, out);
		}

/*
**  Copy input.
*/
		while(fgets(buf, sizeof(buf), spoolfp) != NULL) {
			(void) fputs(buf, out);
		}
/*
**  Get exit status and if non-zero, set global exitstat so when we exit
**  we can indicate an error.
*/
form_error:
		if (debug != YES) {
			if(form == ERROR) {
				exitstat = EX_NOHOST;
			} else if (status = pclose(out)) {
				exitstat = status >> 8;
			}
			/*
			 * The 'retrying' check prevents a smail loop.
			 */
			if(exitstat != 0) {
				/*
				** the mail failed, probably because the host
				** being uux'ed isn't in L.sys or local user
				** is unknown.
				*/

				if((retrying == 0)	/* first pass */
				&& (routing != REROUTE)	/* have higher level */
				&& (form != LOCAL)) {	/* can't route local */
					/*
					** Try again using a higher
					** level of routing.
					*/
					ADVISE("%s failed (%d)\ntrying %s\n",
						command, exitstat, scommand);
					exitstat = 0;
					retrying = 1;
					form = SENT;
					goto retry;
				}

				/*
				** if we have no other routing possibilities
				** see that the mail is returned to sender.
				*/

				if((routing == REROUTE)
			        || (form == LOCAL)) {

					/*
					** if this was our last chance,
					** return the mail to the sender.
					*/

					ADVISE("%s failed (%d)\n",
						command, exitstat);
					
					(void) fseek(spoolfp, message, 0);
#ifdef SENDMAIL
					/* if we have sendmail, then it
					** was handed the mail, which failed.
					** sendmail returns the failed mail
					** for us, so we need not do it again.
					*/
					if(form != LOCAL)
#endif
					{
						return_mail(from, command);
					}
					exitstat = 0;
				}
			}
# ifdef LOG
			else {
				if(retrying == 0) log(command, from, size); /* */
			}
# endif
		}
	}
/*
**  Update logs and records.
*/
# ifdef RECORD
	(void) fseek(spoolfp, message, 0);
	record(command, from, size);
# endif

/*
**  close spool file pointer.
**  if we created it, then unlink file.
*/
	(void) fclose(spoolfp);
	if(spoolmaster) {
		(void) unlink(spoolfile);
	}
	(void) unlink(stderrfile);
}

/*
** return mail to sender, as determined by From_ line.
*/
return_mail(from, fcommand)
char *from, *fcommand;
{
	char buf[SMLBUF];
	char domain[SMLBUF], user[SMLBUF];
	char *r;
	FILE *fp, *out, *popen();
	int i = 0;

	r = buf;

	(void) sprintf(r, "%s %s", SMAIL, VFLAG);
	r += strlen(r);

	if(islocal(from, domain, user)) {
		(void) sprintf(r, LARG(user));
	} else {
		(void) sprintf(r, RLARG(domain, user));
	}

	i = 0;
	do {
		out = popen(buf, "w");
		if (out) break;
		/*
		 * Fork failed.  System probably overloaded.
		 * Wait awhile and try again 10 times.
		 * If it keeps failing, probably some
		 * other problem, like no uux or smail.
		 */
		(void) sleep(60);
	} while (++i < 10);

	if(out == NULL) {
		(void) printf("couldn't execute %s.\n", buf);
		return;
	}

	(void) fprintf(out, "Date: %s\n", arpanows);
	(void) fprintf(out, "From: MAILER-DAEMON@%s\n", hostdomain);
	(void) fprintf(out, "Subject: failed mail\n");
	(void) fprintf(out, "To: %s\n", from);
	(void) fprintf(out, "\n");
	(void) fprintf(out, "=======     command failed      =======\n\n");
	(void) fprintf(out, " COMMAND: %s\n\n", fcommand);

	(void) fprintf(out, "======= standard error follows  =======\n");
	(void) fflush(stderr);
	if((fp = fopen(stderrfile, "r")) != NULL) {
		while(fgets(buf, sizeof(buf), fp) != NULL) {
			(void) fputs(buf, out);
		}
	}
	(void) fclose(fp);
	(void) fprintf(out, "======= text of message follows =======\n");
/*
**  Copy input.
*/
	(void) fprintf(out, "From %s\n", from);
	while(fgets(buf, sizeof(buf), spoolfp) != NULL) {
		(void) fputs(buf, out);
	}
	(void) pclose(out);
}
