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