2 * Copyright (c) 1999-2002, 2009 Proofpoint, 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-2002, 2009 Proofpoint, 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.148 2013-11-22 20:52:02 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;
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 *));
67 ** VACATION -- return a message to the sender when on vacation.
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
75 #define VDB ".vacation" /* vacation database */
76 #define VMSG ".vacation.msg" /* vacation message */
77 #define SECSPERDAY (60 * 60 * 24)
91 bool CloseMBDB = false;
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__) */
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
105 #endif /* SM_CONF_SYSLOG_INT */
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));
113 /* exit after reading input */
114 #define EXITIT(excode) \
119 sm_mbdb_terminate(); \
125 #define EXITM(excode) \
127 if (!initdb && !list) \
131 sm_mbdb_terminate(); \
142 bool alwaysrespond = false;
143 bool initdb, exclude;
144 bool runasuser = false;
146 int mfail = 0, ufail = 0;
153 char *dbfilename = NULL;
154 char *msgfilename = NULL;
157 char *returnaddr = NULL;
158 SMDB_USER_INFO user_info;
159 static char rnamebuf[MAXNAME];
160 extern int optind, opterr;
163 /* Vars needed to link with smutil */
164 clrbitmap(DontBlameSendmail);
165 RunAsUid = RealUid = getuid();
166 RunAsGid = RealGid = getgid();
167 pw = getpwuid(RealUid);
170 if (strlen(pw->pw_name) > MAXNAME - 1)
171 pw->pw_name[MAXNAME] = '\0';
172 sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
175 sm_snprintf(rnamebuf, sizeof rnamebuf,
176 "Unknown UID %d", (int) RealUid);
177 RunAsUserName = RealUserName = rnamebuf;
180 openlog("vacation", LOG_PID, LOG_MAIL);
181 # else /* LOG_MAIL */
182 openlog("vacation", LOG_PID);
183 # endif /* LOG_MAIL */
188 interval = INTERVAL_UNDEF;
192 #define OPTIONS "a:C:df:Iijlm:R:r:s:t:Uxz"
194 while (mfail == 0 && ufail == 0 &&
195 (ch = getopt(argc, argv, OPTIONS)) != -1)
199 case 'a': /* alias */
200 cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS));
215 case 'd': /* debug mode */
219 case 'f': /* alternate database */
223 case 'I': /* backward compatible */
224 case 'i': /* init the database */
229 alwaysrespond = true;
233 list = true; /* list the database */
236 case 'm': /* alternate message file */
237 msgfilename = optarg;
245 if (isascii(*optarg) && isdigit(*optarg))
247 interval = atol(optarg) * SECSPERDAY;
252 interval = ONLY_ONCE;
255 case 's': /* alternate sender name */
256 (void) sm_strlcpy(From, optarg, sizeof From);
259 case 't': /* SunOS: -t1d (default expire) */
262 case 'U': /* run as single user mode */
286 "vacation: can't allocate memory for alias.\n");
294 if (!initdb && !list && !exclude)
296 if ((pw = getpwuid(getuid())) == NULL)
299 "vacation: no such user uid %u.\n", getuid());
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)
310 "vacation: no such directory %s.\n",
317 name = strdup(*argv);
318 if (dbfilename == NULL || msgfilename == NULL)
321 "vacation: -U requires setting both -f and -m\n");
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);
332 SM_CF_OPT_T mbdbname;
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);
343 "vacation: can't open mailbox database: %s.\n",
348 err = sm_mbdb_lookup(*argv, &user);
349 if (err == EX_NOUSER)
351 msglog(LOG_ERR, "vacation: no such user %s.\n", *argv);
357 "vacation: can't read mailbox database: %s.\n",
361 name = strdup(user.mbdb_name);
362 if (chdir(user.mbdb_homedir) != 0)
365 "vacation: no such directory %s.\n",
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);
377 "vacation: can't allocate memory for username.\n");
381 if (dbfilename == NULL)
383 if (msgfilename == NULL)
387 if (getegid() != getgid())
389 /* Allow a set-group-ID vacation binary */
390 RunAsGid = user_info.smdbu_group_id = getegid();
391 sff |= SFF_OPENASROOT;
395 /* Allow root to initialize user's vacation databases */
396 sff |= SFF_OPENASROOT|SFF_ROOTOK;
399 sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY;
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)
409 msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
410 sm_errstring(result));
417 (void) Db->smdb_close(Db);
421 if (interval != INTERVAL_UNDEF)
422 setinterval(interval);
424 if (initdb && !exclude)
426 (void) Db->smdb_close(Db);
433 (void) Db->smdb_close(Db);
437 if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
440 "vacation: can't allocate memory for username.\n");
441 (void) Db->smdb_close(Db);
448 result = readheaders(alwaysrespond);
449 if (result == EX_OK && !recent())
455 (void) Db->smdb_close(Db);
456 sendmessage(name, msgfilename, returnaddr);
459 (void) Db->smdb_close(Db);
460 if (result == EX_NOUSER)
466 ** EATMSG -- read stdin till EOF
480 ** read the rest of the e-mail and ignore it to avoid problems
481 ** with EPIPE in sendmail
483 while (getc(stdin) != EOF)
488 ** READHEADERS -- read mail headers
491 ** alwaysrespond -- respond regardless of whether msg is to me
494 ** a exit code: NOUSER if no reply, OK if reply, * if error
502 readheaders(alwaysrespond)
511 tome = alwaysrespond;
512 while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) >= 0 &&
517 case 'F': /* "From " */
519 if (strncmp(buf, "From ", 5) == 0)
526 /* escaped character */
533 "vacation: badly formatted \"From \" line.\n");
539 else if (*p == '\r' || *p == '\n')
541 else if (*p == ' ' && !quoted)
548 "vacation: badly formatted \"From \" line.\n");
553 /* ok since both strings have MAXLINE length */
555 (void) sm_strlcpy(From, buf + 5,
557 if ((p = strchr(buf + 5, '\n')) != NULL)
559 if (junkmail(buf + 5))
564 case 'P': /* "Precedence:" */
567 if (strlen(buf) <= 10 ||
568 strncasecmp(buf, "Precedence", 10) != 0 ||
569 (buf[10] != ':' && buf[10] != ' ' &&
572 if ((p = strchr(buf, ':')) == NULL)
574 while (*++p != '\0' && isascii(*p) && isspace(*p));
577 if (strncasecmp(p, "junk", 4) == 0 ||
578 strncasecmp(p, "bulk", 4) == 0 ||
579 strncasecmp(p, "list", 4) == 0)
583 case 'C': /* "Cc:" */
585 if (strncasecmp(buf, "Cc:", 3) != 0)
590 case 'T': /* "To:" */
592 if (strncasecmp(buf, "To:", 3) != 0)
598 if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
605 !tome && cur != NULL;
607 tome = nsearch(cur->name, buf);
614 msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
622 ** do a nice, slow, search of a string for a substring.
625 ** name -- name to search.
626 ** str -- string in which to search.
629 ** is name a substring of str?
635 register char *name, *str;
642 for (s = str; *s != '\0'; ++s)
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.
649 ** This prevents matching "eric" to "derick" while still
650 ** matching "eric" to "<eric+detail>".
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))))
664 ** read the header and return if automagic/junk/bulk/list mail
667 ** from -- sender address.
670 ** is this some automated/junk/bulk/list mail?
680 typedef struct ignore IGNORE_T;
682 #define MAX_USER_LEN 256 /* maximum length of local part (sender) */
684 /* delimiters for the local part of an address */
685 #define isdelim(c) ((c) == '%' || (c) == '@' || (c) == '+')
695 char sender[MAX_USER_LEN];
696 static IGNORE_T ignore[] =
698 { "postmaster", 10 },
700 { "mailer-daemon", 13 },
705 static IGNORE_T ignorepost[] =
713 static IGNORE_T ignorepre[] =
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:
724 ** From site!site!SENDER%site.domain%site.domain@site.domain
730 while (*e != '\0' && (quot || !isdelim(*e)))
742 /* '\\' at end of string? */
745 if (len < MAX_USER_LEN)
750 if (*e == '!' && !quot)
756 if (len < MAX_USER_LEN)
760 if (len < MAX_USER_LEN)
763 sender[MAX_USER_LEN - 1] = '\0';
769 return false; /* syntax error... */
773 for (cur = ignorepre; cur->name != NULL; ++cur)
775 if (len >= cur->len &&
776 strncasecmp(cur->name, sender, cur->len) == 0)
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
785 ** The address surely can't match any entry in ignore[]
786 ** (as long as all of them are shorter than MAX_USER_LEN).
789 if (len > MAX_USER_LEN)
792 /* test full local parts */
793 for (cur = ignore; cur->name != NULL; ++cur)
795 if (len == cur->len &&
796 strncasecmp(cur->name, sender, cur->len) == 0)
801 for (cur = ignorepost; cur->name != NULL; ++cur)
803 if (len >= cur->len &&
804 strncasecmp(cur->name, e - cur->len - 1,
811 #define VIT "__VACATION__INTERVAL__TIMER__"
815 ** find out if user has gotten a vacation message recently.
821 ** true iff user has gotten a vacation message recently.
828 SMDB_DBENT key, data;
830 bool trydomain = false;
834 memset(&key, '\0', sizeof key);
835 memset(&data, '\0', sizeof data);
837 /* get interval time */
839 key.size = sizeof(VIT);
841 st = Db->smdb_get(Db, &key, &data, 0);
843 next = SECSPERDAY * DAYSPERWEEK;
845 memmove(&next, data.data, sizeof(next));
847 memset(&data, '\0', sizeof data);
849 /* get record for this address */
851 key.size = strlen(From);
855 st = Db->smdb_get(Db, &key, &data, 0);
858 memmove(&then, data.data, sizeof(then));
859 if (next == ONLY_ONCE || then == ONLY_ONCE ||
860 then + next > time(NULL))
863 if ((trydomain = !trydomain) &&
864 (domain = strchr(From, '@')) != NULL)
867 key.size = strlen(domain);
875 ** store the reply interval
878 ** interval -- time interval for replies.
884 ** stores the reply interval in database.
888 setinterval(interval)
891 SMDB_DBENT key, data;
893 memset(&key, '\0', sizeof key);
894 memset(&data, '\0', sizeof data);
897 key.size = sizeof(VIT);
898 data.data = (char*) &interval;
899 data.size = sizeof(interval);
900 (void) (Db->smdb_put)(Db, &key, &data, 0);
905 ** store that this user knows about the vacation.
908 ** from -- sender address.
909 ** when -- last reply time.
915 ** stores user/time in database.
923 SMDB_DBENT key, data;
925 memset(&key, '\0', sizeof key);
926 memset(&data, '\0', sizeof data);
929 key.size = strlen(from);
930 data.data = (char*) &when;
931 data.size = sizeof(when);
932 (void) (Db->smdb_put)(Db, &key, &data, 0);
937 ** add users to vacation db so they don't get a reply.
940 ** f -- file pointer with list of address to exclude
946 ** stores users in database.
953 char buf[MAXLINE], *p;
957 while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) >= 0)
959 if ((p = strchr(buf, '\n')) != NULL)
961 setreply(buf, ONLY_ONCE);
967 ** exec sendmail to send the vacation file to sender
970 ** myname -- user name.
971 ** msgfn -- name of file with vacation message.
972 ** sender -- use as sender address
978 ** sends vacation reply.
982 sendmessage(myname, msgfn, sender)
987 SM_FILE_T *mfp, *sfp;
993 mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
997 msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
999 msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
1003 if (pipe(pvect) < 0)
1005 msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
1021 msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
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);
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)
1041 #if _FFR_VAC_WAIT4SM
1044 # else /* WAITUNION */
1046 # endif /* WAITUNION */
1047 #endif /* _FFR_VAC_WAIT4SM */
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) >= 0)
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
1058 #endif /* _FFR_VAC_WAIT4SM */
1062 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1063 msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
1064 exit(EX_UNAVAILABLE);
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",
1078 ** LISTDB -- list the contents of the vacation database
1092 SMDB_CURSOR *cursor = NULL;
1093 SMDB_DBENT db_key, db_value;
1095 memset(&db_key, '\0', sizeof db_key);
1096 memset(&db_value, '\0', sizeof db_value);
1098 result = Db->smdb_cursor(Db, &cursor, 0);
1099 if (result != SMDBE_OK)
1101 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1102 "vacation: set cursor: %s\n",
1103 sm_errstring(result));
1107 while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1108 SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
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)
1118 /* skip bogus values */
1119 if (db_value.size != sizeof t)
1121 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1122 "vacation: %.*s invalid time stamp\n",
1123 (int) db_key.size, (char *) db_key.data);
1127 memcpy(&t, db_value.data, sizeof t);
1129 if (db_key.size > 40)
1134 /* must be an exclude */
1135 timestamp = "(exclusion)\n";
1139 timestamp = ctime(&t);
1141 sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
1142 (int) db_key.size, (char *) db_key.data,
1145 memset(&db_key, '\0', sizeof db_key);
1146 memset(&db_value, '\0', sizeof db_value);
1149 if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1151 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1152 "vacation: get value at cursor: %s\n",
1153 sm_errstring(result));
1156 (void) cursor->smdbc_close(cursor);
1161 (void) cursor->smdbc_close(cursor);
1166 ** DEBUGLOG -- write message to standard error
1168 ** Append a message to the standard error for the convenience of
1169 ** end-users debugging without access to the syslog messages.
1172 ** i -- syslog log level
1173 ** fmt -- string format
1182 debuglog(int i, const char *fmt, ...)
1183 #else /* __STDC__ */
1184 debuglog(i, fmt, va_alist)
1188 #endif /* __STDC__ */
1193 SM_VA_START(ap, fmt);
1194 sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);