/*
 * Copyright (c) 1983  Eric P. Allman
 *
 * Copyright (c) 1983, 1987, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Modifications from the original:
 *
 * Copyright (c) 2001, 2002	Greg A. Woods <woods@planix.com>
 */

#include <sys/cdefs.h>

#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1983, 1987, 1993\n\
	The Regents of the University of California.  All rights reserved.\n\
@(#) Copyright (c) 2002  Greg A. Woods <woods@planix.com>\n");
#endif /* not lint */

#ifndef lint
__RCSID("@(#)vacation: :vacation.c,v 1.6 2002/02/12 20:48:45 woods Exp ");
# if 0
static char sccsid[] = "@(#)vacation.c	8.2 (Berkeley) 1/26/94";
# endif
#endif /* not lint */

#include <sys/param.h>
#include <sys/stat.h>

#include <ctype.h>
#include <db.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#ifdef __FreeBSD__
# include </usr/src/lib/libc/stdtime/tzfile.h>	/* you lose! */
#else
# include <tzfile.h>
#endif
#include <unistd.h>
#include <assert.h>

#include <sysexits.h>			/* we are a mail application */

#include "rfc822.h"
#include <rfc2822.h>

#include "vacation.h"

/*
 *    VACATION -- a mail agent that replies on your behalf
 *
 * This program reads an incoming e-mail message in RFC-2822 format (with
 * optional Unix mailbox separator prepended) on its standard input.  It
 * replies with a message specified by the user to whomever sent the mail,
 * taking care not to return a message too often to prevent "I am on vacation"
 * loops; as well as avoiding replying to other auto-repsonders and mailing
 * list mail, etc.  Instances of the string "$SUBJECT" in any headers of the
 * user-supplied reply message are replaced with the content of the "subject:"
 * header from the incoming messsage.
 *
 * TODO:
 *
 * Make sure any error recording the correspondent address in the db file
 * causes an error exit so that no reply is sent if it cannot be recorded.
 *
 * Also make sure any actual error reading the db file to test a correspondent
 * address is detected and will cause an error exit to prevent a reply from
 * being sent.
 *
 * Allow aliases ('-a' optargs) to be REs.
 *
 * Add a command-line option to allow the user to specify a (list of) header
 * fields (body and/or contents, substring, exact, or RE?) that should be used
 * to mark messages that should not be responded to.
 *
 * Multiple "login" parameters should be allowed (and treated like '-a').
 *
 * There should be a very simple way to create a default ~/.vacation.msg file
 * from a template, and this template should be created automatically and be
 * used if the message file was not created by the user.
 *
 * Properly re-fold the headers of the outgoing messsage.
 */

#define	PATH_VDB	".vacation.db"	/* db's database */
#define	PATH_VMSG	".vacation.msg"	/* vacation message */

#define	VIT_KEY		"__VACATION__INTERVAL__TIMER__"	/* magic db key, incl. NUL */

DB *db = NULL;
const char *argv0 = "vacation";
int debug = 0;
const char *vdbfilename = PATH_VDB;
char *vdbdir = NULL;
const char *vmsgfilename = PATH_VMSG;
const char * const version_id = " ";

int main __P((int, char **));
mbxlst_t *mbox_alloc __P((char *));
void dbclose __P((void));
void list_db __P((void));
void preload_db __P((void));
int isjunkmail __P((char *));
int isautoresponder __P((char *));
mbxlst_t *readheaders __P((mbxlst_t *, char **, char **, char **, int *));
void tossbody __P((void));
int isrecent __P((char *));
int sendmessage __P((const char *, char *, char *));
int istome __P((const char *, mbxlst_t *));
void setfrom __P((char *, char *));
void setinterval __P((time_t, int));
void saverecent __P((char *, size_t, time_t));
void usage __P((const char *));

extern char *optarg;
extern int   optind;
extern int   optopt;
extern int   opterr;
extern int   optreset;

int
main(argc, argv)
	int argc;
	char *argv[];
{
	mbxlst_t *aliases = NULL;	/* me and all my aliases.... */
	mbxlst_t *from = NULL;		/* names from "reply-to", or "from" */
	char *returnpath = NULL;	/* address from "return-path" header */
	char *precedence = NULL;	/* the "precedence" header contents */
	char *subject = NULL;		/* the "subject" header contents */
	int tome = 0;			/* was the message addressed to me? */
	struct passwd *pw;
	time_t now;
	time_t interval;
	int ch;
	int init_db = 0;
	int do_list_db = 0;
	int do_preload_db = 0;
	int zflag = 0;
	mbxlst_t *cur;

	argv0 = (argv0 = strrchr(argv[0], '/')) ? argv0 + 1 : argv[0];
	interval = DAYSPERWEEK * SECSPERDAY;
	opterr = 0;			/* don't let getopt() print errors */

	/*
	 * We need this right away [usage() calls syslog()], but we might
	 * change it if certain command-line options are given....
	 */
	openlog(argv0, 0, LOG_MAIL);

	while ((ch = getopt(argc, argv, "a:f:dIilm:r:Vxz")) != -1) {
# define UNKNWN_MSG		"unknown option flag: '-?'"
# define UNKNWN_MSG_OPT_LOC	(sizeof(UNKNWN_MSG) - 3) /* i.e. the question mark */
		char unknwn_msg[sizeof(UNKNWN_MSG)];

		switch ((char) ch) {
		case 'a':
			cur = mbox_alloc(optarg);
			cur->next = aliases;
			aliases = cur;
			break;
		case 'd':
			++debug;
			closelog();
			openlog(argv0, LOG_PERROR, LOG_MAIL);
			break;
		case 'f':
			vdbfilename = optarg;
			if (*vdbfilename == '/') {
				usage("db not relative pathname!");
			}
			break;
		case 'I':		/* backward compatible */
		case 'i':		/* init the database */
			init_db = 1;
			break;
		case 'l':
			do_list_db = 1;
			closelog();
			openlog(argv0, LOG_PERROR, LOG_USER); /* should just do stderr? */
			break;
		case 'm':
			vmsgfilename = optarg;
			break;
		case 'r':
			if (strncasecmp(optarg, "inf", (size_t) 3) == 0) {
				interval = (time_t) ~0; /* well, not really infinity! */
			} else if (isdigit((int) *optarg)) {
				interval = atol(optarg) * SECSPERDAY;
				if (interval <= 0) { /* don't allow zero! */
					usage("interval not greater than zero!");
				}
			} else {
				usage("interval not a number");
			}
			break;
		case 'V':
			;
			break;
		case 'x':
			do_preload_db = 1;
			break;
		case 'z':		/* we'll leave this undocumented.... */
			zflag = 1;
			break;
		case '?':
		default:
			strcpy(unknwn_msg, UNKNWN_MSG);
			unknwn_msg[UNKNWN_MSG_OPT_LOC] = optopt;
			usage(unknwn_msg);
		}
	}
	argc -= optind;
	argv += optind;

	if (argc > 1 ||
	    (argc == 1 && (init_db || do_preload_db || do_list_db)) ||
	    (init_db && do_list_db) ||
	    (do_preload_db && do_list_db)) {
		usage("invalid combination of parameters"); /* silly to allow these combinations... */
	}
	if (!argc) {
		if (!(pw = getpwuid(getuid()))) {
			syslog(LOG_ERR, "no such user uid %u.", (u_int) getuid());
			exit(EX_NOUSER);
			/* NOTREACHED */
		}
	} else if (!(pw = getpwnam(*argv))) {
		syslog(LOG_ERR, "no such user %s, as given on command-line", *argv);
		exit(EX_NOUSER);
		/* NOTREACHED */
	}
	cur = mbox_alloc(pw->pw_name);
	cur->next = aliases;
	aliases = cur;
	vdbdir = safe_strdup(pw->pw_dir);
	if (chdir(vdbdir)) {
		syslog(LOG_NOTICE, "could not chdir(%s), home for %s: %m.", vdbdir, pw->pw_name);
		exit(1);
		/* NOTREACHED */
	}
	db = dbopen(vdbfilename, (O_RDWR | O_CREAT | (init_db ? O_TRUNC : 0)),
		    (S_IRUSR | S_IWUSR),
		    DB_HASH,
		    (void *) NULL);
	if (!db) {
		syslog(LOG_NOTICE, "dbopen(%s/%s): %m.", vdbdir, vdbfilename);
		exit(EX_NOINPUT);
		/* NOTREACHED */
	}
	if (atexit(dbclose) == -1) {
		syslog(LOG_NOTICE, "atexit(dbclose): %m.");
		if ((db->close)(db) == -1) {
			syslog(LOG_NOTICE, "dbclose(%s/%s): %m.", vdbdir, vdbfilename);
		}
		exit(EX_OSERR);
		/* NOTREACHED */
	}
	if (!do_list_db) {
		setinterval(interval, init_db);
	}
	if (do_preload_db) {
		preload_db();
	}
	if (do_list_db) {
		list_db();
	}
	if (init_db || do_preload_db || do_list_db) {
		exit(0);		/* we're done */
		/* NOTREACHED */
	}

	rfc2822_clear_done_headers();
	if (debug > 1) {
		syslog(LOG_DEBUG, "Reading message...");
	}
	from = readheaders(aliases, &returnpath, &precedence, &subject, &tome);
	tossbody();			/* so mailer doesn't get SIGPIPE */
	if (!from) {
		syslog(LOG_NOTICE, "Could not find any reply address.");
		exit(EX_PROTOCOL);
		/* NOTREACHED */
	}
	if (!tome) {
		if (debug) {
			syslog(LOG_DEBUG, "Ignoring mail not addressed to me from %s.",
			       from ? from->mailbox : "(nobody)");
		}
		exit(0);
		/* NOTREACHED */
	}
	if (isjunkmail(precedence)) {
		if (debug) {
			syslog(LOG_DEBUG, "ignoring precedence '%s' mail.", precedence);
		}
		exit(0);
		/* NOTREACHED */
	}
	if (isautoresponder(returnpath)) {
		exit(0);
		/* NOTREACHED */
	}
	(void) time(&now);
	for (cur = from; cur; cur = cur->next) {
		if (cur->start_group || !cur->mailbox) { /* start or end of group sub-list */
			continue;
		}
		if (isautoresponder(cur->mailbox)) {
			continue;
		}
		if (!isrecent(cur->mailbox)) {
			saverecent(cur->mailbox, strlen(cur->mailbox), now);
			/* XXX error check? */
			(void) sendmessage(zflag ? "<>" : pw->pw_name, cur->mailbox, subject);
		} else if (debug) {
			syslog(LOG_DEBUG, "Have sent a reply to %s recently.", cur->mailbox);
		}
	}

	exit(0);
	/* NOTREACHED */
}

void *
safe_malloc(size_t len)
{
	void *np;

	if (len == 0) {
		return NULL;
	}
	if (!(np = malloc(len))) {
		syslog(LOG_NOTICE, "malloc() failed [for UID %d]: %m", getuid());
		exit(EX_OSERR);
		/* NOTREACHED */
	}

	return np;
}

void *
safe_calloc(size_t nelem, size_t size)
{
	void *np;

	if (!nelem || !size) {
		return NULL;
	}
	if (!(np = calloc(nelem, size))) {
		syslog(LOG_NOTICE, "calloc() failed [for UID %d]: %m", getuid());
		exit(EX_OSERR);
		/* NOTREACHED */
	}

	return np;
}

void
safe_free(void **p)
{
	if (*p) {
		free(*p);
		*p = 0;
	}

	return;
}

char *
safe_strdup(str)
	const char *str;
{
	char *retval = strdup(str);

	if (!retval) {
		syslog(LOG_NOTICE, "strdup() failed [for UID %d]: %m", getuid());
		exit(EX_OSERR);
		/* NOTREACHED */
	}

	return retval;
}

/*
 * find the beginning of the string not containing any chars in charset
 */
char *
strrchrs(start, end, charset)
	char *start;
	char *end;
	const char *charset;
{
	char *p;
	const char *spanp;
	char c, sc;

	/*
	 * Walking backwards from 'end' to 'start', stop as soon as we find any
	 * character from charset.
	 */
	for (p = (char *) end; p > start;) {
		c = *p--;
		spanp = charset;
		do {
			if ((sc = *spanp++) == c) {
				return p + 1;
			}
		} while (sc != '\0');
	}

	return start;
}


/*
 * Allocate a single element of an alias list, initialize its name field to the
 * passed name, make it the head of an empty list, and return a pointer to it.
 */
mbxlst_t *
mbox_alloc(nm)
	char *nm;			/* new name */
{
	mbxlst_t *np;

	np = rfc822_new_addr();
	np->next = NULL;
	np->mailbox = safe_strdup(nm);

	return np;
}


/*
 * dbclose() -- close the db, complaining if there's an error doing so....
 *
 * registered by atexit() to be called by exit().
 */
void
dbclose()
{
	if (db && (db->close)(db) == -1) {
		syslog(LOG_NOTICE, "dbclose(%s/%s): %m.", vdbdir, vdbfilename);
	}
	return;
}

/*
 * list_db() --
 *
 * list the contents of the recent correspondents database
 */
void
list_db()
{
	DBT key, data;
	int rv;
	time_t t;
	char *user;
	size_t user_sz;

	/* if you don't have getpagesize then #define getpagesize() BUFSIZ */
	user_sz = getpagesize();
	if (!(user = (char *) malloc(user_sz))) {
		fprintf(stderr, "malloc(user, %lu) failed: '%m'\n", (unsigned long) user_sz);
		(void) (db->close)(db);
		exit(EX_OSERR);
		/* NOTREACHED */
	}
	while ((rv = (db->seq)(db, &key, &data, R_NEXT)) == 0) {
		/* ignore the interval definition entry */
		if (memcmp(key.data, VIT_KEY, MIN(key.size, sizeof(VIT_KEY))) == 0) {
			if (debug) {
				/* use memmove() not assignment for machines with alignment restrictions */
				memmove(&t, data.data, sizeof(t));
				fprintf(stderr, "read interval record: %lu seconds\n", (unsigned long) t);
			}
			continue;
		}
		if ((key.size + 1) >= user_sz) {
			user_sz = ((key.size + 1) / getpagesize()) + getpagesize();
			if (!(user = (char *) realloc(user, user_sz))) {
				fprintf(stderr, "realloc(user, %lu) failed: '%m'\n", (unsigned long) user_sz);
				(void) (db->close)(db);
				exit(EX_OSERR);		/* XXX do we really have to? */
				/* NOTREACHED */
			}
		}
		memmove(user, key.data, key.size);
		user[key.size] = '\0';		/* just to be safe... */
		if (data.size != sizeof(t)) {
			fprintf(stderr, "%s: key %s: invalid data size: %lu", vdbfilename, user, (unsigned long) data.size);
			continue;
		}
		/* use memmove() not assignment for machines with alignment restrictions */
		memmove(&t, data.data, sizeof(t));
		printf("%-54s ", user);		/* 80 - strlen(ctime()) */
		if (t == ~0) {
			puts("preloaded (never expires)");
		} else {
			fputs(ctime(&t), stdout);
		}
	}
	if (rv == -1) {
		fprintf(stderr, "%s: error reading database: %m", vdbfilename);
	}
	free(user);

	return;
}

/*
 * preload_db() --
 *
 * Pre-load the recent correspondents database with values read from stdin.
 */
void
preload_db()
{
	char *buf;
	size_t len;

	for (;;) {
		if (!(buf = fgetln(stdin, &len))) {
			if (ferror(stdin)) {
				fprintf(stderr, "preload_db: fgetln() failed: '%m'");
			}
			return;
		}
		if (*buf == '\n') {	/* XXX && len == 1 */
			continue;
		}
		if (*buf == '#') {	/* XXX && len == 1 */
			continue;
		}
                if (buf[len - 1] == '\n') {
                        --len;
		}
		saverecent(buf, len, (time_t) LONG_MAX);
	}
}

/*
 * readheaders() --
 *
 * Read mail headers, setting pointers to the coalesced contents of the
 * relevant ones, and NULL for those that are not found.
 *
 * The return value is set to a list of addresses as per the RFC-2822 algorithm
 * for searching for appropriate reply addresses.
 *
 * XXX think harder about whether or not [resent-]sender: header should be
 * used.  Normally we want to reply in the way that was suggested (i.e. with
 * the reply-to: over-riding the from:).  However when [resent-]sender: is used
 * properly the person who actually sent the message may _also_ want to see our
 * response.
 *
 * XXX always returns the value of the last of any duplicate headers, except
 * the "from: header, which is always that of the first found, unless there's a
 * "reply-to" header, in which case it's the last one found!  Phew!
 */
mbxlst_t *
readheaders(aliases, returnpathp, precedencep, subjectp, tomep)
	mbxlst_t *aliases;		/* all my possible addresses */
	char **returnpathp;		/* "return-path:" */
	char **precedencep;		/* "precedence:" */
	char **subjectp;		/* "subject:" */
	int *tomep;			/* is it to me? */
{
	char *header;
	char *colon;			/* pointer to ':' in header */
	char *to = NULL;
	char *cc = NULL;
	char *from = NULL;
	char *reply_to = NULL;
	char *resent_to = NULL;
	char *resent_cc = NULL;
	char *resent_from = NULL;
	char *resent_reply_to = NULL;
	char *p;

	assert(returnpathp);
	*returnpathp = NULL;
	assert(precedencep);
	*precedencep = NULL;
	assert(subjectp);
	*subjectp = NULL;
	assert(tomep);
	*tomep = 0;

	/*
	 * XXX we should probably choose only the first header seen, not the
	 * last (or should we?)
	 */
	while ((header = rfc2822_gethfield(stdin, &colon, FALSE))) {
		if (!colon) {
			syslog(LOG_DEBUG, "Read non-RFC-2822 header: '%s'", header);
			continue;
		}
		if (debug) {
			syslog(LOG_DEBUG, "Read header: '%s'", header);
		}
		if ((p = rfc2822_ishfield(header, colon, "return-path"))) {
			if (*returnpathp && debug) {
				syslog(LOG_DEBUG, "Another 'return-path' header overrides the previous: '%s'", p);
			}
			*returnpathp = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "from"))) {
			if (from && debug) {
				syslog(LOG_DEBUG, "Another 'from' header overrides the previous: '%s'", p);
			}
			from = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "reply-to"))) {
			if (reply_to && debug) {
				syslog(LOG_DEBUG, "Another 'reply-to' header overrides the previous: '%s'", p);
			}
			reply_to = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "subject"))) {
			if (*subjectp && debug) {
				syslog(LOG_DEBUG, "Another 'subject' header overrides the previous: '%s'", p);
			}
			*subjectp = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "to"))) {
			if (to && debug) {
				syslog(LOG_DEBUG, "Another 'to' header overrides the previous: '%s'", p);
			}
			to = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "cc"))) {
			if (cc && debug) {
				syslog(LOG_DEBUG, "Another 'cc' header overrides the previous: '%s'", p);
			}
			cc = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "resent-to"))) {
			if (resent_to && debug) {
				syslog(LOG_DEBUG, "Another 'resent-to' header overrides the previous: '%s'", p);
			}
			resent_to = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "resent-cc"))) {
			if (resent_cc && debug) {
				syslog(LOG_DEBUG, "Another 'resent-cc' header overrides the previous: '%s'", p);
			}
			resent_cc = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "resent-from"))) {
			if (resent_to && debug) {
				syslog(LOG_DEBUG, "Another 'resent-to' header overrides the previous: '%s'", p);
			}
			resent_to = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "resent-reply-to"))) {	/* RFC-2822 obsolete */
			if (resent_reply_to && debug) {
				syslog(LOG_DEBUG, "Another 'resent_reply_to' header overrides the previous: '%s'", p);
			}
			resent_reply_to = p;
			continue;
		}
		if ((p = rfc2822_ishfield(header, colon, "precedence"))) {
			if (*precedencep && debug) {
				syslog(LOG_DEBUG, "Another 'precedence' header overrides the previous: '%s'", p);
			}
			*precedencep = p;
			continue;
		}
	}
	if (!header && rfc2822_errno != RFC2822_OK) {
		syslog(LOG_INFO, "Header error: %s\n", rfc2822_strerror(rfc2822_errno));
	}
	if (resent_cc || resent_to) {
		if (resent_cc) {
			*tomep += istome(resent_cc, aliases);
		}
		if (resent_to) { 
			*tomep += istome(resent_to, aliases);
		}
		if (resent_from) {
			from = resent_from;
		}
		if (resent_reply_to) {
			from = resent_reply_to; /* resent-reply-to: over-rides resent-from: */
		}
	} else {
		if (to) {
			*tomep += istome(to, aliases);
		}
		if (cc) {
			*tomep += istome(cc, aliases);
		}
		if (reply_to) {
			from = reply_to;	/* reply-to: over-rides from: */
		}
	}

	return rfc822_parse_addrlist((mbxlst_t *) NULL, from);
}

