/*
#ident "@(#)contrib/vacation:RELEASE-3_2_0_118:vacation.c,v 1.2 2003/03/25 20:34:58 woods Exp"
 */
/*
 * Copyright (c) 1983  Eric P. Allman
 *
 * Copyright (c) 1983, 1987, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Copyright (c) 2001  Greg A. Woods <woods@planix.com>
 *
 * 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.
 */

#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) 2001  Greg A. Woods <woods@planix.com>\n");
#endif /* not lint */

#ifndef lint
__RCSID("@(#)vacation:VACATION-2_0:vacation.c,v 1.1 2001/12/14 04:46:26 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>
#include <tzfile.h>
#include <unistd.h>

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

/*
 *    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.  Instances of the string "$SUBJECT" in the user-supplied reply
 * message are replaced with the content of the "subject:" header from the
 * incoming messsage.
 *
 * TODO:
 *
 * Fix the way recipient addresses are stored.  We need to parse full RFC-2822
 * address headers to do this though, and then store each individual recipient
 * without comments, etc.  Only then will the preloaded "@domain" exclusions
 * always work.
 *
 * 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.
 */

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

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

typedef struct alias {
	struct alias *next;
	const char *name;
} alias_t;

alias_t *names = NULL;			/* me and all my aliases.... */

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

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

int
main(argc, argv)
	int argc;
	char *argv[];
{
	char *sender = NULL;		/* from the "return-path" header */
	char *from = NULL;		/* from "reply-to", or "from" */
	char *precedence = NULL;	/* from the "precedence" header */
	char *subject = NULL;		/* from the "subject" header */
	int tome;
	struct passwd *pw;
	time_t interval;
	int ch;
	int init_db = 0;
	int do_list_db = 0;
	int do_preload_db = 0;
	int zflag = 0;
	alias_t *cur;

	argv0 = (argv0 = strrchr(argv[0], '/')) ? argv0 + 1 : argv[0];
	opterr = 0;
	interval = DAYSPERWEEK * SECSPERDAY;
	openlog(argv0, 0, LOG_MAIL);
	while ((ch = getopt(argc, argv, "a:f:dIilm:r:xz")) != -1) {
		switch ((char) ch) {
		case 'a':
			if (!(cur = (alias_t *) malloc((u_int) sizeof(alias_t)))) {
				syslog(LOG_NOTICE, "malloc(alias_t) failed: '%s'", strerror(errno));
				break;
			}
			cur->name = optarg;
			cur->next = names;
			names = cur;
			break;
		case 'd':
			debug = 1;
			closelog();
			openlog(argv0, LOG_PERROR, LOG_MAIL);
			break;
		case 'f':
			vdbfilename = optarg;
			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", 3) == 0)
				interval = (time_t) LONG_MAX; /* XXX not really infinity! */
			else if (isdigit((int) *optarg)) {
				interval = atol(optarg) * SECSPERDAY;
				if (interval <= 0) /* don't allow zero! */
					usage();
			} else
				usage();
			break;
		case 'x':
			do_preload_db = 1;
			break;
		case 'z':		/* we'll leave this undocumented.... */
			zflag = 1;
			break;
		case '?':
		default:
			usage();
		}
	}
	argc -= optind;

	if (argc > 1 || (argc == 1 && (init_db || do_preload_db || do_list_db)))
		usage();		/* silly to allow this... */
	if (!argc) {
		if (!(pw = getpwuid(getuid()))) {
			syslog(LOG_ERR, "no such user uid %u.", getuid());
			exit(EX_NOUSER);
		}
	} else if (!(pw = getpwnam(argv[argc]))) {
		syslog(LOG_ERR, "no such user %s as given on command-line.", argv[argc]);
		exit(EX_NOUSER);
	}
	if (!(cur = (alias_t *) malloc((u_int) sizeof(alias_t)))) {
		syslog(LOG_NOTICE, "malloc(alias_t) failed: '%s'", strerror(errno));
		exit(EX_OSERR);		/* XXX do we really have to? */
	}
	cur->name = pw->pw_name;	/* XXX pw_name is static in getpwnam()! */
	cur->next = names;
	names = cur;
	vdbdir = pw->pw_dir;		/* XXX pw_dir is static in getpwnam()! */
	if (chdir(vdbdir)) {
		syslog(LOG_NOTICE, "could not chdir(%s), home for %s: %m.", vdbdir, pw->pw_name);
		exit(1);
	}
	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);
	}
	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);
	}
	setinterval(interval);		/* always, in case we just created a new db */
	if (do_preload_db)
		preload_db();
	if (do_list_db)
		list_db();
	if (init_db || do_preload_db || do_list_db) {
		exit(0);
	}
	readheaders(&sender, &from, &precedence, &subject, &tome);
	tossbody();			/* so mailer doesn't get SIGPIPE */
	if (!tome) {
		if (debug)
			syslog(LOG_DEBUG, "Ignoring mail not addressed to me from %s.",
			       from ? from : "(nobody)");
		exit(0);
	}
	if (!from) {
		syslog(LOG_NOTICE, "Could not find any reply address.");
		exit(EX_PROTOCOL);
	}
	if (isjunkmail(sender, from, precedence)) {
		exit(0);
	}
	if (!isrecent(from)) {
		time_t now;

		(void) time(&now);
		saverecent(from, strlen(from), now);
		if (debug)
			syslog(LOG_DEBUG, "Replying to %s.", from);
		/* note that sendmessage syslog()s its own errors */
		(void) sendmessage(zflag ? "<>" : pw->pw_name, from, subject);
	} else if (debug) {
		syslog(LOG_DEBUG, "Have sent a reply to %s recently.", from);
	}

	exit(0);
	/* NOTREACHED */
}

