xref: /freebsd/contrib/sendmail/vacation/vacation.c (revision eacee0ff7ec955b32e09515246bd97b6edcd2b0f)
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 #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.131 2001/12/12 00:02:42 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, NULL);
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 
374 	result = smdb_open_database(&Db, dbfilename,
375 				    O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
376 				    S_IRUSR|S_IWUSR, sff,
377 				    SMDB_TYPE_DEFAULT, &user_info, NULL);
378 	if (result != SMDBE_OK)
379 	{
380 		msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
381 		       sm_errstring(result));
382 		EXITM(EX_DATAERR);
383 	}
384 
385 	if (lflag)
386 	{
387 		listdb();
388 		(void) Db->smdb_close(Db);
389 		exit(EX_OK);
390 	}
391 
392 	if (interval != INTERVAL_UNDEF)
393 		setinterval(interval);
394 
395 	if (iflag && !exclude)
396 	{
397 		(void) Db->smdb_close(Db);
398 		exit(EX_OK);
399 	}
400 
401 	if (exclude)
402 	{
403 		xclude(smioin);
404 		(void) Db->smdb_close(Db);
405 		EXITM(EX_OK);
406 	}
407 
408 	if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
409 	{
410 		msglog(LOG_NOTICE,
411 		       "vacation: can't allocate memory for username.\n");
412 		(void) Db->smdb_close(Db);
413 		EXITM(EX_OSERR);
414 	}
415 	cur->name = name;
416 	cur->next = Names;
417 	Names = cur;
418 
419 	result = readheaders();
420 	if (result == EX_OK && !recent())
421 	{
422 		time_t now;
423 
424 		(void) time(&now);
425 		setreply(From, now);
426 		(void) Db->smdb_close(Db);
427 		sendmessage(name, msgfilename, returnaddr);
428 	}
429 	else
430 		(void) Db->smdb_close(Db);
431 	if (result == EX_NOUSER)
432 		result = EX_OK;
433 	exit(result);
434 }
435 
436 /*
437 ** EATMSG -- read stdin till EOF
438 **
439 **	Parameters:
440 **		none.
441 **
442 **	Returns:
443 **		nothing.
444 **
445 */
446 
447 static void
448 eatmsg()
449 {
450 	/*
451 	**  read the rest of the e-mail and ignore it to avoid problems
452 	**  with EPIPE in sendmail
453 	*/
454 	while (getc(stdin) != EOF)
455 		continue;
456 }
457 
458 /*
459 ** READHEADERS -- read mail headers
460 **
461 **	Parameters:
462 **		none.
463 **
464 **	Returns:
465 **		a exit code: NOUSER if no reply, OK if reply, * if error
466 **
467 **	Side Effects:
468 **		may exit().
469 **
470 */
471 
472 int
473 readheaders()
474 {
475 	bool tome, cont;
476 	register char *p;
477 	register ALIAS *cur;
478 	char buf[MAXLINE];
479 	extern bool junkmail __P((char *));
480 	extern bool nsearch __P((char *, char *));
481 
482 	cont = tome = false;
483 	while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) &&
484 	       *buf != '\n')
485 	{
486 		switch(*buf)
487 		{
488 		  case 'F':		/* "From " */
489 			cont = false;
490 			if (strncmp(buf, "From ", 5) == 0)
491 			{
492 				bool quoted = false;
493 
494 				p = buf + 5;
495 				while (*p != '\0')
496 				{
497 					/* escaped character */
498 					if (*p == '\\')
499 					{
500 						p++;
501 						if (*p == '\0')
502 						{
503 							msglog(LOG_NOTICE,
504 							       "vacation: badly formatted \"From \" line.\n");
505 							EXITIT(EX_DATAERR);
506 						}
507 					}
508 					else if (*p == '"')
509 						quoted = !quoted;
510 					else if (*p == '\r' || *p == '\n')
511 						break;
512 					else if (*p == ' ' && !quoted)
513 						break;
514 					p++;
515 				}
516 				if (quoted)
517 				{
518 					msglog(LOG_NOTICE,
519 					       "vacation: badly formatted \"From \" line.\n");
520 					EXITIT(EX_DATAERR);
521 				}
522 				*p = '\0';
523 
524 				/* ok since both strings have MAXLINE length */
525 				if (*From == '\0')
526 					(void) sm_strlcpy(From, buf + 5,
527 							  sizeof From);
528 				if ((p = strchr(buf + 5, '\n')) != NULL)
529 					*p = '\0';
530 				if (junkmail(buf + 5))
531 					EXITIT(EX_NOUSER);
532 			}
533 			break;
534 
535 		  case 'P':		/* "Precedence:" */
536 		  case 'p':
537 			cont = false;
538 			if (strlen(buf) <= 10 ||
539 			    strncasecmp(buf, "Precedence", 10) != 0 ||
540 			    (buf[10] != ':' && buf[10] != ' ' &&
541 			     buf[10] != '\t'))
542 				break;
543 			if ((p = strchr(buf, ':')) == NULL)
544 				break;
545 			while (*++p != '\0' && isascii(*p) && isspace(*p));
546 			if (*p == '\0')
547 				break;
548 			if (strncasecmp(p, "junk", 4) == 0 ||
549 			    strncasecmp(p, "bulk", 4) == 0 ||
550 			    strncasecmp(p, "list", 4) == 0)
551 				EXITIT(EX_NOUSER);
552 			break;
553 
554 		  case 'C':		/* "Cc:" */
555 		  case 'c':
556 			if (strncasecmp(buf, "Cc:", 3) != 0)
557 				break;
558 			cont = true;
559 			goto findme;
560 
561 		  case 'T':		/* "To:" */
562 		  case 't':
563 			if (strncasecmp(buf, "To:", 3) != 0)
564 				break;
565 			cont = true;
566 			goto findme;
567 
568 		  default:
569 			if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
570 			{
571 				cont = false;
572 				break;
573 			}
574 findme:
575 			for (cur = Names;
576 			     !tome && cur != NULL;
577 			     cur = cur->next)
578 				tome = nsearch(cur->name, buf);
579 		}
580 	}
581 	if (!tome)
582 		EXITIT(EX_NOUSER);
583 	if (*From == '\0')
584 	{
585 		msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
586 		EXITIT(EX_DATAERR);
587 	}
588 	EXITIT(EX_OK);
589 }
590 
591 /*
592 ** NSEARCH --
593 **	do a nice, slow, search of a string for a substring.
594 **
595 **	Parameters:
596 **		name -- name to search.
597 **		str -- string in which to search.
598 **
599 **	Returns:
600 **		is name a substring of str?
601 **
602 */
603 
604 bool
605 nsearch(name, str)
606 	register char *name, *str;
607 {
608 	register size_t len;
609 	register char *s;
610 
611 	len = strlen(name);
612 
613 	for (s = str; *s != '\0'; ++s)
614 	{
615 		/*
616 		**  Check to make sure that the string matches and
617 		**  the previous character is not an alphanumeric and
618 		**  the next character after the match is not an alphanumeric.
619 		**
620 		**  This prevents matching "eric" to "derick" while still
621 		**  matching "eric" to "<eric+detail>".
622 		*/
623 
624 		if (tolower(*s) == tolower(*name) &&
625 		    strncasecmp(name, s, len) == 0 &&
626 		    (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
627 		    (!isascii(*(s + len)) || !isalnum(*(s + len))))
628 			return true;
629 	}
630 	return false;
631 }
632 
633 /*
634 ** JUNKMAIL --
635 **	read the header and return if automagic/junk/bulk/list mail
636 **
637 **	Parameters:
638 **		from -- sender address.
639 **
640 **	Returns:
641 **		is this some automated/junk/bulk/list mail?
642 **
643 */
644 
645 struct ignore
646 {
647 	char	*name;
648 	size_t	len;
649 };
650 
651 typedef struct ignore IGNORE_T;
652 
653 #define MAX_USER_LEN 256	/* maximum length of local part (sender) */
654 
655 /* delimiters for the local part of an address */
656 #define isdelim(c)	((c) == '%' || (c) == '@' || (c) == '+')
657 
658 bool
659 junkmail(from)
660 	char *from;
661 {
662 	bool quot;
663 	char *e;
664 	size_t len;
665 	IGNORE_T *cur;
666 	char sender[MAX_USER_LEN];
667 	static IGNORE_T ignore[] =
668 	{
669 		{ "postmaster",		10	},
670 		{ "uucp",		4	},
671 		{ "mailer-daemon",	13	},
672 		{ "mailer",		6	},
673 		{ NULL,			0	}
674 	};
675 
676 	static IGNORE_T ignorepost[] =
677 	{
678 		{ "-request",		8	},
679 		{ "-relay",		6	},
680 		{ "-owner",		6	},
681 		{ NULL,			0	}
682 	};
683 
684 	static IGNORE_T ignorepre[] =
685 	{
686 		{ "owner-",		6	},
687 		{ NULL,			0	}
688 	};
689 
690 	/*
691 	**  This is mildly amusing, and I'm not positive it's right; trying
692 	**  to find the "real" name of the sender, assuming that addresses
693 	**  will be some variant of:
694 	**
695 	**  From site!site!SENDER%site.domain%site.domain@site.domain
696 	*/
697 
698 	quot = false;
699 	e = from;
700 	len = 0;
701 	while (*e != '\0' && (quot || !isdelim(*e)))
702 	{
703 		if (*e == '"')
704 		{
705 			quot = !quot;
706 			++e;
707 			continue;
708 		}
709 		if (*e == '\\')
710 		{
711 			if (*(++e) == '\0')
712 			{
713 				/* '\\' at end of string? */
714 				break;
715 			}
716 			if (len < MAX_USER_LEN)
717 				sender[len++] = *e;
718 			++e;
719 			continue;
720 		}
721 		if (*e == '!' && !quot)
722 		{
723 			len = 0;
724 			sender[len] = '\0';
725 		}
726 		else
727 			if (len < MAX_USER_LEN)
728 				sender[len++] = *e;
729 		++e;
730 	}
731 	if (len < MAX_USER_LEN)
732 		sender[len] = '\0';
733 	else
734 		sender[MAX_USER_LEN - 1] = '\0';
735 
736 	if (len <= 0)
737 		return false;
738 #if 0
739 	if (quot)
740 		return false;	/* syntax error... */
741 #endif /* 0 */
742 
743 	/* test prefixes */
744 	for (cur = ignorepre; cur->name != NULL; ++cur)
745 	{
746 		if (len >= cur->len &&
747 		    strncasecmp(cur->name, sender, cur->len) == 0)
748 			return true;
749 	}
750 
751 	/*
752 	**  If the name is truncated, don't test the rest.
753 	**	We could extract the "tail" of the sender address and
754 	**	compare it it ignorepost, however, it seems not worth
755 	**	the effort.
756 	**	The address surely can't match any entry in ignore[]
757 	**	(as long as all of them are shorter than MAX_USER_LEN).
758 	*/
759 
760 	if (len > MAX_USER_LEN)
761 		return false;
762 
763 	/* test full local parts */
764 	for (cur = ignore; cur->name != NULL; ++cur)
765 	{
766 		if (len == cur->len &&
767 		    strncasecmp(cur->name, sender, cur->len) == 0)
768 			return true;
769 	}
770 
771 	/* test postfixes */
772 	for (cur = ignorepost; cur->name != NULL; ++cur)
773 	{
774 		if (len >= cur->len &&
775 		    strncasecmp(cur->name, e - cur->len - 1,
776 				cur->len) == 0)
777 			return true;
778 	}
779 	return false;
780 }
781 
782 #define	VIT	"__VACATION__INTERVAL__TIMER__"
783 
784 /*
785 ** RECENT --
786 **	find out if user has gotten a vacation message recently.
787 **
788 **	Parameters:
789 **		none.
790 **
791 **	Returns:
792 **		true iff user has gotten a vacation message recently.
793 **
794 */
795 
796 bool
797 recent()
798 {
799 	SMDB_DBENT key, data;
800 	time_t then, next;
801 	bool trydomain = false;
802 	int st;
803 	char *domain;
804 
805 	memset(&key, '\0', sizeof key);
806 	memset(&data, '\0', sizeof data);
807 
808 	/* get interval time */
809 	key.data = VIT;
810 	key.size = sizeof(VIT);
811 
812 	st = Db->smdb_get(Db, &key, &data, 0);
813 	if (st != SMDBE_OK)
814 		next = SECSPERDAY * DAYSPERWEEK;
815 	else
816 		memmove(&next, data.data, sizeof(next));
817 
818 	memset(&data, '\0', sizeof data);
819 
820 	/* get record for this address */
821 	key.data = From;
822 	key.size = strlen(From);
823 
824 	do
825 	{
826 		st = Db->smdb_get(Db, &key, &data, 0);
827 		if (st == SMDBE_OK)
828 		{
829 			memmove(&then, data.data, sizeof(then));
830 			if (next == ONLY_ONCE || then == ONLY_ONCE ||
831 			    then + next > time(NULL))
832 				return true;
833 		}
834 		if ((trydomain = !trydomain) &&
835 		    (domain = strchr(From, '@')) != NULL)
836 		{
837 			key.data = domain;
838 			key.size = strlen(domain);
839 		}
840 	} while (trydomain);
841 	return false;
842 }
843 
844 /*
845 ** SETINTERVAL --
846 **	store the reply interval
847 **
848 **	Parameters:
849 **		interval -- time interval for replies.
850 **
851 **	Returns:
852 **		nothing.
853 **
854 **	Side Effects:
855 **		stores the reply interval in database.
856 */
857 
858 void
859 setinterval(interval)
860 	time_t interval;
861 {
862 	SMDB_DBENT key, data;
863 
864 	memset(&key, '\0', sizeof key);
865 	memset(&data, '\0', sizeof data);
866 
867 	key.data = VIT;
868 	key.size = sizeof(VIT);
869 	data.data = (char*) &interval;
870 	data.size = sizeof(interval);
871 	(void) (Db->smdb_put)(Db, &key, &data, 0);
872 }
873 
874 /*
875 ** SETREPLY --
876 **	store that this user knows about the vacation.
877 **
878 **	Parameters:
879 **		from -- sender address.
880 **		when -- last reply time.
881 **
882 **	Returns:
883 **		nothing.
884 **
885 **	Side Effects:
886 **		stores user/time in database.
887 */
888 
889 void
890 setreply(from, when)
891 	char *from;
892 	time_t when;
893 {
894 	SMDB_DBENT key, data;
895 
896 	memset(&key, '\0', sizeof key);
897 	memset(&data, '\0', sizeof data);
898 
899 	key.data = from;
900 	key.size = strlen(from);
901 	data.data = (char*) &when;
902 	data.size = sizeof(when);
903 	(void) (Db->smdb_put)(Db, &key, &data, 0);
904 }
905 
906 /*
907 ** XCLUDE --
908 **	add users to vacation db so they don't get a reply.
909 **
910 **	Parameters:
911 **		f -- file pointer with list of address to exclude
912 **
913 **	Returns:
914 **		nothing.
915 **
916 **	Side Effects:
917 **		stores users in database.
918 */
919 
920 void
921 xclude(f)
922 	SM_FILE_T *f;
923 {
924 	char buf[MAXLINE], *p;
925 
926 	if (f == NULL)
927 		return;
928 	while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf))
929 	{
930 		if ((p = strchr(buf, '\n')) != NULL)
931 			*p = '\0';
932 		setreply(buf, ONLY_ONCE);
933 	}
934 }
935 
936 /*
937 ** SENDMESSAGE --
938 **	exec sendmail to send the vacation file to sender
939 **
940 **	Parameters:
941 **		myname -- user name.
942 **		msgfn -- name of file with vacation message.
943 **		sender -- use as sender address
944 **
945 **	Returns:
946 **		nothing.
947 **
948 **	Side Effects:
949 **		sends vacation reply.
950 */
951 
952 void
953 sendmessage(myname, msgfn, sender)
954 	char *myname;
955 	char *msgfn;
956 	char *sender;
957 {
958 	SM_FILE_T *mfp, *sfp;
959 	int i;
960 	int pvect[2];
961 	char *pv[8];
962 	char buf[MAXLINE];
963 
964 	mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
965 	if (mfp == NULL)
966 	{
967 		if (msgfn[0] == '/')
968 			msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
969 		else
970 			msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
971 			       myname, msgfn);
972 		exit(EX_NOINPUT);
973 	}
974 	if (pipe(pvect) < 0)
975 	{
976 		msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
977 		exit(EX_OSERR);
978 	}
979 	pv[0] = "sendmail";
980 	pv[1] = "-oi";
981 	pv[2] = "-f";
982 	if (sender != NULL)
983 		pv[3] = sender;
984 	else
985 		pv[3] = myname;
986 	pv[4] = "--";
987 	pv[5] = From;
988 	pv[6] = NULL;
989 	i = fork();
990 	if (i < 0)
991 	{
992 		msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
993 		exit(EX_OSERR);
994 	}
995 	if (i == 0)
996 	{
997 		(void) dup2(pvect[0], 0);
998 		(void) close(pvect[0]);
999 		(void) close(pvect[1]);
1000 		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
1001 		(void) execv(_PATH_SENDMAIL, pv);
1002 		msglog(LOG_ERR, "vacation: can't exec %s: %s",
1003 			_PATH_SENDMAIL, sm_errstring(errno));
1004 		exit(EX_UNAVAILABLE);
1005 	}
1006 	/* check return status of the following calls? XXX */
1007 	(void) close(pvect[0]);
1008 	if ((sfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
1009 			      (void *) &(pvect[1]),
1010 			      SM_IO_WRONLY, NULL)) != NULL)
1011 	{
1012 		(void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, "To: %s\n", From);
1013 		(void) sm_io_fprintf(sfp, SM_TIME_DEFAULT,
1014 				     "Auto-Submitted: auto-replied\n");
1015 		while (sm_io_fgets(mfp, SM_TIME_DEFAULT, buf, sizeof buf))
1016 			(void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf);
1017 		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
1018 		(void) sm_io_close(sfp, SM_TIME_DEFAULT);
1019 	}
1020 	else
1021 	{
1022 		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
1023 		msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
1024 		exit(EX_UNAVAILABLE);
1025 	}
1026 }
1027 
1028 void
1029 usage()
1030 {
1031 	char *retusage;
1032 
1033 #if _FFR_RETURN_ADDR
1034 	retusage = "[-R returnaddr] ";
1035 #else /* _FFR_RETURN_ADDR */
1036 	retusage = "";
1037 #endif /* _FFR_RETURN_ADDR */
1038 
1039 	msglog(LOG_NOTICE,
1040 	       "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",
1041 	       getuid(), retusage);
1042 	exit(EX_USAGE);
1043 }
1044 
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 		sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1070 			      "vacation: set cursor: %s\n",
1071 			      sm_errstring(result));
1072 		return;
1073 	}
1074 
1075 	while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1076 					   SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
1077 	{
1078 		/* skip magic VIT entry */
1079 		if ((int)db_key.size - 1 == strlen(VIT) &&
1080 		    strncmp((char *)db_key.data, VIT,
1081 			    (int)db_key.size - 1) == 0)
1082 			continue;
1083 
1084 		/* skip bogus values */
1085 		if (db_value.size != sizeof t)
1086 		{
1087 			sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1088 				      "vacation: %.*s invalid time stamp\n",
1089 				      (int) db_key.size, (char *) db_key.data);
1090 			continue;
1091 		}
1092 
1093 		memcpy(&t, db_value.data, sizeof t);
1094 
1095 		if (db_key.size > 40)
1096 			db_key.size = 40;
1097 
1098 		sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
1099 			      (int) db_key.size, (char *) db_key.data,
1100 			      ctime(&t));
1101 
1102 		memset(&db_key, '\0', sizeof db_key);
1103 		memset(&db_value, '\0', sizeof db_value);
1104 	}
1105 
1106 	if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1107 	{
1108 		sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1109 			      "vacation: get value at cursor: %s\n",
1110 			      sm_errstring(result));
1111 		if (cursor != NULL)
1112 		{
1113 			(void) cursor->smdbc_close(cursor);
1114 			cursor = NULL;
1115 		}
1116 		return;
1117 	}
1118 	(void) cursor->smdbc_close(cursor);
1119 	cursor = NULL;
1120 }
1121 
1122 /*
1123 ** DEBUGLOG -- write message to standard error
1124 **
1125 **	Append a message to the standard error for the convenience of
1126 **	end-users debugging without access to the syslog messages.
1127 **
1128 **	Parameters:
1129 **		i -- syslog log level
1130 **		fmt -- string format
1131 **
1132 **	Returns:
1133 **		nothing.
1134 */
1135 
1136 /*VARARGS2*/
1137 static SYSLOG_RET_T
1138 #ifdef __STDC__
1139 debuglog(int i, const char *fmt, ...)
1140 #else /* __STDC__ */
1141 debuglog(i, fmt, va_alist)
1142 	int i;
1143 	const char *fmt;
1144 	va_dcl
1145 #endif /* __STDC__ */
1146 
1147 {
1148 	SM_VA_LOCAL_DECL
1149 
1150 	SM_VA_START(ap, fmt);
1151 	sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);
1152 	SM_VA_END(ap);
1153 	SYSLOG_RET;
1154 }
1155