/*
 * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 * Copyright (c) 1983, 1987, 1993
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1983 Eric P. Allman.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#include <sm/gen.h>

SM_IDSTR(copyright,
"@(#) Copyright (c) 1999-2001 Sendmail, Inc. and its suppliers.\n\
	All rights reserved.\n\
     Copyright (c) 1983, 1987, 1993\n\
	The Regents of the University of California.  All rights reserved.\n\
     Copyright (c) 1983 Eric P. Allman.  All rights reserved.\n")

SM_IDSTR(id, "@(#)$Id: vacation.c,v 1.1.1.4 2003/02/22 09:24:56 zarzycki Exp $")


#include <ctype.h>
#include <stdlib.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#ifdef EX_OK
# undef EX_OK		/* unistd.h may have another use for this */
#endif /* EX_OK */
#include <sm/sysexits.h>

#include <sm/cf.h>
#include <sm/mbdb.h>
#include "sendmail/sendmail.h"
#include <sendmail/pathnames.h>
#include "libsmdb/smdb.h"

#define ONLY_ONCE	((time_t) 0)	/* send at most one reply */
#define INTERVAL_UNDEF	((time_t) (-1))	/* no value given */

uid_t	RealUid;
gid_t	RealGid;
char	*RealUserName;
uid_t	RunAsUid;
uid_t	RunAsGid;
char	*RunAsUserName;
int	Verbose = 2;
bool	DontInitGroups = false;
uid_t	TrustedUid = 0;
BITMAP256 DontBlameSendmail;

/*
**  VACATION -- return a message to the sender when on vacation.
**
**	This program is invoked as a message receiver.  It returns 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.
*/

#define	VDB	".vacation"		/* vacation database */
#define	VMSG	".vacation.msg"		/* vacation message */
#define SECSPERDAY	(60 * 60 * 24)
#define DAYSPERWEEK	7

typedef struct alias
{
	char *name;
	struct alias *next;
} ALIAS;

ALIAS *Names = NULL;

SMDB_DATABASE *Db;

char From[MAXLINE];

#if defined(__hpux) || defined(__osf__)
# ifndef SM_CONF_SYSLOG_INT
#  define SM_CONF_SYSLOG_INT	1
# endif /* SM_CONF_SYSLOG_INT */
#endif /* defined(__hpux) || defined(__osf__) */

#if SM_CONF_SYSLOG_INT
# define SYSLOG_RET_T	int
# define SYSLOG_RET	return 0
#else /* SM_CONF_SYSLOG_INT */
# define SYSLOG_RET_T	void
# define SYSLOG_RET
#endif /* SM_CONF_SYSLOG_INT */

typedef SYSLOG_RET_T SYSLOG_T __P((int, const char *, ...));
SYSLOG_T *msglog = syslog;
static SYSLOG_RET_T debuglog __P((int, const char *, ...));
static void eatmsg __P((void));
static void listdb __P((void));

/* exit after reading input */
#define EXITIT(excode) \
{ \
	eatmsg(); \
	return excode; \
}

#define EXITM(excode) \
{ \
	if (!initdb && !list) \
		eatmsg(); \
	exit(excode); \
}

