/*
 *	Copyright 1990 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

#include <stdio.h>
#include "hostenv.h"
#include <ctype.h>
#include <pwd.h>
#include <signal.h>
#include <sysexits.h>
#include <string.h>
#include <sys/param.h>

#if	defined(USE_HOSTS) || defined(USE_RESOLV)
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#endif	/* USE_HOSTS || USE_RESOLV */
#include "ta.h"

#if	defined(TRY_AGAIN) && defined(USE_RESOLV)
#define	BIND		/* Want BIND (named) nameserver support enabled */
#endif	/* TRY_AGAIN */
#ifdef	BIND
#ifdef NOERROR
#undef NOERROR
#endif
#include <arpa/nameser.h>
#include <resolv.h>

#ifndef	BIND_VER
#ifdef	GETLONG
/* 4.7.3 introduced the {GET,PUT}{LONG,SHORT} macros in nameser.h */
#define	BIND_VER	473
#else	/* !GETLONG */
#define	BIND_VER	472
#endif	/* GETLONG */
#endif	/* !BIND_VER */
#endif	/* BIND */

#if	defined(BIND_VER) && (BIND_VER >= 473)
typedef u_char msgdata;
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
typedef char msgdata;
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */

#include <mail.h>
#include "splay.h"

#ifdef	USE_UNIONWAIT
#include <sys/wait.h>
#endif	/* USE_UNIONWAIT */

#ifndef	IPPORT_SMTP
#define	IPPORT_SMTP	25
#endif	/* IPPORT_SMTP */

#ifndef	L_SET
#define	L_SET	0
#endif	/* L_SET */

#define	PROGNAME	"hold"	/* for logging */
#define	CHANNEL		"hold"	/* the default channel name we look at */

char errormsg[BUFSIZ];
char *progname;
char *postoffice;
char *cmdline, *eocmdline;
int pid;

extern void warning(), report();
extern char *emalloc();

#ifdef	lint
#undef	putc
#define	putc	fputc
#endif	/* lint */

int
main(argc, argv)
	int argc;
	char *argv[];
{
	char *channel, file[MAXPATHLEN+1];
	int errflg, c, i;
	struct ctldesc *dp;
	extern char *optarg;
	extern int optind;
	extern int emptyline();
	extern void prversion(), process();

	pid = getpid();
	cmdline = &argv[0][0];
	eocmdline = cmdline;
	for (i = 0; i < argc; ++i)
		eocmdline += strlen(argv[i]) + 1;
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		(void) signal(SIGINT, wantout);
	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
		(void) signal(SIGHUP, wantout);
	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
		(void) signal(SIGTERM, wantout);

	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		++progname;
	errflg = 0;
	channel = CHANNEL;
	while ((c = getopt(argc, argv, "c:V")) != EOF) {
		switch (c) {
		case 'c':	/* specify channel scanned for */
			channel = optarg;
			break;
		case 'V':
			prversion(PROGNAME);
			exit(EX_OK);
			break;
		default:
			++errflg;
			break;
		}
	}
	if (errflg || optind != argc) {
		(void) fprintf(stderr, "Usage: %s [-V] [-c channel]\n",
				argv[0]);
		exit(EX_USAGE);
	}

	while (!getout && fgets(file, sizeof file, stdin)) {
		if (emptyline(file, sizeof file))
			break;
		dp = ctlopen(file, channel, (char *)NULL, &getout, (int(*)())0);
		if (dp == NULL)
			continue;
		process(dp);
		ctlclose(dp);
	}
	exit(0);
	/* NOTREACHED */
	return 0;
}

/*
 * process - resubmit the message if the hold condition has disappeared.
 */

void
process(dp)
	struct ctldesc *dp;
{
	FILE *mfp;
	int n, sawok, code;
	struct rcpt *rp;
	char *cp, buf[BUFSIZ];
	extern int hold();

	sawok = 0;
	for (rp = dp->recipients; rp != NULL; rp = rp->next) {
		cp = rp->addr->user;
		rp->status = hold(rp->addr->host, &(rp->addr->user));
		if (rp->status == EX_OK) {
			rp->addr->user = cp;
			sawok = 1;
		} else
			diagnostic(rp, rp->status, "%s", rp->addr->user);
	}

	if (!sawok)
		return;

