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
main(argc,argv)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
dump_content(key_size,key_ptr,content_size,content_ptr)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
dump_all_content(first)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
dumplist()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 *
getfrom(shortp)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
junkmail(from)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
filter_ok(from,filter_file)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
knows(user)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
setknows(user)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
any8bitchars(line)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
sendmessage(msgf,user,myname)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
initialize(db_file_base)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
usrerr(const char * f,...)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 *
newstr(s)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
sameword(a,b)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
AutoInstall()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
ask(prompt)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