vacation.c revision 1.33 1 /* $NetBSD: vacation.c,v 1.33 2007/07/13 13:21:49 christos 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.33 2007/07/13 13:21:49 christos 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) __attribute__((__noreturn__));
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)) {
247 syslog(LOG_ERR, "%s: no such directory %s.",
248 getprogname(), pw->pw_dir);
249 exit(1);
250 }
251
252 opendb();
253
254 if (interval != -1)
255 setinterval((time_t)interval);
256
257 if (iflag) {
258 (void)(db->close)(db);
259 exit(0);
260 }
261
262 if (!(cur = malloc((size_t)sizeof(alias_t)))) {
263 syslog(LOG_ERR, "%s: %m", getprogname());
264 (void)(db->close)(db);
265 exit(1);
266 }
267 cur->name = pw->pw_name;
268 cur->next = names;
269 names = cur;
270
271 if ((rv = readheaders()) != -1) {
272 (void)(db->close)(db);
273 exit(rv);
274 }
275
276 if (!recent()) {
277 setreply();
278 (void)(db->close)(db);
279 sendmessage(pw->pw_name);
280 }
281 else
282 (void)(db->close)(db);
283 exit(0);
284 /* NOTREACHED */
285 }
286
287 static void
288 opendb(void)
289 {
290 char path[MAXPATHLEN];
291
292 (void)snprintf(path, sizeof(path), "%s.db", dbprefix);
293 db = dbopen(path, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
294 S_IRUSR|S_IWUSR, DB_HASH, NULL);
295
296 if (!db) {
297 syslog(LOG_ERR, "%s: %s: %m", getprogname(), path);
298 exit(1);
299 }
300 }
301
302 /*
303 * readheaders --
304 * read mail headers
305 */
306 static int
307 readheaders(void)
308 {
309 alias_t *cur;
310 char *p;
311 int tome, cont;
312 char buf[MAXLINE];
313
314 cont = tome = 0;
315 #define COMPARE(a, b) strncmp(a, b, sizeof(b) - 1)
316 #define CASECOMPARE(a, b) strncasecmp(a, b, sizeof(b) - 1)
317 while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
318 switch(*buf) {
319 case 'F': /* "From " or "From:" */
320 cont = 0;
321 if (COMPARE(buf, "From ") == 0)
322 getfrom(buf + sizeof("From ") - 1);
323 if ((fflag & FROM_FROM) != 0 &&
324 COMPARE(buf, "From:") == 0)
325 getfrom(buf + sizeof("From:") - 1);
326 break;
327 case 'P': /* "Precedence:" */
328 cont = 0;
329 if (CASECOMPARE(buf, "Precedence") != 0 ||
330 (buf[10] != ':' && buf[10] != ' ' &&
331 buf[10] != '\t'))
332 break;
333 if ((p = strchr(buf, ':')) == NULL)
334 break;
335 while (*++p && isspace((unsigned char)*p))
336 continue;
337 if (!*p)
338 break;
339 if (CASECOMPARE(p, "junk") == 0 ||
340 CASECOMPARE(p, "bulk") == 0||
341 CASECOMPARE(p, "list") == 0)
342 exit(0);
343 break;
344 case 'C': /* "Cc:" */
345 if (COMPARE(buf, "Cc:"))
346 break;
347 cont = 1;
348 goto findme;
349 case 'T': /* "To:" */
350 if (COMPARE(buf, "To:"))
351 break;
352 cont = 1;
353 goto findme;
354 case 'A': /* "Apparently-To:" */
355 if ((tflag & APPARENTLY_TO) == 0 ||
356 COMPARE(buf, "Apparently-To:") != 0)
357 break;
358 cont = 1;
359 goto findme;
360 case 'D': /* "Delivered-To:" */
361 if ((tflag & DELIVERED_TO) == 0 ||
362 COMPARE(buf, "Delivered-To:") != 0)
363 break;
364 cont = 1;
365 goto findme;
366 case 'R': /* "Return-Path:" */
367 cont = 0;
368 if ((fflag & RETURN_PATH_FROM) != 0 &&
369 COMPARE(buf, "Return-Path:") == 0)
370 getfrom(buf + sizeof("Return-Path:") - 1);
371 break;
372 case 'S': /* "Sender:" */
373 cont = 0;
374 if (COMPARE(buf, "Subject:") == 0) {
375 /* trim leading blanks */
376 char *s = NULL;
377 for (p = buf + sizeof("Subject:") - 1; *p; p++)
378 if (s == NULL &&
379 !isspace((unsigned char)*p))
380 s = p;
381 /* trim trailing blanks */
382 if (s) {
383 for (--p; p != s; p--)
384 if (!isspace((unsigned char)*p))
385 break;
386 *++p = '\0';
387 }
388 if (s) {
389 (void)strlcpy(subject, s, sizeof(subject));
390 } else {
391 subject[0] = '\0';
392 }
393 }
394 if ((fflag & SENDER_FROM) != 0 &&
395 COMPARE(buf, "Sender:") == 0)
396 getfrom(buf + sizeof("Sender:") - 1);
397 break;
398 default:
399 if (!isspace((unsigned char)*buf) || !cont || tome) {
400 cont = 0;
401 break;
402 }
403 findme: for (cur = names; !tome && cur; cur = cur->next)
404 tome += nsearch(cur->name, buf);
405 }
406 if (!toanybody && !tome)
407 return 0;
408 if (!*from) {
409 syslog(LOG_ERR, "%s: no initial \"From\" line.",
410 getprogname());
411 return 1;
412 }
413 return -1;
414 }
415
416 /*
417 * nsearch --
418 * do a nice, slow, search of a string for a substring.
419 */
420 static int
421 nsearch(const char *name, const char *str)
422 {
423 size_t len;
424
425 for (len = strlen(name); *str; ++str)
426 if (!strncasecmp(name, str, len))
427 return(1);
428 return(0);
429 }
430
431 /*
432 * getfrom --
433 * return the first string in the buffer, stripping leading and trailing
434 * blanks and <>.
435 */
436 void
437 getfrom(char *buf)
438 {
439 char *s, *p;
440
441 if ((s = strchr(buf, '<')) != NULL)
442 s++;
443 else
444 s = buf;
445
446 for (; *s && isspace((unsigned char)*s); s++)
447 continue;
448 for (p = s; *p && !isspace((unsigned char)*p); p++)
449 continue;
450
451 if (*--p == '>')
452 *p = '\0';
453 else
454 *++p = '\0';
455
456 if (junkmail(s))
457 exit(0);
458
459 if (!*from)
460 (void)strlcpy(from, s, sizeof(from));
461 }
462
463 /*
464 * junkmail --
465 * read the header and return if automagic/junk/bulk/list mail
466 */
467 static int
468 junkmail(const char *addr)
469 {
470 static struct ignore {
471 const char *name;
472 size_t len;
473 } ignore[] = {
474 #define INIT(a) { a, sizeof(a) - 1 }
475 INIT("-request"),
476 INIT("postmaster"),
477 INIT("uucp"),
478 INIT("mailer-daemon"),
479 INIT("mailer"),
480 INIT("-relay"),
481 {NULL, 0 }
482 };
483 struct ignore *cur;
484 size_t len;
485 const char *p;
486
487 /*
488 * This is mildly amusing, and I'm not positive it's right; trying
489 * to find the "real" name of the sender, assuming that addresses
490 * will be some variant of:
491 *
492 * From site!site!SENDER%site.domain%site.domain (at) site.domain
493 */
494 if (!(p = strchr(addr, '%')))
495 if (!(p = strchr(addr, '@'))) {
496 if ((p = strrchr(addr, '!')) != NULL)
497 ++p;
498 else
499 p = addr;
500 for (; *p; ++p)
501 continue;
502 }
503 len = p - addr;
504 for (cur = ignore; cur->name; ++cur)
505 if (len >= cur->len &&
506 !strncasecmp(cur->name, p - cur->len, cur->len))
507 return(1);
508 return(0);
509 }
510
511 #define VIT "__VACATION__INTERVAL__TIMER__"
512
513 /*
514 * recent --
515 * find out if user has gotten a vacation message recently.
516 * use memmove for machines with alignment restrictions
517 */
518 static int
519 recent(void)
520 {
521 DBT key, data;
522 time_t then, next;
523
524 /* get interval time */
525 key.data = (void *)(intptr_t)VIT;
526 key.size = sizeof(VIT);
527 if ((db->get)(db, &key, &data, 0))
528 next = SECSPERDAY * DAYSPERWEEK;
529 else
530 (void)memmove(&next, data.data, sizeof(next));
531
532 /* get record for this address */
533 key.data = from;
534 key.size = strlen(from);
535 if (!(db->get)(db, &key, &data, 0)) {
536 (void)memmove(&then, data.data, sizeof(then));
537 if (next == (time_t)LONG_MAX || /* XXX */
538 then + next > time(NULL))
539 return(1);
540 }
541 return(0);
542 }
543
544 /*
545 * setinterval --
546 * store the reply interval
547 */
548 static void
549 setinterval(time_t interval)
550 {
551 DBT key, data;
552
553 key.data = (void *)(intptr_t)VIT;
554 key.size = sizeof(VIT);
555 data.data = &interval;
556 data.size = sizeof(interval);
557 (void)(db->put)(db, &key, &data, 0);
558 }
559
560 /*
561 * setreply --
562 * store that this user knows about the vacation.
563 */
564 static void
565 setreply(void)
566 {
567 DBT key, data;
568 time_t now;
569
570 key.data = from;
571 key.size = strlen(from);
572 (void)time(&now);
573 data.data = &now;
574 data.size = sizeof(now);
575 (void)(db->put)(db, &key, &data, 0);
576 }
577
578 /*
579 * sendmessage --
580 * exec sendmail to send the vacation file to sender
581 */
582 static void
583 sendmessage(const char *myname)
584 {
585 FILE *mfp, *sfp;
586 int i;
587 int pvect[2];
588 char buf[MAXLINE];
589
590 mfp = fopen(msgfile, "r");
591 if (mfp == NULL) {
592 syslog(LOG_ERR, "%s: no `%s' file for `%s'.", getprogname(),
593 myname, msgfile);
594 exit(1);
595 }
596
597 if (debug) {
598 sfp = stdout;
599 } else {
600 if (pipe(pvect) < 0) {
601 syslog(LOG_ERR, "%s: pipe: %m", getprogname());
602 exit(1);
603 }
604 i = vfork();
605 if (i < 0) {
606 syslog(LOG_ERR, "%s: fork: %m", getprogname());
607 exit(1);
608 }
609 if (i == 0) {
610 (void)dup2(pvect[0], 0);
611 (void)close(pvect[0]);
612 (void)close(pvect[1]);
613 (void)close(fileno(mfp));
614 (void)execl(_PATH_SENDMAIL, "sendmail", "-f", myname,
615 "--", from, NULL);
616 syslog(LOG_ERR, "%s: can't exec %s: %m",
617 getprogname(), _PATH_SENDMAIL);
618 _exit(1);
619 }
620 (void)close(pvect[0]);
621 sfp = fdopen(pvect[1], "w");
622 if (sfp == NULL) {
623 syslog(LOG_ERR, "%s: can't fdopen %d: %m",
624 getprogname(), pvect[1]);
625 _exit(1);
626 }
627 }
628 (void)fprintf(sfp, "To: %s\n", from);
629 (void)fputs("Auto-Submitted: auto-replied\n", sfp);
630 while (fgets(buf, sizeof buf, mfp) != NULL) {
631 char *p;
632 if ((p = strstr(buf, "$SUBJECT")) != NULL) {
633 *p = '\0';
634 (void)fputs(buf, sfp);
635 (void)fputs(subject, sfp);
636 p += sizeof("$SUBJECT") - 1;
637 (void)fputs(p, sfp);
638 } else
639 (void)fputs(buf, sfp);
640 }
641 (void)fclose(mfp);
642 if (sfp != stdout)
643 (void)fclose(sfp);
644 }
645
646 static void
647 usage(void)
648 {
649
650 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]"
651 " login", getuid(), getprogname());
652 exit(1);
653 }
654