/*
 * tossbody() -- bit-bucket the remainder of stdin
 *
 * we do this so that mailers don't have to decide whether getting a SIGPIPE
 * while writing to the pipe we read from is a bad thing or not....
 *
 * All errors reading from the MTA are ignored too as by now all the
 * information necessary has been gathered from the headers.
 */
void
tossbody()
{
	char junk[BUFSIZ];

	while (fread(junk, sizeof(junk), (size_t) 1, stdin) > 0) {
		;
	}

	return;
}

/*
 * istome() --
 *
 * For now we just assume that the address will either have an '@' separator or
 * fully match the alias.  This will fail badly for route addresses.  We really
 * should parse out just the mailbox part, but our rfc822 parser library
 * doesn't yet have such a capability....
 */
int
istome(hdr, aliases)
	const char *hdr;		/* header contents */
	mbxlst_t *aliases;
{
	mbxlst_t *addressees;
	mbxlst_t *cur;
	mbxlst_t *np;
	addressees = rfc822_parse_addrlist((mbxlst_t *) NULL, hdr);

	/* for every alias... */
	for (cur = aliases; cur; cur = cur->next) {
		if (cur->start_group || !cur->mailbox) { /* start or end of group sub-list */
			continue;
		}
		/* check for a match with any address in 'hdr' */
		for (np = addressees; np; np = np->next) {
			char at;
			char *atp;
			char *mbox = safe_strdup(np->mailbox);

			atp = rfc822_end_of_mbox(mbox);
			at = *atp;
			*atp = '\0';

			if (debug > 1) {
				syslog(LOG_DEBUG, "istome(): checking alias '%s' against mailbox '%s' found in address '%s'", cur->mailbox, mbox, np->mailbox);
			}
			if (strcasecmp(cur->mailbox, mbox) == 0) {
				if (debug) {
					syslog(LOG_DEBUG, "istome(): found alias '%s' in '%s'", cur->mailbox, hdr);
				}
				*atp = at;	/* we don't have to, but what the heck.... */
				safe_free((void **)&mbox);
				rfc822_free_addr_list(&addressees);

				return 1;	/* any match is good enough! */
			}
			*atp = at;		/* we don't have to, but what the heck.... */
			safe_free((void **) &mbox);
		}
	}
	rfc822_free_addr_list(&addressees);
	if (debug) {
		syslog(LOG_DEBUG, "istome(): did not find anything useful in '%s'", hdr);
	}

        return 0;
}

