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