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