Home | History | Annotate | Line # | Download | only in vacation
vacation.c revision 1.27
      1 /*	$NetBSD: vacation.c,v 1.27 2004/04/04 01:19:35 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.27 2004/04/04 01:19:35 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 	opterr = iflag = 0;
    121 	interval = -1;
    122 	openlog(getprogname(), 0, LOG_USER);
    123 	while ((ch = getopt(argc, argv, "a:df:Iir:t:")) != -1)
    124 		switch((char)ch) {
    125 		case 'a':			/* alias */
    126 			if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t))))
    127 				break;
    128 			cur->name = optarg;
    129 			cur->next = names;
    130 			names = cur;
    131 			break;
    132 		case 'd':
    133 			debug++;
    134 			break;
    135 		case 'f':
    136 			for (p = optarg; *p; p++)
    137 				switch (*p) {
    138 				case 'F':
    139 					fflag |= FROM_FROM;
    140 					break;
    141 				case 'R':
    142 					fflag |= RETURN_PATH_FROM;
    143 					break;
    144 				case 'S':
    145 					fflag |= SENDER_FROM;
    146 					break;
    147 				default:
    148 					errx(1, "Unknown -f option `%c'", *p);
    149 				}
    150 			break;
    151 		case 'I':			/* backward compatible */
    152 		case 'i':			/* init the database */
    153 			iflag = 1;
    154 			break;
    155 		case 'r':
    156 			if (isdigit((unsigned char)*optarg)) {
    157 				interval = atol(optarg) * SECSPERDAY;
    158 				if (interval < 0)
    159 					usage();
    160 			}
    161 			else
    162 				interval = (time_t)LONG_MAX;	/* XXX */
    163 			break;
    164 		case 't':
    165 			for (p = optarg; *p; p++)
    166 				switch (*p) {
    167 				case 'A':
    168 					tflag |= APPARENTLY_TO;
    169 					break;
    170 				case 'D':
    171 					tflag |= DELIVERED_TO;
    172 					break;
    173 				default:
    174 					errx(1, "Unknown -t option `%c'", *p);
    175 				}
    176 			break;
    177 		case '?':
    178 		default:
    179 			usage();
    180 		}
    181 	argc -= optind;
    182 	argv += optind;
    183 
    184 	if (argc != 1) {
    185 		if (!iflag)
    186 			usage();
    187 		if (!(pw = getpwuid(getuid()))) {
    188 			syslog(LOG_ERR, "%s: no such user uid %u.",
    189 			    getprogname(), getuid());
    190 			exit(1);
    191 		}
    192 	}
    193 	else if (!(pw = getpwnam(*argv))) {
    194 		syslog(LOG_ERR, "%s: no such user %s.",
    195 		    getprogname(), *argv);
    196 		exit(1);
    197 	}
    198 	if (chdir(pw->pw_dir)) {
    199 		syslog(LOG_ERR, "%s: no such directory %s.",
    200 		    getprogname(), pw->pw_dir);
    201 		exit(1);
    202 	}
    203 
    204 	db = dbopen(VDB, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0),
    205 	    S_IRUSR|S_IWUSR, DB_HASH, NULL);
    206 	if (!db) {
    207 		syslog(LOG_ERR, "%s: %s: %m", getprogname(), VDB);
    208 		exit(1);
    209 	}
    210 
    211 	if (interval != -1)
    212 		setinterval(interval);
    213 
    214 	if (iflag) {
    215 		(void)(db->close)(db);
    216 		exit(0);
    217 	}
    218 
    219 	if (!(cur = malloc((size_t)sizeof(alias_t)))) {
    220 		syslog(LOG_ERR, "%s: %m", getprogname());
    221 		(void)(db->close)(db);
    222 		exit(1);
    223 	}
    224 	cur->name = pw->pw_name;
    225 	cur->next = names;
    226 	names = cur;
    227 
    228 	if ((rv = readheaders()) != -1) {
    229 		(void)(db->close)(db);
    230 		exit(rv);
    231 	}
    232 
    233 	if (!recent()) {
    234 		setreply();
    235 		(void)(db->close)(db);
    236 		sendmessage(pw->pw_name);
    237 	}
    238 	else
    239 		(void)(db->close)(db);
    240 	exit(0);
    241 	/* NOTREACHED */
    242 }
    243 
    244 /*
    245  * readheaders --
    246  *	read mail headers
    247  */
    248 int
    249 readheaders(void)
    250 {
    251 	alias_t *cur;
    252 	char *p;
    253 	int tome, cont;
    254 	char buf[MAXLINE];
    255 
    256 	cont = tome = 0;
    257 	while (fgets(buf, sizeof(buf), stdin) && *buf != '\n')
    258 		switch(*buf) {
    259 		case 'F':		/* "From " or "From:" */
    260 			cont = 0;
    261 			if (!strncmp(buf, "From ", 5))
    262 				getfrom(buf + 5);
    263 			if ((fflag & FROM_FROM) != 0 &&
    264 			    strncmp(buf, "From:", 5) == 0)
    265 				getfrom(buf + 5);
    266 			break;
    267 		case 'P':		/* "Precedence:" */
    268 			cont = 0;
    269 			if (strncasecmp(buf, "Precedence", 10) ||
    270 			    (buf[10] != ':' && buf[10] != ' ' &&
    271 			    buf[10] != '\t'))
    272 				break;
    273 			if (!(p = strchr(buf, ':')))
    274 				break;
    275 			while (*++p && isspace((unsigned char)*p))
    276 				continue;
    277 			if (!*p)
    278 				break;
    279 			if (!strncasecmp(p, "junk", 4) ||
    280 			    !strncasecmp(p, "bulk", 4) ||
    281 			    !strncasecmp(p, "list", 4))
    282 				exit(0);
    283 			break;
    284 		case 'C':		/* "Cc:" */
    285 			if (strncmp(buf, "Cc:", 3))
    286 				break;
    287 			cont = 1;
    288 			goto findme;
    289 		case 'T':		/* "To:" */
    290 			if (strncmp(buf, "To:", 3))
    291 				break;
    292 			cont = 1;
    293 			goto findme;
    294 		case 'A':		/* "Apparently-To:" */
    295 			if ((tflag & APPARENTLY_TO) == 0 ||
    296 			    strncmp(buf, "Apparently-To:", 14))
    297 				break;
    298 			cont = 1;
    299 			goto findme;
    300 		case 'D':		/* "Delivered-To:" */
    301 			if ((tflag & DELIVERED_TO) == 0 ||
    302 			    strncmp(buf, "Delivered-To:", 13))
    303 				break;
    304 			cont = 1;
    305 			goto findme;
    306 		case 'R':		/* "Return-Path:" */
    307 			cont = 0;
    308 			if ((fflag & RETURN_PATH_FROM) != 0 &&
    309 			    strncmp(buf, "Return-Path:", 12) == 0)
    310 				getfrom(buf + 12);
    311 			break;
    312 		case 'S':		/* "Sender:" */
    313 			cont = 0;
    314 			if ((fflag & SENDER_FROM) != 0 &&
    315 			    strncmp(buf, "Sender:", 7) == 0)
    316 				getfrom(buf + 7);
    317 			break;
    318 		default:
    319 			if (!isspace((unsigned char)*buf) || !cont || tome) {
    320 				cont = 0;
    321 				break;
    322 			}
    323 findme:			for (cur = names; !tome && cur; cur = cur->next)
    324 				tome += nsearch(cur->name, buf);
    325 		}
    326 	if (!tome)
    327 		return 0;
    328 	if (!*from) {
    329 		syslog(LOG_ERR, "%s: no initial \"From\" line.",
    330 			getprogname());
    331 		return 1;
    332 	}
    333 	return -1;
    334 }
    335 
    336 /*
    337  * nsearch --
    338  *	do a nice, slow, search of a string for a substring.
    339  */
    340 int
    341 nsearch(const char *name, const char *str)
    342 {
    343 	size_t len;
    344 
    345 	for (len = strlen(name); *str; ++str)
    346 		if (!strncasecmp(name, str, len))
    347 			return(1);
    348 	return(0);
    349 }
    350 
    351 /*
    352  * getfrom --
    353  *	return the first string in the buffer, stripping leading and trailing
    354  *	blanks and <>.
    355  */
    356 void
    357 getfrom(char *buf)
    358 {
    359 	char *s, *p;
    360 
    361 	if ((s = strchr(buf, '<')) != NULL)
    362 		s++;
    363 	else
    364 		s = buf;
    365 
    366 	for (; *s && isspace((unsigned char)*s); s++)
    367 		continue;
    368 	for (p = s; *p && !isspace((unsigned char)*p); p++)
    369 		continue;
    370 	if (*--p == '>')
    371 		*p = '\0';
    372 	else
    373 		*++p = '\0';
    374 	(void)strlcpy(from, s, sizeof(from));
    375 	if (junkmail())
    376 		exit(0);
    377 }
    378 
    379 /*
    380  * junkmail --
    381  *	read the header and return if automagic/junk/bulk/list mail
    382  */
    383 int
    384 junkmail(void)
    385 {
    386 	static struct ignore {
    387 		char	*name;
    388 		int	len;
    389 	} ignore[] = {
    390 		{ "-request", 8 },
    391 		{ "postmaster", 10 },
    392 		{ "uucp", 4 },
    393 		{ "mailer-daemon", 13 },
    394 		{ "mailer", 6 },
    395 		{ "-relay", 6 },
    396 		{NULL, 0 }
    397 	};
    398 	struct ignore *cur;
    399 	int len;
    400 	char *p;
    401 
    402 	/*
    403 	 * This is mildly amusing, and I'm not positive it's right; trying
    404 	 * to find the "real" name of the sender, assuming that addresses
    405 	 * will be some variant of:
    406 	 *
    407 	 * From site!site!SENDER%site.domain%site.domain (at) site.domain
    408 	 */
    409 	if (!(p = strchr(from, '%')))
    410 		if (!(p = strchr(from, '@'))) {
    411 			if ((p = strrchr(from, '!')))
    412 				++p;
    413 			else
    414 				p = from;
    415 			for (; *p; ++p);
    416 		}
    417 	len = p - from;
    418 	for (cur = ignore; cur->name; ++cur)
    419 		if (len >= cur->len &&
    420 		    !strncasecmp(cur->name, p - cur->len, cur->len))
    421 			return(1);
    422 	return(0);
    423 }
    424 
    425 #define	VIT	"__VACATION__INTERVAL__TIMER__"
    426 
    427 /*
    428  * recent --
    429  *	find out if user has gotten a vacation message recently.
    430  *	use memmove for machines with alignment restrictions
    431  */
    432 int
    433 recent(void)
    434 {
    435 	DBT key, data;
    436 	time_t then, next;
    437 
    438 	/* get interval time */
    439 	key.data = VIT;
    440 	key.size = sizeof(VIT);
    441 	if ((db->get)(db, &key, &data, 0))
    442 		next = SECSPERDAY * DAYSPERWEEK;
    443 	else
    444 		memmove(&next, data.data, sizeof(next));
    445 
    446 	/* get record for this address */
    447 	key.data = from;
    448 	key.size = strlen(from);
    449 	if (!(db->get)(db, &key, &data, 0)) {
    450 		memmove(&then, data.data, sizeof(then));
    451 		if (next == (time_t)LONG_MAX ||			/* XXX */
    452 		    then + next > time(NULL))
    453 			return(1);
    454 	}
    455 	return(0);
    456 }
    457 
    458 /*
    459  * setinterval --
    460  *	store the reply interval
    461  */
    462 void
    463 setinterval(time_t interval)
    464 {
    465 	DBT key, data;
    466 
    467 	key.data = VIT;
    468 	key.size = sizeof(VIT);
    469 	data.data = &interval;
    470 	data.size = sizeof(interval);
    471 	(void)(db->put)(db, &key, &data, 0);
    472 }
    473 
    474 /*
    475  * setreply --
    476  *	store that this user knows about the vacation.
    477  */
    478 void
    479 setreply(void)
    480 {
    481 	DBT key, data;
    482 	time_t now;
    483 
    484 	key.data = from;
    485 	key.size = strlen(from);
    486 	(void)time(&now);
    487 	data.data = &now;
    488 	data.size = sizeof(now);
    489 	(void)(db->put)(db, &key, &data, 0);
    490 }
    491 
    492 /*
    493  * sendmessage --
    494  *	exec sendmail to send the vacation file to sender
    495  */
    496 void
    497 sendmessage(const char *myname)
    498 {
    499 	FILE *mfp, *sfp;
    500 	int i;
    501 	int pvect[2];
    502 	char buf[MAXLINE];
    503 
    504 	mfp = fopen(VMSG, "r");
    505 	if (mfp == NULL) {
    506 		syslog(LOG_ERR, "%s: no ~%s/%s file.", getprogname(),
    507 		    myname, VMSG);
    508 		exit(1);
    509 	}
    510 
    511 	if (debug) {
    512 		sfp = stdout;
    513 	} else {
    514 		if (pipe(pvect) < 0) {
    515 			syslog(LOG_ERR, "%s: pipe: %m", getprogname());
    516 			exit(1);
    517 		}
    518 		i = vfork();
    519 		if (i < 0) {
    520 			syslog(LOG_ERR, "%s: fork: %m", getprogname());
    521 			exit(1);
    522 		}
    523 		if (i == 0) {
    524 			dup2(pvect[0], 0);
    525 			close(pvect[0]);
    526 			close(pvect[1]);
    527 			close(fileno(mfp));
    528 			execl(_PATH_SENDMAIL, "sendmail", "-f", myname,
    529 			    "--", from, NULL);
    530 			syslog(LOG_ERR, "%s: can't exec %s: %m",
    531 			    getprogname(), _PATH_SENDMAIL);
    532 			_exit(1);
    533 		}
    534 		(void)close(pvect[0]);
    535 		sfp = fdopen(pvect[1], "w");
    536 	}
    537 	fprintf(sfp, "To: %s\n", from);
    538 	while (fgets(buf, sizeof buf, mfp))
    539 		fputs(buf, sfp);
    540 	(void)fclose(mfp);
    541 	if (sfp != stdout)
    542 		(void)fclose(sfp);
    543 }
    544 
    545 void
    546 usage(void)
    547 {
    548 
    549 	syslog(LOG_ERR, "uid %u: Usage: %s [-id] [-a alias] [-t A|D] [-f F|R|S]"
    550 	    " login", getuid(), getprogname());
    551 	exit(1);
    552 }
    553