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