xref: /titanic_50/usr/src/cmd/sendmail/aux/vacation.c (revision 80868c5387b92f32fe0e8ea709e36cb535287e03)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  *
5  *	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
6  *	  All Rights Reserved
7  */
8 
9 /*
10  *  Vacation
11  *  Copyright (c) 1983  Eric P. Allman
12  *  Berkeley, California
13  *
14  *  Copyright (c) 1983 Regents of the University of California.
15  *  All rights reserved.  The Berkeley software License Agreement
16  *  specifies the terms and conditions for redistribution.
17  */
18 
19 #pragma ident	"%Z%%M%	%I%	%E% SMI"
20 
21 #ifndef lint
22 static char	SccsId[] = "%W%	%E% SMI";
23 #endif /* not lint */
24 
25 #include <stdio.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <sysexits.h>
30 #include <pwd.h>
31 #include <ndbm.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <fcntl.h>
35 #include <strings.h>
36 #include <errno.h>
37 
38 /*
39  *  VACATION -- return a message to the sender when on vacation.
40  *
41  *	This program could be invoked as a message receiver
42  *	when someone is on vacation.  It returns a message
43  *	specified by the user to whoever sent the mail, taking
44  *	care not to return a message too often to prevent
45  *	"I am on vacation" loops.
46  *
47  *	For best operation, this program should run setuid to
48  *	root or uucp or someone else that sendmail will believe
49  *	a -f flag from.  Otherwise, the user must be careful
50  *	to include a header on his .vacation.msg file.
51  *
52  *	Positional Parameters:
53  *		the user to collect the vacation message from.
54  *
55  *	Flag Parameters:
56  *		-I	initialize the database.
57  *		-d	turn on debugging.
58  *		-tT	set the timeout to T.  messages arriving more
59  *			often than T will be ignored to avoid loops.
60  *
61  *	Side Effects:
62  *		A message is sent back to the sender.
63  *
64  *	Author:
65  *		Eric Allman
66  *		UCB/INGRES
67  */
68 
69 #define	MAXLINE	256	/* max size of a line */
70 
71 #define	ONEWEEK	(60L*60L*24L*7L)
72 #define	MsgFile "/.vacation.msg"
73 #define	FilterFile "/.vacation.filter"
74 #define	DbFileBase "/.vacation"
75 #define	_PATH_TMP	"/tmp/vacation.XXXXXX"
76 
77 typedef int bool;
78 
79 #define	FALSE	0
80 #define	TRUE	1
81 
82 static time_t	Timeout = ONEWEEK;	/* timeout between notices per user */
83 static DBM	*db;
84 static bool	Debug = FALSE;
85 static bool	AnswerAll = FALSE;	/* default: answer if in To:/Cc: only */
86 static char	*Subject = NULL;	/* subject in message header */
87 static char	*EncodedSubject = NULL;	/* subject in message header */
88 static char	Charset[MAXLINE];	/* for use in reply message */
89 static char	*AliasList[MAXLINE];	/* list of aliases to allow */
90 static int	AliasCount = 0;
91 static char	*myname;		/* name of person "on vacation" */
92 static char	*homedir;		/* home directory of said person */
93 
94 extern time_t	convtime(char *, char);
95 extern bool	decode_rfc2047(char *, char *, char *);
96 
97 static bool	ask(char *);
98 static bool	junkmail(char *);
99 static bool	filter_ok(char *, char *);
100 static bool	knows(char *);
101 static bool	sameword(char *, char *);
102 static char	*getfrom(char **);
103 static char	*newstr(char *);
104 static void	AutoInstall();
105 static void	initialize(char *);
106 static void	sendmessage(char *, char *, char *);
107 static void	setknows(char *);
108 
109 void	usrerr(const char *, ...);
110 
111 int
112 main(argc, argv)
113 	int argc;
114 	char **argv;
115 {
116 	char *from;
117 	char *p, *at, *c;
118 	struct passwd *pw;
119 	char *shortfrom;
120 	char buf[MAXLINE];
121 	char *message_file = MsgFile;
122 	char *db_file_base = DbFileBase;
123 	char *filter_file = FilterFile;
124 	char *sender;
125 	bool sender_oob = FALSE;
126 	bool initialize_only = FALSE;
127 
128 	/* process arguments */
129 	while (--argc > 0 && (p = *++argv) != NULL && *p == '-')
130 	{
131 		switch (*++p)
132 		{
133 		    case 'a':	/* add this to list of acceptable aliases */
134 			AliasList[AliasCount++] = argv[1];
135 			if (argc > 0) {
136 				argc--; argv++;
137 			}
138 			break;
139 
140 		    case 'd':	/* debug */
141 			Debug = TRUE;
142 			break;
143 
144 		    case 'e':	/* alternate filter file */
145 			filter_file = argv[1];
146 			if (argc > 0) {
147 				argc--; argv++;
148 			}
149 			break;
150 
151 		    case 'f':	/* alternate database file name base */
152 			db_file_base = argv[1];
153 			if (argc > 0) {
154 				argc--; argv++;
155 			}
156 			break;
157 
158 		    case 'I':	/* initialize */
159 			initialize_only = TRUE;
160 			break;
161 
162 		    case 'j':	/* answer all mail, even if not in To/Cc */
163 			AnswerAll = TRUE;
164 			break;
165 
166 		    case 'm':	/* alternate message file */
167 			message_file = argv[1];
168 			if (argc > 0) {
169 				argc--; argv++;
170 			}
171 			break;
172 
173 		    case 's':	/* sender: use this instead of getfrom() */
174 			sender = argv[1];
175 			sender_oob = TRUE;
176 			if (argc > 0) {
177 				argc--; argv++;
178 			}
179 			break;
180 
181 		    case 't':	/* set timeout */
182 			Timeout = convtime(++p, 'w');
183 			break;
184 
185 		    default:
186 			usrerr("Unknown flag -%s", p);
187 			exit(EX_USAGE);
188 		}
189 	}
190 
191 	if (initialize_only)
192 	{
193 		initialize(db_file_base);
194 		exit(EX_OK);
195 	}
196 
197 	/* verify recipient argument */
198 	if (argc == 0)
199 		AutoInstall();
200 
201 	if (argc != 1)
202 	{
203 		usrerr("Usage: vacation username (or) vacation -I");
204 		exit(EX_USAGE);
205 	}
206 
207 	myname = p;
208 	Charset[0] = '\0';
209 
210 	/* find user's home directory */
211 	pw = getpwnam(myname);
212 	if (pw == NULL)
213 	{
214 		usrerr("user %s look up failed, name services outage ?",
215 		    myname);
216 		exit(EX_TEMPFAIL);
217 	}
218 	homedir = newstr(pw->pw_dir);
219 
220 	(void) snprintf(buf, sizeof (buf), "%s%s%s", homedir,
221 			(db_file_base[0] == '/') ? "" : "/", db_file_base);
222 	if (!(db = dbm_open(buf, O_RDWR, 0))) {
223 		usrerr("%s: %s\n", buf, strerror(errno));
224 		exit(EX_DATAERR);
225 	}
226 
227 	if (sender_oob)
228 	{
229 		at = strchr(sender, '@');
230 		if (at != NULL)
231 			for (c = at + 1; *c; c++)
232 				*c = (char)tolower((char)*c);
233 		from = sender;
234 		shortfrom = sender;
235 	}
236 	else
237 		/* read message from standard input (just from line) */
238 		from = getfrom(&shortfrom);
239 
240 	/* check if junk mail or this person is already informed */
241 	if (!junkmail(shortfrom) && filter_ok(shortfrom, filter_file) &&
242 	    !knows(shortfrom))
243 	{
244 		/* mark this person as knowing */
245 		setknows(shortfrom);
246 
247 		/* send the message back */
248 		(void) strlcpy(buf, homedir, sizeof (buf));
249 		if (message_file[0] != '/')
250 		    (void) strlcat(buf, "/", sizeof (buf));
251 		(void) strlcat(buf, message_file, sizeof (buf));
252 		if (Debug)
253 			printf("Sending %s to %s\n", buf, from);
254 		else
255 		{
256 			sendmessage(buf, from, myname);
257 			/*NOTREACHED*/
258 		}
259 	}
260 	return (EX_OK);
261 }
262 
263 /*
264  *  GETFROM -- read message from standard input and return sender
265  *
266  *	Parameters:
267  *		none.
268  *
269  *	Returns:
270  *		pointer to the sender address.
271  *
272  *	Side Effects:
273  *		Reads first line from standard input.
274  */
275 
276 static char *
277 getfrom(shortp)
278 char **shortp;
279 {
280 	static char line[MAXLINE];
281 	char *p, *start, *at, *bang, *c;
282 	char saveat;
283 
284 	/* read the from line */
285 	if (fgets(line, sizeof (line), stdin) == NULL ||
286 	    strncmp(line, "From ", 5) != NULL)
287 	{
288 		usrerr("No initial From line");
289 		exit(EX_PROTOCOL);
290 	}
291 
292 	/* find the end of the sender address and terminate it */
293 	start = &line[5];
294 	p = strchr(start, ' ');
295 	if (p == NULL)
296 	{
297 		usrerr("Funny From line '%s'", line);
298 		exit(EX_PROTOCOL);
299 	}
300 	*p = '\0';
301 
302 	/*
303 	 * Strip all but the rightmost UUCP host
304 	 * to prevent loops due to forwarding.
305 	 * Start searching leftward from the leftmost '@'.
306 	 *	a!b!c!d yields a short name of c!d
307 	 *	a!b!c!d@e yields a short name of c!d@e
308 	 *	e@a!b!c yields the same short name
309 	 */
310 #ifdef VDEBUG
311 printf("start='%s'\n", start);
312 #endif /* VDEBUG */
313 	*shortp = start;			/* assume whole addr */
314 	if ((at = strchr(start, '@')) == NULL)	/* leftmost '@' */
315 		at = p;				/* if none, use end of addr */
316 	saveat = *at;
317 	*at = '\0';
318 	if ((bang = strrchr(start, '!')) != NULL) {	/* rightmost '!' */
319 		char *bang2;
320 		*bang = '\0';
321 		/* 2nd rightmost '!' */
322 		if ((bang2 = strrchr(start, '!')) != NULL)
323 			*shortp = bang2 + 1;		/* move past ! */
324 		*bang = '!';
325 	}
326 	*at = saveat;
327 #ifdef VDEBUG
328 printf("place='%s'\n", *shortp);
329 #endif /* VDEBUG */
330 	for (c = at + 1; *c; c++)
331 		*c = (char)tolower((char)*c);
332 
333 	/* return the sender address */
334 	return (start);
335 }
336 
337 /*
338  *  JUNKMAIL -- read the header and tell us if this is junk/bulk mail.
339  *
340  *	Parameters:
341  *		from -- the Return-Path of the sender.  We assume that
342  *			anything from "*-REQUEST@*" is bulk mail.
343  *
344  *	Returns:
345  *		TRUE -- if this is junk or bulk mail (that is, if the
346  *			sender shouldn't receive a response).
347  *		FALSE -- if the sender deserves a response.
348  *
349  *	Side Effects:
350  *		May read the header from standard input.  When this
351  *		returns the position on stdin is undefined.
352  */
353 
354 static bool
355 junkmail(from)
356 	char *from;
357 {
358 	register char *p;
359 	char buf[MAXLINE+1];
360 	bool inside, onlist;
361 
362 	/* test for inhuman sender */
363 	p = strrchr(from, '@');
364 	if (p != NULL)
365 	{
366 		*p = '\0';
367 		if (sameword(&p[-8],  "-REQUEST") ||
368 		    sameword(&p[-10], "Postmaster") ||
369 		    sameword(&p[-13], "MAILER-DAEMON"))
370 		{
371 			*p = '@';
372 			return (TRUE);
373 		}
374 		*p = '@';
375 	}
376 
377 #define	Delims " \n\t:,:;()<>@!"
378 
379 	/* read the header looking for "interesting" lines */
380 	inside = FALSE;
381 	onlist = FALSE;
382 	while (fgets(buf, MAXLINE, stdin) != NULL && buf[0] != '\n')
383 	{
384 		if (buf[0] != ' ' && buf[0] != '\t' && strchr(buf, ':') == NULL)
385 			return (FALSE);			/* no header found */
386 
387 		p = strtok(buf, Delims);
388 		if (p == NULL)
389 			continue;
390 
391 		if (sameword(p, "To") || sameword(p, "Cc"))
392 		{
393 			inside = TRUE;
394 			p = strtok((char *)NULL, Delims);
395 			if (p == NULL)
396 				continue;
397 
398 		} else				/* continuation line? */
399 		    if (inside)
400 			inside =  (buf[0] == ' ' || buf[0] == '\t');
401 
402 		if (inside) {
403 		    int i;
404 
405 		    do {
406 			if (sameword(p, myname))
407 				onlist = TRUE;		/* I am on the list */
408 
409 			for (i = 0; i < AliasCount; i++)
410 			    if (sameword(p, AliasList[i]))
411 				onlist = TRUE;		/* alias on list */
412 
413 		    } while (p = strtok((char *)NULL, Delims));
414 		    continue;
415 		}
416 
417 		if (sameword(p, "Precedence"))
418 		{
419 			/* find the value of this field */
420 			p = strtok((char *)NULL, Delims);
421 			if (p == NULL)
422 				continue;
423 
424 			/* see if it is "junk" or "bulk" */
425 			p[4] = '\0';
426 			if (sameword(p, "junk") || sameword(p, "bulk"))
427 				return (TRUE);
428 		}
429 
430 		if (sameword(p, "Subject"))
431 		{
432 			char *decoded_subject;
433 
434 			Subject = newstr(buf+9);
435 			if (p = strrchr(Subject, '\n'))
436 				*p = '\0';
437 			EncodedSubject = newstr(Subject);
438 			decoded_subject = newstr(Subject);
439 			if (decode_rfc2047(Subject, decoded_subject, Charset))
440 				Subject = decoded_subject;
441 			else
442 				Charset[0] = '\0';
443 			if (Debug)
444 				printf("Subject=%s\n", Subject);
445 		}
446 	}
447 	if (AnswerAll)
448 		return (FALSE);
449 	else
450 		return (!onlist);
451 }
452 
453 /*
454  *  FILTER_OK -- see if the Return-Path is in the filter file.
455  *		 Note that a non-existent filter file means everything
456  *		 is OK, but an empty file means nothing is OK.
457  *
458  *	Parameters:
459  *		from -- the Return-Path of the sender.
460  *
461  *	Returns:
462  *		TRUE -- if this is in the filter file
463  *			(sender should receive a response).
464  *		FALSE -- if the sender does not deserve a response.
465  */
466 
467 static bool
468 filter_ok(from, filter_file)
469 	char *from;
470 	char *filter_file;
471 {
472 	char file[MAXLINE];
473 	char line[MAXLINE];
474 	char *match_start;
475 	size_t line_len, from_len;
476 	bool result = FALSE;
477 	bool negated = FALSE;
478 	FILE *f;
479 
480 	from_len = strlen(from);
481 	(void) strlcpy(file, homedir, sizeof (file));
482 	if (filter_file[0] != '/')
483 	    (void) strlcat(file, "/", sizeof (file));
484 	(void) strlcat(file, filter_file, sizeof (file));
485 	f = fopen(file, "r");
486 	if (f == NULL) {
487 		/*
488 		 * If the file does not exist, then there is no filter to
489 		 * apply, so we simply return TRUE.
490 		 */
491 		if (Debug)
492 			(void) printf("%s does not exist, filter ok.\n",
493 			    file);
494 		return (TRUE);
495 	}
496 	while (fgets(line, MAXLINE, f)) {
497 		line_len = strlen(line);
498 		/* zero out trailing newline */
499 		if (line[line_len - 1] == '\n')
500 			line[--line_len] = '\0';
501 		/* skip blank lines */
502 		if (line_len == 0)
503 			continue;
504 		/* skip comment lines */
505 		if (line[0] == '#')
506 			continue;
507 		if (line[0] == '!') {
508 			negated = TRUE;
509 			match_start = &line[1];
510 			line_len--;
511 		} else {
512 			negated = FALSE;
513 			match_start = &line[0];
514 		}
515 		if (strchr(line, '@') != NULL) {
516 			/* @ => full address */
517 			if (strcasecmp(match_start, from) == 0) {
518 				result = TRUE;
519 				if (Debug)
520 					(void) printf("filter match on %s\n",
521 					    line);
522 				break;
523 			}
524 		} else {
525 			/* no @ => domain */
526 			if (from_len <= line_len)
527 				continue;
528 			/*
529 			 * Make sure the last part of from is the domain line
530 			 * and that the character immediately preceding is an
531 			 * '@' or a '.', otherwise we could get false positives
532 			 * from e.g. twinsun.com for sun.com .
533 			 */
534 			if (strncasecmp(&from[from_len - line_len],
535 			    match_start, line_len) == 0 &&
536 			    (from[from_len - line_len -1] == '@' ||
537 			    from[from_len - line_len -1] == '.')) {
538 				result = TRUE;
539 				if (Debug)
540 					(void) printf("filter match on %s\n",
541 					    line);
542 				break;
543 			}
544 		}
545 	}
546 	(void) fclose(f);
547 	if (Debug && !result)
548 		(void) printf("no filter match\n");
549 	return (!negated && result);
550 }
551 
552 /*
553  *  KNOWS -- predicate telling if user has already been informed.
554  *
555  *	Parameters:
556  *		user -- the user who sent this message.
557  *
558  *	Returns:
559  *		TRUE if 'user' has already been informed that the
560  *			recipient is on vacation.
561  *		FALSE otherwise.
562  *
563  *	Side Effects:
564  *		none.
565  */
566 
567 static bool
568 knows(user)
569 	char *user;
570 {
571 	datum key, data;
572 	time_t now, then;
573 
574 	(void) time(&now);
575 	key.dptr = user;
576 	key.dsize = strlen(user) + 1;
577 	data = dbm_fetch(db, key);
578 	if (data.dptr == NULL)
579 		return (FALSE);
580 
581 	bcopy(data.dptr, (char *)&then, sizeof (then));
582 	if (then + Timeout < now)
583 		return (FALSE);
584 	if (Debug)
585 		printf("User %s already knows\n", user);
586 	return (TRUE);
587 }
588 
589 /*
590  *  SETKNOWS -- set that this user knows about the vacation.
591  *
592  *	Parameters:
593  *		user -- the user who should be marked.
594  *
595  *	Returns:
596  *		none.
597  *
598  *	Side Effects:
599  *		The dbm file is updated as appropriate.
600  */
601 
602 static void
603 setknows(user)
604 	char *user;
605 {
606 	datum key, data;
607 	time_t now;
608 
609 	key.dptr = user;
610 	key.dsize = strlen(user) + 1;
611 	(void) time(&now);
612 	data.dptr = (char *)&now;
613 	data.dsize = sizeof (now);
614 	dbm_store(db, key, data, DBM_REPLACE);
615 }
616 
617 static bool
618 any8bitchars(line)
619 	char *line;
620 {
621 	char *c;
622 
623 	for (c = line; *c; c++)
624 		if (*c & 0x80)
625 			return (TRUE);
626 	return (FALSE);
627 }
628 
629 /*
630  *  SENDMESSAGE -- send a message to a particular user.
631  *
632  *	Parameters:
633  *		msgf -- filename containing the message.
634  *		user -- user who should receive it.
635  *
636  *	Returns:
637  *		none.
638  *
639  *	Side Effects:
640  *		sends mail to 'user' using /usr/lib/sendmail.
641  */
642 
643 static void
644 sendmessage(msgf, user, myname)
645 	char *msgf;
646 	char *user;
647 	char *myname;
648 {
649 	FILE *f, *fpipe, *tmpf;
650 	char line[MAXLINE];
651 	char *p, *tmpf_name;
652 	int i, pipefd[2], tmpfd;
653 	bool seen8bitchars = FALSE;
654 	bool in_header = TRUE;
655 
656 	/* find the message to send */
657 	f = fopen(msgf, "r");
658 	if (f == NULL)
659 	{
660 		f = fopen("/etc/mail/vacation.def", "r");
661 		if (f == NULL)
662 			usrerr("No message to send");
663 			exit(EX_OSFILE);
664 	}
665 
666 	if (pipe(pipefd) < 0) {
667 		usrerr("pipe() failed");
668 		exit(EX_OSERR);
669 	}
670 	i = fork();
671 	if (i < 0) {
672 		usrerr("fork() failed");
673 		exit(EX_OSERR);
674 	}
675 	if (i == 0) {
676 		dup2(pipefd[0], 0);
677 		close(pipefd[0]);
678 		close(pipefd[1]);
679 		fclose(f);
680 		execl("/usr/lib/sendmail", "sendmail", "-eq", "-f", myname,
681 			"--", user, NULL);
682 		usrerr("can't exec /usr/lib/sendmail");
683 		exit(EX_OSERR);
684 	}
685 	close(pipefd[0]);
686 	fpipe = fdopen(pipefd[1], "w");
687 	if (fpipe == NULL) {
688 		usrerr("fdopen() failed");
689 		exit(EX_OSERR);
690 	}
691 	fprintf(fpipe, "To: %s\n", user);
692 	fputs("Auto-Submitted: auto-replied\n", fpipe);
693 	fputs("X-Mailer: vacation %I%\n", fpipe);
694 
695 	/*
696 	 * We used to write directly to the pipe.  But now we need to know
697 	 * what character set to use, and we need to examine the entire
698 	 * message to determine this.  So write to a temp file first.
699 	 */
700 	tmpf_name = strdup(_PATH_TMP);
701 	if (tmpf_name == NULL) {
702 		usrerr("newstr: cannot alloc memory");
703 		exit(EX_OSERR);
704 	}
705 	tmpfd = -1;
706 	tmpfd = mkstemp(tmpf_name);
707 	if (tmpfd == -1) {
708 		usrerr("can't open temp file %s", tmpf_name);
709 		exit(EX_OSERR);
710 	}
711 	tmpf = fdopen(tmpfd, "w");
712 	if (tmpf == NULL) {
713 		usrerr("can't open temp file %s", tmpf_name);
714 		exit(EX_OSERR);
715 	}
716 	while (fgets(line, MAXLINE, f)) {
717 		/*
718 		 * Check for a line with no ':' character.  If it's just \n,
719 		 * we're at the end of the headers and all is fine.  Or if
720 		 * it starts with white-space, then it's a continuation header.
721 		 * Otherwise, it's the start of the body, which means the
722 		 * header/body separator was skipped.  So output it.
723 		 */
724 		if (in_header && line[0] != '\0' && strchr(line, ':') == NULL) {
725 			if (line[0] == '\n')
726 				in_header = FALSE;
727 			else if (!isspace(line[0])) {
728 				in_header = FALSE;
729 				fputs("\n", tmpf);
730 			}
731 		}
732 		p = strchr(line, '$');
733 		if (p && strncmp(p, "$SUBJECT", 8) == 0) {
734 			*p = '\0';
735 			seen8bitchars |= any8bitchars(line);
736 			fputs(line, tmpf);
737 			if (Subject) {
738 				if (in_header)
739 					fputs(EncodedSubject, tmpf);
740 				else {
741 					seen8bitchars |= any8bitchars(Subject);
742 					fputs(Subject, tmpf);
743 				}
744 			}
745 			seen8bitchars |= any8bitchars(p+8);
746 			fputs(p+8, tmpf);
747 			continue;
748 		}
749 		seen8bitchars |= any8bitchars(line);
750 		fputs(line, tmpf);
751 	}
752 	fclose(f);
753 	fclose(tmpf);
754 
755 	/*
756 	 * If we haven't seen a funky Subject with Charset, use the default.
757 	 * If we have and it's us-ascii, 8-bit chars in the message file will
758 	 * still result in iso-8859-1.
759 	 */
760 	if (Charset[0] == '\0')
761 		(void) strlcpy(Charset, (seen8bitchars) ? "iso-8859-1" :
762 		    "us-ascii", sizeof (Charset));
763 	else if ((strcasecmp(Charset, "us-ascii") == 0) && seen8bitchars)
764 		(void) strlcpy(Charset, "iso-8859-1", sizeof (Charset));
765 	if (Debug)
766 		printf("Charset is %s\n", Charset);
767 	fprintf(fpipe, "Content-Type: text/plain; charset=%s\n", Charset);
768 	fputs("Mime-Version: 1.0\n", fpipe);
769 
770 	/*
771 	 * Now read back in from the temp file and write to the pipe.
772 	 */
773 	tmpf = fopen(tmpf_name, "r");
774 	if (tmpf == NULL) {
775 		usrerr("can't open temp file %s", tmpf_name);
776 		exit(EX_OSERR);
777 	}
778 	while (fgets(line, MAXLINE, tmpf))
779 		fputs(line, fpipe);
780 	fclose(fpipe);
781 	fclose(tmpf);
782 	(void) unlink(tmpf_name);
783 	free(tmpf_name);
784 }
785 
786 /*
787  *  INITIALIZE -- initialize the database before leaving for vacation
788  *
789  *	Parameters:
790  *		none.
791  *
792  *	Returns:
793  *		none.
794  *
795  *	Side Effects:
796  *		Initializes the files .vacation.{pag,dir} in the
797  *		caller's home directory.
798  */
799 
800 static void
801 initialize(db_file_base)
802 	char *db_file_base;
803 {
804 	char *homedir;
805 	char buf[MAXLINE];
806 	DBM *db;
807 
808 	setgid(getgid());
809 	setuid(getuid());
810 	homedir = getenv("HOME");
811 	if (homedir == NULL) {
812 		usrerr("No home!");
813 		exit(EX_NOUSER);
814 	}
815 	(void) snprintf(buf, sizeof (buf), "%s%s%s", homedir,
816 		(db_file_base[0] == '/') ? "" : "/", db_file_base);
817 
818 	if (!(db = dbm_open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0644))) {
819 		usrerr("%s: %s\n", buf, strerror(errno));
820 		exit(EX_DATAERR);
821 	}
822 	dbm_close(db);
823 }
824 
825 /*
826  *  USRERR -- print user error
827  *
828  *	Parameters:
829  *		f -- format.
830  *
831  *	Returns:
832  *		none.
833  *
834  *	Side Effects:
835  *		none.
836  */
837 
838 /* PRINTFLIKE1 */
839 void
840 usrerr(const char *f, ...)
841 {
842 	va_list alist;
843 
844 	va_start(alist, f);
845 	(void) fprintf(stderr, "vacation: ");
846 	(void) vfprintf(stderr, f, alist);
847 	(void) fprintf(stderr, "\n");
848 	va_end(alist);
849 }
850 
851 /*
852  *  NEWSTR -- copy a string
853  *
854  *	Parameters:
855  *		s -- the string to copy.
856  *
857  *	Returns:
858  *		A copy of the string.
859  *
860  *	Side Effects:
861  *		none.
862  */
863 
864 static char *
865 newstr(s)
866 	char *s;
867 {
868 	char *p;
869 	size_t s_sz = strlen(s);
870 
871 	p = malloc(s_sz + 1);
872 	if (p == NULL)
873 	{
874 		usrerr("newstr: cannot alloc memory");
875 		exit(EX_OSERR);
876 	}
877 	(void) strlcpy(p, s, s_sz + 1);
878 	return (p);
879 }
880 
881 /*
882  *  SAMEWORD -- return TRUE if the words are the same
883  *
884  *	Ignores case.
885  *
886  *	Parameters:
887  *		a, b -- the words to compare.
888  *
889  *	Returns:
890  *		TRUE if a & b match exactly (modulo case)
891  *		FALSE otherwise.
892  *
893  *	Side Effects:
894  *		none.
895  */
896 
897 static bool
898 sameword(a, b)
899 	register char *a, *b;
900 {
901 	char ca, cb;
902 
903 	do
904 	{
905 		ca = *a++;
906 		cb = *b++;
907 		if (isascii(ca) && isupper(ca))
908 			ca = ca - 'A' + 'a';
909 		if (isascii(cb) && isupper(cb))
910 			cb = cb - 'A' + 'a';
911 	} while (ca != '\0' && ca == cb);
912 	return (ca == cb);
913 }
914 
915 /*
916  * When invoked with no arguments, we fall into an automatic installation
917  * mode, stepping the user through a default installation.
918  */
919 
920 static void
921 AutoInstall()
922 {
923 	char file[MAXLINE];
924 	char forward[MAXLINE];
925 	char cmd[MAXLINE];
926 	char line[MAXLINE];
927 	char *editor;
928 	FILE *f;
929 	struct passwd *pw;
930 	extern mode_t umask(mode_t cmask);
931 
932 	umask(022);
933 	pw = getpwuid(getuid());
934 	if (pw == NULL) {
935 		usrerr("User ID unknown");
936 		exit(EX_NOUSER);
937 	}
938 	myname = strdup(pw->pw_name);
939 	if (myname == NULL) {
940 		usrerr("Out of memory");
941 		exit(EX_OSERR);
942 	}
943 	homedir = getenv("HOME");
944 	if (homedir == NULL) {
945 		usrerr("Home directory unknown");
946 		exit(EX_NOUSER);
947 	}
948 
949 	printf("This program can be used to answer your mail automatically\n");
950 	printf("when you go away on vacation.\n");
951 	(void) strlcpy(file, homedir, sizeof (file));
952 	(void) strlcat(file, MsgFile, sizeof (file));
953 	do {
954 		f = fopen(file, "r");
955 		if (f) {
956 			printf("You have a message file in %s.\n", file);
957 			if (ask("Would you like to see it")) {
958 				(void) snprintf(cmd, sizeof (cmd),
959 				    "/usr/bin/more %s", file);
960 				system(cmd);
961 			}
962 			if (ask("Would you like to edit it"))
963 				f = NULL;
964 		} else {
965 			printf("You need to create a message file"
966 				" in %s first.\n", file);
967 			f = fopen(file, "w");
968 			if (f == NULL) {
969 				usrerr("Cannot open %s", file);
970 				exit(EX_CANTCREAT);
971 			}
972 			fprintf(f, "Subject: away from my mail\n");
973 			fprintf(f, "\nI will not be reading my mail"
974 				" for a while.\n");
975 			fprintf(f, "Your mail regarding \"$SUBJECT\" will"
976 				" be read when I return.\n");
977 			fclose(f);
978 			f = NULL;
979 		}
980 		if (f == NULL) {
981 			editor = getenv("VISUAL");
982 			if (editor == NULL)
983 				editor = getenv("EDITOR");
984 			if (editor == NULL)
985 				editor = "/usr/bin/vi";
986 			(void) snprintf(cmd, sizeof (cmd), "%s %s", editor,
987 			    file);
988 			printf("Please use your editor (%s)"
989 				" to edit this file.\n", editor);
990 			system(cmd);
991 		}
992 	} while (f == NULL);
993 	fclose(f);
994 	(void) strlcpy(forward, homedir, sizeof (forward));
995 	(void) strlcat(forward, "/.forward", sizeof (forward));
996 	f = fopen(forward, "r");
997 	if (f) {
998 		printf("You have a .forward file"
999 			" in your home directory containing:\n");
1000 		while (fgets(line, MAXLINE, f))
1001 			printf("    %s", line);
1002 		fclose(f);
1003 		if (!ask("Would you like to remove it and"
1004 				" disable the vacation feature"))
1005 			exit(EX_OK);
1006 		if (unlink(forward))
1007 			perror("Error removing .forward file:");
1008 		else
1009 			printf("Back to normal reception of mail.\n");
1010 		exit(EX_OK);
1011 	}
1012 
1013 	printf("To enable the vacation feature"
1014 		" a \".forward\" file is created.\n");
1015 	if (!ask("Would you like to enable the vacation feature")) {
1016 		printf("OK, vacation feature NOT enabled.\n");
1017 		exit(EX_OK);
1018 	}
1019 	f = fopen(forward, "w");
1020 	if (f == NULL) {
1021 		perror("Error opening .forward file");
1022 		exit(EX_CANTCREAT);
1023 	}
1024 	fprintf(f, "\\%s, \"|/usr/bin/vacation %s\"\n", myname, myname);
1025 	fclose(f);
1026 	printf("Vacation feature ENABLED."
1027 		" Please remember to turn it off when\n");
1028 	printf("you get back from vacation. Bon voyage.\n");
1029 
1030 	initialize(DbFileBase);
1031 	exit(EX_OK);
1032 }
1033 
1034 
1035 /*
1036  * Ask the user a question until we get a reasonable answer
1037  */
1038 
1039 static bool
1040 ask(prompt)
1041 	char *prompt;
1042 {
1043 	char line[MAXLINE];
1044 	char *res;
1045 
1046 	for (;;) {
1047 		printf("%s? ", prompt);
1048 		fflush(stdout);
1049 		res = fgets(line, sizeof (line), stdin);
1050 		if (res == NULL)
1051 			return (FALSE);
1052 		if (res[0] == 'y' || res[0] == 'Y')
1053 			return (TRUE);
1054 		if (res[0] == 'n' || res[0] == 'N')
1055 			return (FALSE);
1056 		printf("Please reply \"yes\" or \"no\" (\'y\' or \'n\')\n");
1057 	}
1058 }
1059