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