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