1 /* $OpenBSD: vacation.c,v 1.39 2024/08/29 21:04:16 op Exp $ */
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.
16 * 3. Neither the name of the University nor the names of its contributors
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 /*
34 ** Vacation
35 ** Copyright (c) 1983 Eric P. Allman
36 ** Berkeley, California
37 */
38
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <pwd.h>
42 #include <db.h>
43 #include <time.h>
44 #include <syslog.h>
45 #include <errno.h>
46 #include <unistd.h>
47 #include <stdio.h>
48 #include <ctype.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <paths.h>
52
53 /*
54 * VACATION -- return a message to the sender when on vacation.
55 *
56 * This program is invoked as a message receiver. It returns a
57 * message specified by the user to whomever sent the mail, taking
58 * care not to return a message too often to prevent "I am on
59 * vacation" loops.
60 */
61
62 #define MAXLINE 1024 /* max line from mail header */
63 #define VDB ".vacation.db" /* dbm's database */
64 #define VMSG ".vacation.msg" /* vacation message */
65
66 typedef struct alias {
67 struct alias *next;
68 char *name;
69 } ALIAS;
70 ALIAS *names;
71
72 DB *db;
73 char from[MAXLINE];
74 char subj[MAXLINE];
75
76 int junkmail(void);
77 int nsearch(char *, char *);
78 void readheaders(void);
79 int recent(void);
80 void sendmessage(char *);
81 void setinterval(time_t);
82 void setreply(void);
83 void usage(void);
84
85 #define SECSPERDAY (24 * 60 * 60)
86
87 int
main(int argc,char * argv[])88 main(int argc, char *argv[])
89 {
90 int ch, iflag, flags;
91 struct passwd *pw;
92 time_t interval;
93 struct stat sb;
94 ALIAS *cur;
95
96 opterr = iflag = 0;
97 interval = -1;
98 while ((ch = getopt(argc, argv, "a:Iir:")) != -1)
99 switch ((char)ch) {
100 case 'a': /* alias */
101 if (!(cur = malloc(sizeof(ALIAS))))
102 break;
103 cur->name = optarg;
104 cur->next = names;
105 names = cur;
106 break;
107 case 'I': /* backward compatible */
108 case 'i': /* init the database */
109 iflag = 1;
110 break;
111 case 'r':
112 if (isdigit((unsigned char)*optarg)) {
113 interval = atol(optarg) * SECSPERDAY;
114 if (interval < 0)
115 usage();
116 } else
117 interval = 0; /* one time only */
118 break;
119 default:
120 usage();
121 }
122 argc -= optind;
123 argv += optind;
124
125 if (argc != 1) {
126 if (!iflag)
127 usage();
128 if (!(pw = getpwuid(getuid()))) {
129 syslog(LOG_ERR,
130 "no such user uid %u.", getuid());
131 exit(1);
132 }
133 } else if (!(pw = getpwnam(*argv))) {
134 syslog(LOG_ERR, "no such user %s.", *argv);
135 exit(1);
136 }
137 if (chdir(pw->pw_dir)) {
138 syslog(LOG_NOTICE,
139 "no such directory %s.", pw->pw_dir);
140 exit(1);
141 }
142
143 /*
144 * dbopen(3) can not deal with a zero-length file w/o O_TRUNC.
145 */
146 if (iflag == 1 || (stat(VDB, &sb) == 0 && sb.st_size == (off_t)0))
147 flags = O_CREAT|O_RDWR|O_TRUNC;
148 else
149 flags = O_CREAT|O_RDWR;
150
151 db = dbopen(VDB, flags, S_IRUSR|S_IWUSR, DB_HASH, NULL);
152 if (!db) {
153 syslog(LOG_NOTICE, "%s: %m", VDB);
154 exit(1);
155 }
156
157 if (interval != -1)
158 setinterval(interval);
159
160 if (iflag) {
161 (void)(db->close)(db);
162 exit(0);
163 }
164
165 if (!(cur = malloc(sizeof(ALIAS))))
166 exit(1);
167 cur->name = pw->pw_name;
168 cur->next = names;
169 names = cur;
170
171 readheaders();
172 if (!recent()) {
173 setreply();
174 (void)(db->close)(db);
175 sendmessage(pw->pw_name);
176 } else
177 (void)(db->close)(db);
178 exit(0);
179 /* NOTREACHED */
180 }
181
182 /*
183 * readheaders --
184 * read mail headers
185 */
186 void
readheaders(void)187 readheaders(void)
188 {
189 char buf[MAXLINE], *p;
190 int tome, cont;
191 ALIAS *cur;
192
193 cont = tome = 0;
194 while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
195 switch (*buf) {
196 case 'A': /* "Auto-Submitted:" */
197 case 'a':
198 cont = 0;
199 if (strncasecmp(buf, "Auto-Submitted:", 15))
200 break;
201 for (p = buf + 15; isspace((unsigned char)*p); ++p)
202 ;
203 /*
204 * RFC 3834 section 2:
205 * Automatic responses SHOULD NOT be issued in response
206 * to any message which contains an Auto-Submitted
207 * header where the field has any value other than "no".
208 */
209 if ((p[0] == 'n' || p[0] == 'N') &&
210 (p[1] == 'o' || p[1] == 'O')) {
211 for (p += 2; isspace((unsigned char)*p); ++p)
212 ;
213 if (*p == '\0')
214 break; /* Auto-Submitted: no */
215 }
216 exit(0);
217 case 'F': /* "From " */
218 case 'f':
219 cont = 0;
220 if (!strncasecmp(buf, "From ", 5)) {
221 for (p = buf + 5; *p && *p != ' '; ++p)
222 ;
223 *p = '\0';
224 (void)strlcpy(from, buf + 5, sizeof(from));
225 from[strcspn(from, "\n")] = '\0';
226 if (junkmail())
227 exit(0);
228 }
229 break;
230 case 'L': /* "List-Id:" */
231 case 'l':
232 cont = 0;
233 /*
234 * If present (with any value), message is coming from a
235 * mailing list, cf. RFC2919.
236 */
237 if (strncasecmp(buf, "List-Id:", 8) == 0)
238 exit(0);
239 break;
240 case 'R': /* "Return-Path:" */
241 case 'r':
242 cont = 0;
243 if (strncasecmp(buf, "Return-Path:",
244 sizeof("Return-Path:")-1) ||
245 (buf[12] != ' ' && buf[12] != '\t'))
246 break;
247 for (p = buf + 12; isspace((unsigned char)*p); ++p)
248 ;
249 if (*p == '<') {
250 ++p;
251 p[strcspn(p, ">")] = '\0';
252 }
253 if (strlcpy(from, p, sizeof(from)) >= sizeof(from)) {
254 syslog(LOG_NOTICE,
255 "Return-Path %s exceeds limits", p);
256 exit(1);
257 }
258 from[strcspn(from, "\n")] = '\0';
259 if (junkmail())
260 exit(0);
261 break;
262 case 'P': /* "Precedence:" */
263 case 'p':
264 cont = 0;
265 if (strncasecmp(buf, "Precedence:", 11))
266 break;
267 for (p = buf + 11; isspace((unsigned char)*p); ++p)
268 ;
269 if (!strncasecmp(p, "junk", 4) ||
270 !strncasecmp(p, "bulk", 4) ||
271 !strncasecmp(p, "list", 4))
272 exit(0);
273 break;
274 case 'S': /* Subject: */
275 case 's':
276 cont = 0;
277 if (strncasecmp(buf, "Subject:",
278 sizeof("Subject:")-1) ||
279 (buf[8] != ' ' && buf[8] != '\t'))
280 break;
281 for (p = buf + 8; isspace((unsigned char)*p); ++p)
282 ;
283 if (strlcpy(subj, p, sizeof(subj)) >= sizeof(subj)) {
284 syslog(LOG_NOTICE,
285 "Subject %s exceeds limits", p);
286 exit(1);
287 }
288 subj[strcspn(subj, "\n")] = '\0';
289 break;
290 case 'C': /* "Cc:" */
291 case 'c':
292 if (strncasecmp(buf, "Cc:", 3))
293 break;
294 cont = 1;
295 goto findme;
296 case 'T': /* "To:" */
297 case 't':
298 if (strncasecmp(buf, "To:", 3))
299 break;
300 cont = 1;
301 goto findme;
302 default:
303 if (!isspace((unsigned char)*buf) || !cont || tome) {
304 cont = 0;
305 break;
306 }
307 findme: for (cur = names; !tome && cur; cur = cur->next)
308 tome += nsearch(cur->name, buf);
309 }
310 if (!tome || !*from)
311 exit(0);
312 }
313
314 /*
315 * nsearch --
316 * do a nice, slow, search of a string for a substring.
317 */
318 int
nsearch(char * name,char * str)319 nsearch(char *name, char *str)
320 {
321 int len;
322
323 for (len = strlen(name); *str; ++str)
324 if (!strncasecmp(name, str, len))
325 return(1);
326 return(0);
327 }
328
329 /*
330 * junkmail --
331 * read the header and return if automagic/junk/bulk/list mail
332 */
333 int
junkmail(void)334 junkmail(void)
335 {
336 static struct ignore {
337 char *name;
338 int len;
339 } ignore[] = {
340 { "-request", 8 },
341 { "postmaster", 10 },
342 { "uucp", 4 },
343 { "mailer-daemon", 13 },
344 { "mailer", 6 },
345 { "-relay", 6 },
346 { NULL, 0 }
347 };
348 struct ignore *cur;
349 int len;
350 char *p;
351
352 /*
353 * This is mildly amusing, and I'm not positive it's right; trying
354 * to find the "real" name of the sender, assuming that addresses
355 * will be some variant of:
356 *
357 * From site!site!SENDER%site.domain%site.domain@site.domain
358 */
359 if (!(p = strchr(from, '%'))) {
360 if (!(p = strchr(from, '@'))) {
361 if ((p = strrchr(from, '!')))
362 ++p;
363 else
364 p = from;
365 for (; *p; ++p)
366 ;
367 }
368 }
369 len = p - from;
370 for (cur = ignore; cur->name; ++cur)
371 if (len >= cur->len &&
372 !strncasecmp(cur->name, p - cur->len, cur->len))
373 return(1);
374 return(0);
375 }
376
377 #define VIT "__VACATION__INTERVAL__TIMER__"
378
379 /*
380 * recent --
381 * find out if user has gotten a vacation message recently.
382 * use bcopy for machines with alignment restrictions
383 */
384 int
recent(void)385 recent(void)
386 {
387 time_t then, next;
388 DBT key, data;
389
390 /* get interval time */
391 key.data = VIT;
392 key.size = sizeof(VIT);
393 if ((db->get)(db, &key, &data, 0))
394 next = SECSPERDAY * 7;
395 else
396 bcopy(data.data, &next, sizeof(next));
397
398 /* get record for this address */
399 key.data = from;
400 key.size = strlen(from);
401 if (!(db->get)(db, &key, &data, 0)) {
402 bcopy(data.data, &then, sizeof(then));
403 if (next == 0 ||
404 then + next > time(NULL))
405 return(1);
406 }
407 return(0);
408 }
409
410 /*
411 * setinterval --
412 * store the reply interval
413 */
414 void
setinterval(time_t interval)415 setinterval(time_t interval)
416 {
417 DBT key, data;
418
419 key.data = VIT;
420 key.size = sizeof(VIT);
421 data.data = &interval;
422 data.size = sizeof(interval);
423 (void)(db->put)(db, &key, &data, 0);
424 }
425
426 /*
427 * setreply --
428 * store that this user knows about the vacation.
429 */
430 void
setreply(void)431 setreply(void)
432 {
433 DBT key, data;
434 time_t now;
435
436 key.data = from;
437 key.size = strlen(from);
438 (void)time(&now);
439 data.data = &now;
440 data.size = sizeof(now);
441 (void)(db->put)(db, &key, &data, 0);
442 }
443
444 /*
445 * sendmessage --
446 * exec sendmail to send the vacation file to sender
447 */
448 void
sendmessage(char * myname)449 sendmessage(char *myname)
450 {
451 char buf[MAXLINE];
452 FILE *mfp, *sfp;
453 int pvect[2], i;
454
455 mfp = fopen(VMSG, "r");
456 if (mfp == NULL) {
457 syslog(LOG_NOTICE, "no ~%s/%s file.", myname, VMSG);
458 exit(1);
459 }
460 if (pipe(pvect) == -1) {
461 syslog(LOG_ERR, "pipe: %m");
462 exit(1);
463 }
464 i = vfork();
465 if (i == -1) {
466 syslog(LOG_ERR, "fork: %m");
467 exit(1);
468 }
469 if (i == 0) {
470 dup2(pvect[0], 0);
471 close(pvect[0]);
472 close(pvect[1]);
473 close(fileno(mfp));
474 execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--",
475 from, (char *)NULL);
476 syslog(LOG_ERR, "can't exec %s: %m", _PATH_SENDMAIL);
477 _exit(1);
478 }
479 close(pvect[0]);
480 sfp = fdopen(pvect[1], "w");
481 if (sfp == NULL) {
482 /* XXX could not fdopen; likely out of memory */
483 fclose(mfp);
484 close(pvect[1]);
485 return;
486 }
487 fprintf(sfp, "To: %s\n", from);
488 fputs("Auto-Submitted: auto-replied\n", sfp);
489 while (fgets(buf, sizeof buf, mfp)) {
490 char *s = strstr(buf, "$SUBJECT");
491
492 if (s) {
493 *s = 0;
494 fputs(buf, sfp);
495 fputs(subj, sfp);
496 fputs(s+8, sfp);
497 } else {
498 fputs(buf, sfp);
499 }
500 }
501 fclose(mfp);
502 fclose(sfp);
503 }
504
505 void
usage(void)506 usage(void)
507 {
508 syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login",
509 getuid());
510 exit(1);
511 }
512