xref: /freebsd/contrib/sendmail/vacation/vacation.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
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.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 
45 uid_t	RealUid;
46 gid_t	RealGid;
47 char	*RealUserName;
48 uid_t	RunAsUid;
49 gid_t	RunAsGid;
50 char	*RunAsUserName;
51 int	Verbose = 2;
52 bool	DontInitGroups = false;
53 uid_t	TrustedUid = 0;
54 BITMAP256 DontBlameSendmail;
55 
56 static int readheaders __P((bool));
57 static bool junkmail __P((char *));
58 static bool nsearch __P((char *, char *));
59 static void usage __P((void));
60 static void setinterval __P((time_t));
61 static bool recent __P((void));
62 static void setreply __P((char *, time_t));
63 static void sendmessage __P((char *, char *, char *));
64 static 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 
80 typedef struct alias
81 {
82 	char *name;
83 	struct alias *next;
84 } ALIAS;
85 
86 ALIAS *Names = NULL;
87 
88 SMDB_DATABASE *Db;
89 
90 char From[MAXLINE];
91 bool 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 
107 typedef SYSLOG_RET_T SYSLOG_T __P((int, const char *, ...));
108 SYSLOG_T *msglog = syslog;
109 static SYSLOG_RET_T debuglog __P((int, const char *, ...));
110 static void eatmsg __P((void));
111 static 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 
137 int
138 main(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 
470 static void
471 eatmsg()
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 
495 static int
496 readheaders(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 			}
597 findme:
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 
627 static bool
628 nsearch(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 
668 struct ignore
669 {
670 	char	*name;
671 	size_t	len;
672 };
673 
674 typedef 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 
681 static bool
682 junkmail(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 
819 static bool
820 recent()
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 
881 static void
882 setinterval(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 
912 static void
913 setreply(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 
943 static void
944 xclude(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 
975 static void
976 sendmessage(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 
1051 static void
1052 usage()
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 
1070 static void
1071 listdb()
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*/
1163 static SYSLOG_RET_T
1164 #ifdef __STDC__
1165 debuglog(int i, const char *fmt, ...)
1166 #else /* __STDC__ */
1167 debuglog(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