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