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