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