/*
 * dbclose() -- close the db, complaining if there's an error doing so....
 *
 * 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 recipients database
 */
void
list_db()
{
	DBT key, data;
	int rv;
	time_t t;
	char *user;
	u_int 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, %u) failed: '%s'\n", user_sz, strerror(errno));
		(void) (db->close)(db);
		exit(EX_OSERR);
	}
	while ((rv = (db->seq)(db, &key, &data, R_NEXT)) == 0) {
		/* ignore the interval definition entry */
		if (memcmp(key.data, VIT, MIN(key.size, sizeof(VIT))) == 0)
			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, %u) failed: '%s'\n", user_sz, strerror(errno));
				(void) (db->close)(db);
				exit(EX_OSERR);		/* XXX do we really have to? */
			}
		}
		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: %u", vdbfilename, user, 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 0
		if (t == LONG_MAX)
			puts("preloaded (never expires)");
		else
#endif
			fputs(ctime(&t), stdout);
	}
	if (rv == -1)
		fprintf(stderr, "%s: error reading database: %s", vdbfilename, strerror(errno));
	return;
}

/*
 * preload_db() --
 *
 * Pre-load the recent recipients 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: '%s'", strerror(errno));
			return;
		}
		if (*buf == '\n')	/* XXX && len == 1 */
			continue;
		if (*buf == '#')	/* XXX && len == 1 */
			continue;
                if (buf[len - 1] == '\n')
                        --len;
		saverecent(buf, len, 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 from value is set as per the RFC-2822 algorithm for searching for an
 * appropriate reply value.
 *
 * 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!
 */
void
readheaders(senderp, fromp, precedencep, subjectp, tomep)
	char **senderp;			/* "return-path:" */
	char **fromp;			/* "from:" */
	char **precedencep;		/* "precedence:" */
	char **subjectp;		/* "subject:" */
	int *tomep;			/* is it to me? */
{
	char *header;
	char *colon;			/* pointer to ':' in header */
	char *p;

	*senderp = NULL;
	*fromp = NULL;
	*precedencep = NULL;
	*tomep = 0;
	while ((header = gethfield(stdin, &colon))) {
		if ((p = ishfield(header, colon, "return-path"))) {
			if (*precedencep && debug)
				syslog(LOG_DEBUG, "Another return-path header overrides the previous: '%s'", p);
			*senderp = p;
			continue;
		}
		if ((p = ishfield(header, colon, "from")) && !*fromp) {
			*fromp = p;
			continue;
		}
		if ((p = ishfield(header, colon, "reply-to"))) {
			if (*precedencep && debug)
				syslog(LOG_DEBUG, "Another reply-to header overrides the previous: '%s'", p);
			*fromp = p;
			continue;
		}
		if ((p = ishfield(header, colon, "subject"))) {
			if (*subjectp && debug)
				syslog(LOG_DEBUG, "Another subject header overrides the previous: '%s'", p);
			*subjectp = p;
			continue;
		}
		if ((p = ishfield(header, colon, "to"))) {
			*tomep += istome(p);
			continue;
		}
		if ((p = ishfield(header, colon, "cc"))) {
			*tomep += istome(p);
			continue;
		}
		/* XXX what about multiple values in precedence? */
		if ((p = ishfield(header, colon, "precedence"))) {
			if (*precedencep && debug)
				syslog(LOG_DEBUG, "Another precedence header overrides the previous: '%s'", p);
			*precedencep = p;
			continue;
		}
	}

	return;
}

/*
 * 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;
}

/*
 * gethfield() --
 *
 * Return the next header field found in the given message.  Return a pointer
 * to the buffer if something is found, NULL elsewise.
 *
 * "colon" is set to point to the colon in the header.  Must deal with '\'
 * continuations & other such RFC-2822 niceties.
 *
 * XXX borrowed from /usr/src/usr.bin/mail/aux.c and then hacked upon (has been
 * made immune to line length limits, etc.)....
 */
char *
gethfield(fp, colon)
	FILE *fp;
	char **colon;
{
	char *header;
	char *buf;
	size_t len;
	char *cp;

	for (;;) {
		if (!(buf = fgetln(fp, &len))) {
			if (ferror(fp))
				syslog(LOG_INFO, "Read header: fgetln() failed: '%s'", strerror(errno));
			return NULL;
		}
		if (*buf == '\n')	/* XXX && len == 1 */
			return NULL;	/* end of headers */
                if (buf[len - 1] == '\n')
                        --len;
		if (!(cp = header = (char *) calloc(len + 1, sizeof(char)))) {
			syslog(LOG_NOTICE, "Read header: calloc() failed: '%s'", strerror(errno));
			return NULL;
		}
		/* the string is NUL-terminated by virtue of calloc() */
		memcpy(header, buf, len);
		if (debug)
			syslog(LOG_DEBUG, "Read header: '%s'", header);
		while (isprint((int) *cp) && *cp != ' ' && *cp != '\t' && *cp != ':')
			cp++;
		if (*cp != ':' || cp == header) {
			free((void *) header);
			header = NULL;
			continue;	/* ignore 'From ' and other junk */
		}
		/*
		 * I guess we got ourselves a header line.
		 * Handle any wrap-arounding....
		 */
		*colon = cp;
		cp = header + len;	/* point to where the NUL goes */
		for (;;) {
			int c;
			int olen;
			char *line2;
			char *cp2;

			ungetc((c = getc(fp)), fp); /* XXX error check */
			if (c != ' ' && c != '\t')
				break;	/* next line not a continuation */
			olen = len;
			while (--cp >= header && (*cp == ' ' || *cp == '\t')) {
				;	/* trim off any trailing whitespace */
			}
			cp++;
			if ((buf = fgetln(fp, &len))) {
				syslog(LOG_INFO, "Read header: fgetln() failed: '%s'", strerror(errno));
				return NULL;
			}
			if (buf[len - 1] == '\n')
				--len;
			if (!(cp2 = line2 = (char *) calloc(len + 1, sizeof(char)))) {
				syslog(LOG_NOTICE, "Read header: calloc() failed: '%s'", strerror(errno));

				return NULL;
			}
			/* the string is NUL-terminated by virtue of calloc() */
			memcpy(line2, buf, len);
			while (*cp2 == ' ' || *cp2 == '\t')
				cp2++;	/* skip leading whitespace */
			len -= cp2 - line2;
			if (!(header = (char *) realloc((void *) header, olen + len + 1))) {
				syslog(LOG_NOTICE, "Read header: realloc() failed: '%s'", strerror(errno));
				free((void *) line2);
				return NULL;
			}
			cp = header + olen; /* must do after realloc() */
			*cp++ = ' ';
			memmove(cp, cp2, len);
			cp += len;
			len += olen;
		}
		*cp = '\0';		/* stringify the result */
		return header;
	}
	/* NOTREACHED */
}


/*
 * ishfield() --
 *
 * Check whether the passed line is an RFC-2822 header line with the desired
 * field name.  Return a pointer to the field body, or NULL if the field name
 * does not match.
 *
 * XXX borrowed from /usr/src/mail/usr.bin/aux.c and hacked upon.
 */
char *
ishfield(linebuf, colon, field)
	char linebuf[];			/* array containing header */
	char *colon;			/* pointer to ':' in header */
	char *field;			/* field name to match */
{
	char *cp = colon;

	*cp = 0;
	if (strcasecmp(linebuf, field) != 0) {
		*cp = ':';
		return NULL;
	}
	*cp = ':';
	/* skip past any whitespace after the colon */
	for (cp++; *cp == ' ' || *cp == '\t'; cp++) {
		;
	}
	if (!*cp)			/* empty fields are useless */
		return NULL;

	return cp;
}


/*
 * istome() --
 *
 * do a nice, slow, case-insensitive, search of a string for a substring for
 * every name in names.
 *
 * This is kinda lame -- we should do proper RFC-2822 parsing to find the
 * separate addresses in the header contents, and if the alias name is not
 * fully qualified then also break apart the addresses to find just the local
 * parts, and then mabye even do exact comparisons (local parts are supposed to
 * be case sensitive, though depending on how the MTA is configured, it may not
 * be treating them that way).
 */
int
istome(hdr)
	const char *hdr;		/* header contents */
{
	size_t len;
	alias_t *cur;
	const char *str = hdr;

	for (cur = names; cur; cur = cur->next) {
		for (len = strlen(cur->name); *str; ++str) {
			if (strncasecmp(cur->name, str, len) == 0 && isdelim(str[len])) {
				if (debug)
					syslog(LOG_DEBUG, "istome(): found '%s' in '%s'", cur->name, hdr);
				return (1);
			}
		}
	}

        return (0);
}

/*
 * isdelim() -- Is 'c' a delimiting character for a recipient mailbox name?
 */
int
isdelim(c)
	int c;
{
	if (isalnum(c))
		return (0);
	if (c == '_' || c == '-' || c == '.')
		return (0);

	return (1);
}

/*
 * 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(sender, from, precedence)
	char *sender;				/* return-path: contents */
	char *from;				/* from: contents */
	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", 4) == 0 ||
		    strncasecmp(precedence, "bulk", 4) == 0 ||
		    strncasecmp(precedence, "list", 4) == 0) {
			if (debug)
				syslog(LOG_DEBUG, "ignoring precedence '%s' mail.", precedence);
			return 1;
		}
	}
	if (sender && isautoresponder(sender)) {
		if (debug)
			syslog(LOG_DEBUG, "Ignoring autoresponder mail sent by '%s'", sender);
		return 1;
	}
	if (from && isautoresponder(from)) { /* unlikely if not also a sender but possible. */
		if (debug)
			syslog(LOG_DEBUG, "Ignoring autoresponder mail from '%s'", from);
		return 1;
	}
	/*
	 * XXX This is a bogus test -- we really need to parse full RFC-2822
	 * addresses to avoid finding commas in quoted mailbox parts!
	 */
	if (from && strchr(from, ',')) {
		if (debug)
			syslog(LOG_DEBUG, "Ignoring mail from multiple from addresses: '%s'", from);
		return 1;		/* multiple addresses are never junk */
	}

	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 {
		char	*name;
		int	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;
	int len;
	char *p;

	/*
	 * 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;
	}
	/*
	 * Try finding a pointer to the *END* of the sender's mailbox name.
	 *
	 * This is mildly amusing, and I'm not positive it's right; trying
	 * to find the "real" name of the sender, assuming that addresses
	 * will be some variant of:
	 *
	 *	site!site!SENDER%site.domain%site.domain@site.domain
	 */
	if (!(p = strchr(from, '%'))) {
		if (!(p = strchr(from, '@'))) {
			if ((p = strrchr(from, '!')))
				++p;
			else
				p = from;
			for (; *p; ++p) {
				;
			}
		}
	}
	len = p - from;
	/*
	 * now test to see if the suffix of the mailbox name matches any of the
	 * strings given in ignore_senders
	 */
	for (cur = ignore_senders; cur->name; ++cur) {
		if (len >= cur->len && strncasecmp(cur->name, p - cur->len, cur->len) == 0) {
			if (debug)
				syslog(LOG_DEBUG, "isautoresponder(): matches '%s'", cur->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, data;
	time_t then, next;
	char *domain;

	/* get interval time */
	key.data = VIT;
	key.size = sizeof(VIT);			/* include the NUL */
	if ((db->get)(db, &key, &data, 0))
		next = SECSPERDAY * DAYSPERWEEK;
	else
		memmove(&next, data.data, sizeof(next));

	/* get record for this address */
	key.data = from;
	key.size = strlen(key.data);
	if (!(db->get)(db, &key, &data, 0)) {
		memmove(&then, data.data, sizeof(then));
		/* XXX */
		if (next == (time_t) LONG_MAX || then + next > time((time_t *) NULL))
			return 1;
	}

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

/*
 * setinterval() --
 *
 * store the reply interval under the special key.
 */
void
setinterval(time_t interval)
{
	DBT key, data;

	key.data = VIT;
	key.size = sizeof(VIT);			/* include the NUL! */
	data.data = &interval;
	data.size = sizeof(interval);
	(void) (db->put)(db, &key, &data, 0);
}

/*
 * saverecent() --
 *
 * store that this user knows about the vacation.
 *
 * XXX should separately store multi-address fields
 *
 * XXX should strip RFC822 comments and other gunk
 */
void
saverecent(from, len, tr)
	char *from;
	size_t len;
	time_t tr;
{
	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);
}

/*
 * 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 is message 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 buf[BUFSIZ];

	if (!(mfp = fopen(vmsgfilename, "r"))) {
		syslog(LOG_NOTICE, "Cannot open ~%s/%s file: %m", myname, vmsgfilename);
		return 0;
	}
	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);
		close(pvect[0]);
		close(pvect[1]);
		close(fileno(mfp));
		execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "-t", (char *) NULL);
		syslog(LOG_ERR, "vacation: can't exec %s: %m", _PATH_SENDMAIL);
		_exit(EX_OSERR);		/* just the child... */
	}
	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!
	 * (We should get a SIGPIPE anyway if a STDIO write fails...)
	 */
	fprintf(sfp, "To: %s\n", dest);		/* see '-t' above! */
	fprintf(sfp, "Precedence: bulk\n");
	/*
	 * XXX we should probably try reading the message file in the same way
	 * we read the incoming message, by first reading the headers with
	 * gethfield(), dropping things like the two headers we write ourself,
	 * re-folding them nicely, and spitting them out one-by-one; and then
	 * finally spew the message body out with a quick loop like the one
	 * below, doing only the most basic substitutions desired.
	 */
	while (fgets(buf, sizeof(buf), mfp)) {
		char *svar;
		char *rest;

		svar = strstr(buf, "$SUBJECT");
		if (svar) {
			rest = svar + sizeof("$SUBJECT") - 1;
			*svar = '\0';		/* tromp on '$' */
			fputs(buf, sfp);	/* output up to '$' */
			fputs(subject, sfp);	/* output subject */
			fputs(rest, sfp);	/* output rest of buf */
		} else
			fputs(buf, sfp);
	}
	fclose(mfp);
	if (fclose(sfp) == EOF)
		syslog(LOG_ERR, "fclose() pipe to sendmail failed: %m");

	return 1;
}

/*
 * usage() -- spew about command-line errors.
 */
void
usage()
{
	syslog(LOG_NOTICE, "uid %u has bad vacation invocation", getuid());
	if (db) {
		(void) (db->close)(db);
	}
	exit(EX_USAGE);
}
