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