1 /*
2 * Copyright (c) 1999-2002, 2009 Proofpoint, Inc. and its suppliers.
3 * All rights reserved.
4 * Copyright (c) 1983, 1987, 1993
5 * The Regents of the University of California. All rights reserved.
6 * Copyright (c) 1983 Eric P. Allman. All rights reserved.
7 *
8 * By using this file, you agree to the terms and conditions set
9 * forth in the LICENSE file which can be found at the top level of
10 * the sendmail distribution.
11 *
12 */
13
14 #include <sm/gen.h>
15
16 SM_IDSTR(copyright,
17 "@(#) Copyright (c) 1999-2002, 2009 Proofpoint, Inc. and its suppliers.\n\
18 All rights reserved.\n\
19 Copyright (c) 1983, 1987, 1993\n\
20 The Regents of the University of California. All rights reserved.\n\
21 Copyright (c) 1983 Eric P. Allman. All rights reserved.\n")
22
23 SM_IDSTR(id, "@(#)$Id: vacation.c,v 8.148 2013-11-22 20:52:02 ca Exp $")
24
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <syslog.h>
28 #include <time.h>
29 #include <unistd.h>
30 #include <sm/sendmail.h>
31 #include <sm/sysexits.h>
32
33 #include <sm/cf.h>
34 #include <sm/mbdb.h>
35 #include <sendmail/sendmail.h>
36 #include <sendmail/pathnames.h>
37 #include <libsmdb/smdb.h>
38
39 #define ONLY_ONCE ((time_t) 0) /* send at most one reply */
40 #define INTERVAL_UNDEF ((time_t) (-1)) /* no value given */
41
42 uid_t RealUid;
43 gid_t RealGid;
44 char *RealUserName;
45 uid_t RunAsUid;
46 gid_t RunAsGid;
47 char *RunAsUserName;
48 int Verbose = 2;
49 bool DontInitGroups = false;
50 uid_t TrustedUid = 0;
51 BITMAP256 DontBlameSendmail;
52
53 static int readheaders __P((bool));
54 static bool junkmail __P((char *));
55 static bool nsearch __P((char *, char *));
56 static void usage __P((void));
57 static void setinterval __P((time_t));
58 static bool recent __P((void));
59 static void setreply __P((char *, time_t));
60 static void sendmessage __P((char *, char *, char *));
61 static void xclude __P((SM_FILE_T *));
62
63 /*
64 ** VACATION -- return a message to the sender when on vacation.
65 **
66 ** This program is invoked as a message receiver. It returns a
67 ** message specified by the user to whomever sent the mail, taking
68 ** care not to return a message too often to prevent "I am on
69 ** vacation" loops.
70 */
71
72 #define VDB ".vacation" /* vacation database */
73 #define VMSG ".vacation.msg" /* vacation message */
74 #define SECSPERDAY (60 * 60 * 24)
75 #define DAYSPERWEEK 7
76
77 typedef struct alias
78 {
79 char *name;
80 struct alias *next;
81 } ALIAS;
82
83 ALIAS *Names = NULL;
84
85 SMDB_DATABASE *Db;
86
87 char From[MAXLINE];
88 char Subject[MAXLINE];
89 bool CloseMBDB = false;
90
91 #if defined(__hpux) || defined(__osf__)
92 # ifndef SM_CONF_SYSLOG_INT
93 # define SM_CONF_SYSLOG_INT 1
94 # endif
95 #endif /* defined(__hpux) || defined(__osf__) */
96
97 #if SM_CONF_SYSLOG_INT
98 # define SYSLOG_RET_T int
99 # define SYSLOG_RET return 0
100 #else
101 # define SYSLOG_RET_T void
102 # define SYSLOG_RET
103 #endif
104
105 typedef SYSLOG_RET_T SYSLOG_T __P((int, const char *, ...));
106 SYSLOG_T *msglog = syslog;
107 static SYSLOG_RET_T debuglog __P((int, const char *, ...));
108 static void eatmsg __P((void));
109 static void listdb __P((void));
110
111 /* exit after reading input */
112 #define EXITIT(excode) \
113 { \
114 eatmsg(); \
115 if (CloseMBDB) \
116 { \
117 sm_mbdb_terminate(); \
118 CloseMBDB = false; \
119 } \
120 return excode; \
121 }
122
123 #define EXITM(excode) \
124 { \
125 if (!initdb && !list) \
126 eatmsg(); \
127 if (CloseMBDB) \
128 { \
129 sm_mbdb_terminate(); \
130 CloseMBDB = false; \
131 } \
132 exit(excode); \
133 }
134
135 int
main(argc,argv)136 main(argc, argv)
137 int argc;
138 char **argv;
139 {
140 bool alwaysrespond = false;
141 bool initdb, exclude;
142 bool runasuser = false;
143 bool list = false;
144 int mfail = 0, ufail = 0;
145 int ch;
146 int result;
147 long sff;
148 time_t interval;
149 struct passwd *pw;
150 ALIAS *cur;
151 char *dbfilename = NULL;
152 char *msgfilename = NULL;
153 char *cfpath = NULL;
154 char *name = NULL;
155 char *returnaddr = NULL;
156 SMDB_USER_INFO user_info;
157 static char rnamebuf[MAXNAME];
158 extern int optind, opterr;
159 extern char *optarg;
160
161 /* Vars needed to link with smutil */
162 clrbitmap(DontBlameSendmail);
163 RunAsUid = RealUid = getuid();
164 RunAsGid = RealGid = getgid();
165 pw = getpwuid(RealUid);
166 if (pw != NULL)
167 {
168 if (strlen(pw->pw_name) > MAXNAME - 1)
169 pw->pw_name[MAXNAME] = '\0';
170 sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
171 }
172 else
173 sm_snprintf(rnamebuf, sizeof rnamebuf,
174 "Unknown UID %d", (int) RealUid);
175 RunAsUserName = RealUserName = rnamebuf;
176
177 #ifdef LOG_MAIL
178 openlog("vacation", LOG_PID, LOG_MAIL);
179 #else
180 openlog("vacation", LOG_PID);
181 #endif
182
183 opterr = 0;
184 initdb = false;
185 exclude = false;
186 interval = INTERVAL_UNDEF;
187 *From = '\0';
188 *Subject = '\0';
189
190 #define OPTIONS "a:C:df:Iijlm:R:r:s:t:Uxz"
191
192 while (mfail == 0 && ufail == 0 &&
193 (ch = getopt(argc, argv, OPTIONS)) != -1)
194 {
195 switch((char)ch)
196 {
197 case 'a': /* alias */
198 cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS));
199 if (cur == NULL)
200 {
201 mfail++;
202 break;
203 }
204 cur->name = optarg;
205 cur->next = Names;
206 Names = cur;
207 break;
208
209 case 'C':
210 cfpath = optarg;
211 break;
212
213 case 'd': /* debug mode */
214 msglog = debuglog;
215 break;
216
217 case 'f': /* alternate database */
218 dbfilename = optarg;
219 break;
220
221 case 'I': /* backward compatible */
222 case 'i': /* init the database */
223 initdb = true;
224 break;
225
226 case 'j':
227 alwaysrespond = true;
228 break;
229
230 case 'l':
231 list = true; /* list the database */
232 break;
233
234 case 'm': /* alternate message file */
235 msgfilename = optarg;
236 break;
237
238 case 'R':
239 returnaddr = optarg;
240 break;
241
242 case 'r':
243 if (isascii(*optarg) && isdigit(*optarg))
244 {
245 interval = atol(optarg) * SECSPERDAY;
246 if (interval < 0)
247 ufail++;
248 }
249 else
250 interval = ONLY_ONCE;
251 break;
252
253 case 's': /* alternate sender name */
254 (void) sm_strlcpy(From, optarg, sizeof From);
255 break;
256
257 case 't': /* SunOS: -t1d (default expire) */
258 break;
259
260 case 'U': /* run as single user mode */
261 runasuser = true;
262 break;
263
264 case 'x':
265 exclude = true;
266 break;
267
268 case 'z':
269 returnaddr = "<>";
270 break;
271
272 case '?':
273 default:
274 ufail++;
275 break;
276 }
277 }
278 argc -= optind;
279 argv += optind;
280
281 if (mfail != 0)
282 {
283 msglog(LOG_NOTICE,
284 "vacation: can't allocate memory for alias");
285 EXITM(EX_TEMPFAIL);
286 }
287 if (ufail != 0)
288 usage();
289
290 if (argc != 1)
291 {
292 if (!initdb && !list && !exclude)
293 usage();
294 if ((pw = getpwuid(getuid())) == NULL)
295 {
296 msglog(LOG_ERR,
297 "vacation: no such user uid %u", getuid());
298 EXITM(EX_NOUSER);
299 }
300 name = strdup(pw->pw_name);
301 user_info.smdbu_id = pw->pw_uid;
302 user_info.smdbu_group_id = pw->pw_gid;
303 (void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
304 SMDB_MAX_USER_NAME_LEN);
305 if (chdir(pw->pw_dir) != 0)
306 {
307 msglog(LOG_NOTICE,
308 "vacation: no such directory %s",
309 pw->pw_dir);
310 EXITM(EX_NOINPUT);
311 }
312 }
313 else if (runasuser)
314 {
315 name = strdup(*argv);
316 if (dbfilename == NULL || msgfilename == NULL)
317 {
318 msglog(LOG_NOTICE,
319 "vacation: -U requires setting both -f and -m");
320 EXITM(EX_NOINPUT);
321 }
322 user_info.smdbu_id = pw->pw_uid;
323 user_info.smdbu_group_id = pw->pw_gid;
324 (void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
325 SMDB_MAX_USER_NAME_LEN);
326 }
327 else
328 {
329 int err;
330 SM_CF_OPT_T mbdbname;
331 SM_MBDB_T user;
332
333 cfpath = getcfname(0, 0, SM_GET_SENDMAIL_CF, cfpath);
334 mbdbname.opt_name = "MailboxDatabase";
335 mbdbname.opt_val = "pw";
336 (void) sm_cf_getopt(cfpath, 1, &mbdbname);
337 err = sm_mbdb_initialize(mbdbname.opt_val);
338 if (err != EX_OK)
339 {
340 msglog(LOG_ERR,
341 "vacation: can't open mailbox database: %s",
342 sm_strexit(err));
343 EXITM(err);
344 }
345 CloseMBDB = true;
346 err = sm_mbdb_lookup(*argv, &user);
347 if (err == EX_NOUSER)
348 {
349 msglog(LOG_ERR, "vacation: no such user %s", *argv);
350 EXITM(EX_NOUSER);
351 }
352 if (err != EX_OK)
353 {
354 msglog(LOG_ERR,
355 "vacation: can't read mailbox database: %s",
356 sm_strexit(err));
357 EXITM(err);
358 }
359 name = strdup(user.mbdb_name);
360 if (chdir(user.mbdb_homedir) != 0)
361 {
362 msglog(LOG_NOTICE,
363 "vacation: no such directory %s",
364 user.mbdb_homedir);
365 EXITM(EX_NOINPUT);
366 }
367 user_info.smdbu_id = user.mbdb_uid;
368 user_info.smdbu_group_id = user.mbdb_gid;
369 (void) sm_strlcpy(user_info.smdbu_name, user.mbdb_name,
370 SMDB_MAX_USER_NAME_LEN);
371 }
372 if (name == NULL)
373 {
374 msglog(LOG_ERR,
375 "vacation: can't allocate memory for username");
376 EXITM(EX_OSERR);
377 }
378
379 if (dbfilename == NULL)
380 dbfilename = VDB;
381 if (msgfilename == NULL)
382 msgfilename = VMSG;
383
384 sff = SFF_CREAT;
385 if (getegid() != getgid())
386 {
387 /* Allow a set-group-ID vacation binary */
388 RunAsGid = user_info.smdbu_group_id = getegid();
389 sff |= SFF_OPENASROOT;
390 }
391 if (getuid() == 0)
392 {
393 /* Allow root to initialize user's vacation databases */
394 sff |= SFF_OPENASROOT|SFF_ROOTOK;
395
396 /* ... safely */
397 sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY;
398 }
399
400 result = smdb_open_database(&Db, dbfilename,
401 O_CREAT|O_RDWR | (initdb ? O_TRUNC : 0),
402 S_IRUSR|S_IWUSR, sff,
403 SMDB_TYPE_DEFAULT, &user_info, NULL);
404 if (result != SMDBE_OK)
405 {
406 msglog(LOG_NOTICE, "vacation: %s: %s", dbfilename,
407 sm_errstring(result));
408 EXITM(EX_DATAERR);
409 }
410
411 if (list)
412 {
413 listdb();
414 (void) Db->smdb_close(Db);
415 exit(EX_OK);
416 }
417
418 if (interval != INTERVAL_UNDEF)
419 setinterval(interval);
420
421 if (initdb && !exclude)
422 {
423 (void) Db->smdb_close(Db);
424 exit(EX_OK);
425 }
426
427 if (exclude)
428 {
429 xclude(smioin);
430 (void) Db->smdb_close(Db);
431 EXITM(EX_OK);
432 }
433
434 if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
435 {
436 msglog(LOG_NOTICE,
437 "vacation: can't allocate memory for username");
438 (void) Db->smdb_close(Db);
439 EXITM(EX_OSERR);
440 }
441 cur->name = name;
442 cur->next = Names;
443 Names = cur;
444
445 result = readheaders(alwaysrespond);
446 if (result == EX_OK && !recent())
447 {
448 time_t now;
449
450 (void) time(&now);
451 setreply(From, now);
452 (void) Db->smdb_close(Db);
453 sendmessage(name, msgfilename, returnaddr);
454 }
455 else
456 (void) Db->smdb_close(Db);
457 if (result == EX_NOUSER)
458 result = EX_OK;
459 exit(result);
460 }
461
462 /*
463 ** EATMSG -- read stdin till EOF
464 **
465 ** Parameters:
466 ** none.
467 **
468 ** Returns:
469 ** nothing.
470 */
471
472 static void
eatmsg()473 eatmsg()
474 {
475 /*
476 ** read the rest of the e-mail and ignore it to avoid problems
477 ** with EPIPE in sendmail
478 */
479 while (getc(stdin) != EOF)
480 continue;
481 }
482
483 /*
484 ** READHEADERS -- read mail headers
485 **
486 ** Parameters:
487 ** alwaysrespond -- respond regardless of whether msg is to me
488 **
489 ** Returns:
490 ** a exit code: NOUSER if no reply, OK if reply, * if error
491 **
492 ** Side Effects:
493 ** may exit().
494 */
495
496 #define CLEANADDR(addr, type) \
497 { \
498 bool quoted = false; \
499 \
500 while (*addr != '\0') \
501 { \
502 /* escaped character */ \
503 if (*addr == '\\') \
504 { \
505 addr++; \
506 if (*addr == '\0') \
507 { \
508 msglog(LOG_NOTICE, \
509 "vacation: badly formatted \"%s\" line",\
510 type); \
511 EXITIT(EX_DATAERR); \
512 } \
513 } \
514 else if (*addr == '"') \
515 quoted = !quoted; \
516 else if (*addr == '\r' || *addr == '\n') \
517 break; \
518 else if (*addr == ' ' && !quoted) \
519 break; \
520 addr++; \
521 } \
522 if (quoted) \
523 { \
524 msglog(LOG_NOTICE, \
525 "vacation: badly formatted \"%s\" line", type); \
526 EXITIT(EX_DATAERR); \
527 } \
528 *addr = '\0'; \
529 }
530
531 static int
readheaders(alwaysrespond)532 readheaders(alwaysrespond)
533 bool alwaysrespond;
534 {
535 bool tome, cont;
536 register char *p, *s;
537 register ALIAS *cur;
538 char buf[MAXLINE];
539
540 cont = false;
541 tome = alwaysrespond;
542 while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) >= 0 &&
543 *buf != '\n')
544 {
545 switch(*buf)
546 {
547 case 'A': /* "Auto-Submitted:" */
548 case 'a':
549 cont = false;
550 if (strlen(buf) <= 14 ||
551 strncasecmp(buf, "Auto-Submitted", 14) != 0 ||
552 (buf[14] != ':' && buf[14] != ' ' &&
553 buf[14] != '\t'))
554 break;
555 if ((p = strchr(buf, ':')) == NULL)
556 break;
557 while (*++p != '\0' && isascii(*p) && isspace(*p))
558 continue;
559 if (*p == '\0')
560 break;
561 if ((s = strpbrk(p, " \t\r\n")) != NULL)
562 *s = '\0';
563 /* Obey RFC3834: no auto-reply for auto-submitted mail */
564 if (strcasecmp(p, "no") != 0)
565 EXITIT(EX_NOUSER);
566 break;
567
568 case 'F': /* "From " */
569 cont = false;
570 if (strncmp(buf, "From ", 5) == 0)
571 {
572 p = buf + 5;
573 CLEANADDR(p, "From ");
574
575 /* ok since both strings have MAXLINE length */
576 if (*From == '\0')
577 (void) sm_strlcpy(From, buf + 5,
578 sizeof From);
579 if ((p = strchr(buf + 5, '\n')) != NULL)
580 *p = '\0';
581 if (junkmail(buf + 5))
582 EXITIT(EX_NOUSER);
583 }
584 break;
585
586 case 'L': /* "List-Id:" */
587 case 'l':
588 cont = false;
589 if (strlen(buf) <= 7 ||
590 strncasecmp(buf, "List-Id", 7) != 0 ||
591 (buf[7] != ':' && buf[7] != ' ' &&
592 buf[7] != '\t'))
593 break;
594 if ((p = strchr(buf, ':')) == NULL)
595 break;
596
597 /* If we found a List-Id: header, don't send a reply */
598 EXITIT(EX_NOUSER);
599
600 /* NOTREACHED */
601 break;
602
603 case 'P': /* "Precedence:" */
604 case 'p':
605 cont = false;
606 if (strlen(buf) <= 10 ||
607 strncasecmp(buf, "Precedence", 10) != 0 ||
608 (buf[10] != ':' && buf[10] != ' ' &&
609 buf[10] != '\t'))
610 break;
611 if ((p = strchr(buf, ':')) == NULL)
612 break;
613 while (*++p != '\0' && isascii(*p) && isspace(*p))
614 continue;
615 if (*p == '\0')
616 break;
617 if (strncasecmp(p, "junk", 4) == 0 ||
618 strncasecmp(p, "bulk", 4) == 0 ||
619 strncasecmp(p, "list", 4) == 0)
620 EXITIT(EX_NOUSER);
621 break;
622
623 case 'R': /* Return-Path */
624 case 'r':
625 cont = false;
626 if (strlen(buf) <= 11 ||
627 strncasecmp(buf, "Return-Path", 11) != 0 ||
628 (buf[11] != ':' && buf[11] != ' ' &&
629 buf[11] != '\t'))
630 break;
631 if ((p = strchr(buf, ':')) == NULL)
632 break;
633 while (*++p != '\0' && isascii(*p) && isspace(*p))
634 continue;
635 if (*p == '\0')
636 break;
637 (void) sm_strlcpy(From, p, sizeof From);
638 p = From;
639 CLEANADDR(p, "Return-Path:");
640 if (junkmail(From))
641 EXITIT(EX_NOUSER);
642 break;
643
644 case 'S': /* Subject */
645 case 's':
646 cont = false;
647 if (strlen(buf) <= 7 ||
648 strncasecmp(buf, "Subject", 7) != 0 ||
649 (buf[7] != ':' && buf[7] != ' ' &&
650 buf[7] != '\t'))
651 break;
652 if ((p = strchr(buf, ':')) == NULL)
653 break;
654 while (*++p != '\0' && isascii(*p) && isspace(*p))
655 continue;
656 if (*p == '\0')
657 break;
658 (void) sm_strlcpy(Subject, p, sizeof Subject);
659 if ((s = strpbrk(Subject, "\r\n")) != NULL)
660 *s = '\0';
661 break;
662
663 case 'C': /* "Cc:" */
664 case 'c':
665 if (strncasecmp(buf, "Cc:", 3) != 0)
666 break;
667 cont = true;
668 goto findme;
669
670 case 'T': /* "To:" */
671 case 't':
672 if (strncasecmp(buf, "To:", 3) != 0)
673 break;
674 cont = true;
675 goto findme;
676
677 default:
678 if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
679 {
680 cont = false;
681 break;
682 }
683 findme:
684 for (cur = Names;
685 !tome && cur != NULL;
686 cur = cur->next)
687 tome = nsearch(cur->name, buf);
688 }
689 }
690 if (!tome)
691 EXITIT(EX_NOUSER);
692 if (*From == '\0')
693 {
694 msglog(LOG_NOTICE, "vacation: no initial \"From \" line");
695 EXITIT(EX_DATAERR);
696 }
697 EXITIT(EX_OK);
698 }
699
700 /*
701 ** NSEARCH -- do a nice, slow, search of a string for a substring.
702 **
703 ** Parameters:
704 ** name -- name to search.
705 ** str -- string in which to search.
706 **
707 ** Returns:
708 ** is name a substring of str?
709 */
710
711 static bool
nsearch(name,str)712 nsearch(name, str)
713 register char *name, *str;
714 {
715 register size_t len;
716 register char *s;
717
718 len = strlen(name);
719
720 for (s = str; *s != '\0'; ++s)
721 {
722 /*
723 ** Check to make sure that the string matches and
724 ** the previous character is not an alphanumeric and
725 ** the next character after the match is not an alphanumeric.
726 **
727 ** This prevents matching "eric" to "derick" while still
728 ** matching "eric" to "<eric+detail>".
729 */
730
731 if (SM_STRNCASEEQ(name, s, len) &&
732 (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
733 (!isascii(*(s + len)) || !isalnum(*(s + len))))
734 return true;
735 }
736 return false;
737 }
738
739 /*
740 ** JUNKMAIL -- read the header and return if automagic/junk/bulk/list mail
741 **
742 ** Parameters:
743 ** from -- sender address.
744 **
745 ** Returns:
746 ** is this some automated/junk/bulk/list mail?
747 */
748
749 struct ignore
750 {
751 char *name;
752 size_t len;
753 };
754
755 typedef struct ignore IGNORE_T;
756
757 #define MAX_USER_LEN 256 /* maximum length of local part (sender) */
758
759 /* delimiters for the local part of an address */
760 #define isdelim(c) ((c) == '%' || (c) == '@' || (c) == '+')
761
762 static bool
junkmail(from)763 junkmail(from)
764 char *from;
765 {
766 bool quot;
767 char *e;
768 size_t len;
769 IGNORE_T *cur;
770 char sender[MAX_USER_LEN];
771 static IGNORE_T ignore[] =
772 {
773 { "postmaster", 10 },
774 { "uucp", 4 },
775 { "mailer-daemon", 13 },
776 { "mailer", 6 },
777 { NULL, 0 }
778 };
779
780 static IGNORE_T ignorepost[] =
781 {
782 { "-request", 8 },
783 { "-relay", 6 },
784 { "-owner", 6 },
785 { NULL, 0 }
786 };
787
788 static IGNORE_T ignorepre[] =
789 {
790 { "owner-", 6 },
791 { NULL, 0 }
792 };
793
794 /*
795 ** This is mildly amusing, and I'm not positive it's right; trying
796 ** to find the "real" name of the sender, assuming that addresses
797 ** will be some variant of:
798 **
799 ** From site!site!SENDER%site.domain%site.domain@site.domain
800 */
801
802 quot = false;
803 e = from;
804 len = 0;
805 while (*e != '\0' && (quot || !isdelim(*e)))
806 {
807 if (*e == '"')
808 {
809 quot = !quot;
810 ++e;
811 continue;
812 }
813 if (*e == '\\')
814 {
815 if (*(++e) == '\0')
816 {
817 /* '\\' at end of string? */
818 break;
819 }
820 if (len < MAX_USER_LEN)
821 sender[len++] = *e;
822 ++e;
823 continue;
824 }
825 if (*e == '!' && !quot)
826 {
827 len = 0;
828 sender[len] = '\0';
829 }
830 else
831 if (len < MAX_USER_LEN)
832 sender[len++] = *e;
833 ++e;
834 }
835 if (len < MAX_USER_LEN)
836 sender[len] = '\0';
837 else
838 sender[MAX_USER_LEN - 1] = '\0';
839
840 if (len <= 0)
841 return false;
842 #if 0
843 if (quot)
844 return false; /* syntax error... */
845 #endif
846
847 /* test prefixes */
848 for (cur = ignorepre; cur->name != NULL; ++cur)
849 {
850 if (len >= cur->len &&
851 strncasecmp(cur->name, sender, cur->len) == 0)
852 return true;
853 }
854
855 /*
856 ** If the name is truncated, don't test the rest.
857 ** We could extract the "tail" of the sender address and
858 ** compare it it ignorepost, however, it seems not worth
859 ** the effort.
860 ** The address surely can't match any entry in ignore[]
861 ** (as long as all of them are shorter than MAX_USER_LEN).
862 */
863
864 if (len > MAX_USER_LEN)
865 return false;
866
867 /* test full local parts */
868 for (cur = ignore; cur->name != NULL; ++cur)
869 {
870 if (len == cur->len &&
871 strncasecmp(cur->name, sender, cur->len) == 0)
872 return true;
873 }
874
875 /* test postfixes */
876 for (cur = ignorepost; cur->name != NULL; ++cur)
877 {
878 if (len >= cur->len &&
879 strncasecmp(cur->name, e - cur->len - 1,
880 cur->len) == 0)
881 return true;
882 }
883 return false;
884 }
885
886 #define VIT "__VACATION__INTERVAL__TIMER__"
887
888 /*
889 ** RECENT -- find out if user has gotten a vacation message recently.
890 **
891 ** Parameters:
892 ** none.
893 **
894 ** Returns:
895 ** true iff user has gotten a vacation message recently.
896 */
897
898 static bool
recent()899 recent()
900 {
901 SMDB_DBENT key, data;
902 time_t then, next;
903 bool trydomain = false;
904 int st;
905 char *domain;
906
907 memset(&key, '\0', sizeof key);
908 memset(&data, '\0', sizeof data);
909
910 /* get interval time */
911 key.data = VIT;
912 key.size = sizeof(VIT);
913
914 st = Db->smdb_get(Db, &key, &data, 0);
915 if (st != SMDBE_OK)
916 next = SECSPERDAY * DAYSPERWEEK;
917 else
918 memmove(&next, data.data, sizeof(next));
919
920 memset(&data, '\0', sizeof data);
921
922 /* get record for this address */
923 key.data = From;
924 key.size = strlen(From);
925
926 do
927 {
928 st = Db->smdb_get(Db, &key, &data, 0);
929 if (st == SMDBE_OK)
930 {
931 memmove(&then, data.data, sizeof(then));
932 if (next == ONLY_ONCE || then == ONLY_ONCE ||
933 then + next > time(NULL))
934 return true;
935 }
936 if ((trydomain = !trydomain) &&
937 (domain = strchr(From, '@')) != NULL)
938 {
939 key.data = domain;
940 key.size = strlen(domain);
941 }
942 } while (trydomain);
943 return false;
944 }
945
946 /*
947 ** SETINTERVAL -- store the reply interval
948 **
949 ** Parameters:
950 ** interval -- time interval for replies.
951 **
952 ** Returns:
953 ** nothing.
954 **
955 ** Side Effects:
956 ** stores the reply interval in database.
957 */
958
959 static void
setinterval(interval)960 setinterval(interval)
961 time_t interval;
962 {
963 SMDB_DBENT key, data;
964
965 memset(&key, '\0', sizeof key);
966 memset(&data, '\0', sizeof data);
967
968 key.data = VIT;
969 key.size = sizeof(VIT);
970 data.data = (char*) &interval;
971 data.size = sizeof(interval);
972 (void) (Db->smdb_put)(Db, &key, &data, 0);
973 }
974
975 /*
976 ** SETREPLY -- store that this user knows about the vacation.
977 **
978 ** Parameters:
979 ** from -- sender address.
980 ** when -- last reply time.
981 **
982 ** Returns:
983 ** nothing.
984 **
985 ** Side Effects:
986 ** stores user/time in database.
987 */
988
989 static void
setreply(from,when)990 setreply(from, when)
991 char *from;
992 time_t when;
993 {
994 SMDB_DBENT key, data;
995
996 memset(&key, '\0', sizeof key);
997 memset(&data, '\0', sizeof data);
998
999 key.data = from;
1000 key.size = strlen(from);
1001 data.data = (char*) &when;
1002 data.size = sizeof(when);
1003 (void) (Db->smdb_put)(Db, &key, &data, 0);
1004 }
1005
1006 /*
1007 ** XCLUDE -- add users to vacation db so they don't get a reply.
1008 **
1009 ** Parameters:
1010 ** f -- file pointer with list of address to exclude
1011 **
1012 ** Returns:
1013 ** nothing.
1014 **
1015 ** Side Effects:
1016 ** stores users in database.
1017 */
1018
1019 static void
xclude(f)1020 xclude(f)
1021 SM_FILE_T *f;
1022 {
1023 char buf[MAXLINE], *p;
1024
1025 if (f == NULL)
1026 return;
1027 while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) >= 0)
1028 {
1029 if ((p = strchr(buf, '\n')) != NULL)
1030 *p = '\0';
1031 setreply(buf, ONLY_ONCE);
1032 }
1033 }
1034
1035 /*
1036 ** SENDMESSAGE -- exec sendmail to send the vacation file to sender
1037 **
1038 ** Parameters:
1039 ** myname -- user name.
1040 ** msgfn -- name of file with vacation message.
1041 ** sender -- use as sender address
1042 **
1043 ** Returns:
1044 ** nothing.
1045 **
1046 ** Side Effects:
1047 ** sends vacation reply.
1048 */
1049
1050 static void
sendmessage(myname,msgfn,sender)1051 sendmessage(myname, msgfn, sender)
1052 char *myname;
1053 char *msgfn;
1054 char *sender;
1055 {
1056 SM_FILE_T *mfp, *sfp;
1057 int i;
1058 int pvect[2];
1059 char *s;
1060 char *pv[8];
1061 char buf[MAXLINE];
1062
1063 mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
1064 if (mfp == NULL)
1065 {
1066 if (msgfn[0] == '/')
1067 msglog(LOG_NOTICE, "vacation: no %s file", msgfn);
1068 else
1069 msglog(LOG_NOTICE, "vacation: no ~%s/%s file",
1070 myname, msgfn);
1071 exit(EX_NOINPUT);
1072 }
1073 if (pipe(pvect) < 0)
1074 {
1075 msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
1076 exit(EX_OSERR);
1077 }
1078 pv[0] = "sendmail";
1079 pv[1] = "-oi";
1080 pv[2] = "-f";
1081 if (sender != NULL)
1082 pv[3] = sender;
1083 else
1084 pv[3] = myname;
1085 pv[4] = "--";
1086 pv[5] = From;
1087 pv[6] = NULL;
1088 i = fork();
1089 if (i < 0)
1090 {
1091 msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
1092 exit(EX_OSERR);
1093 }
1094 if (i == 0)
1095 {
1096 (void) dup2(pvect[0], 0);
1097 (void) close(pvect[0]);
1098 (void) close(pvect[1]);
1099 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1100 (void) execv(_PATH_SENDMAIL, pv);
1101 msglog(LOG_ERR, "vacation: can't exec %s: %s",
1102 _PATH_SENDMAIL, sm_errstring(errno));
1103 exit(EX_UNAVAILABLE);
1104 }
1105 /* check return status of the following calls? XXX */
1106 (void) close(pvect[0]);
1107 if ((sfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
1108 (void *) &(pvect[1]),
1109 SM_IO_WRONLY, NULL)) != NULL)
1110 {
1111 #if _FFR_VAC_WAIT4SM
1112 # ifdef WAITUNION
1113 union wait st;
1114 # else
1115 auto int st;
1116 # endif
1117 #endif /* _FFR_VAC_WAIT4SM */
1118
1119 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, "To: %s\n", From);
1120 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT,
1121 "Auto-Submitted: auto-replied\n");
1122 while (sm_io_fgets(mfp, SM_TIME_DEFAULT, buf, sizeof buf) >= 0)
1123 {
1124 if ((s = strstr(buf, "$SUBJECT")) != NULL)
1125 {
1126 *s = '\0';
1127 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf);
1128 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, Subject);
1129 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, s + 8);
1130 }
1131 else
1132 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf);
1133 }
1134 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1135 (void) sm_io_close(sfp, SM_TIME_DEFAULT);
1136 #if _FFR_VAC_WAIT4SM
1137 (void) wait(&st);
1138 #endif
1139 }
1140 else
1141 {
1142 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1143 msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
1144 exit(EX_UNAVAILABLE);
1145 }
1146 }
1147
1148 static void
usage()1149 usage()
1150 {
1151 msglog(LOG_NOTICE,
1152 "uid %u: usage: vacation [-a alias] [-C cfpath] [-d] [-f db] [-i] [-j] [-l] [-m msg] [-R returnaddr] [-r interval] [-s sender] [-t time] [-U] [-x] [-z] login",
1153 getuid());
1154 exit(EX_USAGE);
1155 }
1156
1157 /*
1158 ** LISTDB -- list the contents of the vacation database
1159 **
1160 ** Parameters:
1161 ** none.
1162 **
1163 ** Returns:
1164 ** nothing.
1165 */
1166
1167 static void
listdb()1168 listdb()
1169 {
1170 int result;
1171 time_t t;
1172 SMDB_CURSOR *cursor = NULL;
1173 SMDB_DBENT db_key, db_value;
1174
1175 memset(&db_key, '\0', sizeof db_key);
1176 memset(&db_value, '\0', sizeof db_value);
1177
1178 result = Db->smdb_cursor(Db, &cursor, 0);
1179 if (result != SMDBE_OK)
1180 {
1181 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1182 "vacation: set cursor: %s\n",
1183 sm_errstring(result));
1184 return;
1185 }
1186
1187 while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1188 SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
1189 {
1190 char *timestamp;
1191
1192 /* skip magic VIT entry */
1193 if (db_key.size == strlen(VIT) + 1 &&
1194 strncmp((char *)db_key.data, VIT,
1195 (int)db_key.size - 1) == 0)
1196 continue;
1197
1198 /* skip bogus values */
1199 if (db_value.size != sizeof t)
1200 {
1201 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1202 "vacation: %.*s invalid time stamp\n",
1203 (int) db_key.size, (char *) db_key.data);
1204 continue;
1205 }
1206
1207 memcpy(&t, db_value.data, sizeof t);
1208
1209 if (db_key.size > 40)
1210 db_key.size = 40;
1211
1212 if (t <= 0)
1213 {
1214 /* must be an exclude */
1215 timestamp = "(exclusion)\n";
1216 }
1217 else
1218 {
1219 timestamp = ctime(&t);
1220 }
1221 sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
1222 (int) db_key.size, (char *) db_key.data,
1223 timestamp);
1224
1225 memset(&db_key, '\0', sizeof db_key);
1226 memset(&db_value, '\0', sizeof db_value);
1227 }
1228
1229 if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1230 {
1231 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1232 "vacation: get value at cursor: %s\n",
1233 sm_errstring(result));
1234 if (cursor != NULL)
1235 {
1236 (void) cursor->smdbc_close(cursor);
1237 cursor = NULL;
1238 }
1239 return;
1240 }
1241 (void) cursor->smdbc_close(cursor);
1242 cursor = NULL;
1243 }
1244
1245 /*
1246 ** DEBUGLOG -- write message to standard error
1247 **
1248 ** Append a message to the standard error for the convenience of
1249 ** end-users debugging without access to the syslog messages.
1250 **
1251 ** Parameters:
1252 ** i -- syslog log level
1253 ** fmt -- string format
1254 **
1255 ** Returns:
1256 ** nothing.
1257 */
1258
1259 /*VARARGS2*/
1260 static SYSLOG_RET_T
1261 #ifdef __STDC__
debuglog(int i,const char * fmt,...)1262 debuglog(int i, const char *fmt, ...)
1263 #else /* __STDC__ */
1264 debuglog(i, fmt, va_alist)
1265 int i;
1266 const char *fmt;
1267 va_dcl
1268 #endif /* __STDC__ */
1269
1270 {
1271 SM_VA_LOCAL_DECL
1272
1273 SM_VA_START(ap, fmt);
1274 sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);
1275 SM_VA_END(ap);
1276 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "\n");
1277 SYSLOG_RET;
1278 }
1279