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