int
main(argc, argv)
	int argc;
	char **argv;
{
	bool alwaysrespond = false;
	bool initdb, exclude;
	bool runasuser = false;
	bool list = false;
	int mfail = 0, ufail = 0;
	int ch;
	int result;
	long sff;
	time_t interval;
	struct passwd *pw;
	ALIAS *cur;
	char *dbfilename = NULL;
	char *msgfilename = NULL;
	char *cfpath = NULL;
	char *name;
	char *returnaddr = NULL;
	SMDB_USER_INFO user_info;
	static char rnamebuf[MAXNAME];
	extern int optind, opterr;
	extern char *optarg;
	extern void usage __P((void));
	extern void setinterval __P((time_t));
	extern int readheaders __P((bool));
	extern bool recent __P((void));
	extern void setreply __P((char *, time_t));
	extern void sendmessage __P((char *, char *, char *));
	extern void xclude __P((SM_FILE_T *));

	/* Vars needed to link with smutil */
	clrbitmap(DontBlameSendmail);
	RunAsUid = RealUid = getuid();
	RunAsGid = RealGid = getgid();
	pw = getpwuid(RealUid);
	if (pw != NULL)
	{
		if (strlen(pw->pw_name) > MAXNAME - 1)
			pw->pw_name[MAXNAME] = '\0';
		sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
	}
	else
		sm_snprintf(rnamebuf, sizeof rnamebuf,
			    "Unknown UID %d", (int) RealUid);
	RunAsUserName = RealUserName = rnamebuf;

# ifdef LOG_MAIL
	openlog("vacation", LOG_PID, LOG_MAIL);
# else /* LOG_MAIL */
	openlog("vacation", LOG_PID);
# endif /* LOG_MAIL */

	opterr = 0;
	initdb = false;
	exclude = false;
	interval = INTERVAL_UNDEF;
	*From = '\0';


#define OPTIONS	"a:C:df:Iijlm:R:r:s:t:Uxz"

	while (mfail == 0 && ufail == 0 &&
	       (ch = getopt(argc, argv, OPTIONS)) != -1)
	{
		switch((char)ch)
		{
		  case 'a':			/* alias */
			cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS));
			if (cur == NULL)
			{
				mfail++;
				break;
			}
			cur->name = optarg;
			cur->next = Names;
			Names = cur;
			break;

		  case 'C':
			cfpath = optarg;
			break;

		  case 'd':			/* debug mode */
			msglog = debuglog;
			break;

		  case 'f':		/* alternate database */
			dbfilename = optarg;
			break;

		  case 'I':			/* backward compatible */
		  case 'i':			/* init the database */
			initdb = true;
			break;

#if _FFR_RESPOND_ALL
		  case 'j':
			alwaysrespond = true;
			break;
#endif /* _FFR_RESPOND_ALL */

		  case 'l':
			list = true;		/* list the database */
			break;

		  case 'm':		/* alternate message file */
			msgfilename = optarg;
			break;

#if _FFR_RETURN_ADDR
		  case 'R':
			returnaddr = optarg;
			break;
#endif /* _FFR_RETURN_ADDR */

		  case 'r':
			if (isascii(*optarg) && isdigit(*optarg))
			{
				interval = atol(optarg) * SECSPERDAY;
				if (interval < 0)
					ufail++;
			}
			else
				interval = ONLY_ONCE;
			break;

		  case 's':		/* alternate sender name */
			(void) sm_strlcpy(From, optarg, sizeof From);
			break;

		  case 't':		/* SunOS: -t1d (default expire) */
			break;

		  case 'U':		/* run as single user mode */
			runasuser = true;
			break;

		  case 'x':
			exclude = true;
			break;

		  case 'z':
			returnaddr = "<>";
			break;

		  case '?':
		  default:
			ufail++;
			break;
		}
	}
	argc -= optind;
	argv += optind;

	if (mfail != 0)
	{
		msglog(LOG_NOTICE,
		       "vacation: can't allocate memory for alias.\n");
		EXITM(EX_TEMPFAIL);
	}
	if (ufail != 0)
		usage();

	if (argc != 1)
	{
		if (!initdb && !list && !exclude)
			usage();
		if ((pw = getpwuid(getuid())) == NULL)
		{
			msglog(LOG_ERR,
			       "vacation: no such user uid %u.\n", getuid());
			EXITM(EX_NOUSER);
		}
		name = pw->pw_name;
		user_info.smdbu_id = pw->pw_uid;
		user_info.smdbu_group_id = pw->pw_gid;
		(void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
				  SMDB_MAX_USER_NAME_LEN);
		if (chdir(pw->pw_dir) != 0)
		{
			msglog(LOG_NOTICE,
			       "vacation: no such directory %s.\n",
			       pw->pw_dir);
			EXITM(EX_NOINPUT);
		}
	}
	else if (runasuser)
	{
		name = *argv;
		if (dbfilename == NULL || msgfilename == NULL)
		{
			msglog(LOG_NOTICE,
			       "vacation: -U requires setting both -f and -m\n");
			EXITM(EX_NOINPUT);
		}
		user_info.smdbu_id = pw->pw_uid;
		user_info.smdbu_group_id = pw->pw_gid;
		(void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
			       SMDB_MAX_USER_NAME_LEN);
	}
	else
	{
		int err;
		SM_CF_OPT_T mbdbname;
		SM_MBDB_T user;

		cfpath = getcfname(0, 0, SM_GET_SENDMAIL_CF, cfpath);
		mbdbname.opt_name = "MailboxDatabase";
		mbdbname.opt_val = "pw";
		(void) sm_cf_getopt(cfpath, 1, &mbdbname);
		err = sm_mbdb_initialize(mbdbname.opt_val);
		if (err != EX_OK)
		{
			msglog(LOG_ERR,
			       "vacation: can't open mailbox database: %s.\n",
			       sm_strexit(err));
			EXITM(err);
		}
		err = sm_mbdb_lookup(*argv, &user);
		if (err == EX_NOUSER)
		{
			msglog(LOG_ERR, "vacation: no such user %s.\n", *argv);
			EXITM(EX_NOUSER);
		}
		if (err != EX_OK)
		{
			msglog(LOG_ERR,
			       "vacation: can't read mailbox database: %s.\n",
			       sm_strexit(err));
			EXITM(err);
		}
		name = user.mbdb_name;
		if (chdir(user.mbdb_homedir) != 0)
		{
			msglog(LOG_NOTICE,
			       "vacation: no such directory %s.\n",
			       user.mbdb_homedir);
			EXITM(EX_NOINPUT);
		}
		user_info.smdbu_id = user.mbdb_uid;
		user_info.smdbu_group_id = user.mbdb_gid;
		(void) sm_strlcpy(user_info.smdbu_name, user.mbdb_name,
			       SMDB_MAX_USER_NAME_LEN);
	}

	if (dbfilename == NULL)
		dbfilename = VDB;
	if (msgfilename == NULL)
		msgfilename = VMSG;

	sff = SFF_CREAT;
	if (getegid() != getgid())
	{
		/* Allow a set-group-ID vacation binary */
		RunAsGid = user_info.smdbu_group_id = getegid();
		sff |= SFF_OPENASROOT;
	}
	if (getuid() == 0)
	{
		/* Allow root to initialize user's vacation databases */
		sff |= SFF_OPENASROOT|SFF_ROOTOK;

		/* ... safely */
		sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY;
	}


	result = smdb_open_database(&Db, dbfilename,
				    O_CREAT|O_RDWR | (initdb ? O_TRUNC : 0),
				    S_IRUSR|S_IWUSR, sff,
				    SMDB_TYPE_DEFAULT, &user_info, NULL);
	if (result != SMDBE_OK)
	{
		msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
		       sm_errstring(result));
		EXITM(EX_DATAERR);
	}

	if (list)
	{
		listdb();
		(void) Db->smdb_close(Db);
		exit(EX_OK);
	}

	if (interval != INTERVAL_UNDEF)
		setinterval(interval);

	if (initdb && !exclude)
	{
		(void) Db->smdb_close(Db);
		exit(EX_OK);
	}

	if (exclude)
	{
		xclude(smioin);
		(void) Db->smdb_close(Db);
		EXITM(EX_OK);
	}

	if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
	{
		msglog(LOG_NOTICE,
		       "vacation: can't allocate memory for username.\n");
		(void) Db->smdb_close(Db);
		EXITM(EX_OSERR);
	}
	cur->name = name;
	cur->next = Names;
	Names = cur;

	result = readheaders(alwaysrespond);
	if (result == EX_OK && !recent())
	{
		time_t now;

		(void) time(&now);
		setreply(From, now);
		(void) Db->smdb_close(Db);
		sendmessage(name, msgfilename, returnaddr);
	}
	else
		(void) Db->smdb_close(Db);
	if (result == EX_NOUSER)
		result = EX_OK;
	exit(result);
}

