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