/*
* Copyright (c) 1983 Eric P. Allman
*
* Copyright (c) 1983, 1987, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Modifications from the original:
*
* Copyright (c) 2001, 2002 Greg A. Woods <woods@planix.com>
*/
#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1983, 1987, 1993\n\
The Regents of the University of California. All rights reserved.\n\
@(#) Copyright (c) 2002 Greg A. Woods <woods@planix.com>\n");
#endif /* not lint */
#ifndef lint
__RCSID("@(#)vacation: :vacation.c,v 1.6 2002/02/12 20:48:45 woods Exp ");
# if 0
static char sccsid[] = "@(#)vacation.c 8.2 (Berkeley) 1/26/94";
# endif
#endif /* not lint */
#include <sys/param.h>
#include <sys/stat.h>
#include <ctype.h>
#include <db.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#ifdef __FreeBSD__
# include </usr/src/lib/libc/stdtime/tzfile.h> /* you lose! */
#else
# include <tzfile.h>
#endif
#include <unistd.h>
#include <assert.h>
#include <sysexits.h> /* we are a mail application */
#include "rfc822.h"
#include <rfc2822.h>
#include "vacation.h"
/*
* VACATION -- a mail agent that replies on your behalf
*
* This program reads an incoming e-mail message in RFC-2822 format (with
* optional Unix mailbox separator prepended) on its standard input. It
* replies with a message specified by the user to whomever sent the mail,
* taking care not to return a message too often to prevent "I am on vacation"
* loops; as well as avoiding replying to other auto-repsonders and mailing
* list mail, etc. Instances of the string "$SUBJECT" in any headers of the
* user-supplied reply message are replaced with the content of the "subject:"
* header from the incoming messsage.
*
* TODO:
*
* Make sure any error recording the correspondent address in the db file
* causes an error exit so that no reply is sent if it cannot be recorded.
*
* Also make sure any actual error reading the db file to test a correspondent
* address is detected and will cause an error exit to prevent a reply from
* being sent.
*
* Allow aliases ('-a' optargs) to be REs.
*
* Add a command-line option to allow the user to specify a (list of) header
* fields (body and/or contents, substring, exact, or RE?) that should be used
* to mark messages that should not be responded to.
*
* Multiple "login" parameters should be allowed (and treated like '-a').
*
* There should be a very simple way to create a default ~/.vacation.msg file
* from a template, and this template should be created automatically and be
* used if the message file was not created by the user.
*
* Properly re-fold the headers of the outgoing messsage.
*/
#define PATH_VDB ".vacation.db" /* db's database */
#define PATH_VMSG ".vacation.msg" /* vacation message */
#define VIT_KEY "__VACATION__INTERVAL__TIMER__" /* magic db key, incl. NUL */
DB *db = NULL;
const char *argv0 = "vacation";
int debug = 0;
const char *vdbfilename = PATH_VDB;
char *vdbdir = NULL;
const char *vmsgfilename = PATH_VMSG;
const char * const version_id = " ";
int main __P((int, char **));
mbxlst_t *mbox_alloc __P((char *));
void dbclose __P((void));
void list_db __P((void));
void preload_db __P((void));
int isjunkmail __P((char *));
int isautoresponder __P((char *));
mbxlst_t *readheaders __P((mbxlst_t *, char **, char **, char **, int *));
void tossbody __P((void));
int isrecent __P((char *));
int sendmessage __P((const char *, char *, char *));
int istome __P((const char *, mbxlst_t *));
void setfrom __P((char *, char *));
void setinterval __P((time_t, int));
void saverecent __P((char *, size_t, time_t));
void usage __P((const char *));
extern char *optarg;
extern int optind;
extern int optopt;
extern int opterr;
extern int optreset;
int
main(argc, argv)
int argc;
char *argv[];
{
mbxlst_t *aliases = NULL; /* me and all my aliases.... */
mbxlst_t *from = NULL; /* names from "reply-to", or "from" */
char *returnpath = NULL; /* address from "return-path" header */
char *precedence = NULL; /* the "precedence" header contents */
char *subject = NULL; /* the "subject" header contents */
int tome = 0; /* was the message addressed to me? */
struct passwd *pw;
time_t now;
time_t interval;
int ch;
int init_db = 0;
int do_list_db = 0;
int do_preload_db = 0;
int zflag = 0;
mbxlst_t *cur;
argv0 = (argv0 = strrchr(argv[0], '/')) ? argv0 + 1 : argv[0];
interval = DAYSPERWEEK * SECSPERDAY;
opterr = 0; /* don't let getopt() print errors */
/*
* We need this right away [usage() calls syslog()], but we might
* change it if certain command-line options are given....
*/
openlog(argv0, 0, LOG_MAIL);
while ((ch = getopt(argc, argv, "a:f:dIilm:r:Vxz")) != -1) {
# define UNKNWN_MSG "unknown option flag: '-?'"
# define UNKNWN_MSG_OPT_LOC (sizeof(UNKNWN_MSG) - 3) /* i.e. the question mark */
char unknwn_msg[sizeof(UNKNWN_MSG)];
switch ((char) ch) {
case 'a':
cur = mbox_alloc(optarg);
cur->next = aliases;
aliases = cur;
break;
case 'd':
++debug;
closelog();
openlog(argv0, LOG_PERROR, LOG_MAIL);
break;
case 'f':
vdbfilename = optarg;
if (*vdbfilename == '/') {
usage("db not relative pathname!");
}
break;
case 'I': /* backward compatible */
case 'i': /* init the database */
init_db = 1;
break;
case 'l':
do_list_db = 1;
closelog();
openlog(argv0, LOG_PERROR, LOG_USER); /* should just do stderr? */
break;
case 'm':
vmsgfilename = optarg;
break;
case 'r':
if (strncasecmp(optarg, "inf", (size_t) 3) == 0) {
interval = (time_t) ~0; /* well, not really infinity! */
} else if (isdigit((int) *optarg)) {
interval = atol(optarg) * SECSPERDAY;
if (interval <= 0) { /* don't allow zero! */
usage("interval not greater than zero!");
}
} else {
usage("interval not a number");
}
break;
case 'V':
;
break;
case 'x':
do_preload_db = 1;
break;
case 'z': /* we'll leave this undocumented.... */
zflag = 1;
break;
case '?':
default:
strcpy(unknwn_msg, UNKNWN_MSG);
unknwn_msg[UNKNWN_MSG_OPT_LOC] = optopt;
usage(unknwn_msg);
}
}
argc -= optind;
argv += optind;
if (argc > 1 ||
(argc == 1 && (init_db || do_preload_db || do_list_db)) ||
(init_db && do_list_db) ||
(do_preload_db && do_list_db)) {
usage("invalid combination of parameters"); /* silly to allow these combinations... */
}
if (!argc) {
if (!(pw = getpwuid(getuid()))) {
syslog(LOG_ERR, "no such user uid %u.", (u_int) getuid());
exit(EX_NOUSER);
/* NOTREACHED */
}
} else if (!(pw = getpwnam(*argv))) {
syslog(LOG_ERR, "no such user %s, as given on command-line", *argv);
exit(EX_NOUSER);
/* NOTREACHED */
}
cur = mbox_alloc(pw->pw_name);
cur->next = aliases;
aliases = cur;
vdbdir = safe_strdup(pw->pw_dir);
if (chdir(vdbdir)) {
syslog(LOG_NOTICE, "could not chdir(%s), home for %s: %m.", vdbdir, pw->pw_name);
exit(1);
/* NOTREACHED */
}
db = dbopen(vdbfilename, (O_RDWR | O_CREAT | (init_db ? O_TRUNC : 0)),
(S_IRUSR | S_IWUSR),
DB_HASH,
(void *) NULL);
if (!db) {
syslog(LOG_NOTICE, "dbopen(%s/%s): %m.", vdbdir, vdbfilename);
exit(EX_NOINPUT);
/* NOTREACHED */
}
if (atexit(dbclose) == -1) {
syslog(LOG_NOTICE, "atexit(dbclose): %m.");
if ((db->close)(db) == -1) {
syslog(LOG_NOTICE, "dbclose(%s/%s): %m.", vdbdir, vdbfilename);
}
exit(EX_OSERR);
/* NOTREACHED */
}
if (!do_list_db) {
setinterval(interval, init_db);
}
if (do_preload_db) {
preload_db();
}
if (do_list_db) {
list_db();
}
if (init_db || do_preload_db || do_list_db) {
exit(0); /* we're done */
/* NOTREACHED */
}
rfc2822_clear_done_headers();
if (debug > 1) {
syslog(LOG_DEBUG, "Reading message...");
}
from = readheaders(aliases, &returnpath, &precedence, &subject, &tome);
tossbody(); /* so mailer doesn't get SIGPIPE */
if (!from) {
syslog(LOG_NOTICE, "Could not find any reply address.");
exit(EX_PROTOCOL);
/* NOTREACHED */
}
if (!tome) {
if (debug) {
syslog(LOG_DEBUG, "Ignoring mail not addressed to me from %s.",
from ? from->mailbox : "(nobody)");
}
exit(0);
/* NOTREACHED */
}
if (isjunkmail(precedence)) {
if (debug) {
syslog(LOG_DEBUG, "ignoring precedence '%s' mail.", precedence);
}
exit(0);
/* NOTREACHED */
}
if (isautoresponder(returnpath)) {
exit(0);
/* NOTREACHED */
}
(void) time(&now);
for (cur = from; cur; cur = cur->next) {
if (cur->start_group || !cur->mailbox) { /* start or end of group sub-list */
continue;
}
if (isautoresponder(cur->mailbox)) {
continue;
}
if (!isrecent(cur->mailbox)) {
saverecent(cur->mailbox, strlen(cur->mailbox), now);
/* XXX error check? */
(void) sendmessage(zflag ? "<>" : pw->pw_name, cur->mailbox, subject);
} else if (debug) {
syslog(LOG_DEBUG, "Have sent a reply to %s recently.", cur->mailbox);
}
}
exit(0);
/* NOTREACHED */
}
void *
safe_malloc(size_t len)
{
void *np;
if (len == 0) {
return NULL;
}
if (!(np = malloc(len))) {
syslog(LOG_NOTICE, "malloc() failed [for UID %d]: %m", getuid());
exit(EX_OSERR);
/* NOTREACHED */
}
return np;
}
void *
safe_calloc(size_t nelem, size_t size)
{
void *np;
if (!nelem || !size) {
return NULL;
}
if (!(np = calloc(nelem, size))) {
syslog(LOG_NOTICE, "calloc() failed [for UID %d]: %m", getuid());
exit(EX_OSERR);
/* NOTREACHED */
}
return np;
}
void
safe_free(void **p)
{
if (*p) {
free(*p);
*p = 0;
}
return;
}
char *
safe_strdup(str)
const char *str;
{
char *retval = strdup(str);
if (!retval) {
syslog(LOG_NOTICE, "strdup() failed [for UID %d]: %m", getuid());
exit(EX_OSERR);
/* NOTREACHED */
}
return retval;
}
/*
* find the beginning of the string not containing any chars in charset
*/
char *
strrchrs(start, end, charset)
char *start;
char *end;
const char *charset;
{
char *p;
const char *spanp;
char c, sc;
/*
* Walking backwards from 'end' to 'start', stop as soon as we find any
* character from charset.
*/
for (p = (char *) end; p > start;) {
c = *p--;
spanp = charset;
do {
if ((sc = *spanp++) == c) {
return p + 1;
}
} while (sc != '\0');
}
return start;
}
/*
* Allocate a single element of an alias list, initialize its name field to the
* passed name, make it the head of an empty list, and return a pointer to it.
*/
mbxlst_t *
mbox_alloc(nm)
char *nm; /* new name */
{
mbxlst_t *np;
np = rfc822_new_addr();
np->next = NULL;
np->mailbox = safe_strdup(nm);
return np;
}
/*
* dbclose() -- close the db, complaining if there's an error doing so....
*
* registered by atexit() to be called by exit().
*/
void
dbclose()
{
if (db && (db->close)(db) == -1) {
syslog(LOG_NOTICE, "dbclose(%s/%s): %m.", vdbdir, vdbfilename);
}
return;
}
/*
* list_db() --
*
* list the contents of the recent correspondents database
*/
void
list_db()
{
DBT key, data;
int rv;
time_t t;
char *user;
size_t user_sz;
/* if you don't have getpagesize then #define getpagesize() BUFSIZ */
user_sz = getpagesize();
if (!(user = (char *) malloc(user_sz))) {
fprintf(stderr, "malloc(user, %lu) failed: '%m'\n", (unsigned long) user_sz);
(void) (db->close)(db);
exit(EX_OSERR);
/* NOTREACHED */
}
while ((rv = (db->seq)(db, &key, &data, R_NEXT)) == 0) {
/* ignore the interval definition entry */
if (memcmp(key.data, VIT_KEY, MIN(key.size, sizeof(VIT_KEY))) == 0) {
if (debug) {
/* use memmove() not assignment for machines with alignment restrictions */
memmove(&t, data.data, sizeof(t));
fprintf(stderr, "read interval record: %lu seconds\n", (unsigned long) t);
}
continue;
}
if ((key.size + 1) >= user_sz) {
user_sz = ((key.size + 1) / getpagesize()) + getpagesize();
if (!(user = (char *) realloc(user, user_sz))) {
fprintf(stderr, "realloc(user, %lu) failed: '%m'\n", (unsigned long) user_sz);
(void) (db->close)(db);
exit(EX_OSERR); /* XXX do we really have to? */
/* NOTREACHED */
}
}
memmove(user, key.data, key.size);
user[key.size] = '\0'; /* just to be safe... */
if (data.size != sizeof(t)) {
fprintf(stderr, "%s: key %s: invalid data size: %lu", vdbfilename, user, (unsigned long) data.size);
continue;
}
/* use memmove() not assignment for machines with alignment restrictions */
memmove(&t, data.data, sizeof(t));
printf("%-54s ", user); /* 80 - strlen(ctime()) */
if (t == ~0) {
puts("preloaded (never expires)");
} else {
fputs(ctime(&t), stdout);
}
}
if (rv == -1) {
fprintf(stderr, "%s: error reading database: %m", vdbfilename);
}
free(user);
return;
}
/*
* preload_db() --
*
* Pre-load the recent correspondents database with values read from stdin.
*/
void
preload_db()
{
char *buf;
size_t len;
for (;;) {
if (!(buf = fgetln(stdin, &len))) {
if (ferror(stdin)) {
fprintf(stderr, "preload_db: fgetln() failed: '%m'");
}
return;
}
if (*buf == '\n') { /* XXX && len == 1 */
continue;
}
if (*buf == '#') { /* XXX && len == 1 */
continue;
}
if (buf[len - 1] == '\n') {
--len;
}
saverecent(buf, len, (time_t) LONG_MAX);
}
}
/*
* readheaders() --
*
* Read mail headers, setting pointers to the coalesced contents of the
* relevant ones, and NULL for those that are not found.
*
* The return value is set to a list of addresses as per the RFC-2822 algorithm
* for searching for appropriate reply addresses.
*
* XXX think harder about whether or not [resent-]sender: header should be
* used. Normally we want to reply in the way that was suggested (i.e. with
* the reply-to: over-riding the from:). However when [resent-]sender: is used
* properly the person who actually sent the message may _also_ want to see our
* response.
*
* XXX always returns the value of the last of any duplicate headers, except
* the "from: header, which is always that of the first found, unless there's a
* "reply-to" header, in which case it's the last one found! Phew!
*/
mbxlst_t *
readheaders(aliases, returnpathp, precedencep, subjectp, tomep)
mbxlst_t *aliases; /* all my possible addresses */
char **returnpathp; /* "return-path:" */
char **precedencep; /* "precedence:" */
char **subjectp; /* "subject:" */
int *tomep; /* is it to me? */
{
char *header;
char *colon; /* pointer to ':' in header */
char *to = NULL;
char *cc = NULL;
char *from = NULL;
char *reply_to = NULL;
char *resent_to = NULL;
char *resent_cc = NULL;
char *resent_from = NULL;
char *resent_reply_to = NULL;
char *p;
assert(returnpathp);
*returnpathp = NULL;
assert(precedencep);
*precedencep = NULL;
assert(subjectp);
*subjectp = NULL;
assert(tomep);
*tomep = 0;
/*
* XXX we should probably choose only the first header seen, not the
* last (or should we?)
*/
while ((header = rfc2822_gethfield(stdin, &colon, FALSE))) {
if (!colon) {
syslog(LOG_DEBUG, "Read non-RFC-2822 header: '%s'", header);
continue;
}
if (debug) {
syslog(LOG_DEBUG, "Read header: '%s'", header);
}
if ((p = rfc2822_ishfield(header, colon, "return-path"))) {
if (*returnpathp && debug) {
syslog(LOG_DEBUG, "Another 'return-path' header overrides the previous: '%s'", p);
}
*returnpathp = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "from"))) {
if (from && debug) {
syslog(LOG_DEBUG, "Another 'from' header overrides the previous: '%s'", p);
}
from = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "reply-to"))) {
if (reply_to && debug) {
syslog(LOG_DEBUG, "Another 'reply-to' header overrides the previous: '%s'", p);
}
reply_to = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "subject"))) {
if (*subjectp && debug) {
syslog(LOG_DEBUG, "Another 'subject' header overrides the previous: '%s'", p);
}
*subjectp = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "to"))) {
if (to && debug) {
syslog(LOG_DEBUG, "Another 'to' header overrides the previous: '%s'", p);
}
to = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "cc"))) {
if (cc && debug) {
syslog(LOG_DEBUG, "Another 'cc' header overrides the previous: '%s'", p);
}
cc = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "resent-to"))) {
if (resent_to && debug) {
syslog(LOG_DEBUG, "Another 'resent-to' header overrides the previous: '%s'", p);
}
resent_to = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "resent-cc"))) {
if (resent_cc && debug) {
syslog(LOG_DEBUG, "Another 'resent-cc' header overrides the previous: '%s'", p);
}
resent_cc = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "resent-from"))) {
if (resent_to && debug) {
syslog(LOG_DEBUG, "Another 'resent-to' header overrides the previous: '%s'", p);
}
resent_to = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "resent-reply-to"))) { /* RFC-2822 obsolete */
if (resent_reply_to && debug) {
syslog(LOG_DEBUG, "Another 'resent_reply_to' header overrides the previous: '%s'", p);
}
resent_reply_to = p;
continue;
}
if ((p = rfc2822_ishfield(header, colon, "precedence"))) {
if (*precedencep && debug) {
syslog(LOG_DEBUG, "Another 'precedence' header overrides the previous: '%s'", p);
}
*precedencep = p;
continue;
}
}
if (!header && rfc2822_errno != RFC2822_OK) {
syslog(LOG_INFO, "Header error: %s\n", rfc2822_strerror(rfc2822_errno));
}
if (resent_cc || resent_to) {
if (resent_cc) {
*tomep += istome(resent_cc, aliases);
}
if (resent_to) {
*tomep += istome(resent_to, aliases);
}
if (resent_from) {
from = resent_from;
}
if (resent_reply_to) {
from = resent_reply_to; /* resent-reply-to: over-rides resent-from: */
}
} else {
if (to) {
*tomep += istome(to, aliases);
}
if (cc) {
*tomep += istome(cc, aliases);
}
if (reply_to) {
from = reply_to; /* reply-to: over-rides from: */
}
}
return rfc822_parse_addrlist((mbxlst_t *) NULL, from);
}
/*
* tossbody() -- bit-bucket the remainder of stdin
*
* we do this so that mailers don't have to decide whether getting a SIGPIPE
* while writing to the pipe we read from is a bad thing or not....
*
* All errors reading from the MTA are ignored too as by now all the
* information necessary has been gathered from the headers.
*/
void
tossbody()
{
char junk[BUFSIZ];
while (fread(junk, sizeof(junk), (size_t) 1, stdin) > 0) {
;
}
return;
}
/*
* istome() --
*
* For now we just assume that the address will either have an '@' separator or
* fully match the alias. This will fail badly for route addresses. We really
* should parse out just the mailbox part, but our rfc822 parser library
* doesn't yet have such a capability....
*/
int
istome(hdr, aliases)
const char *hdr; /* header contents */
mbxlst_t *aliases;
{
mbxlst_t *addressees;
mbxlst_t *cur;
mbxlst_t *np;
addressees = rfc822_parse_addrlist((mbxlst_t *) NULL, hdr);
/* for every alias... */
for (cur = aliases; cur; cur = cur->next) {
if (cur->start_group || !cur->mailbox) { /* start or end of group sub-list */
continue;
}
/* check for a match with any address in 'hdr' */
for (np = addressees; np; np = np->next) {
char at;
char *atp;
char *mbox = safe_strdup(np->mailbox);
atp = rfc822_end_of_mbox(mbox);
at = *atp;
*atp = '\0';
if (debug > 1) {
syslog(LOG_DEBUG, "istome(): checking alias '%s' against mailbox '%s' found in address '%s'", cur->mailbox, mbox, np->mailbox);
}
if (strcasecmp(cur->mailbox, mbox) == 0) {
if (debug) {
syslog(LOG_DEBUG, "istome(): found alias '%s' in '%s'", cur->mailbox, hdr);
}
*atp = at; /* we don't have to, but what the heck.... */
safe_free((void **)&mbox);
rfc822_free_addr_list(&addressees);
return 1; /* any match is good enough! */
}
*atp = at; /* we don't have to, but what the heck.... */
safe_free((void **) &mbox);
}
}
rfc822_free_addr_list(&addressees);
if (debug) {
syslog(LOG_DEBUG, "istome(): did not find anything useful in '%s'", hdr);
}
return 0;
}
/*
* isjunkmail() --
*
* return (1) if mail seems to be from an auto-responder, or is listed with a
* precedence that indicates it should not recieve a personal response.
*/
int
isjunkmail(precedence)
char *precedence; /* precedence: contents */
{
/*
* XXX what about multiple values in precedence? We only test the
* first one for now...
*/
if (precedence) {
if (strncasecmp(precedence, "junk", (size_t) 4) == 0 ||
strncasecmp(precedence, "bulk", (size_t) 4) == 0 ||
strncasecmp(precedence, "list", (size_t) 4) == 0) {
return 1;
}
}
return 0;
}
/*
* isautoresponder() --
*
* Test an address to see if it's from a well-known auto-responder address,
* i.e. something that is in effect generating a response to us, such as a
* bounce message or mailing list administrative reply, etc....
*/
int
isautoresponder(from)
char *from;
{
static struct ignore {
const char *i_name;
size_t i_len;
} ignore_senders[] = {
#define S_ENTRY(str) { str, (sizeof(str) - 1) }
S_ENTRY("-request"), /* usually mailing lists */
S_ENTRY("mailer-daemon"), /* usually a bounce */
S_ENTRY("listserv"), /* mailing list manager program */
S_ENTRY("mailer"), /* XXX ???? */
S_ENTRY("-relay"), /* XXX ???? */
S_ENTRY("-outgoing"), /* XXX some mailing lists */
#undef S_ENTRY
{ NULL, 0 }
};
struct ignore *cur;
size_t len;
char *p;
if (!from) {
return 0;
}
/*
* Check if the _prefix_ of the address matches... some mailing lists
* use this more arcane owner address format, particularly that most
* broken MLM, Lsoft's LISTSERV (as of the last inspection of it).
*/
if (strncmp(from, "owner-", sizeof("owner-") - 1) == 0) {
if (debug) {
syslog(LOG_DEBUG, "isautoresponder(): a mailing list owner '%s'", from);
}
return 1;
}
/*
* now test to see if the _suffix_ of the mailbox name matches any of
* the strings given in ignore_senders
*/
p = rfc822_end_of_mbox(from);
len = p - from;
for (cur = ignore_senders; cur->i_name; ++cur) {
if (len >= cur->i_len && strncasecmp(cur->i_name, p - cur->i_len, cur->i_len) == 0) {
if (debug) {
syslog(LOG_DEBUG, "isautoresponder(%s): matches '%s'", from, cur->i_name);
}
return 1;
}
}
return 0;
}
/*
* isrecent() --
*
* find out if a reply message was sent to the specified address recently.
*
* uses memmove() instead of assignment of data field to the time_t variables
* in order to accomodate machines with alignment restrictions
*/
int
isrecent(from)
char *from;
{
DBT key;
DBT data;
time_t then; /* last time sent... */
time_t delay; /* time to wait... */
time_t now = time((time_t *) NULL);
char *domain;
char vit[] = VIT_KEY; /* discard const using array */
/* get interval time */
key.data = vit;
key.size = sizeof(VIT_KEY); /* include the NUL */
if (!vit || (db->get)(db, &key, &data, 0) == -1) {
delay = DAYSPERWEEK * SECSPERDAY; /* the default will do */
} else {
memmove(&delay, data.data, sizeof(delay));
}
/*
* if '-r inifinity' was given to initialise the database then always
* claim the address was recently used...
*/
if (delay == (time_t) LONG_MAX) {
return 1;
}
/* get record for this address */
key.data = from;
key.size = strlen(from);
if (!(db->get)(db, &key, &data, 0)) {
memmove(&then, data.data, sizeof(then));
if (then + delay > now) {
return 1;
}
}
/* get record for the domain of this address */
if (!(domain = strchr(from, '@'))) {
return 0;
}
key.data = domain;
key.size = strlen(key.data);
if (!(db->get)(db, &key, &data, 0)) {
memmove(&then, data.data, sizeof(then));
if (then + delay > now) {
return 1;
}
}
return 0;
}
/*
* setinterval() --
*
* store the reply interval under the special key.
*/
void
setinterval(interval, init_db)
time_t interval;
int init_db; /* first time? */
{
DBT key, data;
char vit[] = VIT_KEY; /* discard const using array */
key.data = vit;
key.size = sizeof(VIT_KEY); /* include the NUL! */
data.data = &interval;
data.size = sizeof(interval);
(void) (db->put)(db, &key, &data, 0);
if (debug) {
syslog(LOG_DEBUG, "setinterval(): have %s ~/%s interval at %lu seconds",
init_db ? "initialized" : "reset",
PATH_VDB,
(unsigned long) interval);
}
return;
}
/*
* saverecent() --
*
* store that this user knows about the vacation.
*/
void
saverecent(from, len, tr)
char *from; /* sender's address */
size_t len; /* length of from */
time_t tr; /* time address received */
{
DBT key, data;
key.data = from;
key.size = len; /* don't include NUL! */
data.data = &tr;
data.size = sizeof(tr);
(void) (db->put)(db, &key, &data, 0);
if (debug) {
syslog(LOG_DEBUG, "saverecent(): put ~/%s: from='%s'[%lu], time='%lu'",
PATH_VDB,
from,
(unsigned long) len,
(unsigned long) tr);
}
return;
}
/*
* sendmessage() --
*
* start a sendmail process to send the vacation file to the sender
*
* A "Precedence: bulk" header is automatically added to the message.
*
* Returns true if message was apparently sent OK.
*/
int
sendmessage(myname, dest, subject)
const char *myname; /* name for 'sendmail -f' */
char *dest; /* i.e. the reply destination */
char *subject; /* incoming's subject header contents */
{
FILE *mfp; /* vacation message file */
FILE *sfp; /* pipe stream to sendmail child */
int i;
int pvect[2];
char *header; /* poiter to start of header */
char *colon; /* pointer to ':' in header */
char buf[BUFSIZ];
if (debug) {
syslog(LOG_DEBUG, "Replying to %s.", dest);
}
if (!(mfp = fopen(vmsgfilename, "r"))) {
syslog(LOG_NOTICE, "Cannot open ~%s/%s file: %m", myname, vmsgfilename);
return 0;
}
/*
* XXX If popen(3) didn't use /bin/sh then we could use it, but
* otherwise it's best to avoid even though we're not giving any
* possible way whatsoever for the sender to affect the commandline
* itself.....
*/
if (pipe(pvect) < 0) {
syslog(LOG_ERR, "pipe() to sendmail failed: %m");
return 0;
}
/*
* XXX should we loop a few times to be more resilient to resource
* starvation problems?
*/
if ((i = vfork()) < 0) {
syslog(LOG_ERR, "fork() for sendmail failed: %m");
return 0;
}
if (i == 0) {
dup2(pvect[0], 0); /* XXX error check! */
close(pvect[0]); /* XXX error check? */
close(pvect[1]); /* XXX error check? */
close(fileno(mfp)); /* XXX error check? */
execl(_PATH_SENDMAIL,
"sendmail",
debug ? (debug > 1 ? "-v99" : "-v9") : "-oeq",
"-t",
(char *) NULL);
syslog(LOG_ERR, "vacation: can't exec %s: %m", _PATH_SENDMAIL);
_exit(EX_OSERR); /* just the child... */
/* NOTREACHED */
}
close(pvect[0]);
if (!(sfp = fdopen(pvect[1], "w"))) {
syslog(LOG_ERR, "fdopen() pipe to sendmail failed: %m");
return 0;
}
/*
* XXX we should probably try to do better error checking on output!
* [though we should get a SIGPIPE if a write() inside STDIO fails...]
*/
rfc2822_clear_done_headers();
fprintf(sfp, "To: %s\n", dest); /* note we use '-t' above! */
while ((header = rfc2822_gethfield(mfp, &colon, TRUE))) {
char *svar;
char *rest;
if ((rest = rfc2822_ishfield(header, colon, "precedence")) ||
(rest = rfc2822_ishfield(header, colon, "to"))) {
if (debug) {
syslog(LOG_DEBUG, "Ignoring '%s' header in %s", header, vmsgfilename);
}
continue;
}
/*
* XXX we should probably try re-folding the headers nicely...
*/
svar = NULL;
if (subject) {
svar = strstr(colon, "$SUBJECT");
}
if (svar) {
rest = svar + sizeof("$SUBJECT") - 1;
*svar = '\0'; /* tromp on '$' */
fputs(header, sfp); /* output up to '$' */
fputs(subject, sfp); /* output subject */
fputs(rest, sfp); /* output rest of buf */
} else {
fputs(header, sfp); /* output the whole thing */
}
}
if (!header && rfc2822_errno != RFC2822_OK) {
syslog(LOG_INFO, "%s: Header error: %s\n", vmsgfilename, rfc2822_strerror(rfc2822_errno));
}
fprintf(sfp, "Precedence: bulk\n");
fprintf(sfp, "X-BSD-Vacation-V2: %s\n", version_id);
fputc('\n', sfp); /* header separator */
/*
* now spew out the rest of the message body
*
* XXX Should we do $SUBJECT substitution in the body too?
*/
while (fgets(buf, (int) sizeof(buf), mfp)) {
fputs(buf, sfp); /* XXX error check! */
}
(void) fclose(mfp);
if (fclose(sfp) == EOF) {
syslog(LOG_ERR, "fclose(): pipe to sendmail failed: %m");
return 0;
}
/* XXX waitpid(i) and complain and return false if child failed */
return 1;
}
/*
* usage() -- spew about command-line errors.
*/
void
usage(msg)
const char *msg;
{
syslog(LOG_NOTICE, "UID %u has bad command-line invocation%s%s",
getuid(),
msg && *msg ? ": " : "",
msg && *msg ? msg : ".");
if (db) {
(void) (db->close)(db);
}
exit(EX_USAGE);
/* NOTREACHED */
}
syntax highlighted by Code2HTML, v. 0.9.1