1 #ifndef lint 2 static char sccsid[] = "@(#)bugfiler.c 4.4 (Berkeley) 08/11/83"; 3 #endif 4 5 /* 6 * Bug report processing program. 7 * It is designed to be invoked by alias(5) and to be compatible with mh. 8 */ 9 10 #include <stdio.h> 11 #include <ctype.h> 12 #include <signal.h> 13 #include <sys/types.h> 14 #include <sys/stat.h> 15 #include <sys/dir.h> 16 17 char deliver[] = "/usr/local/lib/mh/deliver"; 18 char unixtomh[] = "/usr/local/lib/mh/unixtomh"; 19 char *maildir = "/ra/bugs/mail"; 20 char ackfile[] = ".ack"; 21 char errfile[] = ".format"; 22 char sumfile[] = "summary"; 23 char logfile[] = "errors/log"; 24 char tmpname[] = "BfXXXXXX"; 25 char draft[] = "RpXXXXXX"; 26 27 char buf[8192]; 28 char folder[MAXNAMLEN]; 29 int num; 30 int msg_prot = 0664; 31 32 int debug; 33 34 char *index(); 35 char *rindex(); 36 char *fixaddr(); 37 38 main(argc, argv) 39 char *argv[]; 40 { 41 register char *cp; 42 register int n; 43 int pfd[2]; 44 45 if (argc > 3) { 46 usage: 47 fprintf(stderr, "Usage: bugfiler [-d] [-mmsg_mode] [maildir]\n"); 48 exit(1); 49 } 50 while (--argc > 0) { 51 cp = *++argv; 52 if (*cp == '-') 53 switch (cp[1]) { 54 case 'd': 55 debug++; 56 break; 57 58 case 'm': /* set message protection */ 59 n = 0; 60 for (cp += 2; *cp >= '0' && *cp <= '7'; ) 61 n = (n << 3) + (*cp++ - '0'); 62 msg_prot = n & 0777; 63 break; 64 65 default: 66 goto usage; 67 } 68 else 69 maildir = cp; 70 } 71 if (!debug) 72 freopen(logfile, "a", stderr); 73 74 if (chdir(maildir) < 0) { 75 fprintf(stderr, "can't chdir to %s\n", maildir); 76 exit(1); 77 } 78 umask(0); 79 80 #ifdef UNIXCOMP 81 /* 82 * Convert UNIX style mail to mh style by filtering stdin through 83 * unixtomh. 84 */ 85 if (pipe(pfd) >= 0) { 86 while ((n = fork()) == -1) 87 sleep(5); 88 if (n == 0) { 89 close(pfd[0]); 90 dup2(pfd[1], 1); 91 close(pfd[1]); 92 execl(unixtomh, "unixtomh", 0); 93 _exit(127); 94 } 95 close(pfd[1]); 96 dup2(pfd[0], 0); 97 close(pfd[0]); 98 } 99 #endif 100 while (process()) 101 ; 102 exit(0); 103 } 104 105 /* states */ 106 107 #define EOM 0 /* End of message seen */ 108 #define FLD 1 /* Looking for header lines */ 109 #define BODY 2 /* Looking for message body lines */ 110 111 /* defines used for tag attributes */ 112 113 #define H_REQ 01 114 #define H_SAV 02 115 #define H_HDR 04 116 #define H_FND 010 117 118 #define FROM_I headers[0].h_info 119 #define SUBJECT_I headers[1].h_info 120 #define INDEX &headers[2] 121 #define INDEX_I headers[2].h_info 122 #define DATE_I headers[3].h_info 123 #define MSGID_I headers[4].h_info 124 #define REPLYTO_I headers[5].h_info 125 #define RETURNPATH_I headers[6].h_info 126 #define TO_I headers[7].h_info 127 #define CC_I headers[8].h_info 128 #define FIX headers[11] 129 130 struct header { 131 char *h_tag; 132 int h_flags; 133 char *h_info; 134 } headers[] = { 135 "From", H_REQ|H_SAV|H_HDR, 0, 136 "Subject", H_REQ|H_SAV|H_HDR, 0, 137 "Index", H_REQ|H_SAV, 0, 138 "Date", H_SAV|H_HDR, 0, 139 "Message-Id", H_SAV|H_HDR, 0, 140 "Reply-To", H_SAV|H_HDR, 0, 141 "Return-Path", H_SAV|H_HDR, 0, 142 "To", H_SAV|H_HDR, 0, 143 "Cc", H_SAV|H_HDR, 0, 144 "Description", H_REQ, 0, 145 "Repeat-By", H_REQ, 0, 146 "Fix", 0, 0, 147 0, 0, 0, 148 }; 149 150 struct header *findheader(); 151 152 process() 153 { 154 register struct header *hp; 155 register char *cp; 156 register int c; 157 char *info; 158 int state, tmp; 159 FILE *tfp, *fs; 160 161 /* 162 * Insure all headers are in a consistent 163 * state. Anything left there is free'd. 164 */ 165 for (hp = headers; hp->h_tag; hp++) { 166 hp->h_flags &= ~H_FND; 167 if (hp->h_info) { 168 free(hp->h_info); 169 hp->h_info = 0; 170 } 171 } 172 /* 173 * Read the report and make a copy. Must conform to RFC822 and 174 * be of the form... <tag>: <info> 175 * Note that the input is expected to be in mh mail format 176 * (i.e., messages are separated by lines of ^A's). 177 */ 178 while ((c = getchar()) == '\001' && peekc(stdin) == '\001') 179 while (getchar() != '\n') 180 ; 181 if (c == EOF) 182 return(0); /* all done */ 183 184 mktemp(tmpname); 185 if ((tmp = creat(tmpname, msg_prot)) < 0) { 186 fprintf(stderr, "cannont create %s\n", tmpname); 187 exit(1); 188 } 189 if ((tfp = fdopen(tmp, "w")) == NULL) { 190 fprintf(stderr, "cannot fdopen temp file\n"); 191 exit(1); 192 } 193 194 for (state = FLD; state != EOF && state != EOM; c = getchar()) { 195 switch (state) { 196 case FLD: 197 if (c == '\n' || c == '-') 198 goto body; 199 for (cp = buf; c != ':'; c = getchar()) { 200 if (cp < buf+sizeof(buf)-1 && c != '\n' && c != EOF) { 201 *cp++ = c; 202 continue; 203 } 204 *cp = '\0'; 205 fputs(buf, tfp); 206 state = EOF; 207 while (c != EOF) { 208 if (c == '\n') 209 if ((tmp = peekc(stdin)) == EOF) 210 break; 211 else if (tmp == '\001') { 212 state = EOM; 213 break; 214 } 215 putc(c, tfp); 216 c = getchar(); 217 } 218 fclose(tfp); 219 goto badfmt; 220 } 221 *cp = '\0'; 222 fprintf(tfp, "%s:", buf); 223 hp = findheader(buf, state); 224 225 for (cp = buf; ; ) { 226 if (cp >= buf+sizeof(buf)-1) { 227 fprintf(stderr, "field truncated\n"); 228 while ((c = getchar()) != EOF && c != '\n') 229 putc(c, tfp); 230 } 231 if ((c = getchar()) == EOF) { 232 state = EOF; 233 break; 234 } 235 putc(c, tfp); 236 *cp++ = c; 237 if (c == '\n') 238 if ((c = peekc(stdin)) != ' ' && c != '\t') { 239 if (c == EOF) 240 state = EOF; 241 else if (c == '\001') 242 state = EOM; 243 break; 244 } 245 } 246 *cp = '\0'; 247 cp = buf; 248 break; 249 250 body: 251 state = BODY; 252 case BODY: 253 for (cp = buf; ; c = getchar()) { 254 if (c == EOF) { 255 state = EOF; 256 break; 257 } 258 if (c == '\001' && peekc(stdin) == '\001') { 259 state = EOM; 260 break; 261 } 262 putc(c, tfp); 263 *cp++ = c; 264 if (cp >= buf+sizeof(buf)-1 || c == '\n') 265 break; 266 } 267 *cp = '\0'; 268 if ((cp = index(buf, ':')) == NULL) 269 continue; 270 *cp++ = '\0'; 271 hp = findheader(buf, state); 272 } 273 274 /* 275 * Don't save the info if the header wasn't found, we don't 276 * care about the info, or the header is repeated. 277 */ 278 if (hp == NULL || !(hp->h_flags & H_SAV) || hp->h_info) 279 continue; 280 while (isspace(*cp)) 281 cp++; 282 if (*cp) { 283 info = cp; 284 while (*cp++); 285 cp--; 286 while (isspace(cp[-1])) 287 *--cp = '\0'; 288 hp->h_info = (char *) malloc(strlen(info) + 1); 289 if (hp->h_info == NULL) { 290 fprintf(stderr, "ran out of memory\n"); 291 continue; 292 } 293 strcpy(hp->h_info, info); 294 if (hp == INDEX) 295 chkindex(hp); 296 } 297 } 298 fclose(tfp); 299 /* 300 * Verify all the required pieces of information 301 * are present. 302 */ 303 for (hp = headers; hp->h_tag; hp++) { 304 /* 305 * Mail the bug report back to the sender with a note 306 * explaining they must conform to the specification. 307 */ 308 if ((hp->h_flags & H_REQ) && !(hp->h_flags & H_FND)) { 309 if (debug) 310 printf("Missing %s\n", hp->h_tag); 311 badfmt: 312 reply(FROM_I, errfile, tmpname); 313 file(tmpname, "errors"); 314 return(state == EOM); 315 } 316 } 317 /* 318 * Acknowledge receipt. 319 */ 320 reply(FROM_I, ackfile, (char *)0); 321 file(tmpname, folder); 322 /* 323 * Append information about the new bug report 324 * to the summary file. 325 */ 326 if ((fs = fopen(sumfile, "a")) == NULL) 327 fprintf(stderr, "Can't open %s\n", sumfile); 328 else { 329 fprintf(fs, "%14.14s/%-3d ", folder, num); 330 fprintf(fs, "%-51.51s Recv\n", INDEX_I); 331 fprintf(fs, "\t\t %-51.51s\n", SUBJECT_I); 332 } 333 fclose(fs); 334 return(state == EOM); 335 } 336 337 /* 338 * Lookup the string in the list of headers and return a pointer 339 * to the entry or NULL. 340 */ 341 342 struct header * 343 findheader(name, state) 344 char *name; 345 int state; 346 { 347 register struct header *hp; 348 349 if (debug) 350 printf("findheader(%s, %d)\n", name, state); 351 352 for (hp = headers; hp->h_tag; hp++) { 353 if (!streq(hp->h_tag, buf)) 354 continue; 355 if ((hp->h_flags & H_HDR) && state != FLD) 356 continue; 357 hp->h_flags |= H_FND; 358 return(hp); 359 } 360 return(NULL); 361 } 362 363 /* 364 * Check the format of the Index information. 365 * A side effect is to set the name of the folder if all is well. 366 */ 367 368 chkindex(hp) 369 struct header *hp; 370 { 371 register char *cp1, *cp2; 372 register char c; 373 struct stat stbuf; 374 375 if (debug) 376 printf("chkindex(%s)\n", hp->h_info); 377 /* 378 * Strip of leading "/", "usr/", "src/" or "sys/". 379 */ 380 cp1 = hp->h_info; 381 while (*cp1 == '/') 382 cp1++; 383 while (substr(cp1, "usr/") || substr(cp1, "src/") || substr(cp1, "sys/")) 384 cp1 += 4; 385 /* 386 * Read the folder name and remove it from the index line. 387 */ 388 for (cp2 = folder; ;) { 389 switch (c = *cp1++) { 390 case '/': 391 if (cp2 == folder) 392 continue; 393 break; 394 case '\0': 395 cp1--; 396 break; 397 case ' ': 398 case '\t': 399 while (isspace(*cp1)) 400 cp1++; 401 break; 402 default: 403 if (cp2 < folder+sizeof(folder)-1) 404 *cp2++ = c; 405 continue; 406 } 407 *cp2 = '\0'; 408 for (cp2 = hp->h_info; *cp2++ = *cp1++; ) 409 ; 410 break; 411 } 412 if (debug) 413 printf("folder = %s\n", folder); 414 /* 415 * Check to make sure we have a valid folder name 416 */ 417 if (stat(folder, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR) 418 return; 419 /* 420 * The Index line is not in the correct format so clear 421 * the H_FND flag to mail back the correct format. 422 */ 423 hp->h_flags &= ~H_FND; 424 } 425 426 /* 427 * Move or copy the file msg to the folder (directory). 428 * A side effect is to set num to the number of the file in folder. 429 */ 430 431 file(fname, folder) 432 char *fname, *folder; 433 { 434 register char *cp, n; 435 char msgname[MAXNAMLEN*2+2]; 436 struct stat stbuf; 437 DIR *dirp; 438 struct direct *d; 439 440 if (debug) 441 printf("file(%s, %s)\n", fname, folder); 442 /* 443 * Get the next number to use by finding the last message number 444 * in folder and adding one. 445 */ 446 if ((dirp = opendir(folder)) == NULL) { 447 fprintf(stderr, "Cannot open %s/%s\n", maildir, folder); 448 return; 449 } 450 num = 0; 451 while ((d = readdir(dirp)) != NULL) { 452 cp = d->d_name; 453 n = 0; 454 while (isdigit(*cp)) 455 n = n * 10 + (*cp++ - '0'); 456 if (*cp == '\0' && n > num) 457 num = n; 458 } 459 closedir(dirp); 460 num++; 461 /* 462 * Create the destination file "folder/num" and copy fname to it. 463 */ 464 sprintf(msgname, "%s/%d", folder, num); 465 if (link(fname, msgname) < 0) { 466 int fin, fout; 467 468 if ((fin = open(fname, 0)) < 0) { 469 fprintf(stderr, "cannot open %s\n", fname); 470 return; 471 } 472 if ((fout = creat(msgname, msg_prot)) < 0) { 473 fprintf(stderr, "cannot create %s\n", msgname); 474 return; 475 } 476 while ((n = read(fin, buf, sizeof(buf))) > 0) 477 write(fout, buf, n); 478 close(fin); 479 close(fout); 480 } 481 unlink(fname); 482 } 483 484 /* 485 * Mail file1 and file2 back to the sender. 486 */ 487 488 reply(to, file1, file2) 489 char *to, *file1, *file2; 490 { 491 int (*istat)(), (*qstat)(); 492 int pid, w, status, pfd[2], in; 493 FILE *fout; 494 495 if (debug) 496 printf("reply(%s, %s, %s)\n", to, file1, file2); 497 498 /* 499 * Create a temporary file to put the message in. 500 */ 501 mktemp(draft); 502 if ((fout = fopen(draft, "w")) == NULL) { 503 fprintf(stderr, "Can't create %s\n", draft); 504 return; 505 } 506 /* 507 * Output the proper header information. 508 */ 509 fprintf(fout, "Reply-To: 4bsd-bugs%%ucbarpa@BERKELEY\n"); 510 if (RETURNPATH_I != NULL) 511 to = RETURNPATH_I; 512 if (REPLYTO_I != NULL) 513 to = REPLYTO_I; 514 if ((to = fixaddr(to)) == 0) { 515 fprintf(stderr, "No one to reply to\n"); 516 return; 517 } 518 fprintf(fout, "To: %s\n", to); 519 if (SUBJECT_I) { 520 fprintf(fout, "Subject: "); 521 if ((SUBJECT_I[0] != 'R' && SUBJECT_I[0] != 'r') || 522 (SUBJECT_I[1] != 'E' && SUBJECT_I[1] != 'e') || 523 SUBJECT_I[2] != ':') 524 fprintf(fout, "Re: "); 525 fprintf(fout, "%s\n", SUBJECT_I); 526 } 527 if (DATE_I) { 528 fprintf(fout, "In-Acknowledgement-Of: Your message of "); 529 fprintf(fout, "%s.\n", DATE_I); 530 if (MSGID_I) 531 fprintf(fout, " %s\n", MSGID_I); 532 } 533 fprintf(fout, "----------\n"); 534 if ((in = open(file1, 0)) >= 0) { 535 while ((w = read(in, buf, sizeof(buf))) > 0) 536 fwrite(buf, 1, w, fout); 537 close(in); 538 } 539 if (file2 && (in = open(file2, 0)) >= 0) { 540 while ((w = read(in, buf, sizeof(buf))) > 0) 541 fwrite(buf, 1, w, fout); 542 close(in); 543 } 544 fclose(fout); 545 while ((pid = fork()) == -1) 546 sleep(5); 547 if (pid == 0) { 548 execl(deliver, "deliver", draft, 0); 549 _exit(127); 550 } 551 istat = signal(SIGINT, SIG_IGN); 552 qstat = signal(SIGQUIT, SIG_IGN); 553 while ((w = wait(&status)) != -1 && w != pid); 554 signal(SIGINT, istat); 555 signal(SIGQUIT, qstat); 556 if (w != -1 && status == 0) 557 unlink(draft); 558 } 559 560 /* 561 * fix names like "xxx (something)" to "xxx" and 562 * "xxx <something>" to "something". 563 */ 564 565 char * 566 fixaddr(text) 567 char *text; 568 { 569 register char *cp, *lp, c; 570 char *tp; 571 572 if (!text) 573 return(0); 574 for (lp = cp = text; ; ) { 575 switch (c = *cp++) { 576 case '(': 577 while (*cp && *cp++ != ')'); 578 continue; 579 case '<': 580 lp = text; 581 case '>': 582 continue; 583 case '\0': 584 while (lp != text && (*lp == ' ' || *lp == '\t')) 585 lp--; 586 *lp = c; 587 return(text); 588 } 589 *lp++ = c; 590 } 591 } 592 593 /* 594 * Compare two strings and convert any upper case letters to lower case. 595 */ 596 597 streq(s1, s2) 598 register char *s1, *s2; 599 { 600 register int c; 601 602 while (c = *s1++) 603 if ((c | 040) != (*s2++ | 040)) 604 return(0); 605 return(*s2 == '\0'); 606 } 607 608 /* 609 * Return true if string s2 matches the first part of s1. 610 */ 611 612 substr(s1, s2) 613 register char *s1, *s2; 614 { 615 register int c; 616 617 while (c = *s2++) 618 if (c != *s1++) 619 return(0); 620 return(1); 621 } 622 623 peekc(fp) 624 FILE *fp; 625 { 626 register c; 627 628 c = getc(fp); 629 ungetc(c, fp); 630 return(c); 631 } 632