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 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 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 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 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 * 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 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 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 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 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 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 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 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 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 * 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 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 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 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