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