xref: /titanic_44/usr/src/cmd/sendmail/aux/vacation.c (revision 587032cf0967234b39ccb50adca936a367841063)
1 /*
2  * Copyright 2006 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 	while (fgets(buf, MAXLINE, stdin) != NULL)
261 		continue; /* drain input */
262 	return (EX_OK);
263 }
264 
265 /*
266  *  GETFROM -- read message from standard input and return sender
267  *
268  *	Parameters:
269  *		none.
270  *
271  *	Returns:
272  *		pointer to the sender address.
273  *
274  *	Side Effects:
275  *		Reads first line from standard input.
276  */
277 
278 static char *
279 getfrom(shortp)
280 char **shortp;
281 {
282 	static char line[MAXLINE];
283 	char *p, *start, *at, *bang, *c;
284 	char saveat;
285 
286 	/* read the from line */
287 	if (fgets(line, sizeof (line), stdin) == NULL ||
288 	    strncmp(line, "From ", 5) != NULL)
289 	{
290 		usrerr("No initial From line");
291 		exit(EX_PROTOCOL);
292 	}
293 
294 	/* find the end of the sender address and terminate it */
295 	start = &line[5];
296 	p = strchr(start, ' ');
297 	if (p == NULL)
298 	{
299 		usrerr("Funny From line '%s'", line);
300 		exit(EX_PROTOCOL);
301 	}
302 	*p = '\0';
303 
304 	/*
305 	 * Strip all but the rightmost UUCP host
306 	 * to prevent loops due to forwarding.
307 	 * Start searching leftward from the leftmost '@'.
308 	 *	a!b!c!d yields a short name of c!d
309 	 *	a!b!c!d@e yields a short name of c!d@e
310 	 *	e@a!b!c yields the same short name
311 	 */
312 #ifdef VDEBUG
313 printf("start='%s'\n", start);
314 #endif /* VDEBUG */
315 	*shortp = start;			/* assume whole addr */
316 	if ((at = strchr(start, '@')) == NULL)	/* leftmost '@' */
317 		at = p;				/* if none, use end of addr */
318 	saveat = *at;
319 	*at = '\0';
320 	if ((bang = strrchr(start, '!')) != NULL) {	/* rightmost '!' */
321 		char *bang2;
322 		*bang = '\0';
323 		/* 2nd rightmost '!' */
324 		if ((bang2 = strrchr(start, '!')) != NULL)
325 			*shortp = bang2 + 1;		/* move past ! */
326 		*bang = '!';
327 	}
328 	*at = saveat;
329 #ifdef VDEBUG
330 printf("place='%s'\n", *shortp);
331 #endif /* VDEBUG */
332 	for (c = at + 1; *c; c++)
333 		*c = (char)tolower((char)*c);
334 
335 	/* return the sender address */
336 	return (start);
337 }
338 
339 /*
340  *  JUNKMAIL -- read the header and tell us if this is junk/bulk mail.
341  *
342  *	Parameters:
343  *		from -- the Return-Path of the sender.  We assume that
344  *			anything from "*-REQUEST@*" is bulk mail.
345  *
346  *	Returns:
347  *		TRUE -- if this is junk or bulk mail (that is, if the
348  *			sender shouldn't receive a response).
349  *		FALSE -- if the sender deserves a response.
350  *
351  *	Side Effects:
352  *		May read the header from standard input.  When this
353  *		returns the position on stdin is undefined.
354  */
355 
356 static bool
357 junkmail(from)
358 	char *from;
359 {
360 	register char *p;
361 	char buf[MAXLINE+1];
362 	bool inside, onlist;
363 
364 	/* test for inhuman sender */
365 	p = strrchr(from, '@');
366 	if (p != NULL)
367 	{
368 		*p = '\0';
369 		if (sameword(&p[-8],  "-REQUEST") ||
370 		    sameword(&p[-10], "Postmaster") ||
371 		    sameword(&p[-13], "MAILER-DAEMON"))
372 		{
373 			*p = '@';
374 			return (TRUE);
375 		}
376 		*p = '@';
377 	}
378 
379 #define	Delims " \n\t:,:;()<>@!"
380 
381 	/* read the header looking for "interesting" lines */
382 	inside = FALSE;
383 	onlist = FALSE;
384 	while (fgets(buf, MAXLINE, stdin) != NULL && buf[0] != '\n')
385 	{
386 		if (buf[0] != ' ' && buf[0] != '\t' && strchr(buf, ':') == NULL)
387 			return (FALSE);			/* no header found */
388 
389 		p = strtok(buf, Delims);
390 		if (p == NULL)
391 			continue;
392 
393 		if (sameword(p, "To") || sameword(p, "Cc"))
394 		{
395 			inside = TRUE;
396 			p = strtok((char *)NULL, Delims);
397 			if (p == NULL)
398 				continue;
399 
400 		} else				/* continuation line? */
401 		    if (inside)
402 			inside =  (buf[0] == ' ' || buf[0] == '\t');
403 
404 		if (inside) {
405 		    int i;
406 
407 		    do {
408 			if (sameword(p, myname))
409 				onlist = TRUE;		/* I am on the list */
410 
411 			for (i = 0; i < AliasCount; i++)
412 			    if (sameword(p, AliasList[i]))
413 				onlist = TRUE;		/* alias on list */
414 
415 		    } while (p = strtok((char *)NULL, Delims));
416 		    continue;
417 		}
418 
419 		if (sameword(p, "Precedence"))
420 		{
421 			/* find the value of this field */
422 			p = strtok((char *)NULL, Delims);
423 			if (p == NULL)
424 				continue;
425 
426 			/* see if it is "junk" or "bulk" */
427 			p[4] = '\0';
428 			if (sameword(p, "junk") || sameword(p, "bulk"))
429 				return (TRUE);
430 		}
431 
432 		if (sameword(p, "Subject"))
433 		{
434 			char *decoded_subject;
435 
436 			Subject = newstr(buf+9);
437 			if (p = strrchr(Subject, '\n'))
438 				*p = '\0';
439 			EncodedSubject = newstr(Subject);
440 			decoded_subject = newstr(Subject);
441 			if (decode_rfc2047(Subject, decoded_subject, Charset))
442 				Subject = decoded_subject;
443 			else
444 				Charset[0] = '\0';
445 			if (Debug)
446 				printf("Subject=%s\n", Subject);
447 		}
448 	}
449 	if (AnswerAll)
450 		return (FALSE);
451 	else
452 		return (!onlist);
453 }
454 
455 /*
456  *  FILTER_OK -- see if the Return-Path is in the filter file.
457  *		 Note that a non-existent filter file means everything
458  *		 is OK, but an empty file means nothing is OK.
459  *
460  *	Parameters:
461  *		from -- the Return-Path of the sender.
462  *
463  *	Returns:
464  *		TRUE -- if this is in the filter file
465  *			(sender should receive a response).
466  *		FALSE -- if the sender does not deserve a response.
467  */
468 
469 static bool
470 filter_ok(from, filter_file)
471 	char *from;
472 	char *filter_file;
473 {
474 	char file[MAXLINE];
475 	char line[MAXLINE];
476 	char *match_start;
477 	size_t line_len, from_len;
478 	bool result = FALSE;
479 	bool negated = FALSE;
480 	FILE *f;
481 
482 	from_len = strlen(from);
483 	(void) strlcpy(file, homedir, sizeof (file));
484 	if (filter_file[0] != '/')
485 	    (void) strlcat(file, "/", sizeof (file));
486 	(void) strlcat(file, filter_file, sizeof (file));
487 	f = fopen(file, "r");
488 	if (f == NULL) {
489 		/*
490 		 * If the file does not exist, then there is no filter to
491 		 * apply, so we simply return TRUE.
492 		 */
493 		if (Debug)
494 			(void) printf("%s does not exist, filter ok.\n",
495 			    file);
496 		return (TRUE);
497 	}
498 	while (fgets(line, MAXLINE, f)) {
499 		line_len = strlen(line);
500 		/* zero out trailing newline */
501 		if (line[line_len - 1] == '\n')
502 			line[--line_len] = '\0';
503 		/* skip blank lines */
504 		if (line_len == 0)
505 			continue;
506 		/* skip comment lines */
507 		if (line[0] == '#')
508 			continue;
509 		if (line[0] == '!') {
510 			negated = TRUE;
511 			match_start = &line[1];
512 			line_len--;
513 		} else {
514 			negated = FALSE;
515 			match_start = &line[0];
516 		}
517 		if (strchr(line, '@') != NULL) {
518 			/* @ => full address */
519 			if (strcasecmp(match_start, from) == 0) {
520 				result = TRUE;
521 				if (Debug)
522 					(void) printf("filter match on %s\n",
523 					    line);
524 				break;
525 			}
526 		} else {
527 			/* no @ => domain */
528 			if (from_len <= line_len)
529 				continue;
530 			/*
531 			 * Make sure the last part of from is the domain line
532 			 * and that the character immediately preceding is an
533 			 * '@' or a '.', otherwise we could get false positives
534 			 * from e.g. twinsun.com for sun.com .
535 			 */
536 			if (strncasecmp(&from[from_len - line_len],
537 			    match_start, line_len) == 0 &&
538 			    (from[from_len - line_len -1] == '@' ||
539 			    from[from_len - line_len -1] == '.')) {
540 				result = TRUE;
541 				if (Debug)
542 					(void) printf("filter match on %s\n",
543 					    line);
544 				break;
545 			}
546 		}
547 	}
548 	(void) fclose(f);
549 	if (Debug && !result)
550 		(void) printf("no filter match\n");
551 	return (!negated && result);
552 }
553 
554 /*
555  *  KNOWS -- predicate telling if user has already been informed.
556  *
557  *	Parameters:
558  *		user -- the user who sent this message.
559  *
560  *	Returns:
561  *		TRUE if 'user' has already been informed that the
562  *			recipient is on vacation.
563  *		FALSE otherwise.
564  *
565  *	Side Effects:
566  *		none.
567  */
568 
569 static bool
570 knows(user)
571 	char *user;
572 {
573 	datum key, data;
574 	time_t now, then;
575 
576 	(void) time(&now);
577 	key.dptr = user;
578 	key.dsize = strlen(user) + 1;
579 	data = dbm_fetch(db, key);
580 	if (data.dptr == NULL)
581 		return (FALSE);
582 
583 	bcopy(data.dptr, (char *)&then, sizeof (then));
584 	if (then + Timeout < now)
585 		return (FALSE);
586 	if (Debug)
587 		printf("User %s already knows\n", user);
588 	return (TRUE);
589 }
590 
591 /*
592  *  SETKNOWS -- set that this user knows about the vacation.
593  *
594  *	Parameters:
595  *		user -- the user who should be marked.
596  *
597  *	Returns:
598  *		none.
599  *
600  *	Side Effects:
601  *		The dbm file is updated as appropriate.
602  */
603 
604 static void
605 setknows(user)
606 	char *user;
607 {
608 	datum key, data;
609 	time_t now;
610 
611 	key.dptr = user;
612 	key.dsize = strlen(user) + 1;
613 	(void) time(&now);
614 	data.dptr = (char *)&now;
615 	data.dsize = sizeof (now);
616 	dbm_store(db, key, data, DBM_REPLACE);
617 }
618 
619 static bool
620 any8bitchars(line)
621 	char *line;
622 {
623 	char *c;
624 
625 	for (c = line; *c; c++)
626 		if (*c & 0x80)
627 			return (TRUE);
628 	return (FALSE);
629 }
630 
631 /*
632  *  SENDMESSAGE -- send a message to a particular user.
633  *
634  *	Parameters:
635  *		msgf -- filename containing the message.
636  *		user -- user who should receive it.
637  *
638  *	Returns:
639  *		none.
640  *
641  *	Side Effects:
642  *		sends mail to 'user' using /usr/lib/sendmail.
643  */
644 
645 static void
646 sendmessage(msgf, user, myname)
647 	char *msgf;
648 	char *user;
649 	char *myname;
650 {
651 	FILE *f, *fpipe, *tmpf;
652 	char line[MAXLINE];
653 	char *p, *tmpf_name;
654 	int i, pipefd[2], tmpfd;
655 	bool seen8bitchars = FALSE;
656 	bool in_header = TRUE;
657 
658 	/* find the message to send */
659 	f = fopen(msgf, "r");
660 	if (f == NULL)
661 	{
662 		f = fopen("/etc/mail/vacation.def", "r");
663 		if (f == NULL)
664 			usrerr("No message to send");
665 			exit(EX_OSFILE);
666 	}
667 
668 	if (pipe(pipefd) < 0) {
669 		usrerr("pipe() failed");
670 		exit(EX_OSERR);
671 	}
672 	i = fork();
673 	if (i < 0) {
674 		usrerr("fork() failed");
675 		exit(EX_OSERR);
676 	}
677 	if (i == 0) {
678 		dup2(pipefd[0], 0);
679 		close(pipefd[0]);
680 		close(pipefd[1]);
681 		fclose(f);
682 		execl("/usr/lib/sendmail", "sendmail", "-eq", "-f", myname,
683 			"--", user, NULL);
684 		usrerr("can't exec /usr/lib/sendmail");
685 		exit(EX_OSERR);
686 	}
687 	close(pipefd[0]);
688 	fpipe = fdopen(pipefd[1], "w");
689 	if (fpipe == NULL) {
690 		usrerr("fdopen() failed");
691 		exit(EX_OSERR);
692 	}
693 	fprintf(fpipe, "To: %s\n", user);
694 	fputs("Auto-Submitted: auto-replied\n", fpipe);
695 	fputs("X-Mailer: vacation %I%\n", fpipe);
696 
697 	/*
698 	 * We used to write directly to the pipe.  But now we need to know
699 	 * what character set to use, and we need to examine the entire
700 	 * message to determine this.  So write to a temp file first.
701 	 */
702 	tmpf_name = strdup(_PATH_TMP);
703 	if (tmpf_name == NULL) {
704 		usrerr("newstr: cannot alloc memory");
705 		exit(EX_OSERR);
706 	}
707 	tmpfd = -1;
708 	tmpfd = mkstemp(tmpf_name);
709 	if (tmpfd == -1) {
710 		usrerr("can't open temp file %s", tmpf_name);
711 		exit(EX_OSERR);
712 	}
713 	tmpf = fdopen(tmpfd, "w");
714 	if (tmpf == NULL) {
715 		usrerr("can't open temp file %s", tmpf_name);
716 		exit(EX_OSERR);
717 	}
718 	while (fgets(line, MAXLINE, f)) {
719 		/*
720 		 * Check for a line with no ':' character.  If it's just \n,
721 		 * we're at the end of the headers and all is fine.  Or if
722 		 * it starts with white-space, then it's a continuation header.
723 		 * Otherwise, it's the start of the body, which means the
724 		 * header/body separator was skipped.  So output it.
725 		 */
726 		if (in_header && line[0] != '\0' && strchr(line, ':') == NULL) {
727 			if (line[0] == '\n')
728 				in_header = FALSE;
729 			else if (!isspace(line[0])) {
730 				in_header = FALSE;
731 				fputs("\n", tmpf);
732 			}
733 		}
734 		p = strchr(line, '$');
735 		if (p && strncmp(p, "$SUBJECT", 8) == 0) {
736 			*p = '\0';
737 			seen8bitchars |= any8bitchars(line);
738 			fputs(line, tmpf);
739 			if (Subject) {
740 				if (in_header)
741 					fputs(EncodedSubject, tmpf);
742 				else {
743 					seen8bitchars |= any8bitchars(Subject);
744 					fputs(Subject, tmpf);
745 				}
746 			}
747 			seen8bitchars |= any8bitchars(p+8);
748 			fputs(p+8, tmpf);
749 			continue;
750 		}
751 		seen8bitchars |= any8bitchars(line);
752 		fputs(line, tmpf);
753 	}
754 	fclose(f);
755 	fclose(tmpf);
756 
757 	/*
758 	 * If we haven't seen a funky Subject with Charset, use the default.
759 	 * If we have and it's us-ascii, 8-bit chars in the message file will
760 	 * still result in iso-8859-1.
761 	 */
762 	if (Charset[0] == '\0')
763 		(void) strlcpy(Charset, (seen8bitchars) ? "iso-8859-1" :
764 		    "us-ascii", sizeof (Charset));
765 	else if ((strcasecmp(Charset, "us-ascii") == 0) && seen8bitchars)
766 		(void) strlcpy(Charset, "iso-8859-1", sizeof (Charset));
767 	if (Debug)
768 		printf("Charset is %s\n", Charset);
769 	fprintf(fpipe, "Content-Type: text/plain; charset=%s\n", Charset);
770 	fputs("Mime-Version: 1.0\n", fpipe);
771 
772 	/*
773 	 * Now read back in from the temp file and write to the pipe.
774 	 */
775 	tmpf = fopen(tmpf_name, "r");
776 	if (tmpf == NULL) {
777 		usrerr("can't open temp file %s", tmpf_name);
778 		exit(EX_OSERR);
779 	}
780 	while (fgets(line, MAXLINE, tmpf))
781 		fputs(line, fpipe);
782 	fclose(fpipe);
783 	fclose(tmpf);
784 	(void) unlink(tmpf_name);
785 	free(tmpf_name);
786 }
787 
788 /*
789  *  INITIALIZE -- initialize the database before leaving for vacation
790  *
791  *	Parameters:
792  *		none.
793  *
794  *	Returns:
795  *		none.
796  *
797  *	Side Effects:
798  *		Initializes the files .vacation.{pag,dir} in the
799  *		caller's home directory.
800  */
801 
802 static void
803 initialize(db_file_base)
804 	char *db_file_base;
805 {
806 	char *homedir;
807 	char buf[MAXLINE];
808 	DBM *db;
809 
810 	setgid(getgid());
811 	setuid(getuid());
812 	homedir = getenv("HOME");
813 	if (homedir == NULL) {
814 		usrerr("No home!");
815 		exit(EX_NOUSER);
816 	}
817 	(void) snprintf(buf, sizeof (buf), "%s%s%s", homedir,
818 		(db_file_base[0] == '/') ? "" : "/", db_file_base);
819 
820 	if (!(db = dbm_open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0644))) {
821 		usrerr("%s: %s\n", buf, strerror(errno));
822 		exit(EX_DATAERR);
823 	}
824 	dbm_close(db);
825 }
826 
827 /*
828  *  USRERR -- print user error
829  *
830  *	Parameters:
831  *		f -- format.
832  *
833  *	Returns:
834  *		none.
835  *
836  *	Side Effects:
837  *		none.
838  */
839 
840 /* PRINTFLIKE1 */
841 void
842 usrerr(const char *f, ...)
843 {
844 	va_list alist;
845 
846 	va_start(alist, f);
847 	(void) fprintf(stderr, "vacation: ");
848 	(void) vfprintf(stderr, f, alist);
849 	(void) fprintf(stderr, "\n");
850 	va_end(alist);
851 }
852 
853 /*
854  *  NEWSTR -- copy a string
855  *
856  *	Parameters:
857  *		s -- the string to copy.
858  *
859  *	Returns:
860  *		A copy of the string.
861  *
862  *	Side Effects:
863  *		none.
864  */
865 
866 static char *
867 newstr(s)
868 	char *s;
869 {
870 	char *p;
871 	size_t s_sz = strlen(s);
872 
873 	p = malloc(s_sz + 1);
874 	if (p == NULL)
875 	{
876 		usrerr("newstr: cannot alloc memory");
877 		exit(EX_OSERR);
878 	}
879 	(void) strlcpy(p, s, s_sz + 1);
880 	return (p);
881 }
882 
883 /*
884  *  SAMEWORD -- return TRUE if the words are the same
885  *
886  *	Ignores case.
887  *
888  *	Parameters:
889  *		a, b -- the words to compare.
890  *
891  *	Returns:
892  *		TRUE if a & b match exactly (modulo case)
893  *		FALSE otherwise.
894  *
895  *	Side Effects:
896  *		none.
897  */
898 
899 static bool
900 sameword(a, b)
901 	register char *a, *b;
902 {
903 	char ca, cb;
904 
905 	do
906 	{
907 		ca = *a++;
908 		cb = *b++;
909 		if (isascii(ca) && isupper(ca))
910 			ca = ca - 'A' + 'a';
911 		if (isascii(cb) && isupper(cb))
912 			cb = cb - 'A' + 'a';
913 	} while (ca != '\0' && ca == cb);
914 	return (ca == cb);
915 }
916 
917 /*
918  * When invoked with no arguments, we fall into an automatic installation
919  * mode, stepping the user through a default installation.
920  */
921 
922 static void
923 AutoInstall()
924 {
925 	char file[MAXLINE];
926 	char forward[MAXLINE];
927 	char cmd[MAXLINE];
928 	char line[MAXLINE];
929 	char *editor;
930 	FILE *f;
931 	struct passwd *pw;
932 	extern mode_t umask(mode_t cmask);
933 
934 	umask(022);
935 	pw = getpwuid(getuid());
936 	if (pw == NULL) {
937 		usrerr("User ID unknown");
938 		exit(EX_NOUSER);
939 	}
940 	myname = strdup(pw->pw_name);
941 	if (myname == NULL) {
942 		usrerr("Out of memory");
943 		exit(EX_OSERR);
944 	}
945 	homedir = getenv("HOME");
946 	if (homedir == NULL) {
947 		usrerr("Home directory unknown");
948 		exit(EX_NOUSER);
949 	}
950 
951 	printf("This program can be used to answer your mail automatically\n");
952 	printf("when you go away on vacation.\n");
953 	(void) strlcpy(file, homedir, sizeof (file));
954 	(void) strlcat(file, MsgFile, sizeof (file));
955 	do {
956 		f = fopen(file, "r");
957 		if (f) {
958 			printf("You have a message file in %s.\n", file);
959 			if (ask("Would you like to see it")) {
960 				(void) snprintf(cmd, sizeof (cmd),
961 				    "/usr/bin/more %s", file);
962 				system(cmd);
963 			}
964 			if (ask("Would you like to edit it"))
965 				f = NULL;
966 		} else {
967 			printf("You need to create a message file"
968 				" in %s first.\n", file);
969 			f = fopen(file, "w");
970 			if (f == NULL) {
971 				usrerr("Cannot open %s", file);
972 				exit(EX_CANTCREAT);
973 			}
974 			fprintf(f, "Subject: away from my mail\n");
975 			fprintf(f, "\nI will not be reading my mail"
976 				" for a while.\n");
977 			fprintf(f, "Your mail regarding \"$SUBJECT\" will"
978 				" be read when I return.\n");
979 			fclose(f);
980 			f = NULL;
981 		}
982 		if (f == NULL) {
983 			editor = getenv("VISUAL");
984 			if (editor == NULL)
985 				editor = getenv("EDITOR");
986 			if (editor == NULL)
987 				editor = "/usr/bin/vi";
988 			(void) snprintf(cmd, sizeof (cmd), "%s %s", editor,
989 			    file);
990 			printf("Please use your editor (%s)"
991 				" to edit this file.\n", editor);
992 			system(cmd);
993 		}
994 	} while (f == NULL);
995 	fclose(f);
996 	(void) strlcpy(forward, homedir, sizeof (forward));
997 	(void) strlcat(forward, "/.forward", sizeof (forward));
998 	f = fopen(forward, "r");
999 	if (f) {
1000 		printf("You have a .forward file"
1001 			" in your home directory containing:\n");
1002 		while (fgets(line, MAXLINE, f))
1003 			printf("    %s", line);
1004 		fclose(f);
1005 		if (!ask("Would you like to remove it and"
1006 				" disable the vacation feature"))
1007 			exit(EX_OK);
1008 		if (unlink(forward))
1009 			perror("Error removing .forward file:");
1010 		else
1011 			printf("Back to normal reception of mail.\n");
1012 		exit(EX_OK);
1013 	}
1014 
1015 	printf("To enable the vacation feature"
1016 		" a \".forward\" file is created.\n");
1017 	if (!ask("Would you like to enable the vacation feature")) {
1018 		printf("OK, vacation feature NOT enabled.\n");
1019 		exit(EX_OK);
1020 	}
1021 	f = fopen(forward, "w");
1022 	if (f == NULL) {
1023 		perror("Error opening .forward file");
1024 		exit(EX_CANTCREAT);
1025 	}
1026 	fprintf(f, "\\%s, \"|/usr/bin/vacation %s\"\n", myname, myname);
1027 	fclose(f);
1028 	printf("Vacation feature ENABLED."
1029 		" Please remember to turn it off when\n");
1030 	printf("you get back from vacation. Bon voyage.\n");
1031 
1032 	initialize(DbFileBase);
1033 	exit(EX_OK);
1034 }
1035 
1036 
1037 /*
1038  * Ask the user a question until we get a reasonable answer
1039  */
1040 
1041 static bool
1042 ask(prompt)
1043 	char *prompt;
1044 {
1045 	char line[MAXLINE];
1046 	char *res;
1047 
1048 	for (;;) {
1049 		printf("%s? ", prompt);
1050 		fflush(stdout);
1051 		res = fgets(line, sizeof (line), stdin);
1052 		if (res == NULL)
1053 			return (FALSE);
1054 		if (res[0] == 'y' || res[0] == 'Y')
1055 			return (TRUE);
1056 		if (res[0] == 'n' || res[0] == 'N')
1057 			return (FALSE);
1058 		printf("Please reply \"yes\" or \"no\" (\'y\' or \'n\')\n");
1059 	}
1060 }
1061