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