Home | History | Annotate | Line # | Download | only in vacation
vacation.c revision 1.22
      1 /*	$NetBSD: vacation.c,v 1.22 2003/04/20 03:32:50 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1983, 1987, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. All advertising materials mentioning features or use of this software
     16  *    must display the following acknowledgement:
     17  *	This product includes software developed by the University of
     18  *	California, Berkeley and its contributors.
     19  * 4. Neither the name of the University nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include <sys/cdefs.h>
     37 
     38 #ifndef lint
     39 __COPYRIGHT("@(#) Copyright (c) 1983, 1987, 1993\n\
     40 	The Regents of the University of California.  All rights reserved.\n");
     41 #endif /* not lint */
     42 
     43 #ifndef lint
     44 #if 0
     45 static char sccsid[] = "@(#)vacation.c	8.2 (Berkeley) 1/26/94";
     46 #endif
     47 __RCSID("$NetBSD: vacation.c,v 1.22 2003/04/20 03:32:50 christos Exp $");
     48 #endif /* not lint */
     49 
     50 /*
     51 **  Vacation
     52 **  Copyright (c) 1983  Eric P. Allman
     53 **  Berkeley, California
     54 */
     55 
     56 #include <sys/param.h>
     57 #include <sys/stat.h>
     58 
     59 #include <ctype.h>
     60 #include <db.h>
     61 #include <err.h>
     62 #include <errno.h>
     63 #include <fcntl.h>
     64 #include <paths.h>
     65 #include <pwd.h>
     66 #include <stdio.h>
     67 #include <stdlib.h>
     68 #include <string.h>
     69 #include <syslog.h>
     70 #include <time.h>
     71 #include <tzfile.h>
     72 #include <unistd.h>
     73 
     74 /*
     75  *  VACATION -- return a message to the sender when on vacation.
     76  *
     77  *	This program is invoked as a message receiver.  It returns a
     78  *	message specified by the user to whomever sent the mail, taking
     79  *	care not to return a message too often to prevent "I am on
     80  *	vacation" loops.
     81  */
     82 
     83 #define	MAXLINE	1024			/* max line from mail header */
     84 #define	VDB	".vacation.db"		/* dbm's database */
     85 #define	VMSG	".vacation.msg"		/* vacation message */
     86 
     87 typedef struct alias {
     88 	struct alias *next;
     89 	const char *name;
     90 } alias_t;
     91 alias_t *names;
     92 
     93 DB *db;
     94 char from[MAXLINE];
     95 static int tflag = 0;
     96 #define	APPARENTLY_TO	1
     97 #define	DELIVERED_TO	2
     98 
     99 int main(int, char **);
    100 int junkmail(void);
    101 int nsearch(const char *, const char *);
    102 void readheaders(void);
    103 int recent(void);
    104 void sendmessage(const char *);
    105 void setinterval(time_t);
    106 void setreply(void);
    107 void usage(void);
    108 
    109 int
    110 main(int argc, char **argv)
    111 {
    112 	struct passwd *pw;
    113 	alias_t *cur;
    114 	time_t interval;
    115 	int ch, iflag;
    116 	char *p;
    117 
    118 	opterr = iflag = 0;
    119 	interval = -1;
    120 	openlog("vacation", 0, LOG_USER);
    121 	while ((ch = getopt(argc, argv, "a:Iir:t:")) != -1)
    122 		switch((char)ch) {
    123 		case 'a':			/* alias */
    124 			if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t))))
    125 				break;
    126 			cur->name = optarg;
    127 			cur->next = names;
    128 			names = cur;
    129 			break;
    130 		case 'I':			/* backward compatible */
    131 		case 'i':			/* init the database */
    132 			iflag = 1;
    133 			break;
    134 		case 'r':
    135 			if (isdigit((unsigned char)*optarg)) {
    136 				interval = atol(optarg) * SECSPERDAY;
    137 				if (interval < 0)
    138 					usage();
    139 			}
    140 			else
    141 				interval = (time_t)LONG_MAX;	/* XXX */
    142 			break;
    143 		case 't':
    144 			for (p = optarg; *p; p++)
    145 				switch (*p) {
    146 				case 'A':
    147 					tflag |= APPARENTLY_TO;
    148 					break;
    149 				case 'D':
    150 					tflag |= DELIVERED_TO;
    151 					break;
    152 				default:
    153 					errx(1, "Unknown -t option `%c'", *p);
    154 				}
    155 			break;
    156 		case '?':
    157 		default:
    158 			usage();
    159 		}
    160 	argc -= optind;
    161 	argv += optind;
    162 
    163 	if (argc != 1) {
    164 		if (!iflag)
    165 			usage();
    166 		if (!(pw = getpwuid(getuid()))) {
    167 			syslog(LOG_ERR,
    168 			    "vacation: no such user uid %u.", getuid());
    169 			exit(1);
    170 		}
    171 	}
    172 	else if (!(pw = getpwnam(*argv))) {
    173 		syslog(LOG_ERR, "vacation: no such user %s.", *argv);
    174 		exit(1);
    175 	}
    176 	if (chdir(pw->pw_dir)) {
    177 		syslog(LOG_ERR,
    178 		    "vacation: no such directory %s.", pw->pw_dir);
    179 		exit(1);
    180 	}
    181 
    182 	db = dbopen(VDB, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
    183 	    S_IRUSR|S_IWUSR, DB_HASH, NULL);
    184 	if (!db) {
    185 		syslog(LOG_ERR, "vacation: %s: %m", VDB);
    186 		exit(1);
    187 	}
    188 
    189 	if (interval != -1)
    190 		setinterval(interval);
    191 
    192 	if (iflag) {
    193 		(void)(db->close)(db);
    194 		exit(0);
    195 	}
    196 
    197 	if (!(cur = malloc((size_t)sizeof(alias_t))))
    198 		exit(1);
    199 	cur->name = pw->pw_name;
    200 	cur->next = names;
    201 	names = cur;
    202 
    203 	readheaders();
    204 	if (!recent()) {
    205 		setreply();
    206 		(void)(db->close)(db);
    207 		sendmessage(pw->pw_name);
    208 	}
    209 	else
    210 		(void)(db->close)(db);
    211 	exit(0);
    212 	/* NOTREACHED */
    213 }
    214 
    215 /*
    216  * readheaders --
    217  *	read mail headers
    218  */
    219 void
    220 readheaders(void)
    221 {
    222 	alias_t *cur;
    223 	char *p;
    224 	int tome, cont;
    225 	char buf[MAXLINE];
    226 
    227 	cont = tome = 0;
    228 	while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
    229 		switch(*buf) {
    230 		case 'F':		/* "From " */
    231 			/* XXX should instead consider Return-Path: and Sender: */
    232 			cont = 0;
    233 			if (!strncmp(buf, "From ", 5)) {
    234 				for (p = buf + 5; *p && *p != ' '; ++p);
    235 				*p = '\0';
    236 				(void)strcpy(from, buf + 5);
    237 				if ((p = strchr(from, '\n')))
    238 					*p = '\0';
    239 				if (junkmail())
    240 					exit(0);
    241 			}
    242 			break;
    243 		case 'P':		/* "Precedence:" */
    244 			cont = 0;
    245 			if (strncasecmp(buf, "Precedence", 10) ||
    246 			    (buf[10] != ':' && buf[10] != ' ' &&
    247 			    buf[10] != '\t'))
    248 				break;
    249 			if (!(p = strchr(buf, ':')))
    250 				break;
    251 			while (*++p && isspace((unsigned char)*p));
    252 			if (!*p)
    253 				break;
    254 			if (!strncasecmp(p, "junk", 4) ||
    255 			    !strncasecmp(p, "bulk", 4) ||
    256 			    !strncasecmp(p, "list", 4))
    257 				exit(0);
    258 			break;
    259 		case 'C':		/* "Cc:" */
    260 			if (strncmp(buf, "Cc:", 3))
    261 				break;
    262 			cont = 1;
    263 			goto findme;
    264 		case 'T':		/* "To:" */
    265 			if (strncmp(buf, "To:", 3))
    266 				break;
    267 			cont = 1;
    268 			goto findme;
    269 		case 'A':
    270 			if ((tflag & APPARENTLY_TO) == 0 ||
    271 			    strncmp(buf, "Apparently-To:", 3))
    272 				break;
    273 			cont = 1;
    274 			goto findme;
    275 		case 'D':
    276 			if ((tflag & DELIVERED_TO) == 0 ||
    277 			    strncmp(buf, "Delivered-To:", 3))
    278 				break;
    279 			cont = 1;
    280 			goto findme;
    281 		default:
    282 			if (!isspace((unsigned char)*buf) || !cont || tome) {
    283 				cont = 0;
    284 				break;
    285 			}
    286 findme:			for (cur = names; !tome && cur; cur = cur->next)
    287 				tome += nsearch(cur->name, buf);
    288 		}
    289 	if (!tome)
    290 		exit(0);
    291 	if (!*from) {
    292 		syslog(LOG_ERR, "vacation: no initial \"From\" line.");
    293 		exit(1);
    294 	}
    295 }
    296 
    297 /*
    298  * nsearch --
    299  *	do a nice, slow, search of a string for a substring.
    300  */
    301 int
    302 nsearch(const char *name, const char *str)
    303 {
    304 	size_t len;
    305 
    306 	for (len = strlen(name); *str; ++str)
    307 		if (!strncasecmp(name, str, len))
    308 			return(1);
    309 	return(0);
    310 }
    311 
    312 /*
    313  * junkmail --
    314  *	read the header and return if automagic/junk/bulk/list mail
    315  */
    316 int
    317 junkmail(void)
    318 {
    319 	static struct ignore {
    320 		char	*name;
    321 		int	len;
    322 	} ignore[] = {
    323 		{ "-request", 8 },
    324 		{ "postmaster", 10 },
    325 		{ "uucp", 4 },
    326 		{ "mailer-daemon", 13 },
    327 		{ "mailer", 6 },
    328 		{ "-relay", 6 },
    329 		{NULL, 0 }
    330 	};
    331 	struct ignore *cur;
    332 	int len;
    333 	char *p;
    334 
    335 	/*
    336 	 * This is mildly amusing, and I'm not positive it's right; trying
    337 	 * to find the "real" name of the sender, assuming that addresses
    338 	 * will be some variant of:
    339 	 *
    340 	 * From site!site!SENDER%site.domain%site.domain (at) site.domain
    341 	 */
    342 	if (!(p = strchr(from, '%')))
    343 		if (!(p = strchr(from, '@'))) {
    344 			if ((p = strrchr(from, '!')))
    345 				++p;
    346 			else
    347 				p = from;
    348 			for (; *p; ++p);
    349 		}
    350 	len = p - from;
    351 	for (cur = ignore; cur->name; ++cur)
    352 		if (len >= cur->len &&
    353 		    !strncasecmp(cur->name, p - cur->len, cur->len))
    354 			return(1);
    355 	return(0);
    356 }
    357 
    358 #define	VIT	"__VACATION__INTERVAL__TIMER__"
    359 
    360 /*
    361  * recent --
    362  *	find out if user has gotten a vacation message recently.
    363  *	use memmove for machines with alignment restrictions
    364  */
    365 int
    366 recent(void)
    367 {
    368 	DBT key, data;
    369 	time_t then, next;
    370 
    371 	/* get interval time */
    372 	key.data = VIT;
    373 	key.size = sizeof(VIT);
    374 	if ((db->get)(db, &key, &data, 0))
    375 		next = SECSPERDAY * DAYSPERWEEK;
    376 	else
    377 		memmove(&next, data.data, sizeof(next));
    378 
    379 	/* get record for this address */
    380 	key.data = from;
    381 	key.size = strlen(from);
    382 	if (!(db->get)(db, &key, &data, 0)) {
    383 		memmove(&then, data.data, sizeof(then));
    384 		if (next == (time_t)LONG_MAX ||			/* XXX */
    385 		    then + next > time(NULL))
    386 			return(1);
    387 	}
    388 	return(0);
    389 }
    390 
    391 /*
    392  * setinterval --
    393  *	store the reply interval
    394  */
    395 void
    396 setinterval(time_t interval)
    397 {
    398 	DBT key, data;
    399 
    400 	key.data = VIT;
    401 	key.size = sizeof(VIT);
    402 	data.data = &interval;
    403 	data.size = sizeof(interval);
    404 	(void)(db->put)(db, &key, &data, 0);
    405 }
    406 
    407 /*
    408  * setreply --
    409  *	store that this user knows about the vacation.
    410  */
    411 void
    412 setreply(void)
    413 {
    414 	DBT key, data;
    415 	time_t now;
    416 
    417 	key.data = from;
    418 	key.size = strlen(from);
    419 	(void)time(&now);
    420 	data.data = &now;
    421 	data.size = sizeof(now);
    422 	(void)(db->put)(db, &key, &data, 0);
    423 }
    424 
    425 /*
    426  * sendmessage --
    427  *	exec sendmail to send the vacation file to sender
    428  */
    429 void
    430 sendmessage(const char *myname)
    431 {
    432 	FILE *mfp, *sfp;
    433 	int i;
    434 	int pvect[2];
    435 	char buf[MAXLINE];
    436 
    437 	mfp = fopen(VMSG, "r");
    438 	if (mfp == NULL) {
    439 		syslog(LOG_ERR, "vacation: no ~%s/%s file.", myname, VMSG);
    440 		exit(1);
    441 	}
    442 	if (pipe(pvect) < 0) {
    443 		syslog(LOG_ERR, "vacation: pipe: %m");
    444 		exit(1);
    445 	}
    446 	i = vfork();
    447 	if (i < 0) {
    448 		syslog(LOG_ERR, "vacation: fork: %m");
    449 		exit(1);
    450 	}
    451 	if (i == 0) {
    452 		dup2(pvect[0], 0);
    453 		close(pvect[0]);
    454 		close(pvect[1]);
    455 		close(fileno(mfp));
    456 		execl(_PATH_SENDMAIL, "sendmail", "-f", myname, "--", from,
    457 		    NULL);
    458 		syslog(LOG_ERR, "vacation: can't exec %s: %m",
    459 		    _PATH_SENDMAIL);
    460 		_exit(1);
    461 	}
    462 	close(pvect[0]);
    463 	sfp = fdopen(pvect[1], "w");
    464 	fprintf(sfp, "To: %s\n", from);
    465 	while (fgets(buf, sizeof buf, mfp))
    466 		fputs(buf, sfp);
    467 	fclose(mfp);
    468 	fclose(sfp);
    469 }
    470 
    471 void
    472 usage(void)
    473 {
    474 
    475 	syslog(LOG_ERR, "uid %u: usage: %s [-i] [-a alias] [-t A|D] login",
    476 	    getuid(), getprogname());
    477 	exit(1);
    478 }
    479