1 /* $OpenBSD: vacation.c,v 1.38 2019/06/28 13:35:05 deraadt 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 (strlcpy(from, p, sizeof(from)) >= sizeof(from)) {
250 syslog(LOG_NOTICE,
251 "Return-Path %s exceeds limits", p);
252 exit(1);
253 }
254 from[strcspn(from, "\n")] = '\0';
255 if (junkmail())
256 exit(0);
257 break;
258 case 'P': /* "Precedence:" */
259 case 'p':
260 cont = 0;
261 if (strncasecmp(buf, "Precedence:", 11))
262 break;
263 for (p = buf + 11; isspace((unsigned char)*p); ++p)
264 ;
265 if (!strncasecmp(p, "junk", 4) ||
266 !strncasecmp(p, "bulk", 4) ||
267 !strncasecmp(p, "list", 4))
268 exit(0);
269 break;
270 case 'S': /* Subject: */
271 case 's':
272 cont = 0;
273 if (strncasecmp(buf, "Subject:",
274 sizeof("Subject:")-1) ||
275 (buf[8] != ' ' && buf[8] != '\t'))
276 break;
277 for (p = buf + 8; isspace((unsigned char)*p); ++p)
278 ;
279 if (strlcpy(subj, p, sizeof(subj)) >= sizeof(subj)) {
280 syslog(LOG_NOTICE,
281 "Subject %s exceeds limits", p);
282 exit(1);
283 }
284 subj[strcspn(subj, "\n")] = '\0';
285 break;
286 case 'C': /* "Cc:" */
287 case 'c':
288 if (strncasecmp(buf, "Cc:", 3))
289 break;
290 cont = 1;
291 goto findme;
292 case 'T': /* "To:" */
293 case 't':
294 if (strncasecmp(buf, "To:", 3))
295 break;
296 cont = 1;
297 goto findme;
298 default:
299 if (!isspace((unsigned char)*buf) || !cont || tome) {
300 cont = 0;
301 break;
302 }
303 findme: for (cur = names; !tome && cur; cur = cur->next)
304 tome += nsearch(cur->name, buf);
305 }
306 if (!tome)
307 exit(0);
308 if (!*from) {
309 syslog(LOG_NOTICE,
310 "no initial \"From\" or \"Return-Path\"line.");
311 exit(1);
312 }
313 }
314
315 /*
316 * nsearch --
317 * do a nice, slow, search of a string for a substring.
318 */
319 int
nsearch(char * name,char * str)320 nsearch(char *name, char *str)
321 {
322 int len;
323
324 for (len = strlen(name); *str; ++str)
325 if (!strncasecmp(name, str, len))
326 return(1);
327 return(0);
328 }
329
330 /*
331 * junkmail --
332 * read the header and return if automagic/junk/bulk/list mail
333 */
334 int
junkmail(void)335 junkmail(void)
336 {
337 static struct ignore {
338 char *name;
339 int len;
340 } ignore[] = {
341 { "-request", 8 },
342 { "postmaster", 10 },
343 { "uucp", 4 },
344 { "mailer-daemon", 13 },
345 { "mailer", 6 },
346 { "-relay", 6 },
347 { NULL, 0 }
348 };
349 struct ignore *cur;
350 int len;
351 char *p;
352
353 /*
354 * This is mildly amusing, and I'm not positive it's right; trying
355 * to find the "real" name of the sender, assuming that addresses
356 * will be some variant of:
357 *
358 * From site!site!SENDER%site.domain%site.domain@site.domain
359 */
360 if (!(p = strchr(from, '%'))) {
361 if (!(p = strchr(from, '@'))) {
362 if ((p = strrchr(from, '!')))
363 ++p;
364 else
365 p = from;
366 for (; *p; ++p)
367 ;
368 }
369 }
370 len = p - from;
371 for (cur = ignore; cur->name; ++cur)
372 if (len >= cur->len &&
373 !strncasecmp(cur->name, p - cur->len, cur->len))
374 return(1);
375 return(0);
376 }
377
378 #define VIT "__VACATION__INTERVAL__TIMER__"
379
380 /*
381 * recent --
382 * find out if user has gotten a vacation message recently.
383 * use bcopy for machines with alignment restrictions
384 */
385 int
recent(void)386 recent(void)
387 {
388 time_t then, next;
389 DBT key, data;
390
391 /* get interval time */
392 key.data = VIT;
393 key.size = sizeof(VIT);
394 if ((db->get)(db, &key, &data, 0))
395 next = SECSPERDAY * 7;
396 else
397 bcopy(data.data, &next, sizeof(next));
398
399 /* get record for this address */
400 key.data = from;
401 key.size = strlen(from);
402 if (!(db->get)(db, &key, &data, 0)) {
403 bcopy(data.data, &then, sizeof(then));
404 if (next == 0 ||
405 then + next > time(NULL))
406 return(1);
407 }
408 return(0);
409 }
410
411 /*
412 * setinterval --
413 * store the reply interval
414 */
415 void
setinterval(time_t interval)416 setinterval(time_t interval)
417 {
418 DBT key, data;
419
420 key.data = VIT;
421 key.size = sizeof(VIT);
422 data.data = &interval;
423 data.size = sizeof(interval);
424 (void)(db->put)(db, &key, &data, 0);
425 }
426
427 /*
428 * setreply --
429 * store that this user knows about the vacation.
430 */
431 void
setreply(void)432 setreply(void)
433 {
434 DBT key, data;
435 time_t now;
436
437 key.data = from;
438 key.size = strlen(from);
439 (void)time(&now);
440 data.data = &now;
441 data.size = sizeof(now);
442 (void)(db->put)(db, &key, &data, 0);
443 }
444
445 /*
446 * sendmessage --
447 * exec sendmail to send the vacation file to sender
448 */
449 void
sendmessage(char * myname)450 sendmessage(char *myname)
451 {
452 char buf[MAXLINE];
453 FILE *mfp, *sfp;
454 int pvect[2], i;
455
456 mfp = fopen(VMSG, "r");
457 if (mfp == NULL) {
458 syslog(LOG_NOTICE, "no ~%s/%s file.", myname, VMSG);
459 exit(1);
460 }
461 if (pipe(pvect) == -1) {
462 syslog(LOG_ERR, "pipe: %m");
463 exit(1);
464 }
465 i = vfork();
466 if (i == -1) {
467 syslog(LOG_ERR, "fork: %m");
468 exit(1);
469 }
470 if (i == 0) {
471 dup2(pvect[0], 0);
472 close(pvect[0]);
473 close(pvect[1]);
474 close(fileno(mfp));
475 execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--",
476 from, (char *)NULL);
477 syslog(LOG_ERR, "can't exec %s: %m", _PATH_SENDMAIL);
478 _exit(1);
479 }
480 close(pvect[0]);
481 sfp = fdopen(pvect[1], "w");
482 if (sfp == NULL) {
483 /* XXX could not fdopen; likely out of memory */
484 fclose(mfp);
485 close(pvect[1]);
486 return;
487 }
488 fprintf(sfp, "To: %s\n", from);
489 fputs("Auto-Submitted: auto-replied\n", sfp);
490 while (fgets(buf, sizeof buf, mfp)) {
491 char *s = strstr(buf, "$SUBJECT");
492
493 if (s) {
494 *s = 0;
495 fputs(buf, sfp);
496 fputs(subj, sfp);
497 fputs(s+8, sfp);
498 } else {
499 fputs(buf, sfp);
500 }
501 }
502 fclose(mfp);
503 fclose(sfp);
504 }
505
506 void
usage(void)507 usage(void)
508 {
509 syslog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias] login",
510 getuid());
511 exit(1);
512 }
513