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