#include "common.h"
#include "send.h"

/* globals to all files */
int rmail;
char *thissys, *altthissys;
int nflg;
int xflg;
int debug;
int rflg;
int iflg = 1;
int nosummary;

/* global to this file */
static String *errstring;
static message *mp;
static int interrupt;
static int savemail;
static Biobuf in;
static int forked;
static int add822headers = 1;
static String *arglist;

/* predeclared */
static int	send(dest *, message *, int);
static void	lesstedious(void);
static void	save_mail(message *);
static int	complain_mail(dest *, message *);
static int	pipe_mail(dest *, message *);
static void	appaddr(String *, dest *);
static void	mkerrstring(String *, message *, dest *, dest *, char *, int);
static int	replymsg(String *, message *, dest *);
static int	catchint(void*, char*);

void
usage(void)
{
	fprint(2, "usage: mail [-birtx] list-of-addresses\n");
	exits("usage");
}

void
main(int argc, char *argv[])
{
	dest *dp=0;
	int checkforward;
	char *base;
	int rv;

	/* process args */
	ARGBEGIN{
	case '#':
		nflg = 1;
		break;
	case 'b':
		add822headers = 0;
		break;
	case 'x':
		nflg = 1;
		xflg = 1;
		break;
	case 'd':
		debug = 1;
		break;
	case 'i':
		iflg = 0;
		break;
	case 'r':
		rflg = 1;
		break;
	default:
		usage();
	}ARGEND

	while(*argv){
		if(shellchars(*argv)){
			fprint(2, "illegal characters in destination\n");
			exits("syntax");
		}
		d_insert(&dp, d_new(s_copy(*argv++)));
	}

	if (dp == 0)
		usage();
	arglist = d_to(dp);

	/*
	 * get context:
	 *	- whether we're rmail or mail
	 */
	base = basename(argv0);
	checkforward = rmail = (strcmp(base, "rmail")==0) | rflg;
	thissys = sysname_read();
	altthissys = alt_sysname_read();
	if(rmail)
		add822headers = 0;

	/*
	 *  read the mail.  If an interrupt occurs while reading, save in
	 *  dead.letter
	 */
	if (!nflg) {
		Binit(&in, 0, OREAD);
		if(!rmail)
			atnotify(catchint, 1);
		mp = m_read(&in, rmail, !iflg);
		if (mp == 0)
			exit(0);
		if (interrupt != 0) {
			save_mail(mp);
			exit(1);
		}
	} else {
		mp = m_new();
		if(default_from(mp) < 0){
			fprint(2, "%s: can't determine login name\n", argv0);
			exit(1);
		}
	}
	errstring = s_new();
	getrules();

	/*
	 *  If this is a gateway, translate the sender address into a local
	 *  address.  This only happens if mail to the local address is 
	 *  forwarded to the sender.
	 */
	gateway(mp);

	/*
	 *  Protect against shell characters in the sender name for
	 *  security reasons.
	 */
	mp->sender = escapespecial(mp->sender);
	if (shellchars(s_to_c(mp->sender)))
		mp->replyaddr = s_copy("postmaster");
	else
		mp->replyaddr = s_clone(mp->sender);

	/*
	 *  reject messages that have been looping for too long
	 */
	if(mp->received > 32)
		exit(refuse(dp, mp, "possible forward loop", 0, 0));

	/*
	 *  reject messages that are too long.  We don't do it earlier
	 *  in m_read since we haven't set up enough things yet.
	 */
	if(mp->size < 0)
		exit(refuse(dp, mp, "message too long", 0, 0));

	rv = send(dp, mp, checkforward);
	if(savemail)
		save_mail(mp);
	if(mp)
		m_free(mp);
	exit(rv);
}

/* send a message to a list of sites */
static int
send(dest *destp, message *mp, int checkforward)
{
	dest *dp;		/* destination being acted upon */
	dest *bound;		/* bound destinations */
	int errors=0;

	/* bind the destinations to actions */
	bound = up_bind(destp, mp, checkforward);
	if(add822headers && mp->haveto == 0){
		if(nosummary)
			mp->to = d_to(bound);
		else
			mp->to = arglist;
	}

	/* loop through and execute commands */
	for (dp = d_rm(&bound); dp != 0; dp = d_rm(&bound)) {
		switch (dp->status) {
		case d_cat:
			errors += cat_mail(dp, mp);
			break;
		case d_pipeto:
		case d_pipe:
			if (!rmail && !nflg && !forked) {
				forked = 1;
				lesstedious();
			}
			errors += pipe_mail(dp, mp);
			break;
		default:
			errors += complain_mail(dp, mp);
			break;
		}
	}

	return errors;
}

/* avoid user tedium (as Mike Lesk said in a previous version) */
static void
lesstedious(void)
{
	int i;

	if(debug)
		return;

	switch(fork()){
	case -1:
		break;
	case 0:
		sysdetach();
		for(i=0; i<3; i++)
			close(i);
		savemail = 0;
		break;
	default:
		exit(0);
	}
}


