2 * Copyright (c) 1999-2000 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.
15 static char copyright[] =
16 "@(#) Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.\n\
17 All rights reserved.\n\
18 Copyright (c) 1983, 1987, 1993\n\
19 The Regents of the University of California. All rights reserved.\n\
20 Copyright (c) 1983 Eric P. Allman. All rights reserved.\n";
24 static char id[] = "@(#)$Id: vacation.c,v 8.68.4.7 2000/09/05 21:48:45 gshapiro Exp $";
33 # undef EX_OK /* unistd.h may have another use for this */
37 #include "sendmail/sendmail.h"
38 #include "libsmdb/smdb.h"
40 #if defined(__hpux) && !defined(HPUX11)
41 # undef syslog /* Undo hard_syslog conf.h change */
42 #endif /* defined(__hpux) && !defined(HPUX11) */
44 #ifndef _PATH_SENDMAIL
45 # define _PATH_SENDMAIL "/usr/lib/sendmail"
46 #endif /* ! _PATH_SENDMAIL */
48 #define ONLY_ONCE ((time_t) 0) /* send at most one reply */
49 #define INTERVAL_UNDEF ((time_t) (-1)) /* no value given */
58 bool DontInitGroups = FALSE;
60 BITMAP256 DontBlameSendmail;
63 ** VACATION -- return a message to the sender when on vacation.
65 ** This program is invoked as a message receiver. It returns a
66 ** message specified by the user to whomever sent the mail, taking
67 ** care not to return a message too often to prevent "I am on
71 #define VDB ".vacation" /* vacation database */
72 #define VMSG ".vacation.msg" /* vacation message */
73 #define SECSPERDAY (60 * 60 * 24)
83 # define __P(protos) protos
85 # define __P(protos) ()
87 # endif /* __STDC__ */
103 void (*msglog)(int, const char *, ...) = &syslog;
104 static void debuglog __P((int, const char *, ...));
105 #else /* _FFR_DEBUG */
106 # define msglog syslog
107 #endif /* _FFR_DEBUG */
109 static void eatmsg __P((void));
111 /* exit after reading input */
112 #define EXITIT(excode) { \
121 bool iflag, emptysender, exclude;
124 #endif /* _FFR_LISTDB */
125 int mfail = 0, ufail = 0;
132 char *dbfilename = VDB;
133 char *msgfilename = VMSG;
135 SMDB_USER_INFO user_info;
136 static char rnamebuf[MAXNAME];
137 extern int optind, opterr;
139 extern void usage __P((void));
140 extern void setinterval __P((time_t));
141 extern void readheaders __P((void));
142 extern bool recent __P((void));
143 extern void setreply __P((char *, time_t));
144 extern void sendmessage __P((char *, char *, bool));
145 extern void xclude __P((FILE *));
147 #define EXITM(excode) { \
148 if (!iflag && !lflag) \
152 #else /* _FFR_LISTDB */
153 #define EXITM(excode) { \
158 #endif /* _FFR_LISTDB */
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 snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
172 snprintf(rnamebuf, sizeof rnamebuf,
173 "Unknown UID %d", (int) RealUid);
174 RunAsUserName = RealUserName = rnamebuf;
177 openlog("vacation", LOG_PID, LOG_MAIL);
179 openlog("vacation", LOG_PID);
180 #endif /* LOG_MAIL */
186 interval = INTERVAL_UNDEF;
189 #if _FFR_DEBUG && _FFR_LISTDB
190 # define OPTIONS "a:df:Iilm:r:s:t:xz"
191 #else /* _FFR_DEBUG && _FFR_LISTDB */
193 # define OPTIONS "a:df:Iim:r:s:t:xz"
194 # else /* _FFR_DEBUG */
196 # define OPTIONS "a:f:Iilm:r:s:t:xz"
197 # else /* _FFR_LISTDB */
198 # define OPTIONS "a:f:Iim:r:s:t:xz"
199 # endif /* _FFR_LISTDB */
200 # endif /* _FFR_DEBUG */
201 #endif /* _FFR_DEBUG && _FFR_LISTDB */
203 while (mfail == 0 && ufail == 0 &&
204 (ch = getopt(argc, argv, OPTIONS)) != -1)
208 case 'a': /* alias */
209 cur = (ALIAS *)malloc((u_int)sizeof(ALIAS));
221 case 'd': /* debug mode */
224 #endif /* _FFR_DEBUG */
227 case 'f': /* alternate database */
231 case 'I': /* backward compatible */
232 case 'i': /* init the database */
238 lflag = TRUE; /* list the database */
240 #endif /* _FFR_LISTDB */
242 case 'm': /* alternate message file */
243 msgfilename = optarg;
247 if (isascii(*optarg) && isdigit(*optarg))
249 interval = atol(optarg) * SECSPERDAY;
254 interval = ONLY_ONCE;
257 case 's': /* alternate sender name */
258 (void) strlcpy(From, optarg, sizeof From);
261 case 't': /* SunOS: -t1d (default expire) */
284 "vacation: can't allocate memory for alias.\n");
295 #endif /* _FFR_LISTDB */
298 if ((pw = getpwuid(getuid())) == NULL)
301 "vacation: no such user uid %u.\n", getuid());
307 #else /* _FFR_BLACKBOX */
308 else if ((pw = getpwnam(*argv)) == NULL)
310 msglog(LOG_ERR, "vacation: no such user %s.\n", *argv);
314 if (chdir(pw->pw_dir) != 0)
317 "vacation: no such directory %s.\n", pw->pw_dir);
320 #endif /* _FFR_BLACKBOX */
321 user_info.smdbu_id = pw->pw_uid;
322 user_info.smdbu_group_id = pw->pw_gid;
323 (void) strlcpy(user_info.smdbu_name, pw->pw_name,
324 SMDB_MAX_USER_NAME_LEN);
328 if (getegid() != getgid())
329 RunAsGid = user_info.smdbu_group_id = getegid();
331 sff |= SFF_NOPATHCHECK|SFF_OPENASROOT;
332 #endif /* _FFR_BLACKBOX */
334 result = smdb_open_database(&Db, dbfilename,
335 O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
336 S_IRUSR|S_IWUSR, sff,
337 SMDB_TYPE_DEFAULT, &user_info, NULL);
338 if (result != SMDBE_OK)
340 msglog(LOG_NOTICE, "vacation: %s: %s\n", dbfilename,
348 static void listdb __P((void));
351 (void)Db->smdb_close(Db);
354 #endif /* _FFR_LISTDB */
356 if (interval != INTERVAL_UNDEF)
357 setinterval(interval);
361 result = Db->smdb_close(Db);
369 result = Db->smdb_close(Db);
373 if ((cur = (ALIAS *)malloc((u_int)sizeof(ALIAS))) == NULL)
376 "vacation: can't allocate memory for username.\n");
390 result = Db->smdb_close(Db);
391 sendmessage(name, msgfilename, emptysender);
394 result = Db->smdb_close(Db);
401 ** EATMSG -- read stdin till EOF
414 ** read the rest of the e-mail and ignore it to avoid problems
415 ** with EPIPE in sendmail
417 while (getc(stdin) != EOF)
422 ** READHEADERS -- read mail headers
441 extern bool junkmail __P((char *));
442 extern bool nsearch __P((char *, char *));
445 while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
449 case 'F': /* "From " */
451 if (strncmp(buf, "From ", 5) == 0)
458 /* escaped character */
465 "vacation: badly formatted \"From \" line.\n");
471 else if (*p == '\r' || *p == '\n')
473 else if (*p == ' ' && !quoted)
480 "vacation: badly formatted \"From \" line.\n");
485 /* ok since both strings have MAXLINE length */
487 (void)strlcpy(From, buf + 5,
489 if ((p = strchr(buf + 5, '\n')) != NULL)
491 if (junkmail(buf + 5))
496 case 'P': /* "Precedence:" */
499 if (strlen(buf) <= 10 ||
500 strncasecmp(buf, "Precedence", 10) != 0 ||
501 (buf[10] != ':' && buf[10] != ' ' &&
504 if ((p = strchr(buf, ':')) == NULL)
506 while (*++p != '\0' && isascii(*p) && isspace(*p));
509 if (strncasecmp(p, "junk", 4) == 0 ||
510 strncasecmp(p, "bulk", 4) == 0 ||
511 strncasecmp(p, "list", 4) == 0)
515 case 'C': /* "Cc:" */
517 if (strncasecmp(buf, "Cc:", 3) != 0)
522 case 'T': /* "To:" */
524 if (strncasecmp(buf, "To:", 3) != 0)
530 if (!isascii(*buf) || !isspace(*buf) || !cont || tome)
537 !tome && cur != NULL;
539 tome = nsearch(cur->name, buf);
546 msglog(LOG_NOTICE, "vacation: no initial \"From \" line.\n");
553 ** do a nice, slow, search of a string for a substring.
556 ** name -- name to search.
557 ** str -- string in which to search.
560 ** is name a substring of str?
565 register char *name, *str;
572 for (s = str; *s != '\0'; ++s)
575 ** Check to make sure that the string matches and
576 ** the previous character is not an alphanumeric and
577 ** the next character after the match is not an alphanumeric.
579 ** This prevents matching "eric" to "derick" while still
580 ** matching "eric" to "<eric+detail>".
583 if (tolower(*s) == tolower(*name) &&
584 strncasecmp(name, s, len) == 0 &&
585 (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) &&
586 (!isascii(*(s + len)) || !isalnum(*(s + len))))
594 ** read the header and return if automagic/junk/bulk/list mail
597 ** from -- sender address.
600 ** is this some automated/junk/bulk/list mail?
609 register struct ignore *cur;
617 { "postmaster", 10 },
619 { "mailer-daemon", 13 },
626 * This is mildly amusing, and I'm not positive it's right; trying
627 * to find the "real" name of the sender, assuming that addresses
628 * will be some variant of:
630 * From site!site!SENDER%site.domain%site.domain@site.domain
632 if ((p = strchr(from, '%')) == NULL &&
633 (p = strchr(from, '@')) == NULL)
635 if ((p = strrchr(from, '!')) != NULL)
643 for (cur = ignore; cur->name != NULL; ++cur)
645 if (len >= cur->len &&
646 strncasecmp(cur->name, p - cur->len, cur->len) == 0)
652 #define VIT "__VACATION__INTERVAL__TIMER__"
656 ** find out if user has gotten a vacation message recently.
662 ** TRUE iff user has gotten a vacation message recently.
668 SMDB_DBENT key, data;
670 bool trydomain = FALSE;
674 memset(&key, '\0', sizeof key);
675 memset(&data, '\0', sizeof data);
677 /* get interval time */
679 key.data.size = sizeof(VIT);
681 st = Db->smdb_get(Db, &key, &data, 0);
683 next = SECSPERDAY * DAYSPERWEEK;
685 memmove(&next, data.data.data, sizeof(next));
687 memset(&data, '\0', sizeof data);
689 /* get record for this address */
690 key.data.data = From;
691 key.data.size = strlen(From);
695 st = Db->smdb_get(Db, &key, &data, 0);
698 memmove(&then, data.data.data, sizeof(then));
699 if (next == ONLY_ONCE || then == ONLY_ONCE ||
700 then + next > time(NULL))
703 if ((trydomain = !trydomain) &&
704 (domain = strchr(From, '@')) != NULL)
706 key.data.data = domain;
707 key.data.size = strlen(domain);
715 ** store the reply interval
718 ** interval -- time interval for replies.
724 ** stores the reply interval in database.
727 setinterval(interval)
730 SMDB_DBENT key, data;
732 memset(&key, '\0', sizeof key);
733 memset(&data, '\0', sizeof data);
736 key.data.size = sizeof(VIT);
737 data.data.data = (char*) &interval;
738 data.data.size = sizeof(interval);
739 (void)(Db->smdb_put)(Db, &key, &data, 0);
744 ** store that this user knows about the vacation.
747 ** from -- sender address.
748 ** when -- last reply time.
754 ** stores user/time in database.
761 SMDB_DBENT key, data;
763 memset(&key, '\0', sizeof key);
764 memset(&data, '\0', sizeof data);
766 key.data.data = from;
767 key.data.size = strlen(from);
768 data.data.data = (char*) &when;
769 data.data.size = sizeof(when);
770 (void)(Db->smdb_put)(Db, &key, &data, 0);
775 ** add users to vacation db so they don't get a reply.
778 ** f -- file pointer with list of address to exclude
784 ** stores users in database.
790 char buf[MAXLINE], *p;
794 while (fgets(buf, sizeof buf, f))
796 if ((p = strchr(buf, '\n')) != NULL)
798 setreply(buf, ONLY_ONCE);
804 ** exec sendmail to send the vacation file to sender
807 ** myname -- user name.
808 ** msgfn -- name of file with vacation message.
809 ** emptysender -- use <> as sender address?
815 ** sends vacation reply.
818 sendmessage(myname, msgfn, emptysender)
828 mfp = fopen(msgfn, "r");
832 msglog(LOG_NOTICE, "vacation: no %s file.\n", msgfn);
834 msglog(LOG_NOTICE, "vacation: no ~%s/%s file.\n",
840 msglog(LOG_ERR, "vacation: pipe: %s", errstring(errno));
846 msglog(LOG_ERR, "vacation: fork: %s", errstring(errno));
851 (void) dup2(pvect[0], 0);
852 (void) close(pvect[0]);
853 (void) close(pvect[1]);
857 (void) execl(_PATH_SENDMAIL, "sendmail", "-oi",
858 "-f", myname, "--", From, NULL);
859 msglog(LOG_ERR, "vacation: can't exec %s: %s",
860 _PATH_SENDMAIL, errstring(errno));
861 exit(EX_UNAVAILABLE);
863 /* check return status of the following calls? XXX */
864 (void) close(pvect[0]);
865 if ((sfp = fdopen(pvect[1], "w")) != NULL)
867 (void) fprintf(sfp, "To: %s\n", From);
868 (void) fprintf(sfp, "Auto-Submitted: auto-generated\n");
869 while (fgets(buf, sizeof buf, mfp))
870 (void) fputs(buf, sfp);
877 msglog(LOG_ERR, "vacation: can't open pipe to sendmail");
878 exit(EX_UNAVAILABLE);
885 msglog(LOG_NOTICE, "uid %u: usage: vacation [-i] [-a alias]%s [-f db]%s [-m msg] [-r interval] [-s sender] [-t time] [-x] [-z] login\n",
889 #else /* _FFR_DEBUG */
891 #endif /* _FFR_DEBUG */
894 #else /* _FFR_LISTDB */
896 #endif /* _FFR_LISTDB */
903 ** LISTDB -- list the contents of the vacation database
917 SMDB_CURSOR *cursor = NULL;
918 SMDB_DBENT db_key, db_value;
920 memset(&db_key, '\0', sizeof db_key);
921 memset(&db_value, '\0', sizeof db_value);
923 result = Db->smdb_cursor(Db, &cursor, 0);
924 if (result != SMDBE_OK)
926 fprintf(stderr, "vacation: set cursor: %s\n",
931 while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
932 SMDB_CURSOR_GET_NEXT)) == SMDBE_OK)
934 /* skip magic VIT entry */
935 if ((int)db_key.data.size -1 == strlen(VIT) &&
936 strncmp((char *)db_key.data.data, VIT,
937 (int)db_key.data.size - 1) == 0)
940 /* skip bogus values */
941 if (db_value.data.size != sizeof t)
943 fprintf(stderr, "vacation: %.*s invalid time stamp\n",
944 (int) db_key.data.size,
945 (char *) db_key.data.data);
949 memcpy(&t, db_value.data.data, sizeof t);
951 if (db_key.data.size > 40)
952 db_key.data.size = 40;
954 printf("%-40.*s %-10s",
955 (int) db_key.data.size, (char *) db_key.data.data,
958 memset(&db_key, '\0', sizeof db_key);
959 memset(&db_value, '\0', sizeof db_value);
962 if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
964 fprintf(stderr, "vacation: get value at cursor: %s\n",
968 (void) cursor->smdbc_close(cursor);
973 (void) cursor->smdbc_close(cursor);
976 #endif /* _FFR_LISTDB */
980 ** DEBUGLOG -- write message to standard error
982 ** Append a message to the standard error for the convenience of
983 ** end-users debugging without access to the syslog messages.
986 ** i -- syslog log level
987 ** fmt -- string format
996 debuglog(int i, const char *fmt, ...)
998 debuglog(i, fmt, va_alist)
1002 #endif /* __STDC__ */
1008 vfprintf(stderr, fmt, ap);
1011 #endif /* _FFR_DEBUG */
1016 message(const char *msg, ...)
1017 #else /* __STDC__ */
1018 message(msg, va_alist)
1021 #endif /* __STDC__ */
1027 if (isascii(m[0]) && isdigit(m[0]) &&
1028 isascii(m[1]) && isdigit(m[1]) &&
1029 isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
1032 (void) vfprintf(stderr, m, ap);
1034 (void) fprintf(stderr, "\n");
1040 syserr(const char *msg, ...)
1041 #else /* __STDC__ */
1042 syserr(msg, va_alist)
1045 #endif /* __STDC__ */
1051 if (isascii(m[0]) && isdigit(m[0]) &&
1052 isascii(m[1]) && isdigit(m[1]) &&
1053 isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
1056 (void) vfprintf(stderr, m, ap);
1058 (void) fprintf(stderr, "\n");
1062 dumpfd(fd, printclosed, logit)