/*
** EATMSG -- read stdin till EOF
**
**	Parameters:
**		none.
**
**	Returns:
**		nothing.
**
*/

static void
eatmsg()
{
	/*
	**  read the rest of the e-mail and ignore it to avoid problems
	**  with EPIPE in sendmail
	*/
	while (getc(stdin) != EOF)
		continue;
}

/*
** READHEADERS -- read mail headers
**
**	Parameters:
**		alwaysrespond -- respond regardless of whether msg is to me
**
**	Returns:
**		a exit code: NOUSER if no reply, OK if reply, * if error
**
**	Side Effects:
**		may exit().
**
*/

int
readheaders(alwaysrespond)
	bool alwaysrespond;
{
	bool tome, cont;
	register char *p;
	register ALIAS *cur;
	char buf[MAXLINE];
	extern bool junkmail __P((char *));
	extern bool nsearch __P((char *, char *));

	cont = false;
	tome = alwaysrespond;
	while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) &&
	       *buf != '\n')
	{
		switch(*buf)
		{
		  case 'F':		/* "From " */
			cont = false;
			if (strncmp(buf, "From ", 5) == 0)
			{
				bool quoted = false;

				p = buf + 5;
				while (*p != '\0')
				{
					/* escaped character */
					if (*p == '\\')
					{
						p++;
						if (*p == '\0')
						{
							msglog(LOG_NOTICE,
							       "vacation: badly formatted \"From \" line.\n");
							EXITIT(EX_DATAERR);
						}
					}
					else if (*p == '"')
						quoted = !quoted;
					else if (*p == '\r' || *p == '\n')
						break;
					else if (*p == ' ' && !quoted)
						break;
					p++;
				}
				if (quoted)
				{
					msglog(LOG_NOTICE,
					       "vacation: badly formatted \"From \" line.\n");
					EXITIT(EX_DATAERR);
				}
				*p = '\0';

				/* ok since both strings have MAXLINE length */
				if (*From == '\0')
					(void) sm_strlcpy(From, buf + 5,
							  sizeof From);
				if ((p = strchr(buf + 5, '\n')) != NULL)
					*p = '\0';
				if (junkmail(buf + 5))
					EXITIT(EX_NOUSER);
			}
			break;

		  case 'P':		/* "Precedence:" */
		  case 'p':
			cont = false;
			if (strlen(buf) <= 10 ||
			    strncasecmp(buf, "Precedence", 10) != 0 ||
			    (buf[10] != ':' && buf[10] != ' ' &&
			     buf[10] != '\t'))
				break;
			if ((p = strchr(buf, ':')) == NULL)
				break;
			while (*++p != '\0' && isascii(*p) && isspace(*p));
			if (*p == '\0')
				break;
			if (strncasecmp(p, "junk", 4) == 0 ||
			    strncasecmp(p, "bulk", 4) == 0 ||
			    strncasecmp(p, "list", 4) == 0)
				EXITIT(EX_NOUSER);
			break;

		  case 'C':		/* "Cc:" */
		  case 'c':
			if (strncasecmp(buf, "Cc:", 3) != 0)
				break;
			cont = true;
			goto findme;

		  case 'T':		/* "To:" */
		  case 't':
			if (strncasecmp(buf, "To:", 3) != 0)
				break;
			cont = true;
			goto findme;

		  default:
			if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
			{
				cont = false;
				break;
			}
findme:
			for (cur = Names;
			     !tome && cur != NULL;
			     cur = cur->next)
				tome = nsearch(cur->name, buf);
		}
	}
	if (!tome)
		EXITIT(EX_NOUSER);
	if (*From == '\0')
	{
		msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
		EXITIT(EX_DATAERR);
	}
	EXITIT(EX_OK);
}