/* save the mail */
static void
save_mail(message *mp)
{
	Biobuf *fp;
	String *file;

	file = s_new();
	deadletter(file);
	fp = sysopen(s_to_c(file), "cAt", 0660);
	if (fp == 0)
		return;
	m_bprint(mp, fp);
	sysclose(fp);
	fprint(2, "saved in %s\n", s_to_c(file));
	s_free(file);
}

/* remember the interrupt happened */

static int
catchint(void *a, char *msg)
{
	USED(a);
	if(strstr(msg, "interrupt") || strstr(msg, "hangup")) {
		interrupt = 1;
		return 1;
	}
	return 0;
}

/* dispose of incorrect addresses */
static int
complain_mail(dest *dp, message *mp)
{
	char *msg;

	switch (dp->status) {
	case d_undefined:
		msg = "Invalid address"; /* a little different, for debugging */
		break;
	case d_syntax:
		msg = "invalid address";
		break;
	case d_unknown:
		msg = "unknown user";
		break;
	case d_eloop:
	case d_loop:
		msg = "forwarding loop";
		break;
	case d_noforward:
		if(dp->pstat && *s_to_c(dp->repl2))
			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
		else
			msg = "destination unknown or forwarding disallowed";
		break;
	case d_pipe:
		msg = "broken pipe";
		break;
	case d_cat:
		msg = "broken cat";
		break;
	case d_translate:
		if(dp->pstat && *s_to_c(dp->repl2))
			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
		else
			msg = "name translation failed";
		break;
	case d_alias:
		msg = "broken alias";
		break;
	case d_badmbox:
		msg = "corrupted mailbox";
		break;
	case d_resource:
		return refuse(dp, mp, "out of some resource.  Try again later.", 0, 1);
	default:
		msg = "unknown d_";
		break;
	}
	if (nflg) {
		print("%s: %s\n", msg, s_to_c(dp->addr));
		return 0;
	}
	return refuse(dp, mp, msg, 0, 0);
}

/* dispose of remote addresses */
static int
pipe_mail(dest *dp, message *mp)
{
	dest *next, *list=0;
	String *cmd;
	process *pp;
	int status, r;
	char *none;
	String *errstring=s_new();

	if (dp->status == d_pipeto)
		none = "none";
	else
		none = 0;
	/*
	 *  collect the arguments
	 */
	next = d_rm_same(&dp);
	if(xflg)
		cmd = s_new();
	else
		cmd = s_clone(next->repl1);
	for(; next != 0; next = d_rm_same(&dp)){
		if(xflg){
			s_append(cmd, s_to_c(next->addr));
			s_append(cmd, "\n");
		} else {
			if (next->repl2 != 0) {
				s_append(cmd, " ");
				s_append(cmd, s_to_c(next->repl2));
			}
		}
		d_insert(&list, next);
	}

	if (nflg) {
		if(xflg)
			print("%s", s_to_c(cmd));
		else
			print("%s\n", s_to_c(cmd));
		s_free(cmd);
		return 0;
	}

	/*
	 *  run the process
	 */
	pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 1, none);
	if(pp==0 || pp->std[0]==0 || pp->std[2]==0)
		return refuse(list, mp, "out of processes, pipes, or memory", 0, 1);
	pipesig(0);
	m_print(mp, pp->std[0]->fp, thissys, 0);
	pipesigoff();
	stream_free(pp->std[0]);
	pp->std[0] = 0;
	while(s_read_line(pp->std[2]->fp, errstring))
		;
	status = proc_wait(pp);
	proc_free(pp);
	s_free(cmd);

	/*
	 *  return status
	 */
	if (status != 0) {
		r = refuse(list, mp, s_to_c(errstring), status, 0);
		s_free(errstring);
		return r;
	}
	s_free(errstring);
	loglist(list, mp, "remote");
	return 0;
}

static void
appaddr(String *sp, dest *dp)
{
	dest *parent;
	String *s;

	if (dp->parent != 0) {
		for(parent=dp->parent; parent->parent!=0; parent=parent->parent)
			;
		s = unescapespecial(s_clone(parent->addr));
		s_append(sp, s_to_c(s));
		s_free(s);
		s_append(sp, "' alias `");
	}
	s = unescapespecial(s_clone(dp->addr));
	s_append(sp, s_to_c(s));
	s_free(s);
}

/*
 *  reject delivery
 *
 *  returns	0	- if mail has been disposed of
 *		other	- if mail has not been disposed
 */
