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