/*
** NSEARCH --
**	do a nice, slow, search of a string for a substring.
**
**	Parameters:
**		name -- name to search.
**		str -- string in which to search.
**
**	Returns:
**		is name a substring of str?
**
*/

bool
nsearch(name, str)
	register char *name, *str;
{
	register size_t len;
	register char *s;

	len = strlen(name);

	for (s = str; *s != '\0'; ++s)
	{
		/*
		**  Check to make sure that the string matches and
		**  the previous character is not an alphanumeric and
		**  the next character after the match is not an alphanumeric.
		**
		**  This prevents matching "eric" to "derick" while still
		**  matching "eric" to "<eric+detail>".
		*/

		if (tolower(*s) == tolower(*name) &&
		    strncasecmp(name, s, len) == 0 &&
		    (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
		    (!isascii(*(s + len)) || !isalnum(*(s + len))))
			return true;
	}
	return false;
}

/*
** JUNKMAIL --
**	read the header and return if automagic/junk/bulk/list mail
**
**	Parameters:
**		from -- sender address.
**
**	Returns:
**		is this some automated/junk/bulk/list mail?
**
*/

struct ignore
{
	char	*name;
	size_t	len;
};

typedef struct ignore IGNORE_T;

#define MAX_USER_LEN 256	/* maximum length of local part (sender) */

/* delimiters for the local part of an address */
#define isdelim(c)	((c) == '%' || (c) == '@' || (c) == '+')

bool
junkmail(from)
	char *from;
{
	bool quot;
	char *e;
	size_t len;
	IGNORE_T *cur;
	char sender[MAX_USER_LEN];
	static IGNORE_T ignore[] =
	{
		{ "postmaster",		10	},
		{ "uucp",		4	},
		{ "mailer-daemon",	13	},
		{ "mailer",		6	},
		{ NULL,			0	}
	};

	static IGNORE_T ignorepost[] =
	{
		{ "-request",		8	},
		{ "-relay",		6	},
		{ "-owner",		6	},
		{ NULL,			0	}
	};

	static IGNORE_T ignorepre[] =
	{
		{ "owner-",		6	},
		{ NULL,			0	}
	};

	/*
	**  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:
	**
	**  From site!site!SENDER%site.domain%site.domain@site.domain
	*/

	quot = false;
	e = from;
	len = 0;
	while (*e != '\0' && (quot || !isdelim(*e)))
	{
		if (*e == '"')
		{
			quot = !quot;
			++e;
			continue;
		}
		if (*e == '\\')
		{
			if (*(++e) == '\0')
			{
				/* '\\' at end of string? */
				break;
			}
			if (len < MAX_USER_LEN)
				sender[len++] = *e;
			++e;
			continue;
		}
		if (*e == '!' && !quot)
		{
			len = 0;
			sender[len] = '\0';
		}
		else
			if (len < MAX_USER_LEN)
				sender[len++] = *e;
		++e;
	}
	if (len < MAX_USER_LEN)
		sender[len] = '\0';
	else
		sender[MAX_USER_LEN - 1] = '\0';

	if (len <= 0)
		return false;
#if 0
	if (quot)
		return false;	/* syntax error... */
#endif /* 0 */

	/* test prefixes */
	for (cur = ignorepre; cur->name != NULL; ++cur)
	{
		if (len >= cur->len &&
		    strncasecmp(cur->name, sender, cur->len) == 0)
			return true;
	}

	/*
	**  If the name is truncated, don't test the rest.
	**	We could extract the "tail" of the sender address and
	**	compare it it ignorepost, however, it seems not worth
	**	the effort.
	**	The address surely can't match any entry in ignore[]
	**	(as long as all of them are shorter than MAX_USER_LEN).
	*/

	if (len > MAX_USER_LEN)
		return false;

	/* test full local parts */
	for (cur = ignore; cur->name != NULL; ++cur)
	{
		if (len == cur->len &&
		    strncasecmp(cur->name, sender, cur->len) == 0)
			return true;
	}

	/* test postfixes */
	for (cur = ignorepost; cur->name != NULL; ++cur)
	{
		if (len >= cur->len &&
		    strncasecmp(cur->name, e - cur->len - 1,
				cur->len) == 0)
			return true;
	}
	return false;
}

#define	VIT	"__VACATION__INTERVAL__TIMER__"

/*
** RECENT --
**	find out if user has gotten a vacation message recently.
**
**	Parameters:
**		none.
**
**	Returns:
**		true iff user has gotten a vacation message recently.
**
*/

bool
recent()
{
	SMDB_DBENT key, data;
	time_t then, next;
	bool trydomain = false;
	int st;
	char *domain;

	memset(&key, '\0', sizeof key);
	memset(&data, '\0', sizeof data);

	/* get interval time */
	key.data = VIT;
	key.size = sizeof(VIT);

	st = Db->smdb_get(Db, &key, &data, 0);
	if (st != SMDBE_OK)
		next = SECSPERDAY * DAYSPERWEEK;
	else
		memmove(&next, data.data, sizeof(next));

	memset(&data, '\0', sizeof data);

	/* get record for this address */
	key.data = From;
	key.size = strlen(From);

	do
	{
		st = Db->smdb_get(Db, &key, &data, 0);
		if (st == SMDBE_OK)
		{
			memmove(&then, data.data, sizeof(then));
			if (next == ONLY_ONCE || then == ONLY_ONCE ||
			    then + next > time(NULL))
				return true;
		}
		if ((trydomain = !trydomain) &&
		    (domain = strchr(From, '@')) != NULL)
		{
			key.data = domain;
			key.size = strlen(domain);
		}
	} while (trydomain);
	return false;
}

/*
** SETINTERVAL --
**	store the reply interval
**
**	Parameters:
**		interval -- time interval for replies.
**
**	Returns:
**		nothing.
**
**	Side Effects:
**		stores the reply interval in database.
*/

void
setinterval(interval)
	time_t interval;
{
	SMDB_DBENT key, data;

	memset(&key, '\0', sizeof key);
	memset(&data, '\0', sizeof data);

	key.data = VIT;
	key.size = sizeof(VIT);
	data.data = (char*) &interval;
	data.size = sizeof(interval);
	(void) (Db->smdb_put)(Db, &key, &data, 0);
}

/*
** SETREPLY --
**	store that this user knows about the vacation.
**
**	Parameters:
**		from -- sender address.
**		when -- last reply time.
**
**	Returns:
**		nothing.
**
**	Side Effects:
**		stores user/time in database.
*/

void
setreply(from, when)
	char *from;
	time_t when;
{
	SMDB_DBENT key, data;

	memset(&key, '\0', sizeof key);
	memset(&data, '\0', sizeof data);

	key.data = from;
	key.size = strlen(from);
	data.data = (char*) &when;
	data.size = sizeof(when);
	(void) (Db->smdb_put)(Db, &key, &data, 0);
}

/*
** XCLUDE --
**	add users to vacation db so they don't get a reply.
**
**	Parameters:
**		f -- file pointer with list of address to exclude
**
**	Returns:
**		nothing.
**
**	Side Effects:
**		stores users in database.
*/

void
xclude(f)
	SM_FILE_T *f;
{
	char buf[MAXLINE], *p;

	if (f == NULL)
		return;
	while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf))
	{
		if ((p = strchr(buf, '\n')) != NULL)
			*p = '\0';
		setreply(buf, ONLY_ONCE);
	}
}

