Annotation of src/usr.bin/vacation/vacation.c, Revision 1.19
1.19 ! millert 1: /* $OpenBSD: vacation.c,v 1.18 2003/03/13 09:09:36 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.19 ! millert 43: static char rcsid[] = "$OpenBSD: vacation.c,v 1.18 2003/03/13 09:09:36 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
101: main(argc, argv)
102: int argc;
103: char **argv;
104: {
105: struct passwd *pw;
1.9 millert 106: struct stat sb;
1.1 deraadt 107: ALIAS *cur;
108: time_t interval;
1.9 millert 109: int ch, iflag, flags;
1.1 deraadt 110:
111: opterr = iflag = 0;
112: interval = -1;
1.4 millert 113: while ((ch = getopt(argc, argv, "a:Iir:")) != -1)
1.1 deraadt 114: switch((char)ch) {
115: case 'a': /* alias */
116: if (!(cur = (ALIAS *)malloc((u_int)sizeof(ALIAS))))
117: break;
118: cur->name = optarg;
119: cur->next = names;
120: names = cur;
121: break;
122: case 'I': /* backward compatible */
123: case 'i': /* init the database */
124: iflag = 1;
125: break;
126: case 'r':
127: if (isdigit(*optarg)) {
128: interval = atol(optarg) * SECSPERDAY;
129: if (interval < 0)
130: usage();
131: }
132: else
133: interval = (time_t)LONG_MAX; /* XXX */
134: break;
135: case '?':
136: default:
137: usage();
138: }
139: argc -= optind;
140: argv += optind;
141:
142: if (argc != 1) {
143: if (!iflag)
144: usage();
145: if (!(pw = getpwuid(getuid()))) {
146: syslog(LOG_ERR,
1.11 marc 147: "no such user uid %u.", getuid());
1.1 deraadt 148: exit(1);
149: }
150: }
151: else if (!(pw = getpwnam(*argv))) {
1.11 marc 152: syslog(LOG_ERR, "no such user %s.", *argv);
1.1 deraadt 153: exit(1);
154: }
155: if (chdir(pw->pw_dir)) {
156: syslog(LOG_NOTICE,
1.11 marc 157: "no such directory %s.", pw->pw_dir);
1.1 deraadt 158: exit(1);
159: }
160:
1.9 millert 161: /*
162: * dbopen(3) can not deal with a zero-length file w/o O_TRUNC.
163: */
164: if (iflag == 1 || (stat(VDB, &sb) == 0 && sb.st_size == (off_t)0))
165: flags = O_CREAT|O_RDWR|O_TRUNC;
166: else
167: flags = O_CREAT|O_RDWR;
168:
169: db = dbopen(VDB, flags, S_IRUSR|S_IWUSR, DB_HASH, NULL);
1.1 deraadt 170: if (!db) {
1.11 marc 171: syslog(LOG_NOTICE, "%s: %m", VDB);
1.1 deraadt 172: exit(1);
173: }
174:
175: if (interval != -1)
176: setinterval(interval);
177:
178: if (iflag) {
179: (void)(db->close)(db);
180: exit(0);
181: }
182:
183: if (!(cur = malloc((u_int)sizeof(ALIAS))))
184: exit(1);
185: cur->name = pw->pw_name;
186: cur->next = names;
187: names = cur;
188:
189: readheaders();
190: if (!recent()) {
191: setreply();
192: (void)(db->close)(db);
193: sendmessage(pw->pw_name);
194: }
195: else
196: (void)(db->close)(db);
197: exit(0);
198: /* NOTREACHED */
199: }
200:
201: /*
202: * readheaders --
203: * read mail headers
204: */
205: void
206: readheaders()
207: {
1.15 mpech 208: ALIAS *cur;
209: char *p;
1.1 deraadt 210: int tome, cont;
211: char buf[MAXLINE];
212:
213: cont = tome = 0;
214: while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
215: switch(*buf) {
216: case 'F': /* "From " */
1.16 mpech 217: case 'f':
1.1 deraadt 218: cont = 0;
1.16 mpech 219: if (!strncasecmp(buf, "From ", 5)) {
1.12 marc 220: for (p = buf + 5; *p && *p != ' '; ++p)
221: ;
1.1 deraadt 222: *p = '\0';
1.18 deraadt 223: (void)strlcpy(from, buf + 5, sizeof from);
1.13 pjanzen 224: if ((p = strchr(from, '\n')))
1.1 deraadt 225: *p = '\0';
226: if (junkmail())
227: exit(0);
228: }
229: break;
1.11 marc 230: case 'R': /* "Return-Path:" */
1.16 mpech 231: case 'r':
1.11 marc 232: cont = 0;
233: if (strncasecmp(buf, "Return-Path:",
234: sizeof("Return-Path:")-1) ||
1.13 pjanzen 235: (buf[12] != ' ' && buf[12] != '\t'))
1.11 marc 236: break;
1.12 marc 237: for (p = buf + 12; *p && isspace(*p); ++p)
238: ;
1.11 marc 239: if (strlcpy(from, p, sizeof from ) > sizeof from) {
240: syslog(LOG_NOTICE,
241: "Return-Path %s exceeds limits", p);
242: exit(1);
243: }
1.13 pjanzen 244: if ((p = strchr(from, '\n')))
1.11 marc 245: *p = '\0';
246: if (junkmail())
247: exit(0);
248: break;
1.1 deraadt 249: case 'P': /* "Precedence:" */
1.16 mpech 250: case 'p':
1.1 deraadt 251: cont = 0;
252: if (strncasecmp(buf, "Precedence", 10) ||
1.13 pjanzen 253: (buf[10] != ':' && buf[10] != ' ' &&
254: buf[10] != '\t'))
1.1 deraadt 255: break;
1.5 millert 256: if (!(p = strchr(buf, ':')))
1.1 deraadt 257: break;
258: while (*++p && isspace(*p));
259: if (!*p)
260: break;
261: if (!strncasecmp(p, "junk", 4) ||
262: !strncasecmp(p, "bulk", 4) ||
263: !strncasecmp(p, "list", 4))
264: exit(0);
265: break;
1.12 marc 266: case 'S': /* Subject: */
1.16 mpech 267: case 's':
1.12 marc 268: cont = 0;
269: if (strncasecmp(buf, "Subject:",
270: sizeof("Subject:")-1) ||
1.13 pjanzen 271: (buf[8] != ' ' && buf[8] != '\t'))
1.12 marc 272: break;
273: for (p = buf + 8; *p && isspace(*p); ++p)
274: ;
275: if (strlcpy(subj, p, sizeof subj ) > sizeof subj) {
276: syslog(LOG_NOTICE,
277: "Subject %s exceeds limits", p);
278: exit(1);
279: }
1.13 pjanzen 280: if ((p = strchr(subj, '\n')))
1.12 marc 281: *p = '\0';
282: break;
1.1 deraadt 283: case 'C': /* "Cc:" */
1.16 mpech 284: case 'c':
285: if (strncasecmp(buf, "Cc:", 3))
1.1 deraadt 286: break;
287: cont = 1;
288: goto findme;
289: case 'T': /* "To:" */
1.16 mpech 290: case 't':
291: if (strncasecmp(buf, "To:", 3))
1.1 deraadt 292: break;
293: cont = 1;
294: goto findme;
295: default:
296: if (!isspace(*buf) || !cont || tome) {
297: cont = 0;
298: break;
299: }
300: findme: for (cur = names; !tome && cur; cur = cur->next)
301: tome += nsearch(cur->name, buf);
302: }
303: if (!tome)
304: exit(0);
305: if (!*from) {
1.11 marc 306: syslog(LOG_NOTICE, "no initial \"From\" or \"Return-Path\"line.");
1.1 deraadt 307: exit(1);
308: }
309: }
310:
311: /*
312: * nsearch --
313: * do a nice, slow, search of a string for a substring.
314: */
315: int
316: nsearch(name, str)
1.15 mpech 317: char *name, *str;
1.1 deraadt 318: {
1.15 mpech 319: int len;
1.1 deraadt 320:
321: for (len = strlen(name); *str; ++str)
1.13 pjanzen 322: if (!strncasecmp(name, str, len))
1.1 deraadt 323: return(1);
324: return(0);
325: }
326:
327: /*
328: * junkmail --
329: * read the header and return if automagic/junk/bulk/list mail
330: */
331: int
332: junkmail()
333: {
334: static struct ignore {
335: char *name;
336: int len;
337: } ignore[] = {
1.13 pjanzen 338: { "-request", 8 },
339: { "postmaster", 10 },
340: { "uucp", 4 },
341: { "mailer-daemon", 13 },
342: { "mailer", 6 },
343: { "-relay", 6 },
344: { NULL, 0 }
1.1 deraadt 345: };
1.15 mpech 346: struct ignore *cur;
347: int len;
348: char *p;
1.1 deraadt 349:
350: /*
351: * This is mildly amusing, and I'm not positive it's right; trying
352: * to find the "real" name of the sender, assuming that addresses
353: * will be some variant of:
354: *
355: * From site!site!SENDER%site.domain%site.domain@site.domain
356: */
1.5 millert 357: if (!(p = strchr(from, '%')))
358: if (!(p = strchr(from, '@'))) {
1.13 pjanzen 359: if ((p = strrchr(from, '!')))
1.1 deraadt 360: ++p;
361: else
362: p = from;
1.12 marc 363: for (; *p; ++p)
364: ;
1.1 deraadt 365: }
366: len = p - from;
367: for (cur = ignore; cur->name; ++cur)
368: if (len >= cur->len &&
369: !strncasecmp(cur->name, p - cur->len, cur->len))
370: return(1);
371: return(0);
372: }
373:
374: #define VIT "__VACATION__INTERVAL__TIMER__"
375:
376: /*
377: * recent --
378: * find out if user has gotten a vacation message recently.
379: * use bcopy for machines with alignment restrictions
380: */
381: int
382: recent()
383: {
384: DBT key, data;
385: time_t then, next;
386:
387: /* get interval time */
388: key.data = VIT;
389: key.size = sizeof(VIT);
390: if ((db->get)(db, &key, &data, 0))
391: next = SECSPERDAY * DAYSPERWEEK;
392: else
393: bcopy(data.data, &next, sizeof(next));
394:
395: /* get record for this address */
396: key.data = from;
397: key.size = strlen(from);
398: if (!(db->get)(db, &key, &data, 0)) {
399: bcopy(data.data, &then, sizeof(then));
400: if (next == (time_t)LONG_MAX || /* XXX */
401: then + next > time(NULL))
402: return(1);
403: }
404: return(0);
405: }
406:
407: /*
408: * setinterval --
409: * store the reply interval
410: */
411: void
412: setinterval(interval)
413: time_t interval;
414: {
415: DBT key, data;
416:
417: key.data = VIT;
418: key.size = sizeof(VIT);
419: data.data = &interval;
420: data.size = sizeof(interval);
421: (void)(db->put)(db, &key, &data, 0);
422: }
423:
424: /*
425: * setreply --
426: * store that this user knows about the vacation.
427: */
428: void
429: setreply()
430: {
431: DBT key, data;
432: time_t now;
433:
434: key.data = from;
435: key.size = strlen(from);
436: (void)time(&now);
437: data.data = &now;
438: data.size = sizeof(now);
439: (void)(db->put)(db, &key, &data, 0);
440: }
441:
442: /*
443: * sendmessage --
444: * exec sendmail to send the vacation file to sender
445: */
446: void
447: sendmessage(myname)
448: char *myname;
449: {
450: FILE *mfp, *sfp;
451: int i;
452: int pvect[2];
453: char buf[MAXLINE];
454:
455: mfp = fopen(VMSG, "r");
456: if (mfp == NULL) {
1.11 marc 457: syslog(LOG_NOTICE, "no ~%s/%s file.", myname, VMSG);
1.1 deraadt 458: exit(1);
459: }
460: if (pipe(pvect) < 0) {
1.11 marc 461: syslog(LOG_ERR, "pipe: %m");
1.1 deraadt 462: exit(1);
463: }
464: i = vfork();
465: if (i < 0) {
1.11 marc 466: syslog(LOG_ERR, "fork: %m");
1.1 deraadt 467: exit(1);
468: }
469: if (i == 0) {
470: dup2(pvect[0], 0);
471: close(pvect[0]);
472: close(pvect[1]);
1.10 deraadt 473: close(fileno(mfp));
1.8 deraadt 474: execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--",
1.14 deraadt 475: from, (char *)NULL);
1.11 marc 476: syslog(LOG_ERR, "can't exec %s: %m", _PATH_SENDMAIL);
1.3 deraadt 477: _exit(1);
1.1 deraadt 478: }
479: close(pvect[0]);
480: sfp = fdopen(pvect[1], "w");
481: fprintf(sfp, "To: %s\n", from);
1.12 marc 482: while (fgets(buf, sizeof buf, mfp)) {
483: char *s = strstr(buf, "$SUBJECT");
484: if ( s ) {
485: *s = 0;
486: fputs(buf, sfp);
487: fputs(subj, sfp);
488: fputs(s+8, sfp);
489: } else {
490: fputs(buf, sfp);
491: }
492: }
1.1 deraadt 493: fclose(mfp);
494: fclose(sfp);
495: }
496:
497: void
498: usage()
499: {
1.9 millert 500: syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login",
1.1 deraadt 501: getuid());
502: exit(1);
503: }