1 #ifndef lint 2 static char sccsid[] = "@(#)bugfiler.c 4.15 (Berkeley) 05/17/84"; 3 #endif 4 5 /* 6 * Bug report processing program. 7 * It is designed to be invoked by alias(5) 8 * and to be compatible with mh. 9 */ 10 11 #include <stdio.h> 12 #include <ctype.h> 13 #include <signal.h> 14 #include <pwd.h> 15 16 #include <sys/types.h> 17 #include <sys/stat.h> 18 #include <sys/dir.h> 19 20 #define BUGS_NAME "4bsd-bugs" 21 #define BUGS_HOME "%ucbarpa@BERKELEY" 22 #define MAILCMD "/usr/lib/sendmail -i -t" 23 24 char unixtomh[] = "/usr/new/lib/mh/unixtomh"; 25 char *bugperson = "bugs"; 26 char *maildir = "mail"; 27 char ackfile[] = ".ack"; 28 char errfile[] = ".format"; 29 char sumfile[] = "summary"; 30 char logfile[] = "errors/log"; 31 char redistfile[] = ".redist"; 32 char tmpname[] = "BfXXXXXX"; 33 char draft[] = "RpXXXXXX"; 34 char disttmp[] = "RcXXXXXX"; 35 36 char buf[8192]; 37 char folder[MAXNAMLEN]; 38 int num; 39 int msg_prot = 0664; 40 41 int debug; 42 43 char *index(); 44 char *rindex(); 45 char *fixaddr(); 46 char *any(); 47 48 main(argc, argv) 49 char *argv[]; 50 { 51 register char *cp; 52 register int n; 53 int pfd[2]; 54 55 if (argc > 4) { 56 usage: 57 fprintf(stderr, "Usage: bugfiler [-d] [-mmsg_mode] [maildir]\n"); 58 exit(1); 59 } 60 while (--argc > 0) { 61 cp = *++argv; 62 if (*cp == '-') 63 switch (cp[1]) { 64 case 'd': 65 debug++; 66 break; 67 68 case 'm': /* set message protection */ 69 n = 0; 70 for (cp += 2; *cp >= '0' && *cp <= '7'; ) 71 n = (n << 3) + (*cp++ - '0'); 72 msg_prot = n & 0777; 73 break; 74 75 default: 76 goto usage; 77 } 78 else 79 maildir = cp; 80 } 81 if (!debug) 82 freopen(logfile, "a", stderr); 83 84 if (bugperson) { 85 struct passwd *pwd = getpwnam(bugperson); 86 87 if (pwd == NULL) { 88 fprintf(stderr, "%s: bugs person is unknown\n", 89 bugperson); 90 exit(1); 91 } 92 if (chdir(pwd->pw_dir) < 0) { 93 fprintf(stderr, "can't chdir to %s\n", pwd->pw_dir); 94 exit(1); 95 } 96 setuid(pwd->pw_uid); 97 } 98 if (chdir(maildir) < 0) { 99 fprintf(stderr, "can't chdir to %s\n", maildir); 100 exit(1); 101 } 102 umask(0); 103 104 #ifdef UNIXCOMP 105 /* 106 * Convert UNIX style mail to mh style by filtering stdin through 107 * unixtomh. 108 */ 109 if (pipe(pfd) >= 0) { 110 while ((n = fork()) == -1) 111 sleep(5); 112 if (n == 0) { 113 close(pfd[0]); 114 dup2(pfd[1], 1); 115 close(pfd[1]); 116 execl(unixtomh, "unixtomh", 0); 117 _exit(127); 118 } 119 close(pfd[1]); 120 dup2(pfd[0], 0); 121 close(pfd[0]); 122 } 123 #endif 124 while (process()) 125 ; 126 exit(0); 127 } 128 129 /* states */ 130 131 #define EOM 0 /* End of message seen */ 132 #define FLD 1 /* Looking for header lines */ 133 #define BODY 2 /* Looking for message body lines */ 134 135 /* defines used for tag attributes */ 136 137 #define H_REQ 01 138 #define H_SAV 02 139 #define H_HDR 04 140 #define H_FND 010 141 142 #define FROM &headers[0] 143 #define FROM_I headers[0].h_info 144 #define SUBJECT_I headers[1].h_info 145 #define INDEX &headers[2] 146 #define INDEX_I headers[2].h_info 147 #define DATE_I headers[3].h_info 148 #define MSGID_I headers[4].h_info 149 #define REPLYTO_I headers[5].h_info 150 #define TO_I headers[6].h_info 151 #define CC_I headers[7].h_info 152 #define FIX headers[10] 153 154 struct header { 155 char *h_tag; 156 int h_flags; 157 char *h_info; 158 } headers[] = { 159 "From", H_REQ|H_SAV|H_HDR, 0, 160 "Subject", H_REQ|H_SAV, 0, 161 "Index", H_REQ|H_SAV, 0, 162 "Date", H_SAV|H_HDR, 0, 163 "Message-Id", H_SAV|H_HDR, 0, 164 "Reply-To", H_SAV|H_HDR, 0, 165 "To", H_SAV|H_HDR, 0, 166 "Cc", H_SAV|H_HDR, 0, 167 "Description", H_REQ, 0, 168 "Repeat-By", 0, 0, 169 "Fix", 0, 0, 170 0, 0, 0, 171 }; 172 173 struct header *findheader(); 174 175 process() 176 { 177 register struct header *hp; 178 register char *cp; 179 register int c; 180 char *info; 181 int state, tmp, no_reply = 0; 182 FILE *tfp, *fs; 183 184 /* 185 * Insure all headers are in a consistent 186 * state. Anything left there is free'd. 187 */ 188 for (hp = headers; hp->h_tag; hp++) { 189 hp->h_flags &= ~H_FND; 190 if (hp->h_info) { 191 free(hp->h_info); 192 hp->h_info = 0; 193 } 194 } 195 /* 196 * Read the report and make a copy. Must conform to RFC822 and 197 * be of the form... <tag>: <info> 198 * Note that the input is expected to be in mh mail format 199 * (i.e., messages are separated by lines of ^A's). 200 */ 201 while ((c = getchar()) == '\001' && peekc(stdin) == '\001') 202 while (getchar() != '\n') 203 ; 204 if (c == EOF) 205 return(0); /* all done */ 206 207 mktemp(tmpname); 208 if ((tmp = creat(tmpname, msg_prot)) < 0) { 209 fprintf(stderr, "cannont create %s\n", tmpname); 210 exit(1); 211 } 212 if ((tfp = fdopen(tmp, "w")) == NULL) { 213 fprintf(stderr, "cannot fdopen temp file\n"); 214 exit(1); 215 } 216 217 for (state = FLD; state != EOF && state != EOM; c = getchar()) { 218 switch (state) { 219 case FLD: 220 if (c == '\n' || c == '-') 221 goto body; 222 for (cp = buf; c != ':'; c = getchar()) { 223 if (cp < buf+sizeof(buf)-1 && c != '\n' && c != EOF) { 224 *cp++ = c; 225 continue; 226 } 227 *cp = '\0'; 228 fputs(buf, tfp); 229 state = EOF; 230 while (c != EOF) { 231 if (c == '\n') 232 if ((tmp = peekc(stdin)) == EOF) 233 break; 234 else if (tmp == '\001') { 235 state = EOM; 236 break; 237 } 238 putc(c, tfp); 239 c = getchar(); 240 } 241 fclose(tfp); 242 goto badfmt; 243 } 244 *cp = '\0'; 245 fprintf(tfp, "%s:", buf); 246 hp = findheader(buf, state); 247 248 for (cp = buf; ; ) { 249 if (cp >= buf+sizeof(buf)-1) { 250 fprintf(stderr, "field truncated\n"); 251 while ((c = getchar()) != EOF && c != '\n') 252 putc(c, tfp); 253 } 254 if ((c = getchar()) == EOF) { 255 state = EOF; 256 break; 257 } 258 putc(c, tfp); 259 *cp++ = c; 260 if (c == '\n') 261 if ((c = peekc(stdin)) != ' ' && c != '\t') { 262 if (c == EOF) 263 state = EOF; 264 else if (c == '\001') 265 state = EOM; 266 break; 267 } 268 } 269 *cp = '\0'; 270 cp = buf; 271 break; 272 273 body: 274 state = BODY; 275 case BODY: 276 for (cp = buf; ; c = getchar()) { 277 if (c == EOF) { 278 state = EOF; 279 break; 280 } 281 if (c == '\001' && peekc(stdin) == '\001') { 282 state = EOM; 283 break; 284 } 285 putc(c, tfp); 286 *cp++ = c; 287 if (cp >= buf+sizeof(buf)-1 || c == '\n') 288 break; 289 } 290 *cp = '\0'; 291 if ((cp = index(buf, ':')) == NULL) 292 continue; 293 *cp++ = '\0'; 294 hp = findheader(buf, state); 295 } 296 297 /* 298 * Don't save the info if the header wasn't found, we don't 299 * care about the info, or the header is repeated. 300 */ 301 if (hp == NULL || !(hp->h_flags & H_SAV) || hp->h_info) 302 continue; 303 while (isspace(*cp)) 304 cp++; 305 if (*cp) { 306 info = cp; 307 while (*cp++); 308 cp--; 309 while (isspace(cp[-1])) 310 *--cp = '\0'; 311 hp->h_info = (char *) malloc(strlen(info) + 1); 312 if (hp->h_info == NULL) { 313 fprintf(stderr, "ran out of memory\n"); 314 continue; 315 } 316 strcpy(hp->h_info, info); 317 if (hp == FROM && chkfrom(hp) < 0) 318 no_reply = 1; 319 if (hp == INDEX) 320 chkindex(hp); 321 } 322 } 323 fclose(tfp); 324 if (no_reply) { 325 unlink(tmpname); 326 exit(0); 327 } 328 /* 329 * Verify all the required pieces of information 330 * are present. 331 */ 332 for (hp = headers; hp->h_tag; hp++) { 333 /* 334 * Mail the bug report back to the sender with a note 335 * explaining they must conform to the specification. 336 */ 337 if ((hp->h_flags & H_REQ) && !(hp->h_flags & H_FND)) { 338 if (debug) 339 printf("Missing %s\n", hp->h_tag); 340 badfmt: 341 reply(FROM_I, errfile, tmpname); 342 file(tmpname, "errors"); 343 return(state == EOM); 344 } 345 } 346 /* 347 * Acknowledge receipt. 348 */ 349 reply(FROM_I, ackfile, (char *)0); 350 file(tmpname, folder); 351 /* 352 * Append information about the new bug report 353 * to the summary file. 354 */ 355 if ((fs = fopen(sumfile, "a")) == NULL) 356 fprintf(stderr, "Can't open %s\n", sumfile); 357 else { 358 fprintf(fs, "%14.14s/%-3d ", folder, num); 359 fprintf(fs, "%-51.51s Recv\n", INDEX_I); 360 fprintf(fs, "\t\t %-51.51s\n", SUBJECT_I); 361 } 362 fclose(fs); 363 /* 364 * Check redistribution list and, if members, 365 * mail a copy of the bug report to these people. 366 */ 367 redistribute(folder, num); 368 return(state == EOM); 369 } 370 371 /* 372 * Lookup the string in the list of headers and return a pointer 373 * to the entry or NULL. 374 */ 375 376 struct header * 377 findheader(name, state) 378 char *name; 379 int state; 380 { 381 register struct header *hp; 382 383 if (debug) 384 printf("findheader(%s, %d)\n", name, state); 385 386 for (hp = headers; hp->h_tag; hp++) { 387 if (!streq(hp->h_tag, buf)) 388 continue; 389 if ((hp->h_flags & H_HDR) && state != FLD) 390 continue; 391 hp->h_flags |= H_FND; 392 return(hp); 393 } 394 return(NULL); 395 } 396 397 /* 398 * Check the FROM line to eliminate loops. 399 */ 400 401 chkfrom(hp) 402 struct header *hp; 403 { 404 register char *cp1, *cp2; 405 register char c; 406 407 if (debug) 408 printf("chkindex(%s)\n", hp->h_info); 409 410 if (substr(hp->h_info, "MAILER-DAEMON")) 411 return(-1); 412 return(0); 413 } 414 415 /* 416 * Check the format of the Index information. 417 * A side effect is to set the name of the folder if all is well. 418 */ 419 420 chkindex(hp) 421 struct header *hp; 422 { 423 register char *cp1, *cp2; 424 register char c; 425 struct stat stbuf; 426 427 if (debug) 428 printf("chkindex(%s)\n", hp->h_info); 429 /* 430 * Strip of leading "/", ".", "usr/", or "src/". 431 */ 432 cp1 = hp->h_info; 433 while (*cp1 == '/' || *cp1 == '.') 434 cp1++; 435 while (substr(cp1, "usr/") || substr(cp1, "src/")) 436 cp1 += 4; 437 /* 438 * Read the folder name and remove it from the index line. 439 */ 440 for (cp2 = folder; ;) { 441 switch (c = *cp1++) { 442 case '/': 443 if (cp2 == folder) 444 continue; 445 break; 446 case '\0': 447 cp1--; 448 break; 449 case ' ': 450 case '\t': 451 while (isspace(*cp1)) 452 cp1++; 453 break; 454 default: 455 if (cp2 < folder+sizeof(folder)-1) 456 *cp2++ = c; 457 continue; 458 } 459 *cp2 = '\0'; 460 for (cp2 = hp->h_info; *cp2++ = *cp1++; ) 461 ; 462 break; 463 } 464 if (debug) 465 printf("folder = %s\n", folder); 466 /* 467 * Check to make sure we have a valid folder name 468 */ 469 if (stat(folder, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR) 470 return; 471 /* 472 * The Index line is not in the correct format so clear 473 * the H_FND flag to mail back the correct format. 474 */ 475 hp->h_flags &= ~H_FND; 476 } 477 478 /* 479 * Move or copy the file msg to the folder (directory). 480 * As a side effect, num is set to the number under which 481 * the message is filed in folder. 482 */ 483 484 file(fname, folder) 485 char *fname, *folder; 486 { 487 register char *cp; 488 register int n; 489 char msgname[MAXNAMLEN*2+2]; 490 struct stat stbuf; 491 DIR *dirp; 492 struct direct *d; 493 494 if (debug) 495 printf("file(%s, %s)\n", fname, folder); 496 /* 497 * Get the next number to use by finding the last message number 498 * in folder and adding one. 499 */ 500 if ((dirp = opendir(folder)) == NULL) { 501 fprintf(stderr, "Cannot open %s/%s\n", maildir, folder); 502 return; 503 } 504 num = 0; 505 while ((d = readdir(dirp)) != NULL) { 506 cp = d->d_name; 507 n = 0; 508 while (isdigit(*cp)) 509 n = n * 10 + (*cp++ - '0'); 510 if (*cp == '\0' && n > num) 511 num = n; 512 } 513 closedir(dirp); 514 num++; 515 /* 516 * Create the destination file "folder/num" and copy fname to it. 517 */ 518 sprintf(msgname, "%s/%d", folder, num); 519 if (link(fname, msgname) < 0) { 520 int fin, fout; 521 522 if ((fin = open(fname, 0)) < 0) { 523 fprintf(stderr, "cannot open %s\n", fname); 524 return; 525 } 526 if ((fout = creat(msgname, msg_prot)) < 0) { 527 fprintf(stderr, "cannot create %s\n", msgname); 528 return; 529 } 530 while ((n = read(fin, buf, sizeof(buf))) > 0) 531 write(fout, buf, n); 532 close(fin); 533 close(fout); 534 } 535 unlink(fname); 536 } 537 538 /* 539 * Redistribute a bug report to those people indicated 540 * in the redistribution list file. Perhaps should also 541 * annotate bug report with this information for future 542 * reference? 543 */ 544 redistribute(folder, num) 545 char *folder; 546 int num; 547 { 548 FILE *fredist, *fbug, *ftemp; 549 char line[BUFSIZ], bug[2 * MAXNAMLEN + 1]; 550 register char *cp; 551 int redistcnt, continuation, first; 552 553 fredist = fopen(redistfile, "r"); 554 if (fredist == NULL) { 555 if (debug) 556 printf("redistribute(%s, %d), no distribution list\n", 557 folder, num); 558 return; 559 } 560 continuation = 0; 561 first = 1; 562 redistcnt = 0; 563 while (fgets(line, sizeof (line) - 1, fredist) != NULL) { 564 if (debug) 565 printf("%s: %s", redistfile, line); 566 if (continuation && index(line, '\\')) 567 continue; 568 continuation = 0; 569 cp = any(line, " \t"); 570 if (cp == NULL) 571 continue; 572 *cp++ = '\0'; 573 if (strcmp(folder, line) == 0) 574 goto found; 575 if (index(cp, '\\')) 576 continuation = 1; 577 } 578 if (debug) 579 printf("no redistribution list found\n"); 580 fclose(fredist); 581 return; 582 found: 583 mktemp(disttmp); 584 ftemp = fopen(disttmp, "w+r"); 585 if (ftemp == NULL) { 586 if (debug) 587 printf("%s: couldn't create\n", disttmp); 588 return; 589 } 590 again: 591 if (debug) 592 printf("redistribution list %s", cp); 593 while (cp) { 594 char *user, terminator; 595 596 while (*cp && (*cp == ' ' || *cp == '\t' || *cp == ',')) 597 cp++; 598 user = cp, cp = any(cp, ", \t\n\\"); 599 if (cp) { 600 terminator = *cp; 601 *cp++ = '\0'; 602 if (terminator == '\n') 603 cp = 0; 604 if (terminator == '\\') 605 continuation++; 606 } 607 if (*user == '\0') 608 continue; 609 if (debug) 610 printf("copy to %s\n", user); 611 if (first) { 612 fprintf(ftemp, "To: %s", user); 613 first = 0; 614 } else 615 fprintf(ftemp, ", %s", user); 616 redistcnt++; 617 } 618 if (!first) 619 putc('\n', ftemp); 620 if (continuation) { 621 first = 1; 622 continuation = 0; 623 cp = line; 624 if (fgets(line, sizeof (line) - 1, fredist)) 625 goto again; 626 } 627 fclose(fredist); 628 if (redistcnt == 0) 629 goto cleanup; 630 fprintf(ftemp, "Subject: "); 631 if (SUBJECT_I) 632 fprintf(ftemp, "%s\n", SUBJECT_I); 633 else 634 fprintf(ftemp, "Untitled bug report\n"); 635 fprintf(ftemp, "\nRedistributed-by: %s%s\n", BUGS_NAME, BUGS_HOME); 636 /* 637 * Create copy of bug report. Perhaps we should 638 * truncate large messages and just give people 639 * a pointer to the original? 640 */ 641 sprintf(bug, "%s/%d", folder, num); 642 fbug = fopen(bug, "r"); 643 if (fbug == NULL) { 644 if (debug) 645 printf("%s: disappeared?\n", bug); 646 goto cleanup; 647 } 648 first = 1; 649 while (fgets(line, sizeof (line) - 1, fbug)) { 650 /* first blank line indicates start of mesg */ 651 if (first && line[0] == '\n') { 652 first = 0; 653 continue; 654 } 655 fputs(line, ftemp); 656 } 657 fclose(fbug); 658 if (first) { 659 if (debug) 660 printf("empty bug report?\n"); 661 goto cleanup; 662 } 663 if (dodeliver(ftemp)) 664 unlink(disttmp); 665 fclose(ftemp); 666 return; 667 cleanup: 668 fclose(ftemp); 669 unlink(disttmp); 670 } 671 672 dodeliver(fd) 673 FILE *fd; 674 { 675 char buf[BUFSIZ], cmd[BUFSIZ]; 676 FILE *pf, *popen(); 677 678 strcpy(cmd, MAILCMD); 679 if (debug) { 680 strcat(cmd, " -v"); 681 printf("dodeliver \"%s\"\n", cmd); 682 } 683 pf = popen(cmd, "w"); 684 if (pf == NULL) { 685 if (debug) 686 printf("dodeliver, \"%s\" failed\n", cmd); 687 return (0); 688 } 689 rewind(fd); 690 while (fgets(buf, sizeof (buf) - 1, fd)) { 691 if (debug) 692 printf("%s", buf); 693 (void) fputs(buf, pf); 694 } 695 if (debug) 696 printf("EOF\n"); 697 (void) pclose(pf); 698 return (1); 699 } 700 701 /* 702 * Mail file1 and file2 back to the sender. 703 */ 704 705 reply(to, file1, file2) 706 char *to, *file1, *file2; 707 { 708 int pfd[2], in, w; 709 FILE *fout; 710 711 if (debug) 712 printf("reply(%s, %s, %s)\n", to, file1, file2); 713 714 /* 715 * Create a temporary file to put the message in. 716 */ 717 mktemp(draft); 718 if ((fout = fopen(draft, "w+r")) == NULL) { 719 fprintf(stderr, "Can't create %s\n", draft); 720 return; 721 } 722 /* 723 * Output the proper header information. 724 */ 725 fprintf(fout, "Reply-To: %s%s\n", BUGS_NAME, BUGS_HOME); 726 fprintf(fout, "From: %s%s (Bugs Bunny)\n", BUGS_NAME, BUGS_HOME); 727 if (REPLYTO_I != NULL) 728 to = REPLYTO_I; 729 if ((to = fixaddr(to)) == 0) { 730 fprintf(stderr, "No one to reply to\n"); 731 return; 732 } 733 fprintf(fout, "To: %s\n", to); 734 if (SUBJECT_I) { 735 fprintf(fout, "Subject: "); 736 if ((SUBJECT_I[0] != 'R' && SUBJECT_I[0] != 'r') || 737 (SUBJECT_I[1] != 'E' && SUBJECT_I[1] != 'e') || 738 SUBJECT_I[2] != ':') 739 fprintf(fout, "Re: "); 740 fprintf(fout, "%s\n", SUBJECT_I); 741 } 742 if (DATE_I) { 743 fprintf(fout, "In-Acknowledgement-Of: Your message of "); 744 fprintf(fout, "%s.\n", DATE_I); 745 if (MSGID_I) 746 fprintf(fout, " %s\n", MSGID_I); 747 } 748 fprintf(fout, "\n"); 749 if ((in = open(file1, 0)) >= 0) { 750 while ((w = read(in, buf, sizeof(buf))) > 0) 751 fwrite(buf, 1, w, fout); 752 close(in); 753 } 754 if (file2 && (in = open(file2, 0)) >= 0) { 755 while ((w = read(in, buf, sizeof(buf))) > 0) 756 fwrite(buf, 1, w, fout); 757 close(in); 758 } 759 if (dodeliver(fout)) 760 unlink(draft); 761 fclose(fout); 762 } 763 764 /* 765 * fix names like "xxx (something)" to "xxx" and 766 * "xxx <something>" to "something". 767 */ 768 769 char * 770 fixaddr(text) 771 char *text; 772 { 773 register char *cp, *lp, c; 774 char *tp; 775 776 if (!text) 777 return(0); 778 for (lp = cp = text; ; ) { 779 switch (c = *cp++) { 780 case '(': 781 while (*cp && *cp++ != ')'); 782 continue; 783 case '<': 784 lp = text; 785 case '>': 786 continue; 787 case '\0': 788 while (lp != text && (*lp == ' ' || *lp == '\t')) 789 lp--; 790 *lp = c; 791 return(text); 792 } 793 *lp++ = c; 794 } 795 } 796 797 /* 798 * Compare two strings and convert any upper case letters to lower case. 799 */ 800 801 streq(s1, s2) 802 register char *s1, *s2; 803 { 804 register int c; 805 806 while (c = *s1++) 807 if ((c | 040) != (*s2++ | 040)) 808 return(0); 809 return(*s2 == '\0'); 810 } 811 812 /* 813 * Return true if string s2 matches the first part of s1. 814 */ 815 816 substr(s1, s2) 817 register char *s1, *s2; 818 { 819 register int c; 820 821 while (c = *s2++) 822 if (c != *s1++) 823 return(0); 824 return(1); 825 } 826 827 char * 828 any(cp, set) 829 register char *cp; 830 char *set; 831 { 832 register char *sp; 833 834 if (cp == 0 || set == 0) 835 return (0); 836 while (*cp) { 837 for (sp = set; *sp; sp++) 838 if (*cp == *sp) 839 return (cp); 840 cp++; 841 } 842 return ((char *)0); 843 } 844 845 peekc(fp) 846 FILE *fp; 847 { 848 register c; 849 850 c = getc(fp); 851 ungetc(c, fp); 852 return(c); 853 } 854