2 * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
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.
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.
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")
23 SM_IDSTR(id, "@(#)$Id: vacation.c,v 8.142 2004/11/02 18:25:33 ca Exp $")
32 # undef EX_OK /* unistd.h may have another use for this */
34 #include <sm/sysexits.h>
38 #include "sendmail/sendmail.h"
39 #include <sendmail/pathnames.h>
40 #include "libsmdb/smdb.h"
42 #define ONLY_ONCE ((time_t) 0) /* send at most one reply */
43 #define INTERVAL_UNDEF ((time_t) (-1)) /* no value given */
52 bool DontInitGroups = false;
54 BITMAP256 DontBlameSendmail;
57 ** VACATION -- return a message to the sender when on vacation.
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
65 #define VDB ".vacation" /* vacation database */
66 #define VMSG ".vacation.msg" /* vacation message */
67 #define SECSPERDAY (60 * 60 * 24)
81 bool CloseMBDB = false;
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__) */
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
95 #endif /* SM_CONF_SYSLOG_INT */
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));
103 /* exit after reading input */
104 #define EXITIT(excode) \
109 sm_mbdb_terminate(); \
115 #define EXITM(excode) \
117 if (!initdb && !list) \
121 sm_mbdb_terminate(); \
132 bool alwaysrespond = false;
133 bool initdb, exclude;
134 bool runasuser = false;
136 int mfail = 0, ufail = 0;
143 char *dbfilename = NULL;
144 char *msgfilename = NULL;
147 char *returnaddr = NULL;
148 SMDB_USER_INFO user_info;
149 static char rnamebuf[MAXNAME];
150 extern int optind, opterr;
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 *));
160 /* Vars needed to link with smutil */
161 clrbitmap(DontBlameSendmail);
162 RunAsUid = RealUid = getuid();
163 RunAsGid = RealGid = getgid();
164 pw = getpwuid(RealUid);
167 if (strlen(pw->pw_name) > MAXNAME - 1)
168 pw->pw_name[MAXNAME] = '\0';
169 sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
172 sm_snprintf(rnamebuf, sizeof rnamebuf,
173 "Unknown UID %d", (int) RealUid);
174 RunAsUserName = RealUserName = rnamebuf;
177 openlog("vacation", LOG_PID, LOG_MAIL);
178 # else /* LOG_MAIL */
179 openlog("vacation", LOG_PID);
180 # endif /* LOG_MAIL */
185 interval = INTERVAL_UNDEF;
189 #define OPTIONS "a:C:df:Iijlm:R:r:s:t:Uxz"
191 while (mfail == 0 && ufail == 0 &&
192 (ch = getopt(argc, argv, OPTIONS)) != -1)
196 case 'a': /* alias */
197 cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS));
212 case 'd': /* debug mode */
216 case 'f': /* alternate database */
220 case 'I': /* backward compatible */
221 case 'i': /* init the database */
226 alwaysrespond = true;
230 list = true; /* list the database */
233 case 'm': /* alternate message file */
234 msgfilename = optarg;
242 if (isascii(*optarg) && isdigit(*optarg))
244 interval = atol(optarg) * SECSPERDAY;
249 interval = ONLY_ONCE;
252 case 's': /* alternate sender name */
253 (void) sm_strlcpy(From, optarg, sizeof From);
256 case 't': /* SunOS: -t1d (default expire) */
259 case 'U': /* run as single user mode */
283 "vacation: can't allocate memory for alias.\n");
291 if (!initdb && !list && !exclude)
293 if ((pw = getpwuid(getuid())) == NULL)
296 "vacation: no such user uid %u.\n", getuid());
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)
307 "vacation: no such directory %s.\n",
315 if (dbfilename == NULL || msgfilename == NULL)
318 "vacation: -U requires setting both -f and -m\n");
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);
329 SM_CF_OPT_T mbdbname;
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);
340 "vacation: can't open mailbox database: %s.\n",
345 err = sm_mbdb_lookup(*argv, &user);
346 if (err == EX_NOUSER)
348 msglog(LOG_ERR, "vacation: no such user %s.\n", *argv);
354 "vacation: can't read mailbox database: %s.\n",
358 name = user.mbdb_name;
359 if (chdir(user.mbdb_homedir) != 0)
362 "vacation: no such directory %s.\n",
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);
372 if (dbfilename == NULL)
374 if (msgfilename == NULL)
378 if (getegid() != getgid())
380 /* Allow a set-group-ID vacation binary */
381 RunAsGid = user_info.smdbu_group_id = getegid();
382 sff |= SFF_OPENASROOT;
386 /* Allow root to initialize user's vacation databases */
387 sff |= SFF_OPENASROOT|SFF_ROOTOK;
390 sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY;
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)
400 msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
401 sm_errstring(result));
408 (void) Db->smdb_close(Db);
412 if (interval != INTERVAL_UNDEF)
413 setinterval(interval);
415 if (initdb && !exclude)
417 (void) Db->smdb_close(Db);
424 (void) Db->smdb_close(Db);
428 if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
431 "vacation: can't allocate memory for username.\n");
432 (void) Db->smdb_close(Db);
439 result = readheaders(alwaysrespond);
440 if (result == EX_OK && !recent())
446 (void) Db->smdb_close(Db);
447 sendmessage(name, msgfilename, returnaddr);
450 (void) Db->smdb_close(Db);
451 if (result == EX_NOUSER)
457 ** EATMSG -- read stdin till EOF
471 ** read the rest of the e-mail and ignore it to avoid problems
472 ** with EPIPE in sendmail
474 while (getc(stdin) != EOF)
479 ** READHEADERS -- read mail headers
482 ** alwaysrespond -- respond regardless of whether msg is to me
485 ** a exit code: NOUSER if no reply, OK if reply, * if error
493 readheaders(alwaysrespond)
500 extern bool junkmail __P((char *));
501 extern bool nsearch __P((char *, char *));
504 tome = alwaysrespond;
505 while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) &&
510 case 'F': /* "From " */
512 if (strncmp(buf, "From ", 5) == 0)
519 /* escaped character */
526 "vacation: badly formatted \"From \" line.\n");
532 else if (*p == '\r' || *p == '\n')
534 else if (*p == ' ' && !quoted)
541 "vacation: badly formatted \"From \" line.\n");
546 /* ok since both strings have MAXLINE length */
548 (void) sm_strlcpy(From, buf + 5,
550 if ((p = strchr(buf + 5, '\n')) != NULL)
552 if (junkmail(buf + 5))
557 case 'P': /* "Precedence:" */
560 if (strlen(buf) <= 10 ||
561 strncasecmp(buf, "Precedence", 10) != 0 ||
562 (buf[10] != ':' && buf[10] != ' ' &&
565 if ((p = strchr(buf, ':')) == NULL)
567 while (*++p != '\0' && isascii(*p) && isspace(*p));
570 if (strncasecmp(p, "junk", 4) == 0 ||
571 strncasecmp(p, "bulk", 4) == 0 ||
572 strncasecmp(p, "list", 4) == 0)
576 case 'C': /* "Cc:" */
578 if (strncasecmp(buf, "Cc:", 3) != 0)
583 case 'T': /* "To:" */
585 if (strncasecmp(buf, "To:", 3) != 0)
591 if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
598 !tome && cur != NULL;
600 tome = nsearch(cur->name, buf);
607 msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
615 ** do a nice, slow, search of a string for a substring.
618 ** name -- name to search.
619 ** str -- string in which to search.
622 ** is name a substring of str?
628 register char *name, *str;
635 for (s = str; *s != '\0'; ++s)
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.
642 ** This prevents matching "eric" to "derick" while still
643 ** matching "eric" to "<eric+detail>".
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))))
657 ** read the header and return if automagic/junk/bulk/list mail
660 ** from -- sender address.
663 ** is this some automated/junk/bulk/list mail?
673 typedef struct ignore IGNORE_T;
675 #define MAX_USER_LEN 256 /* maximum length of local part (sender) */
677 /* delimiters for the local part of an address */
678 #define isdelim(c) ((c) == '%' || (c) == '@' || (c) == '+')
688 char sender[MAX_USER_LEN];
689 static IGNORE_T ignore[] =
691 { "postmaster", 10 },
693 { "mailer-daemon", 13 },
698 static IGNORE_T ignorepost[] =
706 static IGNORE_T ignorepre[] =
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:
717 ** From site!site!SENDER%site.domain%site.domain@site.domain
723 while (*e != '\0' && (quot || !isdelim(*e)))
735 /* '\\' at end of string? */
738 if (len < MAX_USER_LEN)
743 if (*e == '!' && !quot)
749 if (len < MAX_USER_LEN)
753 if (len < MAX_USER_LEN)
756 sender[MAX_USER_LEN - 1] = '\0';
762 return false; /* syntax error... */
766 for (cur = ignorepre; cur->name != NULL; ++cur)
768 if (len >= cur->len &&
769 strncasecmp(cur->name, sender, cur->len) == 0)
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
778 ** The address surely can't match any entry in ignore[]
779 ** (as long as all of them are shorter than MAX_USER_LEN).
782 if (len > MAX_USER_LEN)
785 /* test full local parts */
786 for (cur = ignore; cur->name != NULL; ++cur)
788 if (len == cur->len &&
789 strncasecmp(cur->name, sender, cur->len) == 0)
794 for (cur = ignorepost; cur->name != NULL; ++cur)
796 if (len >= cur->len &&
797 strncasecmp(cur->name, e - cur->len - 1,
804 #define VIT "__VACATION__INTERVAL__TIMER__"
808 ** find out if user has gotten a vacation message recently.
814 ** true iff user has gotten a vacation message recently.
821 SMDB_DBENT key, data;
823 bool trydomain = false;
827 memset(&key, '\0', sizeof key);
828 memset(&data, '\0', sizeof data);
830 /* get interval time */
832 key.size = sizeof(VIT);
834 st = Db->smdb_get(Db, &key, &data, 0);
836 next = SECSPERDAY * DAYSPERWEEK;
838 memmove(&next, data.data, sizeof(next));
840 memset(&data, '\0', sizeof data);
842 /* get record for this address */
844 key.size = strlen(From);
848 st = Db->smdb_get(Db, &key, &data, 0);
851 memmove(&then, data.data, sizeof(then));
852 if (next == ONLY_ONCE || then == ONLY_ONCE ||
853 then + next > time(NULL))
856 if ((trydomain = !trydomain) &&
857 (domain = strchr(From, '@')) != NULL)
860 key.size = strlen(domain);
868 ** store the reply interval
871 ** interval -- time interval for replies.
877 ** stores the reply interval in database.
881 setinterval(interval)
884 SMDB_DBENT key, data;
886 memset(&key, '\0', sizeof key);
887 memset(&data, '\0', sizeof data);
890 key.size = sizeof(VIT);
891 data.data = (char*) &interval;
892 data.size = sizeof(interval);
893 (void) (Db->smdb_put)(Db, &key, &data, 0);
898 ** store that this user knows about the vacation.
901 ** from -- sender address.
902 ** when -- last reply time.
908 ** stores user/time in database.
916 SMDB_DBENT key, data;
918 memset(&key, '\0', sizeof key);
919 memset(&data, '\0', sizeof data);
922 key.size = strlen(from);
923 data.data = (char*) &when;
924 data.size = sizeof(when);
925 (void) (Db->smdb_put)(Db, &key, &data, 0);
930 ** add users to vacation db so they don't get a reply.
933 ** f -- file pointer with list of address to exclude
939 ** stores users in database.
946 char buf[MAXLINE], *p;
950 while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf))
952 if ((p = strchr(buf, '\n')) != NULL)
954 setreply(buf, ONLY_ONCE);
960 ** exec sendmail to send the vacation file to sender
963 ** myname -- user name.
964 ** msgfn -- name of file with vacation message.
965 ** sender -- use as sender address
971 ** sends vacation reply.
975 sendmessage(myname, msgfn, sender)
980 SM_FILE_T *mfp, *sfp;
986 mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
990 msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
992 msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
998 msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
1014 msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
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);
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)
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);
1044 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1045 msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
1046 exit(EX_UNAVAILABLE);
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",
1060 ** LISTDB -- list the contents of the vacation database
1074 SMDB_CURSOR *cursor = NULL;
1075 SMDB_DBENT db_key, db_value;
1077 memset(&db_key, '\0', sizeof db_key);
1078 memset(&db_value, '\0', sizeof db_value);
1080 result = Db->smdb_cursor(Db, &cursor, 0);
1081 if (result != SMDBE_OK)
1083 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1084 "vacation: set cursor: %s\n",
1085 sm_errstring(result));
1089 while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1090 SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
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)
1100 /* skip bogus values */
1101 if (db_value.size != sizeof t)
1103 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1104 "vacation: %.*s invalid time stamp\n",
1105 (int) db_key.size, (char *) db_key.data);
1109 memcpy(&t, db_value.data, sizeof t);
1111 if (db_key.size > 40)
1116 /* must be an exclude */
1117 timestamp = "(exclusion)\n";
1121 timestamp = ctime(&t);
1123 sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
1124 (int) db_key.size, (char *) db_key.data,
1127 memset(&db_key, '\0', sizeof db_key);
1128 memset(&db_value, '\0', sizeof db_value);
1131 if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1133 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1134 "vacation: get value at cursor: %s\n",
1135 sm_errstring(result));
1138 (void) cursor->smdbc_close(cursor);
1143 (void) cursor->smdbc_close(cursor);
1148 ** DEBUGLOG -- write message to standard error
1150 ** Append a message to the standard error for the convenience of
1151 ** end-users debugging without access to the syslog messages.
1154 ** i -- syslog log level
1155 ** fmt -- string format
1164 debuglog(int i, const char *fmt, ...)
1165 #else /* __STDC__ */
1166 debuglog(i, fmt, va_alist)
1170 #endif /* __STDC__ */
1175 SM_VA_START(ap, fmt);
1176 sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);