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.137.2.2 2002/11/01 16:48:55 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)
82 #if defined(__hpux) || defined(__osf__)
83 # ifndef SM_CONF_SYSLOG_INT
84 # define SM_CONF_SYSLOG_INT 1
85 # endif /* SM_CONF_SYSLOG_INT */
86 #endif /* defined(__hpux) || defined(__osf__) */
88 #if SM_CONF_SYSLOG_INT
89 # define SYSLOG_RET_T int
90 # define SYSLOG_RET return 0
91 #else /* SM_CONF_SYSLOG_INT */
92 # define SYSLOG_RET_T void
94 #endif /* SM_CONF_SYSLOG_INT */
96 typedef SYSLOG_RET_T SYSLOG_T __P((int, const char *, ...));
97 SYSLOG_T *msglog = syslog;
98 static SYSLOG_RET_T debuglog __P((int, const char *, ...));
99 static void eatmsg __P((void));
100 static void listdb __P((void));
102 /* exit after reading input */
103 #define EXITIT(excode) \
109 #define EXITM(excode) \
111 if (!initdb && !list) \
121 bool alwaysrespond = false;
122 bool initdb, exclude;
123 bool runasuser = false;
125 int mfail = 0, ufail = 0;
132 char *dbfilename = NULL;
133 char *msgfilename = NULL;
136 char *returnaddr = NULL;
137 SMDB_USER_INFO user_info;
138 static char rnamebuf[MAXNAME];
139 extern int optind, opterr;
141 extern void usage __P((void));
142 extern void setinterval __P((time_t));
143 extern int readheaders __P((bool));
144 extern bool recent __P((void));
145 extern void setreply __P((char *, time_t));
146 extern void sendmessage __P((char *, char *, char *));
147 extern void xclude __P((SM_FILE_T *));
149 /* Vars needed to link with smutil */
150 clrbitmap(DontBlameSendmail);
151 RunAsUid = RealUid = getuid();
152 RunAsGid = RealGid = getgid();
153 pw = getpwuid(RealUid);
156 if (strlen(pw->pw_name) > MAXNAME - 1)
157 pw->pw_name[MAXNAME] = '\0';
158 sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
161 sm_snprintf(rnamebuf, sizeof rnamebuf,
162 "Unknown UID %d", (int) RealUid);
163 RunAsUserName = RealUserName = rnamebuf;
166 openlog("vacation", LOG_PID, LOG_MAIL);
167 # else /* LOG_MAIL */
168 openlog("vacation", LOG_PID);
169 # endif /* LOG_MAIL */
174 interval = INTERVAL_UNDEF;
178 #define OPTIONS "a:C:df:Iijlm:R:r:s:t:Uxz"
180 while (mfail == 0 && ufail == 0 &&
181 (ch = getopt(argc, argv, OPTIONS)) != -1)
185 case 'a': /* alias */
186 cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS));
201 case 'd': /* debug mode */
205 case 'f': /* alternate database */
209 case 'I': /* backward compatible */
210 case 'i': /* init the database */
216 alwaysrespond = true;
218 #endif /* _FFR_RESPOND_ALL */
221 list = true; /* list the database */
224 case 'm': /* alternate message file */
225 msgfilename = optarg;
232 #endif /* _FFR_RETURN_ADDR */
235 if (isascii(*optarg) && isdigit(*optarg))
237 interval = atol(optarg) * SECSPERDAY;
242 interval = ONLY_ONCE;
245 case 's': /* alternate sender name */
246 (void) sm_strlcpy(From, optarg, sizeof From);
249 case 't': /* SunOS: -t1d (default expire) */
252 case 'U': /* run as single user mode */
276 "vacation: can't allocate memory for alias.\n");
284 if (!initdb && !list && !exclude)
286 if ((pw = getpwuid(getuid())) == NULL)
289 "vacation: no such user uid %u.\n", getuid());
293 user_info.smdbu_id = pw->pw_uid;
294 user_info.smdbu_group_id = pw->pw_gid;
295 (void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
296 SMDB_MAX_USER_NAME_LEN);
297 if (chdir(pw->pw_dir) != 0)
300 "vacation: no such directory %s.\n",
308 if (dbfilename == NULL || msgfilename == NULL)
311 "vacation: -U requires setting both -f and -m\n");
314 user_info.smdbu_id = pw->pw_uid;
315 user_info.smdbu_group_id = pw->pw_gid;
316 (void) sm_strlcpy(user_info.smdbu_name, pw->pw_name,
317 SMDB_MAX_USER_NAME_LEN);
322 SM_CF_OPT_T mbdbname;
325 cfpath = getcfname(0, 0, SM_GET_SENDMAIL_CF, cfpath);
326 mbdbname.opt_name = "MailboxDatabase";
327 mbdbname.opt_val = "pw";
328 (void) sm_cf_getopt(cfpath, 1, &mbdbname);
329 err = sm_mbdb_initialize(mbdbname.opt_val);
333 "vacation: can't open mailbox database: %s.\n",
337 err = sm_mbdb_lookup(*argv, &user);
338 if (err == EX_NOUSER)
340 msglog(LOG_ERR, "vacation: no such user %s.\n", *argv);
346 "vacation: can't read mailbox database: %s.\n",
350 name = user.mbdb_name;
351 if (chdir(user.mbdb_homedir) != 0)
354 "vacation: no such directory %s.\n",
358 user_info.smdbu_id = user.mbdb_uid;
359 user_info.smdbu_group_id = user.mbdb_gid;
360 (void) sm_strlcpy(user_info.smdbu_name, user.mbdb_name,
361 SMDB_MAX_USER_NAME_LEN);
364 if (dbfilename == NULL)
366 if (msgfilename == NULL)
370 if (getegid() != getgid())
372 /* Allow a set-group-ID vacation binary */
373 RunAsGid = user_info.smdbu_group_id = getegid();
374 sff |= SFF_OPENASROOT;
378 /* Allow root to initialize user's vacation databases */
379 sff |= SFF_OPENASROOT|SFF_ROOTOK;
382 sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY;
386 result = smdb_open_database(&Db, dbfilename,
387 O_CREAT|O_RDWR | (initdb ? O_TRUNC : 0),
388 S_IRUSR|S_IWUSR, sff,
389 SMDB_TYPE_DEFAULT, &user_info, NULL);
390 if (result != SMDBE_OK)
392 msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
393 sm_errstring(result));
400 (void) Db->smdb_close(Db);
404 if (interval != INTERVAL_UNDEF)
405 setinterval(interval);
407 if (initdb && !exclude)
409 (void) Db->smdb_close(Db);
416 (void) Db->smdb_close(Db);
420 if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL)
423 "vacation: can't allocate memory for username.\n");
424 (void) Db->smdb_close(Db);
431 result = readheaders(alwaysrespond);
432 if (result == EX_OK && !recent())
438 (void) Db->smdb_close(Db);
439 sendmessage(name, msgfilename, returnaddr);
442 (void) Db->smdb_close(Db);
443 if (result == EX_NOUSER)
449 ** EATMSG -- read stdin till EOF
463 ** read the rest of the e-mail and ignore it to avoid problems
464 ** with EPIPE in sendmail
466 while (getc(stdin) != EOF)
471 ** READHEADERS -- read mail headers
474 ** alwaysrespond -- respond regardless of whether msg is to me
477 ** a exit code: NOUSER if no reply, OK if reply, * if error
485 readheaders(alwaysrespond)
492 extern bool junkmail __P((char *));
493 extern bool nsearch __P((char *, char *));
496 tome = alwaysrespond;
497 while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) &&
502 case 'F': /* "From " */
504 if (strncmp(buf, "From ", 5) == 0)
511 /* escaped character */
518 "vacation: badly formatted \"From \" line.\n");
524 else if (*p == '\r' || *p == '\n')
526 else if (*p == ' ' && !quoted)
533 "vacation: badly formatted \"From \" line.\n");
538 /* ok since both strings have MAXLINE length */
540 (void) sm_strlcpy(From, buf + 5,
542 if ((p = strchr(buf + 5, '\n')) != NULL)
544 if (junkmail(buf + 5))
549 case 'P': /* "Precedence:" */
552 if (strlen(buf) <= 10 ||
553 strncasecmp(buf, "Precedence", 10) != 0 ||
554 (buf[10] != ':' && buf[10] != ' ' &&
557 if ((p = strchr(buf, ':')) == NULL)
559 while (*++p != '\0' && isascii(*p) && isspace(*p));
562 if (strncasecmp(p, "junk", 4) == 0 ||
563 strncasecmp(p, "bulk", 4) == 0 ||
564 strncasecmp(p, "list", 4) == 0)
568 case 'C': /* "Cc:" */
570 if (strncasecmp(buf, "Cc:", 3) != 0)
575 case 'T': /* "To:" */
577 if (strncasecmp(buf, "To:", 3) != 0)
583 if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
590 !tome && cur != NULL;
592 tome = nsearch(cur->name, buf);
599 msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
607 ** do a nice, slow, search of a string for a substring.
610 ** name -- name to search.
611 ** str -- string in which to search.
614 ** is name a substring of str?
620 register char *name, *str;
627 for (s = str; *s != '\0'; ++s)
630 ** Check to make sure that the string matches and
631 ** the previous character is not an alphanumeric and
632 ** the next character after the match is not an alphanumeric.
634 ** This prevents matching "eric" to "derick" while still
635 ** matching "eric" to "<eric+detail>".
638 if (tolower(*s) == tolower(*name) &&
639 strncasecmp(name, s, len) == 0 &&
640 (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
641 (!isascii(*(s + len)) || !isalnum(*(s + len))))
649 ** read the header and return if automagic/junk/bulk/list mail
652 ** from -- sender address.
655 ** is this some automated/junk/bulk/list mail?
665 typedef struct ignore IGNORE_T;
667 #define MAX_USER_LEN 256 /* maximum length of local part (sender) */
669 /* delimiters for the local part of an address */
670 #define isdelim(c) ((c) == '%' || (c) == '@' || (c) == '+')
680 char sender[MAX_USER_LEN];
681 static IGNORE_T ignore[] =
683 { "postmaster", 10 },
685 { "mailer-daemon", 13 },
690 static IGNORE_T ignorepost[] =
698 static IGNORE_T ignorepre[] =
705 ** This is mildly amusing, and I'm not positive it's right; trying
706 ** to find the "real" name of the sender, assuming that addresses
707 ** will be some variant of:
709 ** From site!site!SENDER%site.domain%site.domain@site.domain
715 while (*e != '\0' && (quot || !isdelim(*e)))
727 /* '\\' at end of string? */
730 if (len < MAX_USER_LEN)
735 if (*e == '!' && !quot)
741 if (len < MAX_USER_LEN)
745 if (len < MAX_USER_LEN)
748 sender[MAX_USER_LEN - 1] = '\0';
754 return false; /* syntax error... */
758 for (cur = ignorepre; cur->name != NULL; ++cur)
760 if (len >= cur->len &&
761 strncasecmp(cur->name, sender, cur->len) == 0)
766 ** If the name is truncated, don't test the rest.
767 ** We could extract the "tail" of the sender address and
768 ** compare it it ignorepost, however, it seems not worth
770 ** The address surely can't match any entry in ignore[]
771 ** (as long as all of them are shorter than MAX_USER_LEN).
774 if (len > MAX_USER_LEN)
777 /* test full local parts */
778 for (cur = ignore; cur->name != NULL; ++cur)
780 if (len == cur->len &&
781 strncasecmp(cur->name, sender, cur->len) == 0)
786 for (cur = ignorepost; cur->name != NULL; ++cur)
788 if (len >= cur->len &&
789 strncasecmp(cur->name, e - cur->len - 1,
796 #define VIT "__VACATION__INTERVAL__TIMER__"
800 ** find out if user has gotten a vacation message recently.
806 ** true iff user has gotten a vacation message recently.
813 SMDB_DBENT key, data;
815 bool trydomain = false;
819 memset(&key, '\0', sizeof key);
820 memset(&data, '\0', sizeof data);
822 /* get interval time */
824 key.size = sizeof(VIT);
826 st = Db->smdb_get(Db, &key, &data, 0);
828 next = SECSPERDAY * DAYSPERWEEK;
830 memmove(&next, data.data, sizeof(next));
832 memset(&data, '\0', sizeof data);
834 /* get record for this address */
836 key.size = strlen(From);
840 st = Db->smdb_get(Db, &key, &data, 0);
843 memmove(&then, data.data, sizeof(then));
844 if (next == ONLY_ONCE || then == ONLY_ONCE ||
845 then + next > time(NULL))
848 if ((trydomain = !trydomain) &&
849 (domain = strchr(From, '@')) != NULL)
852 key.size = strlen(domain);
860 ** store the reply interval
863 ** interval -- time interval for replies.
869 ** stores the reply interval in database.
873 setinterval(interval)
876 SMDB_DBENT key, data;
878 memset(&key, '\0', sizeof key);
879 memset(&data, '\0', sizeof data);
882 key.size = sizeof(VIT);
883 data.data = (char*) &interval;
884 data.size = sizeof(interval);
885 (void) (Db->smdb_put)(Db, &key, &data, 0);
890 ** store that this user knows about the vacation.
893 ** from -- sender address.
894 ** when -- last reply time.
900 ** stores user/time in database.
908 SMDB_DBENT key, data;
910 memset(&key, '\0', sizeof key);
911 memset(&data, '\0', sizeof data);
914 key.size = strlen(from);
915 data.data = (char*) &when;
916 data.size = sizeof(when);
917 (void) (Db->smdb_put)(Db, &key, &data, 0);
922 ** add users to vacation db so they don't get a reply.
925 ** f -- file pointer with list of address to exclude
931 ** stores users in database.
938 char buf[MAXLINE], *p;
942 while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf))
944 if ((p = strchr(buf, '\n')) != NULL)
946 setreply(buf, ONLY_ONCE);
952 ** exec sendmail to send the vacation file to sender
955 ** myname -- user name.
956 ** msgfn -- name of file with vacation message.
957 ** sender -- use as sender address
963 ** sends vacation reply.
967 sendmessage(myname, msgfn, sender)
972 SM_FILE_T *mfp, *sfp;
978 mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL);
982 msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
984 msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
990 msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno));
1006 msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno));
1011 (void) dup2(pvect[0], 0);
1012 (void) close(pvect[0]);
1013 (void) close(pvect[1]);
1014 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1015 (void) execv(_PATH_SENDMAIL, pv);
1016 msglog(LOG_ERR, "vacation: can't exec %s: %s",
1017 _PATH_SENDMAIL, sm_errstring(errno));
1018 exit(EX_UNAVAILABLE);
1020 /* check return status of the following calls? XXX */
1021 (void) close(pvect[0]);
1022 if ((sfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
1023 (void *) &(pvect[1]),
1024 SM_IO_WRONLY, NULL)) != NULL)
1026 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, "To: %s\n", From);
1027 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT,
1028 "Auto-Submitted: auto-replied\n");
1029 while (sm_io_fgets(mfp, SM_TIME_DEFAULT, buf, sizeof buf))
1030 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf);
1031 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1032 (void) sm_io_close(sfp, SM_TIME_DEFAULT);
1036 (void) sm_io_close(mfp, SM_TIME_DEFAULT);
1037 msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
1038 exit(EX_UNAVAILABLE);
1045 char *retusage = "";
1046 char *respusage = "";
1048 #if _FFR_RETURN_ADDR
1049 retusage = "[-R returnaddr] ";
1050 #endif /* _FFR_RETURN_ADDR */
1052 #if _FFR_RESPOND_ALL
1053 respusage = "[-j] ";
1054 #endif /* _FFR_RESPOND_ALL */
1057 "uid %u: usage: vacation [-a alias] [-C cfpath] [-d] [-f db] [-i] %s[-l] [-m msg] %s[-r interval] [-s sender] [-t time] [-U] [-x] [-z] login\n",
1058 getuid(), respusage, retusage);
1063 ** LISTDB -- list the contents of the vacation database
1077 SMDB_CURSOR *cursor = NULL;
1078 SMDB_DBENT db_key, db_value;
1080 memset(&db_key, '\0', sizeof db_key);
1081 memset(&db_value, '\0', sizeof db_value);
1083 result = Db->smdb_cursor(Db, &cursor, 0);
1084 if (result != SMDBE_OK)
1086 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1087 "vacation: set cursor: %s\n",
1088 sm_errstring(result));
1092 while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
1093 SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
1097 /* skip magic VIT entry */
1098 if (db_key.size == strlen(VIT) + 1 &&
1099 strncmp((char *)db_key.data, VIT,
1100 (int)db_key.size - 1) == 0)
1103 /* skip bogus values */
1104 if (db_value.size != sizeof t)
1106 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1107 "vacation: %.*s invalid time stamp\n",
1108 (int) db_key.size, (char *) db_key.data);
1112 memcpy(&t, db_value.data, sizeof t);
1114 if (db_key.size > 40)
1119 /* must be an exclude */
1120 timestamp = "(exclusion)\n";
1124 timestamp = ctime(&t);
1126 sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s",
1127 (int) db_key.size, (char *) db_key.data,
1130 memset(&db_key, '\0', sizeof db_key);
1131 memset(&db_value, '\0', sizeof db_value);
1134 if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
1136 sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
1137 "vacation: get value at cursor: %s\n",
1138 sm_errstring(result));
1141 (void) cursor->smdbc_close(cursor);
1146 (void) cursor->smdbc_close(cursor);
1151 ** DEBUGLOG -- write message to standard error
1153 ** Append a message to the standard error for the convenience of
1154 ** end-users debugging without access to the syslog messages.
1157 ** i -- syslog log level
1158 ** fmt -- string format
1167 debuglog(int i, const char *fmt, ...)
1168 #else /* __STDC__ */
1169 debuglog(i, fmt, va_alist)
1173 #endif /* __STDC__ */
1178 SM_VA_START(ap, fmt);
1179 sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);