vacation.c revision 1.35 1 /* $NetBSD: vacation.c,v 1.35 2007/12/15 19:44:54 perry 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.35 2007/12/15 19:44:54 perry 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
81 static const char *dbprefix = ".vacation"; /* dbm's database sans .db */
82 static const char *msgfile = ".vacation.msg"; /* vacation message */
83
84 typedef struct alias {
85 struct alias *next;
86 const char *name;
87 } alias_t;
88 static alias_t *names;
89
90 static DB *db;
91 static char from[MAXLINE];
92 static char subject[MAXLINE];
93
94 static int iflag = 0; /* Initialize the database */
95
96 static int tflag = 0;
97 #define APPARENTLY_TO 1
98 #define DELIVERED_TO 2
99
100 static int fflag = 0;
101 #define FROM_FROM 1
102 #define RETURN_PATH_FROM 2
103 #define SENDER_FROM 4
104
105 static int toanybody = 0; /* Don't check if we appear in the to or cc */
106
107 static int debug = 0;
108
109 static void opendb(void);
110 static int junkmail(const char *);
111 static int nsearch(const char *, const char *);
112 static int readheaders(void);
113 static int recent(void);
114 static void getfrom(char *);
115 static void sendmessage(const char *);
116 static void setinterval(time_t);
117 static void setreply(void);
118 static void usage(void) __dead;
119
120 int
121 main(int argc, char **argv)
122 {
123 struct passwd *pw;
124 alias_t *cur;
125 long interval;
126 int ch, rv;
127 char *p;
128
129 setprogname(argv[0]);
130 opterr = 0;
131 interval = -1;
132 openlog(getprogname(), 0, LOG_USER);
133 while ((ch = getopt(argc, argv, "a:df:F:Iijm:r:s:t:T:")) != -1)
134 switch((char)ch) {
135 case 'a': /* alias */
136 if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t))))
137 break;
138 cur->name = optarg;
139 cur->next = names;
140 names = cur;
141 break;
142 case 'd':
143 debug++;
144 break;
145 case 'F':
146 for (p = optarg; *p; p++)
147 switch (*p) {
148 case 'F':
149 fflag |= FROM_FROM;
150 break;
151 case 'R':
152 fflag |= RETURN_PATH_FROM;
153 break;
154 case 'S':
155 fflag |= SENDER_FROM;
156 break;
157 default:
158 errx(1, "Unknown -f option `%c'", *p);
159 }
160 break;
161 case 'f':
162 dbprefix = optarg;
163 break;
164 case 'I': /* backward compatible */
165 case 'i': /* init the database */
166 iflag = 1;
167 break;
168 case 'j':
169 toanybody = 1;
170 break;
171 case 'm':
172 msgfile = optarg;
173 break;
174 case 'r':
175 case 't': /* Solaris compatibility */
176 if (!isdigit((unsigned char)*optarg)) {
177 interval = LONG_MAX;
178 break;
179 }
180 if (*optarg == '\0')
181 goto bad;
182 interval = strtol(optarg, &p, 0);
183 if (errno == ERANGE &&
184 (interval == LONG_MAX || interval == LONG_MIN))
185 err(1, "Bad interval `%s'", optarg);
186 switch (*p) {
187 case 's':
188 break;
189 case 'm':
190 interval *= SECSPERMIN;
191 break;
192 case 'h':
193 interval *= SECSPERHOUR;
194 break;
195 case 'd':
196 case '\0':
197 interval *= SECSPERDAY;
198 break;
199 case 'w':
200 interval *= DAYSPERWEEK * SECSPERDAY;
201 break;
202 default:
203 bad:
204 errx(1, "Invalid interval `%s'", optarg);
205 }
206 if (interval < 0 || (*p && p[1]))
207 goto bad;
208 break;
209 case 's':
210 (void)strlcpy(from, optarg, sizeof(from));
211 break;
212 case 'T':
213 for (p = optarg; *p; p++)
214 switch (*p) {
215 case 'A':
216 tflag |= APPARENTLY_TO;
217 break;
218 case 'D':
219 tflag |= DELIVERED_TO;
220 break;
221 default:
222 errx(1, "Unknown -t option `%c'", *p);
223 }
224 break;
225 case '?':
226 default:
227 usage();
228 }
229 argc -= optind;
230 argv += optind;
231
232 if (argc != 1) {
233 if (!iflag)
234 usage();
235 if (!(pw = getpwuid(getuid()))) {
236 syslog(LOG_ERR, "%s: no such user uid %u.",
237 getprogname(), getuid());
238 exit(1);
239 }
240 }
241 else if (!(pw = getpwnam(*argv))) {
242 syslog(LOG_ERR, "%s: no such user %s.",
243 getprogname(), *argv);
244 exit(1);
245 }
246 if (chdir(pw->pw_dir) == -1 &&
247 (dbprefix[0] != '/' || msgfile[0] != '/')) {
248 syslog(LOG_ERR, "%s: no such directory %s.",
249 getprogname(), pw->pw_dir);
250 exit(1);
251 }
252
253 opendb();
254
255 if (interval != -1)
256 setinterval((time_t)interval);
257
258 if (iflag) {
259 (void)(db->close)(db);
260 exit(0);
261 }
262
263 if (!(cur = malloc((size_t)sizeof(alias_t)))) {
264 syslog(LOG_ERR, "%s: %m", getprogname());
265 (void)(db->close)(db);
266 exit(1);
267 }
268 cur->name = pw->pw_name;
269 cur->next = names;
270 names = cur;
271
272 if ((rv = readheaders()) != -1) {
273 (void)(db->close)(db);
274 exit(rv);
275 }
276
277 if (!recent()) {
278 setreply();
279 (void)(db->close)(db);
280 sendmessage(pw->pw_name);
281 }
282 else
283 (void)(db->close)(db);
284 exit(0);
285 /* NOTREACHED */
286 }
287
288 static void
289 opendb(void)
290 {
291 char path[MAXPATHLEN];
292
293 (void)snprintf(path, sizeof(path), "%s.db", dbprefix);
294 db = dbopen(path, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
295 S_IRUSR|S_IWUSR, DB_HASH, NULL);
296
297 if (!db) {
298 syslog(LOG_ERR, "%s: %s: %m", getprogname(), path);
299 exit(1);
300 }
301 }
302
303 /*
304 * readheaders --
305 * read mail headers
306 */
307 static int
308 readheaders(void)
309 {
310 alias_t *cur;
311 char *p;
312 int tome, cont;
313 char buf[MAXLINE];
314
315 cont = tome = 0;
316 #define COMPARE(a, b) strncmp(a, b, sizeof(b) - 1)
317 #define CASECOMPARE(a, b) strncasecmp(a, b, sizeof(b) - 1)
318 while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
319 switch(*buf) {
320 case 'F': /* "From " or "From:" */
321 cont = 0;
322 if (COMPARE(buf, "From ") == 0)
323 getfrom(buf + sizeof("From ") - 1);
324 if ((fflag & FROM_FROM) != 0 &&
325 COMPARE(buf, "From:") == 0)
326 getfrom(buf + sizeof("From:") - 1);
327 break;
328 case 'P': /* "Precedence:" */
329 cont = 0;
330 if (CASECOMPARE(buf, "Precedence") != 0 ||
331 (buf[10] != ':' && buf[10] != ' ' &&
332 buf[10] != '\t'))
333 break;
334 if ((p = strchr(buf, ':')) == NULL)
335 break;
336 while (*++p && isspace((unsigned char)*p))
337 continue;
338 if (!*p)
339 break;
340 if (CASECOMPARE(p, "junk") == 0 ||
341 CASECOMPARE(p, "bulk") == 0||
342 CASECOMPARE(p, "list") == 0)
343 exit(0);
344 break;
345 case 'C': /* "Cc:" */
346 if (COMPARE(buf, "Cc:"))
347 break;
348 cont = 1;
349 goto findme;
350 case 'T': /* "To:" */
351 if (COMPARE(buf, "To:"))
352 break;
353 cont = 1;
354 goto findme;
355 case 'A': /* "Apparently-To:" */
356 if ((tflag & APPARENTLY_TO) == 0 ||
357 COMPARE(buf, "Apparently-To:") != 0)
358 break;
359 cont = 1;
360 goto findme;
361 case 'D': /* "Delivered-To:" */
362 if ((tflag & DELIVERED_TO) == 0 ||
363 COMPARE(buf, "Delivered-To:") != 0)
364 break;
365 cont = 1;
366 goto findme;
367 case 'R': /* "Return-Path:" */
368 cont = 0;
369 if ((fflag & RETURN_PATH_FROM) != 0 &&
370 COMPARE(buf, "Return-Path:") == 0)
371 getfrom(buf + sizeof("Return-Path:") - 1);
372 break;
373 case 'S': /* "Sender:" */
374 cont = 0;
375 if (COMPARE(buf, "Subject:") == 0) {
376 /* trim leading blanks */
377 char *s = NULL;
378 for (p = buf + sizeof("Subject:") - 1; *p; p++)
379 if (s == NULL &&
380 !isspace((unsigned char)*p))
381 s = p;
382 /* trim trailing blanks */
383 if (s) {
384 for (--p; p != s; p--)
385 if (!isspace((unsigned char)*p))
386 break;
387 *++p = '\0';
388 }
389 if (s) {
390 (void)strlcpy(subject, s, sizeof(subject));
391 } else {
392 subject[0] = '\0';
393 }
394 }
395 if ((fflag & SENDER_FROM) != 0 &&
396 COMPARE(buf, "Sender:") == 0)
397 getfrom(buf + sizeof("Sender:") - 1);
398 break;
399 default:
400 if (!isspace((unsigned char)*buf) || !cont || tome) {
401 cont = 0;
402 break;
403 }
404 findme: for (cur = names; !tome && cur; cur = cur->next)
405 tome += nsearch(cur->name, buf);
406 }
407 if (!toanybody && !tome)
408 return 0;
409 if (!*from) {
410 syslog(LOG_ERR, "%s: no initial \"From\" line.",
411 getprogname());
412 return 1;
413 }
414 return -1;
415 }
416
417 /*
418 * nsearch --
419 * do a nice, slow, search of a string for a substring.
420 */
421 static int
422 nsearch(const char *name, const char *str)
423 {
424 size_t len;
425
426 for (len = strlen(name); *str; ++str)
427 if (!strncasecmp(name, str, len))
428 return(1);
429 return(0);
430 }
431
432 /*
433 * getfrom --
434 * return the first string in the buffer, stripping leading and trailing
435 * blanks and <>.
436 */
437 void
438 getfrom(char *buf)
439 {
440 char *s, *p;
441
442 if ((s = strchr(buf, '<')) != NULL)
443 s++;
444 else
445 s = buf;
446
447 for (; *s && isspace((unsigned char)*s); s++)
448 continue;
449 for (p = s; *p && !isspace((unsigned char)*p); p++)
450 continue;
451
452 if (*--p == '>')
453 *p = '\0';
454 else
455 *++p = '\0';
456
457 if (junkmail(s))
458 exit(0);
459
460 if (!*from)
461 (void)strlcpy(from, s, sizeof(from));
462 }
463
464 /*
465 * junkmail --
466 * read the header and return if automagic/junk/bulk/list mail
467 */
468 static int
469 junkmail(const char *addr)
470 {
471 static struct ignore {
472 const char *name;
473 size_t len;
474 } ignore[] = {
475 #define INIT(a) { a, sizeof(a) - 1 }
476 INIT("-request"),
477 INIT("postmaster"),
478 INIT("uucp"),
479 INIT("mailer-daemon"),
480 INIT("mailer"),
481 INIT("-relay"),
482 {NULL, 0 }
483 };
484 struct ignore *cur;
485 size_t len;
486 const char *p;
487
488 /*
489 * This is mildly amusing, and I'm not positive it's right; trying
490 * to find the "real" name of the sender, assuming that addresses
491 * will be some variant of:
492 *
493 * From site!site!SENDER%site.domain%site.domain (at) site.domain
494 */
495 if (!(p = strchr(addr, '%')))
496 if (!(p = strchr(addr, '@'))) {
497 if ((p = strrchr(addr, '!')) != NULL)
498 ++p;
499 else
500 p = addr;
501 for (; *p; ++p)
502 continue;
503 }
504 len = p - addr;
505 for (cur = ignore; cur->name; ++cur)
506 if (len >= cur->len &&
507 !strncasecmp(cur->name, p - cur->len, cur->len))
508 return(1);
509 return(0);
510 }
511
512 #define VIT "__VACATION__INTERVAL__TIMER__"
513
514 /*
515 * recent --
516 * find out if user has gotten a vacation message recently.
517 * use memmove for machines with alignment restrictions
518 */
519 static int
520 recent(void)
521 {
522 DBT key, data;
523 time_t then, next;
524
525 /* get interval time */
526 key.data = (void *)(intptr_t)VIT;
527 key.size = sizeof(VIT);
528 if ((db->get)(db, &key, &data, 0))
529 next = SECSPERDAY * DAYSPERWEEK;
530 else
531 (void)memmove(&next, data.data, sizeof(next));
532
533 /* get record for this address */
534 key.data = from;
535 key.size = strlen(from);
536 if (!(db->get)(db, &key, &data, 0)) {
537 (void)memmove(&then, data.data, sizeof(then));
538 if (next == (time_t)LONG_MAX || /* XXX */
539 then + next > time(NULL))
540 return(1);
541 }
542 return(0);
543 }
544
545 /*
546 * setinterval --
547 * store the reply interval
548 */
549 static void
550 setinterval(time_t interval)
551 {
552 DBT key, data;
553
554 key.data = (void *)(intptr_t)VIT;
555 key.size = sizeof(VIT);
556 data.data = &interval;
557 data.size = sizeof(interval);
558 (void)(db->put)(db, &key, &data, 0);
559 }
560
561 /*
562 * setreply --
563 * store that this user knows about the vacation.
564 */
565 static void
566 setreply(void)
567 {
568 DBT key, data;
569 time_t now;
570
571 key.data = from;
572 key.size = strlen(from);
573 (void)time(&now);
574 data.data = &now;
575 data.size = sizeof(now);
576 (void)(db->put)(db, &key, &data, 0);
577 }
578
579 /*
580 * sendmessage --
581 * exec sendmail to send the vacation file to sender
582 */
583 static void
584 sendmessage(const char *myname)
585 {
586 FILE *mfp, *sfp;
587 int i;
588 int pvect[2];
589 char buf[MAXLINE];
590
591 mfp = fopen(msgfile, "r");
592 if (mfp == NULL) {
593 syslog(LOG_ERR, "%s: no `%s' file for `%s'.", getprogname(),
594 myname, msgfile);
595 exit(1);
596 }
597
598 if (debug) {
599 sfp = stdout;
600 } else {
601 if (pipe(pvect) < 0) {
602 syslog(LOG_ERR, "%s: pipe: %m", getprogname());
603 exit(1);
604 }
605 i = vfork();
606 if (i < 0) {
607 syslog(LOG_ERR, "%s: fork: %m", getprogname());
608 exit(1);
609 }
610 if (i == 0) {
611 (void)dup2(pvect[0], 0);
612 (void)close(pvect[0]);
613 (void)close(pvect[1]);
614 (void)close(fileno(mfp));
615 (void)execl(_PATH_SENDMAIL, "sendmail", "-f", myname,
616 "--", from, NULL);
617 syslog(LOG_ERR, "%s: can't exec %s: %m",
618 getprogname(), _PATH_SENDMAIL);
619 _exit(1);
620 }
621 (void)close(pvect[0]);
622 sfp = fdopen(pvect[1], "w");
623 if (sfp == NULL) {
624 syslog(LOG_ERR, "%s: can't fdopen %d: %m",
625 getprogname(), pvect[1]);
626 _exit(1);
627 }
628 }
629 (void)fprintf(sfp, "To: %s\n", from);
630 (void)fputs("Auto-Submitted: auto-replied\n", sfp);
631 while (fgets(buf, sizeof buf, mfp) != NULL) {
632 char *p;
633 if ((p = strstr(buf, "$SUBJECT")) != NULL) {
634 *p = '\0';
635 (void)fputs(buf, sfp);
636 (void)fputs(subject, sfp);
637 p += sizeof("$SUBJECT") - 1;
638 (void)fputs(p, sfp);
639 } else
640 (void)fputs(buf, sfp);
641 }
642 (void)fclose(mfp);
643 if (sfp != stdout)
644 (void)fclose(sfp);
645 }
646
647 static void
648 usage(void)
649 {
650
651 syslog(LOG_ERR, "uid %u: Usage: %s [-dIij] [-a alias] [-f database_file] [-F F|R|S] [-m message_file] [-s sender] [-t interval] [-T A|D]"
652 " login", getuid(), getprogname());
653 exit(1);
654 }
655