/*
** SENDMESSAGE --
**	exec sendmail to send the vacation file to sender
**
**	Parameters:
**		myname -- user name.
**		msgfn -- name of file with vacation message.
**		sender -- use as sender address
**
**	Returns:
**		nothing.
**
**	Side Effects:
**		sends vacation reply.
*/

void
sendmessage(myname, msgfn, sender)
	char *myname;
	char *msgfn;
	char *sender;
{
	SM_FILE_T *mfp, *sfp;
	int i;
	int pvect[2];
	char *pv[8];
	char buf[MAXLINE];

	mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
	if (mfp == NULL)
	{
		if (msgfn[0] == '/')
			msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
		else
			msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
			       myname, msgfn);
		exit(EX_NOINPUT);
	}
	if (pipe(pvect) < 0)
	{
		msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
		exit(EX_OSERR);
	}
	pv[0] = "sendmail";
	pv[1] = "-oi";
	pv[2] = "-f";
	if (sender != NULL)
		pv[3] = sender;
	else
		pv[3] = myname;
	pv[4] = "--";
	pv[5] = From;
	pv[6] = NULL;
	i = fork();
	if (i < 0)
	{
		msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
		exit(EX_OSERR);
	}
	if (i == 0)
	{
		(void) dup2(pvect[0], 0);
		(void) close(pvect[0]);
		(void) close(pvect[1]);
		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
		(void) execv(_PATH_SENDMAIL, pv);
		msglog(LOG_ERR, "vacation: can't exec %s: %s",
			_PATH_SENDMAIL, sm_errstring(errno));
		exit(EX_UNAVAILABLE);
	}
	/* check return status of the following calls? XXX */
	(void) close(pvect[0]);
	if ((sfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
			      (void *) &(pvect[1]),
			      SM_IO_WRONLY, NULL)) != NULL)
	{
		(void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, "To: %s\n", From);
		(void) sm_io_fprintf(sfp, SM_TIME_DEFAULT,
				     "Auto-Submitted: auto-replied\n");
		while (sm_io_fgets(mfp, SM_TIME_DEFAULT, buf, sizeof buf))
			(void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf);
		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
		(void) sm_io_close(sfp, SM_TIME_DEFAULT);
	}
	else
	{
		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
		msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
		exit(EX_UNAVAILABLE);
	}
}

