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