	if (lseek(dp->msgfd, dp->msgbodyoffset, L_SET) < 0L)
		warning("Cannot seek to message body! (%m)", (char *)NULL);

#ifdef	USE_SETREUID
	(void) setreuid(-1, atoi(dp->senders->misc));
#endif	/* USE_SETREUID */

	if ((mfp = mail_open(MSG_RFC822)) == NULL) {
		for (rp = dp->recipients; rp != NULL; rp = rp->next)
			if (rp->status == EX_OK) {
				diagnostic(rp, EX_TEMPFAIL,
					       "cannot resubmit anything!");
			}
#ifdef	USE_SETREUID
		(void) setreuid(-1, getuid());
#endif	/* USE_SETREUID */
		return;
	}
#ifdef	USE_SETREUID
	(void) setreuid(-1, getuid());
#endif	/* USE_SETREUID */

	(void) fprintf(mfp, "via suspension\n");
	(void) fprintf(mfp, "from <%s>\n", dp->senders->user);
	for (rp = dp->recipients; rp != NULL; rp = rp->next)
		if (rp->status == EX_OK)
			(void) fprintf(mfp, "to <%s>\n", rp->addr->user);

	(void) fwrite(dp->recipients->newmsgheader,
		      1, strlen(dp->recipients->newmsgheader), mfp);
	(void) fputc('\n', mfp);

	/* append message body itself */
	while ((n = read(dp->msgfd, buf, sizeof buf)) > 0)
		(void) fwrite(buf, sizeof buf[0], n, mfp);

	if (ferror(mfp)) {
		(void) mail_abort(mfp);
		code = EX_TEMPFAIL;
		cp = "write error during resubmission";
	} else if (mail_close(mfp) == EOF) {
		code = EX_TEMPFAIL;
		cp = "message not resubmitted";
	} else {
		code = EX_OK;
		cp = NULL;
	}

	for (rp = dp->recipients; rp != NULL; rp = rp->next)
		if (rp->status == EX_OK)
			diagnostic(rp, code, cp);
}


/*
 * The hostname for the hold channel describes a wait condition that can
 * be tested (here) before the message can be resubmitted.  The condition
 * string is canonicalized and parsed hierarchically.
 */

extern int hold_ns(), hold_timeout(), hold_io(), hold_script();

struct holds_info {
	char	*type;
	int	(*f)();
} holds[] = {
	{	"ns",		hold_ns		},
	{	"timeout",	hold_timeout	},
	{	"io",		hold_io		},
	{	"script",	hold_script	},
	{	NULL,		NULL		},
};


int
hold(s, errmsgp)
	char *s, **errmsgp;
{
	char *cp, *colon;
	struct holds_info *hip;
	static struct sptree *spt_hash = NULL;
	struct spblk *spl;
	int v, symid;
	extern int symbol();

	colon = NULL;
	for (cp = s; *cp != '\0'; ++cp) {
		if (isascii(*cp) && isupper(*cp))
			*cp = tolower(*cp);
		else if (*cp == ':')
			colon = cp;
	}

	if (colon == NULL)
		return EX_PROTOCOL;	/* invalid hold condition */

	symid = symbol(s);
	if (spt_hash == NULL)
		spt_hash = sp_init();
	if ((spl = sp_lookup(symid, spt_hash)) != NULL) {
		*errmsgp = (char *)spl->data;
		return spl->mark;
	}

	*colon++ = '\0';
	for (hip = &holds[0]; hip->type != NULL ; ++hip)
		if (strcmp(s, hip->type) == 0)
			break;
	if (hip->type == NULL)
		return EX_SOFTWARE;	/* unsupported hold condition */

	errormsg[0] = '\0';

	if ((hip->f)(colon))
		v = EX_OK;		/* resubmit the message address */
	else
		v = EX_TEMPFAIL;	/* defer resubmission */

	if (errormsg[0] != '\0') {
		cp = emalloc((u_int)strlen(errormsg)+1);
		strcpy(cp, errormsg);
	} else
		cp = "deferred";
	sp_install(symid, (u_char *)cp, v, spt_hash);
	*errmsgp = cp;

	return v;
}

/* return true if the nameserver lookup of (name,type) succeeds */

#ifdef	BIND
extern int	h_errno;

