Annotation of src/usr.bin/vacation/vacation.c, Revision 1.30
1.30 ! gilles 1: /* $OpenBSD: vacation.c,v 1.29 2007/03/21 03:31:19 tedu 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.30 ! gilles 43: static char rcsid[] = "$OpenBSD: vacation.c,v 1.29 2007/03/21 03:31:19 tedu 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;
1.29 tedu 214: for (p = buf + 15; isspace(*p); ++p)
1.25 deraadt 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')) {
1.29 tedu 224: for (p += 2; isspace(*p); ++p)
1.27 millert 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.30 ! gilles 238: from[strcspn(from, "\n")] = '\0';
1.1 deraadt 239: if (junkmail())
240: exit(0);
241: }
1.28 deraadt 242: break;
243: case 'L': /* "List-Id:" */
244: case 'l':
245: cont = 0;
246: /*
247: * If present (with any value), message is coming from a
248: * mailing list, cf. RFC2919.
249: */
250: if (strncasecmp(buf, "List-Id:", 8) == 0)
251: exit(0);
1.1 deraadt 252: break;
1.11 marc 253: case 'R': /* "Return-Path:" */
1.16 mpech 254: case 'r':
1.11 marc 255: cont = 0;
256: if (strncasecmp(buf, "Return-Path:",
1.22 deraadt 257: sizeof("Return-Path:")-1) ||
1.13 pjanzen 258: (buf[12] != ' ' && buf[12] != '\t'))
1.11 marc 259: break;
1.29 tedu 260: for (p = buf + 12; isspace(*p); ++p)
1.12 marc 261: ;
1.21 millert 262: if (strlcpy(from, p, sizeof(from)) >= sizeof(from)) {
1.11 marc 263: syslog(LOG_NOTICE,
1.22 deraadt 264: "Return-Path %s exceeds limits", p);
1.11 marc 265: exit(1);
266: }
1.30 ! gilles 267: from[strcspn(from, "\n")] = '\0';
1.11 marc 268: if (junkmail())
269: exit(0);
270: break;
1.1 deraadt 271: case 'P': /* "Precedence:" */
1.16 mpech 272: case 'p':
1.1 deraadt 273: cont = 0;
1.25 deraadt 274: if (strncasecmp(buf, "Precedence:", 11))
1.1 deraadt 275: break;
1.29 tedu 276: for (p = buf + 11; isspace(*p); ++p)
1.22 deraadt 277: ;
1.1 deraadt 278: if (!strncasecmp(p, "junk", 4) ||
279: !strncasecmp(p, "bulk", 4) ||
280: !strncasecmp(p, "list", 4))
281: exit(0);
282: break;
1.12 marc 283: case 'S': /* Subject: */
1.16 mpech 284: case 's':
1.12 marc 285: cont = 0;
286: if (strncasecmp(buf, "Subject:",
1.22 deraadt 287: sizeof("Subject:")-1) ||
1.13 pjanzen 288: (buf[8] != ' ' && buf[8] != '\t'))
1.12 marc 289: break;
1.29 tedu 290: for (p = buf + 8; isspace(*p); ++p)
1.12 marc 291: ;
1.21 millert 292: if (strlcpy(subj, p, sizeof(subj)) >= sizeof(subj)) {
1.12 marc 293: syslog(LOG_NOTICE,
1.22 deraadt 294: "Subject %s exceeds limits", p);
1.12 marc 295: exit(1);
296: }
1.30 ! gilles 297: subj[strcspn(subj, "\n")] = '\0';
1.12 marc 298: break;
1.1 deraadt 299: case 'C': /* "Cc:" */
1.16 mpech 300: case 'c':
301: if (strncasecmp(buf, "Cc:", 3))
1.1 deraadt 302: break;
303: cont = 1;
304: goto findme;
305: case 'T': /* "To:" */
1.16 mpech 306: case 't':
307: if (strncasecmp(buf, "To:", 3))
1.1 deraadt 308: break;
309: cont = 1;
310: goto findme;
311: default:
312: if (!isspace(*buf) || !cont || tome) {
313: cont = 0;
314: break;
315: }
316: findme: for (cur = names; !tome && cur; cur = cur->next)
317: tome += nsearch(cur->name, buf);
318: }
319: if (!tome)
320: exit(0);
321: if (!*from) {
1.11 marc 322: syslog(LOG_NOTICE, "no initial \"From\" or \"Return-Path\"line.");
1.1 deraadt 323: exit(1);
324: }
325: }
326:
327: /*
328: * nsearch --
329: * do a nice, slow, search of a string for a substring.
330: */
331: int
1.20 deraadt 332: nsearch(char *name, char *str)
1.1 deraadt 333: {
1.15 mpech 334: int len;
1.1 deraadt 335:
336: for (len = strlen(name); *str; ++str)
1.13 pjanzen 337: if (!strncasecmp(name, str, len))
1.1 deraadt 338: return(1);
339: return(0);
340: }
341:
342: /*
343: * junkmail --
344: * read the header and return if automagic/junk/bulk/list mail
345: */
346: int
1.20 deraadt 347: junkmail(void)
1.1 deraadt 348: {
349: static struct ignore {
350: char *name;
351: int len;
352: } ignore[] = {
1.13 pjanzen 353: { "-request", 8 },
354: { "postmaster", 10 },
355: { "uucp", 4 },
356: { "mailer-daemon", 13 },
357: { "mailer", 6 },
358: { "-relay", 6 },
359: { NULL, 0 }
1.1 deraadt 360: };
1.15 mpech 361: struct ignore *cur;
362: int len;
363: char *p;
1.1 deraadt 364:
365: /*
366: * This is mildly amusing, and I'm not positive it's right; trying
367: * to find the "real" name of the sender, assuming that addresses
368: * will be some variant of:
369: *
370: * From site!site!SENDER%site.domain%site.domain@site.domain
371: */
1.22 deraadt 372: if (!(p = strchr(from, '%'))) {
1.5 millert 373: if (!(p = strchr(from, '@'))) {
1.13 pjanzen 374: if ((p = strrchr(from, '!')))
1.1 deraadt 375: ++p;
376: else
377: p = from;
1.12 marc 378: for (; *p; ++p)
379: ;
1.1 deraadt 380: }
1.22 deraadt 381: }
1.1 deraadt 382: len = p - from;
383: for (cur = ignore; cur->name; ++cur)
384: if (len >= cur->len &&
385: !strncasecmp(cur->name, p - cur->len, cur->len))
386: return(1);
387: return(0);
388: }
389:
390: #define VIT "__VACATION__INTERVAL__TIMER__"
391:
392: /*
393: * recent --
394: * find out if user has gotten a vacation message recently.
395: * use bcopy for machines with alignment restrictions
396: */
397: int
1.20 deraadt 398: recent(void)
1.1 deraadt 399: {
1.22 deraadt 400: time_t then, next;
1.1 deraadt 401: DBT key, data;
402:
403: /* get interval time */
404: key.data = VIT;
405: key.size = sizeof(VIT);
406: if ((db->get)(db, &key, &data, 0))
407: next = SECSPERDAY * DAYSPERWEEK;
408: else
409: bcopy(data.data, &next, sizeof(next));
410:
411: /* get record for this address */
412: key.data = from;
413: key.size = strlen(from);
414: if (!(db->get)(db, &key, &data, 0)) {
415: bcopy(data.data, &then, sizeof(then));
416: if (next == (time_t)LONG_MAX || /* XXX */
417: then + next > time(NULL))
418: return(1);
419: }
420: return(0);
421: }
422:
423: /*
424: * setinterval --
425: * store the reply interval
426: */
427: void
1.20 deraadt 428: setinterval(time_t interval)
1.1 deraadt 429: {
430: DBT key, data;
431:
432: key.data = VIT;
433: key.size = sizeof(VIT);
434: data.data = &interval;
435: data.size = sizeof(interval);
436: (void)(db->put)(db, &key, &data, 0);
437: }
438:
439: /*
440: * setreply --
441: * store that this user knows about the vacation.
442: */
443: void
1.20 deraadt 444: setreply(void)
1.1 deraadt 445: {
446: DBT key, data;
447: time_t now;
448:
449: key.data = from;
450: key.size = strlen(from);
451: (void)time(&now);
452: data.data = &now;
453: data.size = sizeof(now);
454: (void)(db->put)(db, &key, &data, 0);
455: }
456:
457: /*
458: * sendmessage --
459: * exec sendmail to send the vacation file to sender
460: */
461: void
1.20 deraadt 462: sendmessage(char *myname)
1.1 deraadt 463: {
1.22 deraadt 464: char buf[MAXLINE];
1.1 deraadt 465: FILE *mfp, *sfp;
1.22 deraadt 466: int pvect[2], i;
1.1 deraadt 467:
468: mfp = fopen(VMSG, "r");
469: if (mfp == NULL) {
1.11 marc 470: syslog(LOG_NOTICE, "no ~%s/%s file.", myname, VMSG);
1.1 deraadt 471: exit(1);
472: }
473: if (pipe(pvect) < 0) {
1.11 marc 474: syslog(LOG_ERR, "pipe: %m");
1.1 deraadt 475: exit(1);
476: }
477: i = vfork();
478: if (i < 0) {
1.11 marc 479: syslog(LOG_ERR, "fork: %m");
1.1 deraadt 480: exit(1);
481: }
482: if (i == 0) {
483: dup2(pvect[0], 0);
484: close(pvect[0]);
485: close(pvect[1]);
1.10 deraadt 486: close(fileno(mfp));
1.8 deraadt 487: execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--",
1.14 deraadt 488: from, (char *)NULL);
1.11 marc 489: syslog(LOG_ERR, "can't exec %s: %m", _PATH_SENDMAIL);
1.3 deraadt 490: _exit(1);
1.1 deraadt 491: }
492: close(pvect[0]);
493: sfp = fdopen(pvect[1], "w");
1.23 deraadt 494: if (sfp == NULL) {
495: /* XXX could not fdopen; likely out of memory */
496: fclose(mfp);
497: close(pvect[1]);
498: return;
499: }
1.1 deraadt 500: fprintf(sfp, "To: %s\n", from);
1.24 millert 501: fputs("Auto-Submitted: auto-replied\n", sfp);
1.12 marc 502: while (fgets(buf, sizeof buf, mfp)) {
503: char *s = strstr(buf, "$SUBJECT");
1.22 deraadt 504:
505: if (s) {
1.12 marc 506: *s = 0;
507: fputs(buf, sfp);
508: fputs(subj, sfp);
509: fputs(s+8, sfp);
510: } else {
511: fputs(buf, sfp);
512: }
513: }
1.1 deraadt 514: fclose(mfp);
515: fclose(sfp);
516: }
517:
518: void
1.20 deraadt 519: usage(void)
1.1 deraadt 520: {
1.9 millert 521: syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login",
1.1 deraadt 522: getuid());
523: exit(1);
524: }