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