int
refuse(dest *list, message *mp, char *cp, int status, int outofresources)
{
	String *errstring=s_new();
	dest *dp;
	int rv;

	dp = d_rm(&list);
	mkerrstring(errstring, mp, dp, list, cp, status);

	/*
	 *  log first in case we get into trouble
	 */
	logrefusal(dp, mp, s_to_c(errstring));

	/*
	 *  bulk mail is never replied to, if we're out of resources,
	 *  let the sender try again
	 */
	if(rmail){
		/* accept it or request a retry */
		if(outofresources){
			fprint(2, "Mail %s\n", s_to_c(errstring));
			rv = 1;					/* try again later */
		} else if(mp->bulk)
			rv = 0;					/* silently discard bulk */
		else
			rv = replymsg(errstring, mp, dp);	/* try later if we can't reply */
	} else {
		/* aysnchronous delivery only happens if !rmail */
		if(forked){
			/*
			 *  if spun off for asynchronous delivery, we own the mail now.
			 *  return it or dump it on the floor.  rv really doesn't matter.
			 */
			rv = 0;
			if(!outofresources && !mp->bulk)
				replymsg(errstring, mp, dp);
		} else {
			fprint(2, "Mail %s\n", s_to_c(errstring));
			savemail = 1;
			rv = 1;
		}
	}

	s_free(errstring);
	return rv;
}

/* make the error message */
static void
mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
{
	dest *next;
	char smsg[64];
	String *sender;

	sender = unescapespecial(s_clone(mp->sender));

	/* list all aliases */
	s_append(errstring, " from '");
	s_append(errstring, s_to_c(sender));
	s_append(errstring, "'\nto '");
	appaddr(errstring, dp);
	for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
		s_append(errstring, "'\nand '");
		appaddr(errstring, next);
		d_insert(&dp, next);
	}
	s_append(errstring, "'\nfailed with error '");
	s_append(errstring, cp);
	s_append(errstring, "'.\n");

	/* >> and | deserve different flavored messages */
	switch(dp->status) {
	case d_pipe:
		s_append(errstring, "The mailer `");
		s_append(errstring, s_to_c(dp->repl1));
		sprint(smsg, "' returned error status %x.\n\n", status);
		s_append(errstring, smsg);
		break;
	}

	s_free(sender);
}

/*
 *  create a new boundary
 */
static String*
mkboundary(void)
{
	char buf[32];
	int i;
	static int already;

	if(already == 0){
		srand((time(0)<<16)|getpid());
		already = 1;
	}
	strcpy(buf, "upas-");
	for(i = 5; i < sizeof(buf)-1; i++)
		buf[i] = 'a' + nrand(26);
	buf[i] = 0;
	return s_copy(buf);
}

/*
 *  reply with up to 1024 characters of the
 *  original message
 */
static int
replymsg(String *errstring, message *mp, dest *dp)
{
	message *refp = m_new();
	dest *ndp;
	char *rcvr;
	int rv;
	String *boundary;

	boundary = mkboundary();

	refp->bulk = 1;
	refp->rfc822headers = 1;
	rcvr = dp->status==d_eloop ? "postmaster" : s_to_c(mp->replyaddr);
	ndp = d_new(s_copy(rcvr));
	s_append(refp->sender, "postmaster");
	s_append(refp->replyaddr, "/dev/null");
	s_append(refp->date, thedate());
	refp->haveto = 1;
	s_append(refp->body, "To: ");
	s_append(refp->body, rcvr);
	s_append(refp->body, "\n");
	s_append(refp->body, "Subject: bounced mail\n");
	s_append(refp->body, "MIME-Version: 1.0\n");
	s_append(refp->body, "Content-Type: multipart/mixed;\n");
	s_append(refp->body, "\tboundary=\"");
	s_append(refp->body, s_to_c(boundary));
	s_append(refp->body, "\"\n");
	s_append(refp->body, "Content-Disposition: inline\n");
	s_append(refp->body, "\n");
	s_append(refp->body, "This is a multi-part message in MIME format.\n");
	s_append(refp->body, "--");
	s_append(refp->body, s_to_c(boundary));
	s_append(refp->body, "\n");
	s_append(refp->body, "Content-Disposition: inline\n");
	s_append(refp->body, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
	s_append(refp->body, "Content-Transfer-Encoding: 7bit\n");
	s_append(refp->body, "\n");
	s_append(refp->body, "The attached mail");
	s_append(refp->body, s_to_c(errstring));
	s_append(refp->body, "--");
	s_append(refp->body, s_to_c(boundary));
	s_append(refp->body, "\n");
	s_append(refp->body, "Content-Type: message/rfc822\n");
	s_append(refp->body, "Content-Disposition: inline\n\n");
	s_append(refp->body, s_to_c(mp->body));
	s_append(refp->body, "--");
	s_append(refp->body, s_to_c(boundary));
	s_append(refp->body, "--\n");

	refp->size = s_len(refp->body);
	rv = send(ndp, refp, 0);
	m_free(refp);
	d_free(ndp);
	return rv;
}
