xref: /original-bsd/usr.bin/vacation/vacation.c (revision f737e041)
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.2 (Berkeley) 01/26/94";
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 	else
152 		(void)(db->close)(db);
153 	exit(0);
154 	/* NOTREACHED */
155 }
156 
157 /*
158  * readheaders --
159  *	read mail headers
160  */
161 readheaders()
162 {
163 	register ALIAS *cur;
164 	register char *p;
165 	int tome, cont;
166 	char buf[MAXLINE];
167 
168 	cont = tome = 0;
169 	while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
170 		switch(*buf) {
171 		case 'F':		/* "From " */
172 			cont = 0;
173 			if (!strncmp(buf, "From ", 5)) {
174 				for (p = buf + 5; *p && *p != ' '; ++p);
175 				*p = '\0';
176 				(void)strcpy(from, buf + 5);
177 				if (p = index(from, '\n'))
178 					*p = '\0';
179 				if (junkmail())
180 					exit(0);
181 			}
182 			break;
183 		case 'P':		/* "Precedence:" */
184 			cont = 0;
185 			if (strncasecmp(buf, "Precedence", 10) ||
186 			    buf[10] != ':' && buf[10] != ' ' && buf[10] != '\t')
187 				break;
188 			if (!(p = index(buf, ':')))
189 				break;
190 			while (*++p && isspace(*p));
191 			if (!*p)
192 				break;
193 			if (!strncasecmp(p, "junk", 4) ||
194 			    !strncasecmp(p, "bulk", 4) ||
195 			    !strncasecmp(p, "list", 4))
196 				exit(0);
197 			break;
198 		case 'C':		/* "Cc:" */
199 			if (strncmp(buf, "Cc:", 3))
200 				break;
201 			cont = 1;
202 			goto findme;
203 		case 'T':		/* "To:" */
204 			if (strncmp(buf, "To:", 3))
205 				break;
206 			cont = 1;
207 			goto findme;
208 		default:
209 			if (!isspace(*buf) || !cont || tome) {
210 				cont = 0;
211 				break;
212 			}
213 findme:			for (cur = names; !tome && cur; cur = cur->next)
214 				tome += nsearch(cur->name, buf);
215 		}
216 	if (!tome)
217 		exit(0);
218 	if (!*from) {
219 		syslog(LOG_NOTICE, "vacation: no initial \"From\" line.\n");
220 		exit(1);
221 	}
222 }
223 
224 /*
225  * nsearch --
226  *	do a nice, slow, search of a string for a substring.
227  */
228 nsearch(name, str)
229 	register char *name, *str;
230 {
231 	register int len;
232 
233 	for (len = strlen(name); *str; ++str)
234 		if (*str == *name && !strncasecmp(name, str, len))
235 			return(1);
236 	return(0);
237 }
238 
239 /*
240  * junkmail --
241  *	read the header and return if automagic/junk/bulk/list mail
242  */
243 junkmail()
244 {
245 	static struct ignore {
246 		char	*name;
247 		int	len;
248 	} ignore[] = {
249 		"-request", 8,		"postmaster", 10,	"uucp", 4,
250 		"mailer-daemon", 13,	"mailer", 6,		"-relay", 6,
251 		NULL, NULL,
252 	};
253 	register struct ignore *cur;
254 	register int len;
255 	register char *p;
256 
257 	/*
258 	 * This is mildly amusing, and I'm not positive it's right; trying
259 	 * to find the "real" name of the sender, assuming that addresses
260 	 * will be some variant of:
261 	 *
262 	 * From site!site!SENDER%site.domain%site.domain@site.domain
263 	 */
264 	if (!(p = index(from, '%')))
265 		if (!(p = index(from, '@'))) {
266 			if (p = rindex(from, '!'))
267 				++p;
268 			else
269 				p = from;
270 			for (; *p; ++p);
271 		}
272 	len = p - from;
273 	for (cur = ignore; cur->name; ++cur)
274 		if (len >= cur->len &&
275 		    !strncasecmp(cur->name, p - cur->len, cur->len))
276 			return(1);
277 	return(0);
278 }
279 
280 #define	VIT	"__VACATION__INTERVAL__TIMER__"
281 
282 /*
283  * recent --
284  *	find out if user has gotten a vacation message recently.
285  *	use bcopy for machines with alignment restrictions
286  */
287 recent()
288 {
289 	DBT key, data;
290 	time_t then, next;
291 
292 	/* get interval time */
293 	key.data = VIT;
294 	key.size = sizeof(VIT);
295 	if ((db->get)(db, &key, &data, 0))
296 		next = SECSPERDAY * DAYSPERWEEK;
297 	else
298 		bcopy(data.data, &next, sizeof(next));
299 
300 	/* get record for this address */
301 	key.data = from;
302 	key.size = strlen(from);
303 	if (!(db->get)(db, &key, &data, 0)) {
304 		bcopy(data.data, &then, sizeof(then));
305 		if (next == LONG_MAX || then + next > time(NULL))
306 			return(1);
307 	}
308 	return(0);
309 }
310 
311 /*
312  * setinterval --
313  *	store the reply interval
314  */
315 setinterval(interval)
316 	time_t interval;
317 {
318 	DBT key, data;
319 
320 	key.data = VIT;
321 	key.size = sizeof(VIT);
322 	data.data = &interval;
323 	data.size = sizeof(interval);
324 	(void)(db->put)(db, &key, &data, 0);
325 }
326 
327 /*
328  * setreply --
329  *	store that this user knows about the vacation.
330  */
331 setreply()
332 {
333 	DBT key, data;
334 	time_t now;
335 
336 	key.data = from;
337 	key.size = strlen(from);
338 	(void)time(&now);
339 	data.data = &now;
340 	data.size = sizeof(now);
341 	(void)(db->put)(db, &key, &data, 0);
342 }
343 
344 /*
345  * sendmessage --
346  *	exec sendmail to send the vacation file to sender
347  */
348 sendmessage(myname)
349 	char *myname;
350 {
351 	FILE *mfp, *sfp;
352 	int i;
353 	int pvect[2];
354 	char buf[MAXLINE];
355 
356 	mfp = fopen(VMSG, "r");
357 	if (mfp == NULL) {
358 		syslog(LOG_NOTICE, "vacation: no ~%s/%s file.\n", myname, VMSG);
359 		exit(1);
360 	}
361 	if (pipe(pvect) < 0) {
362 		syslog(LOG_ERR, "vacation: pipe: %s", strerror(errno));
363 		exit(1);
364 	}
365 	i = vfork();
366 	if (i < 0) {
367 		syslog(LOG_ERR, "vacation: fork: %s", strerror(errno));
368 		exit(1);
369 	}
370 	if (i == 0) {
371 		dup2(pvect[0], 0);
372 		close(pvect[0]);
373 		close(pvect[1]);
374 		fclose(mfp);
375 		execl(_PATH_SENDMAIL, "sendmail", "-f", myname, from, NULL);
376 		syslog(LOG_ERR, "vacation: can't exec %s: %s",
377 			_PATH_SENDMAIL, strerror(errno));
378 		exit(1);
379 	}
380 	close(pvect[0]);
381 	sfp = fdopen(pvect[1], "w");
382 	fprintf(sfp, "To: %s\n", from);
383 	while (fgets(buf, sizeof buf, mfp))
384 		fputs(buf, sfp);
385 	fclose(mfp);
386 	fclose(sfp);
387 }
388 
389 usage()
390 {
391 	syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login\n",
392 	    getuid());
393 	exit(1);
394 }
395