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