vacation.c revision 66494
1/*
2 * Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
3 *	All rights reserved.
4 * Copyright (c) 1983, 1987, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 * Copyright (c) 1983 Eric P. Allman.  All rights reserved.
7 *
8 * By using this file, you agree to the terms and conditions set
9 * forth in the LICENSE file which can be found at the top level of
10 * the sendmail distribution.
11 *
12 */
13
14#ifndef lint
15static char copyright[] =
16"@(#) Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.\n\
17	All rights reserved.\n\
18     Copyright (c) 1983, 1987, 1993\n\
19	The Regents of the University of California.  All rights reserved.\n\
20     Copyright (c) 1983 Eric P. Allman.  All rights reserved.\n";
21#endif /* ! lint */
22
23#ifndef lint
24static char id[] = "@(#)$Id: vacation.c,v 8.68.4.7 2000/09/05 21:48:45 gshapiro Exp $";
25#endif /* ! lint */
26
27#include <ctype.h>
28#include <stdlib.h>
29#include <syslog.h>
30#include <time.h>
31#include <unistd.h>
32#ifdef EX_OK
33# undef EX_OK		/* unistd.h may have another use for this */
34#endif /* EX_OK */
35#include <sysexits.h>
36
37#include "sendmail/sendmail.h"
38#include "libsmdb/smdb.h"
39
40#if defined(__hpux) && !defined(HPUX11)
41# undef syslog		/* Undo hard_syslog conf.h change */
42#endif /* defined(__hpux) && !defined(HPUX11) */
43
44#ifndef _PATH_SENDMAIL
45# define _PATH_SENDMAIL "/usr/lib/sendmail"
46#endif /* ! _PATH_SENDMAIL */
47
48#define ONLY_ONCE	((time_t) 0)	/* send at most one reply */
49#define INTERVAL_UNDEF	((time_t) (-1))	/* no value given */
50
51uid_t	RealUid;
52gid_t	RealGid;
53char	*RealUserName;
54uid_t	RunAsUid;
55uid_t	RunAsGid;
56char	*RunAsUserName;
57int	Verbose = 2;
58bool	DontInitGroups = FALSE;
59uid_t	TrustedUid = 0;
60BITMAP256 DontBlameSendmail;
61
62/*
63**  VACATION -- return a message to the sender when on vacation.
64**
65**	This program is invoked as a message receiver.  It returns a
66**	message specified by the user to whomever sent the mail, taking
67**	care not to return a message too often to prevent "I am on
68**	vacation" loops.
69*/
70
71#define	VDB	".vacation"		/* vacation database */
72#define	VMSG	".vacation.msg"		/* vacation message */
73#define SECSPERDAY	(60 * 60 * 24)
74#define DAYSPERWEEK	7
75
76#ifndef TRUE
77# define TRUE	1
78# define FALSE	0
79#endif /* ! TRUE */
80
81#ifndef __P
82# ifdef __STDC__
83#  define __P(protos)	protos
84# else /* __STDC__ */
85#  define __P(protos)	()
86#  define const
87# endif /* __STDC__ */
88#endif /* ! __P */
89
90typedef struct alias
91{
92	char *name;
93	struct alias *next;
94} ALIAS;
95
96ALIAS *Names = NULL;
97
98SMDB_DATABASE *Db;
99
100char From[MAXLINE];
101
102#if _FFR_DEBUG
103void (*msglog)(int, const char *, ...) = &syslog;
104static void debuglog __P((int, const char *, ...));
105#else /* _FFR_DEBUG */
106# define msglog		syslog
107#endif /* _FFR_DEBUG */
108
109static void eatmsg __P((void));
110
111/* exit after reading input */
112#define EXITIT(excode)	{ \
113				eatmsg(); \
114				exit(excode); \
115			}
116int
117main(argc, argv)
118	int argc;
119	char **argv;
120{
121	bool iflag, emptysender, exclude;
122#if _FFR_LISTDB
123	bool lflag = FALSE;
124#endif /* _FFR_LISTDB */
125	int mfail = 0, ufail = 0;
126	int ch;
127	int result;
128	long sff;
129	time_t interval;
130	struct passwd *pw;
131	ALIAS *cur;
132	char *dbfilename = VDB;
133	char *msgfilename = VMSG;
134	char *name;
135	SMDB_USER_INFO user_info;
136	static char rnamebuf[MAXNAME];
137	extern int optind, opterr;
138	extern char *optarg;
139	extern void usage __P((void));
140	extern void setinterval __P((time_t));
141	extern void readheaders __P((void));
142	extern bool recent __P((void));
143	extern void setreply __P((char *, time_t));
144	extern void sendmessage __P((char *, char *, bool));
145	extern void xclude __P((FILE *));
146#if _FFR_LISTDB
147#define EXITM(excode)	{ \
148				if (!iflag && !lflag) \
149					eatmsg(); \
150				exit(excode); \
151			}
152#else /* _FFR_LISTDB */
153#define EXITM(excode)	{ \
154				if (!iflag) \
155					eatmsg(); \
156				exit(excode); \
157			}
158#endif /* _FFR_LISTDB */
159
160	/* Vars needed to link with smutil */
161	clrbitmap(DontBlameSendmail);
162	RunAsUid = RealUid = getuid();
163	RunAsGid = RealGid = getgid();
164	pw = getpwuid(RealUid);
165	if (pw != NULL)
166	{
167		if (strlen(pw->pw_name) > MAXNAME - 1)
168			pw->pw_name[MAXNAME] = '\0';
169		snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
170	}
171	else
172		snprintf(rnamebuf, sizeof rnamebuf,
173			 "Unknown UID %d", (int) RealUid);
174	RunAsUserName = RealUserName = rnamebuf;
175
176#ifdef LOG_MAIL
177	openlog("vacation", LOG_PID, LOG_MAIL);
178#else /* LOG_MAIL */
179	openlog("vacation", LOG_PID);
180#endif /* LOG_MAIL */
181
182	opterr = 0;
183	iflag = FALSE;
184	emptysender = FALSE;
185	exclude = FALSE;
186	interval = INTERVAL_UNDEF;
187	*From = '\0';
188
189#if _FFR_DEBUG && _FFR_LISTDB
190# define OPTIONS		"a:df:Iilm:r:s:t:xz"
191#else /* _FFR_DEBUG && _FFR_LISTDB */
192# if _FFR_DEBUG
193#  define OPTIONS		"a:df:Iim:r:s:t:xz"
194# else /* _FFR_DEBUG */
195#  if _FFR_LISTDB
196#   define OPTIONS		"a:f:Iilm:r:s:t:xz"
197#  else /* _FFR_LISTDB */
198#   define OPTIONS		"a:f:Iim:r:s:t:xz"
199#  endif /* _FFR_LISTDB */
200# endif /* _FFR_DEBUG */
201#endif /* _FFR_DEBUG && _FFR_LISTDB */
202
203	while (mfail == 0 && ufail == 0 &&
204	       (ch = getopt(argc, argv, OPTIONS)) != -1)
205	{
206		switch((char)ch)
207		{
208		  case 'a':			/* alias */
209			cur = (ALIAS *)malloc((u_int)sizeof(ALIAS));
210			if (cur == NULL)
211			{
212				mfail++;
213				break;
214			}
215			cur->name = optarg;
216			cur->next = Names;
217			Names = cur;
218			break;
219
220#if _FFR_DEBUG
221		case 'd':			/* debug mode */
222			msglog = &debuglog;
223			break;
224#endif /* _FFR_DEBUG */
225
226
227		  case 'f':		/* alternate database */
228			dbfilename = optarg;
229			break;
230
231		  case 'I':			/* backward compatible */
232		  case 'i':			/* init the database */
233			iflag = TRUE;
234			break;
235
236#if _FFR_LISTDB
237		  case 'l':
238			lflag = TRUE;		/* list the database */
239			break;
240#endif /* _FFR_LISTDB */
241
242		  case 'm':		/* alternate message file */
243			msgfilename = optarg;
244			break;
245
246		  case 'r':
247			if (isascii(*optarg) && isdigit(*optarg))
248			{
249				interval = atol(optarg) * SECSPERDAY;
250				if (interval < 0)
251					ufail++;
252			}
253			else
254				interval = ONLY_ONCE;
255			break;
256
257		  case 's':		/* alternate sender name */
258			(void) strlcpy(From, optarg, sizeof From);
259			break;
260
261		  case 't':		/* SunOS: -t1d (default expire) */
262			break;
263
264		  case 'x':
265			exclude = TRUE;
266			break;
267
268		  case 'z':
269			emptysender = TRUE;
270			break;
271
272		  case '?':
273		  default:
274			ufail++;
275			break;
276		}
277	}
278	argc -= optind;
279	argv += optind;
280
281	if (mfail != 0)
282	{
283		msglog(LOG_NOTICE,
284		       "vacation: can't allocate memory for alias.\n");
285		EXITM(EX_TEMPFAIL);
286	}
287	if (ufail != 0)
288		usage();
289
290	if (argc != 1)
291	{
292		if (!iflag &&
293#if _FFR_LISTDB
294		    !lflag &&
295#endif /* _FFR_LISTDB */
296		    !exclude)
297			usage();
298		if ((pw = getpwuid(getuid())) == NULL)
299		{
300			msglog(LOG_ERR,
301			       "vacation: no such user uid %u.\n", getuid());
302			EXITM(EX_NOUSER);
303		}
304	}
305#if _FFR_BLACKBOX
306	name = *argv;
307#else /* _FFR_BLACKBOX */
308	else if ((pw = getpwnam(*argv)) == NULL)
309	{
310		msglog(LOG_ERR, "vacation: no such user %s.\n", *argv);
311		EXITM(EX_NOUSER);
312	}
313	name = pw->pw_name;
314	if (chdir(pw->pw_dir) != 0)
315	{
316		msglog(LOG_NOTICE,
317		       "vacation: no such directory %s.\n", pw->pw_dir);
318		EXITM(EX_NOINPUT);
319	}
320#endif /* _FFR_BLACKBOX */
321	user_info.smdbu_id = pw->pw_uid;
322	user_info.smdbu_group_id = pw->pw_gid;
323	(void) strlcpy(user_info.smdbu_name, pw->pw_name,
324		       SMDB_MAX_USER_NAME_LEN);
325
326	sff = SFF_CREAT;
327#if _FFR_BLACKBOX
328	if (getegid() != getgid())
329		RunAsGid = user_info.smdbu_group_id = getegid();
330
331	sff |= SFF_NOPATHCHECK|SFF_OPENASROOT;
332#endif /* _FFR_BLACKBOX */
333
334	result = smdb_open_database(&Db, dbfilename,
335				    O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
336				    S_IRUSR|S_IWUSR, sff,
337				    SMDB_TYPE_DEFAULT, &user_info, NULL);
338	if (result != SMDBE_OK)
339	{
340		msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
341		       errstring(result));
342		EXITM(EX_DATAERR);
343	}
344
345#if _FFR_LISTDB
346	if (lflag)
347	{
348		static void listdb __P((void));
349
350		listdb();
351		(void)Db->smdb_close(Db);
352		exit(EX_OK);
353	}
354#endif /* _FFR_LISTDB */
355
356	if (interval != INTERVAL_UNDEF)
357		setinterval(interval);
358
359	if (iflag)
360	{
361		result = Db->smdb_close(Db);
362		if (!exclude)
363			exit(EX_OK);
364	}
365
366	if (exclude)
367	{
368		xclude(stdin);
369		result = Db->smdb_close(Db);
370		EXITM(EX_OK);
371	}
372
373	if ((cur = (ALIAS *)malloc((u_int)sizeof(ALIAS))) == NULL)
374	{
375		msglog(LOG_NOTICE,
376		       "vacation: can't allocate memory for username.\n");
377		EXITM(EX_OSERR);
378	}
379	cur->name = name;
380	cur->next = Names;
381	Names = cur;
382
383	readheaders();
384	if (!recent())
385	{
386		time_t now;
387
388		(void) time(&now);
389		setreply(From, now);
390		result = Db->smdb_close(Db);
391		sendmessage(name, msgfilename, emptysender);
392	}
393	else
394		result = Db->smdb_close(Db);
395	exit(EX_OK);
396	/* NOTREACHED */
397	return EX_OK;
398}
399
400/*
401** EATMSG -- read stdin till EOF
402**
403**	Parameters:
404**		none.
405**
406**	Returns:
407**		nothing.
408**
409*/
410static void
411eatmsg()
412{
413	/*
414	**  read the rest of the e-mail and ignore it to avoid problems
415	**  with EPIPE in sendmail
416	*/
417	while (getc(stdin) != EOF)
418		continue;
419}
420
421/*
422** READHEADERS -- read mail headers
423**
424**	Parameters:
425**		none.
426**
427**	Returns:
428**		nothing.
429**
430**	Side Effects:
431**		may exit().
432**
433*/
434void
435readheaders()
436{
437	bool tome, cont;
438	register char *p;
439	register ALIAS *cur;
440	char buf[MAXLINE];
441	extern bool junkmail __P((char *));
442	extern bool nsearch __P((char *, char *));
443
444	cont = tome = FALSE;
445	while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
446	{
447		switch(*buf)
448		{
449		  case 'F':		/* "From " */
450			cont = FALSE;
451			if (strncmp(buf, "From ", 5) == 0)
452			{
453				bool quoted = FALSE;
454
455				p = buf + 5;
456				while (*p != '\0')
457				{
458					/* escaped character */
459					if (*p == '\\')
460					{
461						p++;
462						if (*p == '\0')
463						{
464							msglog(LOG_NOTICE,
465							       "vacation: badly formatted \"From \" line.\n");
466							EXITIT(EX_DATAERR);
467						}
468					}
469					else if (*p == '"')
470						quoted = !quoted;
471					else if (*p == '\r' || *p == '\n')
472						break;
473					else if (*p == ' ' && !quoted)
474						break;
475					p++;
476				}
477				if (quoted)
478				{
479					msglog(LOG_NOTICE,
480					       "vacation: badly formatted \"From \" line.\n");
481					EXITIT(EX_DATAERR);
482				}
483				*p = '\0';
484
485				/* ok since both strings have MAXLINE length */
486				if (*From == '\0')
487					(void)strlcpy(From, buf + 5,
488						      sizeof From);
489				if ((p = strchr(buf + 5, '\n')) != NULL)
490					*p = '\0';
491				if (junkmail(buf + 5))
492					EXITIT(EX_OK);
493			}
494			break;
495
496		  case 'P':		/* "Precedence:" */
497		  case 'p':
498			cont = FALSE;
499			if (strlen(buf) <= 10 ||
500			    strncasecmp(buf, "Precedence", 10) != 0 ||
501			    (buf[10] != ':' && buf[10] != ' ' &&
502			     buf[10] != '\t'))
503				break;
504			if ((p = strchr(buf, ':')) == NULL)
505				break;
506			while (*++p != '\0' && isascii(*p) && isspace(*p));
507			if (*p == '\0')
508				break;
509			if (strncasecmp(p, "junk", 4) == 0 ||
510			    strncasecmp(p, "bulk", 4) == 0 ||
511			    strncasecmp(p, "list", 4) == 0)
512				EXITIT(EX_OK);
513			break;
514
515		  case 'C':		/* "Cc:" */
516		  case 'c':
517			if (strncasecmp(buf, "Cc:", 3) != 0)
518				break;
519			cont = TRUE;
520			goto findme;
521
522		  case 'T':		/* "To:" */
523		  case 't':
524			if (strncasecmp(buf, "To:", 3) != 0)
525				break;
526			cont = TRUE;
527			goto findme;
528
529		  default:
530			if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
531			{
532				cont = FALSE;
533				break;
534			}
535findme:
536			for (cur = Names;
537			     !tome && cur != NULL;
538			     cur = cur->next)
539				tome = nsearch(cur->name, buf);
540		}
541	}
542	if (!tome)
543		EXITIT(EX_OK);
544	if (*From == '\0')
545	{
546		msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
547		EXITIT(EX_DATAERR);
548	}
549}
550
551/*
552** NSEARCH --
553**	do a nice, slow, search of a string for a substring.
554**
555**	Parameters:
556**		name -- name to search.
557**		str -- string in which to search.
558**
559**	Returns:
560**		is name a substring of str?
561**
562*/
563bool
564nsearch(name, str)
565	register char *name, *str;
566{
567	register size_t len;
568	register char *s;
569
570	len = strlen(name);
571
572	for (s = str; *s != '\0'; ++s)
573	{
574		/*
575		**  Check to make sure that the string matches and
576		**  the previous character is not an alphanumeric and
577		**  the next character after the match is not an alphanumeric.
578		**
579		**  This prevents matching "eric" to "derick" while still
580		**  matching "eric" to "<eric+detail>".
581		*/
582
583		if (tolower(*s) == tolower(*name) &&
584		    strncasecmp(name, s, len) == 0 &&
585		    (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
586		    (!isascii(*(s + len)) || !isalnum(*(s + len))))
587			return TRUE;
588	}
589	return FALSE;
590}
591
592/*
593** JUNKMAIL --
594**	read the header and return if automagic/junk/bulk/list mail
595**
596**	Parameters:
597**		from -- sender address.
598**
599**	Returns:
600**		is this some automated/junk/bulk/list mail?
601**
602*/
603bool
604junkmail(from)
605	char *from;
606{
607	register size_t len;
608	register char *p;
609	register struct ignore *cur;
610	static struct ignore
611	{
612		char	*name;
613		size_t	len;
614	} ignore[] =
615	{
616		{ "-request",		8	},
617		{ "postmaster",		10	},
618		{ "uucp",		4	},
619		{ "mailer-daemon",	13	},
620		{ "mailer",		6	},
621		{ "-relay",		6	},
622		{ NULL,			0	}
623	};
624
625	/*
626	 * This is mildly amusing, and I'm not positive it's right; trying
627	 * to find the "real" name of the sender, assuming that addresses
628	 * will be some variant of:
629	 *
630	 * From site!site!SENDER%site.domain%site.domain@site.domain
631	 */
632	if ((p = strchr(from, '%')) == NULL &&
633	    (p = strchr(from, '@')) == NULL)
634	{
635		if ((p = strrchr(from, '!')) != NULL)
636			++p;
637		else
638			p = from;
639		for (; *p; ++p)
640			continue;
641	}
642	len = p - from;
643	for (cur = ignore; cur->name != NULL; ++cur)
644	{
645		if (len >= cur->len &&
646		    strncasecmp(cur->name, p - cur->len, cur->len) == 0)
647			return TRUE;
648	}
649	return FALSE;
650}
651
652#define	VIT	"__VACATION__INTERVAL__TIMER__"
653
654/*
655** RECENT --
656**	find out if user has gotten a vacation message recently.
657**
658**	Parameters:
659**		none.
660**
661**	Returns:
662**		TRUE iff user has gotten a vacation message recently.
663**
664*/
665bool
666recent()
667{
668	SMDB_DBENT key, data;
669	time_t then, next;
670	bool trydomain = FALSE;
671	int st;
672	char *domain;
673
674	memset(&key, '\0', sizeof key);
675	memset(&data, '\0', sizeof data);
676
677	/* get interval time */
678	key.data.data = VIT;
679	key.data.size = sizeof(VIT);
680
681	st = Db->smdb_get(Db, &key, &data, 0);
682	if (st != SMDBE_OK)
683		next = SECSPERDAY * DAYSPERWEEK;
684	else
685		memmove(&next, data.data.data, sizeof(next));
686
687	memset(&data, '\0', sizeof data);
688
689	/* get record for this address */
690	key.data.data = From;
691	key.data.size = strlen(From);
692
693	do
694	{
695		st = Db->smdb_get(Db, &key, &data, 0);
696		if (st == SMDBE_OK)
697		{
698			memmove(&then, data.data.data, sizeof(then));
699			if (next == ONLY_ONCE || then == ONLY_ONCE ||
700			    then + next > time(NULL))
701				return TRUE;
702		}
703		if ((trydomain = !trydomain) &&
704		    (domain = strchr(From, '@')) != NULL)
705		{
706			key.data.data = domain;
707			key.data.size = strlen(domain);
708		}
709	} while (trydomain);
710	return FALSE;
711}
712
713/*
714** SETINTERVAL --
715**	store the reply interval
716**
717**	Parameters:
718**		interval -- time interval for replies.
719**
720**	Returns:
721**		nothing.
722**
723**	Side Effects:
724**		stores the reply interval in database.
725*/
726void
727setinterval(interval)
728	time_t interval;
729{
730	SMDB_DBENT key, data;
731
732	memset(&key, '\0', sizeof key);
733	memset(&data, '\0', sizeof data);
734
735	key.data.data = VIT;
736	key.data.size = sizeof(VIT);
737	data.data.data = (char*) &interval;
738	data.data.size = sizeof(interval);
739	(void)(Db->smdb_put)(Db, &key, &data, 0);
740}
741
742/*
743** SETREPLY --
744**	store that this user knows about the vacation.
745**
746**	Parameters:
747**		from -- sender address.
748**		when -- last reply time.
749**
750**	Returns:
751**		nothing.
752**
753**	Side Effects:
754**		stores user/time in database.
755*/
756void
757setreply(from, when)
758	char *from;
759	time_t when;
760{
761	SMDB_DBENT key, data;
762
763	memset(&key, '\0', sizeof key);
764	memset(&data, '\0', sizeof data);
765
766	key.data.data = from;
767	key.data.size = strlen(from);
768	data.data.data = (char*) &when;
769	data.data.size = sizeof(when);
770	(void)(Db->smdb_put)(Db, &key, &data, 0);
771}
772
773/*
774** XCLUDE --
775**	add users to vacation db so they don't get a reply.
776**
777**	Parameters:
778**		f -- file pointer with list of address to exclude
779**
780**	Returns:
781**		nothing.
782**
783**	Side Effects:
784**		stores users in database.
785*/
786void
787xclude(f)
788	FILE *f;
789{
790	char buf[MAXLINE], *p;
791
792	if (f == NULL)
793		return;
794	while (fgets(buf, sizeof buf, f))
795	{
796		if ((p = strchr(buf, '\n')) != NULL)
797			*p = '\0';
798		setreply(buf, ONLY_ONCE);
799	}
800}
801
802/*
803** SENDMESSAGE --
804**	exec sendmail to send the vacation file to sender
805**
806**	Parameters:
807**		myname -- user name.
808**		msgfn -- name of file with vacation message.
809**		emptysender -- use <> as sender address?
810**
811**	Returns:
812**		nothing.
813**
814**	Side Effects:
815**		sends vacation reply.
816*/
817void
818sendmessage(myname, msgfn, emptysender)
819	char *myname;
820	char *msgfn;
821	bool emptysender;
822{
823	FILE *mfp, *sfp;
824	int i;
825	int pvect[2];
826	char buf[MAXLINE];
827
828	mfp = fopen(msgfn, "r");
829	if (mfp == NULL)
830	{
831		if (msgfn[0] == '/')
832			msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
833		else
834			msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
835			       myname, msgfn);
836		exit(EX_NOINPUT);
837	}
838	if (pipe(pvect) < 0)
839	{
840		msglog(LOG_ERR, "vacation: pipe: %s", errstring(errno));
841		exit(EX_OSERR);
842	}
843	i = fork();
844	if (i < 0)
845	{
846		msglog(LOG_ERR, "vacation: fork: %s", errstring(errno));
847		exit(EX_OSERR);
848	}
849	if (i == 0)
850	{
851		(void) dup2(pvect[0], 0);
852		(void) close(pvect[0]);
853		(void) close(pvect[1]);
854		(void) fclose(mfp);
855		if (emptysender)
856			myname = "<>";
857		(void) execl(_PATH_SENDMAIL, "sendmail", "-oi",
858			     "-f", myname, "--", From, NULL);
859		msglog(LOG_ERR, "vacation: can't exec %s: %s",
860			_PATH_SENDMAIL, errstring(errno));
861		exit(EX_UNAVAILABLE);
862	}
863	/* check return status of the following calls? XXX */
864	(void) close(pvect[0]);
865	if ((sfp = fdopen(pvect[1], "w")) != NULL)
866	{
867		(void) fprintf(sfp, "To: %s\n", From);
868		(void) fprintf(sfp, "Auto-Submitted: auto-generated\n");
869		while (fgets(buf, sizeof buf, mfp))
870			(void) fputs(buf, sfp);
871		(void) fclose(mfp);
872		(void) fclose(sfp);
873	}
874	else
875	{
876		(void) fclose(mfp);
877		msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
878		exit(EX_UNAVAILABLE);
879	}
880}
881
882void
883usage()
884{
885	msglog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias]%s [-f db]%s [-m msg] [-r interval] [-s sender] [-t time] [-x] [-z] login\n",
886	       getuid(),
887#if _FFR_DEBUG
888	       " [-d]",
889#else /* _FFR_DEBUG */
890	       "",
891#endif /* _FFR_DEBUG */
892#if _FFR_LISTDB
893	       " [-l]"
894#else /* _FFR_LISTDB */
895	       ""
896#endif /* _FFR_LISTDB */
897	       );
898	exit(EX_USAGE);
899}
900
901#if _FFR_LISTDB
902/*
903** LISTDB -- list the contents of the vacation database
904**
905**	Parameters:
906**		none.
907**
908**	Returns:
909**		nothing.
910*/
911
912static void
913listdb()
914{
915	int result;
916	time_t t;
917	SMDB_CURSOR *cursor = NULL;
918	SMDB_DBENT db_key, db_value;
919
920	memset(&db_key, '\0', sizeof db_key);
921	memset(&db_value, '\0', sizeof db_value);
922
923	result = Db->smdb_cursor(Db, &cursor, 0);
924	if (result != SMDBE_OK)
925	{
926		fprintf(stderr, "vacation: set cursor: %s\n",
927			errstring(result));
928		return;
929	}
930
931	while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
932					   SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
933	{
934		/* skip magic VIT entry */
935		if ((int)db_key.data.size -1 == strlen(VIT) &&
936		    strncmp((char *)db_key.data.data, VIT,
937			    (int)db_key.data.size - 1) == 0)
938			continue;
939
940		/* skip bogus values */
941		if (db_value.data.size != sizeof t)
942		{
943			fprintf(stderr, "vacation: %.*s invalid time stamp\n",
944				(int) db_key.data.size,
945				(char *) db_key.data.data);
946			continue;
947		}
948
949		memcpy(&t, db_value.data.data, sizeof t);
950
951		if (db_key.data.size > 40)
952			db_key.data.size = 40;
953
954		printf("%-40.*s %-10s",
955		       (int) db_key.data.size, (char *) db_key.data.data,
956		       ctime(&t));
957
958		memset(&db_key, '\0', sizeof db_key);
959		memset(&db_value, '\0', sizeof db_value);
960	}
961
962	if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
963	{
964		fprintf(stderr,	"vacation: get value at cursor: %s\n",
965			errstring(result));
966		if (cursor != NULL)
967		{
968			(void) cursor->smdbc_close(cursor);
969			cursor = NULL;
970		}
971		return;
972	}
973	(void) cursor->smdbc_close(cursor);
974	cursor = NULL;
975}
976#endif /* _FFR_LISTDB */
977
978#if _FFR_DEBUG
979/*
980** DEBUGLOG -- write message to standard error
981**
982**	Append a message to the standard error for the convenience of
983**	end-users debugging without access to the syslog messages.
984**
985**	Parameters:
986**		i -- syslog log level
987**		fmt -- string format
988**
989**	Returns:
990**		nothing.
991*/
992
993/*VARARGS2*/
994static void
995#ifdef __STDC__
996debuglog(int i, const char *fmt, ...)
997#else /* __STDC__ */
998debuglog(i, fmt, va_alist)
999	int i;
1000	const char *fmt;
1001	va_dcl
1002#endif /* __STDC__ */
1003
1004{
1005	VA_LOCAL_DECL
1006
1007	VA_START(fmt);
1008	vfprintf(stderr, fmt, ap);
1009	VA_END;
1010}
1011#endif /* _FFR_DEBUG */
1012
1013/*VARARGS1*/
1014void
1015#ifdef __STDC__
1016message(const char *msg, ...)
1017#else /* __STDC__ */
1018message(msg, va_alist)
1019	const char *msg;
1020	va_dcl
1021#endif /* __STDC__ */
1022{
1023	const char *m;
1024	VA_LOCAL_DECL
1025
1026	m = msg;
1027	if (isascii(m[0]) && isdigit(m[0]) &&
1028	    isascii(m[1]) && isdigit(m[1]) &&
1029	    isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
1030		m += 4;
1031	VA_START(msg);
1032	(void) vfprintf(stderr, m, ap);
1033	VA_END;
1034	(void) fprintf(stderr, "\n");
1035}
1036
1037/*VARARGS1*/
1038void
1039#ifdef __STDC__
1040syserr(const char *msg, ...)
1041#else /* __STDC__ */
1042syserr(msg, va_alist)
1043	const char *msg;
1044	va_dcl
1045#endif /* __STDC__ */
1046{
1047	const char *m;
1048	VA_LOCAL_DECL
1049
1050	m = msg;
1051	if (isascii(m[0]) && isdigit(m[0]) &&
1052	    isascii(m[1]) && isdigit(m[1]) &&
1053	    isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
1054		m += 4;
1055	VA_START(msg);
1056	(void) vfprintf(stderr, m, ap);
1057	VA_END;
1058	(void) fprintf(stderr, "\n");
1059}
1060
1061void
1062dumpfd(fd, printclosed, logit)
1063	int fd;
1064	bool printclosed;
1065	bool logit;
1066{
1067	return;
1068}
1069