# This is a shell archive. Save it in a file, remove anything before # this line, and then unpack it by entering "sh file". Note, it may # create directories; files and directories will be owned by you and # have default permissions. # # This archive contains: # # Makefile # vacation.1 # vacation.c # vacation.cat1 # echo x - Makefile sed 's/^X//' >Makefile << 'END-of-Makefile' X#ident "@(#)vacation:VACATION-2_0:Makefile,v 1.1 2001/12/14 04:46:26 woods Exp" X# X# Makefile.BSD - makefile for vacation on 4.4BSD X# X# derived from: X# $NetBSD: Makefile,v 1.5 1997/10/20 02:53:02 lukem Exp $ X# @(#)Makefile 8.1 (Berkeley) 6/6/93 X XPROG= vacation X X.include END-of-Makefile echo x - vacation.1 sed 's/^X//' >vacation.1 << 'END-of-vacation.1' X.\"ident "@(#)vacation:VACATION-2_0:vacation.1,v 1.1 2001/12/14 04:46:26 woods Exp" X.\" X.\" Copyright (c) 1983 Eric P. Allman X.\" X.\" Copyright (c) 1985, 1987, 1990, 1991, 1993 X.\" The Regents of the University of California. All rights reserved. X.\" X.\" Copyright (c) 2001 Greg A. Woods X.\" X.\" Redistribution and use in source and binary forms, with or without X.\" modification, are permitted provided that the following conditions X.\" are met: X.\" 1. Redistributions of source code must retain the above copyright X.\" notice, this list of conditions and the following disclaimer. X.\" 2. Redistributions in binary form must reproduce the above copyright X.\" notice, this list of conditions and the following disclaimer in the X.\" documentation and/or other materials provided with the distribution. X.\" 3. All advertising materials mentioning features or use of this software X.\" must display the following acknowledgement: X.\" This product includes software developed by the University of X.\" California, Berkeley and its contributors. X.\" 4. Neither the name of the University nor the names of its contributors X.\" may be used to endorse or promote products derived from this software X.\" without specific prior written permission. X.\" X.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND X.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE X.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE X.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE X.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL X.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS X.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) X.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT X.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY X.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF X.\" SUCH DAMAGE. X.\" X.\" @(#)vacation.1 8.2 (Berkeley) 4/28/95 X.\" X.Dd 2001/12/14 04:46:26 X.Dt VACATION 1 X.Os BSD 4.4 X.Sh NAME X.Nm vacation X.Nd reply to incoming e-mail with an X.Dq I am not here Xmessage X.Sh SYNOPSIS X.Nm X.Fl i X.Op Fl d X.Op Fl f Ar db X.Op Fl r Ar interval X.Op Fl x X.Nm vacation X.Fl x X.Op Fl d X.Op Fl f Ar db X.Op Fl r Ar interval X.Nm vacation X.Fl l X.Op Fl d X.Op Fl f Ar db X.Nm vacation X.Oo X.Op Fl a Ar alias X\&... X.Oc X.Op Fl d X.Op Fl f Ar db X.Op Fl m Ar msg X.Op Fl r Ar interval X.Op Ar login X.\" X.Sh DESCRIPTION X.Nm Xprocesses RFC-2822 compliant messages fed to its standard input and may Xreply with a message telling the originator of the incoming message that Xyou are currently not reading your mail. The intended use is as a pipe Xcommand in a X.Pa .forward Xfile. X.\" X.Ss Options XAvailable options: X.Bl -tag -width Ds X.\" X.It Fl a XTreat X.Ar alias Xas an alias for the user running X.Nm vacation . XThere can be multiple instances of X.Dq Fl a Ar alias Xgiven on the command line and each X.Ar alias Xwill be added to an internal list of names. These should probably be Xgiven as fully qualified addresses. This silly trick is necessary if Xyou subscribe to any mailing lists handled by broken mailing list Xsoftware (eg. Lsoft's LISTSERV as of the last inspection) that doesn't Xinclude a proper X.Dq Precedence: Xheader as described below. X.\" X.It Fl d XTurn on debugging. This flag causes informtaional and error messages to Xbe printed to X.Dv stderr Xas well as logged. X.Em Never Xuse this option on the invocation in your X.Pa ~/.forward Xfile! X.\" X.It Fl f Ar db XUse X.Ar db Xas the recent recipients database file instead of the default X.Pa ~/.vacation.db . X.\" X.It Fl i X(Re-)Initialize the recent recipients database file. It should be used Xbefore you modify your X.Pa ~/.forward Xfile, and it must X.Em never Xbe used on the invocation in your X.Pa ~/.forward Xfile. Except when listing the contents the database will always be Xcreated if it does not exist so this option is only necessary if you Xwant to clear an existing database and perhaps set a new reply interval. X.\" X.It Fl l XList the current contents of recent recipients database file. X.\" X.It Fl m Ar msg XUse X.Ar msg Xas the reply message contents file instead of the default X.Pa ~/.vacation.msg . X.\" X.It Fl r XSet the reply interval to X.Ar interval Xdays. This value is stored in the X.Pa ~/.vacation.db Xfile and is usually used with the X.Fl i Xoption. The default reply interval is one week. An interval of Xzero X.Pq Dq 0 Xis not allowed as that would mean a reply would be sent to each message, Xwhich could accidentally cause a mail loop between another less Xcarefully programmed autoresponder. An interval of X.Dq Li infinite Xwill never send more than one reply. X.\" X.It Fl x XReads a list of addresses from standard input, one per line (ignoring Xblank lines and lines that begin with a X.Dq # Xcharacter), and adds them to the recent recipients database. Mail Xcoming from these excluded addresses will not ever get a reply. X.\" XXX This feature doesn't work yet.... (see last bug) X.\" Whole domains can X.\"be excluded using the syntax X.Dq @domain . X.\" X.El X.\" X.Ss Theory of Operation XNo message will be sent unless X.Ar login X(or the invoking user's name), or an X.Ar alias Xsupplied using the X.Fl a Xoption, is part of either the X.Dq To: Xor X.Dq Cc: Xheaders of the mail. X.Pp XNo messages from X.Dq -OUTGOING , X.Dq -RELAY , X.Dq LISTSERV , X.Dq -REQUEST , X.Dq MAILER , Xor X.Dq MAILER-DAEMON Xwill be replied to (where these strings are case insensitive suffixes of Xthe base mailbox name for the sender or originator address). X.Pp XFinally, and most importantly, no notification is sent if a X.Dq Precedence: Xheader is found that contains the value X.Dq bulk , X.Dq list , Xor X.Dq junk . X.Pp X.Nm Xexpects to find a file called X.Pa .vacation.msg , Xin your home directory (or whatever file was specified with X.Fl m ) , Xcontaining an RFC-2822 compliant message to be sent back in response to Xan incoming message. It must be an entire message, including headers, Xbut without recipient headers, and X.Em especially Xwithout the X.Ux Xmailbox X.Dq From_ Xheader). The recipient address(es) will be supplied in a X.Dq to: Xheader that will be automatically prepended to the message. Any Xoccurrence of the string X.Dq $SUBJECT Xwill be replaced by the content of the X.Dq subject: Xheader from the message being read on the standard input. A X.Dq Precedence: bulk Xheader is also automatically prepended to the message. X.Pp XThe reply is sent to the address(es) given in the X.Dq reply-to: Xheader if one is found, and if not then to the address(es) given in the X.Dq from: Xheader (as per the RFC-2822 rules for replying to a message). X.Pp XThe addresses to which replies have been sent are recorded, along with Xthe time a reply was sent to them, in a X.Xr db 3 Xdatabase in the file X.Pa .vacation.db Xin your home directory. No reply will be sent to any address which has Xbeen logged in this file unless the last reply was sent more than X.Ar interval X(see X.Fl r Xabove) days ago. X.Pp XFatal errors, such as calling X.Nm Xwith incorrect arguments, or with a non-existent X.Ar login Ns Ar s , Xand any errors accessing files or errors sending messages are logged in Xthe system log file, using X.Xr syslog 3 , Xand if X.Fl d Xis given they are also printed to the standard error descriptor. X.\" X.Sh EXIT STATUS XThe X.Nm Xutility exits with a value of zero (0) on success, and greater than zero X(>0) if an error occurs. The non-zero exit values are defined in X.Aq Pa sysexits.h . X.\" X.Sh FILES X.Bl -tag -width "vacation.123xxx" -compact X.\" X.It Pa ~/.vacation.db Xdefault recipient database file name in the user's home directory X.\" X.It Pa ~/.vacation.msg Xdefault name of the file containing the message to send X.El X.\" X.Sh EXAMPLES XAn example X.Pa ~/.forward Xfile might contain: X.Bd -literal -offset indent X\e\&eric, "|/usr/bin/vacation" X.Ed X.Pp Xwhich would save messages sent to you in your system mailbox (assuming Xyour login name was eric) and would also reply with copies of your X.Pa ~/.vacation.msg Xmessage (modulo the constraints outlined above). X.Pp XAn example X.Pa ~/.vacation.msg Xfile might contain a message similar to the following: X.Pp X.Bd -unfilled -offset indent -compact XFrom: eric@CS.Berkeley.EDU (Eric Allman) XSubject: I am on vacation XSummary: Re: $SUBJECT XDelivered-By-The-Graces-Of: The Vacation program X XI am on vacation until July 22. If you have something urgent, Xplease contact Keith Bostic . X-- Xeric X.Ed X.\" X.Sh SEE ALSO X.Xr syslog 3 , X.Xr sendmail 8 X.\" X.Sh HISTORY XThe X.Nm Xcommand first appeared in X.Bx 4.3 . X.Pp X.Nm Xwas rewritten by Greg A. Woods X.Aq Li woods@planix.com Xto obey RFC-2822. X.\" X.Sh BUGS XThe value of X.Dq infinity Xfor the reply interval is actually stored as X.Dv LONG_MAX . X.Pp XThere's no facility for folding or filling text or headers in the Xoutgoing message. X.Pp XThe entire recipient address of a sent reply, comments, multiple Xaddresses, and all, is stored in the recent recipients database. END-of-vacation.1 echo x - vacation.c sed 's/^X//' >vacation.c << 'END-of-vacation.c' X/* X * Copyright (c) 1983 Eric P. Allman X * X * Copyright (c) 1983, 1987, 1993 X * The Regents of the University of California. All rights reserved. X * X * Copyright (c) 2001 Greg A. Woods X * X * Redistribution and use in source and binary forms, with or without X * modification, are permitted provided that the following conditions X * are met: X * 1. Redistributions of source code must retain the above copyright X * notice, this list of conditions and the following disclaimer. X * 2. Redistributions in binary form must reproduce the above copyright X * notice, this list of conditions and the following disclaimer in the X * documentation and/or other materials provided with the distribution. X * 3. All advertising materials mentioning features or use of this software X * must display the following acknowledgement: X * This product includes software developed by the University of X * California, Berkeley and its contributors. X * 4. Neither the name of the University nor the names of its contributors X * may be used to endorse or promote products derived from this software X * without specific prior written permission. X * X * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND X * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE X * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE X * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE X * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL X * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS X * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) X * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT X * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY X * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF X * SUCH DAMAGE. X */ X X#include X X#ifndef lint X__COPYRIGHT("@(#) Copyright (c) 1983, 1987, 1993\n\ X The Regents of the University of California. All rights reserved.\n\ X@(#) Copyright (c) 2001 Greg A. Woods \n"); X#endif /* not lint */ X X#ifndef lint X__RCSID("@(#)vacation:VACATION-2_0:vacation.c,v 1.1 2001/12/14 04:46:26 woods Exp"); X# if 0 Xstatic char sccsid[] = "@(#)vacation.c 8.2 (Berkeley) 1/26/94"; X# endif X#endif /* not lint */ X X#include X#include X X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X X#include /* we are a mail application */ X X/* X * VACATION -- a mail agent that replies on your behalf X * X * This program reads an incoming e-mail message in RFC-2822 format (with X * optional Unix mailbox separator prepended) on its standard input. It X * replies with a message specified by the user to whomever sent the mail, X * taking care not to return a message too often to prevent "I am on vacation" X * loops; as well as avoiding replying to other auto-repsonders and mailing X * list mail. Instances of the string "$SUBJECT" in the user-supplied reply X * message are replaced with the content of the "subject:" header from the X * incoming messsage. X * X * TODO: X * X * Fix the way recipient addresses are stored. We need to parse full RFC-2822 X * address headers to do this though, and then store each individual recipient X * without comments, etc. Only then will the preloaded "@domain" exclusions X * always work. X * X * Add a command-line option to allow the user to specify a (list of) header X * fields (body and/or contents, substring, exact, or RE?) that should be used X * to mark messages that should not be responded to. X */ X X#define PATH_VDB ".vacation.db" /* db's database */ X#define PATH_VMSG ".vacation.msg" /* vacation message */ X X#define VIT "__VACATION__INTERVAL__TIMER__" /* magic db key, incl. NUL */ X Xtypedef struct alias { X struct alias *next; X const char *name; X} alias_t; X Xalias_t *names = NULL; /* me and all my aliases.... */ X XDB *db = NULL; Xchar *argv0 = "vacation"; Xint debug = 0; Xchar *vdbfilename = PATH_VDB; Xchar *vdbdir = NULL; Xchar *vmsgfilename = PATH_VMSG; X Xint main __P((int, char **)); Xvoid dbclose __P((void)); Xvoid list_db __P((void)); Xvoid preload_db __P((void)); Xint isjunkmail __P((char *, char *, char *)); Xint isautoresponder __P((char *)); Xvoid readheaders __P((char **, char **, char **, char **, int *)); Xvoid tossbody __P((void)); Xint isrecent __P((char *)); Xint sendmessage __P((const char *, char *, char *)); Xchar *gethfield __P((FILE *, char **)); Xchar *ishfield __P((char *, char *, char *)); Xint istome __P((const char *)); Xint isdelim __P((int)); Xvoid setfrom __P((char *, char *)); Xvoid setinterval __P((time_t)); Xvoid saverecent __P((char *, size_t, time_t)); Xvoid usage __P((void)); X Xint Xmain(argc, argv) X int argc; X char *argv[]; X{ X char *sender = NULL; /* from the "return-path" header */ X char *from = NULL; /* from "reply-to", or "from" */ X char *precedence = NULL; /* from the "precedence" header */ X char *subject = NULL; /* from the "subject" header */ X int tome; X struct passwd *pw; X time_t interval; X int ch; X int init_db = 0; X int do_list_db = 0; X int do_preload_db = 0; X int zflag = 0; X alias_t *cur; X X argv0 = (argv0 = strrchr(argv[0], '/')) ? argv0 + 1 : argv[0]; X opterr = 0; X interval = DAYSPERWEEK * SECSPERDAY; X openlog(argv0, 0, LOG_MAIL); X while ((ch = getopt(argc, argv, "a:f:dIilm:r:xz")) != -1) { X switch ((char) ch) { X case 'a': X if (!(cur = (alias_t *) malloc((u_int) sizeof(alias_t)))) { X syslog(LOG_NOTICE, "malloc(alias_t) failed: '%s'", strerror(errno)); X break; X } X cur->name = optarg; X cur->next = names; X names = cur; X break; X case 'd': X debug = 1; X closelog(); X openlog(argv0, LOG_PERROR, LOG_MAIL); X break; X case 'f': X vdbfilename = optarg; X break; X case 'I': /* backward compatible */ X case 'i': /* init the database */ X init_db = 1; X break; X case 'l': X do_list_db = 1; X closelog(); X openlog(argv0, LOG_PERROR, LOG_USER); /* should just do stderr? */ X break; X case 'm': X vmsgfilename = optarg; X break; X case 'r': X if (strncasecmp(optarg, "inf", 3) == 0) X interval = (time_t) LONG_MAX; /* XXX not really infinity! */ X else if (isdigit((unsigned char) *optarg)) { X interval = atol(optarg) * SECSPERDAY; X if (interval <= 0) /* don't allow zero! */ X usage(); X } else X usage(); X break; X case 'x': X do_preload_db = 1; X break; X case 'z': /* we'll leave this undocumented.... */ X zflag = 1; X break; X case '?': X default: X usage(); X } X } X argc -= optind; X X if (argc > 1 || (argc == 1 && (init_db || do_preload_db || do_list_db))) X usage(); /* silly to allow this... */ X if (!argc) { X if (!(pw = getpwuid(getuid()))) { X syslog(LOG_ERR, "no such user uid %u.", getuid()); X exit(EX_NOUSER); X } X } else if (!(pw = getpwnam(argv[argc]))) { X syslog(LOG_ERR, "no such user %s as given on command-line.", argv[argc]); X exit(EX_NOUSER); X } X if (!(cur = (alias_t *) malloc((u_int) sizeof(alias_t)))) { X syslog(LOG_NOTICE, "malloc(alias_t) failed: '%s'", strerror(errno)); X exit(EX_OSERR); /* XXX do we really have to? */ X } X cur->name = pw->pw_name; /* XXX pw_name is static in getpwnam()! */ X cur->next = names; X names = cur; X vdbdir = pw->pw_dir; /* XXX pw_dir is static in getpwnam()! */ X if (chdir(vdbdir)) { X syslog(LOG_NOTICE, "could not chdir(%s), home for %s: %m.", vdbdir, pw->pw_name); X exit(1); X } X db = dbopen(vdbfilename, (O_RDWR | O_CREAT | (init_db ? O_TRUNC : 0)), X (S_IRUSR | S_IWUSR), X DB_HASH, X (void *) NULL); X if (!db) { X syslog(LOG_NOTICE, "dbopen(%s/%s): %m.", vdbdir, vdbfilename); X exit(EX_NOINPUT); X } X if (atexit(dbclose) == -1) { X syslog(LOG_NOTICE, "atexit(dbclose): %m."); X if ((db->close)(db) == -1) X syslog(LOG_NOTICE, "dbclose(%s/%s): %m.", vdbdir, vdbfilename); X exit(EX_OSERR); X } X setinterval(interval); /* always, in case we just created a new db */ X if (do_preload_db) X preload_db(); X if (do_list_db) X list_db(); X if (init_db || do_preload_db || do_list_db) { X exit(0); X } X readheaders(&sender, &from, &precedence, &subject, &tome); X tossbody(); /* so mailer doesn't get SIGPIPE */ X if (!tome) { X if (debug) X syslog(LOG_DEBUG, "Ignoring mail not addressed to me from %s.", X from ? from : "(nobody)"); X exit(0); X } X if (!from) { X syslog(LOG_NOTICE, "Could not find any reply address."); X exit(EX_PROTOCOL); X } X if (isjunkmail(sender, from, precedence)) { X exit(0); X } X if (!isrecent(from)) { X time_t now; X X (void) time(&now); X saverecent(from, strlen(from), now); X if (debug) X syslog(LOG_DEBUG, "Replying to %s.", from); X /* note that sendmessage syslog()s its own errors */ X (void) sendmessage(zflag ? "<>" : pw->pw_name, from, subject); X } else if (debug) { X syslog(LOG_DEBUG, "Have sent a reply to %s recently.", from); X } X X exit(0); X /* NOTREACHED */ X} X X/* X * dbclose() -- close the db, complaining if there's an error doing so.... X * X * called by exit() X */ Xvoid Xdbclose() X{ X if (db && (db->close)(db) == -1) X syslog(LOG_NOTICE, "dbclose(%s/%s): %m.", vdbdir, vdbfilename); X return; X} X X/* X * list_db() -- X * X * list the contents of the recent recipients database X */ Xvoid Xlist_db() X{ X DBT key, data; X int rv; X time_t t; X char *user; X u_int user_sz; X X /* if you don't have getpagesize then #define getpagesize() BUFSIZ */ X user_sz = getpagesize(); X if (!(user = (char *) malloc(user_sz))) { X fprintf(stderr, "malloc(user, %u) failed: '%s'\n", user_sz, strerror(errno)); X (void) (db->close)(db); X exit(EX_OSERR); X } X while ((rv = (db->seq)(db, &key, &data, R_NEXT)) == 0) { X /* ignore the interval definition entry */ X if (memcmp(key.data, VIT, MIN(key.size, sizeof(VIT))) == 0) X continue; X if ((key.size + 1) >= user_sz) { X user_sz = ((key.size + 1) / getpagesize()) + getpagesize(); X if (!(user = (char *) realloc(user, user_sz))) { X fprintf(stderr, "realloc(user, %u) failed: '%s'\n", user_sz, strerror(errno)); X (void) (db->close)(db); X exit(EX_OSERR); /* XXX do we really have to? */ X } X } X memmove(user, key.data, key.size); X user[key.size] = '\0'; /* just to be safe... */ X if (data.size != sizeof(t)) { X fprintf(stderr, "%s: key %s: invalid data size: %u", vdbfilename, user, data.size); X continue; X } X /* use memmove() not assignment for machines with alignment restrictions */ X memmove(&t, data.data, sizeof(t)); X printf("%-54s ", user); /* 80 - strlen(ctime()) */ X#if 0 X if (t == LONG_MAX) X puts("preloaded (never expires)"); X else X#endif X fputs(ctime(&t), stdout); X } X if (rv == -1) X fprintf(stderr, "%s: error reading database: %s", vdbfilename, strerror(errno)); X return; X} X X/* X * preload_db() -- X * X * Pre-load the recent recipients database with values read from stdin. X */ Xvoid Xpreload_db() X{ X char *buf; X size_t len; X X for (;;) { X if (!(buf = fgetln(stdin, &len))) { X if (ferror(stdin)) X fprintf(stderr, "preload_db: fgetln() failed: '%s'", strerror(errno)); X return; X } X if (*buf == '\n') /* XXX && len == 1 */ X continue; X if (*buf == '#') /* XXX && len == 1 */ X continue; X if (buf[len - 1] == '\n') X --len; X saverecent(buf, len, LONG_MAX); X } X} X X/* X * readheaders() -- X * X * Read mail headers, setting pointers to the coalesced contents of the X * relevant ones, and NULL for those that are not found. X * X * The from value is set as per the RFC-2822 algorithm for searching for an X * appropriate reply value. X * X * XXX always returns the value of the last of any duplicate headers, except X * the "from: header, which is always that of the first found, unless there's a X * "reply-to" header, in which case it's the last one found! Phew! X */ Xvoid Xreadheaders(senderp, fromp, precedencep, subjectp, tomep) X char **senderp; /* "return-path:" */ X char **fromp; /* "from:" */ X char **precedencep; /* "precedence:" */ X char **subjectp; /* "subject:" */ X int *tomep; /* is it to me? */ X{ X char *header; X char *colon; /* pointer to ':' in header */ X char *p; X X *senderp = NULL; X *fromp = NULL; X *precedencep = NULL; X *tomep = 0; X while ((header = gethfield(stdin, &colon))) { X if ((p = ishfield(header, colon, "return-path"))) { X if (*precedencep && debug) X syslog(LOG_DEBUG, "Another return-path header overrides the previous: '%s'", p); X *senderp = p; X continue; X } X if ((p = ishfield(header, colon, "from")) && !*fromp) { X *fromp = p; X continue; X } X if ((p = ishfield(header, colon, "reply-to"))) { X if (*precedencep && debug) X syslog(LOG_DEBUG, "Another reply-to header overrides the previous: '%s'", p); X *fromp = p; X continue; X } X if ((p = ishfield(header, colon, "subject"))) { X if (*subjectp && debug) X syslog(LOG_DEBUG, "Another subject header overrides the previous: '%s'", p); X *subjectp = p; X continue; X } X if ((p = ishfield(header, colon, "to"))) { X *tomep += istome(p); X continue; X } X if ((p = ishfield(header, colon, "cc"))) { X *tomep += istome(p); X continue; X } X /* XXX what about multiple values in precedence? */ X if ((p = ishfield(header, colon, "precedence"))) { X if (*precedencep && debug) X syslog(LOG_DEBUG, "Another precedence header overrides the previous: '%s'", p); X *precedencep = p; X continue; X } X } X X return; X} X X/* X * tossbody() -- bit-bucket the remainder of stdin X * X * we do this so that mailers don't have to decide whether getting a SIGPIPE X * while writing to the pipe we read from is a bad thing or not.... X * X * All errors reading from the MTA are ignored too as by now all the X * information necessary has been gathered from the headers. X */ Xvoid Xtossbody() X{ X char junk[BUFSIZ]; X X while (fread(junk, sizeof(junk), (size_t) 1, stdin) > 0) { X ; X } X X return; X} X X/* X * gethfield() -- X * X * Return the next header field found in the given message. Return a pointer X * to the buffer if something is found, NULL elsewise. X * X * "colon" is set to point to the colon in the header. Must deal with '\' X * continuations & other such RFC-2822 niceties. X * X * XXX borrowed from /usr/src/usr.bin/mail/aux.c and then hacked upon (has been X * made immune to line length limits, etc.).... X */ Xchar * Xgethfield(fp, colon) X FILE *fp; X char **colon; X{ X char *header; X char *buf; X size_t len; X char *cp; X X for (;;) { X if (!(buf = fgetln(fp, &len))) { X if (ferror(fp)) X syslog(LOG_INFO, "Read header: fgetln() failed: '%s'", strerror(errno)); X return NULL; X } X if (*buf == '\n') /* XXX && len == 1 */ X return NULL; /* end of headers */ X if (buf[len - 1] == '\n') X --len; X if (!(cp = header = (char *) calloc(len + 1, sizeof(char)))) { X syslog(LOG_NOTICE, "Read header: calloc() failed: '%s'", strerror(errno)); X return NULL; X } X /* the string is NUL-terminated by virtue of calloc() */ X memcpy(header, buf, len); X if (debug) X syslog(LOG_DEBUG, "Read header: '%s'", header); X while (isprint(*cp) && *cp != ' ' && *cp != '\t' && *cp != ':') X cp++; X if (*cp != ':' || cp == header) { X free((void *) header); X header = NULL; X continue; /* ignore 'From ' and other junk */ X } X /* X * I guess we got ourselves a header line. X * Handle any wrap-arounding.... X */ X *colon = cp; X cp = header + len; /* point to where the NUL goes */ X for (;;) { X int c; X int olen; X char *line2; X char *cp2; X X ungetc((c = getc(fp)), fp); /* XXX error check */ X if (c != ' ' && c != '\t') X break; /* next line not a continuation */ X olen = len; X while (--cp >= header && (*cp == ' ' || *cp == '\t')) { X ; /* trim off any trailing whitespace */ X } X cp++; X if ((buf = fgetln(fp, &len))) { X syslog(LOG_INFO, "Read header: fgetln() failed: '%s'", strerror(errno)); X return NULL; X } X if (buf[len - 1] == '\n') X --len; X if (!(cp2 = line2 = (char *) calloc(len + 1, sizeof(char)))) { X syslog(LOG_NOTICE, "Read header: calloc() failed: '%s'", strerror(errno)); X X return NULL; X } X /* the string is NUL-terminated by virtue of calloc() */ X memcpy(line2, buf, len); X while (*cp2 == ' ' || *cp2 == '\t') X cp2++; /* skip leading whitespace */ X len -= cp2 - line2; X if (!(header = (char *) realloc((void *) header, olen + len + 1))) { X syslog(LOG_NOTICE, "Read header: realloc() failed: '%s'", strerror(errno)); X free((void *) line2); X return NULL; X } X cp = header + olen; /* must do after realloc() */ X *cp++ = ' '; X memmove(cp, cp2, len); X cp += len; X len += olen; X } X *cp = '\0'; /* stringify the result */ X return header; X } X /* NOTREACHED */ X} X X X/* X * ishfield() -- X * X * Check whether the passed line is an RFC-2822 header line with the desired X * field name. Return a pointer to the field body, or NULL if the field name X * does not match. X * X * XXX borrowed from /usr/src/mail/usr.bin/aux.c and hacked upon. X */ Xchar * Xishfield(linebuf, colon, field) X char linebuf[]; /* array containing header */ X char *colon; /* pointer to ':' in header */ X char *field; /* field name to match */ X{ X char *cp = colon; X X *cp = 0; X if (strcasecmp(linebuf, field) != 0) { X *cp = ':'; X return NULL; X } X *cp = ':'; X /* skip past any whitespace after the colon */ X for (cp++; *cp == ' ' || *cp == '\t'; cp++) { X ; X } X if (!*cp) /* empty fields are useless */ X return NULL; X X return cp; X} X X X/* X * istome() -- X * X * do a nice, slow, case-insensitive, search of a string for a substring for X * every name in names. X * X * This is kinda lame -- we should do proper RFC-2822 parsing to find the X * separate addresses in the header contents, and if the alias name is not X * fully qualified then also break apart the addresses to find just the local X * parts, and then mabye even do exact comparisons (local parts are supposed to X * be case sensitive, though depending on how the MTA is configured, it may not X * be treating them that way). X */ Xint Xistome(hdr) X const char *hdr; /* header contents */ X{ X size_t len; X alias_t *cur; X const char *str = hdr; X X for (cur = names; cur; cur = cur->next) { X for (len = strlen(cur->name); *str; ++str) { X if (strncasecmp(cur->name, str, len) == 0 && isdelim(str[len])) { X if (debug) X syslog(LOG_DEBUG, "istome(): found '%s' in '%s'", cur->name, hdr); X return (1); X } X } X } X X return (0); X} X X/* X * isdelim() -- Is 'c' a delimiting character for a recipient mailbox name? X */ Xint Xisdelim(c) X int c; X{ X if (isalnum(c)) X return (0); X if (c == '_' || c == '-' || c == '.') X return (0); X X return (1); X} X X/* X * isjunkmail() -- X * X * return (1) if mail seems to be from an auto-responder, or is listed with a X * precedence that indicates it should not recieve a personal response. X */ Xint Xisjunkmail(sender, from, precedence) X char *sender; /* return-path: contents */ X char *from; /* from: contents */ X char *precedence; /* precedence: contents */ X{ X /* X * XXX what about multiple values in precedence? We only test the X * first one for now... X */ X if (precedence) { X if (strncasecmp(precedence, "junk", 4) == 0 || X strncasecmp(precedence, "bulk", 4) == 0 || X strncasecmp(precedence, "list", 4) == 0) { X if (debug) X syslog(LOG_DEBUG, "ignoring precedence '%s' mail.", precedence); X return 1; X } X } X if (sender && isautoresponder(sender)) { X if (debug) X syslog(LOG_DEBUG, "Ignoring autoresponder mail sent by '%s'", sender); X return 1; X } X if (from && isautoresponder(from)) { /* unlikely if not also a sender but possible. */ X if (debug) X syslog(LOG_DEBUG, "Ignoring autoresponder mail from '%s'", from); X return 1; X } X /* X * XXX This is a bogus test -- we really need to parse full RFC-2822 X * addresses to avoid finding commas in quoted mailbox parts! X */ X if (from && strchr(from, ',')) { X if (debug) X syslog(LOG_DEBUG, "Ignoring mail from multiple from addresses: '%s'", from); X return 1; /* multiple addresses are never junk */ X } X X return 0; X} X X/* X * isautoresponder() -- X * X * Test an address to see if it's from a well-known auto-responder address, X * i.e. something that is in effect generating a response to us, such as a X * bounce message or mailing list administrative reply, etc.... X */ Xint Xisautoresponder(from) X char *from; X{ X static struct ignore { X char *name; X int len; X } ignore_senders[] = { X#define S_ENTRY(str) { str, sizeof(str)-1 } X S_ENTRY("-request"), /* usually mailing lists */ X S_ENTRY("mailer-daemon"), /* usually a bounce */ X S_ENTRY("listserv"), /* mailing list manager program */ X S_ENTRY("mailer"), /* XXX ???? */ X S_ENTRY("-relay"), /* XXX ???? */ X S_ENTRY("-outgoing"), /* XXX some mailing lists */ X#undef S_ENTRY X {NULL, 0 } X }; X struct ignore *cur; X int len; X char *p; X X /* X * Check if the *prefix* of the address matches... some mailing lists X * use this more arcane owner address format, particularly that most X * broken MLM, Lsoft's LISTSERV (as of the last inspection of it). X */ X if (strncmp(from, "owner-", sizeof("owner-") - 1) == 0) { X if (debug) X syslog(LOG_DEBUG, "isautoresponder(): a mailing list owner '%s'", from); X return 1; X } X /* X * Try finding a pointer to the *END* of the sender's mailbox name. X * X * This is mildly amusing, and I'm not positive it's right; trying X * to find the "real" name of the sender, assuming that addresses X * will be some variant of: X * X * site!site!SENDER%site.domain%site.domain@site.domain X */ X if (!(p = strchr(from, '%'))) { X if (!(p = strchr(from, '@'))) { X if ((p = strrchr(from, '!'))) X ++p; X else X p = from; X for (; *p; ++p) { X ; X } X } X } X len = p - from; X /* X * now test to see if the suffix of the mailbox name matches any of the X * strings given in ignore_senders X */ X for (cur = ignore_senders; cur->name; ++cur) { X if (len >= cur->len && strncasecmp(cur->name, p - cur->len, cur->len) == 0) { X if (debug) X syslog(LOG_DEBUG, "isautoresponder(): matches '%s'", cur->name); X return 1; X } X } X X return 0; X} X X/* X * isrecent() -- X * X * find out if a reply message was sent to the specified address recently. X * X * uses memmove() instead of assignment of data field to the time_t variables X * in order to accomodate machines with alignment restrictions X */ Xint Xisrecent(from) X char *from; X{ X DBT key, data; X time_t then, next; X char *domain; X X /* get interval time */ X key.data = VIT; X key.size = sizeof(VIT); /* include the NUL */ X if ((db->get)(db, &key, &data, 0)) X next = SECSPERDAY * DAYSPERWEEK; X else X memmove(&next, data.data, sizeof(next)); X X /* get record for this address */ X key.data = from; X key.size = strlen(key.data); X if (!(db->get)(db, &key, &data, 0)) { X memmove(&then, data.data, sizeof(then)); X /* XXX */ X if (next == (time_t) LONG_MAX || then + next > time((time_t *) NULL)) X return 1; X } X X /* get record for the domain of this address */ X if ((domain = strchr(from, '@')) == NULL) X return 0; X key.data = domain; X key.size = strlen(key.data); X if (!(db->get)(db, &key, &data, 0)) { X memmove(&then, data.data, sizeof(then)); X /* XXX */ X if (next == (time_t) LONG_MAX || then + next > time((time_t *) NULL)) X return 1; X } X return 0; X} X X/* X * setinterval() -- X * X * store the reply interval under the special key. X */ Xvoid Xsetinterval(time_t interval) X{ X DBT key, data; X X key.data = VIT; X key.size = sizeof(VIT); /* include the NUL! */ X data.data = &interval; X data.size = sizeof(interval); X (void) (db->put)(db, &key, &data, 0); X} X X/* X * saverecent() -- X * X * store that this user knows about the vacation. X * X * XXX should separately store multi-address fields X * X * XXX should strip RFC822 comments and other gunk X */ Xvoid Xsaverecent(from, len, tr) X char *from; X size_t len; X time_t tr; X{ X DBT key, data; X X key.data = from; X key.size = len; /* don't include NUL! */ X data.data = &tr; X data.size = sizeof(tr); X (void) (db->put)(db, &key, &data, 0); X} X X/* X * sendmessage() -- X * X * start a sendmail process to send the vacation file to the sender X * X * A "Precedence: bulk" header is automatically added to the message. X * X * Returns true is message apparently sent OK. X */ Xint Xsendmessage(myname, dest, subject) X const char *myname; /* name for 'sendmail -f' */ X char *dest; /* i.e. the reply destination */ X char *subject; /* incoming's subject header contents */ X{ X FILE *mfp; /* vacation message file */ X FILE *sfp; /* pipe stream to sendmail child */ X int i; X int pvect[2]; X char buf[BUFSIZ]; X X if (!(mfp = fopen(vmsgfilename, "r"))) { X syslog(LOG_NOTICE, "Cannot open ~%s/%s file: %m", myname, vmsgfilename); X return 0; X } X if (pipe(pvect) < 0) { X syslog(LOG_ERR, "pipe() to sendmail failed: %m"); X return 0; X } X /* XXX should we loop a few times to be more resilient to resource X * starvation problems? X */ X if ((i = vfork()) < 0) { X syslog(LOG_ERR, "fork() for sendmail failed: %m"); X return 0; X } X if (i == 0) { X dup2(pvect[0], 0); X close(pvect[0]); X close(pvect[1]); X close(fileno(mfp)); X execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "-t", (char *) NULL); X syslog(LOG_ERR, "vacation: can't exec %s: %m", _PATH_SENDMAIL); X _exit(EX_OSERR); /* just the child... */ X } X close(pvect[0]); X if (!(sfp = fdopen(pvect[1], "w"))) { X syslog(LOG_ERR, "fdopen() pipe to sendmail failed: %m"); X return 0; X } X /* X * XXX we should probably try to do better error checking on output! X * (We should get a SIGPIPE anyway if a STDIO write fails...) X */ X fprintf(sfp, "To: %s\n", dest); /* see '-t' above! */ X fprintf(sfp, "Precedence: bulk\n"); X /* X * XXX we should probably try reading the message file in the same way X * we read the incoming message, by first reading the headers with X * gethfield(), dropping things like the two headers we write ourself, X * re-folding them nicely, and spitting them out one-by-one; and then X * finally spew the message body out with a quick loop like the one X * below, doing only the most basic substitutions desired. X */ X while (fgets(buf, sizeof(buf), mfp)) { X char *svar; X char *rest; X X svar = strstr(buf, "$SUBJECT"); X if (svar) { X rest = svar + sizeof("$SUBJECT") - 1; X *svar = '\0'; /* tromp on '$' */ X fputs(buf, sfp); /* output up to '$' */ X fputs(subject, sfp); /* output subject */ X fputs(rest, sfp); /* output rest of buf */ X } else X fputs(buf, sfp); X } X fclose(mfp); X if (fclose(sfp) == EOF) X syslog(LOG_ERR, "fclose() pipe to sendmail failed: %m"); X X return 1; X} X X/* X * usage() -- spew about command-line errors. X */ Xvoid Xusage() X{ X syslog(LOG_NOTICE, "uid %u has bad vacation invocation", getuid()); X if (db) { X (void) (db->close)(db); X } X exit(EX_USAGE); X} END-of-vacation.c echo x - vacation.cat1 sed 's/^X//' >vacation.cat1 << 'END-of-vacation.cat1' XVACATION(1) NetBSD Reference Manual VACATION(1) X XNNAAMMEE X vvaaccaattiioonn - reply to incoming e-mail with an ``I am not here'' message X XSSYYNNOOPPSSIISS X vvaaccaattiioonn --ii [--dd] [--ff _d_b] [--rr _i_n_t_e_r_v_a_l] [--xx] X vvaaccaattiioonn --xx [--dd] [--ff _d_b] [--rr _i_n_t_e_r_v_a_l] X vvaaccaattiioonn --ll [--dd] [--ff _d_b] X vvaaccaattiioonn [[--aa _a_l_i_a_s] ...] [--dd] [--ff _d_b] [--mm _m_s_g] [--rr _i_n_t_e_r_v_a_l] [_l_o_g_i_n] X XDDEESSCCRRIIPPTTIIOONN X vvaaccaattiioonn processes RFC-2822 compliant messages fed to its standard input X and may reply with a message telling the originator of the incoming mes- X sage that you are currently not reading your mail. The intended use is X as a pipe command in a _._f_o_r_w_a_r_d file. X X OOppttiioonnss X Available options: X X --aa Treat _a_l_i_a_s as an alias for the user running vvaaccaattiioonn. There can X be multiple instances of ``--aa _a_l_i_a_s'' given on the command line X and each _a_l_i_a_s will be added to an internal list of names. These X should probably be given as fully qualified addresses. This sil- X ly trick is necessary if you subscribe to any mailing lists han- X dled by broken mailing list software (eg. Lsoft's LISTSERV as of X the last inspection) that doesn't include a proper X ``Precedence:'' header as described below. X X --dd Turn on debugging. This flag causes informtaional and error mes- X sages to be printed to stderr as well as logged. _N_e_v_e_r use this X option on the invocation in your _~_/_._f_o_r_w_a_r_d file! X X --ff _d_b Use _d_b as the recent recipients database file instead of the de- X fault _~_/_._v_a_c_a_t_i_o_n_._d_b. X X --ii (Re-)Initialize the recent recipients database file. It should X be used before you modify your _~_/_._f_o_r_w_a_r_d file, and it must _n_e_v_e_r X be used on the invocation in your _~_/_._f_o_r_w_a_r_d file. Except when X listing the contents the database will always be created if it X does not exist so this option is only necessary if you want to X clear an existing database and perhaps set a new reply interval. X X --ll List the current contents of recent recipients database file. X X --mm _m_s_g Use _m_s_g as the reply message contents file instead of the default X _~_/_._v_a_c_a_t_i_o_n_._m_s_g. X X --rr Set the reply interval to _i_n_t_e_r_v_a_l days. This value is stored in X the _~_/_._v_a_c_a_t_i_o_n_._d_b file and is usually used with the --ii option. X The default reply interval is one week. An interval of zero X (``0'') is not allowed as that would mean a reply would be sent X to each message, which could accidentally cause a mail loop be- X tween another less carefully programmed autoresponder. An inter- X val of ``infinite'' will never send more than one reply. X X --xx Reads a list of addresses from standard input, one per line (ig- X noring blank lines and lines that begin with a ``#'' character), X and adds them to the recent recipients database. Mail coming X from these excluded addresses will not ever get a reply. X ``@domain''. X X TThheeoorryy ooff OOppeerraattiioonn X No message will be sent unless _l_o_g_i_n (or the invoking user's name), or an X _a_l_i_a_s supplied using the --aa option, is part of either the ``To:'' or X ``Cc:'' headers of the mail. X X No messages from ``-OUTGOING'', ``-RELAY'', ``LISTSERV'', ``-REQUEST'', X ``MAILER'', or ``MAILER-DAEMON'' will be replied to (where these strings X are case insensitive suffixes of the base mailbox name for the sender or X originator address). X X Finally, and most importantly, no notification is sent if a X ``Precedence:'' header is found that contains the value ``bulk'', X ``list'', or ``junk''. X X vvaaccaattiioonn expects to find a file called _._v_a_c_a_t_i_o_n_._m_s_g, in your home direc- X tory (or whatever file was specified with --mm), containing an RFC-2822 X compliant message to be sent back in response to an incoming message. It X must be an entire message, including headers, but without recipient head- X ers, and _e_s_p_e_c_i_a_l_l_y without the UNIX mailbox ``From_'' header). The re- X cipient address(es) will be supplied in a ``to:'' header that will be au- X tomatically prepended to the message. Any occurrence of the string X ``$SUBJECT'' will be replaced by the content of the ``subject:'' header X from the message being read on the standard input. A ``Precedence: X bulk'' header is also automatically prepended to the message. X X The reply is sent to the address(es) given in the ``reply-to:'' header if X one is found, and if not then to the address(es) given in the ``from:'' X header (as per the RFC-2822 rules for replying to a message). X X The addresses to which replies have been sent are recorded, along with X the time a reply was sent to them, in a db(3) database in the file X _._v_a_c_a_t_i_o_n_._d_b in your home directory. No reply will be sent to any ad- X dress which has been logged in this file unless the last reply was sent X more than _i_n_t_e_r_v_a_l (see --rr above) days ago. X X Fatal errors, such as calling vvaaccaattiioonn with incorrect arguments, or with X a non-existent _l_o_g_i_n_s, and any errors accessing files or errors sending X messages are logged in the system log file, using syslog(3), and if --dd is X given they are also printed to the standard error descriptor. X XEEXXIITT SSTTAATTUUSS X The vvaaccaattiioonn utility exits with a value of zero (0) on success, and X greater than zero (>0) if an error occurs. The non-zero exit values are X defined in <_s_y_s_e_x_i_t_s_._h>. X XFFIILLEESS X ~/.vacation.db default recipient database file name in the user's home X directory X ~/.vacation.msg default name of the file containing the message to send X XEEXXAAMMPPLLEESS X An example _~_/_._f_o_r_w_a_r_d file might contain: X X \eric, "|/usr/bin/vacation" X X which would save messages sent to you in your system mailbox (assuming X your login name was eric) and would also reply with copies of your X _~_/_._v_a_c_a_t_i_o_n_._m_s_g message (modulo the constraints outlined above). X X An example _~_/_._v_a_c_a_t_i_o_n_._m_s_g file might contain a message similar to the X following: X X From: eric@CS.Berkeley.EDU (Eric Allman) X Subject: I am on vacation X Summary: Re: $SUBJECT X Delivered-By-The-Graces-Of: The Vacation program X X I am on vacation until July 22. If you have something urgent, X please contact Keith Bostic . X -- X eric X XSSEEEE AALLSSOO X syslog(3), sendmail(8) X XHHIISSTTOORRYY X The vvaaccaattiioonn command first appeared in 4.3BSD. X X vvaaccaattiioonn was rewritten by Greg A. Woods to obey X RFC-2822. X XBBUUGGSS X The value of ``infinity'' for the reply interval is actually stored as X LONG_MAX. X X There's no facility for folding or filling text or headers in the outgo- X ing message. X X The entire recipient address of a sent reply, comments, multiple address- X es, and all, is stored in the recent recipients database. X X4.4BSD December 13, 2001 3 END-of-vacation.cat1 exit .