Annotation of src/usr.bin/vacation/vacation.c, Revision 1.28
1.28 ! deraadt 1: /* $OpenBSD: vacation.c,v 1.27 2007/02/23 15:55:17 millert Exp $ */
1.1 deraadt 2: /* $NetBSD: vacation.c,v 1.7 1995/04/29 05:58:27 cgd Exp $ */
3:
4: /*
5: * Copyright (c) 1983, 1987, 1993
6: * The Regents of the University of California. All rights reserved.
7: *
8: * Redistribution and use in source and binary forms, with or without
9: * modification, are permitted provided that the following conditions
10: * are met:
11: * 1. Redistributions of source code must retain the above copyright
12: * notice, this list of conditions and the following disclaimer.
13: * 2. Redistributions in binary form must reproduce the above copyright
14: * notice, this list of conditions and the following disclaimer in the
15: * documentation and/or other materials provided with the distribution.
1.19 millert 16: * 3. Neither the name of the University nor the names of its contributors
1.1 deraadt 17: * may be used to endorse or promote products derived from this software
18: * without specific prior written permission.
19: *
20: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30: * SUCH DAMAGE.
31: */
32:
33: #ifndef lint
34: static char copyright[] =
35: "@(#) 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
1.28 ! deraadt 43: static char rcsid[] = "$OpenBSD: vacation.c,v 1.27 2007/02/23 15:55:17 millert Exp $";
1.1 deraadt 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: #include <fcntl.h>
55: #include <pwd.h>
56: #include <db.h>
57: #include <time.h>
58: #include <syslog.h>
59: #include <tzfile.h>
60: #include <errno.h>
61: #include <unistd.h>
62: #include <stdio.h>
63: #include <ctype.h>
64: #include <stdlib.h>
65: #include <string.h>
66: #include <paths.h>
67:
68: /*
69: * VACATION -- return a message to the sender when on vacation.
70: *
71: * This program is invoked as a message receiver. It returns a
72: * message specified by the user to whomever sent the mail, taking
73: * care not to return a message too often to prevent "I am on
74: * vacation" loops.
75: */
76:
77: #define MAXLINE 1024 /* max line from mail header */
78: #define VDB ".vacation.db" /* dbm's database */
79: #define VMSG ".vacation.msg" /* vacation message */
80:
81: typedef struct alias {
82: struct alias *next;
83: char *name;
84: } ALIAS;
85: ALIAS *names;
86:
87: DB *db;
88: char from[MAXLINE];
1.12 marc 89: char subj[MAXLINE];
1.1 deraadt 90:
1.17 millert 91: int junkmail(void);
92: int nsearch(char *, char *);
93: void readheaders(void);
94: int recent(void);
95: void sendmessage(char *);
96: void setinterval(time_t);
97: void setreply(void);
98: void usage(void);
1.1 deraadt 99:
100: int
1.20 deraadt 101: main(int argc, char *argv[])
1.1 deraadt 102: {
1.22 deraadt 103: int ch, iflag, flags;
1.1 deraadt 104: struct passwd *pw;
1.22 deraadt 105: time_t interval;
1.9 millert 106: struct stat sb;
1.1 deraadt 107: ALIAS *cur;
108:
109: opterr = iflag = 0;
110: interval = -1;
1.4 millert 111: while ((ch = getopt(argc, argv, "a:Iir:")) != -1)
1.22 deraadt 112: switch ((char)ch) {
1.1 deraadt 113: case 'a': /* alias */
114: if (!(cur = (ALIAS *)malloc((u_int)sizeof(ALIAS))))
115: break;
116: cur->name = optarg;
117: cur->next = names;
118: names = cur;
119: break;
120: case 'I': /* backward compatible */
121: case 'i': /* init the database */
122: iflag = 1;
123: break;
124: case 'r':
125: if (isdigit(*optarg)) {
126: interval = atol(optarg) * SECSPERDAY;
127: if (interval < 0)
128: usage();
1.22 deraadt 129: } else
1.1 deraadt 130: interval = (time_t)LONG_MAX; /* XXX */
131: break;
132: default:
133: usage();
134: }
135: argc -= optind;
136: argv += optind;
137:
138: if (argc != 1) {
139: if (!iflag)
140: usage();
141: if (!(pw = getpwuid(getuid()))) {
142: syslog(LOG_ERR,
1.11 marc 143: "no such user uid %u.", getuid());
1.1 deraadt 144: exit(1);
145: }
1.22 deraadt 146: } else if (!(pw = getpwnam(*argv))) {
1.11 marc 147: syslog(LOG_ERR, "no such user %s.", *argv);
1.1 deraadt 148: exit(1);
149: }
150: if (chdir(pw->pw_dir)) {
151: syslog(LOG_NOTICE,
1.11 marc 152: "no such directory %s.", pw->pw_dir);
1.1 deraadt 153: exit(1);
154: }
155:
1.9 millert 156: /*
157: * dbopen(3) can not deal with a zero-length file w/o O_TRUNC.
158: */
159: if (iflag == 1 || (stat(VDB, &sb) == 0 && sb.st_size == (off_t)0))
160: flags = O_CREAT|O_RDWR|O_TRUNC;
161: else
162: flags = O_CREAT|O_RDWR;
1.22 deraadt 163:
1.9 millert 164: db = dbopen(VDB, flags, S_IRUSR|S_IWUSR, DB_HASH, NULL);
1.1 deraadt 165: if (!db) {
1.11 marc 166: syslog(LOG_NOTICE, "%s: %m", VDB);
1.1 deraadt 167: exit(1);
168: }
169:
170: if (interval != -1)
171: setinterval(interval);
172:
173: if (iflag) {
174: (void)(db->close)(db);
175: exit(0);
176: }
177:
178: if (!(cur = malloc((u_int)sizeof(ALIAS))))
179: exit(1);
180: cur->name = pw->pw_name;
181: cur->next = names;
182: names = cur;
183:
184: readheaders();
185: if (!recent()) {
186: setreply();
187: (void)(db->close)(db);
188: sendmessage(pw->pw_name);
1.22 deraadt 189: } else
1.1 deraadt 190: (void)(db->close)(db);
191: exit(0);
192: /* NOTREACHED */
193: }
194:
195: /*
196: * readheaders --
197: * read mail headers
198: */
199: void
1.20 deraadt 200: readheaders(void)
1.1 deraadt 201: {
1.22 deraadt 202: char buf[MAXLINE], *p;
203: int tome, cont;
1.15 mpech 204: ALIAS *cur;
1.1 deraadt 205:
206: cont = tome = 0;
207: while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
1.22 deraadt 208: switch (*buf) {
1.25 deraadt 209: case 'A': /* "Auto-Submitted:" */
210: case 'a':
211: cont = 0;
212: if (strncasecmp(buf, "Auto-Submitted:", 15))
213: break;
214: for (p = buf + 15; *p && isspace(*p); ++p)
215: ;
216: /*
217: * RFC 3834 section 2:
218: * Automatic responses SHOULD NOT be issued in response
219: * to any message which contains an Auto-Submitted
220: * header where the field has any value other than "no".
221: */
1.27 millert 222: if ((p[0] == 'n' || p[0] == 'N') &&
223: (p[1] == 'o' || p[1] == 'O')) {
224: for (p += 2; *p && isspace(*p); ++p)
225: ;
226: if (*p == '\0')
227: break; /* Auto-Submitted: no */
228: }
229: exit(0);
1.1 deraadt 230: case 'F': /* "From " */
1.16 mpech 231: case 'f':
1.1 deraadt 232: cont = 0;
1.16 mpech 233: if (!strncasecmp(buf, "From ", 5)) {
1.12 marc 234: for (p = buf + 5; *p && *p != ' '; ++p)
235: ;
1.1 deraadt 236: *p = '\0';
1.21 millert 237: (void)strlcpy(from, buf + 5, sizeof(from));
1.13 pjanzen 238: if ((p = strchr(from, '\n')))
1.1 deraadt 239: *p = '\0';
240: if (junkmail())
241: exit(0);
242: }
1.28 ! deraadt 243: break;
! 244: case 'L': /* "List-Id:" */
! 245: case 'l':
! 246: cont = 0;
! 247: /*
! 248: * If present (with any value), message is coming from a
! 249: * mailing list, cf. RFC2919.
! 250: */
! 251: if (strncasecmp(buf, "List-Id:", 8) == 0)
! 252: exit(0);
1.1 deraadt 253: break;
1.11 marc 254: case 'R': /* "Return-Path:" */
1.16 mpech 255: case 'r':
1.11 marc 256: cont = 0;
257: if (strncasecmp(buf, "Return-Path:",
1.22 deraadt 258: sizeof("Return-Path:")-1) ||
1.13 pjanzen 259: (buf[12] != ' ' && buf[12] != '\t'))
1.11 marc 260: break;
1.12 marc 261: for (p = buf + 12; *p && isspace(*p); ++p)
262: ;
1.21 millert 263: if (strlcpy(from, p, sizeof(from)) >= sizeof(from)) {
1.11 marc 264: syslog(LOG_NOTICE,
1.22 deraadt 265: "Return-Path %s exceeds limits", p);
1.11 marc 266: exit(1);
267: }
1.13 pjanzen 268: if ((p = strchr(from, '\n')))
1.11 marc 269: *p = '\0';
270: if (junkmail())
271: exit(0);
272: break;
1.1 deraadt 273: case 'P': /* "Precedence:" */
1.16 mpech 274: case 'p':
1.1 deraadt 275: cont = 0;
1.25 deraadt 276: if (strncasecmp(buf, "Precedence:", 11))
1.1 deraadt 277: break;
1.25 deraadt 278: for (p = buf + 11; *p && isspace(*p); ++p)
1.22 deraadt 279: ;
1.1 deraadt 280: if (!strncasecmp(p, "junk", 4) ||
281: !strncasecmp(p, "bulk", 4) ||
282: !strncasecmp(p, "list", 4))
283: exit(0);
284: break;
1.12 marc 285: case 'S': /* Subject: */
1.16 mpech 286: case 's':
1.12 marc 287: cont = 0;
288: if (strncasecmp(buf, "Subject:",
1.22 deraadt 289: sizeof("Subject:")-1) ||
1.13 pjanzen 290: (buf[8] != ' ' && buf[8] != '\t'))
1.12 marc 291: break;
292: for (p = buf + 8; *p && isspace(*p); ++p)
293: ;
1.21 millert 294: if (strlcpy(subj, p, sizeof(subj)) >= sizeof(subj)) {
1.12 marc 295: syslog(LOG_NOTICE,
1.22 deraadt 296: "Subject %s exceeds limits", p);
1.12 marc 297: exit(1);
298: }
1.13 pjanzen 299: if ((p = strchr(subj, '\n')))
1.12 marc 300: *p = '\0';
301: break;
1.1 deraadt 302: case 'C': /* "Cc:" */
1.16 mpech 303: case 'c':
304: if (strncasecmp(buf, "Cc:", 3))
1.1 deraadt 305: break;
306: cont = 1;
307: goto findme;
308: case 'T': /* "To:" */
1.16 mpech 309: case 't':
310: if (strncasecmp(buf, "To:", 3))
1.1 deraadt 311: break;
312: cont = 1;
313: goto findme;
314: default:
315: if (!isspace(*buf) || !cont || tome) {
316: cont = 0;
317: break;
318: }
319: findme: for (cur = names; !tome && cur; cur = cur->next)
320: tome += nsearch(cur->name, buf);
321: }
322: if (!tome)
323: exit(0);
324: if (!*from) {
1.11 marc 325: syslog(LOG_NOTICE, "no initial \"From\" or \"Return-Path\"line.");
1.1 deraadt 326: exit(1);
327: }
328: }
329:
330: /*
331: * nsearch --
332: * do a nice, slow, search of a string for a substring.
333: */
334: int
1.20 deraadt 335: nsearch(char *name, char *str)
1.1 deraadt 336: {
1.15 mpech 337: int len;
1.1 deraadt 338:
339: for (len = strlen(name); *str; ++str)
1.13 pjanzen 340: if (!strncasecmp(name, str, len))
1.1 deraadt 341: return(1);
342: return(0);
343: }
344:
345: /*
346: * junkmail --
347: * read the header and return if automagic/junk/bulk/list mail
348: */
349: int
1.20 deraadt 350: junkmail(void)
1.1 deraadt 351: {
352: static struct ignore {
353: char *name;
354: int len;
355: } ignore[] = {
1.13 pjanzen 356: { "-request", 8 },
357: { "postmaster", 10 },
358: { "uucp", 4 },
359: { "mailer-daemon", 13 },
360: { "mailer", 6 },
361: { "-relay", 6 },
362: { NULL, 0 }
1.1 deraadt 363: };
1.15 mpech 364: struct ignore *cur;
365: int len;
366: char *p;
1.1 deraadt 367:
368: /*
369: * This is mildly amusing, and I'm not positive it's right; trying
370: * to find the "real" name of the sender, assuming that addresses
371: * will be some variant of:
372: *
373: * From site!site!SENDER%site.domain%site.domain@site.domain
374: */
1.22 deraadt 375: if (!(p = strchr(from, '%'))) {
1.5 millert 376: if (!(p = strchr(from, '@'))) {
1.13 pjanzen 377: if ((p = strrchr(from, '!')))
1.1 deraadt 378: ++p;
379: else
380: p = from;
1.12 marc 381: for (; *p; ++p)
382: ;
1.1 deraadt 383: }
1.22 deraadt 384: }
1.1 deraadt 385: len = p - from;
386: for (cur = ignore; cur->name; ++cur)
387: if (len >= cur->len &&
388: !strncasecmp(cur->name, p - cur->len, cur->len))
389: return(1);
390: return(0);
391: }
392:
393: #define VIT "__VACATION__INTERVAL__TIMER__"
394:
395: /*
396: * recent --
397: * find out if user has gotten a vacation message recently.
398: * use bcopy for machines with alignment restrictions
399: */
400: int
1.20 deraadt 401: recent(void)
1.1 deraadt 402: {
1.22 deraadt 403: time_t then, next;
1.1 deraadt 404: DBT key, data;
405:
406: /* get interval time */
407: key.data = VIT;
408: key.size = sizeof(VIT);
409: if ((db->get)(db, &key, &data, 0))
410: next = SECSPERDAY * DAYSPERWEEK;
411: else
412: bcopy(data.data, &next, sizeof(next));
413:
414: /* get record for this address */
415: key.data = from;
416: key.size = strlen(from);
417: if (!(db->get)(db, &key, &data, 0)) {
418: bcopy(data.data, &then, sizeof(then));
419: if (next == (time_t)LONG_MAX || /* XXX */
420: then + next > time(NULL))
421: return(1);
422: }
423: return(0);
424: }
425:
426: /*
427: * setinterval --
428: * store the reply interval
429: */
430: void
1.20 deraadt 431: setinterval(time_t interval)
1.1 deraadt 432: {
433: DBT key, data;
434:
435: key.data = VIT;
436: key.size = sizeof(VIT);
437: data.data = &interval;
438: data.size = sizeof(interval);
439: (void)(db->put)(db, &key, &data, 0);
440: }
441:
442: /*
443: * setreply --
444: * store that this user knows about the vacation.
445: */
446: void
1.20 deraadt 447: setreply(void)
1.1 deraadt 448: {
449: DBT key, data;
450: time_t now;
451:
452: key.data = from;
453: key.size = strlen(from);
454: (void)time(&now);
455: data.data = &now;
456: data.size = sizeof(now);
457: (void)(db->put)(db, &key, &data, 0);
458: }
459:
460: /*
461: * sendmessage --
462: * exec sendmail to send the vacation file to sender
463: */
464: void
1.20 deraadt 465: sendmessage(char *myname)
1.1 deraadt 466: {
1.22 deraadt 467: char buf[MAXLINE];
1.1 deraadt 468: FILE *mfp, *sfp;
1.22 deraadt 469: int pvect[2], i;
1.1 deraadt 470:
471: mfp = fopen(VMSG, "r");
472: if (mfp == NULL) {
1.11 marc 473: syslog(LOG_NOTICE, "no ~%s/%s file.", myname, VMSG);
1.1 deraadt 474: exit(1);
475: }
476: if (pipe(pvect) < 0) {
1.11 marc 477: syslog(LOG_ERR, "pipe: %m");
1.1 deraadt 478: exit(1);
479: }
480: i = vfork();
481: if (i < 0) {
1.11 marc 482: syslog(LOG_ERR, "fork: %m");
1.1 deraadt 483: exit(1);
484: }
485: if (i == 0) {
486: dup2(pvect[0], 0);
487: close(pvect[0]);
488: close(pvect[1]);
1.10 deraadt 489: close(fileno(mfp));
1.8 deraadt 490: execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--",
1.14 deraadt 491: from, (char *)NULL);
1.11 marc 492: syslog(LOG_ERR, "can't exec %s: %m", _PATH_SENDMAIL);
1.3 deraadt 493: _exit(1);
1.1 deraadt 494: }
495: close(pvect[0]);
496: sfp = fdopen(pvect[1], "w");
1.23 deraadt 497: if (sfp == NULL) {
498: /* XXX could not fdopen; likely out of memory */
499: fclose(mfp);
500: close(pvect[1]);
501: return;
502: }
1.1 deraadt 503: fprintf(sfp, "To: %s\n", from);
1.24 millert 504: fputs("Auto-Submitted: auto-replied\n", sfp);
1.12 marc 505: while (fgets(buf, sizeof buf, mfp)) {
506: char *s = strstr(buf, "$SUBJECT");
1.22 deraadt 507:
508: if (s) {
1.12 marc 509: *s = 0;
510: fputs(buf, sfp);
511: fputs(subj, sfp);
512: fputs(s+8, sfp);
513: } else {
514: fputs(buf, sfp);
515: }
516: }
1.1 deraadt 517: fclose(mfp);
518: fclose(sfp);
519: }
520:
521: void
1.20 deraadt 522: usage(void)
1.1 deraadt 523: {
1.9 millert 524: syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login",
1.1 deraadt 525: getuid());
526: exit(1);
527: }