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