xref: /original-bsd/usr.bin/vacation/vacation.c (revision c3e32dec)
1 /*
2  * Copyright (c) 1983, 1987, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1983, 1987, 1993\n\
11 	The Regents of the University of California.  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)vacation.c	8.1 (Berkeley) 06/06/93";
16 #endif /* not lint */
17 
18 /*
19 **  Vacation
20 **  Copyright (c) 1983  Eric P. Allman
21 **  Berkeley, California
22 */
23 
24 #include <sys/param.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <pwd.h>
28 #include <db.h>
29 #include <time.h>
30 #include <syslog.h>
31 #include <tzfile.h>
32 #include <errno.h>
33 #include <unistd.h>
34 #include <stdio.h>
35 #include <ctype.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <paths.h>
39 
40 /*
41  *  VACATION -- return a message to the sender when on vacation.
42  *
43  *	This program is invoked as a message receiver.  It returns a
44  *	message specified by the user to whomever sent the mail, taking
45  *	care not to return a message too often to prevent "I am on
46  *	vacation" loops.
47  */
48 
49 #define	MAXLINE	1024			/* max line from mail header */
50 #define	VDB	".vacation.db"		/* dbm's database */
51 #define	VMSG	".vacation.msg"		/* vacation message */
52 
53 typedef struct alias {
54 	struct alias *next;
55 	char *name;
56 } ALIAS;
57 ALIAS *names;
58 
59 DB *db;
60 
61 char from[MAXLINE];
62 
63 main(argc, argv)
64 	int argc;
65 	char **argv;
66 {
67 	extern int optind, opterr;
68 	extern char *optarg;
69 	struct passwd *pw;
70 	ALIAS *cur;
71 	time_t interval;
72 	int ch, iflag;
73 
74 	opterr = iflag = 0;
75 	interval = -1;
76 	while ((ch = getopt(argc, argv, "a:Iir:")) != EOF)
77 		switch((char)ch) {
78 		case 'a':			/* alias */
79 			if (!(cur = (ALIAS *)malloc((u_int)sizeof(ALIAS))))
80 				break;
81 			cur->name = optarg;
82 			cur->next = names;
83 			names = cur;
84 			break;
85 		case 'I':			/* backward compatible */
86 		case 'i':			/* init the database */
87 			iflag = 1;
88 			break;
89 		case 'r':
90 			if (isdigit(*optarg)) {
91 				interval = atol(optarg) * SECSPERDAY;
92 				if (interval < 0)
93 					usage();
94 			}
95 			else
96 				interval = LONG_MAX;
97 			break;
98 		case '?':
99 		default:
100 			usage();
101 		}
102 	argc -= optind;
103 	argv += optind;
104 
105 	if (argc != 1) {
106 		if (!iflag)
107 			usage();
108 		if (!(pw = getpwuid(getuid()))) {
109 			syslog(LOG_ERR,
110 			    "vacation: no such user uid %u.\n", getuid());
111 			exit(1);
112 		}
113 	}
114 	else if (!(pw = getpwnam(*argv))) {
115 		syslog(LOG_ERR, "vacation: no such user %s.\n", *argv);
116 		exit(1);
117 	}
118 	if (chdir(pw->pw_dir)) {
119 		syslog(LOG_NOTICE,
120 		    "vacation: no such directory %s.\n", pw->pw_dir);
121 		exit(1);
122 	}
123 
124 	db = dbopen(VDB, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
125 	    S_IRUSR|S_IWUSR, DB_HASH, NULL);
126 	if (!db) {
127 		syslog(LOG_NOTICE, "vacation: %s: %s\n", VDB, strerror(errno));
128 		exit(1);
129 	}
130 
131 	if (interval != -1)
132 		setinterval(interval);
133 
134 	if (iflag) {
135 		(void)(db->close)(db);
136 		exit(0);
137 	}
138 
139 	if (!(cur = malloc((u_int)sizeof(ALIAS))))
140 		exit(1);
141 	cur->name = pw->pw_name;
142 	cur->next = names;
143 	names = cur;
144 
145 	readheaders();
146 	if (!recent()) {
147 		setreply();
148 		(void)(db->close)(db);
149 		sendmessage(pw->pw_name);
150 	}
151 	(void)(db->close)(db);
152 	exit(0);
153 	/* NOTREACHED */
154 }
155 
156 /*
157  * readheaders --
158  *	read mail headers
159  */
160 readheaders()
161 {
162 	register ALIAS *cur;
163 	register char *p;
164 	int tome, cont;
165 	char buf[MAXLINE];
166 
167 	cont = tome = 0;
168 	while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
169 		switch(*buf) {
170 		case 'F':		/* "From " */
171 			cont = 0;
172 			if (!strncmp(buf, "From ", 5)) {
173 				for (p = buf + 5; *p && *p != ' '; ++p);
174 				*p = '\0';
175 				(void)strcpy(from, buf + 5);
176 				if (p = index(from, '\n'))
177 					*p = '\0';
178 				if (junkmail())
179 					exit(0);
180 			}
181 			break;
182 		case 'P':		/* "Precedence:" */
183 			cont = 0;
184 			if (strncasecmp(buf, "Precedence", 10) ||
185 			    buf[10] != ':' && buf[10] != ' ' && buf[10] != '\t')
186 				break;
187 			if (!(p = index(buf, ':')))
188 				break;
189 			while (*++p && isspace(*p));
190 			if (!*p)
191 				break;
192 			if (!strncasecmp(p, "junk", 4) ||
193 			    !strncasecmp(p, "bulk", 4) ||
194 			    !strncasecmp(p, "list", 4))
195 				exit(0);
196 			break;
197 		case 'C':		/* "Cc:" */
198 			if (strncmp(buf, "Cc:", 3))
199 				break;
200 			cont = 1;
201 			goto findme;
202 		case 'T':		/* "To:" */
203 			if (strncmp(buf, "To:", 3))
204 				break;
205 			cont = 1;
206 			goto findme;
207 		default:
208 			if (!isspace(*buf) || !cont || tome) {
209 				cont = 0;
210 				break;
211 			}
212 findme:			for (cur = names; !tome && cur; cur = cur->next)
213 				tome += nsearch(cur->name, buf);
214 		}
215 	if (!tome)
216 		exit(0);
217 	if (!*from) {
218 		syslog(LOG_NOTICE, "vacation: no initial \"From\" line.\n");
219 		exit(1);
220 	}
221 }
222 
223 /*
224  * nsearch --
225  *	do a nice, slow, search of a string for a substring.
226  */
227 nsearch(name, str)
228 	register char *name, *str;
229 {
230 	register int len;
231 
232 	for (len = strlen(name); *str; ++str)
233 		if (*str == *name && !strncasecmp(name, str, len))
234 			return(1);
235 	return(0);
236 }
237 
238 /*
239  * junkmail --
240  *	read the header and return if automagic/junk/bulk/list mail
241  */
242 junkmail()
243 {
244 	static struct ignore {
245 		char	*name;
246 		int	len;
247 	} ignore[] = {
248 		"-request", 8,		"postmaster", 10,	"uucp", 4,
249 		"mailer-daemon", 13,	"mailer", 6,		"-relay", 6,
250 		NULL, NULL,
251 	};
252 	register struct ignore *cur;
253 	register int len;
254 	register char *p;
255 
256 	/*
257 	 * This is mildly amusing, and I'm not positive it's right; trying
258 	 * to find the "real" name of the sender, assuming that addresses
259 	 * will be some variant of:
260 	 *
261 	 * From site!site!SENDER%site.domain%site.domain@site.domain
262 	 */
263 	if (!(p = index(from, '%')))
264 		if (!(p = index(from, '@'))) {
265 			if (p = rindex(from, '!'))
266 				++p;
267 			else
268 				p = from;
269 			for (; *p; ++p);
270 		}
271 	len = p - from;
272 	for (cur = ignore; cur->name; ++cur)
273 		if (len >= cur->len &&
274 		    !strncasecmp(cur->name, p - cur->len, cur->len))
275 			return(1);
276 	return(0);
277 }
278 
279 #define	VIT	"__VACATION__INTERVAL__TIMER__"
280 
281 /*
282  * recent --
283  *	find out if user has gotten a vacation message recently.
284  *	use bcopy for machines with alignment restrictions
285  */
286 recent()
287 {
288 	DBT key, data;
289 	time_t then, next;
290 
291 	/* get interval time */
292 	key.data = VIT;
293 	key.size = sizeof(VIT);
294 	if ((db->get)(db, &key, &data, 0))
295 		next = SECSPERDAY * DAYSPERWEEK;
296 	else
297 		bcopy(data.data, &next, sizeof(next));
298 
299 	/* get record for this address */
300 	key.data = from;
301 	key.size = strlen(from);
302 	if (!(db->get)(db, &key, &data, 0)) {
303 		bcopy(data.data, &then, sizeof(then));
304 		if (next == LONG_MAX || then + next > time(NULL))
305 			return(1);
306 	}
307 	return(0);
308 }
309 
310 /*
311  * setinterval --
312  *	store the reply interval
313  */
314 setinterval(interval)
315 	time_t interval;
316 {
317 	DBT key, data;
318 
319 	key.data = VIT;
320 	key.size = sizeof(VIT);
321 	data.data = &interval;
322 	data.size = sizeof(interval);
323 	(void)(db->put)(db, &key, &data, 0);
324 }
325 
326 /*
327  * setreply --
328  *	store that this user knows about the vacation.
329  */
330 setreply()
331 {
332 	DBT key, data;
333 	time_t now;
334 
335 	key.data = from;
336 	key.size = strlen(from);
337 	(void)time(&now);
338 	data.data = &now;
339 	data.size = sizeof(now);
340 	(void)(db->put)(db, &key, &data, 0);
341 }
342 
343 /*
344  * sendmessage --
345  *	exec sendmail to send the vacation file to sender
346  */
347 sendmessage(myname)
348 	char *myname;
349 {
350 	if (!freopen(VMSG, "r", stdin)) {
351 		syslog(LOG_NOTICE, "vacation: no ~%s/%s file.\n", myname, VMSG);
352 		exit(1);
353 	}
354 	execl(_PATH_SENDMAIL, "sendmail", "-f", myname, from, NULL);
355 	syslog(LOG_ERR, "vacation: can't exec %s.\n", _PATH_SENDMAIL);
356 	exit(1);
357 }
358 
359 usage()
360 {
361 	syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login\n",
362 	    getuid());
363 	exit(1);
364 }
365