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