xref: /original-bsd/usr.bin/vacation/vacation.c (revision 68549010)
1 /*
2  * Copyright (c) 1983, 1987 Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 char copyright[] =
10 "@(#) Copyright (c) 1983, 1987 Regents of the University of California.\n\
11  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)vacation.c	5.20 (Berkeley) 09/23/91";
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 				exit(0);
195 			break;
196 		case 'C':		/* "Cc:" */
197 			if (strncmp(buf, "Cc:", 3))
198 				break;
199 			cont = 1;
200 			goto findme;
201 		case 'T':		/* "To:" */
202 			if (strncmp(buf, "To:", 3))
203 				break;
204 			cont = 1;
205 			goto findme;
206 		default:
207 			if (!isspace(*buf) || !cont || tome) {
208 				cont = 0;
209 				break;
210 			}
211 findme:			for (cur = names; !tome && cur; cur = cur->next)
212 				tome += nsearch(cur->name, buf);
213 		}
214 	if (!tome)
215 		exit(0);
216 	if (!*from) {
217 		syslog(LOG_NOTICE, "vacation: no initial \"From\" line.\n");
218 		exit(1);
219 	}
220 }
221 
222 /*
223  * nsearch --
224  *	do a nice, slow, search of a string for a substring.
225  */
226 nsearch(name, str)
227 	register char *name, *str;
228 {
229 	register int len;
230 
231 	for (len = strlen(name); *str; ++str)
232 		if (*str == *name && !strncasecmp(name, str, len))
233 			return(1);
234 	return(0);
235 }
236 
237 /*
238  * junkmail --
239  *	read the header and return if automagic/junk/bulk mail
240  */
241 junkmail()
242 {
243 	static struct ignore {
244 		char	*name;
245 		int	len;
246 	} ignore[] = {
247 		"-request", 8,		"postmaster", 10,	"uucp", 4,
248 		"mailer-daemon", 13,	"mailer", 6,		"-relay", 6,
249 		NULL, NULL,
250 	};
251 	register struct ignore *cur;
252 	register int len;
253 	register char *p;
254 
255 	/*
256 	 * This is mildly amusing, and I'm not positive it's right; trying
257 	 * to find the "real" name of the sender, assuming that addresses
258 	 * will be some variant of:
259 	 *
260 	 * From site!site!SENDER%site.domain%site.domain@site.domain
261 	 */
262 	if (!(p = index(from, '%')))
263 		if (!(p = index(from, '@'))) {
264 			if (p = rindex(from, '!'))
265 				++p;
266 			else
267 				p = from;
268 			for (; *p; ++p);
269 		}
270 	len = p - from;
271 	for (cur = ignore; cur->name; ++cur)
272 		if (len >= cur->len &&
273 		    !strncasecmp(cur->name, p - cur->len, cur->len))
274 			return(1);
275 	return(0);
276 }
277 
278 #define	VIT	"__VACATION__INTERVAL__TIMER__"
279 
280 /*
281  * recent --
282  *	find out if user has gotten a vacation message recently.
283  *	use bcopy for machines with alignment restrictions
284  */
285 recent()
286 {
287 	DBT key, data;
288 	time_t then, next;
289 
290 	/* get interval time */
291 	key.data = VIT;
292 	key.size = sizeof(VIT);
293 	if ((db->get)(db, &key, &data, 0))
294 		next = SECSPERDAY * DAYSPERWEEK;
295 	else
296 		bcopy(data.data, &next, sizeof(next));
297 
298 	/* get record for this address */
299 	key.data = from;
300 	key.size = strlen(from);
301 	if (!(db->get)(db, &key, &data, 0)) {
302 		bcopy(data.data, &then, sizeof(then));
303 		if (next == LONG_MAX || then + next > time(NULL))
304 			return(1);
305 	}
306 	return(0);
307 }
308 
309 /*
310  * setinterval --
311  *	store the reply interval
312  */
313 setinterval(interval)
314 	time_t interval;
315 {
316 	DBT key, data;
317 
318 	key.data = VIT;
319 	key.size = sizeof(VIT);
320 	data.data = &interval;
321 	data.size = sizeof(interval);
322 	(void)(db->put)(db, &key, &data, 0);
323 }
324 
325 /*
326  * setreply --
327  *	store that this user knows about the vacation.
328  */
329 setreply()
330 {
331 	DBT key, data;
332 	time_t now;
333 
334 	key.data = from;
335 	key.size = strlen(from);
336 	(void)time(&now);
337 	data.data = &now;
338 	data.size = sizeof(now);
339 	(void)(db->put)(db, &key, &data, 0);
340 }
341 
342 /*
343  * sendmessage --
344  *	exec sendmail to send the vacation file to sender
345  */
346 sendmessage(myname)
347 	char *myname;
348 {
349 	if (!freopen(VMSG, "r", stdin)) {
350 		syslog(LOG_NOTICE, "vacation: no ~%s/%s file.\n", myname, VMSG);
351 		exit(1);
352 	}
353 	execl(_PATH_SENDMAIL, "sendmail", "-f", myname, from, NULL);
354 	syslog(LOG_ERR, "vacation: can't exec %s.\n", _PATH_SENDMAIL);
355 	exit(1);
356 }
357 
358 usage()
359 {
360 	syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login\n",
361 	    getuid());
362 	exit(1);
363 }
364