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