vacation.c revision 203004
1251881Speter/*
2251881Speter * Copyright (c) 1999-2002, 2009 Sendmail, Inc. and its suppliers.
3251881Speter *	All rights reserved.
4251881Speter * Copyright (c) 1983, 1987, 1993
5251881Speter *	The Regents of the University of California.  All rights reserved.
6251881Speter * Copyright (c) 1983 Eric P. Allman.  All rights reserved.
7251881Speter *
8251881Speter * By using this file, you agree to the terms and conditions set
9251881Speter * forth in the LICENSE file which can be found at the top level of
10251881Speter * the sendmail distribution.
11251881Speter *
12251881Speter */
13251881Speter
14251881Speter#include <sm/gen.h>
15251881Speter
16251881SpeterSM_IDSTR(copyright,
17251881Speter"@(#) Copyright (c) 1999-2002, 2009 Sendmail, Inc. and its suppliers.\n\
18251881Speter	All rights reserved.\n\
19251881Speter     Copyright (c) 1983, 1987, 1993\n\
20251881Speter	The Regents of the University of California.  All rights reserved.\n\
21251881Speter     Copyright (c) 1983 Eric P. Allman.  All rights reserved.\n")
22251881Speter
23251881SpeterSM_IDSTR(id, "@(#)$Id: vacation.c,v 8.146 2009/08/07 21:28:39 ca Exp $")
24251881Speter
25251881Speter
26251881Speter#include <ctype.h>
27251881Speter#include <stdlib.h>
28251881Speter#include <syslog.h>
29251881Speter#include <time.h>
30251881Speter#include <unistd.h>
31251881Speter#ifdef EX_OK
32251881Speter# undef EX_OK		/* unistd.h may have another use for this */
33251881Speter#endif /* EX_OK */
34251881Speter#include <sm/sysexits.h>
35251881Speter
36251881Speter#include <sm/cf.h>
37251881Speter#include <sm/mbdb.h>
38251881Speter#include "sendmail/sendmail.h"
39251881Speter#include <sendmail/pathnames.h>
40251881Speter#include "libsmdb/smdb.h"
41251881Speter
42251881Speter#define ONLY_ONCE	((time_t) 0)	/* send at most one reply */
43251881Speter#define INTERVAL_UNDEF	((time_t) (-1))	/* no value given */
44251881Speter
45251881Speteruid_t	RealUid;
46251881Spetergid_t	RealGid;
47251881Speterchar	*RealUserName;
48251881Speteruid_t	RunAsUid;
49251881Spetergid_t	RunAsGid;
50251881Speterchar	*RunAsUserName;
51251881Speterint	Verbose = 2;
52251881Speterbool	DontInitGroups = false;
53251881Speteruid_t	TrustedUid = 0;
54251881SpeterBITMAP256 DontBlameSendmail;
55251881Speter
56251881Speterstatic int readheaders __P((bool));
57251881Speterstatic bool junkmail __P((char *));
58251881Speterstatic bool nsearch __P((char *, char *));
59251881Speterstatic void usage __P((void));
60251881Speterstatic void setinterval __P((time_t));
61251881Speterstatic bool recent __P((void));
62251881Speterstatic void setreply __P((char *, time_t));
63251881Speterstatic void sendmessage __P((char *, char *, char *));
64251881Speterstatic void xclude __P((SM_FILE_T *));
65251881Speter
66251881Speter/*
67251881Speter**  VACATION -- return a message to the sender when on vacation.
68251881Speter**
69251881Speter**	This program is invoked as a message receiver.  It returns a
70251881Speter**	message specified by the user to whomever sent the mail, taking
71251881Speter**	care not to return a message too often to prevent "I am on
72251881Speter**	vacation" loops.
73251881Speter*/
74251881Speter
75251881Speter#define	VDB	".vacation"		/* vacation database */
76251881Speter#define	VMSG	".vacation.msg"		/* vacation message */
77251881Speter#define SECSPERDAY	(60 * 60 * 24)
78251881Speter#define DAYSPERWEEK	7
79251881Speter
80251881Spetertypedef struct alias
81251881Speter{
82251881Speter	char *name;
83251881Speter	struct alias *next;
84251881Speter} ALIAS;
85251881Speter
86251881SpeterALIAS *Names = NULL;
87251881Speter
88251881SpeterSMDB_DATABASE *Db;
89251881Speter
90251881Speterchar From[MAXLINE];
91251881Speterbool CloseMBDB = false;
92251881Speter
93251881Speter#if defined(__hpux) || defined(__osf__)
94251881Speter# ifndef SM_CONF_SYSLOG_INT
95251881Speter#  define SM_CONF_SYSLOG_INT	1
96251881Speter# endif /* SM_CONF_SYSLOG_INT */
97251881Speter#endif /* defined(__hpux) || defined(__osf__) */
98251881Speter
99251881Speter#if SM_CONF_SYSLOG_INT
100251881Speter# define SYSLOG_RET_T	int
101251881Speter# define SYSLOG_RET	return 0
102251881Speter#else /* SM_CONF_SYSLOG_INT */
103251881Speter# define SYSLOG_RET_T	void
104251881Speter# define SYSLOG_RET
105251881Speter#endif /* SM_CONF_SYSLOG_INT */
106251881Speter
107251881Spetertypedef SYSLOG_RET_T SYSLOG_T __P((int, const char *, ...));
108251881SpeterSYSLOG_T *msglog = syslog;
109251881Speterstatic SYSLOG_RET_T debuglog __P((int, const char *, ...));
110251881Speterstatic void eatmsg __P((void));
111251881Speterstatic void listdb __P((void));
112251881Speter
113251881Speter/* exit after reading input */
114251881Speter#define EXITIT(excode)			\
115251881Speter{					\
116251881Speter	eatmsg();			\
117251881Speter	if (CloseMBDB)			\
118251881Speter	{				\
119251881Speter		sm_mbdb_terminate();	\
120251881Speter		CloseMBDB = false;	\
121251881Speter	}				\
122251881Speter	return excode;			\
123251881Speter}
124251881Speter
125251881Speter#define EXITM(excode)			\
126251881Speter{					\
127251881Speter	if (!initdb && !list)		\
128251881Speter		eatmsg();		\
129251881Speter	if (CloseMBDB)			\
130251881Speter	{				\
131251881Speter		sm_mbdb_terminate();	\
132251881Speter		CloseMBDB = false;	\
133251881Speter	}				\
134251881Speter	exit(excode);			\
135251881Speter}
136251881Speter
137251881Speterint
138251881Spetermain(argc, argv)
139251881Speter	int argc;
140251881Speter	char **argv;
141251881Speter{
142251881Speter	bool alwaysrespond = false;
143251881Speter	bool initdb, exclude;
144251881Speter	bool runasuser = false;
145251881Speter	bool list = false;
146251881Speter	int mfail = 0, ufail = 0;
147251881Speter	int ch;
148251881Speter	int result;
149251881Speter	long sff;
150251881Speter	time_t interval;
151251881Speter	struct passwd *pw;
152251881Speter	ALIAS *cur;
153251881Speter	char *dbfilename = NULL;
154251881Speter	char *msgfilename = NULL;
155251881Speter	char *cfpath = NULL;
156251881Speter	char *name = NULL;
157251881Speter	char *returnaddr = NULL;
158251881Speter	SMDB_USER_INFO user_info;
159251881Speter	static char rnamebuf[MAXNAME];
160251881Speter	extern int optind, opterr;
161251881Speter	extern char *optarg;
162251881Speter
163251881Speter	/* Vars needed to link with smutil */
164251881Speter	clrbitmap(DontBlameSendmail);
165251881Speter	RunAsUid = RealUid = getuid();
166251881Speter	RunAsGid = RealGid = getgid();
167251881Speter	pw = getpwuid(RealUid);
168251881Speter	if (pw != NULL)
169251881Speter	{
170251881Speter		if (strlen(pw->pw_name) > MAXNAME - 1)
171251881Speter			pw->pw_name[MAXNAME] = '\0';
172251881Speter		sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
173251881Speter	}
174251881Speter	else
175251881Speter		sm_snprintf(rnamebuf, sizeof rnamebuf,
176251881Speter			    "Unknown UID %d", (int) RealUid);
177251881Speter	RunAsUserName = RealUserName = rnamebuf;
178251881Speter
179251881Speter# ifdef LOG_MAIL
180251881Speter	openlog("vacation", LOG_PID, LOG_MAIL);
181251881Speter# else /* LOG_MAIL */
182251881Speter	openlog("vacation", LOG_PID);
183251881Speter# endif /* LOG_MAIL */
184251881Speter
185251881Speter	opterr = 0;
186251881Speter	initdb = false;
187251881Speter	exclude = false;
188251881Speter	interval = INTERVAL_UNDEF;
189251881Speter	*From = '\0';
190251881Speter
191251881Speter
192251881Speter#define OPTIONS	"a:C:df:Iijlm:R:r:s:t:Uxz"
193251881Speter
194251881Speter	while (mfail == 0 && ufail == 0 &&
195251881Speter	       (ch = getopt(argc, argv, OPTIONS)) != -1)
196251881Speter	{
197251881Speter		switch((char)ch)
198251881Speter		{
199251881Speter		  case 'a':			/* alias */
200251881Speter			cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS));
201251881Speter			if (cur == NULL)
202251881Speter			{
203251881Speter				mfail++;
204251881Speter				break;
205251881Speter			}
206251881Speter			cur->name = optarg;
207251881Speter			cur->next = Names;
208251881Speter			Names = cur;
209251881Speter			break;
210251881Speter
211251881Speter		  case 'C':
212251881Speter			cfpath = optarg;
213251881Speter			break;
214251881Speter
215251881Speter		  case 'd':			/* debug mode */
216251881Speter			msglog = debuglog;
217251881Speter			break;
218251881Speter
219251881Speter		  case 'f':		/* alternate database */
220251881Speter			dbfilename = optarg;
221251881Speter			break;
222251881Speter
223251881Speter		  case 'I':			/* backward compatible */
224251881Speter		  case 'i':			/* init the database */
225251881Speter			initdb = true;
226251881Speter			break;
227251881Speter
228251881Speter		  case 'j':
229251881Speter			alwaysrespond = true;
230251881Speter			break;
231251881Speter
232251881Speter		  case 'l':
233251881Speter			list = true;		/* list the database */
234251881Speter			break;
235251881Speter
236251881Speter		  case 'm':		/* alternate message file */
237251881Speter			msgfilename = optarg;
238251881Speter			break;
239251881Speter
240251881Speter		  case 'R':
241251881Speter			returnaddr = optarg;
242251881Speter			break;
243251881Speter
244251881Speter		  case 'r':
245251881Speter			if (isascii(*optarg) && isdigit(*optarg))
246251881Speter			{
247251881Speter				interval = atol(optarg) * SECSPERDAY;
248251881Speter				if (interval < 0)
249251881Speter					ufail++;
250251881Speter			}
251251881Speter			else
252251881Speter				interval = ONLY_ONCE;
253251881Speter			break;
254251881Speter
255251881Speter		  case 's':		/* alternate sender name */
256251881Speter			(void) sm_strlcpy(From, optarg, sizeof From);
257251881Speter			break;
258251881Speter
259251881Speter		  case 't':		/* SunOS: -t1d (default expire) */
260251881Speter			break;
261251881Speter
262251881Speter		  case 'U':		/* run as single user mode */
263251881Speter			runasuser = true;
264251881Speter			break;
265251881Speter
266251881Speter		  case 'x':
267251881Speter			exclude = true;
268251881Speter			break;
269251881Speter
270251881Speter		  case 'z':
271251881Speter			returnaddr = "<>";
272251881Speter			break;
273251881Speter
274251881Speter		  case '?':
275251881Speter		  default:
276251881Speter			ufail++;
277251881Speter			break;
278251881Speter		}
279251881Speter	}
280251881Speter	argc -= optind;
281251881Speter	argv += optind;
282251881Speter
283251881Speter	if (mfail != 0)
284251881Speter	{
285251881Speter		msglog(LOG_NOTICE,
286251881Speter		       "vacation: can't allocate memory for alias.\n");
287251881Speter		EXITM(EX_TEMPFAIL);
288251881Speter	}
289251881Speter	if (ufail != 0)
290251881Speter		usage();
291251881Speter
292251881Speter	if (argc != 1)
293251881Speter	{
294251881Speter		if (!initdb && !list && !exclude)
295251881Speter			usage();
296251881Speter		if ((pw = getpwuid(getuid())) == NULL)
297251881Speter		{
298251881Speter			msglog(LOG_ERR,
299251881Speter			       "vacation: no such user uid %u.\n", getuid());
300251881Speter			EXITM(EX_NOUSER);
301251881Speter		}
302251881Speter		name = strdup(pw->pw_name);
303251881Speter		user_info.smdbu_id = pw->pw_uid;
304251881Speter		user_info.smdbu_group_id = pw->pw_gid;
305251881Speter		(void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
306251881Speter				  SMDB_MAX_USER_NAME_LEN);
307251881Speter		if (chdir(pw->pw_dir) != 0)
308251881Speter		{
309251881Speter			msglog(LOG_NOTICE,
310251881Speter			       "vacation: no such directory %s.\n",
311251881Speter			       pw->pw_dir);
312251881Speter			EXITM(EX_NOINPUT);
313251881Speter		}
314251881Speter	}
315251881Speter	else if (runasuser)
316251881Speter	{
317251881Speter		name = strdup(*argv);
318251881Speter		if (dbfilename == NULL || msgfilename == NULL)
319251881Speter		{
320251881Speter			msglog(LOG_NOTICE,
321251881Speter			       "vacation: -U requires setting both -f and -m\n");
322251881Speter			EXITM(EX_NOINPUT);
323251881Speter		}
324251881Speter		user_info.smdbu_id = pw->pw_uid;
325251881Speter		user_info.smdbu_group_id = pw->pw_gid;
326251881Speter		(void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
327251881Speter			       SMDB_MAX_USER_NAME_LEN);
328251881Speter	}
329251881Speter	else
330251881Speter	{
331251881Speter		int err;
332251881Speter		SM_CF_OPT_T mbdbname;
333251881Speter		SM_MBDB_T user;
334251881Speter
335251881Speter		cfpath = getcfname(0, 0, SM_GET_SENDMAIL_CF, cfpath);
336251881Speter		mbdbname.opt_name = "MailboxDatabase";
337251881Speter		mbdbname.opt_val = "pw";
338251881Speter		(void) sm_cf_getopt(cfpath, 1, &mbdbname);
339251881Speter		err = sm_mbdb_initialize(mbdbname.opt_val);
340251881Speter		if (err != EX_OK)
341251881Speter		{
342251881Speter			msglog(LOG_ERR,
343251881Speter			       "vacation: can't open mailbox database: %s.\n",
344251881Speter			       sm_strexit(err));
345251881Speter			EXITM(err);
346251881Speter		}
347251881Speter		CloseMBDB = true;
348251881Speter		err = sm_mbdb_lookup(*argv, &user);
349251881Speter		if (err == EX_NOUSER)
350251881Speter		{
351251881Speter			msglog(LOG_ERR, "vacation: no such user %s.\n", *argv);
352251881Speter			EXITM(EX_NOUSER);
353251881Speter		}
354251881Speter		if (err != EX_OK)
355251881Speter		{
356251881Speter			msglog(LOG_ERR,
357251881Speter			       "vacation: can't read mailbox database: %s.\n",
358251881Speter			       sm_strexit(err));
359251881Speter			EXITM(err);
360251881Speter		}
361251881Speter		name = strdup(user.mbdb_name);
362251881Speter		if (chdir(user.mbdb_homedir) != 0)
363251881Speter		{
364251881Speter			msglog(LOG_NOTICE,
365251881Speter			       "vacation: no such directory %s.\n",
366251881Speter			       user.mbdb_homedir);
367251881Speter			EXITM(EX_NOINPUT);
368251881Speter		}
369251881Speter		user_info.smdbu_id = user.mbdb_uid;
370251881Speter		user_info.smdbu_group_id = user.mbdb_gid;
371251881Speter		(void) sm_strlcpy(user_info.smdbu_name, user.mbdb_name,
372251881Speter			       SMDB_MAX_USER_NAME_LEN);
373251881Speter	}
374251881Speter	if (name == NULL)
375251881Speter	{
376251881Speter		msglog(LOG_ERR,
377251881Speter		       "vacation: can't allocate memory for username.\n");
378251881Speter		EXITM(EX_OSERR);
379251881Speter	}
380251881Speter
381251881Speter	if (dbfilename == NULL)
382251881Speter		dbfilename = VDB;
383251881Speter	if (msgfilename == NULL)
384251881Speter		msgfilename = VMSG;
385251881Speter
386251881Speter	sff = SFF_CREAT;
387251881Speter	if (getegid() != getgid())
388251881Speter	{
389251881Speter		/* Allow a set-group-ID vacation binary */
390251881Speter		RunAsGid = user_info.smdbu_group_id = getegid();
391251881Speter		sff |= SFF_OPENASROOT;
392251881Speter	}
393251881Speter	if (getuid() == 0)
394251881Speter	{
395251881Speter		/* Allow root to initialize user's vacation databases */
396251881Speter		sff |= SFF_OPENASROOT|SFF_ROOTOK;
397251881Speter
398251881Speter		/* ... safely */
399251881Speter		sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY;
400251881Speter	}
401251881Speter
402251881Speter
403251881Speter	result = smdb_open_database(&Db, dbfilename,
404251881Speter				    O_CREAT|O_RDWR | (initdb ? O_TRUNC : 0),
405251881Speter				    S_IRUSR|S_IWUSR, sff,
406251881Speter				    SMDB_TYPE_DEFAULT, &user_info, NULL);
407251881Speter	if (result != SMDBE_OK)
408251881Speter	{
409251881Speter		msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
410251881Speter		       sm_errstring(result));
411251881Speter		EXITM(EX_DATAERR);
412251881Speter	}
413251881Speter
414251881Speter	if (list)
415251881Speter	{
416251881Speter		listdb();
417251881Speter		(void) Db->smdb_close(Db);
418251881Speter		exit(EX_OK);
419251881Speter	}
420251881Speter
421251881Speter	if (interval != INTERVAL_UNDEF)
422251881Speter		setinterval(interval);
423251881Speter
424251881Speter	if (initdb && !exclude)
425251881Speter	{
426251881Speter		(void) Db->smdb_close(Db);
427251881Speter		exit(EX_OK);
428251881Speter	}
429251881Speter
430251881Speter	if (exclude)
431251881Speter	{
432251881Speter		xclude(smioin);
433251881Speter		(void) Db->smdb_close(Db);
434251881Speter		EXITM(EX_OK);
435251881Speter	}
436251881Speter
437251881Speter	if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
438251881Speter	{
439251881Speter		msglog(LOG_NOTICE,
440251881Speter		       "vacation: can't allocate memory for username.\n");
441251881Speter		(void) Db->smdb_close(Db);
442251881Speter		EXITM(EX_OSERR);
443251881Speter	}
444251881Speter	cur->name = name;
445251881Speter	cur->next = Names;
446251881Speter	Names = cur;
447251881Speter
448251881Speter	result = readheaders(alwaysrespond);
449251881Speter	if (result == EX_OK && !recent())
450251881Speter	{
451251881Speter		time_t now;
452251881Speter
453251881Speter		(void) time(&now);
454251881Speter		setreply(From, now);
455251881Speter		(void) Db->smdb_close(Db);
456251881Speter		sendmessage(name, msgfilename, returnaddr);
457251881Speter	}
458251881Speter	else
459251881Speter		(void) Db->smdb_close(Db);
460251881Speter	if (result == EX_NOUSER)
461251881Speter		result = EX_OK;
462251881Speter	exit(result);
463251881Speter}
464251881Speter
465251881Speter/*
466251881Speter** EATMSG -- read stdin till EOF
467251881Speter**
468251881Speter**	Parameters:
469251881Speter**		none.
470251881Speter**
471251881Speter**	Returns:
472251881Speter**		nothing.
473251881Speter**
474251881Speter*/
475251881Speter
476251881Speterstatic void
477251881Spetereatmsg()
478251881Speter{
479251881Speter	/*
480251881Speter	**  read the rest of the e-mail and ignore it to avoid problems
481251881Speter	**  with EPIPE in sendmail
482251881Speter	*/
483251881Speter	while (getc(stdin) != EOF)
484251881Speter		continue;
485251881Speter}
486251881Speter
487251881Speter/*
488251881Speter** READHEADERS -- read mail headers
489251881Speter**
490251881Speter**	Parameters:
491251881Speter**		alwaysrespond -- respond regardless of whether msg is to me
492251881Speter**
493251881Speter**	Returns:
494251881Speter**		a exit code: NOUSER if no reply, OK if reply, * if error
495251881Speter**
496251881Speter**	Side Effects:
497251881Speter**		may exit().
498251881Speter**
499251881Speter*/
500251881Speter
501251881Speterstatic int
502251881Speterreadheaders(alwaysrespond)
503251881Speter	bool alwaysrespond;
504251881Speter{
505251881Speter	bool tome, cont;
506251881Speter	register char *p;
507251881Speter	register ALIAS *cur;
508251881Speter	char buf[MAXLINE];
509251881Speter
510251881Speter	cont = false;
511251881Speter	tome = alwaysrespond;
512251881Speter	while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) &&
513251881Speter	       *buf != '\n')
514251881Speter	{
515251881Speter		switch(*buf)
516251881Speter		{
517251881Speter		  case 'F':		/* "From " */
518251881Speter			cont = false;
519251881Speter			if (strncmp(buf, "From ", 5) == 0)
520251881Speter			{
521251881Speter				bool quoted = false;
522251881Speter
523251881Speter				p = buf + 5;
524251881Speter				while (*p != '\0')
525251881Speter				{
526251881Speter					/* escaped character */
527251881Speter					if (*p == '\\')
528251881Speter					{
529251881Speter						p++;
530251881Speter						if (*p == '\0')
531251881Speter						{
532251881Speter							msglog(LOG_NOTICE,
533251881Speter							       "vacation: badly formatted \"From \" line.\n");
534251881Speter							EXITIT(EX_DATAERR);
535251881Speter						}
536251881Speter					}
537251881Speter					else if (*p == '"')
538251881Speter						quoted = !quoted;
539251881Speter					else if (*p == '\r' || *p == '\n')
540251881Speter						break;
541251881Speter					else if (*p == ' ' && !quoted)
542251881Speter						break;
543251881Speter					p++;
544251881Speter				}
545251881Speter				if (quoted)
546251881Speter				{
547251881Speter					msglog(LOG_NOTICE,
548251881Speter					       "vacation: badly formatted \"From \" line.\n");
549251881Speter					EXITIT(EX_DATAERR);
550251881Speter				}
551251881Speter				*p = '\0';
552251881Speter
553251881Speter				/* ok since both strings have MAXLINE length */
554251881Speter				if (*From == '\0')
555251881Speter					(void) sm_strlcpy(From, buf + 5,
556251881Speter							  sizeof From);
557251881Speter				if ((p = strchr(buf + 5, '\n')) != NULL)
558251881Speter					*p = '\0';
559251881Speter				if (junkmail(buf + 5))
560251881Speter					EXITIT(EX_NOUSER);
561251881Speter			}
562251881Speter			break;
563251881Speter
564251881Speter		  case 'P':		/* "Precedence:" */
565251881Speter		  case 'p':
566251881Speter			cont = false;
567251881Speter			if (strlen(buf) <= 10 ||
568251881Speter			    strncasecmp(buf, "Precedence", 10) != 0 ||
569251881Speter			    (buf[10] != ':' && buf[10] != ' ' &&
570251881Speter			     buf[10] != '\t'))
571251881Speter				break;
572251881Speter			if ((p = strchr(buf, ':')) == NULL)
573251881Speter				break;
574251881Speter			while (*++p != '\0' && isascii(*p) && isspace(*p));
575251881Speter			if (*p == '\0')
576251881Speter				break;
577251881Speter			if (strncasecmp(p, "junk", 4) == 0 ||
578251881Speter			    strncasecmp(p, "bulk", 4) == 0 ||
579251881Speter			    strncasecmp(p, "list", 4) == 0)
580251881Speter				EXITIT(EX_NOUSER);
581251881Speter			break;
582251881Speter
583251881Speter		  case 'C':		/* "Cc:" */
584251881Speter		  case 'c':
585251881Speter			if (strncasecmp(buf, "Cc:", 3) != 0)
586251881Speter				break;
587251881Speter			cont = true;
588251881Speter			goto findme;
589251881Speter
590251881Speter		  case 'T':		/* "To:" */
591251881Speter		  case 't':
592			if (strncasecmp(buf, "To:", 3) != 0)
593				break;
594			cont = true;
595			goto findme;
596
597		  default:
598			if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
599			{
600				cont = false;
601				break;
602			}
603findme:
604			for (cur = Names;
605			     !tome && cur != NULL;
606			     cur = cur->next)
607				tome = nsearch(cur->name, buf);
608		}
609	}
610	if (!tome)
611		EXITIT(EX_NOUSER);
612	if (*From == '\0')
613	{
614		msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
615		EXITIT(EX_DATAERR);
616	}
617	EXITIT(EX_OK);
618}
619
620/*
621** NSEARCH --
622**	do a nice, slow, search of a string for a substring.
623**
624**	Parameters:
625**		name -- name to search.
626**		str -- string in which to search.
627**
628**	Returns:
629**		is name a substring of str?
630**
631*/
632
633static bool
634nsearch(name, str)
635	register char *name, *str;
636{
637	register size_t len;
638	register char *s;
639
640	len = strlen(name);
641
642	for (s = str; *s != '\0'; ++s)
643	{
644		/*
645		**  Check to make sure that the string matches and
646		**  the previous character is not an alphanumeric and
647		**  the next character after the match is not an alphanumeric.
648		**
649		**  This prevents matching "eric" to "derick" while still
650		**  matching "eric" to "<eric+detail>".
651		*/
652
653		if (tolower(*s) == tolower(*name) &&
654		    strncasecmp(name, s, len) == 0 &&
655		    (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
656		    (!isascii(*(s + len)) || !isalnum(*(s + len))))
657			return true;
658	}
659	return false;
660}
661
662/*
663** JUNKMAIL --
664**	read the header and return if automagic/junk/bulk/list mail
665**
666**	Parameters:
667**		from -- sender address.
668**
669**	Returns:
670**		is this some automated/junk/bulk/list mail?
671**
672*/
673
674struct ignore
675{
676	char	*name;
677	size_t	len;
678};
679
680typedef struct ignore IGNORE_T;
681
682#define MAX_USER_LEN 256	/* maximum length of local part (sender) */
683
684/* delimiters for the local part of an address */
685#define isdelim(c)	((c) == '%' || (c) == '@' || (c) == '+')
686
687static bool
688junkmail(from)
689	char *from;
690{
691	bool quot;
692	char *e;
693	size_t len;
694	IGNORE_T *cur;
695	char sender[MAX_USER_LEN];
696	static IGNORE_T ignore[] =
697	{
698		{ "postmaster",		10	},
699		{ "uucp",		4	},
700		{ "mailer-daemon",	13	},
701		{ "mailer",		6	},
702		{ NULL,			0	}
703	};
704
705	static IGNORE_T ignorepost[] =
706	{
707		{ "-request",		8	},
708		{ "-relay",		6	},
709		{ "-owner",		6	},
710		{ NULL,			0	}
711	};
712
713	static IGNORE_T ignorepre[] =
714	{
715		{ "owner-",		6	},
716		{ NULL,			0	}
717	};
718
719	/*
720	**  This is mildly amusing, and I'm not positive it's right; trying
721	**  to find the "real" name of the sender, assuming that addresses
722	**  will be some variant of:
723	**
724	**  From site!site!SENDER%site.domain%site.domain@site.domain
725	*/
726
727	quot = false;
728	e = from;
729	len = 0;
730	while (*e != '\0' && (quot || !isdelim(*e)))
731	{
732		if (*e == '"')
733		{
734			quot = !quot;
735			++e;
736			continue;
737		}
738		if (*e == '\\')
739		{
740			if (*(++e) == '\0')
741			{
742				/* '\\' at end of string? */
743				break;
744			}
745			if (len < MAX_USER_LEN)
746				sender[len++] = *e;
747			++e;
748			continue;
749		}
750		if (*e == '!' && !quot)
751		{
752			len = 0;
753			sender[len] = '\0';
754		}
755		else
756			if (len < MAX_USER_LEN)
757				sender[len++] = *e;
758		++e;
759	}
760	if (len < MAX_USER_LEN)
761		sender[len] = '\0';
762	else
763		sender[MAX_USER_LEN - 1] = '\0';
764
765	if (len <= 0)
766		return false;
767#if 0
768	if (quot)
769		return false;	/* syntax error... */
770#endif /* 0 */
771
772	/* test prefixes */
773	for (cur = ignorepre; cur->name != NULL; ++cur)
774	{
775		if (len >= cur->len &&
776		    strncasecmp(cur->name, sender, cur->len) == 0)
777			return true;
778	}
779
780	/*
781	**  If the name is truncated, don't test the rest.
782	**	We could extract the "tail" of the sender address and
783	**	compare it it ignorepost, however, it seems not worth
784	**	the effort.
785	**	The address surely can't match any entry in ignore[]
786	**	(as long as all of them are shorter than MAX_USER_LEN).
787	*/
788
789	if (len > MAX_USER_LEN)
790		return false;
791
792	/* test full local parts */
793	for (cur = ignore; cur->name != NULL; ++cur)
794	{
795		if (len == cur->len &&
796		    strncasecmp(cur->name, sender, cur->len) == 0)
797			return true;
798	}
799
800	/* test postfixes */
801	for (cur = ignorepost; cur->name != NULL; ++cur)
802	{
803		if (len >= cur->len &&
804		    strncasecmp(cur->name, e - cur->len - 1,
805				cur->len) == 0)
806			return true;
807	}
808	return false;
809}
810
811#define	VIT	"__VACATION__INTERVAL__TIMER__"
812
813/*
814** RECENT --
815**	find out if user has gotten a vacation message recently.
816**
817**	Parameters:
818**		none.
819**
820**	Returns:
821**		true iff user has gotten a vacation message recently.
822**
823*/
824
825static bool
826recent()
827{
828	SMDB_DBENT key, data;
829	time_t then, next;
830	bool trydomain = false;
831	int st;
832	char *domain;
833
834	memset(&key, '\0', sizeof key);
835	memset(&data, '\0', sizeof data);
836
837	/* get interval time */
838	key.data = VIT;
839	key.size = sizeof(VIT);
840
841	st = Db->smdb_get(Db, &key, &data, 0);
842	if (st != SMDBE_OK)
843		next = SECSPERDAY * DAYSPERWEEK;
844	else
845		memmove(&next, data.data, sizeof(next));
846
847	memset(&data, '\0', sizeof data);
848
849	/* get record for this address */
850	key.data = From;
851	key.size = strlen(From);
852
853	do
854	{
855		st = Db->smdb_get(Db, &key, &data, 0);
856		if (st == SMDBE_OK)
857		{
858			memmove(&then, data.data, sizeof(then));
859			if (next == ONLY_ONCE || then == ONLY_ONCE ||
860			    then + next > time(NULL))
861				return true;
862		}
863		if ((trydomain = !trydomain) &&
864		    (domain = strchr(From, '@')) != NULL)
865		{
866			key.data = domain;
867			key.size = strlen(domain);
868		}
869	} while (trydomain);
870	return false;
871}
872
873/*
874** SETINTERVAL --
875**	store the reply interval
876**
877**	Parameters:
878**		interval -- time interval for replies.
879**
880**	Returns:
881**		nothing.
882**
883**	Side Effects:
884**		stores the reply interval in database.
885*/
886
887static void
888setinterval(interval)
889	time_t interval;
890{
891	SMDB_DBENT key, data;
892
893	memset(&key, '\0', sizeof key);
894	memset(&data, '\0', sizeof data);
895
896	key.data = VIT;
897	key.size = sizeof(VIT);
898	data.data = (char*) &interval;
899	data.size = sizeof(interval);
900	(void) (Db->smdb_put)(Db, &key, &data, 0);
901}
902
903/*
904** SETREPLY --
905**	store that this user knows about the vacation.
906**
907**	Parameters:
908**		from -- sender address.
909**		when -- last reply time.
910**
911**	Returns:
912**		nothing.
913**
914**	Side Effects:
915**		stores user/time in database.
916*/
917
918static void
919setreply(from, when)
920	char *from;
921	time_t when;
922{
923	SMDB_DBENT key, data;
924
925	memset(&key, '\0', sizeof key);
926	memset(&data, '\0', sizeof data);
927
928	key.data = from;
929	key.size = strlen(from);
930	data.data = (char*) &when;
931	data.size = sizeof(when);
932	(void) (Db->smdb_put)(Db, &key, &data, 0);
933}
934
935/*
936** XCLUDE --
937**	add users to vacation db so they don't get a reply.
938**
939**	Parameters:
940**		f -- file pointer with list of address to exclude
941**
942**	Returns:
943**		nothing.
944**
945**	Side Effects:
946**		stores users in database.
947*/
948
949static void
950xclude(f)
951	SM_FILE_T *f;
952{
953	char buf[MAXLINE], *p;
954
955	if (f == NULL)
956		return;
957	while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf))
958	{
959		if ((p = strchr(buf, '\n')) != NULL)
960			*p = '\0';
961		setreply(buf, ONLY_ONCE);
962	}
963}
964
965/*
966** SENDMESSAGE --
967**	exec sendmail to send the vacation file to sender
968**
969**	Parameters:
970**		myname -- user name.
971**		msgfn -- name of file with vacation message.
972**		sender -- use as sender address
973**
974**	Returns:
975**		nothing.
976**
977**	Side Effects:
978**		sends vacation reply.
979*/
980
981static void
982sendmessage(myname, msgfn, sender)
983	char *myname;
984	char *msgfn;
985	char *sender;
986{
987	SM_FILE_T *mfp, *sfp;
988	int i;
989	int pvect[2];
990	char *pv[8];
991	char buf[MAXLINE];
992
993	mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
994	if (mfp == NULL)
995	{
996		if (msgfn[0] == '/')
997			msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
998		else
999			msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
1000			       myname, msgfn);
1001		exit(EX_NOINPUT);
1002	}
1003	if (pipe(pvect) < 0)
1004	{
1005		msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
1006		exit(EX_OSERR);
1007	}
1008	pv[0] = "sendmail";
1009	pv[1] = "-oi";
1010	pv[2] = "-f";
1011	if (sender != NULL)
1012		pv[3] = sender;
1013	else
1014		pv[3] = myname;
1015	pv[4] = "--";
1016	pv[5] = From;
1017	pv[6] = NULL;
1018	i = fork();
1019	if (i < 0)
1020	{
1021		msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
1022		exit(EX_OSERR);
1023	}
1024	if (i == 0)
1025	{
1026		(void) dup2(pvect[0], 0);
1027		(void) close(pvect[0]);
1028		(void) close(pvect[1]);
1029		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
1030		(void) execv(_PATH_SENDMAIL, pv);
1031		msglog(LOG_ERR, "vacation: can't exec %s: %s",
1032			_PATH_SENDMAIL, sm_errstring(errno));
1033		exit(EX_UNAVAILABLE);
1034	}
1035	/* check return status of the following calls? XXX */
1036	(void) close(pvect[0]);
1037	if ((sfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
1038			      (void *) &(pvect[1]),
1039			      SM_IO_WRONLY, NULL)) != NULL)
1040	{
1041#if _FFR_VAC_WAIT4SM
1042# ifdef WAITUNION
1043		union wait st;
1044# else /* WAITUNION */
1045		auto int st;
1046# endif /* WAITUNION */
1047#endif /* _FFR_VAC_WAIT4SM */
1048
1049		(void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, "To: %s\n", From);
1050		(void) sm_io_fprintf(sfp, SM_TIME_DEFAULT,
1051				     "Auto-Submitted: auto-replied\n");
1052		while (sm_io_fgets(mfp, SM_TIME_DEFAULT, buf, sizeof buf))
1053			(void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf);
1054		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
1055		(void) sm_io_close(sfp, SM_TIME_DEFAULT);
1056#if _FFR_VAC_WAIT4SM
1057		(void) wait(&st);
1058#endif /* _FFR_VAC_WAIT4SM */
1059	}
1060	else
1061	{
1062		(void) sm_io_close(mfp, SM_TIME_DEFAULT);
1063		msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
1064		exit(EX_UNAVAILABLE);
1065	}
1066}
1067
1068static void
1069usage()
1070{
1071	msglog(LOG_NOTICE,
1072	       "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",
1073	       getuid());
1074	exit(EX_USAGE);
1075}
1076
1077/*
1078** LISTDB -- list the contents of the vacation database
1079**
1080**	Parameters:
1081**		none.
1082**
1083**	Returns:
1084**		nothing.
1085*/
1086
1087static void
1088listdb()
1089{
1090	int result;
1091	time_t t;
1092	SMDB_CURSOR *cursor = NULL;
1093	SMDB_DBENT db_key, db_value;
1094
1095	memset(&db_key, '\0', sizeof db_key);
1096	memset(&db_value, '\0', sizeof db_value);
1097
1098	result = Db->smdb_cursor(Db, &cursor, 0);
1099	if (result != SMDBE_OK)
1100	{
1101		sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1102			      "vacation: set cursor: %s\n",
1103			      sm_errstring(result));
1104		return;
1105	}
1106
1107	while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1108					   SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
1109	{
1110		char *timestamp;
1111
1112		/* skip magic VIT entry */
1113		if (db_key.size == strlen(VIT) + 1 &&
1114		    strncmp((char *)db_key.data, VIT,
1115			    (int)db_key.size - 1) == 0)
1116			continue;
1117
1118		/* skip bogus values */
1119		if (db_value.size != sizeof t)
1120		{
1121			sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1122				      "vacation: %.*s invalid time stamp\n",
1123				      (int) db_key.size, (char *) db_key.data);
1124			continue;
1125		}
1126
1127		memcpy(&t, db_value.data, sizeof t);
1128
1129		if (db_key.size > 40)
1130			db_key.size = 40;
1131
1132		if (t <= 0)
1133		{
1134			/* must be an exclude */
1135			timestamp = "(exclusion)\n";
1136		}
1137		else
1138		{
1139			timestamp = ctime(&t);
1140		}
1141		sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
1142			      (int) db_key.size, (char *) db_key.data,
1143			      timestamp);
1144
1145		memset(&db_key, '\0', sizeof db_key);
1146		memset(&db_value, '\0', sizeof db_value);
1147	}
1148
1149	if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1150	{
1151		sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1152			      "vacation: get value at cursor: %s\n",
1153			      sm_errstring(result));
1154		if (cursor != NULL)
1155		{
1156			(void) cursor->smdbc_close(cursor);
1157			cursor = NULL;
1158		}
1159		return;
1160	}
1161	(void) cursor->smdbc_close(cursor);
1162	cursor = NULL;
1163}
1164
1165/*
1166** DEBUGLOG -- write message to standard error
1167**
1168**	Append a message to the standard error for the convenience of
1169**	end-users debugging without access to the syslog messages.
1170**
1171**	Parameters:
1172**		i -- syslog log level
1173**		fmt -- string format
1174**
1175**	Returns:
1176**		nothing.
1177*/
1178
1179/*VARARGS2*/
1180static SYSLOG_RET_T
1181#ifdef __STDC__
1182debuglog(int i, const char *fmt, ...)
1183#else /* __STDC__ */
1184debuglog(i, fmt, va_alist)
1185	int i;
1186	const char *fmt;
1187	va_dcl
1188#endif /* __STDC__ */
1189
1190{
1191	SM_VA_LOCAL_DECL
1192
1193	SM_VA_START(ap, fmt);
1194	sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);
1195	SM_VA_END(ap);
1196	SYSLOG_RET;
1197}
1198