struct qtypes {
	char	*typename;
	u_short	value;
} qt[] = {
	{	"cname",	T_CNAME		},
	{	"mx",		T_MX		},
	{	"a",		T_A		},
#ifdef	T_MP
	{	"mp",		T_MP		},
#endif	/* T_MP */
#ifdef	T_UNAME
	{	"uname",	T_UNAME		},
#endif	/* T_UNAME */
#ifdef	T_TXT
	{	"txt",		T_TXT		},
#endif	/* T_TXT */
	{	"wks",		T_WKS		},
	{	"ptr",		T_PTR		},
	{	NULL,		0		}
};

int
hold_ns(s)
	char *s;
{
	struct qtypes *qtp;
	char *cp, host[BUFSIZ];
	extern int getrrtype();

	if ((cp = strrchr(s, '/')) == NULL)
		return 1;	/* human error, lets be nice */
	if (cp > s && *(cp-1) == '.')
		*(cp-1) = '\0';
	else
		*cp = '\0';
	++cp;

	for (qtp = &qt[0]; qtp->typename != NULL ; ++qtp) {
		if (strcmp(qtp->typename, cp) == 0)
			break;
	}
	if (qtp->typename == NULL) {
		(void) fprintf(stderr, "%s: unknown nameserver type '%s'\n",
				progname, cp);
		return 1;	/* inconsistency with search_res.c, yell! */
	}
	strcpy(host, s);
	switch (getrrtype(host, sizeof host, qtp->value)) {
	case 0:
		return 1;	/* negative reply */
	case 1:
		return 1;	/* positive reply */
	case -1:
	case -2:
	case -3:
		return 0;	/* no reply */
	}
	return 0;
}
#else	/* !BIND */

struct qtypes {
	char	*typename;
	u_short	value;
} qt[] = {
	{	"cname",	1		},
	{	"a",		1		},
	{	"ptr",		2		},
	{	NULL,		NULL		}
};

int
hold_ns(s)
	char *s;
{
#ifdef	USE_HOSTS
	struct qtypes *qtp;
	char *cp, host[BUFSIZ];
	struct hostent *hp;
	struct in_addr ia;
	extern struct hostent *gethostbyname(), *gethostbyaddr();

	if ((cp = strrchr(s, '/')) == NULL)
		return 1;	/* human error, lets be nice */
	if (cp > s && *(cp-1) == '.')
		*(cp-1) = '\0';
	else
		*cp = '\0';
	++cp;

	for (qtp = &qt[0]; qtp->typename != NULL ; ++qtp) {
		if (strcmp(qtp->typename, cp) == 0)
			break;
	}
	if (qtp->typename == NULL) {
		fprintf(stderr, "%s: unknown hosts file lookup '%s'\n",
				progname, cp);
		return 1;	/* inconsistency with search_res.c, yell! */
	}
	strcpy(host, s);
	hp = NULL;
	switch (qtp->value) {
	case 1:
		hp = gethostbyname(host);
		break;
	case 2:
		ia.s_addr = inet_addr(host);
		hp = gethostbyaddr(&ia, sizeof ia.s_addr, AF_INET);
		break;
	}
	return hp != NULL;
#else	/* !USE_HOSTS */
	return 0;
#endif	/* USE_HOSTS */
}
#endif	/* BIND */

/* return true if the seconds-since-epoch in argument has passed */

int
hold_timeout(s)
	char *s;
{
	time_t now, then;
	extern time_t time();

	(void) time(&now);
	then = (time_t)atol(s);
	return now >= then;
}

/* return true if we should retry the I/O operation causing the error */

int
hold_io(s)
	char *s;
{
	extern int ranny();
	
	return ranny(9) == 0;	/* 10% of the time */
}

/* based on tuple "hold" "script:command/$user" "$address"
   return exit status of "$MAILBIN/bin/command $user" command */