/*
 * isjunkmail() --
 *
 * return (1) if mail seems to be from an auto-responder, or is listed with a
 * precedence that indicates it should not recieve a personal response.
 */
int
isjunkmail(precedence)
	char *precedence;			/* precedence: contents */
{
	/*
	 * XXX what about multiple values in precedence?  We only test the
	 * first one for now...
	 */
	if (precedence) {
		if (strncasecmp(precedence, "junk", (size_t) 4) == 0 ||
		    strncasecmp(precedence, "bulk", (size_t) 4) == 0 ||
		    strncasecmp(precedence, "list", (size_t) 4) == 0) {
			return 1;
		}
	}

	return 0;
}

/*
 * isautoresponder() --
 *
 * Test an address to see if it's from a well-known auto-responder address,
 * i.e. something that is in effect generating a response to us, such as a
 * bounce message or mailing list administrative reply, etc....
 */
int
isautoresponder(from)
	char *from;
{
	static struct ignore {
		const char *i_name;
		size_t i_len;
	} ignore_senders[] = {
#define S_ENTRY(str)	{ str, (sizeof(str) - 1) }
		S_ENTRY("-request"),	/* usually mailing lists */
		S_ENTRY("mailer-daemon"), /* usually a bounce */
		S_ENTRY("listserv"),	/* mailing list manager program */
		S_ENTRY("mailer"),	/* XXX ???? */
		S_ENTRY("-relay"),	/* XXX ???? */
		S_ENTRY("-outgoing"),	/* XXX some mailing lists */
#undef S_ENTRY
		{ NULL, 0 }
	};
	struct ignore *cur;
	size_t len;
	char *p;

	if (!from) {
		return 0;
	}
	/*
	 * Check if the _prefix_ of the address matches...  some mailing lists
	 * use this more arcane owner address format, particularly that most
	 * broken MLM, Lsoft's LISTSERV (as of the last inspection of it).
	 */
	if (strncmp(from, "owner-", sizeof("owner-") - 1) == 0) {
		if (debug) {
			syslog(LOG_DEBUG, "isautoresponder(): a mailing list owner '%s'", from);
		}
		return 1;
	}
	/*
	 * now test to see if the _suffix_ of the mailbox name matches any of
	 * the strings given in ignore_senders
	 */
	p = rfc822_end_of_mbox(from);
	len = p - from;
	for (cur = ignore_senders; cur->i_name; ++cur) {
		if (len >= cur->i_len && strncasecmp(cur->i_name, p - cur->i_len, cur->i_len) == 0) {
			if (debug) {
				syslog(LOG_DEBUG, "isautoresponder(%s): matches '%s'", from, cur->i_name);
			}
			return 1;
		}
	}

	return 0;
}

