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