int
hold_script(command)
	char *command;
{
	int i, in[2];
	char *env[20], buf[8192], *cp, *s, *arg;
	FILE *errfp;
#ifdef	USE_UNIONWAIT
	union wait status;
#else	/* !USE_UNIONWAIT */
	int status;
#endif	/* USE_UNIONWAIT */
	extern char **environ;

	if ((arg = strchr(command, '/')) != NULL)
		*arg++ = '\0';
	else
		arg = NULL;
	i = 0;
	env[i++] = "SHELL=/bin/sh";
	cp = buf;
	if ((s = getzenv("PATH")) == NULL)
		env[i++] = "PATH=/usr/ucb:/usr/bin:/bin";
	else {
		(void) sprintf(cp, "PATH=%s", s);
		env[i++] = cp;
		cp += strlen(cp) + 1;
	}
	env[i++] = "HOME=/tmp";
	env[i++] = "USER=anonymous";
	if ((s = getzenv("ZCONFIG")) == NULL)
		s = ZMAILER_ENV_FILE;
	(void) sprintf(cp, "ZCONFIG=%s", s);
	env[i++] = cp;
	if ((s = getzenv("MAILSHARE")) == NULL)
		s = MAILBIN;
	cp += strlen(cp) + 1;
	(void) sprintf(cp, "MAILSHARE=%s", s);
	env[i++] = cp;
	if ((s = getzenv("MAILBIN")) == NULL)
		s = MAILSHARE;
	cp += strlen(cp) + 1;
	(void) sprintf(cp, "MAILBIN=%s", s);
	env[i++] = cp;
	env[i] = NULL;

	/* now we can fork off and run the command... */
	if (pipe(in) < 0) {
		(void) sprintf(errormsg,
			       "cannot create pipe from \"%s\"", command);
		return 0;
	}
	cp += strlen(cp) + 1;
	(void) sprintf(cp, "%s/bin/%s", s, command);

	if ((pid = fork()) == 0) {		/* child */
		environ = env;
		(void) dup2(in[1],1);
		(void) dup2(in[1],2);
		(void) close(0);
		if (in[0] != 1 && in[0] != 2)
			(void) close(in[0]);
		if (in[1] != 1 && in[1] != 2)
			(void) close(in[1]);
		(void) signal(SIGINT, SIG_IGN);
		(void) signal(SIGHUP, SIG_IGN);
		(void) signal(SIGTERM, SIG_DFL);
		/*
		 * Note that argv[0] is set to the command we are running.
		 * That way, we should get some better error messages, at
		 * least more understandable in rejection messages.
		 * Some bourne shells may go into restricted mode if the
		 * stuff to run contains an 'r'. XX: investigate.
		 */
		(void) execl("/bin/sh", "sh", cp, arg, (char *)NULL);
		(void) execl("/sbin/sh", "sh", cp, arg, (char *)NULL);
		(void) write(2, "Cannot exec /bin/sh\n", 20);
		_exit(128);
	} else if (pid < 0) {			/* fork failed */
		(void) sprintf(errormsg, "cannot fork");
		return 0;
	}					/* parent */
	(void) close(in[1]);
	errfp = fdopen(in[0], "r");
	/* read any messages from its stdout/err on in[0] */
	cp = errormsg;
	if (fgets(errormsg, sizeof errormsg, errfp) == NULL)
		errormsg[0] = '\0';
	else if ((cp = strchr(errormsg, '\n')) != NULL)
		*cp = '\0';
	(void) wait(&status);
	(void) fclose(errfp);
	(void) close(in[0]);
#ifdef  USE_UNIONWAIT
        if (status.w_termsig) {
                if (cp != errormsg)
                        *cp++ = ' ';
                (void) sprintf(cp, "[signal %d", status.w_termsig);
                if (status.w_coredump)
                        (void) strcat(cp, " (Core dumped)");
                (void) strcat(cp, "]");
                return 0;
        } else if (status.w_retcode) {
                if (cp != errormsg)
                        *cp++ = ' ';
                (void) sprintf(cp, "[exit status %d]", status.w_retcode);
		return 0;
        }
#else   /* !USE_UNIONWAIT */
        if ((status&0177) > 0) {
                if (cp != errormsg)
                        *cp++ = ' ';
                (void) sprintf(cp, "[signal %d", status&0177);
                if (status&0200)
                        (void) strcat(cp, " (Core dumped)");
                (void) strcat(cp, "]");
		return 0;
        } else if (((status>>8)&0377) > 0) {
                if (cp != errormsg)
                        *cp++ = ' ';
                (void) sprintf(cp, "[exit status %d]", (status>>8)&0377);
		return 0;
        }
#endif  /* USE_UNIONWAIT */
	return 1;
}