/*
 * isrecent() --
 *
 * find out if a reply message was sent to the specified address recently.
 *
 * uses memmove() instead of assignment of data field to the time_t variables
 * in order to accomodate machines with alignment restrictions
 */
int
isrecent(from)
	char *from;
{
	DBT key;
	DBT data;
	time_t then;				/* last time sent... */
	time_t delay;				/* time to wait... */
	time_t now = time((time_t *) NULL);
	char *domain;
	char vit[] = VIT_KEY;			/* discard const using array */

	/* get interval time */
	key.data = vit;
	key.size = sizeof(VIT_KEY);		/* include the NUL */
	if (!vit || (db->get)(db, &key, &data, 0) == -1) {
		delay = DAYSPERWEEK * SECSPERDAY; /* the default will do */
	} else {
		memmove(&delay, data.data, sizeof(delay));
	}

	/*
	 * if '-r inifinity' was given to initialise the database then always
	 * claim the address was recently used...
	 */
	if (delay == (time_t) LONG_MAX) {
		return 1;
	}
	/* get record for this address */
	key.data = from;
	key.size = strlen(from);
	if (!(db->get)(db, &key, &data, 0)) {
		memmove(&then, data.data, sizeof(then));
		if (then + delay > now) {
			return 1;
		}
	}

	/* get record for the domain of this address */
        if (!(domain = strchr(from, '@'))) {
                return 0;
	}
	key.data = domain;
	key.size = strlen(key.data);
	if (!(db->get)(db, &key, &data, 0)) {
		memmove(&then, data.data, sizeof(then));
		if (then + delay > now) {
			return 1;
		}
	}
	return 0;
}

