vacation.c revision 1.24 1 /* $NetBSD: vacation.c,v 1.24 2003/08/07 11:16:59 agc 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\n\
36 The Regents of the University of California. All rights reserved.\n");
37 #endif /* not lint */
38
39 #ifndef lint
40 #if 0
41 static char sccsid[] = "@(#)vacation.c 8.2 (Berkeley) 1/26/94";
42 #endif
43 __RCSID("$NetBSD: vacation.c,v 1.24 2003/08/07 11:16:59 agc 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 #define VDB ".vacation.db" /* dbm's database */
81 #define VMSG ".vacation.msg" /* vacation message */
82
83 typedef struct alias {
84 struct alias *next;
85 const char *name;
86 } alias_t;
87 alias_t *names;
88
89 DB *db;
90 char from[MAXLINE];
91 static int tflag = 0;
92 #define APPARENTLY_TO 1
93 #define DELIVERED_TO 2
94
95 int main(int, char **);
96 int junkmail(void);
97 int nsearch(const char *, const char *);
98 void readheaders(void);
99 int recent(void);
100 void sendmessage(const char *);
101 void setinterval(time_t);
102 void setreply(void);
103 void usage(void);
104
105 int
106 main(int argc, char **argv)
107 {
108 struct passwd *pw;
109 alias_t *cur;
110 time_t interval;
111 int ch, iflag;
112 char *p;
113
114 opterr = iflag = 0;
115 interval = -1;
116 openlog("vacation", 0, LOG_USER);
117 while ((ch = getopt(argc, argv, "a:Iir:t:")) != -1)
118 switch((char)ch) {
119 case 'a': /* alias */
120 if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t))))
121 break;
122 cur->name = optarg;
123 cur->next = names;
124 names = cur;
125 break;
126 case 'I': /* backward compatible */
127 case 'i': /* init the database */
128 iflag = 1;
129 break;
130 case 'r':
131 if (isdigit((unsigned char)*optarg)) {
132 interval = atol(optarg) * SECSPERDAY;
133 if (interval < 0)
134 usage();
135 }
136 else
137 interval = (time_t)LONG_MAX; /* XXX */
138 break;
139 case 't':
140 for (p = optarg; *p; p++)
141 switch (*p) {
142 case 'A':
143 tflag |= APPARENTLY_TO;
144 break;
145 case 'D':
146 tflag |= DELIVERED_TO;
147 break;
148 default:
149 errx(1, "Unknown -t option `%c'", *p);
150 }
151 break;
152 case '?':
153 default:
154 usage();
155 }
156 argc -= optind;
157 argv += optind;
158
159 if (argc != 1) {
160 if (!iflag)
161 usage();
162 if (!(pw = getpwuid(getuid()))) {
163 syslog(LOG_ERR,
164 "vacation: no such user uid %u.", getuid());
165 exit(1);
166 }
167 }
168 else if (!(pw = getpwnam(*argv))) {
169 syslog(LOG_ERR, "vacation: no such user %s.", *argv);
170 exit(1);
171 }
172 if (chdir(pw->pw_dir)) {
173 syslog(LOG_ERR,
174 "vacation: no such directory %s.", pw->pw_dir);
175 exit(1);
176 }
177
178 db = dbopen(VDB, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
179 S_IRUSR|S_IWUSR, DB_HASH, NULL);
180 if (!db) {
181 syslog(LOG_ERR, "vacation: %s: %m", VDB);
182 exit(1);
183 }
184
185 if (interval != -1)
186 setinterval(interval);
187
188 if (iflag) {
189 (void)(db->close)(db);
190 exit(0);
191 }
192
193 if (!(cur = malloc((size_t)sizeof(alias_t))))
194 exit(1);
195 cur->name = pw->pw_name;
196 cur->next = names;
197 names = cur;
198
199 readheaders();
200 if (!recent()) {
201 setreply();
202 (void)(db->close)(db);
203 sendmessage(pw->pw_name);
204 }
205 else
206 (void)(db->close)(db);
207 exit(0);
208 /* NOTREACHED */
209 }
210
211 /*
212 * readheaders --
213 * read mail headers
214 */
215 void
216 readheaders(void)
217 {
218 alias_t *cur;
219 char *p;
220 int tome, cont;
221 char buf[MAXLINE];
222
223 cont = tome = 0;
224 while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
225 switch(*buf) {
226 case 'F': /* "From " */
227 /* XXX should instead consider Return-Path: and Sender: */
228 cont = 0;
229 if (!strncmp(buf, "From ", 5)) {
230 for (p = buf + 5; *p && *p != ' '; ++p);
231 *p = '\0';
232 (void)strlcpy(from, buf + 5, sizeof(from));
233 if ((p = strchr(from, '\n')))
234 *p = '\0';
235 if (junkmail())
236 exit(0);
237 }
238 break;
239 case 'P': /* "Precedence:" */
240 cont = 0;
241 if (strncasecmp(buf, "Precedence", 10) ||
242 (buf[10] != ':' && buf[10] != ' ' &&
243 buf[10] != '\t'))
244 break;
245 if (!(p = strchr(buf, ':')))
246 break;
247 while (*++p && isspace((unsigned char)*p));
248 if (!*p)
249 break;
250 if (!strncasecmp(p, "junk", 4) ||
251 !strncasecmp(p, "bulk", 4) ||
252 !strncasecmp(p, "list", 4))
253 exit(0);
254 break;
255 case 'C': /* "Cc:" */
256 if (strncmp(buf, "Cc:", 3))
257 break;
258 cont = 1;
259 goto findme;
260 case 'T': /* "To:" */
261 if (strncmp(buf, "To:", 3))
262 break;
263 cont = 1;
264 goto findme;
265 case 'A':
266 if ((tflag & APPARENTLY_TO) == 0 ||
267 strncmp(buf, "Apparently-To:", 3))
268 break;
269 cont = 1;
270 goto findme;
271 case 'D':
272 if ((tflag & DELIVERED_TO) == 0 ||
273 strncmp(buf, "Delivered-To:", 3))
274 break;
275 cont = 1;
276 goto findme;
277 default:
278 if (!isspace((unsigned char)*buf) || !cont || tome) {
279 cont = 0;
280 break;
281 }
282 findme: for (cur = names; !tome && cur; cur = cur->next)
283 tome += nsearch(cur->name, buf);
284 }
285 if (!tome)
286 exit(0);
287 if (!*from) {
288 syslog(LOG_ERR, "vacation: no initial \"From\" line.");
289 exit(1);
290 }
291 }
292
293 /*
294 * nsearch --
295 * do a nice, slow, search of a string for a substring.
296 */
297 int
298 nsearch(const char *name, const char *str)
299 {
300 size_t len;
301
302 for (len = strlen(name); *str; ++str)
303 if (!strncasecmp(name, str, len))
304 return(1);
305 return(0);
306 }
307
308 /*
309 * junkmail --
310 * read the header and return if automagic/junk/bulk/list mail
311 */
312 int
313 junkmail(void)
314 {
315 static struct ignore {
316 char *name;
317 int len;
318 } ignore[] = {
319 { "-request", 8 },
320 { "postmaster", 10 },
321 { "uucp", 4 },
322 { "mailer-daemon", 13 },
323 { "mailer", 6 },
324 { "-relay", 6 },
325 {NULL, 0 }
326 };
327 struct ignore *cur;
328 int len;
329 char *p;
330
331 /*
332 * This is mildly amusing, and I'm not positive it's right; trying
333 * to find the "real" name of the sender, assuming that addresses
334 * will be some variant of:
335 *
336 * From site!site!SENDER%site.domain%site.domain (at) site.domain
337 */
338 if (!(p = strchr(from, '%')))
339 if (!(p = strchr(from, '@'))) {
340 if ((p = strrchr(from, '!')))
341 ++p;
342 else
343 p = from;
344 for (; *p; ++p);
345 }
346 len = p - from;
347 for (cur = ignore; cur->name; ++cur)
348 if (len >= cur->len &&
349 !strncasecmp(cur->name, p - cur->len, cur->len))
350 return(1);
351 return(0);
352 }
353
354 #define VIT "__VACATION__INTERVAL__TIMER__"
355
356 /*
357 * recent --
358 * find out if user has gotten a vacation message recently.
359 * use memmove for machines with alignment restrictions
360 */
361 int
362 recent(void)
363 {
364 DBT key, data;
365 time_t then, next;
366
367 /* get interval time */
368 key.data = VIT;
369 key.size = sizeof(VIT);
370 if ((db->get)(db, &key, &data, 0))
371 next = SECSPERDAY * DAYSPERWEEK;
372 else
373 memmove(&next, data.data, sizeof(next));
374
375 /* get record for this address */
376 key.data = from;
377 key.size = strlen(from);
378 if (!(db->get)(db, &key, &data, 0)) {
379 memmove(&then, data.data, sizeof(then));
380 if (next == (time_t)LONG_MAX || /* XXX */
381 then + next > time(NULL))
382 return(1);
383 }
384 return(0);
385 }
386
387 /*
388 * setinterval --
389 * store the reply interval
390 */
391 void
392 setinterval(time_t interval)
393 {
394 DBT key, data;
395
396 key.data = VIT;
397 key.size = sizeof(VIT);
398 data.data = &interval;
399 data.size = sizeof(interval);
400 (void)(db->put)(db, &key, &data, 0);
401 }
402
403 /*
404 * setreply --
405 * store that this user knows about the vacation.
406 */
407 void
408 setreply(void)
409 {
410 DBT key, data;
411 time_t now;
412
413 key.data = from;
414 key.size = strlen(from);
415 (void)time(&now);
416 data.data = &now;
417 data.size = sizeof(now);
418 (void)(db->put)(db, &key, &data, 0);
419 }
420
421 /*
422 * sendmessage --
423 * exec sendmail to send the vacation file to sender
424 */
425 void
426 sendmessage(const char *myname)
427 {
428 FILE *mfp, *sfp;
429 int i;
430 int pvect[2];
431 char buf[MAXLINE];
432
433 mfp = fopen(VMSG, "r");
434 if (mfp == NULL) {
435 syslog(LOG_ERR, "vacation: no ~%s/%s file.", myname, VMSG);
436 exit(1);
437 }
438 if (pipe(pvect) < 0) {
439 syslog(LOG_ERR, "vacation: pipe: %m");
440 exit(1);
441 }
442 i = vfork();
443 if (i < 0) {
444 syslog(LOG_ERR, "vacation: fork: %m");
445 exit(1);
446 }
447 if (i == 0) {
448 dup2(pvect[0], 0);
449 close(pvect[0]);
450 close(pvect[1]);
451 close(fileno(mfp));
452 execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--", from,
453 NULL);
454 syslog(LOG_ERR, "vacation: can't exec %s: %m",
455 _PATH_SENDMAIL);
456 _exit(1);
457 }
458 close(pvect[0]);
459 sfp = fdopen(pvect[1], "w");
460 fprintf(sfp, "To: %s\n", from);
461 while (fgets(buf, sizeof buf, mfp))
462 fputs(buf, sfp);
463 fclose(mfp);
464 fclose(sfp);
465 }
466
467 void
468 usage(void)
469 {
470
471 syslog(LOG_ERR, "uid %u: usage: %s [-i] [-a alias] [-t A|D] login",
472 getuid(), getprogname());
473 exit(1);
474 }
475