xref: /original-bsd/libexec/bugfiler/bugfiler.c (revision 3708840b)
1 /*	bugfiler.c	4.1	83/05/11	*/
2 /*
3  * Bug report processing program.
4  * It is designed to be invoked by alias(5) and to be compatible with mh.
5  */
6 
7 #include <stdio.h>
8 #include <ctype.h>
9 #include <signal.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <dir.h>
13 
14 char	deliver[] = "/usr/local/lib/mh/deliver";
15 char	unixtomh[] = "/usr/local/lib/mh/unixtomh";
16 char	*maildir = "/ra/bugs/mail";
17 char	ackfile[] = ".ack";
18 char	errfile[] = ".format";
19 char	sumfile[] = "summary";
20 char	logfile[] = "errors/log";
21 char	tmpname[] = "BfXXXXXX";
22 char	draft[] = "RpXXXXXX";
23 
24 char	line[BUFSIZ];
25 char	folder[MAXNAMLEN];
26 int	num;
27 int	msg_prot = 0664;
28 
29 int	debug;
30 
31 char	*index();
32 char	*rindex();
33 char	*fixaddr();
34 
35 main(argc, argv)
36 	char *argv[];
37 {
38 	register char *cp;
39 
40 	if (argc > 3) {
41 	usage:
42 		fprintf(stderr, "Usage: bugfiler [-d] [maildir]\n");
43 		exit(1);
44 	}
45 	while (--argc > 0) {
46 		cp = *++argv;
47 		if (*cp == '-') while (*++cp)
48 			switch (*cp) {
49 			case 'd':
50 				debug++;
51 				break;
52 			default:
53 				goto usage;
54 			}
55 		else
56 			maildir = cp;
57 	}
58 	if (chdir(maildir) < 0) {
59 		fprintf(stderr, "can't chdir to %s\n", maildir);
60 		exit(1);
61 	}
62 	if (freopen(logfile, "a", stderr) == NULL)
63 		freopen("/dev/null", "w", stderr);
64 	exit(process());
65 }
66 
67 /* defines used for tag attributes */
68 
69 #define H_REQ 01
70 #define H_OPT 02
71 #define H_SAV 04
72 
73 #define FROM_I headers[0].h_info
74 #define SUBJECT_I headers[1].h_info
75 #define INDEX &headers[2]
76 #define INDEX_I headers[2].h_info
77 #define DATE_I headers[3].h_info
78 #define MSGID_I headers[4].h_info
79 #define REPLYTO_I headers[5].h_info
80 #define RETURNPATH_I headers[6].h_info
81 #define TO_I headers[7].h_info
82 #define CC_I headers[8].h_info
83 #define FIX headers[11]
84 
85 struct header {
86 	char	*h_tag;
87 	int	h_flags;
88 	char	*h_info;
89 } headers[] = {
90 	"From",		H_REQ|H_SAV, 0,
91 	"Subject",	H_REQ|H_SAV, 0,
92 	"Index",	H_REQ|H_SAV, 0,
93 	"Date",		H_OPT|H_SAV, 0,
94 	"Message-Id",	H_OPT|H_SAV, 0,
95 	"Reply-To",	H_OPT|H_SAV, 0,
96 	"Return-Path",	H_OPT|H_SAV, 0,
97 	"To",		H_OPT|H_SAV, 0,
98 	"Cc",		H_OPT|H_SAV, 0,
99 	"Description",	H_REQ,       0,
100 	"Repeat-By",	H_REQ,	     0,
101 	"Fix",		H_OPT,	     0,
102 	0,	0,	0,
103 };
104 
105 process()
106 {
107 	register struct header *hp;
108 	register char *cp;
109 	char *info;
110 	int tmp, pfd[2];
111 	FILE *fs;
112 
113 	/*
114 	 * Insure all headers are in a consistent
115 	 * state.  Anything left there is free'd.
116 	 */
117 	for (hp = headers; hp->h_tag; hp++) {
118 		if (hp->h_info) {
119 			if (hp->h_info != (char *) 1)
120 				free(hp->h_info);
121 			hp->h_info = 0;
122 		}
123 	}
124 #ifdef UNIXCOMP
125 	/*
126 	 * Convert UNIX style mail to mh style by filtering stdin through
127 	 * unixtomh.
128 	 */
129 	if (pipe(pfd) >= 0) {
130 		register int n;
131 
132 		while ((n = fork()) == -1)
133 			sleep(5);
134 		if (n == 0) {
135 			close(pfd[0]);
136 			dup2(pfd[1], 1);
137 			close(pfd[1]);
138 			execl(unixtomh, "unixtomh", 0);
139 			_exit(127);
140 		}
141 		close(pfd[1]);
142 		dup2(pfd[0], 0);
143 		close(pfd[0]);
144 	}
145 #endif
146 	/*
147 	 * Read the report and make a copy.  Must conform to RFC822 and
148 	 * be of the form... <tag>: <info>
149 	 */
150 	mktemp(tmpname);
151 	if ((tmp = creat(tmpname, msg_prot)) < 0)
152 		return(1);
153 	while ((cp = fgets(line, sizeof(line), stdin)) != NULL) {
154 		if (line[0] == '\01')
155 			continue;
156 		write(tmp, cp, strlen(cp));
157 		cp = index(cp, ':');
158 		if (cp == 0)
159 			continue;
160 		*cp++ = '\0';
161 		for (hp = headers; hp->h_tag; hp++)
162 			if (streq(hp->h_tag, line))
163 				break;
164 		if (hp->h_tag == 0)
165 			continue;
166 		if (!(hp->h_flags & H_SAV)) {
167 			hp->h_info = (char *) 1;
168 			continue;
169 		}
170 		while (isspace(*cp))
171 			cp++;
172 		if (*cp) {
173 			info = cp;
174 			while (*cp++);
175 			cp--;
176 			while (isspace(cp[-1]))
177 				*--cp = '\0';
178 			hp->h_info = (char *) malloc(strlen(info) + 1);
179 			if (hp->h_info == NULL)
180 				continue;
181 			strcpy(hp->h_info, info);
182 			if (hp == INDEX)
183 				chkindex(hp);
184 		}
185 	}
186 	close(tmp);
187 	/*
188 	 * Verify all the required pieces of information
189 	 * are present.
190 	 */
191 	for (hp = headers; hp->h_tag; hp++)
192 		if ((hp->h_flags & H_REQ) && !hp->h_info)
193 			break;
194 	if (hp->h_tag) {
195 		/*
196 		 * Mail the bug report back to the sender with a note
197 		 * explaining they must conform to the specification.
198 		 */
199 		if (debug)
200 			fprintf(stderr, "Missing %s\n", hp->h_tag);
201 		reply(FROM_I, errfile, tmpname);
202 		file(tmpname, "errors");
203 		return(0);
204 	}
205 	else {	/* Acknowledge receipt */
206 		reply(FROM_I, ackfile, (char *)0);
207 		file(tmpname, folder);
208 	}
209 	/*
210 	 * Append information about the new bug report
211 	 * to the summary file.
212 	 */
213 	if ((fs = fopen(sumfile, "a")) == NULL) {
214 		fprintf(stderr, "Can't open %s\n", sumfile);
215 		return(1);
216 	}
217 	fprintf(fs, "%14.14s/%-3d  %s\n\t\t    %s\n", folder, num, INDEX_I, SUBJECT_I);
218 	fclose(fs);
219 	return(0);
220 }
221 
222 /*
223  * Check the format of the Index information.
224  * A side effect is to set the name of the folder if all is well.
225  */
226 
227 chkindex(hp)
228 	struct header *hp;
229 {
230 	register char *cp1, *cp2, *cp3, *cp4;
231 	register char c;
232 	struct stat stbuf;
233 
234 	if (debug)
235 		fprintf(stderr, "chkindex(%s)\n", hp->h_info);
236 	/*
237 	 * Read the folder name and remove it from the index line.
238 	 */
239 	for (cp1 = hp->h_info, cp2 = NULL, cp3 = folder, cp4 == NULL; ;) {
240 		c = *cp1++;
241 		if (c == '\0' || isspace(c) || cp3 >= folder+sizeof(folder)-1) {
242 			if (cp4 == NULL)
243 				*cp3 = '\0';
244 			else
245 				*cp4 = '\0';
246 			if (cp2 == NULL) {
247 				cp2 = cp1 - 1;
248 				while (isspace(*cp2))
249 					cp2++;
250 			}
251 			for (cp3 = hp->h_info; *cp3++ = *cp2++; );
252 			break;
253 		} else {
254 			if (c == '/') {
255 				cp2 = cp1;
256 				cp4 = cp3;
257 			}
258 			*cp3++ = c;
259 		}
260 	}
261 	/*
262 	 * Check to see if a Fix is included.
263 	if ((cp1 = rindex(hp->h_info, ' ')) == NULL) {
264 		if ((cp1 = rindex(hp->h_info, '\t')) != NULL)
265 			cp1++;
266 	} else
267 		cp1++;
268 	if (cp1 != NULL && streq(cp1, FIX.h_tag))
269 		FIX.h_flags = H_REQ;
270 	else
271 		FIX.h_flags = 0;
272 	 */
273 	/*
274 	 * Check to make sure we have a valid folder name
275 	 */
276 	if (stat(folder, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR)
277 		return;
278 	/*
279 	 * The Index line is not in the correct format so clear
280 	 * the h_info line to mail back the correct format.
281 	 */
282 	hp->h_info = 0;
283 }
284 
285 /*
286  * Move or copy the file msg to the folder (directory).
287  * A side effect is to set num to the number of the file in folder.
288  */
289 
290 file(fname, folder)
291 	char *fname, *folder;
292 {
293 	register char *cp, n;
294 	char msgname[MAXNAMLEN*2+2];
295 	struct stat stbuf;
296 	DIR *dirp;
297 	struct direct *d;
298 
299 	if (debug)
300 		fprintf(stderr, "file(%s, %s)\n", fname, folder);
301 	/*
302 	 * Get the next number to use by finding the last message number
303 	 * in folder and adding one.
304 	 */
305 	if ((dirp = opendir(folder)) == NULL) {
306 		fprintf(stderr, "Cannot open %s/%s\n", maildir, folder);
307 		return;
308 	}
309 	num = 0;
310 	while ((d = readdir(dirp)) != NULL) {
311 		cp = d->d_name;
312 		n = 0;
313 		while (isdigit(*cp))
314 			n = n * 10 + (*cp++ - '0');
315 		if (*cp == '\0' && n > num)
316 			num = n;
317 	}
318 	closedir(dirp);
319 	num++;
320 	/*
321 	 * Create the destination file "folder/num" and copy fname to it.
322 	 */
323 	sprintf(msgname, "%s/%d", folder, num);
324 	if (link(fname, msgname) < 0) {
325 		int fin, fout;
326 
327 		if ((fin = open(fname, 0)) < 0)
328 			return;
329 		if ((fout = open(msgname, 1)) < 0)
330 			return;
331 		while ((n = read(fin, line, sizeof(line))) > 0)
332 			write(fout, line, n);
333 		close(fin);
334 		close(fout);
335 	}
336 	unlink(fname);
337 }
338 
339 /*
340  * Mail file1 and file2 back to the sender.
341  */
342 
343 reply(to, file1, file2)
344 	char	*to, *file1, *file2;
345 {
346 	int (*istat)(), (*qstat)();
347 	int pid, w, status, pfd[2], in;
348 	FILE *fout;
349 
350 	if (debug)
351 		fprintf(stderr, "reply(%s, %s, %s)\n", to, file1, file2);
352 	/*
353 	 * Create a temporary file to put the message in.
354 	 */
355 	mktemp(draft);
356 	if ((fout = fopen(draft, "w")) == NULL) {
357 		fprintf(stderr, "Can't create %s\n", draft);
358 		return;
359 	}
360 	/*
361 	 * Output the proper header information.
362 	 */
363 	fprintf(fout, "Reply-To: 4bsd-bugs@BERKELEY\n");
364 	if (RETURNPATH_I != NULL)
365 		to = RETURNPATH_I;
366 	if (REPLYTO_I != NULL)
367 		to = REPLYTO_I;
368 	if ((to = fixaddr(to)) == 0) {
369 		fprintf(stderr, "No one to reply to\n");
370 		return;
371 	}
372 	fprintf(fout, "To: %s\n", to);
373 	if (SUBJECT_I) {
374 		fprintf(fout, "Subject: ");
375 		if ((SUBJECT_I[0] != 'R' && SUBJECT_I[0] != 'r') ||
376 		    (SUBJECT_I[1] != 'E' && SUBJECT_I[1] != 'e') ||
377 		    SUBJECT_I[2] != ':')
378 			fprintf(fout, "Re: ");
379 		fprintf(fout, "%s\n", SUBJECT_I);
380 	}
381 	if (DATE_I) {
382 		fprintf(fout, "In-Acknowledgement-Of: Your message of ");
383 		fprintf(fout, "%s.\n", DATE_I);
384 		if (MSGID_I)
385 			fprintf(fout, "             %s\n", MSGID_I);
386 	}
387 	fprintf(fout, "----------\n");
388 	if ((in = open(file1, 0)) >= 0) {
389 		while ((w = read(in, line, sizeof(line))) > 0)
390 			fwrite(line, 1, w, fout);
391 		close(in);
392 	}
393 	if (file2 && (in = open(file2, 0)) >= 0) {
394 		while ((w = read(in, line, sizeof(line))) > 0)
395 			fwrite(line, 1, w, fout);
396 		close(in);
397 	}
398 	fclose(fout);
399 	while ((pid = fork()) == -1)
400 		sleep(5);
401 	if (pid == 0) {
402 		execl(deliver, "deliver", draft, 0);
403 		_exit(127);
404 	}
405 	istat = signal(SIGINT, SIG_IGN);
406 	qstat = signal(SIGQUIT, SIG_IGN);
407 	while ((w = wait(&status)) != -1 && w != pid);
408 	signal(SIGINT, istat);
409 	signal(SIGQUIT, qstat);
410 	if (w != -1 && status == 0)
411 		unlink(draft);
412 }
413 
414 /*
415  * fix names like "xxx (something)" to "xxx" and
416  * "xxx <something>" to "something".
417  */
418 
419 char *
420 fixaddr(text)
421 	char *text;
422 {
423 	register char *cp, *lp, c;
424 	char *tp;
425 
426 	if (!text)
427 		return(0);
428 	for (lp = cp = text; ; ) {
429 		switch (c = *cp++) {
430 		case '(':
431 			while (*cp && *cp++ != ')');
432 			continue;
433 		case '<':
434 			lp = text;
435 		case '>':
436 			continue;
437 		case '\0':
438 			while (lp != text && (*lp == ' ' || *lp == '\t'))
439 				lp--;
440 			*lp = c;
441 			return(text);
442 		}
443 		*lp++ = c;
444 	}
445 }
446 
447 /*
448  * Compare two strings and convert any upper case letters to lower case.
449  */
450 
451 streq(c1, c2)
452 	register char *c1, *c2;
453 {
454 	register int c;
455 
456 	while (c = *c1++)
457 		if ((c | 040) != (*c2++ | 040))
458 			return(0);
459 	return(*c2 == '\0');
460 }
461