1: /*
2: * Copyright (c) 1983 Regents of the University of California.
3: * All rights reserved. The Berkeley software License Agreement
4: * specifies the terms and conditions for redistribution.
5: */
6:
7: #ifndef lint
8: char copyright[] =
9: "@(#) Copyright (c) 1983 Regents of the University of California.\n\
10: All rights reserved.\n";
11: #endif not lint
12:
13: #ifndef lint
14: static char sccsid[] = "@(#)bugfiler.c 5.5 (Berkeley) 86/05/20";
15: #endif not lint
16:
17: /*
18: * Bug report processing program.
19: * It is designed to be invoked by alias(5)
20: * and to be compatible with mh.
21: */
22:
23: #include <stdio.h>
24: #include <ctype.h>
25: #include <signal.h>
26: #include <pwd.h>
27:
28: #include <sys/types.h>
29: #include <sys/stat.h>
30: #include <sys/dir.h>
31:
32: #ifndef BUGS_NAME
33: #define BUGS_NAME "4bsd-bugs"
34: #endif
35: #ifndef BUGS_HOME
36: #define BUGS_HOME "@ucbvax.BERKELEY.EDU"
37: #endif
38: #define MAILCMD "/usr/lib/sendmail -i -t"
39:
40: #ifndef UNIXTOMH
41: #define UNIXTOMH "/usr/lib/unixtomh"
42: #endif
43: char *bugperson = "bugs";
44: char *maildir = "mail";
45: char ackfile[] = ".ack";
46: char errfile[] = ".format";
47: char sumfile[] = "summary";
48: char logfile[] = "errors/log";
49: char redistfile[] = ".redist";
50: char tmpname[] = "BfXXXXXX";
51: char draft[] = "RpXXXXXX";
52: char disttmp[] = "RcXXXXXX";
53:
54: char buf[8192];
55: char folder[MAXNAMLEN];
56: int num;
57: int msg_prot = 0640;
58: int folder_prot = 0750;
59:
60: int debug;
61:
62: char *index();
63: char *rindex();
64: char *fixaddr();
65: char *any();
66:
67: main(argc, argv)
68: char *argv[];
69: {
70: register char *cp;
71: register int n;
72: int pfd[2];
73:
74: if (argc > 4) {
75: usage:
76: fprintf(stderr, "Usage: bugfiler [-d] [-mmsg_mode] [maildir]\n");
77: exit(1);
78: }
79: while (--argc > 0) {
80: cp = *++argv;
81: if (*cp == '-')
82: switch (cp[1]) {
83: case 'd':
84: debug++;
85: break;
86:
87: case 'm': /* set message protection */
88: n = 0;
89: for (cp += 2; *cp >= '0' && *cp <= '7'; )
90: n = (n << 3) + (*cp++ - '0');
91: msg_prot = n & 0777;
92: break;
93:
94: default:
95: goto usage;
96: }
97: else
98: maildir = cp;
99: }
100: if (!debug)
101: freopen(logfile, "a", stderr);
102:
103: if (bugperson) {
104: struct passwd *pwd = getpwnam(bugperson);
105:
106: if (pwd == NULL) {
107: fprintf(stderr, "%s: bugs person is unknown\n",
108: bugperson);
109: exit(1);
110: }
111: if (chdir(pwd->pw_dir) < 0) {
112: fprintf(stderr, "can't chdir to %s\n", pwd->pw_dir);
113: exit(1);
114: }
115: setuid(pwd->pw_uid);
116: }
117: if (chdir(maildir) < 0) {
118: fprintf(stderr, "can't chdir to %s\n", maildir);
119: exit(1);
120: }
121: umask(0);
122:
123: #ifdef UNIXCOMP
124: /*
125: * Convert UNIX style mail to mh style by filtering stdin through
126: * unixtomh.
127: */
128: if (pipe(pfd) >= 0) {
129: while ((n = fork()) == -1)
130: sleep(5);
131: if (n == 0) {
132: close(pfd[0]);
133: dup2(pfd[1], 1);
134: close(pfd[1]);
135: execl(UNIXTOMH, "unixtomh", 0);
136: _exit(127);
137: }
138: close(pfd[1]);
139: dup2(pfd[0], 0);
140: close(pfd[0]);
141: }
142: #endif
143: while (process())
144: ;
145: exit(0);
146: }
147:
148: /* states */
149:
150: #define EOM 0 /* End of message seen */
151: #define FLD 1 /* Looking for header lines */
152: #define BODY 2 /* Looking for message body lines */
153:
154: /* defines used for tag attributes */
155:
156: #define H_REQ 01
157: #define H_SAV 02
158: #define H_HDR 04
159: #define H_FND 010
160:
161: #define FROM &headers[0]
162: #define FROM_I headers[0].h_info
163: #define SUBJECT_I headers[1].h_info
164: #define INDEX &headers[2]
165: #define INDEX_I headers[2].h_info
166: #define DATE_I headers[3].h_info
167: #define MSGID_I headers[4].h_info
168: #define REPLYTO_I headers[5].h_info
169: #define TO_I headers[6].h_info
170: #define CC_I headers[7].h_info
171: #define FIX headers[10]
172:
173: struct {
174: char *h_tag;
175: int h_flags;
176: char *h_info;
177: } [] = {
178: "From", H_REQ|H_SAV|H_HDR, 0,
179: "Subject", H_REQ|H_SAV, 0,
180: "Index", H_REQ|H_SAV, 0,
181: "Date", H_SAV|H_HDR, 0,
182: "Message-Id", H_SAV|H_HDR, 0,
183: "Reply-To", H_SAV|H_HDR, 0,
184: "To", H_SAV|H_HDR, 0,
185: "Cc", H_SAV|H_HDR, 0,
186: "Description", H_REQ, 0,
187: "Repeat-By", 0, 0,
188: "Fix", 0, 0,
189: 0, 0, 0,
190: };
191:
192: struct header *findheader();
193:
194: process()
195: {
196: register struct header *hp;
197: register char *cp;
198: register int c;
199: char *info;
200: int state, tmp, no_reply = 0;
201: FILE *tfp, *fs;
202:
203: /*
204: * Insure all headers are in a consistent
205: * state. Anything left there is free'd.
206: */
207: for (hp = headers; hp->h_tag; hp++) {
208: hp->h_flags &= ~H_FND;
209: if (hp->h_info) {
210: free(hp->h_info);
211: hp->h_info = 0;
212: }
213: }
214: /*
215: * Read the report and make a copy. Must conform to RFC822 and
216: * be of the form... <tag>: <info>
217: * Note that the input is expected to be in mh mail format
218: * (i.e., messages are separated by lines of ^A's).
219: */
220: while ((c = getchar()) == '\001' && peekc(stdin) == '\001')
221: while (getchar() != '\n')
222: ;
223: if (c == EOF)
224: return(0); /* all done */
225:
226: mktemp(tmpname);
227: if ((tmp = creat(tmpname, msg_prot)) < 0) {
228: fprintf(stderr, "cannot create %s\n", tmpname);
229: exit(1);
230: }
231: if ((tfp = fdopen(tmp, "w")) == NULL) {
232: fprintf(stderr, "cannot fdopen temp file\n");
233: exit(1);
234: }
235:
236: for (state = FLD; state != EOF && state != EOM; c = getchar()) {
237: switch (state) {
238: case FLD:
239: if (c == '\n' || c == '-')
240: goto body;
241: for (cp = buf; c != ':'; c = getchar()) {
242: if (cp < buf+sizeof(buf)-1 && c != '\n' && c != EOF) {
243: *cp++ = c;
244: continue;
245: }
246: *cp = '\0';
247: fputs(buf, tfp);
248: state = EOF;
249: while (c != EOF) {
250: if (c == '\n')
251: if ((tmp = peekc(stdin)) == EOF)
252: break;
253: else if (tmp == '\001') {
254: state = EOM;
255: break;
256: }
257: putc(c, tfp);
258: c = getchar();
259: }
260: fclose(tfp);
261: goto badfmt;
262: }
263: *cp = '\0';
264: fprintf(tfp, "%s:", buf);
265: hp = findheader(buf, state);
266:
267: for (cp = buf; ; ) {
268: if (cp >= buf+sizeof(buf)-1) {
269: fprintf(stderr, "field truncated\n");
270: while ((c = getchar()) != EOF && c != '\n')
271: putc(c, tfp);
272: }
273: if ((c = getchar()) == EOF) {
274: state = EOF;
275: break;
276: }
277: putc(c, tfp);
278: *cp++ = c;
279: if (c == '\n')
280: if ((c = peekc(stdin)) != ' ' && c != '\t') {
281: if (c == EOF)
282: state = EOF;
283: else if (c == '\001')
284: state = EOM;
285: break;
286: }
287: }
288: *cp = '\0';
289: cp = buf;
290: break;
291:
292: body:
293: state = BODY;
294: case BODY:
295: for (cp = buf; ; c = getchar()) {
296: if (c == EOF) {
297: state = EOF;
298: break;
299: }
300: if (c == '\001' && peekc(stdin) == '\001') {
301: state = EOM;
302: break;
303: }
304: putc(c, tfp);
305: *cp++ = c;
306: if (cp >= buf+sizeof(buf)-1 || c == '\n')
307: break;
308: }
309: *cp = '\0';
310: if ((cp = index(buf, ':')) == NULL)
311: continue;
312: *cp++ = '\0';
313: hp = findheader(buf, state);
314: }
315:
316: /*
317: * Don't save the info if the header wasn't found, we don't
318: * care about the info, or the header is repeated.
319: */
320: if (hp == NULL || !(hp->h_flags & H_SAV) || hp->h_info)
321: continue;
322: while (isspace(*cp))
323: cp++;
324: if (*cp) {
325: info = cp;
326: while (*cp++);
327: cp--;
328: while (isspace(cp[-1]))
329: *--cp = '\0';
330: hp->h_info = (char *) malloc(strlen(info) + 1);
331: if (hp->h_info == NULL) {
332: fprintf(stderr, "ran out of memory\n");
333: continue;
334: }
335: strcpy(hp->h_info, info);
336: if (hp == FROM && chkfrom(hp) < 0)
337: no_reply = 1;
338: if (hp == INDEX)
339: chkindex(hp);
340: }
341: }
342: fclose(tfp);
343: if (no_reply) {
344: unlink(tmpname);
345: exit(0);
346: }
347: /*
348: * Verify all the required pieces of information
349: * are present.
350: */
351: for (hp = headers; hp->h_tag; hp++) {
352: /*
353: * Mail the bug report back to the sender with a note
354: * explaining they must conform to the specification.
355: */
356: if ((hp->h_flags & H_REQ) && !(hp->h_flags & H_FND)) {
357: if (debug)
358: printf("Missing %s\n", hp->h_tag);
359: badfmt:
360: reply(FROM_I, errfile, tmpname);
361: file(tmpname, "errors");
362: redistribute("errors", num);
363: return(state == EOM);
364: }
365: }
366: /*
367: * Acknowledge receipt.
368: */
369: reply(FROM_I, ackfile, (char *)0);
370: file(tmpname, folder);
371: /*
372: * Append information about the new bug report
373: * to the summary file.
374: */
375: if ((fs = fopen(sumfile, "a")) == NULL)
376: fprintf(stderr, "Can't open %s\n", sumfile);
377: else {
378: fprintf(fs, "%14.14s/%-3d ", folder, num);
379: fprintf(fs, "%-51.51s Recv\n", INDEX_I);
380: fprintf(fs, "\t\t %-51.51s\n", SUBJECT_I);
381: }
382: fclose(fs);
383: /*
384: * Check redistribution list and, if members,
385: * mail a copy of the bug report to these people.
386: */
387: redistribute(folder, num);
388: return(state == EOM);
389: }
390:
391: /*
392: * Lookup the string in the list of headers and return a pointer
393: * to the entry or NULL.
394: */
395:
396: header *
397: findheader(name, state)
398: char *name;
399: int state;
400: {
401: register struct header *hp;
402:
403: if (debug)
404: printf("findheader(%s, %d)\n", name, state);
405:
406: for (hp = headers; hp->h_tag; hp++) {
407: if (!streq(hp->h_tag, buf))
408: continue;
409: if ((hp->h_flags & H_HDR) && state != FLD)
410: continue;
411: hp->h_flags |= H_FND;
412: return(hp);
413: }
414: return(NULL);
415: }
416:
417: /*
418: * Check the FROM line to eliminate loops.
419: */
420:
421: chkfrom(hp)
422: struct header *hp;
423: {
424: register char *cp1, *cp2;
425: register char c;
426:
427: if (debug)
428: printf("chkfrom(%s)\n", hp->h_info);
429:
430: if (substr(hp->h_info, BUGS_NAME))
431: return(-1);
432: if (substr(hp->h_info, "MAILER-DAEMON"))
433: return(-1);
434: return(0);
435: }
436:
437: /*
438: * Check the format of the Index information.
439: * A side effect is to set the name of the folder if all is well.
440: */
441:
442: chkindex(hp)
443: struct header *hp;
444: {
445: register char *cp1, *cp2;
446: register char c;
447: struct stat stbuf;
448:
449: if (debug)
450: printf("chkindex(%s)\n", hp->h_info);
451: /*
452: * Strip of leading "/", ".", "usr/", or "src/".
453: */
454: cp1 = hp->h_info;
455: while (*cp1 == '/' || *cp1 == '.')
456: cp1++;
457: while (substr(cp1, "usr/") || substr(cp1, "src/"))
458: cp1 += 4;
459: /*
460: * Read the folder name and remove it from the index line.
461: */
462: for (cp2 = folder; ;) {
463: switch (c = *cp1++) {
464: case '/':
465: if (cp2 == folder)
466: continue;
467: break;
468: case '\0':
469: cp1--;
470: break;
471: case ' ':
472: case '\t':
473: while (isspace(*cp1))
474: cp1++;
475: break;
476: default:
477: if (cp2 < folder+sizeof(folder)-1)
478: *cp2++ = c;
479: continue;
480: }
481: *cp2 = '\0';
482: for (cp2 = hp->h_info; *cp2++ = *cp1++; )
483: ;
484: break;
485: }
486: if (debug)
487: printf("folder = %s\n", folder);
488: /*
489: * Check to make sure we have a valid folder name
490: */
491: if (stat(folder, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR)
492: return;
493: /*
494: * The Index line is not in the correct format so clear
495: * the H_FND flag to mail back the correct format.
496: */
497: hp->h_flags &= ~H_FND;
498: }
499:
500: /*
501: * Move or copy the file msg to the folder (directory).
502: * As a side effect, num is set to the number under which
503: * the message is filed in folder.
504: */
505:
506: file(fname, folder)
507: char *fname, *folder;
508: {
509: register char *cp;
510: register int n;
511: char msgname[MAXNAMLEN*2+2];
512: struct stat stbuf;
513: DIR *dirp;
514: struct direct *d;
515:
516: if (debug)
517: printf("file(%s, %s)\n", fname, folder);
518: /*
519: * Get the next number to use by finding the last message number
520: * in folder and adding one.
521: */
522: if ((dirp = opendir(folder)) == NULL) {
523: (void) mkdir(folder, folder_prot);
524: if ((dirp = opendir(folder)) == NULL) {
525: fprintf(stderr, "Cannot open %s/%s\n", maildir, folder);
526: return;
527: }
528: }
529: num = 0;
530: while ((d = readdir(dirp)) != NULL) {
531: cp = d->d_name;
532: n = 0;
533: while (isdigit(*cp))
534: n = n * 10 + (*cp++ - '0');
535: if (*cp == '\0' && n > num)
536: num = n;
537: }
538: closedir(dirp);
539: num++;
540: /*
541: * Create the destination file "folder/num" and copy fname to it.
542: */
543: sprintf(msgname, "%s/%d", folder, num);
544: if (link(fname, msgname) < 0) {
545: int fin, fout;
546:
547: if ((fin = open(fname, 0)) < 0) {
548: fprintf(stderr, "cannot open %s\n", fname);
549: return;
550: }
551: if ((fout = creat(msgname, msg_prot)) < 0) {
552: fprintf(stderr, "cannot create %s\n", msgname);
553: return;
554: }
555: while ((n = read(fin, buf, sizeof(buf))) > 0)
556: write(fout, buf, n);
557: close(fin);
558: close(fout);
559: }
560: unlink(fname);
561: }
562:
563: /*
564: * Redistribute a bug report to those people indicated
565: * in the redistribution list file. Perhaps should also
566: * annotate bug report with this information for future
567: * reference?
568: */
569: redistribute(folder, num)
570: char *folder;
571: int num;
572: {
573: FILE *fredist, *fbug, *ftemp;
574: char line[BUFSIZ], bug[2 * MAXNAMLEN + 1];
575: register char *cp;
576: int redistcnt, continuation, first;
577:
578: fredist = fopen(redistfile, "r");
579: if (fredist == NULL) {
580: if (debug)
581: printf("redistribute(%s, %d), no distribution list\n",
582: folder, num);
583: return;
584: }
585: continuation = 0;
586: first = 1;
587: redistcnt = 0;
588: while (fgets(line, sizeof (line) - 1, fredist) != NULL) {
589: if (debug)
590: printf("%s: %s", redistfile, line);
591: if (continuation && index(line, '\\'))
592: continue;
593: continuation = 0;
594: cp = any(line, " \t");
595: if (cp == NULL)
596: continue;
597: *cp++ = '\0';
598: if (strcmp(folder, line) == 0)
599: goto found;
600: if (index(cp, '\\'))
601: continuation = 1;
602: }
603: if (debug)
604: printf("no redistribution list found\n");
605: fclose(fredist);
606: return;
607: found:
608: mktemp(disttmp);
609: ftemp = fopen(disttmp, "w+r");
610: if (ftemp == NULL) {
611: if (debug)
612: printf("%s: couldn't create\n", disttmp);
613: return;
614: }
615: again:
616: if (debug)
617: printf("redistribution list %s", cp);
618: while (cp) {
619: char *user, terminator;
620:
621: while (*cp && (*cp == ' ' || *cp == '\t' || *cp == ','))
622: cp++;
623: user = cp, cp = any(cp, ", \t\n\\");
624: if (cp) {
625: terminator = *cp;
626: *cp++ = '\0';
627: if (terminator == '\n')
628: cp = 0;
629: if (terminator == '\\')
630: continuation++;
631: }
632: if (*user == '\0')
633: continue;
634: if (debug)
635: printf("copy to %s\n", user);
636: if (first) {
637: fprintf(ftemp, "Resent-To: %s", user);
638: first = 0;
639: } else
640: fprintf(ftemp, ", %s", user);
641: redistcnt++;
642: }
643: if (!first)
644: putc('\n', ftemp);
645: if (continuation) {
646: first = 1;
647: continuation = 0;
648: cp = line;
649: if (fgets(line, sizeof (line) - 1, fredist))
650: goto again;
651: }
652: fclose(fredist);
653: if (redistcnt == 0)
654: goto cleanup;
655: if (! SUBJECT_I) {
656: fprintf(ftemp, "Subject: ");
657: fprintf(ftemp, "Untitled bug report\n");
658: }
659: fprintf(ftemp, "Folder: %s/%d\n", folder, num);
660: /*
661: * Create copy of bug report. Perhaps we should
662: * truncate large messages and just give people
663: * a pointer to the original?
664: */
665: sprintf(bug, "%s/%d", folder, num);
666: fbug = fopen(bug, "r");
667: if (fbug == NULL) {
668: if (debug)
669: printf("%s: disappeared?\n", bug);
670: goto cleanup;
671: }
672: first = 1;
673: while (fgets(line, sizeof (line) - 1, fbug)) {
674: /* first blank line indicates start of mesg */
675: if (first && line[0] == '\n') {
676: first = 0;
677: }
678: fputs(line, ftemp);
679: }
680: fclose(fbug);
681: if (first) {
682: if (debug)
683: printf("empty bug report?\n");
684: goto cleanup;
685: }
686: if (dodeliver(ftemp))
687: unlink(disttmp);
688: fclose(ftemp);
689: return;
690: cleanup:
691: fclose(ftemp);
692: unlink(disttmp);
693: }
694:
695: dodeliver(fd)
696: FILE *fd;
697: {
698: char buf[BUFSIZ], cmd[BUFSIZ];
699: FILE *pf, *popen();
700:
701: strcpy(cmd, MAILCMD);
702: if (debug) {
703: strcat(cmd, " -v");
704: printf("dodeliver \"%s\"\n", cmd);
705: }
706: pf = popen(cmd, "w");
707: if (pf == NULL) {
708: if (debug)
709: printf("dodeliver, \"%s\" failed\n", cmd);
710: return (0);
711: }
712: rewind(fd);
713: while (fgets(buf, sizeof (buf) - 1, fd)) {
714: if (debug)
715: printf("%s", buf);
716: (void) fputs(buf, pf);
717: }
718: if (debug)
719: printf("EOF\n");
720: (void) pclose(pf);
721: return (1);
722: }
723:
724: /*
725: * Mail file1 and file2 back to the sender.
726: */
727:
728: reply(to, file1, file2)
729: char *to, *file1, *file2;
730: {
731: int pfd[2], in, w;
732: FILE *fout;
733:
734: if (debug)
735: printf("reply(%s, %s, %s)\n", to, file1, file2);
736:
737: /*
738: * Create a temporary file to put the message in.
739: */
740: mktemp(draft);
741: if ((fout = fopen(draft, "w+r")) == NULL) {
742: fprintf(stderr, "Can't create %s\n", draft);
743: return;
744: }
745: /*
746: * Output the proper header information.
747: */
748: fprintf(fout, "Reply-To: %s%s\n", BUGS_NAME, BUGS_HOME);
749: fprintf(fout, "From: %s%s (Bugs Bunny)\n", BUGS_NAME, BUGS_HOME);
750: if (REPLYTO_I != NULL)
751: to = REPLYTO_I;
752: if ((to = fixaddr(to)) == 0) {
753: fprintf(stderr, "No one to reply to\n");
754: return;
755: }
756: fprintf(fout, "To: %s\n", to);
757: if (SUBJECT_I) {
758: fprintf(fout, "Subject: ");
759: if ((SUBJECT_I[0] != 'R' && SUBJECT_I[0] != 'r') ||
760: (SUBJECT_I[1] != 'E' && SUBJECT_I[1] != 'e') ||
761: SUBJECT_I[2] != ':')
762: fprintf(fout, "Re: ");
763: fprintf(fout, "%s\n", SUBJECT_I);
764: }
765: if (DATE_I) {
766: fprintf(fout, "In-Acknowledgement-Of: Your message of ");
767: fprintf(fout, "%s.\n", DATE_I);
768: if (MSGID_I)
769: fprintf(fout, " %s\n", MSGID_I);
770: }
771: fprintf(fout, "\n");
772: if ((in = open(file1, 0)) >= 0) {
773: while ((w = read(in, buf, sizeof(buf))) > 0)
774: fwrite(buf, 1, w, fout);
775: close(in);
776: }
777: if (file2 && (in = open(file2, 0)) >= 0) {
778: while ((w = read(in, buf, sizeof(buf))) > 0)
779: fwrite(buf, 1, w, fout);
780: close(in);
781: }
782: if (dodeliver(fout))
783: unlink(draft);
784: fclose(fout);
785: }
786:
787: /*
788: * fix names like "xxx (something)" to "xxx" and
789: * "xxx <something>" to "something".
790: */
791:
792: char *
793: fixaddr(text)
794: char *text;
795: {
796: register char *cp, *lp, c;
797: char *tp;
798:
799: if (!text)
800: return(0);
801: for (lp = cp = text; ; ) {
802: switch (c = *cp++) {
803: case '(':
804: while (*cp && *cp++ != ')');
805: continue;
806: case '<':
807: lp = text;
808: case '>':
809: continue;
810: case '\0':
811: while (lp != text && (*lp == ' ' || *lp == '\t'))
812: lp--;
813: *lp = c;
814: return(text);
815: }
816: *lp++ = c;
817: }
818: }
819:
820: /*
821: * Compare two strings and convert any upper case letters to lower case.
822: */
823:
824: streq(s1, s2)
825: register char *s1, *s2;
826: {
827: register int c;
828:
829: while (c = *s1++)
830: if ((c | 040) != (*s2++ | 040))
831: return(0);
832: return(*s2 == '\0');
833: }
834:
835: /*
836: * Return true if string s2 matches the first part of s1.
837: */
838:
839: substr(s1, s2)
840: register char *s1, *s2;
841: {
842: register int c;
843:
844: while (c = *s2++)
845: if (c != *s1++)
846: return(0);
847: return(1);
848: }
849:
850: char *
851: any(cp, set)
852: register char *cp;
853: char *set;
854: {
855: register char *sp;
856:
857: if (cp == 0 || set == 0)
858: return (0);
859: while (*cp) {
860: for (sp = set; *sp; sp++)
861: if (*cp == *sp)
862: return (cp);
863: cp++;
864: }
865: return ((char *)0);
866: }
867:
868: peekc(fp)
869: FILE *fp;
870: {
871: register c;
872:
873: c = getc(fp);
874: ungetc(c, fp);
875: return(c);
876: }