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