/*
* vacation -- originally BSD vacation by Eric Allman,
*
* Adapted to ZMailer by Rayan Zachariassen, and further
* modified by Matti Aarnio over years 1988(?) thru 2002
*/
#include "mailer.h"
#include <stdio.h>
#include <ctype.h>
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <limits.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <time.h>
#include "zsyslog.h"
/* #include <tzfile.h> */ /* Not needed ? */
#include <errno.h>
#include <sysexits.h>
/* Database format preferrence order:
NDBM, GDBM, SleepyCat4, SleepyCat3, SleepyCat2, BSD DB 1
*/
#ifdef HAVE_NDBM
#include <ndbm.h>
#include <fcntl.h>
#else
#ifdef HAVE_GDBM
#include <gdbm.h>
#include <fcntl.h>
#else
#if defined(HAVE_DB_H) || defined(HAVE_DB1_DB_H) || \
defined(HAVE_DB2_DB_H) || defined(HAVE_DB3_DB_H) || \
defined(HAVE_DB4_DB_H)
#include "sleepycatdb.h"
#else
:error:error:error "To compile, VACATION needs ndbm.h, gdbm.h, or db.h; none found!"
#endif
#endif
#endif
/* #include "useful.h" */
/* #include "userdbm.h" */
#include "mail.h"
#include "zmalloc.h"
#include "libz.h"
#include "libc.h"
extern char * newstr __((const char *));
extern void setinterval __((time_t));
extern void setreply __((void));
extern void usage __((void));
#ifndef LONG_MAX
# define LONG_MAX 1000000000 /* 100 million seconds - 3.2 years */
#endif
/* SCCSID(@(#)vacation.c 4.1 7/25/83); */
#undef VDEBUG
/*
** VACATION -- return a message to the sender when on vacation.
**
** This program could be invoked as a message receiver
** when someone is on vacation. It returns a message
** specified by the user to whoever sent the mail, taking
** care not to return a message too often to prevent
** "I am on vacation" loops.
**
** For best operation, this program should run setuid to
** root or uucp or someone else that sendmail will believe
** a -f flag from. Otherwise, the user must be careful
** to include a header on his .vacation.msg file.
**
** Positional Parameters:
** the user to collect the vacation message from.
**
** Flag Parameters:
** -I initialize the database.
** -m FILE set the filename to use for the reply message to
** FILE.
** -d Turns off the logging of messages in the
** ~/.vacation.{dir,pag} files to determine
** whom to reply to.
**
** Side Effects:
** A message is sent back to the sender.
**
** Author:
** Eric Allman
** UCB/INGRES
*/
#define MAXLINE 1024 /* max size of a line */
#define VDB ".vacation"
#define VMSG ".vacation.msg"
#ifndef VMSGDEF
#define VMSGDEF "/usr/lib/vacation.def"
#endif
typedef struct alias {
struct alias *next;
const char *name;
} ALIAS;
ALIAS *names = NULL;
#ifdef HAVE_NDBM
DBM *db;
#define DBT datum
#else
#ifdef HAVE_GDBM
GDBM_FILE db;
#define DBT datum
#else /* HAVE_DB_H */
DB *db;
/* Natural datum type is: DBT */
#define dptr data
#define dsize size
#endif /* GDBM */
#endif /* NDBM */
char from[MAXLINE];
char *subject_str = NULL; /* Glob subject from input */
int dblog = 1;
extern void purge_input __((void));
extern int optind, opterr;
extern char *optarg;
extern FILE *freopen(), *tmpfile();
extern char *getenv();
#ifndef HAVE_STDLIB_H
extern char *malloc();
#endif
extern void usrerr __((char *));
extern void syserr __((char *));
extern void readheaders __((void));
extern char *strerror __((int));
static int recent __((void));
static int junkmail __((void));
static int nsearch __((const char *name, const char *str));
static void sendmessage __((const char *msgf, const char *myname));
const char *progname;
int
main(argc, argv)
int argc;
char *argv[];
{
register char *p;
struct Zpasswd *pw;
ALIAS *cur;
time_t interval;
char *msgfile = NULL;
int ch, iflag, ret;
progname = argv[0];
/* process arguments */
opterr = iflag = 0;
interval = -1;
while ((ch = getopt(argc,argv,"a:Iir:t:m:d?")) != EOF) {
switch ((char)ch) {
case 'a': /* alias */
cur = (ALIAS*) malloc((u_int)sizeof(ALIAS));
if (!cur)
break;
cur->name = optarg;
cur->next = names;
names = cur;
break;
case 'I': /* backwards compatible */
case 'i': /* initialize the database*/
iflag = 1;
break;
case 'm': /* set file to get message from */
msgfile = optarg;
break;
case 'd': /* No dbm log of sender */
dblog = 0;
break;
case 't':
case 'r':
if (isdigit(*optarg)) {
interval = atol(optarg) * (24*60*60);
if (interval < 0)
usage();
}
else
interval = INT_MAX;
break;
case '?':
default:
usage();
}
}
argc -= optind;
argv += optind;
/* verify recipient argument */
#ifdef ZMAILER
if (argc == 0) {
p = getenv("USER");
if (p == NULL) {
usrerr("Zmailer error: USER environment variable not set");
exit(EX_USAGE+101);
}
}
#endif /* ZMAILER */
if (argc != 1) {
if (!iflag)
usage();
pw = zgetpwuid(getuid());
if (!pw) {
fprintf(stderr, "vacation: no such user uid %ld.\n", (long)getuid());
exit(EX_NOUSER);
}
} else if (!(pw = zgetpwnam(*argv))) {
fprintf(stderr, "vacation: no such user %s.\n", *argv);
exit(EX_NOUSER);
}
if (chdir(pw->pw_dir)) {
fprintf(stderr, "vacation: no such directory %s.\n", pw->pw_dir);
exit(EX_NOUSER);
}
#ifdef HAVE_NDBM
if (dblog)
db = dbm_open(VDB, O_RDWR | (iflag ? O_TRUNC|O_CREAT : 0),
S_IRUSR|S_IWUSR);
#else /* !NDBM */
#ifdef HAVE_GDBM
if (dblog)
db = gdbm_open(VDB ".pag" /* Catenates these strings */, 8192,
iflag ? GDBM_NEWDB : GDBM_WRITER,
S_IRUSR|S_IWUSR, NULL );
#else
db = NULL;
#ifdef HAVE_DB_CREATE
if (dblog) {
ret = db_create(&db, NULL, 0);
#ifndef DB_UPGRADE
#define DB_UPGRADE 0
#endif
if (ret == 0)
ret = db->open(db, VDB ".db", NULL, DB_BTREE,
DB_CREATE|DB_UPGRADE, S_IRUSR|S_IWUSR);
if (ret)
db = NULL;
}
#elif defined(HAVE_DB_OPEN2)
if (dblog)
db_open(VDB ".db", DB_BTREE, DB_CREATE, S_IRUSR|S_IWUSR,
NULL, NULL, &db);
#else
if (dblog)
db = dbopen(VDB ".db", iflag ? (O_RDWR|O_CREAT) : O_RDWR,
S_IRUSR|S_IWUSR, DB_BTREE, NULL);
#endif
#endif
#endif
ret = EX_OK;
if (dblog && !db) {
fprintf(stderr, "vacation: %s.* database file(s): %s\n",
VDB, strerror(errno));
exit(EX_CANTCREAT);
}
if (interval != -1)
setinterval(interval);
if (!iflag) {
cur = (ALIAS *)malloc((u_int)sizeof(ALIAS));
if (!cur) {
ret = EX_TEMPFAIL;
} else {
cur->name = pw->pw_name;
cur->next = names;
names = cur;
/* read message from standard input (just from line) */
readheaders();
purge_input();
if (!recent()) {
setreply();
sendmessage(msgfile,pw->pw_name);
}
}
}
#ifdef HAVE_NDBM
if (dblog)
dbm_close(db);
#else
#ifdef HAVE_GDBM
if (dblog)
gdbm_close(db);
#else
#ifdef HAVE_DB_CLOSE2
if (dblog)
db->close(db, 0);
#else
if (dblog)
db->close(db);
#endif
#endif
#endif
exit(ret);
}
/*
** SENDMESSAGE -- send a message to a particular user.
**
** Parameters:
** msgf -- filename containing the message.
** user -- user who should receive it.
**
** Returns:
** none.
**
** Side Effects:
** sends mail to 'user' using /usr/lib/sendmail.
*/
static void
sendmessage(msgf, myname)
const char *msgf;
const char *myname;
{
FILE *f;
FILE *mf;
char linebuf[512];
char *s;
#ifdef VDEBUG
fprintf(stderr, "sendmessage(%s, %s)\n", msgf, myname);
fflush(stderr);
#endif
/* find the message to send */
f = NULL;
if (msgf)
f = freopen(msgf, "r", stdout);
if (f == NULL)
f = freopen(VMSG, "r", stdout);
if (f == NULL)
f = freopen(VMSGDEF, "r", stdout);
if (f == NULL)
syserr("No message to send");
mf = mail_open(MSG_RFC822);
fprintf(mf, "from %s\n",myname);
fprintf(mf, "to %s\n", from);
fprintf(mf, "env-end\n");
fprintf(mf,"To: %s\n", from);
while (!feof(f) && !ferror(f)) {
if (fgets(linebuf,sizeof(linebuf),f) == NULL) break;
if ((s = strchr(linebuf,'$')) != NULL) {
/* Possibly $SUBJECT ? */
if (strncmp(s+1,"SUBJECT",7)==0) {
/* It is $SUBJECT */
*s = 0;
fputs(linebuf,mf);
if (subject_str)
fputs(subject_str,mf);
s += 8;
fputs(s,mf);
continue;
}
}
fputs(linebuf,mf);
}
fclose(f);
mail_close(mf);
}
/*
** USRERR -- print user error
**
** Parameters:
** f -- format.
** p -- first parameter.
**
** Returns:
** none.
**
** Side Effects:
** none.
*/
void
usrerr(msg)
char *msg;
{
fprintf(stderr, "vacation: %s\n",msg);
}
/*
** SYSERR -- print system error
**
** Parameters:
** f -- format.
** p -- first parameter.
**
** Returns:
** none.
**
** Side Effects:
** none.
*/
/* VARARGS 1 */
void
syserr(msg)
char *msg;
{
fprintf(stderr, "vacation: %s\n", msg);
exit(EX_USAGE+103);
}
/*
** NEWSTR -- copy a string
**
** Parameters:
** s -- the string to copy.
**
** Returns:
** A copy of the string.
**
** Side Effects:
** none.
*/
char *
newstr(s)
const char *s;
{
char *p;
p = malloc((unsigned)strlen(s) + 1);
if (p == NULL)
{
syserr("newstr: cannot alloc memory");
exit(EX_OSERR);
}
strcpy(p, s);
return p;
}
/*
* readheaders --
* read mail headers
*/
void
readheaders()
{
register ALIAS *cur;
register char *p;
int tome, cont;
char buf[MAXLINE];
char *sender;
int has_from = 0;
#ifdef ZMAILER
/* get SENDER from environment, ensure null-terminated. This is the
SMTP MAIL FROM address, i.e. the error return address if the
message comes from a mailing list. */
if ( (sender=getenv("SENDER")) != NULL ) {
strncpy(buf,sender,MAXLINE);
if (buf[MAXLINE-1] != '\0') {
usrerr("SENDER environment variable too long");
exit(EX_USAGE+104);
}
strcpy(from,buf);
has_from = 1;
if (junkmail()) {
purge_input();
exit(EX_OK);
}
}
#endif
cont = tome = 0;
while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
switch(*buf) {
case 'F': /* "From " */
cont = 0;
if (has_from) break;
if (!strncmp(buf, "From ", 5)) {
for (p = buf + 5; *p && *p != ' '; ++p);
*p = '\0';
strcpy(from, buf + 5);
p = strchr(from, '\n');
if (p != NULL)
*p = '\0';
if (junkmail()) {
purge_input();
exit(EX_OK);
}
}
break;
case 'P': /* "Precedence:" */
cont = 0;
if (strncasecmp(buf, "Precedence", 10) ||
(buf[10] != ':' && buf[10] != ' ' && buf[10] != '\t'))
break;
if (!(p = strchr(buf, ':')))
break;
while (*++p && isspace(*p));
if (!*p)
break;
if (!strncasecmp(p, "junk", 4) ||
!strncasecmp(p, "bulk", 4)) {
purge_input();
exit(EX_OK);
}
break;
case 'C': /* "Cc:" */
if (strncmp(buf, "Cc:", 3))
break;
cont = 1;
goto findme;
case 'S': /* "Subject:" */
if (strncmp(buf, "Subject:", 8))
break;
cont = 1;
subject_str = newstr(buf+9);
p = strchr(subject_str,'\n');
if (p) *p = 0; /* Zap the newline */
break;
case 'T': /* "To:" */
if (strncmp(buf, "To:", 3))
break;
cont = 1;
goto findme;
default:
if (!isspace(*buf) || !cont || tome) {
cont = 0;
break;
}
findme: for (cur = names; !tome && cur; cur = cur->next)
tome += nsearch(cur->name, buf);
} /* switch() */
if (!tome) {
purge_input();
exit(EX_OK);
}
if (!*from) {
zopenlog("vacation", LOG_PID, LOG_MAIL);
zsyslog((LOG_NOTICE, "vacation: no initial \"From\" line.\n"));
exit(EX_USAGE+105);
}
}
/*
* nsearch --
* do a nice, slow, search of a string for a substring.
*/
static int
nsearch(name, str)
register const char *name, *str;
{
register int len;
for (len = strlen(name); *str; ++str)
if (*str == *name && !strncasecmp(name, str, len))
return(1);
return(0);
}
/*
* junkmail --
* read the header and return if automagic/junk/bulk mail
*/
static int
junkmail()
{
static struct ignore {
const char *name;
int len;
} ignore[] = {
{ "-request", 8 },
{ "postmaster", 10 },
{ "uucp", 4 },
{ "mailer-daemon", 13 },
{ "mailer", 6 },
{ "-relay", 6 },
{ NULL, 0 }
};
register struct ignore *cur;
register int len;
register char *p;
if (strcmp(from, "<>") == 0)
return(1);
/*
* This is mildly amusing, and I'm not positive it's right; trying
* to find the "real" name of the sender, assuming that addresses
* will be some variant of:
*
* From site!site!SENDER%site.domain%site.domain@site.domain
*/
if (!(p = strchr(from, '%')))
if (!(p = strchr(from, '@'))) {
p = strrchr(from, '!');
if (p != NULL)
++p;
else
p = from;
for (; *p; ++p);
}
len = p - from;
for (cur = ignore; cur->name; ++cur)
if (len >= cur->len &&
!strncasecmp(cur->name, p - cur->len, cur->len))
return(1);
return(0);
}
/*
* purge_input()
*
* Read in the stdin.
*
*/
void
purge_input()
{
char buf[256];
int read_rc;
while (!feof(stdin) && !ferror(stdin)) {
read_rc = fread(buf,1,sizeof(buf),stdin);
if (read_rc == 0) break;
}
}
#define VIT "__VACATION__INTERVAL__TIMER__"
/*
* recent --
* find out if user has gotten a vacation message recently.
* use memcpy for machines with alignment restrictions
*/
static int
recent()
{
DBT key, data;
time_t then, next;
if (!dblog) return 0;
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
/* get interval time */
key.dptr = VIT;
key.dsize = sizeof(VIT);
#ifdef HAVE_NDBM
data = dbm_fetch(db, key);
#else
#ifdef HAVE_GDBM
data = gdbm_fetch(db, key);
#else
#ifdef DB_INIT_TXN
if (db->get(db, NULL, &key, &data, 0) != 0)
data.dptr = NULL;
#else
if (db->get(db, &key, &data, 0) != 0)
data.dptr = NULL;
#endif
#endif
#endif
if (data.dptr == NULL)
next = (60*60*24*7); /* One week */
else
memcpy(&next, data.dptr, sizeof(next));
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
/* get record for this address */
key.dptr = from;
key.dsize = strlen(from);
#ifdef HAVE_NDBM
data = dbm_fetch(db, key);
#else
#ifdef HAVE_GDBM
data = gdbm_fetch(db, key);
#else
#ifdef DB_INIT_TXN
if (db->get(db, NULL, &key, &data, 0) != 0)
data.dptr = NULL;
#else
if (db->get(db, &key, &data, 0) != 0)
data.dptr = NULL;
#endif
#endif
#endif
if (data.dptr) {
memcpy(&then, data.dptr, sizeof(then));
if (next == INT_MAX || then + next > time(NULL))
return(1);
}
return(0);
}
/*
* setinterval --
* store the reply interval
*/
void
setinterval(interval)
time_t interval;
{
DBT key, data;
if (!dblog) return;
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
key.dptr = VIT;
key.dsize = sizeof(VIT);
data.dptr = (void*)&interval;
data.dsize = sizeof(interval);
#ifdef HAVE_NDBM
dbm_store(db, key, data, DBM_REPLACE);
#else
#ifdef HAVE_GDBM
gdbm_store(db, key, data, GDBM_REPLACE);
#else
#ifdef DB_INIT_TXN
db->put(db, NULL, &key, &data, 0);
#else
db->put(db, &key, &data, 0);
#endif
#endif
#endif
}
/*
* setreply --
* store that this user knows about the vacation.
*/
void
setreply()
{
DBT key, data;
time_t now;
if (!dblog) return;
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
key.dptr = from;
key.dsize = strlen(from);
time(&now);
data.dptr = (void*)&now;
data.dsize = sizeof(now);
#ifdef HAVE_NDBM
dbm_store(db, key, data, DBM_REPLACE);
#else
#ifdef HAVE_GDBM
gdbm_store(db, key, data, GDBM_REPLACE);
#else
#ifdef DB_INIT_TXN
db->put(db, NULL, &key, &data, 0);
#else
db->put(db, &key, &data, 0);
#endif
#endif
#endif
}
void
usage()
{
fprintf(stderr,"vacation: [-i] [-d] [-a alias] [-m msgfile] [-r interval] {login | 'start' | 'stop' }\n");
exit(EX_USAGE);
}
syntax highlighted by Code2HTML, v. 0.9.1