#ifdef	BIND

typedef union {
	HEADER qb1;
	char qb2[PACKETSZ];
} querybuf;

extern int h_errno;

int
getrrtype(host, hbsize, rrtype)
	char *host;
	int hbsize;
	u_short rrtype;
{

	HEADER *hp;
	msgdata *eom, *cp;
	querybuf buf, answer;
	int n, ancount, qdcount, ok;
	u_short type;
	msgdata nbuf[BUFSIZ];
	int first;
	extern int res_mkquery(), res_send(), dn_expand(), dn_skipname();

	n = res_mkquery(QUERY, host, C_IN, rrtype, (char *)NULL, 0, NULL,
		(char *)&buf, sizeof(buf));
	if (n < 0) {
		(void) fprintf(stderr, "res_mkquery failed\n");
		h_errno = NO_RECOVERY;
		strcpy(errormsg, "no recovery");
		return -2;
	}
	n = res_send((char *)&buf, n, (char *)&answer, sizeof(answer));
	if (n < 0) {
		h_errno = TRY_AGAIN;
		strcpy(errormsg, "try again");
		return -1;
	}
	eom = (msgdata *)&answer + n;
	/*
	 * find first satisfactory answer
	 */
	hp = (HEADER *) &answer;
	ancount = ntohs(hp->ancount);
	qdcount = ntohs(hp->qdcount);
	h_errno = 0;
	/*
	 * We don't care about errors here, only if we got an answer
	 */
	if (ancount == 0) {
		if (rrtype == T_CNAME && hp->rcode == NOERROR) {
			if (qdcount > 0 && strchr(host, '.') == NULL) {
				cp = (msgdata *)&answer + sizeof(HEADER);
				if (dn_expand((msgdata *)&answer,
					      eom, cp, host, hbsize) >= 0) {
					if (host[0] == '\0') {
						host[0] = '.';
						host[1] = '\0';
					}
				}
			}
			return 1;
		}
		return (hp->rcode == NOERROR || hp->rcode == NXDOMAIN) ? 0 : -3;
	}
	cp = (msgdata *)&answer + sizeof(HEADER);
	for (; qdcount > 0; --qdcount)
#if	defined(BIND_VER) && (BIND_VER >= 473)
		cp += dn_skipname(cp, eom) + QFIXEDSZ;
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
		cp += dn_skip(cp) + QFIXEDSZ;
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */
	first = 1;
	ok = rrtype != T_WKS;
	while (--ancount >= 0 && cp < eom) {
		if ((n = dn_expand((msgdata *)&answer, eom, cp, nbuf,
		    sizeof(nbuf))) < 0)
			break;
		if (first) {
			(void)strncpy(host, (char *)nbuf, hbsize);
			host[hbsize - 1] = '\0';
			first = 0;
		}
		cp += n;
		type = _getshort(cp);
 		cp += sizeof(u_short);				/* type */
 		cp += sizeof(u_short);				/* class */
		cp += sizeof(u_long);				/* ttl */
		n = _getshort(cp);
		cp += sizeof(u_short);				/* dlen */
		if (type != rrtype) {
			cp += n;
			continue;
		}
		/*
		 * Assume that only one rrtype will be found.  More
		 * than one is undefined.
		 */
		if (type == T_WKS) {
			msgdata *nextcp = cp + n;
			/* If we have seen a WKS, it had better have SMTP,
			 * however in absence of a WKS, assume SMTP.
			 */
			if (n < sizeof (u_long) + 1) {
				cp = nextcp;	/* ? */
				continue;
			}
			ok = 0;
			cp += sizeof(u_long);		/* skip IP address */
			if (*cp++ == IPPROTO_TCP) {	/* check protocol */
				if (cp + (IPPORT_SMTP/8) < nextcp
				    && (*(cp+(IPPORT_SMTP/8))
					 & (0x80>>IPPORT_SMTP%8)))
					return 1;
			}
			cp = nextcp;
			continue;
		} else {
			if ((n = dn_expand((msgdata *)&answer, eom, cp, nbuf,
			    sizeof(nbuf))) < 0)
				break;
			(void)strncpy(host, (char *)nbuf, hbsize);
			host[hbsize - 1] = '\0';
		}
		return ok;
	}
	return 0;
}
#endif	/* BIND */
