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