/*
 * setinterval() --
 *
 * store the reply interval under the special key.
 */
void
setinterval(interval, init_db)
	time_t interval;
	int init_db;				/* first time? */
{
	DBT key, data;
	char vit[] = VIT_KEY;			/* discard const using array */

	key.data = vit;
	key.size = sizeof(VIT_KEY);		/* include the NUL! */
	data.data = &interval;
	data.size = sizeof(interval);
	(void) (db->put)(db, &key, &data, 0);
	if (debug) {
		syslog(LOG_DEBUG, "setinterval(): have %s ~/%s interval at %lu seconds",
		       init_db ? "initialized" : "reset",
		       PATH_VDB,
		       (unsigned long) interval);
	}
	return;
}

/*
 * saverecent() --
 *
 * store that this user knows about the vacation.
 */
void
saverecent(from, len, tr)
	char *from;				/* sender's address */
	size_t len;				/* length of from */
	time_t tr;				/* time address received */
{
	DBT key, data;

	key.data = from;
	key.size = len;				/* don't include NUL! */
	data.data = &tr;
	data.size = sizeof(tr);
	(void) (db->put)(db, &key, &data, 0);
	if (debug) {
		syslog(LOG_DEBUG, "saverecent(): put ~/%s: from='%s'[%lu], time='%lu'",
		       PATH_VDB,
		       from,
		       (unsigned long) len,
		       (unsigned long) tr);
	}
	return;
}

