vacation.c revision 1.17 1 /* $NetBSD: vacation.c,v 1.17 2000/07/21 01:21:31 mjl 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.17 2000/07/21 01:21:31 mjl 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 <errno.h>
62 #include <fcntl.h>
63 #include <paths.h>
64 #include <pwd.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <syslog.h>
69 #include <time.h>
70 #include <tzfile.h>
71 #include <unistd.h>
72
73 /*
74 * VACATION -- return a message to the sender when on vacation.
75 *
76 * This program is invoked as a message receiver. It returns a
77 * message specified by the user to whomever sent the mail, taking
78 * care not to return a message too often to prevent "I am on
79 * vacation" loops.
80 */
81
82 #define MAXLINE 1024 /* max line from mail header */
83 #define VDB ".vacation.db" /* dbm's database */
84 #define VMSG ".vacation.msg" /* vacation message */
85
86 typedef struct alias {
87 struct alias *next;
88 const char *name;
89 } alias_t;
90 alias_t *names;
91
92 DB *db;
93 char from[MAXLINE];
94
95 int main(int, char **);
96 int junkmail(void);
97 int nsearch(const char *, const char *);
98 void readheaders(void);
99 int recent(void);
100 void sendmessage(const char *);
101 void setinterval(time_t);
102 void setreply(void);
103 void usage(void);
104
105 int
106 main(int argc, char **argv)
107 {
108 struct passwd *pw;
109 alias_t *cur;
110 time_t interval;
111 int ch, iflag;
112
113 opterr = iflag = 0;
114 interval = -1;
115 while ((ch = getopt(argc, argv, "a:Iir:")) != -1)
116 switch((char)ch) {
117 case 'a': /* alias */
118 if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t))))
119 break;
120 cur->name = optarg;
121 cur->next = names;
122 names = cur;
123 break;
124 case 'I': /* backward compatible */
125 case 'i': /* init the database */
126 iflag = 1;
127 break;
128 case 'r':
129 if (isdigit((unsigned char)*optarg)) {
130 interval = atol(optarg) * SECSPERDAY;
131 if (interval < 0)
132 usage();
133 }
134 else
135 interval = (time_t)LONG_MAX; /* XXX */
136 break;
137 case '?':
138 default:
139 usage();
140 }
141 argc -= optind;
142 argv += optind;
143
144 if (argc != 1) {
145 if (!iflag)
146 usage();
147 if (!(pw = getpwuid(getuid()))) {
148 syslog(LOG_ERR,
149 "vacation: no such user uid %u.", getuid());
150 exit(1);
151 }
152 }
153 else if (!(pw = getpwnam(*argv))) {
154 syslog(LOG_ERR, "vacation: no such user %s.", *argv);
155 exit(1);
156 }
157 if (chdir(pw->pw_dir)) {
158 syslog(LOG_NOTICE,
159 "vacation: no such directory %s.", pw->pw_dir);
160 exit(1);
161 }
162
163 db = dbopen(VDB, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
164 S_IRUSR|S_IWUSR, DB_HASH, NULL);
165 if (!db) {
166 syslog(LOG_NOTICE, "vacation: %s: %m", VDB);
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((size_t)sizeof(alias_t))))
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);
189 }
190 else
191 (void)(db->close)(db);
192 exit(0);
193 /* NOTREACHED */
194 }
195
196 /*
197 * readheaders --
198 * read mail headers
199 */
200 void
201 readheaders()
202 {
203 alias_t *cur;
204 char *p;
205 int tome, cont;
206 char buf[MAXLINE];
207
208 cont = tome = 0;
209 while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
210 switch(*buf) {
211 case 'F': /* "From " */
212 cont = 0;
213 if (!strncmp(buf, "From ", 5)) {
214 for (p = buf + 5; *p && *p != ' '; ++p);
215 *p = '\0';
216 (void)strcpy(from, buf + 5);
217 if ((p = strchr(from, '\n')))
218 *p = '\0';
219 if (junkmail())
220 exit(0);
221 }
222 break;
223 case 'P': /* "Precedence:" */
224 cont = 0;
225 if (strncasecmp(buf, "Precedence", 10) ||
226 (buf[10] != ':' && buf[10] != ' ' &&
227 buf[10] != '\t'))
228 break;
229 if (!(p = strchr(buf, ':')))
230 break;
231 while (*++p && isspace((unsigned char)*p));
232 if (!*p)
233 break;
234 if (!strncasecmp(p, "junk", 4) ||
235 !strncasecmp(p, "bulk", 4) ||
236 !strncasecmp(p, "list", 4))
237 exit(0);
238 break;
239 case 'C': /* "Cc:" */
240 if (strncmp(buf, "Cc:", 3))
241 break;
242 cont = 1;
243 goto findme;
244 case 'T': /* "To:" */
245 if (strncmp(buf, "To:", 3))
246 break;
247 cont = 1;
248 goto findme;
249 default:
250 if (!isspace((unsigned char)*buf) || !cont || tome) {
251 cont = 0;
252 break;
253 }
254 findme: for (cur = names; !tome && cur; cur = cur->next)
255 tome += nsearch(cur->name, buf);
256 }
257 if (!tome)
258 exit(0);
259 if (!*from) {
260 syslog(LOG_NOTICE, "vacation: no initial \"From\" line.");
261 exit(1);
262 }
263 }
264
265 /*
266 * nsearch --
267 * do a nice, slow, search of a string for a substring.
268 */
269 int
270 nsearch(const char *name, const char *str)
271 {
272 size_t len;
273
274 for (len = strlen(name); *str; ++str)
275 if (!strncasecmp(name, str, len))
276 return(1);
277 return(0);
278 }
279
280 /*
281 * junkmail --
282 * read the header and return if automagic/junk/bulk/list mail
283 */
284 int
285 junkmail()
286 {
287 static struct ignore {
288 char *name;
289 int len;
290 } ignore[] = {
291 { "-request", 8 },
292 { "postmaster", 10 },
293 { "uucp", 4 },
294 { "mailer-daemon", 13 },
295 { "mailer", 6 },
296 { "-relay", 6 },
297 {NULL, 0 }
298 };
299 struct ignore *cur;
300 int len;
301 char *p;
302
303 /*
304 * This is mildly amusing, and I'm not positive it's right; trying
305 * to find the "real" name of the sender, assuming that addresses
306 * will be some variant of:
307 *
308 * From site!site!SENDER%site.domain%site.domain (at) site.domain
309 */
310 if (!(p = strchr(from, '%')))
311 if (!(p = strchr(from, '@'))) {
312 if ((p = strrchr(from, '!')))
313 ++p;
314 else
315 p = from;
316 for (; *p; ++p);
317 }
318 len = p - from;
319 for (cur = ignore; cur->name; ++cur)
320 if (len >= cur->len &&
321 !strncasecmp(cur->name, p - cur->len, cur->len))
322 return(1);
323 return(0);
324 }
325
326 #define VIT "__VACATION__INTERVAL__TIMER__"
327
328 /*
329 * recent --
330 * find out if user has gotten a vacation message recently.
331 * use memmove for machines with alignment restrictions
332 */
333 int
334 recent()
335 {
336 DBT key, data;
337 time_t then, next;
338
339 /* get interval time */
340 key.data = VIT;
341 key.size = sizeof(VIT);
342 if ((db->get)(db, &key, &data, 0))
343 next = SECSPERDAY * DAYSPERWEEK;
344 else
345 memmove(&next, data.data, sizeof(next));
346
347 /* get record for this address */
348 key.data = from;
349 key.size = strlen(from);
350 if (!(db->get)(db, &key, &data, 0)) {
351 memmove(&then, data.data, sizeof(then));
352 if (next == (time_t)LONG_MAX || /* XXX */
353 then + next > time(NULL))
354 return(1);
355 }
356 return(0);
357 }
358
359 /*
360 * setinterval --
361 * store the reply interval
362 */
363 void
364 setinterval(time_t interval)
365 {
366 DBT key, data;
367
368 key.data = VIT;
369 key.size = sizeof(VIT);
370 data.data = &interval;
371 data.size = sizeof(interval);
372 (void)(db->put)(db, &key, &data, 0);
373 }
374
375 /*
376 * setreply --
377 * store that this user knows about the vacation.
378 */
379 void
380 setreply()
381 {
382 DBT key, data;
383 time_t now;
384
385 key.data = from;
386 key.size = strlen(from);
387 (void)time(&now);
388 data.data = &now;
389 data.size = sizeof(now);
390 (void)(db->put)(db, &key, &data, 0);
391 }
392
393 /*
394 * sendmessage --
395 * exec sendmail to send the vacation file to sender
396 */
397 void
398 sendmessage(const char *myname)
399 {
400 FILE *mfp, *sfp;
401 int i;
402 int pvect[2];
403 char buf[MAXLINE];
404
405 mfp = fopen(VMSG, "r");
406 if (mfp == NULL) {
407 syslog(LOG_NOTICE, "vacation: no ~%s/%s file.", myname, VMSG);
408 exit(1);
409 }
410 if (pipe(pvect) < 0) {
411 syslog(LOG_ERR, "vacation: pipe: %m");
412 exit(1);
413 }
414 i = vfork();
415 if (i < 0) {
416 syslog(LOG_ERR, "vacation: fork: %m");
417 exit(1);
418 }
419 if (i == 0) {
420 dup2(pvect[0], 0);
421 close(pvect[0]);
422 close(pvect[1]);
423 close(fileno(mfp));
424 execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--", from,
425 NULL);
426 syslog(LOG_ERR, "vacation: can't exec %s: %m",
427 _PATH_SENDMAIL);
428 _exit(1);
429 }
430 close(pvect[0]);
431 sfp = fdopen(pvect[1], "w");
432 fprintf(sfp, "To: %s\n", from);
433 while (fgets(buf, sizeof buf, mfp))
434 fputs(buf, sfp);
435 fclose(mfp);
436 fclose(sfp);
437 }
438
439 void
440 usage()
441 {
442
443 syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login",
444 getuid());
445 exit(1);
446 }
447