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