xref: /original-bsd/libexec/bugfiler/bugfiler.c (revision 8251a00e)
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