1/*	$NetBSD: vacation.c,v 1.35 2007/12/15 19:44:54 perry Exp $	*/
2
3/*
4 * Copyright (c) 1983, 1987, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33
34#ifndef lint
35__COPYRIGHT("@(#) Copyright (c) 1983, 1987, 1993\
36 The Regents of the University of California.  All rights reserved.");
37#endif /* not lint */
38
39#ifndef lint
40#if 0
41static char sccsid[] = "@(#)vacation.c	8.2 (Berkeley) 1/26/94";
42#endif
43__RCSID("$NetBSD: vacation.c,v 1.35 2007/12/15 19:44:54 perry Exp $");
44#endif /* not lint */
45
46/*
47**  Vacation
48**  Copyright (c) 1983  Eric P. Allman
49**  Berkeley, California
50*/
51
52#include <sys/param.h>
53#include <sys/stat.h>
54
55#include <ctype.h>
56#include <db.h>
57#include <err.h>
58#include <errno.h>
59#include <fcntl.h>
60#include <paths.h>
61#include <pwd.h>
62#include <stdio.h>
63#include <stdlib.h>
64#include <string.h>
65#include <syslog.h>
66#include <time.h>
67#include <tzfile.h>
68#include <unistd.h>
69
70/*
71 *  VACATION -- return a message to the sender when on vacation.
72 *
73 *	This program is invoked as a message receiver.  It returns a
74 *	message specified by the user to whomever sent the mail, taking
75 *	care not to return a message too often to prevent "I am on
76 *	vacation" loops.
77 */
78
79#define	MAXLINE	1024			/* max line from mail header */
80
81static const char *dbprefix = ".vacation";	/* dbm's database sans .db */
82static const char *msgfile = ".vacation.msg";	/* vacation message */
83
84typedef struct alias {
85	struct alias *next;
86	const char *name;
87} alias_t;
88static alias_t *names;
89
90static DB *db;
91static char from[MAXLINE];
92static char subject[MAXLINE];
93
94static int iflag = 0;		/* Initialize the database */
95
96static int tflag = 0;
97#define	APPARENTLY_TO		1
98#define	DELIVERED_TO		2
99
100static int fflag = 0;
101#define	FROM_FROM		1
102#define	RETURN_PATH_FROM	2
103#define	SENDER_FROM		4
104
105static int toanybody = 0;	/* Don't check if we appear in the to or cc */
106
107static int debug = 0;
108
109static void opendb(void);
110static int junkmail(const char *);
111static int nsearch(const char *, const char *);
112static int readheaders(void);
113static int recent(void);
114static void getfrom(char *);
115static void sendmessage(const char *);
116static void setinterval(time_t);
117static void setreply(void);
118static void usage(void) __dead;
119
120int
121main(int argc, char **argv)
122{
123	struct passwd *pw;
124	alias_t *cur;
125	long interval;
126	int ch, rv;
127	char *p;
128
129	setprogname(argv[0]);
130	opterr = 0;
131	interval = -1;
132	openlog(getprogname(), 0, LOG_USER);
133	while ((ch = getopt(argc, argv, "a:df:F:Iijm:r:s:t:T:")) != -1)
134		switch((char)ch) {
135		case 'a':			/* alias */
136			if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t))))
137				break;
138			cur->name = optarg;
139			cur->next = names;
140			names = cur;
141			break;
142		case 'd':
143			debug++;
144			break;
145		case 'F':
146			for (p = optarg; *p; p++)
147				switch (*p) {
148				case 'F':
149					fflag |= FROM_FROM;
150					break;
151				case 'R':
152					fflag |= RETURN_PATH_FROM;
153					break;
154				case 'S':
155					fflag |= SENDER_FROM;
156					break;
157				default:
158					errx(1, "Unknown -f option `%c'", *p);
159				}
160			break;
161		case 'f':
162			dbprefix = optarg;
163			break;
164		case 'I':			/* backward compatible */
165		case 'i':			/* init the database */
166			iflag = 1;
167			break;
168		case 'j':
169			toanybody = 1;
170			break;
171		case 'm':
172			msgfile = optarg;
173			break;
174		case 'r':
175		case 't':	/* Solaris compatibility */
176			if (!isdigit((unsigned char)*optarg)) {
177				interval = LONG_MAX;
178				break;
179			}
180			if (*optarg == '\0')
181				goto bad;
182			interval = strtol(optarg, &p, 0);
183			if (errno == ERANGE &&
184			    (interval == LONG_MAX || interval == LONG_MIN))
185				err(1, "Bad interval `%s'", optarg);
186			switch (*p) {
187			case 's':
188				break;
189			case 'm':
190				interval *= SECSPERMIN;
191				break;
192			case 'h':
193				interval *= SECSPERHOUR;
194				break;
195			case 'd':
196			case '\0':
197				interval *= SECSPERDAY;
198				break;
199			case 'w':
200				interval *= DAYSPERWEEK * SECSPERDAY;
201				break;
202			default:
203			bad:
204				errx(1, "Invalid interval `%s'", optarg);
205			}
206			if (interval < 0 || (*p && p[1]))
207				goto bad;
208			break;
209		case 's':
210			(void)strlcpy(from, optarg, sizeof(from));
211			break;
212		case 'T':
213			for (p = optarg; *p; p++)
214				switch (*p) {
215				case 'A':
216					tflag |= APPARENTLY_TO;
217					break;
218				case 'D':
219					tflag |= DELIVERED_TO;
220					break;
221				default:
222					errx(1, "Unknown -t option `%c'", *p);
223				}
224			break;
225		case '?':
226		default:
227			usage();
228		}
229	argc -= optind;
230	argv += optind;
231
232	if (argc != 1) {
233		if (!iflag)
234			usage();
235		if (!(pw = getpwuid(getuid()))) {
236			syslog(LOG_ERR, "%s: no such user uid %u.",
237			    getprogname(), getuid());
238			exit(1);
239		}
240	}
241	else if (!(pw = getpwnam(*argv))) {
242		syslog(LOG_ERR, "%s: no such user %s.",
243		    getprogname(), *argv);
244		exit(1);
245	}
246	if (chdir(pw->pw_dir) == -1 &&
247	    (dbprefix[0] != '/' || msgfile[0] != '/')) {
248		syslog(LOG_ERR, "%s: no such directory %s.",
249		    getprogname(), pw->pw_dir);
250		exit(1);
251	}
252
253	opendb();
254
255	if (interval != -1)
256		setinterval((time_t)interval);
257
258	if (iflag) {
259		(void)(db->close)(db);
260		exit(0);
261	}
262
263	if (!(cur = malloc((size_t)sizeof(alias_t)))) {
264		syslog(LOG_ERR, "%s: %m", getprogname());
265		(void)(db->close)(db);
266		exit(1);
267	}
268	cur->name = pw->pw_name;
269	cur->next = names;
270	names = cur;
271
272	if ((rv = readheaders()) != -1) {
273		(void)(db->close)(db);
274		exit(rv);
275	}
276
277	if (!recent()) {
278		setreply();
279		(void)(db->close)(db);
280		sendmessage(pw->pw_name);
281	}
282	else
283		(void)(db->close)(db);
284	exit(0);
285	/* NOTREACHED */
286}
287
288static void
289opendb(void)
290{
291	char path[MAXPATHLEN];
292
293	(void)snprintf(path, sizeof(path), "%s.db", dbprefix);
294	db = dbopen(path, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
295	    S_IRUSR|S_IWUSR, DB_HASH, NULL);
296
297	if (!db) {
298		syslog(LOG_ERR, "%s: %s: %m", getprogname(), path);
299		exit(1);
300	}
301}
302
303/*
304 * readheaders --
305 *	read mail headers
306 */
307static int
308readheaders(void)
309{
310	alias_t *cur;
311	char *p;
312	int tome, cont;
313	char buf[MAXLINE];
314
315	cont = tome = 0;
316#define COMPARE(a, b)		strncmp(a, b, sizeof(b) - 1)
317#define CASECOMPARE(a, b)	strncasecmp(a, b, sizeof(b) - 1)
318	while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
319		switch(*buf) {
320		case 'F':		/* "From " or "From:" */
321			cont = 0;
322			if (COMPARE(buf, "From ") == 0)
323				getfrom(buf + sizeof("From ") - 1);
324			if ((fflag & FROM_FROM) != 0 &&
325			    COMPARE(buf, "From:") == 0)
326				getfrom(buf + sizeof("From:") - 1);
327			break;
328		case 'P':		/* "Precedence:" */
329			cont = 0;
330			if (CASECOMPARE(buf, "Precedence") != 0 ||
331			    (buf[10] != ':' && buf[10] != ' ' &&
332			    buf[10] != '\t'))
333				break;
334			if ((p = strchr(buf, ':')) == NULL)
335				break;
336			while (*++p && isspace((unsigned char)*p))
337				continue;
338			if (!*p)
339				break;
340			if (CASECOMPARE(p, "junk") == 0 ||
341			    CASECOMPARE(p, "bulk") == 0||
342			    CASECOMPARE(p, "list") == 0)
343				exit(0);
344			break;
345		case 'C':		/* "Cc:" */
346			if (COMPARE(buf, "Cc:"))
347				break;
348			cont = 1;
349			goto findme;
350		case 'T':		/* "To:" */
351			if (COMPARE(buf, "To:"))
352				break;
353			cont = 1;
354			goto findme;
355		case 'A':		/* "Apparently-To:" */
356			if ((tflag & APPARENTLY_TO) == 0 ||
357			    COMPARE(buf, "Apparently-To:") != 0)
358				break;
359			cont = 1;
360			goto findme;
361		case 'D':		/* "Delivered-To:" */
362			if ((tflag & DELIVERED_TO) == 0 ||
363			    COMPARE(buf, "Delivered-To:") != 0)
364				break;
365			cont = 1;
366			goto findme;
367		case 'R':		/* "Return-Path:" */
368			cont = 0;
369			if ((fflag & RETURN_PATH_FROM) != 0 &&
370			    COMPARE(buf, "Return-Path:") == 0)
371				getfrom(buf + sizeof("Return-Path:") - 1);
372			break;
373		case 'S':		/* "Sender:" */
374			cont = 0;
375			if (COMPARE(buf, "Subject:") == 0) {
376				/* trim leading blanks */
377				char *s = NULL;
378				for (p = buf + sizeof("Subject:") - 1; *p; p++)
379					if (s == NULL &&
380					    !isspace((unsigned char)*p))
381						s = p;
382				/* trim trailing blanks */
383				if (s) {
384					for (--p; p != s; p--)
385						if (!isspace((unsigned char)*p))
386							break;
387					*++p = '\0';
388				}
389				if (s) {
390					(void)strlcpy(subject, s, sizeof(subject));
391				} else {
392					subject[0] = '\0';
393				}
394			}
395			if ((fflag & SENDER_FROM) != 0 &&
396			    COMPARE(buf, "Sender:") == 0)
397				getfrom(buf + sizeof("Sender:") - 1);
398			break;
399		default:
400			if (!isspace((unsigned char)*buf) || !cont || tome) {
401				cont = 0;
402				break;
403			}
404findme:			for (cur = names; !tome && cur; cur = cur->next)
405				tome += nsearch(cur->name, buf);
406		}
407	if (!toanybody && !tome)
408		return 0;
409	if (!*from) {
410		syslog(LOG_ERR, "%s: no initial \"From\" line.",
411			getprogname());
412		return 1;
413	}
414	return -1;
415}
416
417/*
418 * nsearch --
419 *	do a nice, slow, search of a string for a substring.
420 */
421static int
422nsearch(const char *name, const char *str)
423{
424	size_t len;
425
426	for (len = strlen(name); *str; ++str)
427		if (!strncasecmp(name, str, len))
428			return(1);
429	return(0);
430}
431
432/*
433 * getfrom --
434 *	return the first string in the buffer, stripping leading and trailing
435 *	blanks and <>.
436 */
437void
438getfrom(char *buf)
439{
440	char *s, *p;
441
442	if ((s = strchr(buf, '<')) != NULL)
443		s++;
444	else
445		s = buf;
446
447	for (; *s && isspace((unsigned char)*s); s++)
448		continue;
449	for (p = s; *p && !isspace((unsigned char)*p); p++)
450		continue;
451
452	if (*--p == '>')
453		*p = '\0';
454	else
455		*++p = '\0';
456
457	if (junkmail(s))
458		exit(0);
459
460	if (!*from)
461		(void)strlcpy(from, s, sizeof(from));
462}
463
464/*
465 * junkmail --
466 *	read the header and return if automagic/junk/bulk/list mail
467 */
468static int
469junkmail(const char *addr)
470{
471	static struct ignore {
472		const char *name;
473		size_t	len;
474	} ignore[] = {
475#define INIT(a) { a, sizeof(a) - 1 }
476		INIT("-request"),
477		INIT("postmaster"),
478		INIT("uucp"),
479		INIT("mailer-daemon"),
480		INIT("mailer"),
481		INIT("-relay"),
482		{NULL, 0 }
483	};
484	struct ignore *cur;
485	size_t len;
486	const char *p;
487
488	/*
489	 * This is mildly amusing, and I'm not positive it's right; trying
490	 * to find the "real" name of the sender, assuming that addresses
491	 * will be some variant of:
492	 *
493	 * From site!site!SENDER%site.domain%site.domain@site.domain
494	 */
495	if (!(p = strchr(addr, '%')))
496		if (!(p = strchr(addr, '@'))) {
497			if ((p = strrchr(addr, '!')) != NULL)
498				++p;
499			else
500				p = addr;
501			for (; *p; ++p)
502				continue;
503		}
504	len = p - addr;
505	for (cur = ignore; cur->name; ++cur)
506		if (len >= cur->len &&
507		    !strncasecmp(cur->name, p - cur->len, cur->len))
508			return(1);
509	return(0);
510}
511
512#define	VIT	"__VACATION__INTERVAL__TIMER__"
513
514/*
515 * recent --
516 *	find out if user has gotten a vacation message recently.
517 *	use memmove for machines with alignment restrictions
518 */
519static int
520recent(void)
521{
522	DBT key, data;
523	time_t then, next;
524
525	/* get interval time */
526	key.data = (void *)(intptr_t)VIT;
527	key.size = sizeof(VIT);
528	if ((db->get)(db, &key, &data, 0))
529		next = SECSPERDAY * DAYSPERWEEK;
530	else
531		(void)memmove(&next, data.data, sizeof(next));
532
533	/* get record for this address */
534	key.data = from;
535	key.size = strlen(from);
536	if (!(db->get)(db, &key, &data, 0)) {
537		(void)memmove(&then, data.data, sizeof(then));
538		if (next == (time_t)LONG_MAX ||			/* XXX */
539		    then + next > time(NULL))
540			return(1);
541	}
542	return(0);
543}
544
545/*
546 * setinterval --
547 *	store the reply interval
548 */
549static void
550setinterval(time_t interval)
551{
552	DBT key, data;
553
554	key.data = (void *)(intptr_t)VIT;
555	key.size = sizeof(VIT);
556	data.data = &interval;
557	data.size = sizeof(interval);
558	(void)(db->put)(db, &key, &data, 0);
559}
560
561/*
562 * setreply --
563 *	store that this user knows about the vacation.
564 */
565static void
566setreply(void)
567{
568	DBT key, data;
569	time_t now;
570
571	key.data = from;
572	key.size = strlen(from);
573	(void)time(&now);
574	data.data = &now;
575	data.size = sizeof(now);
576	(void)(db->put)(db, &key, &data, 0);
577}
578
579/*
580 * sendmessage --
581 *	exec sendmail to send the vacation file to sender
582 */
583static void
584sendmessage(const char *myname)
585{
586	FILE *mfp, *sfp;
587	int i;
588	int pvect[2];
589	char buf[MAXLINE];
590
591	mfp = fopen(msgfile, "r");
592	if (mfp == NULL) {
593		syslog(LOG_ERR, "%s: no `%s' file for `%s'.", getprogname(),
594		    myname, msgfile);
595		exit(1);
596	}
597
598	if (debug) {
599		sfp = stdout;
600	} else {
601		if (pipe(pvect) < 0) {
602			syslog(LOG_ERR, "%s: pipe: %m", getprogname());
603			exit(1);
604		}
605		i = vfork();
606		if (i < 0) {
607			syslog(LOG_ERR, "%s: fork: %m", getprogname());
608			exit(1);
609		}
610		if (i == 0) {
611			(void)dup2(pvect[0], 0);
612			(void)close(pvect[0]);
613			(void)close(pvect[1]);
614			(void)close(fileno(mfp));
615			(void)execl(_PATH_SENDMAIL, "sendmail", "-f", myname,
616			    "--", from, NULL);
617			syslog(LOG_ERR, "%s: can't exec %s: %m",
618			    getprogname(), _PATH_SENDMAIL);
619			_exit(1);
620		}
621		(void)close(pvect[0]);
622		sfp = fdopen(pvect[1], "w");
623		if (sfp == NULL) {
624			syslog(LOG_ERR, "%s: can't fdopen %d: %m",
625			    getprogname(), pvect[1]);
626			_exit(1);
627		}
628	}
629	(void)fprintf(sfp, "To: %s\n", from);
630	(void)fputs("Auto-Submitted: auto-replied\n", sfp);
631	while (fgets(buf, sizeof buf, mfp) != NULL) {
632		char *p;
633		if ((p = strstr(buf, "$SUBJECT")) != NULL) {
634			*p = '\0';
635			(void)fputs(buf, sfp);
636			(void)fputs(subject, sfp);
637			p += sizeof("$SUBJECT") - 1;
638			(void)fputs(p, sfp);
639		} else
640		    (void)fputs(buf, sfp);
641	}
642	(void)fclose(mfp);
643	if (sfp != stdout)
644		(void)fclose(sfp);
645}
646
647static void
648usage(void)
649{
650
651	syslog(LOG_ERR, "uid %u: Usage: %s [-dIij] [-a alias] [-f database_file] [-F F|R|S] [-m message_file] [-s sender] [-t interval] [-T A|D]"
652	    " login", getuid(), getprogname());
653	exit(1);
654}
655