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