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
72typedef int bool;
73
74#define	FALSE	0
75#define	TRUE	1
76
77static time_t	Timeout = ONEWEEK;	/* timeout between notices per user */
78static DBM	*db;
79static bool	Debug = FALSE;
80static bool	ListMode = FALSE;
81static bool	AnswerAll = FALSE;	/* default: answer if in To:/Cc: only */
82static char	*Subject = NULL;	/* subject in message header */
83static char	*EncodedSubject = NULL;	/* subject in message header */
84static char	Charset[MAXLINE];	/* for use in reply message */
85static char	*AliasList[MAXLINE];	/* list of aliases to allow */
86static int	AliasCount = 0;
87static char	*myname;		/* name of person "on vacation" */
88static char	*homedir;		/* home directory of said person */
89
90extern time_t	convtime(char *, char);
91extern bool	decode_rfc2047(char *, char *, char *);
92
93static bool	ask(char *);
94static bool	junkmail(char *);
95static bool	filter_ok(char *, char *);
96static bool	knows(char *);
97static bool	sameword(char *, char *);
98static char	*getfrom(char **);
99static char	*newstr(char *);
100static void	AutoInstall();
101static void	initialize(char *);
102static void	sendmessage(char *, char *, char *);
103static void	setknows(char *);
104static void	dumplist();
105
106void	usrerr(const char *, ...);
107
108int
109main(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
275struct 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
284static void
285dump_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
301static void
302dump_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
313static void
314dumplist()
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
375static char *
376getfrom(shortp)
377char **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
410printf("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
427printf("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
453static bool
454junkmail(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
566static bool
567filter_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
666static bool
667knows(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
701static void
702setknows(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
716static bool
717any8bitchars(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
742static void
743sendmessage(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
900static void
901initialize(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 */
939void
940usrerr(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
964static char *
965newstr(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
997static bool
998sameword(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
1020static void
1021AutoInstall()
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
1139static bool
1140ask(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