/*
 * sendmessage() --
 *
 * start a sendmail process to send the vacation file to the sender
 *
 * A "Precedence: bulk" header is automatically added to the message.
 *
 * Returns true if message was apparently sent OK.
 */
int
sendmessage(myname, dest, subject)
	const char *myname;		/* name for 'sendmail -f' */
	char *dest;			/* i.e. the reply destination */
	char *subject;			/* incoming's subject header contents */
{
	FILE *mfp;			/* vacation message file */
	FILE *sfp;			/* pipe stream to sendmail child */
	int i;
	int pvect[2];
	char *header;			/* poiter to start of header */
	char *colon;			/* pointer to ':' in header */
	char buf[BUFSIZ];

	if (debug) {
		syslog(LOG_DEBUG, "Replying to %s.", dest);
	}
	if (!(mfp = fopen(vmsgfilename, "r"))) {
		syslog(LOG_NOTICE, "Cannot open ~%s/%s file: %m", myname, vmsgfilename);
		return 0;
	}
	/*
	 * XXX If popen(3) didn't use /bin/sh then we could use it, but
	 * otherwise it's best to avoid even though we're not giving any
	 * possible way whatsoever for the sender to affect the commandline
	 * itself.....
	 */
	if (pipe(pvect) < 0) {
		syslog(LOG_ERR, "pipe() to sendmail failed: %m");
		return 0;
	}
	/*
	 * XXX should we loop a few times to be more resilient to resource
	 * starvation problems?
	 */
	if ((i = vfork()) < 0) {
		syslog(LOG_ERR, "fork() for sendmail failed: %m");
		return 0;
	}
	if (i == 0) {
		dup2(pvect[0], 0);		/* XXX error check! */
		close(pvect[0]);		/* XXX error check? */
		close(pvect[1]);		/* XXX error check? */
		close(fileno(mfp));		/* XXX error check? */
		execl(_PATH_SENDMAIL,
		      "sendmail",
		      debug ? (debug > 1 ? "-v99" : "-v9") : "-oeq",
		      "-t",
		      (char *) NULL);
		syslog(LOG_ERR, "vacation: can't exec %s: %m", _PATH_SENDMAIL);
		_exit(EX_OSERR);		/* just the child... */
		/* NOTREACHED */
	}
	close(pvect[0]);
	if (!(sfp = fdopen(pvect[1], "w"))) {
		syslog(LOG_ERR, "fdopen() pipe to sendmail failed: %m");
		return 0;
	}
	/*
	 * XXX we should probably try to do better error checking on output!
	 * [though we should get a SIGPIPE if a write() inside STDIO fails...]
	 */
	rfc2822_clear_done_headers();
	fprintf(sfp, "To: %s\n", dest);		/* note we use '-t' above! */
	while ((header = rfc2822_gethfield(mfp, &colon, TRUE))) {
		char *svar;
		char *rest;

		if ((rest = rfc2822_ishfield(header, colon, "precedence")) ||
		    (rest = rfc2822_ishfield(header, colon, "to"))) {
			if (debug) {
				syslog(LOG_DEBUG, "Ignoring '%s' header in %s", header, vmsgfilename);
			}
			continue;
		}
		/*
		 * XXX we should probably try re-folding the headers nicely...
		 */
		svar = NULL;
		if (subject) {
			svar = strstr(colon, "$SUBJECT");
		}
		if (svar) {
			rest = svar + sizeof("$SUBJECT") - 1;
			*svar = '\0';		/* tromp on '$' */
			fputs(header, sfp);	/* output up to '$' */
			fputs(subject, sfp);	/* output subject */
			fputs(rest, sfp);	/* output rest of buf */
		} else {
			fputs(header, sfp);	/* output the whole thing */
		}
	}
	if (!header && rfc2822_errno != RFC2822_OK) {
		syslog(LOG_INFO, "%s: Header error: %s\n", vmsgfilename, rfc2822_strerror(rfc2822_errno));
	}
	fprintf(sfp, "Precedence: bulk\n");
	fprintf(sfp, "X-BSD-Vacation-V2: %s\n", version_id);
	fputc('\n', sfp);			/* header separator */
	/*
	 * now spew out the rest of the message body
	 *
	 * XXX Should we do $SUBJECT substitution in the body too?
	 */
	while (fgets(buf, (int) sizeof(buf), mfp)) {
		fputs(buf, sfp);		/* XXX error check! */
	}
	(void) fclose(mfp);
	if (fclose(sfp) == EOF) {
		syslog(LOG_ERR, "fclose(): pipe to sendmail failed: %m");
		return 0;
	}
	/* XXX waitpid(i) and complain and return false if child failed */

	return 1;
}

/*
 * usage() -- spew about command-line errors.
 */
void
usage(msg)
	const char *msg;
{
	syslog(LOG_NOTICE, "UID %u has bad command-line invocation%s%s",
	       getuid(),
	       msg && *msg ? ": " : "",
	       msg && *msg ? msg : ".");
	if (db) {
		(void) (db->close)(db);
	}
	exit(EX_USAGE);
	/* NOTREACHED */
}
