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
16SM_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
23SM_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
42uid_t	RealUid;
43gid_t	RealGid;
44char	*RealUserName;
45uid_t	RunAsUid;
46gid_t	RunAsGid;
47char	*RunAsUserName;
48int	Verbose = 2;
49bool	DontInitGroups = false;
50uid_t	TrustedUid = 0;
51BITMAP256 DontBlameSendmail;
52
53static int readheaders __P((bool));
54static bool junkmail __P((char *));
55static bool nsearch __P((char *, char *));
56static void usage __P((void));
57static void setinterval __P((time_t));
58static bool recent __P((void));
59static void setreply __P((char *, time_t));
60static void sendmessage __P((char *, char *, char *));
61static 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
77typedef struct alias
78{
79	char *name;
80	struct alias *next;
81} ALIAS;
82
83ALIAS *Names = NULL;
84
85SMDB_DATABASE *Db;
86
87char From[MAXLINE];
88char Subject[MAXLINE];
89bool 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
105typedef SYSLOG_RET_T SYSLOG_T __P((int, const char *, ...));
106SYSLOG_T *msglog = syslog;
107static SYSLOG_RET_T debuglog __P((int, const char *, ...));
108static void eatmsg __P((void));
109static 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
135int
136main(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
472static void
473eatmsg()
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
531static int
532readheaders(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			}
683findme:
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
711static bool
712nsearch(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
749struct ignore
750{
751	char	*name;
752	size_t	len;
753};
754
755typedef 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
762static bool
763junkmail(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
898static bool
899recent()
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
959static void
960setinterval(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
989static void
990setreply(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
1019static void
1020xclude(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
1050static void
1051sendmessage(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
1148static void
1149usage()
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
1167static void
1168listdb()
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*/
1260static SYSLOG_RET_T
1261#ifdef __STDC__
1262debuglog(int i, const char *fmt, ...)
1263#else /* __STDC__ */
1264debuglog(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