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