vacation.c revision 1.30 1 /* $NetBSD: vacation.c,v 1.30 2004/08/19 13:43:54 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.30 2004/08/19 13:43:54 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 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)) {
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 (void)strlcpy(subject, s, sizeof(subject));
390 }
391 if ((fflag & SENDER_FROM) != 0 &&
392 COMPARE(buf, "Sender:") == 0)
393 getfrom(buf + sizeof("Sender:") - 1);
394 break;
395 default:
396 if (!isspace((unsigned char)*buf) || !cont || tome) {
397 cont = 0;
398 break;
399 }
400 findme: for (cur = names; !tome && cur; cur = cur->next)
401 tome += nsearch(cur->name, buf);
402 }
403 if (!toanybody && !tome)
404 return 0;
405 if (!*from) {
406 syslog(LOG_ERR, "%s: no initial \"From\" line.",
407 getprogname());
408 return 1;
409 }
410 return -1;
411 }
412
413 /*
414 * nsearch --
415 * do a nice, slow, search of a string for a substring.
416 */
417 static int
418 nsearch(const char *name, const char *str)
419 {
420 size_t len;
421
422 for (len = strlen(name); *str; ++str)
423 if (!strncasecmp(name, str, len))
424 return(1);
425 return(0);
426 }
427
428 /*
429 * getfrom --
430 * return the first string in the buffer, stripping leading and trailing
431 * blanks and <>.
432 */
433 void
434 getfrom(char *buf)
435 {
436 char *s, *p;
437
438 if ((s = strchr(buf, '<')) != NULL)
439 s++;
440 else
441 s = buf;
442
443 for (; *s && isspace((unsigned char)*s); s++)
444 continue;
445 for (p = s; *p && !isspace((unsigned char)*p); p++)
446 continue;
447
448 if (*--p == '>')
449 *p = '\0';
450 else
451 *++p = '\0';
452
453 if (junkmail(s))
454 exit(0);
455
456 if (!*from)
457 (void)strlcpy(from, s, sizeof(from));
458 }
459
460 /*
461 * junkmail --
462 * read the header and return if automagic/junk/bulk/list mail
463 */
464 static int
465 junkmail(const char *addr)
466 {
467 static struct ignore {
468 const char *name;
469 size_t len;
470 } ignore[] = {
471 #define INIT(a) { a, sizeof(a) - 1 }
472 INIT("-request"),
473 INIT("postmaster"),
474 INIT("uucp"),
475 INIT("mailer-daemon"),
476 INIT("mailer"),
477 INIT("-relay"),
478 {NULL, 0 }
479 };
480 struct ignore *cur;
481 size_t len;
482 const char *p;
483
484 /*
485 * This is mildly amusing, and I'm not positive it's right; trying
486 * to find the "real" name of the sender, assuming that addresses
487 * will be some variant of:
488 *
489 * From site!site!SENDER%site.domain%site.domain (at) site.domain
490 */
491 if (!(p = strchr(addr, '%')))
492 if (!(p = strchr(addr, '@'))) {
493 if ((p = strrchr(addr, '!')) != NULL)
494 ++p;
495 else
496 p = addr;
497 for (; *p; ++p)
498 continue;
499 }
500 len = p - addr;
501 for (cur = ignore; cur->name; ++cur)
502 if (len >= cur->len &&
503 !strncasecmp(cur->name, p - cur->len, cur->len))
504 return(1);
505 return(0);
506 }
507
508 #define VIT "__VACATION__INTERVAL__TIMER__"
509
510 /*
511 * recent --
512 * find out if user has gotten a vacation message recently.
513 * use memmove for machines with alignment restrictions
514 */
515 static int
516 recent(void)
517 {
518 DBT key, data;
519 time_t then, next;
520
521 /* get interval time */
522 key.data = (void *)(intptr_t)VIT;
523 key.size = sizeof(VIT);
524 if ((db->get)(db, &key, &data, 0))
525 next = SECSPERDAY * DAYSPERWEEK;
526 else
527 (void)memmove(&next, data.data, sizeof(next));
528
529 /* get record for this address */
530 key.data = from;
531 key.size = strlen(from);
532 if (!(db->get)(db, &key, &data, 0)) {
533 (void)memmove(&then, data.data, sizeof(then));
534 if (next == (time_t)LONG_MAX || /* XXX */
535 then + next > time(NULL))
536 return(1);
537 }
538 return(0);
539 }
540
541 /*
542 * setinterval --
543 * store the reply interval
544 */
545 static void
546 setinterval(time_t interval)
547 {
548 DBT key, data;
549
550 key.data = (void *)(intptr_t)VIT;
551 key.size = sizeof(VIT);
552 data.data = &interval;
553 data.size = sizeof(interval);
554 (void)(db->put)(db, &key, &data, 0);
555 }
556
557 /*
558 * setreply --
559 * store that this user knows about the vacation.
560 */
561 static void
562 setreply(void)
563 {
564 DBT key, data;
565 time_t now;
566
567 key.data = from;
568 key.size = strlen(from);
569 (void)time(&now);
570 data.data = &now;
571 data.size = sizeof(now);
572 (void)(db->put)(db, &key, &data, 0);
573 }
574
575 /*
576 * sendmessage --
577 * exec sendmail to send the vacation file to sender
578 */
579 static void
580 sendmessage(const char *myname)
581 {
582 FILE *mfp, *sfp;
583 int i;
584 int pvect[2];
585 char buf[MAXLINE];
586
587 mfp = fopen(msgfile, "r");
588 if (mfp == NULL) {
589 syslog(LOG_ERR, "%s: no `%s' file for `%s'.", getprogname(),
590 myname, msgfile);
591 exit(1);
592 }
593
594 if (debug) {
595 sfp = stdout;
596 } else {
597 if (pipe(pvect) < 0) {
598 syslog(LOG_ERR, "%s: pipe: %m", getprogname());
599 exit(1);
600 }
601 i = vfork();
602 if (i < 0) {
603 syslog(LOG_ERR, "%s: fork: %m", getprogname());
604 exit(1);
605 }
606 if (i == 0) {
607 (void)dup2(pvect[0], 0);
608 (void)close(pvect[0]);
609 (void)close(pvect[1]);
610 (void)close(fileno(mfp));
611 (void)execl(_PATH_SENDMAIL, "sendmail", "-f", myname,
612 "--", from, NULL);
613 syslog(LOG_ERR, "%s: can't exec %s: %m",
614 getprogname(), _PATH_SENDMAIL);
615 _exit(1);
616 }
617 (void)close(pvect[0]);
618 sfp = fdopen(pvect[1], "w");
619 if (sfp == NULL) {
620 syslog(LOG_ERR, "%s: can't fdopen %d: %m",
621 getprogname(), pvect[1]);
622 _exit(1);
623 }
624 }
625 (void)fprintf(sfp, "To: %s\n", from);
626 while (fgets(buf, sizeof buf, mfp) != NULL) {
627 char *p;
628 if ((p = strstr(buf, "$SUBJECT")) != NULL) {
629 *p = '\0';
630 (void)fputs(buf, sfp);
631 (void)fputs(subject, sfp);
632 p += sizeof("$SUBJECT") - 1;
633 (void)fputs(p, sfp);
634 } else
635 (void)fputs(buf, sfp);
636 }
637 (void)fclose(mfp);
638 if (sfp != stdout)
639 (void)fclose(sfp);
640 }
641
642 static void
643 usage(void)
644 {
645
646 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]"
647 " login", getuid(), getprogname());
648 exit(1);
649 }
650