void
usage()
{
	char *retusage = "";
	char *respusage = "";

#if _FFR_RETURN_ADDR
	retusage = "[-R returnaddr] ";
#endif /* _FFR_RETURN_ADDR */

#if _FFR_RESPOND_ALL
	respusage = "[-j] ";
#endif /* _FFR_RESPOND_ALL */

	msglog(LOG_NOTICE,
	       "uid %u: usage: vacation [-a alias] [-C cfpath] [-d] [-f db] [-i] %s[-l] [-m msg] %s[-r interval] [-s sender] [-t time] [-U] [-x] [-z] login\n",
	       getuid(), respusage, retusage);
	exit(EX_USAGE);
}

/*
** LISTDB -- list the contents of the vacation database
**
**	Parameters:
**		none.
**
**	Returns:
**		nothing.
*/

static void
listdb()
{
	int result;
	time_t t;
	SMDB_CURSOR *cursor = NULL;
	SMDB_DBENT db_key, db_value;

	memset(&db_key, '\0', sizeof db_key);
	memset(&db_value, '\0', sizeof db_value);

	result = Db->smdb_cursor(Db, &cursor, 0);
	if (result != SMDBE_OK)
	{
		sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
			      "vacation: set cursor: %s\n",
			      sm_errstring(result));
		return;
	}

	while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
					   SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
	{
		char *timestamp;

		/* skip magic VIT entry */
		if (db_key.size == strlen(VIT) + 1 &&
		    strncmp((char *)db_key.data, VIT,
			    (int)db_key.size - 1) == 0)
			continue;

		/* skip bogus values */
		if (db_value.size != sizeof t)
		{
			sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
				      "vacation: %.*s invalid time stamp\n",
				      (int) db_key.size, (char *) db_key.data);
			continue;
		}

		memcpy(&t, db_value.data, sizeof t);

		if (db_key.size > 40)
			db_key.size = 40;

		if (t <= 0)
		{
			/* must be an exclude */
			timestamp = "(exclusion)\n";
		}
		else
		{
			timestamp = ctime(&t);
		}
		sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
			      (int) db_key.size, (char *) db_key.data,
			      timestamp);

		memset(&db_key, '\0', sizeof db_key);
		memset(&db_value, '\0', sizeof db_value);
	}

	if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
	{
		sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
			      "vacation: get value at cursor: %s\n",
			      sm_errstring(result));
		if (cursor != NULL)
		{
			(void) cursor->smdbc_close(cursor);
			cursor = NULL;
		}
		return;
	}
	(void) cursor->smdbc_close(cursor);
	cursor = NULL;
}

/*
** DEBUGLOG -- write message to standard error
**
**	Append a message to the standard error for the convenience of
**	end-users debugging without access to the syslog messages.
**
**	Parameters:
**		i -- syslog log level
**		fmt -- string format
**
**	Returns:
**		nothing.
*/

/*VARARGS2*/
static SYSLOG_RET_T
#ifdef __STDC__
debuglog(int i, const char *fmt, ...)
#else /* __STDC__ */
debuglog(i, fmt, va_alist)
	int i;
	const char *fmt;
	va_dcl
#endif /* __STDC__ */

{
	SM_VA_LOCAL_DECL

	SM_VA_START(ap, fmt);
	sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);
	SM_VA_END(ap);
	SYSLOG_RET;
}
