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.144 2007/05/11 18:50:36 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());
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",
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
= 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
);
375 if (dbfilename
== NULL
)
377 if (msgfilename
== NULL
)
381 if (getegid() != getgid())
383 /* Allow a set-group-ID vacation binary */
384 RunAsGid
= user_info
.smdbu_group_id
= getegid();
385 sff
|= SFF_OPENASROOT
;
389 /* Allow root to initialize user's vacation databases */
390 sff
|= SFF_OPENASROOT
|SFF_ROOTOK
;
393 sff
|= SFF_NOSLINK
|SFF_NOHLINK
|SFF_REGONLY
;
397 result
= smdb_open_database(&Db
, dbfilename
,
398 O_CREAT
|O_RDWR
| (initdb
? O_TRUNC
: 0),
399 S_IRUSR
|S_IWUSR
, sff
,
400 SMDB_TYPE_DEFAULT
, &user_info
, NULL
);
401 if (result
!= SMDBE_OK
)
403 msglog(LOG_NOTICE
, "vacation: %s: %s\n", dbfilename
,
404 sm_errstring(result
));
411 (void) Db
->smdb_close(Db
);
415 if (interval
!= INTERVAL_UNDEF
)
416 setinterval(interval
);
418 if (initdb
&& !exclude
)
420 (void) Db
->smdb_close(Db
);
427 (void) Db
->smdb_close(Db
);
431 if ((cur
= (ALIAS
*) malloc((unsigned int) sizeof(ALIAS
))) == NULL
)
434 "vacation: can't allocate memory for username.\n");
435 (void) Db
->smdb_close(Db
);
442 result
= readheaders(alwaysrespond
);
443 if (result
== EX_OK
&& !recent())
449 (void) Db
->smdb_close(Db
);
450 sendmessage(name
, msgfilename
, returnaddr
);
453 (void) Db
->smdb_close(Db
);
454 if (result
== EX_NOUSER
)
460 ** EATMSG -- read stdin till EOF
474 ** read the rest of the e-mail and ignore it to avoid problems
475 ** with EPIPE in sendmail
477 while (getc(stdin
) != EOF
)
482 ** READHEADERS -- read mail headers
485 ** alwaysrespond -- respond regardless of whether msg is to me
488 ** a exit code: NOUSER if no reply, OK if reply, * if error
496 readheaders(alwaysrespond
)
505 tome
= alwaysrespond
;
506 while (sm_io_fgets(smioin
, SM_TIME_DEFAULT
, buf
, sizeof(buf
)) &&
511 case 'F': /* "From " */
513 if (strncmp(buf
, "From ", 5) == 0)
520 /* escaped character */
527 "vacation: badly formatted \"From \" line.\n");
533 else if (*p
== '\r' || *p
== '\n')
535 else if (*p
== ' ' && !quoted
)
542 "vacation: badly formatted \"From \" line.\n");
547 /* ok since both strings have MAXLINE length */
549 (void) sm_strlcpy(From
, buf
+ 5,
551 if ((p
= strchr(buf
+ 5, '\n')) != NULL
)
553 if (junkmail(buf
+ 5))
558 case 'P': /* "Precedence:" */
561 if (strlen(buf
) <= 10 ||
562 strncasecmp(buf
, "Precedence", 10) != 0 ||
563 (buf
[10] != ':' && buf
[10] != ' ' &&
566 if ((p
= strchr(buf
, ':')) == NULL
)
568 while (*++p
!= '\0' && isascii(*p
) && isspace(*p
));
571 if (strncasecmp(p
, "junk", 4) == 0 ||
572 strncasecmp(p
, "bulk", 4) == 0 ||
573 strncasecmp(p
, "list", 4) == 0)
577 case 'C': /* "Cc:" */
579 if (strncasecmp(buf
, "Cc:", 3) != 0)
584 case 'T': /* "To:" */
586 if (strncasecmp(buf
, "To:", 3) != 0)
592 if (!isascii(*buf
) || !isspace(*buf
) || !cont
|| tome
)
599 !tome
&& cur
!= NULL
;
601 tome
= nsearch(cur
->name
, buf
);
608 msglog(LOG_NOTICE
, "vacation: no initial \"From \" line.\n");
616 ** do a nice, slow, search of a string for a substring.
619 ** name -- name to search.
620 ** str -- string in which to search.
623 ** is name a substring of str?
629 register char *name
, *str
;
636 for (s
= str
; *s
!= '\0'; ++s
)
639 ** Check to make sure that the string matches and
640 ** the previous character is not an alphanumeric and
641 ** the next character after the match is not an alphanumeric.
643 ** This prevents matching "eric" to "derick" while still
644 ** matching "eric" to "<eric+detail>".
647 if (tolower(*s
) == tolower(*name
) &&
648 strncasecmp(name
, s
, len
) == 0 &&
649 (s
== str
|| !isascii(*(s
- 1)) || !isalnum(*(s
- 1))) &&
650 (!isascii(*(s
+ len
)) || !isalnum(*(s
+ len
))))
658 ** read the header and return if automagic/junk/bulk/list mail
661 ** from -- sender address.
664 ** is this some automated/junk/bulk/list mail?
674 typedef struct ignore IGNORE_T
;
676 #define MAX_USER_LEN 256 /* maximum length of local part (sender) */
678 /* delimiters for the local part of an address */
679 #define isdelim(c) ((c) == '%' || (c) == '@' || (c) == '+')
689 char sender
[MAX_USER_LEN
];
690 static IGNORE_T ignore
[] =
692 { "postmaster", 10 },
694 { "mailer-daemon", 13 },
699 static IGNORE_T ignorepost
[] =
707 static IGNORE_T ignorepre
[] =
714 ** This is mildly amusing, and I'm not positive it's right; trying
715 ** to find the "real" name of the sender, assuming that addresses
716 ** will be some variant of:
718 ** From site!site!SENDER%site.domain%site.domain@site.domain
724 while (*e
!= '\0' && (quot
|| !isdelim(*e
)))
736 /* '\\' at end of string? */
739 if (len
< MAX_USER_LEN
)
744 if (*e
== '!' && !quot
)
750 if (len
< MAX_USER_LEN
)
754 if (len
< MAX_USER_LEN
)
757 sender
[MAX_USER_LEN
- 1] = '\0';
763 return false; /* syntax error... */
767 for (cur
= ignorepre
; cur
->name
!= NULL
; ++cur
)
769 if (len
>= cur
->len
&&
770 strncasecmp(cur
->name
, sender
, cur
->len
) == 0)
775 ** If the name is truncated, don't test the rest.
776 ** We could extract the "tail" of the sender address and
777 ** compare it it ignorepost, however, it seems not worth
779 ** The address surely can't match any entry in ignore[]
780 ** (as long as all of them are shorter than MAX_USER_LEN).
783 if (len
> MAX_USER_LEN
)
786 /* test full local parts */
787 for (cur
= ignore
; cur
->name
!= NULL
; ++cur
)
789 if (len
== cur
->len
&&
790 strncasecmp(cur
->name
, sender
, cur
->len
) == 0)
795 for (cur
= ignorepost
; cur
->name
!= NULL
; ++cur
)
797 if (len
>= cur
->len
&&
798 strncasecmp(cur
->name
, e
- cur
->len
- 1,
805 #define VIT "__VACATION__INTERVAL__TIMER__"
809 ** find out if user has gotten a vacation message recently.
815 ** true iff user has gotten a vacation message recently.
822 SMDB_DBENT key
, data
;
824 bool trydomain
= false;
828 memset(&key
, '\0', sizeof key
);
829 memset(&data
, '\0', sizeof data
);
831 /* get interval time */
833 key
.size
= sizeof(VIT
);
835 st
= Db
->smdb_get(Db
, &key
, &data
, 0);
837 next
= SECSPERDAY
* DAYSPERWEEK
;
839 memmove(&next
, data
.data
, sizeof(next
));
841 memset(&data
, '\0', sizeof data
);
843 /* get record for this address */
845 key
.size
= strlen(From
);
849 st
= Db
->smdb_get(Db
, &key
, &data
, 0);
852 memmove(&then
, data
.data
, sizeof(then
));
853 if (next
== ONLY_ONCE
|| then
== ONLY_ONCE
||
854 then
+ next
> time(NULL
))
857 if ((trydomain
= !trydomain
) &&
858 (domain
= strchr(From
, '@')) != NULL
)
861 key
.size
= strlen(domain
);
869 ** store the reply interval
872 ** interval -- time interval for replies.
878 ** stores the reply interval in database.
882 setinterval(interval
)
885 SMDB_DBENT key
, data
;
887 memset(&key
, '\0', sizeof key
);
888 memset(&data
, '\0', sizeof data
);
891 key
.size
= sizeof(VIT
);
892 data
.data
= (char*) &interval
;
893 data
.size
= sizeof(interval
);
894 (void) (Db
->smdb_put
)(Db
, &key
, &data
, 0);
899 ** store that this user knows about the vacation.
902 ** from -- sender address.
903 ** when -- last reply time.
909 ** stores user/time in database.
917 SMDB_DBENT key
, data
;
919 memset(&key
, '\0', sizeof key
);
920 memset(&data
, '\0', sizeof data
);
923 key
.size
= strlen(from
);
924 data
.data
= (char*) &when
;
925 data
.size
= sizeof(when
);
926 (void) (Db
->smdb_put
)(Db
, &key
, &data
, 0);
931 ** add users to vacation db so they don't get a reply.
934 ** f -- file pointer with list of address to exclude
940 ** stores users in database.
947 char buf
[MAXLINE
], *p
;
951 while (sm_io_fgets(f
, SM_TIME_DEFAULT
, buf
, sizeof buf
))
953 if ((p
= strchr(buf
, '\n')) != NULL
)
955 setreply(buf
, ONLY_ONCE
);
961 ** exec sendmail to send the vacation file to sender
964 ** myname -- user name.
965 ** msgfn -- name of file with vacation message.
966 ** sender -- use as sender address
972 ** sends vacation reply.
976 sendmessage(myname
, msgfn
, sender
)
981 SM_FILE_T
*mfp
, *sfp
;
987 mfp
= sm_io_open(SmFtStdio
, SM_TIME_DEFAULT
, msgfn
, SM_IO_RDONLY
, NULL
);
991 msglog(LOG_NOTICE
, "vacation: no %s file.\n", msgfn
);
993 msglog(LOG_NOTICE
, "vacation: no ~%s/%s file.\n",
999 msglog(LOG_ERR
, "vacation: pipe: %s", sm_errstring(errno
));
1015 msglog(LOG_ERR
, "vacation: fork: %s", sm_errstring(errno
));
1020 (void) dup2(pvect
[0], 0);
1021 (void) close(pvect
[0]);
1022 (void) close(pvect
[1]);
1023 (void) sm_io_close(mfp
, SM_TIME_DEFAULT
);
1024 (void) execv(_PATH_SENDMAIL
, pv
);
1025 msglog(LOG_ERR
, "vacation: can't exec %s: %s",
1026 _PATH_SENDMAIL
, sm_errstring(errno
));
1027 exit(EX_UNAVAILABLE
);
1029 /* check return status of the following calls? XXX */
1030 (void) close(pvect
[0]);
1031 if ((sfp
= sm_io_open(SmFtStdiofd
, SM_TIME_DEFAULT
,
1032 (void *) &(pvect
[1]),
1033 SM_IO_WRONLY
, NULL
)) != NULL
)
1035 (void) sm_io_fprintf(sfp
, SM_TIME_DEFAULT
, "To: %s\n", From
);
1036 (void) sm_io_fprintf(sfp
, SM_TIME_DEFAULT
,
1037 "Auto-Submitted: auto-replied\n");
1038 while (sm_io_fgets(mfp
, SM_TIME_DEFAULT
, buf
, sizeof buf
))
1039 (void) sm_io_fputs(sfp
, SM_TIME_DEFAULT
, buf
);
1040 (void) sm_io_close(mfp
, SM_TIME_DEFAULT
);
1041 (void) sm_io_close(sfp
, SM_TIME_DEFAULT
);
1045 (void) sm_io_close(mfp
, SM_TIME_DEFAULT
);
1046 msglog(LOG_ERR
, "vacation: can't open pipe to sendmail");
1047 exit(EX_UNAVAILABLE
);
1055 "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",
1061 ** LISTDB -- list the contents of the vacation database
1075 SMDB_CURSOR
*cursor
= NULL
;
1076 SMDB_DBENT db_key
, db_value
;
1078 memset(&db_key
, '\0', sizeof db_key
);
1079 memset(&db_value
, '\0', sizeof db_value
);
1081 result
= Db
->smdb_cursor(Db
, &cursor
, 0);
1082 if (result
!= SMDBE_OK
)
1084 sm_io_fprintf(smioerr
, SM_TIME_DEFAULT
,
1085 "vacation: set cursor: %s\n",
1086 sm_errstring(result
));
1090 while ((result
= cursor
->smdbc_get(cursor
, &db_key
, &db_value
,
1091 SMDB_CURSOR_GET_NEXT
)) == SMDBE_OK
)
1095 /* skip magic VIT entry */
1096 if (db_key
.size
== strlen(VIT
) + 1 &&
1097 strncmp((char *)db_key
.data
, VIT
,
1098 (int)db_key
.size
- 1) == 0)
1101 /* skip bogus values */
1102 if (db_value
.size
!= sizeof t
)
1104 sm_io_fprintf(smioerr
, SM_TIME_DEFAULT
,
1105 "vacation: %.*s invalid time stamp\n",
1106 (int) db_key
.size
, (char *) db_key
.data
);
1110 memcpy(&t
, db_value
.data
, sizeof t
);
1112 if (db_key
.size
> 40)
1117 /* must be an exclude */
1118 timestamp
= "(exclusion)\n";
1122 timestamp
= ctime(&t
);
1124 sm_io_fprintf(smioout
, SM_TIME_DEFAULT
, "%-40.*s %-10s",
1125 (int) db_key
.size
, (char *) db_key
.data
,
1128 memset(&db_key
, '\0', sizeof db_key
);
1129 memset(&db_value
, '\0', sizeof db_value
);
1132 if (result
!= SMDBE_OK
&& result
!= SMDBE_LAST_ENTRY
)
1134 sm_io_fprintf(smioerr
, SM_TIME_DEFAULT
,
1135 "vacation: get value at cursor: %s\n",
1136 sm_errstring(result
));
1139 (void) cursor
->smdbc_close(cursor
);
1144 (void) cursor
->smdbc_close(cursor
);
1149 ** DEBUGLOG -- write message to standard error
1151 ** Append a message to the standard error for the convenience of
1152 ** end-users debugging without access to the syslog messages.
1155 ** i -- syslog log level
1156 ** fmt -- string format
1165 debuglog(int i
, const char *fmt
, ...)
1166 #else /* __STDC__ */
1167 debuglog(i
, fmt
, va_alist
)
1171 #endif /* __STDC__ */
1176 SM_VA_START(ap
, fmt
);
1177 sm_io_vfprintf(smioerr
, SM_TIME_DEFAULT
, fmt
, ap
);