sendmail: Update vendor branch to v8.14.4
[dragonfly.git] / contrib / sendmail-8.14 / vacation / vacation.c
1 /*
2  * Copyright (c) 1999-2002, 2009 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-2002, 2009 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.146 2009/08/07 21:28:39 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 = NULL;
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 = strdup(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 = strdup(*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 = strdup(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         if (name == NULL)
375         {
376                 msglog(LOG_ERR,
377                        "vacation: can't allocate memory for username.\n");
378                 EXITM(EX_OSERR);
379         }
380
381         if (dbfilename == NULL)
382                 dbfilename = VDB;
383         if (msgfilename == NULL)
384                 msgfilename = VMSG;
385
386         sff = SFF_CREAT;
387         if (getegid() != getgid())
388         {
389                 /* Allow a set-group-ID vacation binary */
390                 RunAsGid = user_info.smdbu_group_id = getegid();
391                 sff |= SFF_OPENASROOT;
392         }
393         if (getuid() == 0)
394         {
395                 /* Allow root to initialize user's vacation databases */
396                 sff |= SFF_OPENASROOT|SFF_ROOTOK;
397
398                 /* ... safely */
399                 sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY;
400         }
401
402
403         result = smdb_open_database(&Db, dbfilename,
404                                     O_CREAT|O_RDWR | (initdb ? O_TRUNC : 0),
405                                     S_IRUSR|S_IWUSR, sff,
406                                     SMDB_TYPE_DEFAULT, &user_info, NULL);
407         if (result != SMDBE_OK)
408         {
409                 msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
410                        sm_errstring(result));
411                 EXITM(EX_DATAERR);
412         }
413
414         if (list)
415         {
416                 listdb();
417                 (void) Db->smdb_close(Db);
418                 exit(EX_OK);
419         }
420
421         if (interval != INTERVAL_UNDEF)
422                 setinterval(interval);
423
424         if (initdb && !exclude)
425         {
426                 (void) Db->smdb_close(Db);
427                 exit(EX_OK);
428         }
429
430         if (exclude)
431         {
432                 xclude(smioin);
433                 (void) Db->smdb_close(Db);
434                 EXITM(EX_OK);
435         }
436
437         if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
438         {
439                 msglog(LOG_NOTICE,
440                        "vacation: can't allocate memory for username.\n");
441                 (void) Db->smdb_close(Db);
442                 EXITM(EX_OSERR);
443         }
444         cur->name = name;
445         cur->next = Names;
446         Names = cur;
447
448         result = readheaders(alwaysrespond);
449         if (result == EX_OK && !recent())
450         {
451                 time_t now;
452
453                 (void) time(&now);
454                 setreply(From, now);
455                 (void) Db->smdb_close(Db);
456                 sendmessage(name, msgfilename, returnaddr);
457         }
458         else
459                 (void) Db->smdb_close(Db);
460         if (result == EX_NOUSER)
461                 result = EX_OK;
462         exit(result);
463 }
464
465 /*
466 ** EATMSG -- read stdin till EOF
467 **
468 **      Parameters:
469 **              none.
470 **
471 **      Returns:
472 **              nothing.
473 **
474 */
475
476 static void
477 eatmsg()
478 {
479         /*
480         **  read the rest of the e-mail and ignore it to avoid problems
481         **  with EPIPE in sendmail
482         */
483         while (getc(stdin) != EOF)
484                 continue;
485 }
486
487 /*
488 ** READHEADERS -- read mail headers
489 **
490 **      Parameters:
491 **              alwaysrespond -- respond regardless of whether msg is to me
492 **
493 **      Returns:
494 **              a exit code: NOUSER if no reply, OK if reply, * if error
495 **
496 **      Side Effects:
497 **              may exit().
498 **
499 */
500
501 static int
502 readheaders(alwaysrespond)
503         bool alwaysrespond;
504 {
505         bool tome, cont;
506         register char *p;
507         register ALIAS *cur;
508         char buf[MAXLINE];
509
510         cont = false;
511         tome = alwaysrespond;
512         while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) &&
513                *buf != '\n')
514         {
515                 switch(*buf)
516                 {
517                   case 'F':             /* "From " */
518                         cont = false;
519                         if (strncmp(buf, "From ", 5) == 0)
520                         {
521                                 bool quoted = false;
522
523                                 p = buf + 5;
524                                 while (*p != '\0')
525                                 {
526                                         /* escaped character */
527                                         if (*p == '\\')
528                                         {
529                                                 p++;
530                                                 if (*p == '\0')
531                                                 {
532                                                         msglog(LOG_NOTICE,
533                                                                "vacation: badly formatted \"From \" line.\n");
534                                                         EXITIT(EX_DATAERR);
535                                                 }
536                                         }
537                                         else if (*p == '"')
538                                                 quoted = !quoted;
539                                         else if (*p == '\r' || *p == '\n')
540                                                 break;
541                                         else if (*p == ' ' && !quoted)
542                                                 break;
543                                         p++;
544                                 }
545                                 if (quoted)
546                                 {
547                                         msglog(LOG_NOTICE,
548                                                "vacation: badly formatted \"From \" line.\n");
549                                         EXITIT(EX_DATAERR);
550                                 }
551                                 *p = '\0';
552
553                                 /* ok since both strings have MAXLINE length */
554                                 if (*From == '\0')
555                                         (void) sm_strlcpy(From, buf + 5,
556                                                           sizeof From);
557                                 if ((p = strchr(buf + 5, '\n')) != NULL)
558                                         *p = '\0';
559                                 if (junkmail(buf + 5))
560                                         EXITIT(EX_NOUSER);
561                         }
562                         break;
563
564                   case 'P':             /* "Precedence:" */
565                   case 'p':
566                         cont = false;
567                         if (strlen(buf) <= 10 ||
568                             strncasecmp(buf, "Precedence", 10) != 0 ||
569                             (buf[10] != ':' && buf[10] != ' ' &&
570                              buf[10] != '\t'))
571                                 break;
572                         if ((p = strchr(buf, ':')) == NULL)
573                                 break;
574                         while (*++p != '\0' && isascii(*p) && isspace(*p));
575                         if (*p == '\0')
576                                 break;
577                         if (strncasecmp(p, "junk", 4) == 0 ||
578                             strncasecmp(p, "bulk", 4) == 0 ||
579                             strncasecmp(p, "list", 4) == 0)
580                                 EXITIT(EX_NOUSER);
581                         break;
582
583                   case 'C':             /* "Cc:" */
584                   case 'c':
585                         if (strncasecmp(buf, "Cc:", 3) != 0)
586                                 break;
587                         cont = true;
588                         goto findme;
589
590                   case 'T':             /* "To:" */
591                   case 't':
592                         if (strncasecmp(buf, "To:", 3) != 0)
593                                 break;
594                         cont = true;
595                         goto findme;
596
597                   default:
598                         if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
599                         {
600                                 cont = false;
601                                 break;
602                         }
603 findme:
604                         for (cur = Names;
605                              !tome && cur != NULL;
606                              cur = cur->next)
607                                 tome = nsearch(cur->name, buf);
608                 }
609         }
610         if (!tome)
611                 EXITIT(EX_NOUSER);
612         if (*From == '\0')
613         {
614                 msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
615                 EXITIT(EX_DATAERR);
616         }
617         EXITIT(EX_OK);
618 }
619
620 /*
621 ** NSEARCH --
622 **      do a nice, slow, search of a string for a substring.
623 **
624 **      Parameters:
625 **              name -- name to search.
626 **              str -- string in which to search.
627 **
628 **      Returns:
629 **              is name a substring of str?
630 **
631 */
632
633 static bool
634 nsearch(name, str)
635         register char *name, *str;
636 {
637         register size_t len;
638         register char *s;
639
640         len = strlen(name);
641
642         for (s = str; *s != '\0'; ++s)
643         {
644                 /*
645                 **  Check to make sure that the string matches and
646                 **  the previous character is not an alphanumeric and
647                 **  the next character after the match is not an alphanumeric.
648                 **
649                 **  This prevents matching "eric" to "derick" while still
650                 **  matching "eric" to "<eric+detail>".
651                 */
652
653                 if (tolower(*s) == tolower(*name) &&
654                     strncasecmp(name, s, len) == 0 &&
655                     (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
656                     (!isascii(*(s + len)) || !isalnum(*(s + len))))
657                         return true;
658         }
659         return false;
660 }
661
662 /*
663 ** JUNKMAIL --
664 **      read the header and return if automagic/junk/bulk/list mail
665 **
666 **      Parameters:
667 **              from -- sender address.
668 **
669 **      Returns:
670 **              is this some automated/junk/bulk/list mail?
671 **
672 */
673
674 struct ignore
675 {
676         char    *name;
677         size_t  len;
678 };
679
680 typedef struct ignore IGNORE_T;
681
682 #define MAX_USER_LEN 256        /* maximum length of local part (sender) */
683
684 /* delimiters for the local part of an address */
685 #define isdelim(c)      ((c) == '%' || (c) == '@' || (c) == '+')
686
687 static bool
688 junkmail(from)
689         char *from;
690 {
691         bool quot;
692         char *e;
693         size_t len;
694         IGNORE_T *cur;
695         char sender[MAX_USER_LEN];
696         static IGNORE_T ignore[] =
697         {
698                 { "postmaster",         10      },
699                 { "uucp",               4       },
700                 { "mailer-daemon",      13      },
701                 { "mailer",             6       },
702                 { NULL,                 0       }
703         };
704
705         static IGNORE_T ignorepost[] =
706         {
707                 { "-request",           8       },
708                 { "-relay",             6       },
709                 { "-owner",             6       },
710                 { NULL,                 0       }
711         };
712
713         static IGNORE_T ignorepre[] =
714         {
715                 { "owner-",             6       },
716                 { NULL,                 0       }
717         };
718
719         /*
720         **  This is mildly amusing, and I'm not positive it's right; trying
721         **  to find the "real" name of the sender, assuming that addresses
722         **  will be some variant of:
723         **
724         **  From site!site!SENDER%site.domain%site.domain@site.domain
725         */
726
727         quot = false;
728         e = from;
729         len = 0;
730         while (*e != '\0' && (quot || !isdelim(*e)))
731         {
732                 if (*e == '"')
733                 {
734                         quot = !quot;
735                         ++e;
736                         continue;
737                 }
738                 if (*e == '\\')
739                 {
740                         if (*(++e) == '\0')
741                         {
742                                 /* '\\' at end of string? */
743                                 break;
744                         }
745                         if (len < MAX_USER_LEN)
746                                 sender[len++] = *e;
747                         ++e;
748                         continue;
749                 }
750                 if (*e == '!' && !quot)
751                 {
752                         len = 0;
753                         sender[len] = '\0';
754                 }
755                 else
756                         if (len < MAX_USER_LEN)
757                                 sender[len++] = *e;
758                 ++e;
759         }
760         if (len < MAX_USER_LEN)
761                 sender[len] = '\0';
762         else
763                 sender[MAX_USER_LEN - 1] = '\0';
764
765         if (len <= 0)
766                 return false;
767 #if 0
768         if (quot)
769                 return false;   /* syntax error... */
770 #endif /* 0 */
771
772         /* test prefixes */
773         for (cur = ignorepre; cur->name != NULL; ++cur)
774         {
775                 if (len >= cur->len &&
776                     strncasecmp(cur->name, sender, cur->len) == 0)
777                         return true;
778         }
779
780         /*
781         **  If the name is truncated, don't test the rest.
782         **      We could extract the "tail" of the sender address and
783         **      compare it it ignorepost, however, it seems not worth
784         **      the effort.
785         **      The address surely can't match any entry in ignore[]
786         **      (as long as all of them are shorter than MAX_USER_LEN).
787         */
788
789         if (len > MAX_USER_LEN)
790                 return false;
791
792         /* test full local parts */
793         for (cur = ignore; cur->name != NULL; ++cur)
794         {
795                 if (len == cur->len &&
796                     strncasecmp(cur->name, sender, cur->len) == 0)
797                         return true;
798         }
799
800         /* test postfixes */
801         for (cur = ignorepost; cur->name != NULL; ++cur)
802         {
803                 if (len >= cur->len &&
804                     strncasecmp(cur->name, e - cur->len - 1,
805                                 cur->len) == 0)
806                         return true;
807         }
808         return false;
809 }
810
811 #define VIT     "__VACATION__INTERVAL__TIMER__"
812
813 /*
814 ** RECENT --
815 **      find out if user has gotten a vacation message recently.
816 **
817 **      Parameters:
818 **              none.
819 **
820 **      Returns:
821 **              true iff user has gotten a vacation message recently.
822 **
823 */
824
825 static bool
826 recent()
827 {
828         SMDB_DBENT key, data;
829         time_t then, next;
830         bool trydomain = false;
831         int st;
832         char *domain;
833
834         memset(&key, '\0', sizeof key);
835         memset(&data, '\0', sizeof data);
836
837         /* get interval time */
838         key.data = VIT;
839         key.size = sizeof(VIT);
840
841         st = Db->smdb_get(Db, &key, &data, 0);
842         if (st != SMDBE_OK)
843                 next = SECSPERDAY * DAYSPERWEEK;
844         else
845                 memmove(&next, data.data, sizeof(next));
846
847         memset(&data, '\0', sizeof data);
848
849         /* get record for this address */
850         key.data = From;
851         key.size = strlen(From);
852
853         do
854         {
855                 st = Db->smdb_get(Db, &key, &data, 0);
856                 if (st == SMDBE_OK)
857                 {
858                         memmove(&then, data.data, sizeof(then));
859                         if (next == ONLY_ONCE || then == ONLY_ONCE ||
860                             then + next > time(NULL))
861                                 return true;
862                 }
863                 if ((trydomain = !trydomain) &&
864                     (domain = strchr(From, '@')) != NULL)
865                 {
866                         key.data = domain;
867                         key.size = strlen(domain);
868                 }
869         } while (trydomain);
870         return false;
871 }
872
873 /*
874 ** SETINTERVAL --
875 **      store the reply interval
876 **
877 **      Parameters:
878 **              interval -- time interval for replies.
879 **
880 **      Returns:
881 **              nothing.
882 **
883 **      Side Effects:
884 **              stores the reply interval in database.
885 */
886
887 static void
888 setinterval(interval)
889         time_t interval;
890 {
891         SMDB_DBENT key, data;
892
893         memset(&key, '\0', sizeof key);
894         memset(&data, '\0', sizeof data);
895
896         key.data = VIT;
897         key.size = sizeof(VIT);
898         data.data = (char*) &interval;
899         data.size = sizeof(interval);
900         (void) (Db->smdb_put)(Db, &key, &data, 0);
901 }
902
903 /*
904 ** SETREPLY --
905 **      store that this user knows about the vacation.
906 **
907 **      Parameters:
908 **              from -- sender address.
909 **              when -- last reply time.
910 **
911 **      Returns:
912 **              nothing.
913 **
914 **      Side Effects:
915 **              stores user/time in database.
916 */
917
918 static void
919 setreply(from, when)
920         char *from;
921         time_t when;
922 {
923         SMDB_DBENT key, data;
924
925         memset(&key, '\0', sizeof key);
926         memset(&data, '\0', sizeof data);
927
928         key.data = from;
929         key.size = strlen(from);
930         data.data = (char*) &when;
931         data.size = sizeof(when);
932         (void) (Db->smdb_put)(Db, &key, &data, 0);
933 }
934
935 /*
936 ** XCLUDE --
937 **      add users to vacation db so they don't get a reply.
938 **
939 **      Parameters:
940 **              f -- file pointer with list of address to exclude
941 **
942 **      Returns:
943 **              nothing.
944 **
945 **      Side Effects:
946 **              stores users in database.
947 */
948
949 static void
950 xclude(f)
951         SM_FILE_T *f;
952 {
953         char buf[MAXLINE], *p;
954
955         if (f == NULL)
956                 return;
957         while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf))
958         {
959                 if ((p = strchr(buf, '\n')) != NULL)
960                         *p = '\0';
961                 setreply(buf, ONLY_ONCE);
962         }
963 }
964
965 /*
966 ** SENDMESSAGE --
967 **      exec sendmail to send the vacation file to sender
968 **
969 **      Parameters:
970 **              myname -- user name.
971 **              msgfn -- name of file with vacation message.
972 **              sender -- use as sender address
973 **
974 **      Returns:
975 **              nothing.
976 **
977 **      Side Effects:
978 **              sends vacation reply.
979 */
980
981 static void
982 sendmessage(myname, msgfn, sender)
983         char *myname;
984         char *msgfn;
985         char *sender;
986 {
987         SM_FILE_T *mfp, *sfp;
988         int i;
989         int pvect[2];
990         char *pv[8];
991         char buf[MAXLINE];
992
993         mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
994         if (mfp == NULL)
995         {
996                 if (msgfn[0] == '/')
997                         msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
998                 else
999                         msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
1000                                myname, msgfn);
1001                 exit(EX_NOINPUT);
1002         }
1003         if (pipe(pvect) < 0)
1004         {
1005                 msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
1006                 exit(EX_OSERR);
1007         }
1008         pv[0] = "sendmail";
1009         pv[1] = "-oi";
1010         pv[2] = "-f";
1011         if (sender != NULL)
1012                 pv[3] = sender;
1013         else
1014                 pv[3] = myname;
1015         pv[4] = "--";
1016         pv[5] = From;
1017         pv[6] = NULL;
1018         i = fork();
1019         if (i < 0)
1020         {
1021                 msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
1022                 exit(EX_OSERR);
1023         }
1024         if (i == 0)
1025         {
1026                 (void) dup2(pvect[0], 0);
1027                 (void) close(pvect[0]);
1028                 (void) close(pvect[1]);
1029                 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1030                 (void) execv(_PATH_SENDMAIL, pv);
1031                 msglog(LOG_ERR, "vacation: can't exec %s: %s",
1032                         _PATH_SENDMAIL, sm_errstring(errno));
1033                 exit(EX_UNAVAILABLE);
1034         }
1035         /* check return status of the following calls? XXX */
1036         (void) close(pvect[0]);
1037         if ((sfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
1038                               (void *) &(pvect[1]),
1039                               SM_IO_WRONLY, NULL)) != NULL)
1040         {
1041 #if _FFR_VAC_WAIT4SM
1042 # ifdef WAITUNION
1043                 union wait st;
1044 # else /* WAITUNION */
1045                 auto int st;
1046 # endif /* WAITUNION */
1047 #endif /* _FFR_VAC_WAIT4SM */
1048
1049                 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, "To: %s\n", From);
1050                 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT,
1051                                      "Auto-Submitted: auto-replied\n");
1052                 while (sm_io_fgets(mfp, SM_TIME_DEFAULT, buf, sizeof buf))
1053                         (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf);
1054                 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1055                 (void) sm_io_close(sfp, SM_TIME_DEFAULT);
1056 #if _FFR_VAC_WAIT4SM
1057                 (void) wait(&st);
1058 #endif /* _FFR_VAC_WAIT4SM */
1059         }
1060         else
1061         {
1062                 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1063                 msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
1064                 exit(EX_UNAVAILABLE);
1065         }
1066 }
1067
1068 static void
1069 usage()
1070 {
1071         msglog(LOG_NOTICE,
1072                "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",
1073                getuid());
1074         exit(EX_USAGE);
1075 }
1076
1077 /*
1078 ** LISTDB -- list the contents of the vacation database
1079 **
1080 **      Parameters:
1081 **              none.
1082 **
1083 **      Returns:
1084 **              nothing.
1085 */
1086
1087 static void
1088 listdb()
1089 {
1090         int result;
1091         time_t t;
1092         SMDB_CURSOR *cursor = NULL;
1093         SMDB_DBENT db_key, db_value;
1094
1095         memset(&db_key, '\0', sizeof db_key);
1096         memset(&db_value, '\0', sizeof db_value);
1097
1098         result = Db->smdb_cursor(Db, &cursor, 0);
1099         if (result != SMDBE_OK)
1100         {
1101                 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1102                               "vacation: set cursor: %s\n",
1103                               sm_errstring(result));
1104                 return;
1105         }
1106
1107         while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1108                                            SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
1109         {
1110                 char *timestamp;
1111
1112                 /* skip magic VIT entry */
1113                 if (db_key.size == strlen(VIT) + 1 &&
1114                     strncmp((char *)db_key.data, VIT,
1115                             (int)db_key.size - 1) == 0)
1116                         continue;
1117
1118                 /* skip bogus values */
1119                 if (db_value.size != sizeof t)
1120                 {
1121                         sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1122                                       "vacation: %.*s invalid time stamp\n",
1123                                       (int) db_key.size, (char *) db_key.data);
1124                         continue;
1125                 }
1126
1127                 memcpy(&t, db_value.data, sizeof t);
1128
1129                 if (db_key.size > 40)
1130                         db_key.size = 40;
1131
1132                 if (t <= 0)
1133                 {
1134                         /* must be an exclude */
1135                         timestamp = "(exclusion)\n";
1136                 }
1137                 else
1138                 {
1139                         timestamp = ctime(&t);
1140                 }
1141                 sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
1142                               (int) db_key.size, (char *) db_key.data,
1143                               timestamp);
1144
1145                 memset(&db_key, '\0', sizeof db_key);
1146                 memset(&db_value, '\0', sizeof db_value);
1147         }
1148
1149         if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1150         {
1151                 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1152                               "vacation: get value at cursor: %s\n",
1153                               sm_errstring(result));
1154                 if (cursor != NULL)
1155                 {
1156                         (void) cursor->smdbc_close(cursor);
1157                         cursor = NULL;
1158                 }
1159                 return;
1160         }
1161         (void) cursor->smdbc_close(cursor);
1162         cursor = NULL;
1163 }
1164
1165 /*
1166 ** DEBUGLOG -- write message to standard error
1167 **
1168 **      Append a message to the standard error for the convenience of
1169 **      end-users debugging without access to the syslog messages.
1170 **
1171 **      Parameters:
1172 **              i -- syslog log level
1173 **              fmt -- string format
1174 **
1175 **      Returns:
1176 **              nothing.
1177 */
1178
1179 /*VARARGS2*/
1180 static SYSLOG_RET_T
1181 #ifdef __STDC__
1182 debuglog(int i, const char *fmt, ...)
1183 #else /* __STDC__ */
1184 debuglog(i, fmt, va_alist)
1185         int i;
1186         const char *fmt;
1187         va_dcl
1188 #endif /* __STDC__ */
1189
1190 {
1191         SM_VA_LOCAL_DECL
1192
1193         SM_VA_START(ap, fmt);
1194         sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);
1195         SM_VA_END(ap);
1196         SYSLOG_RET;
1197 }