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