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