vacation.c revision 73188
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) 1999-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.16 2001/02/14 05:02:21 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
51#ifndef TRUE
52# define TRUE	1
53# define FALSE	0
54#endif /* ! TRUE */
55
56uid_t	RealUid;
57gid_t	RealGid;
58char	*RealUserName;
59uid_t	RunAsUid;
60uid_t	RunAsGid;
61char	*RunAsUserName;
62int	Verbose = 2;
63bool	DontInitGroups = FALSE;
64uid_t	TrustedUid = 0;
65BITMAP256 DontBlameSendmail;
66
67/*
68**  VACATION -- return a message to the sender when on vacation.
69**
70**	This program is invoked as a message receiver.  It returns a
71**	message specified by the user to whomever sent the mail, taking
72**	care not to return a message too often to prevent "I am on
73**	vacation" loops.
74*/
75
76#define	VDB	".vacation"		/* vacation database */
77#define	VMSG	".vacation.msg"		/* vacation message */
78#define SECSPERDAY	(60 * 60 * 24)
79#define DAYSPERWEEK	7
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				return 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 int 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 && !exclude)
360	{
361		(void) Db->smdb_close(Db);
362		exit(EX_OK);
363	}
364
365	if (exclude)
366	{
367		xclude(stdin);
368		(void) Db->smdb_close(Db);
369		EXITM(EX_OK);
370	}
371
372	if ((cur = (ALIAS *)malloc((u_int)sizeof(ALIAS))) == NULL)
373	{
374		msglog(LOG_NOTICE,
375		       "vacation: can't allocate memory for username.\n");
376		(void) Db->smdb_close(Db);
377		EXITM(EX_OSERR);
378	}
379	cur->name = name;
380	cur->next = Names;
381	Names = cur;
382
383	result = readheaders();
384	if (result == EX_OK && !recent())
385	{
386		time_t now;
387
388		(void) time(&now);
389		setreply(From, now);
390		(void) Db->smdb_close(Db);
391		sendmessage(name, msgfilename, emptysender);
392	}
393	else
394		(void) Db->smdb_close(Db);
395	if (result == EX_NOUSER)
396		result = EX_OK;
397	exit(result);
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**		a exit code: NOUSER if no reply, OK if reply, * if error
429**
430**	Side Effects:
431**		may exit().
432**
433*/
434
435int
436readheaders()
437{
438	bool tome, cont;
439	register char *p;
440	register ALIAS *cur;
441	char buf[MAXLINE];
442	extern bool junkmail __P((char *));
443	extern bool nsearch __P((char *, char *));
444
445	cont = tome = FALSE;
446	while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
447	{
448		switch(*buf)
449		{
450		  case 'F':		/* "From " */
451			cont = FALSE;
452			if (strncmp(buf, "From ", 5) == 0)
453			{
454				bool quoted = FALSE;
455
456				p = buf + 5;
457				while (*p != '\0')
458				{
459					/* escaped character */
460					if (*p == '\\')
461					{
462						p++;
463						if (*p == '\0')
464						{
465							msglog(LOG_NOTICE,
466							       "vacation: badly formatted \"From \" line.\n");
467							EXITIT(EX_DATAERR);
468						}
469					}
470					else if (*p == '"')
471						quoted = !quoted;
472					else if (*p == '\r' || *p == '\n')
473						break;
474					else if (*p == ' ' && !quoted)
475						break;
476					p++;
477				}
478				if (quoted)
479				{
480					msglog(LOG_NOTICE,
481					       "vacation: badly formatted \"From \" line.\n");
482					EXITIT(EX_DATAERR);
483				}
484				*p = '\0';
485
486				/* ok since both strings have MAXLINE length */
487				if (*From == '\0')
488					(void) strlcpy(From, buf + 5,
489						       sizeof From);
490				if ((p = strchr(buf + 5, '\n')) != NULL)
491					*p = '\0';
492				if (junkmail(buf + 5))
493					EXITIT(EX_NOUSER);
494			}
495			break;
496
497		  case 'P':		/* "Precedence:" */
498		  case 'p':
499			cont = FALSE;
500			if (strlen(buf) <= 10 ||
501			    strncasecmp(buf, "Precedence", 10) != 0 ||
502			    (buf[10] != ':' && buf[10] != ' ' &&
503			     buf[10] != '\t'))
504				break;
505			if ((p = strchr(buf, ':')) == NULL)
506				break;
507			while (*++p != '\0' && isascii(*p) && isspace(*p));
508			if (*p == '\0')
509				break;
510			if (strncasecmp(p, "junk", 4) == 0 ||
511			    strncasecmp(p, "bulk", 4) == 0 ||
512			    strncasecmp(p, "list", 4) == 0)
513				EXITIT(EX_NOUSER);
514			break;
515
516		  case 'C':		/* "Cc:" */
517		  case 'c':
518			if (strncasecmp(buf, "Cc:", 3) != 0)
519				break;
520			cont = TRUE;
521			goto findme;
522
523		  case 'T':		/* "To:" */
524		  case 't':
525			if (strncasecmp(buf, "To:", 3) != 0)
526				break;
527			cont = TRUE;
528			goto findme;
529
530		  default:
531			if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
532			{
533				cont = FALSE;
534				break;
535			}
536findme:
537			for (cur = Names;
538			     !tome && cur != NULL;
539			     cur = cur->next)
540				tome = nsearch(cur->name, buf);
541		}
542	}
543	if (!tome)
544		EXITIT(EX_NOUSER);
545	if (*From == '\0')
546	{
547		msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
548		EXITIT(EX_DATAERR);
549	}
550	EXITIT(EX_OK);
551}
552
553/*
554** NSEARCH --
555**	do a nice, slow, search of a string for a substring.
556**
557**	Parameters:
558**		name -- name to search.
559**		str -- string in which to search.
560**
561**	Returns:
562**		is name a substring of str?
563**
564*/
565bool
566nsearch(name, str)
567	register char *name, *str;
568{
569	register size_t len;
570	register char *s;
571
572	len = strlen(name);
573
574	for (s = str; *s != '\0'; ++s)
575	{
576		/*
577		**  Check to make sure that the string matches and
578		**  the previous character is not an alphanumeric and
579		**  the next character after the match is not an alphanumeric.
580		**
581		**  This prevents matching "eric" to "derick" while still
582		**  matching "eric" to "<eric+detail>".
583		*/
584
585		if (tolower(*s) == tolower(*name) &&
586		    strncasecmp(name, s, len) == 0 &&
587		    (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
588		    (!isascii(*(s + len)) || !isalnum(*(s + len))))
589			return TRUE;
590	}
591	return FALSE;
592}
593
594/*
595** JUNKMAIL --
596**	read the header and return if automagic/junk/bulk/list mail
597**
598**	Parameters:
599**		from -- sender address.
600**
601**	Returns:
602**		is this some automated/junk/bulk/list mail?
603**
604*/
605
606struct ignore
607{
608	char	*name;
609	size_t	len;
610};
611
612typedef struct ignore IGNORE_T;
613
614#define MAX_USER_LEN 256	/* maximum length of local part (sender) */
615
616/* delimiters for the local part of an address */
617#define isdelim(c)	((c) == '%' || (c) == '@' || (c) == '+')
618
619bool
620junkmail(from)
621	char *from;
622{
623	bool quot;
624	char *e;
625	size_t len;
626	IGNORE_T *cur;
627	char sender[MAX_USER_LEN];
628	static IGNORE_T ignore[] =
629	{
630		{ "postmaster",		10	},
631		{ "uucp",		4	},
632		{ "mailer-daemon",	13	},
633		{ "mailer",		6	},
634		{ NULL,			0	}
635	};
636
637	static IGNORE_T ignorepost[] =
638	{
639		{ "-request",		8	},
640		{ "-relay",		6	},
641		{ "-owner",		6	},
642		{ NULL,			0	}
643	};
644
645	static IGNORE_T ignorepre[] =
646	{
647		{ "owner-",		6	},
648		{ NULL,			0	}
649	};
650
651	/*
652	**  This is mildly amusing, and I'm not positive it's right; trying
653	**  to find the "real" name of the sender, assuming that addresses
654	**  will be some variant of:
655	**
656	**  From site!site!SENDER%site.domain%site.domain@site.domain
657	*/
658
659	quot = FALSE;
660	e = from;
661	len = 0;
662	while (*e != '\0' && (quot || !isdelim(*e)))
663	{
664		if (*e == '"')
665		{
666			quot = !quot;
667			++e;
668			continue;
669		}
670		if (*e == '\\')
671		{
672			if (*(++e) == '\0')
673			{
674				/* '\\' at end of string? */
675				break;
676			}
677			if (len < MAX_USER_LEN)
678				sender[len++] = *e;
679			++e;
680			continue;
681		}
682		if (*e == '!' && !quot)
683		{
684			len = 0;
685			sender[len] = '\0';
686		}
687		else
688			if (len < MAX_USER_LEN)
689				sender[len++] = *e;
690		++e;
691	}
692	if (len < MAX_USER_LEN)
693		sender[len] = '\0';
694	else
695		sender[MAX_USER_LEN - 1] = '\0';
696
697	if (len <= 0)
698		return FALSE;
699#if 0
700	if (quot)
701		return FALSE;	/* syntax error... */
702#endif /* 0 */
703
704	/* test prefixes */
705	for (cur = ignorepre; cur->name != NULL; ++cur)
706	{
707		if (len >= cur->len &&
708		    strncasecmp(cur->name, sender, cur->len) == 0)
709			return TRUE;
710	}
711
712	/*
713	**  If the name is truncated, don't test the rest.
714	**	We could extract the "tail" of the sender address and
715	**	compare it it ignorepost, however, it seems not worth
716	**	the effort.
717	**	The address surely can't match any entry in ignore[]
718	**	(as long as all of them are shorter than MAX_USER_LEN).
719	*/
720
721	if (len > MAX_USER_LEN)
722		return FALSE;
723
724	/* test full local parts */
725	for (cur = ignore; cur->name != NULL; ++cur)
726	{
727		if (len == cur->len &&
728		    strncasecmp(cur->name, sender, cur->len) == 0)
729			return TRUE;
730	}
731
732	/* test postfixes */
733	for (cur = ignorepost; cur->name != NULL; ++cur)
734	{
735		if (len >= cur->len &&
736		    strncasecmp(cur->name, e - cur->len - 1,
737				cur->len) == 0)
738			return TRUE;
739	}
740
741	return FALSE;
742}
743
744#define	VIT	"__VACATION__INTERVAL__TIMER__"
745
746/*
747** RECENT --
748**	find out if user has gotten a vacation message recently.
749**
750**	Parameters:
751**		none.
752**
753**	Returns:
754**		TRUE iff user has gotten a vacation message recently.
755**
756*/
757bool
758recent()
759{
760	SMDB_DBENT key, data;
761	time_t then, next;
762	bool trydomain = FALSE;
763	int st;
764	char *domain;
765
766	memset(&key, '\0', sizeof key);
767	memset(&data, '\0', sizeof data);
768
769	/* get interval time */
770	key.data = VIT;
771	key.size = sizeof(VIT);
772
773	st = Db->smdb_get(Db, &key, &data, 0);
774	if (st != SMDBE_OK)
775		next = SECSPERDAY * DAYSPERWEEK;
776	else
777		memmove(&next, data.data, sizeof(next));
778
779	memset(&data, '\0', sizeof data);
780
781	/* get record for this address */
782	key.data = From;
783	key.size = strlen(From);
784
785	do
786	{
787		st = Db->smdb_get(Db, &key, &data, 0);
788		if (st == SMDBE_OK)
789		{
790			memmove(&then, data.data, sizeof(then));
791			if (next == ONLY_ONCE || then == ONLY_ONCE ||
792			    then + next > time(NULL))
793				return TRUE;
794		}
795		if ((trydomain = !trydomain) &&
796		    (domain = strchr(From, '@')) != NULL)
797		{
798			key.data = domain;
799			key.size = strlen(domain);
800		}
801	} while (trydomain);
802	return FALSE;
803}
804
805/*
806** SETINTERVAL --
807**	store the reply interval
808**
809**	Parameters:
810**		interval -- time interval for replies.
811**
812**	Returns:
813**		nothing.
814**
815**	Side Effects:
816**		stores the reply interval in database.
817*/
818void
819setinterval(interval)
820	time_t interval;
821{
822	SMDB_DBENT key, data;
823
824	memset(&key, '\0', sizeof key);
825	memset(&data, '\0', sizeof data);
826
827	key.data = VIT;
828	key.size = sizeof(VIT);
829	data.data = (char*) &interval;
830	data.size = sizeof(interval);
831	(void) (Db->smdb_put)(Db, &key, &data, 0);
832}
833
834/*
835** SETREPLY --
836**	store that this user knows about the vacation.
837**
838**	Parameters:
839**		from -- sender address.
840**		when -- last reply time.
841**
842**	Returns:
843**		nothing.
844**
845**	Side Effects:
846**		stores user/time in database.
847*/
848void
849setreply(from, when)
850	char *from;
851	time_t when;
852{
853	SMDB_DBENT key, data;
854
855	memset(&key, '\0', sizeof key);
856	memset(&data, '\0', sizeof data);
857
858	key.data = from;
859	key.size = strlen(from);
860	data.data = (char*) &when;
861	data.size = sizeof(when);
862	(void) (Db->smdb_put)(Db, &key, &data, 0);
863}
864
865/*
866** XCLUDE --
867**	add users to vacation db so they don't get a reply.
868**
869**	Parameters:
870**		f -- file pointer with list of address to exclude
871**
872**	Returns:
873**		nothing.
874**
875**	Side Effects:
876**		stores users in database.
877*/
878void
879xclude(f)
880	FILE *f;
881{
882	char buf[MAXLINE], *p;
883
884	if (f == NULL)
885		return;
886	while (fgets(buf, sizeof buf, f))
887	{
888		if ((p = strchr(buf, '\n')) != NULL)
889			*p = '\0';
890		setreply(buf, ONLY_ONCE);
891	}
892}
893
894/*
895** SENDMESSAGE --
896**	exec sendmail to send the vacation file to sender
897**
898**	Parameters:
899**		myname -- user name.
900**		msgfn -- name of file with vacation message.
901**		emptysender -- use <> as sender address?
902**
903**	Returns:
904**		nothing.
905**
906**	Side Effects:
907**		sends vacation reply.
908*/
909void
910sendmessage(myname, msgfn, emptysender)
911	char *myname;
912	char *msgfn;
913	bool emptysender;
914{
915	FILE *mfp, *sfp;
916	int i;
917	int pvect[2];
918	char buf[MAXLINE];
919
920	mfp = fopen(msgfn, "r");
921	if (mfp == NULL)
922	{
923		if (msgfn[0] == '/')
924			msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
925		else
926			msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
927			       myname, msgfn);
928		exit(EX_NOINPUT);
929	}
930	if (pipe(pvect) < 0)
931	{
932		msglog(LOG_ERR, "vacation: pipe: %s", errstring(errno));
933		exit(EX_OSERR);
934	}
935	i = fork();
936	if (i < 0)
937	{
938		msglog(LOG_ERR, "vacation: fork: %s", errstring(errno));
939		exit(EX_OSERR);
940	}
941	if (i == 0)
942	{
943		(void) dup2(pvect[0], 0);
944		(void) close(pvect[0]);
945		(void) close(pvect[1]);
946		(void) fclose(mfp);
947		if (emptysender)
948			myname = "<>";
949		(void) execl(_PATH_SENDMAIL, "sendmail", "-oi",
950			     "-f", myname, "--", From, NULL);
951		msglog(LOG_ERR, "vacation: can't exec %s: %s",
952			_PATH_SENDMAIL, errstring(errno));
953		exit(EX_UNAVAILABLE);
954	}
955	/* check return status of the following calls? XXX */
956	(void) close(pvect[0]);
957	if ((sfp = fdopen(pvect[1], "w")) != NULL)
958	{
959		(void) fprintf(sfp, "To: %s\n", From);
960		(void) fprintf(sfp, "Auto-Submitted: auto-generated\n");
961		while (fgets(buf, sizeof buf, mfp))
962			(void) fputs(buf, sfp);
963		(void) fclose(mfp);
964		(void) fclose(sfp);
965	}
966	else
967	{
968		(void) fclose(mfp);
969		msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
970		exit(EX_UNAVAILABLE);
971	}
972}
973
974void
975usage()
976{
977	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",
978	       getuid(),
979#if _FFR_DEBUG
980	       " [-d]",
981#else /* _FFR_DEBUG */
982	       "",
983#endif /* _FFR_DEBUG */
984#if _FFR_LISTDB
985	       " [-l]"
986#else /* _FFR_LISTDB */
987	       ""
988#endif /* _FFR_LISTDB */
989	       );
990	exit(EX_USAGE);
991}
992
993#if _FFR_LISTDB
994/*
995** LISTDB -- list the contents of the vacation database
996**
997**	Parameters:
998**		none.
999**
1000**	Returns:
1001**		nothing.
1002*/
1003
1004static void
1005listdb()
1006{
1007	int result;
1008	time_t t;
1009	SMDB_CURSOR *cursor = NULL;
1010	SMDB_DBENT db_key, db_value;
1011
1012	memset(&db_key, '\0', sizeof db_key);
1013	memset(&db_value, '\0', sizeof db_value);
1014
1015	result = Db->smdb_cursor(Db, &cursor, 0);
1016	if (result != SMDBE_OK)
1017	{
1018		fprintf(stderr, "vacation: set cursor: %s\n",
1019			errstring(result));
1020		return;
1021	}
1022
1023	while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1024					   SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
1025	{
1026		/* skip magic VIT entry */
1027		if ((int)db_key.size -1 == strlen(VIT) &&
1028		    strncmp((char *)db_key.data, VIT,
1029			    (int)db_key.size - 1) == 0)
1030			continue;
1031
1032		/* skip bogus values */
1033		if (db_value.size != sizeof t)
1034		{
1035			fprintf(stderr, "vacation: %.*s invalid time stamp\n",
1036				(int) db_key.size, (char *) db_key.data);
1037			continue;
1038		}
1039
1040		memcpy(&t, db_value.data, sizeof t);
1041
1042		if (db_key.size > 40)
1043			db_key.size = 40;
1044
1045		printf("%-40.*s %-10s",
1046		       (int) db_key.size, (char *) db_key.data, ctime(&t));
1047
1048		memset(&db_key, '\0', sizeof db_key);
1049		memset(&db_value, '\0', sizeof db_value);
1050	}
1051
1052	if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1053	{
1054		fprintf(stderr,	"vacation: get value at cursor: %s\n",
1055			errstring(result));
1056		if (cursor != NULL)
1057		{
1058			(void) cursor->smdbc_close(cursor);
1059			cursor = NULL;
1060		}
1061		return;
1062	}
1063	(void) cursor->smdbc_close(cursor);
1064	cursor = NULL;
1065}
1066#endif /* _FFR_LISTDB */
1067
1068#if _FFR_DEBUG
1069/*
1070** DEBUGLOG -- write message to standard error
1071**
1072**	Append a message to the standard error for the convenience of
1073**	end-users debugging without access to the syslog messages.
1074**
1075**	Parameters:
1076**		i -- syslog log level
1077**		fmt -- string format
1078**
1079**	Returns:
1080**		nothing.
1081*/
1082
1083/*VARARGS2*/
1084static void
1085#ifdef __STDC__
1086debuglog(int i, const char *fmt, ...)
1087#else /* __STDC__ */
1088debuglog(i, fmt, va_alist)
1089	int i;
1090	const char *fmt;
1091	va_dcl
1092#endif /* __STDC__ */
1093
1094{
1095	VA_LOCAL_DECL
1096
1097	VA_START(fmt);
1098	vfprintf(stderr, fmt, ap);
1099	VA_END;
1100}
1101#endif /* _FFR_DEBUG */
1102
1103/*VARARGS1*/
1104void
1105#ifdef __STDC__
1106message(const char *msg, ...)
1107#else /* __STDC__ */
1108message(msg, va_alist)
1109	const char *msg;
1110	va_dcl
1111#endif /* __STDC__ */
1112{
1113	const char *m;
1114	VA_LOCAL_DECL
1115
1116	m = msg;
1117	if (isascii(m[0]) && isdigit(m[0]) &&
1118	    isascii(m[1]) && isdigit(m[1]) &&
1119	    isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
1120		m += 4;
1121	VA_START(msg);
1122	(void) vfprintf(stderr, m, ap);
1123	VA_END;
1124	(void) fprintf(stderr, "\n");
1125}
1126
1127/*VARARGS1*/
1128void
1129#ifdef __STDC__
1130syserr(const char *msg, ...)
1131#else /* __STDC__ */
1132syserr(msg, va_alist)
1133	const char *msg;
1134	va_dcl
1135#endif /* __STDC__ */
1136{
1137	const char *m;
1138	VA_LOCAL_DECL
1139
1140	m = msg;
1141	if (isascii(m[0]) && isdigit(m[0]) &&
1142	    isascii(m[1]) && isdigit(m[1]) &&
1143	    isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
1144		m += 4;
1145	VA_START(msg);
1146	(void) vfprintf(stderr, m, ap);
1147	VA_END;
1148	(void) fprintf(stderr, "\n");
1149}
1150
1151void
1152dumpfd(fd, printclosed, logit)
1153	int fd;
1154	bool printclosed;
1155	bool logit;
1156{
1157	return;
1158}
1159