/* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ #if !defined(lint) && defined(DOSCCS) char copyright[] = "@(#) Copyright (c) 1983 Regents of the University of California.\n\ All rights reserved.\n"; static char sccsid[] = "@(#)bugfiler.c 5.5.2 (2.11BSD GTE) 1996/10/23"; #endif /* * Bug report processing program. * It is designed to be invoked by alias(5) * and to be compatible with mh. */ #include #include #include #include #include #include #include #include #ifndef BUGS_NAME #define BUGS_NAME "2bsd-bugs" #endif #ifndef BUGS_HOME #define BUGS_HOME "@moe.2bsd.com" #endif #ifndef UNIXTOMH #define UNIXTOMH "/usr/libexec/unixtomh" #endif char *bugperson = "bugs"; char *maildir = "mail"; char ackfile[] = ".ack"; char errfile[] = ".format"; char sumfile[] = "summary"; char logfile[] = "errors/log"; char redistfile[] = ".redist"; char tmpname[] = "BfXXXXXX"; char draft[] = "RpXXXXXX"; char disttmp[] = "RcXXXXXX"; char buf[8192]; char folder[MAXNAMLEN]; int num; int msg_prot = 0640; int folder_prot = 0750; int debug; char *index(); char *rindex(); char *fixaddr(); char *any(); main(argc, argv) char *argv[]; { register char *cp; register int n; int pfd[2]; if (argc > 4) { usage: fprintf(stderr, "Usage: bugfiler [-d] [-mmsg_mode] [maildir]\n"); exit(1); } while (--argc > 0) { cp = *++argv; if (*cp == '-') switch (cp[1]) { case 'd': debug++; break; case 'm': /* set message protection */ n = 0; for (cp += 2; *cp >= '0' && *cp <= '7'; ) n = (n << 3) + (*cp++ - '0'); msg_prot = n & 0777; break; default: goto usage; } else maildir = cp; } if (!debug) freopen(logfile, "a", stderr); if (bugperson) { struct passwd *pwd = getpwnam(bugperson); if (pwd == NULL) { fprintf(stderr, "%s: bugs person is unknown\n", bugperson); exit(1); } if (chdir(pwd->pw_dir) < 0) { fprintf(stderr, "can't chdir to %s\n", pwd->pw_dir); exit(1); } setuid(pwd->pw_uid); } if (chdir(maildir) < 0) { fprintf(stderr, "can't chdir to %s\n", maildir); exit(1); } umask(0); #ifdef UNIXCOMP /* * Convert UNIX style mail to mh style by filtering stdin through * unixtomh. */ if (pipe(pfd) >= 0) { while ((n = fork()) == -1) sleep(5); if (n == 0) { close(pfd[0]); dup2(pfd[1], 1); close(pfd[1]); execl(UNIXTOMH, "unixtomh", 0); _exit(127); } close(pfd[1]); dup2(pfd[0], 0); close(pfd[0]); } #endif while (process()) ; exit(0); } /* states */ #define EOM 0 /* End of message seen */ #define FLD 1 /* Looking for header lines */ #define BODY 2 /* Looking for message body lines */ /* defines used for tag attributes */ #define H_REQ 01 #define H_SAV 02 #define H_HDR 04 #define H_FND 010 #define FROM &headers[0] #define FROM_I headers[0].h_info #define SUBJECT_I headers[1].h_info #define INDEX &headers[2] #define INDEX_I headers[2].h_info #define DATE_I headers[3].h_info #define MSGID_I headers[4].h_info #define REPLYTO_I headers[5].h_info #define TO_I headers[6].h_info #define CC_I headers[7].h_info #define FIX headers[10] struct header { char *h_tag; int h_flags; char *h_info; } headers[] = { "From", H_REQ|H_SAV|H_HDR, 0, "Subject", H_REQ|H_SAV, 0, "Index", H_REQ|H_SAV, 0, "Date", H_SAV|H_HDR, 0, "Message-Id", H_SAV|H_HDR, 0, "Reply-To", H_SAV|H_HDR, 0, "To", H_SAV|H_HDR, 0, "Cc", H_SAV|H_HDR, 0, "Description", H_REQ, 0, "Repeat-By", 0, 0, "Fix", 0, 0, 0, 0, 0, }; struct header *findheader(); process() { register struct header *hp; register char *cp; register int c; char *info; int state, tmp, no_reply = 0; FILE *tfp, *fs; /* * Insure all headers are in a consistent * state. Anything left there is free'd. */ for (hp = headers; hp->h_tag; hp++) { hp->h_flags &= ~H_FND; if (hp->h_info) { free(hp->h_info); hp->h_info = 0; } } /* * Read the report and make a copy. Must conform to RFC822 and * be of the form... : * Note that the input is expected to be in mh mail format * (i.e., messages are separated by lines of ^A's). */ while ((c = getchar()) == '\001' && peekc(stdin) == '\001') while (getchar() != '\n') ; if (c == EOF) return(0); /* all done */ mktemp(tmpname); if ((tmp = creat(tmpname, msg_prot)) < 0) { fprintf(stderr, "cannot create %s\n", tmpname); exit(1); } if ((tfp = fdopen(tmp, "w")) == NULL) { fprintf(stderr, "cannot fdopen temp file\n"); exit(1); } for (state = FLD; state != EOF && state != EOM; c = getchar()) { switch (state) { case FLD: if (c == '\n' || c == '-') goto body; for (cp = buf; c != ':'; c = getchar()) { if (cp < buf+sizeof(buf)-1 && c != '\n' && c != EOF) { *cp++ = c; continue; } *cp = '\0'; fputs(buf, tfp); state = EOF; while (c != EOF) { if (c == '\n') if ((tmp = peekc(stdin)) == EOF) break; else if (tmp == '\001') { state = EOM; break; } putc(c, tfp); c = getchar(); } fclose(tfp); goto badfmt; } *cp = '\0'; fprintf(tfp, "%s:", buf); hp = findheader(buf, state); for (cp = buf; ; ) { if (cp >= buf+sizeof(buf)-1) { fprintf(stderr, "field truncated\n"); while ((c = getchar()) != EOF && c != '\n') putc(c, tfp); } if ((c = getchar()) == EOF) { state = EOF; break; } putc(c, tfp); *cp++ = c; if (c == '\n') if ((c = peekc(stdin)) != ' ' && c != '\t') { if (c == EOF) state = EOF; else if (c == '\001') state = EOM; break; } } *cp = '\0'; cp = buf; break; body: state = BODY; case BODY: for (cp = buf; ; c = getchar()) { if (c == EOF) { state = EOF; break; } if (c == '\001' && peekc(stdin) == '\001') { state = EOM; break; } putc(c, tfp); *cp++ = c; if (cp >= buf+sizeof(buf)-1 || c == '\n') break; } *cp = '\0'; if ((cp = index(buf, ':')) == NULL) continue; *cp++ = '\0'; hp = findheader(buf, state); } /* * Don't save the info if the header wasn't found, we don't * care about the info, or the header is repeated. */ if (hp == NULL || !(hp->h_flags & H_SAV) || hp->h_info) continue; while (isspace(*cp)) cp++; if (*cp) { info = cp; while (*cp++); cp--; while (isspace(cp[-1])) *--cp = '\0'; hp->h_info = (char *) malloc(strlen(info) + 1); if (hp->h_info == NULL) { fprintf(stderr, "ran out of memory\n"); continue; } strcpy(hp->h_info, info); if (hp == FROM && chkfrom(hp) < 0) no_reply = 1; if (hp == INDEX) chkindex(hp); } } fclose(tfp); if (no_reply) { unlink(tmpname); exit(0); } /* * Verify all the required pieces of information * are present. */ for (hp = headers; hp->h_tag; hp++) { /* * Mail the bug report back to the sender with a note * explaining they must conform to the specification. */ if ((hp->h_flags & H_REQ) && !(hp->h_flags & H_FND)) { if (debug) printf("Missing %s\n", hp->h_tag); badfmt: reply(FROM_I, errfile, tmpname); file(tmpname, "errors"); redistribute("errors", num); return(state == EOM); } } /* * Acknowledge receipt. */ reply(FROM_I, ackfile, (char *)0); file(tmpname, folder); /* * Append information about the new bug report * to the summary file. */ if ((fs = fopen(sumfile, "a")) == NULL) fprintf(stderr, "Can't open %s\n", sumfile); else { fprintf(fs, "%14.14s/%-3d ", folder, num); fprintf(fs, "%-51.51s Recv\n", INDEX_I); fprintf(fs, "\t\t %-51.51s\n", SUBJECT_I); } fclose(fs); /* * Check redistribution list and, if members, * mail a copy of the bug report to these people. */ redistribute(folder, num); return(state == EOM); } /* * Lookup the string in the list of headers and return a pointer * to the entry or NULL. */ struct header * findheader(name, state) char *name; int state; { register struct header *hp; if (debug) printf("findheader(%s, %d)\n", name, state); for (hp = headers; hp->h_tag; hp++) { if (!streq(hp->h_tag, buf)) continue; if ((hp->h_flags & H_HDR) && state != FLD) continue; hp->h_flags |= H_FND; return(hp); } return(NULL); } /* * Check the FROM line to eliminate loops. */ chkfrom(hp) struct header *hp; { register char *cp1, *cp2; register char c; if (debug) printf("chkfrom(%s)\n", hp->h_info); if (substr(hp->h_info, BUGS_NAME)) return(-1); if (substr(hp->h_info, "MAILER-DAEMON")) return(-1); return(0); } /* * Check the format of the Index information. * A side effect is to set the name of the folder if all is well. */ chkindex(hp) struct header *hp; { register char *cp1, *cp2; register char c; struct stat stbuf; if (debug) printf("chkindex(%s)\n", hp->h_info); /* * Strip of leading "/", ".", "usr/", or "src/". */ cp1 = hp->h_info; while (*cp1 == '/' || *cp1 == '.') cp1++; while (substr(cp1, "usr/") || substr(cp1, "src/")) cp1 += 4; /* * Read the folder name and remove it from the index line. */ for (cp2 = folder; ;) { switch (c = *cp1++) { case '/': if (cp2 == folder) continue; break; case '\0': cp1--; break; case ' ': case '\t': while (isspace(*cp1)) cp1++; break; default: if (cp2 < folder+sizeof(folder)-1) *cp2++ = c; continue; } *cp2 = '\0'; for (cp2 = hp->h_info; *cp2++ = *cp1++; ) ; break; } if (debug) printf("folder = %s\n", folder); /* * Check to make sure we have a valid folder name */ if (stat(folder, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR) return; /* * The Index line is not in the correct format so clear * the H_FND flag to mail back the correct format. */ hp->h_flags &= ~H_FND; } /* * Move or copy the file msg to the folder (directory). * As a side effect, num is set to the number under which * the message is filed in folder. */ file(fname, folder) char *fname, *folder; { register char *cp; register int n; char msgname[MAXNAMLEN*2+2]; struct stat stbuf; DIR *dirp; struct direct *d; if (debug) printf("file(%s, %s)\n", fname, folder); /* * Get the next number to use by finding the last message number * in folder and adding one. */ if ((dirp = opendir(folder)) == NULL) { (void) mkdir(folder, folder_prot); if ((dirp = opendir(folder)) == NULL) { fprintf(stderr, "Cannot open %s/%s\n", maildir, folder); return; } } num = 0; while ((d = readdir(dirp)) != NULL) { cp = d->d_name; n = 0; while (isdigit(*cp)) n = n * 10 + (*cp++ - '0'); if (*cp == '\0' && n > num) num = n; } closedir(dirp); num++; /* * Create the destination file "folder/num" and copy fname to it. */ sprintf(msgname, "%s/%d", folder, num); if (link(fname, msgname) < 0) { int fin, fout; if ((fin = open(fname, 0)) < 0) { fprintf(stderr, "cannot open %s\n", fname); return; } if ((fout = creat(msgname, msg_prot)) < 0) { fprintf(stderr, "cannot create %s\n", msgname); return; } while ((n = read(fin, buf, sizeof(buf))) > 0) write(fout, buf, n); close(fin); close(fout); } unlink(fname); } /* * Redistribute a bug report to those people indicated * in the redistribution list file. Perhaps should also * annotate bug report with this information for future * reference? */ redistribute(folder, num) char *folder; int num; { FILE *fredist, *fbug, *ftemp; char line[BUFSIZ], bug[2 * MAXNAMLEN + 1]; register char *cp; int redistcnt, continuation, first; fredist = fopen(redistfile, "r"); if (fredist == NULL) { if (debug) printf("redistribute(%s, %d), no distribution list\n", folder, num); return; } continuation = 0; first = 1; redistcnt = 0; while (fgets(line, sizeof (line) - 1, fredist) != NULL) { if (debug) printf("%s: %s", redistfile, line); if (continuation && index(line, '\\')) continue; continuation = 0; cp = any(line, " \t"); if (cp == NULL) continue; *cp++ = '\0'; if (strcmp(folder, line) == 0) goto found; if (index(cp, '\\')) continuation = 1; } if (debug) printf("no redistribution list found\n"); fclose(fredist); return; found: mktemp(disttmp); ftemp = fopen(disttmp, "w+r"); if (ftemp == NULL) { if (debug) printf("%s: couldn't create\n", disttmp); return; } again: if (debug) printf("redistribution list %s", cp); while (cp) { char *user, terminator; while (*cp && (*cp == ' ' || *cp == '\t' || *cp == ',')) cp++; user = cp, cp = any(cp, ", \t\n\\"); if (cp) { terminator = *cp; *cp++ = '\0'; if (terminator == '\n') cp = 0; if (terminator == '\\') continuation++; } if (*user == '\0') continue; if (debug) printf("copy to %s\n", user); if (first) { fprintf(ftemp, "Resent-To: %s", user); first = 0; } else fprintf(ftemp, ", %s", user); redistcnt++; } if (!first) putc('\n', ftemp); if (continuation) { first = 1; continuation = 0; cp = line; if (fgets(line, sizeof (line) - 1, fredist)) goto again; } fclose(fredist); if (redistcnt == 0) goto cleanup; if (! SUBJECT_I) { fprintf(ftemp, "Subject: "); fprintf(ftemp, "Untitled bug report\n"); } fprintf(ftemp, "Folder: %s/%d\n", folder, num); /* * Create copy of bug report. Perhaps we should * truncate large messages and just give people * a pointer to the original? */ sprintf(bug, "%s/%d", folder, num); fbug = fopen(bug, "r"); if (fbug == NULL) { if (debug) printf("%s: disappeared?\n", bug); goto cleanup; } first = 1; while (fgets(line, sizeof (line) - 1, fbug)) { /* first blank line indicates start of mesg */ if (first && line[0] == '\n') { first = 0; } fputs(line, ftemp); } fclose(fbug); if (first) { if (debug) printf("empty bug report?\n"); goto cleanup; } if (dodeliver(ftemp)) unlink(disttmp); fclose(ftemp); return; cleanup: fclose(ftemp); unlink(disttmp); } dodeliver(fd) FILE *fd; { char buf[BUFSIZ], cmd[BUFSIZ]; FILE *pf, *popen(); sprintf(cmd, "%s -i -t", _PATH_SENDMAIL); if (debug) { strcat(cmd, " -v"); printf("dodeliver \"%s\"\n", cmd); } pf = popen(cmd, "w"); if (pf == NULL) { if (debug) printf("dodeliver, \"%s\" failed\n", cmd); return (0); } rewind(fd); while (fgets(buf, sizeof (buf) - 1, fd)) { if (debug) printf("%s", buf); (void) fputs(buf, pf); } if (debug) printf("EOF\n"); (void) pclose(pf); return (1); } /* * Mail file1 and file2 back to the sender. */ reply(to, file1, file2) char *to, *file1, *file2; { int pfd[2], in, w; FILE *fout; if (debug) printf("reply(%s, %s, %s)\n", to, file1, file2); /* * Create a temporary file to put the message in. */ mktemp(draft); if ((fout = fopen(draft, "w+r")) == NULL) { fprintf(stderr, "Can't create %s\n", draft); return; } /* * Output the proper header information. */ fprintf(fout, "Reply-To: %s%s\n", BUGS_NAME, BUGS_HOME); fprintf(fout, "From: %s%s (Bugs Bunny)\n", BUGS_NAME, BUGS_HOME); if (REPLYTO_I != NULL) to = REPLYTO_I; if ((to = fixaddr(to)) == 0) { fprintf(stderr, "No one to reply to\n"); return; } fprintf(fout, "To: %s\n", to); if (SUBJECT_I) { fprintf(fout, "Subject: "); if ((SUBJECT_I[0] != 'R' && SUBJECT_I[0] != 'r') || (SUBJECT_I[1] != 'E' && SUBJECT_I[1] != 'e') || SUBJECT_I[2] != ':') fprintf(fout, "Re: "); fprintf(fout, "%s\n", SUBJECT_I); } if (DATE_I) { fprintf(fout, "In-Acknowledgement-Of: Your message of "); fprintf(fout, "%s.\n", DATE_I); if (MSGID_I) fprintf(fout, " %s\n", MSGID_I); } fprintf(fout, "\n"); if ((in = open(file1, 0)) >= 0) { while ((w = read(in, buf, sizeof(buf))) > 0) fwrite(buf, 1, w, fout); close(in); } if (file2 && (in = open(file2, 0)) >= 0) { while ((w = read(in, buf, sizeof(buf))) > 0) fwrite(buf, 1, w, fout); close(in); } if (dodeliver(fout)) unlink(draft); fclose(fout); } /* * fix names like "xxx (something)" to "xxx" and * "xxx " to "something". */ char * fixaddr(text) char *text; { register char *cp, *lp, c; char *tp; if (!text) return(0); for (lp = cp = text; ; ) { switch (c = *cp++) { case '(': while (*cp && *cp++ != ')'); continue; case '<': lp = text; case '>': continue; case '\0': while (lp != text && (*lp == ' ' || *lp == '\t')) lp--; *lp = c; return(text); } *lp++ = c; } } /* * Compare two strings and convert any upper case letters to lower case. */ streq(s1, s2) register char *s1, *s2; { register int c; while (c = *s1++) if ((c | 040) != (*s2++ | 040)) return(0); return(*s2 == '\0'); } /* * Return true if string s2 matches the first part of s1. */ substr(s1, s2) register char *s1, *s2; { register int c; while (c = *s2++) if (c != *s1++) return(0); return(1); } char * any(cp, set) register char *cp; char *set; { register char *sp; if (cp == 0 || set == 0) return (0); while (*cp) { for (sp = set; *sp; sp++) if (*cp == *sp) return (cp); cp++; } return ((char *)0); } peekc(fp) FILE *fp; { register c; c = getc(fp); ungetc(c, fp); return(c); }