Annotation of src/usr.bin/mail/lex.c, Revision 1.39
1.39 ! mmcc 1: /* $OpenBSD: lex.c,v 1.38 2014/10/26 20:38:13 guenther Exp $ */
1.6 millert 2: /* $NetBSD: lex.c,v 1.10 1997/05/17 19:55:13 pk Exp $ */
1.2 niklas 3:
1.1 deraadt 4: /*
5: * Copyright (c) 1980, 1993
6: * The Regents of the University of California. All rights reserved.
7: *
8: * Redistribution and use in source and binary forms, with or without
9: * modification, are permitted provided that the following conditions
10: * are met:
11: * 1. Redistributions of source code must retain the above copyright
12: * notice, this list of conditions and the following disclaimer.
13: * 2. Redistributions in binary form must reproduce the above copyright
14: * notice, this list of conditions and the following disclaimer in the
15: * documentation and/or other materials provided with the distribution.
1.28 millert 16: * 3. Neither the name of the University nor the names of its contributors
1.1 deraadt 17: * may be used to endorse or promote products derived from this software
18: * without specific prior written permission.
19: *
20: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30: * SUCH DAMAGE.
31: */
32:
33: #include "rcv.h"
34: #include <errno.h>
35: #include <fcntl.h>
36: #include "extern.h"
37:
38: /*
39: * Mail -- a mail program
40: *
41: * Lexical processing of commands.
42: */
43:
44: char *prompt = "& ";
45:
1.22 millert 46: const struct cmd *com; /* command we are running */
47:
1.1 deraadt 48: /*
49: * Set up editing on the given file name.
50: * If the first character of name is %, we are considered to be
51: * editing the file, otherwise we are reading our mail which has
52: * signficance for mbox and so forth.
53: */
54: int
1.26 millert 55: setfile(char *name)
1.1 deraadt 56: {
57: FILE *ibuf;
1.11 millert 58: int i, fd;
1.1 deraadt 59: struct stat stb;
60: char isedit = *name != '%';
61: char *who = name[1] ? name + 1 : myname;
1.12 millert 62: char tempname[PATHSIZE];
1.1 deraadt 63: static int shudclob;
64:
1.8 millert 65: if ((name = expand(name)) == NULL)
1.6 millert 66: return(-1);
1.1 deraadt 67:
68: if ((ibuf = Fopen(name, "r")) == NULL) {
69: if (!isedit && errno == ENOENT)
70: goto nomail;
1.20 millert 71: warn("%s", name);
1.1 deraadt 72: return(-1);
73: }
74:
75: if (fstat(fileno(ibuf), &stb) < 0) {
1.6 millert 76: warn("fstat");
77: (void)Fclose(ibuf);
78: return(-1);
1.1 deraadt 79: }
80:
81: switch (stb.st_mode & S_IFMT) {
82: case S_IFDIR:
1.6 millert 83: (void)Fclose(ibuf);
1.37 guenther 84: warnc(EISDIR, "%s", name);
1.6 millert 85: return(-1);
1.1 deraadt 86:
87: case S_IFREG:
88: break;
89:
90: default:
1.6 millert 91: (void)Fclose(ibuf);
1.37 guenther 92: warnc(EINVAL, "%s", name);
1.6 millert 93: return(-1);
1.1 deraadt 94: }
95:
96: /*
97: * Looks like all will be well. We must now relinquish our
98: * hold on the current set of stuff. Must hold signals
99: * while we are reading the new file, else we will ruin
100: * the message[] data structure.
101: */
102: holdsigs();
103: if (shudclob)
104: quit();
105:
106: /*
107: * Copy the messages into /tmp
108: * and set pointers.
109: */
110: readonly = 0;
1.26 millert 111: if ((i = open(name, O_WRONLY, 0)) < 0)
1.1 deraadt 112: readonly++;
113: else
1.6 millert 114: (void)close(i);
1.1 deraadt 115: if (shudclob) {
1.6 millert 116: (void)fclose(itf);
117: (void)fclose(otf);
1.1 deraadt 118: }
119: shudclob = 1;
120: edit = isedit;
1.27 millert 121: strlcpy(prevfile, mailname, PATHSIZE);
1.26 millert 122: if (name != mailname)
123: strlcpy(mailname, name, sizeof(mailname));
1.1 deraadt 124: mailsize = fsize(ibuf);
1.11 millert 125: (void)snprintf(tempname, sizeof(tempname),
126: "%s/mail.RxXXXXXXXXXX", tmpdir);
1.38 guenther 127: if ((fd = mkostemp(tempname, O_CLOEXEC)) == -1 ||
1.11 millert 128: (otf = fdopen(fd, "w")) == NULL)
1.20 millert 129: err(1, "%s", tempname);
1.38 guenther 130: if ((itf = fopen(tempname, "re")) == NULL)
1.20 millert 131: err(1, "%s", tempname);
1.12 millert 132: (void)rm(tempname);
1.32 deraadt 133: setptr(ibuf, (off_t)0);
1.1 deraadt 134: setmsize(msgCount);
1.6 millert 135: /*
136: * New mail may have arrived while we were reading
137: * the mail file, so reset mailsize to be where
138: * we really are in the file...
139: */
140: mailsize = ftell(ibuf);
141: (void)Fclose(ibuf);
1.1 deraadt 142: relsesigs();
143: sawcom = 0;
144: if (!edit && msgCount == 0) {
145: nomail:
146: fprintf(stderr, "No mail for %s\n", who);
1.6 millert 147: return(-1);
1.1 deraadt 148: }
149: return(0);
150: }
151:
1.6 millert 152: /*
153: * Incorporate any new mail that has arrived since we first
154: * started reading mail.
155: */
156: int
1.26 millert 157: incfile(void)
1.6 millert 158: {
159: int newsize;
160: int omsgCount = msgCount;
161: FILE *ibuf;
162:
163: ibuf = Fopen(mailname, "r");
164: if (ibuf == NULL)
165: return(-1);
166: holdsigs();
1.24 deraadt 167: if (!spool_lock()) {
168: (void)Fclose(ibuf);
169: relsesigs();
1.10 millert 170: return(-1);
1.24 deraadt 171: }
1.6 millert 172: newsize = fsize(ibuf);
1.9 millert 173: /* make sure mail box has grown and is non-empty */
174: if (newsize == 0 || newsize <= mailsize) {
1.24 deraadt 175: (void)Fclose(ibuf);
1.10 millert 176: spool_unlock();
1.9 millert 177: relsesigs();
178: return(newsize == mailsize ? 0 : -1);
179: }
1.6 millert 180: setptr(ibuf, mailsize);
181: setmsize(msgCount);
182: mailsize = ftell(ibuf);
183: (void)Fclose(ibuf);
1.10 millert 184: spool_unlock();
1.6 millert 185: relsesigs();
186: return(msgCount - omsgCount);
187: }
188:
189:
1.1 deraadt 190: int *msgvec;
1.25 millert 191: int reset_on_stop; /* reset prompt if stopped */
1.1 deraadt 192:
193: /*
194: * Interpret user commands one by one. If standard input is not a tty,
195: * print no prompt.
196: */
197: void
1.26 millert 198: commands(void)
1.1 deraadt 199: {
1.25 millert 200: int n, sig, *sigp;
201: int eofloop = 0;
1.1 deraadt 202: char linebuf[LINESIZE];
203:
1.25 millert 204: prompt:
1.1 deraadt 205: for (;;) {
206: /*
207: * Print the prompt, if needed. Clear out
208: * string space, and flush the output.
209: */
1.8 millert 210: if (!sourcing && value("interactive") != NULL) {
211: if ((value("autoinc") != NULL) && (incfile() > 0))
1.6 millert 212: puts("New mail has arrived.");
1.1 deraadt 213: reset_on_stop = 1;
1.21 deraadt 214: printf("%s", prompt);
1.1 deraadt 215: }
216: fflush(stdout);
217: sreset();
218: /*
219: * Read a line of commands from the current input
220: * and handle end of file specially.
221: */
222: n = 0;
1.25 millert 223: sig = 0;
224: sigp = sourcing ? NULL : &sig;
1.1 deraadt 225: for (;;) {
1.25 millert 226: if (readline(input, &linebuf[n], LINESIZE - n, sigp) < 0) {
227: if (sig) {
228: if (sig == SIGINT)
229: dointr();
230: else if (sig == SIGHUP)
231: /* nothing to do? */
232: exit(1);
233: else {
234: /* Stopped by job control */
235: (void)kill(0, sig);
236: if (reset_on_stop)
237: reset_on_stop = 0;
238: }
239: goto prompt;
240: }
1.1 deraadt 241: if (n == 0)
242: n = -1;
243: break;
244: }
245: if ((n = strlen(linebuf)) == 0)
246: break;
247: n--;
248: if (linebuf[n] != '\\')
249: break;
250: linebuf[n++] = ' ';
251: }
252: reset_on_stop = 0;
253: if (n < 0) {
254: /* eof */
255: if (loading)
256: break;
257: if (sourcing) {
258: unstack();
259: continue;
260: }
1.8 millert 261: if (value("interactive") != NULL &&
262: value("ignoreeof") != NULL &&
1.1 deraadt 263: ++eofloop < 25) {
1.6 millert 264: puts("Use \"quit\" to quit.");
1.1 deraadt 265: continue;
266: }
267: break;
268: }
269: eofloop = 0;
270: if (execute(linebuf, 0))
271: break;
272: }
273: }
274:
275: /*
276: * Execute a single command.
277: * Command functions return 0 for success, 1 for error, and -1
278: * for abort. A 1 or -1 aborts a load or source. A -1 aborts
279: * the interactive command loop.
280: * Contxt is non-zero if called while composing mail.
281: */
282: int
1.26 millert 283: execute(char *linebuf, int contxt)
1.1 deraadt 284: {
285: char word[LINESIZE];
286: char *arglist[MAXARGC];
1.15 millert 287: char *cp, *cp2;
288: int c, muvec[2];
1.1 deraadt 289: int e = 1;
290:
1.22 millert 291: com = NULL;
292:
1.1 deraadt 293: /*
294: * Strip the white space away from the beginning
295: * of the command, then scan out a word, which
296: * consists of anything except digits and white space.
297: *
298: * Handle ! escapes differently to get the correct
299: * lexical conventions.
300: */
1.36 okan 301: for (cp = linebuf; isspace((unsigned char)*cp); cp++)
1.1 deraadt 302: ;
303: if (*cp == '!') {
304: if (sourcing) {
1.6 millert 305: puts("Can't \"!\" while sourcing");
1.1 deraadt 306: goto out;
307: }
308: shell(cp+1);
309: return(0);
310: }
311: cp2 = word;
1.36 okan 312: while (*cp &&
313: strchr(" \t0123456789$^.:/-+*'\"", (unsigned char)*cp) == NULL)
1.1 deraadt 314: *cp2++ = *cp++;
315: *cp2 = '\0';
316:
317: /*
318: * Look up the command; if not found, bitch.
319: * Normally, a blank command would map to the
320: * first command in the table; while sourcing,
321: * however, we ignore blank lines to eliminate
322: * confusion.
323: */
324: if (sourcing && *word == '\0')
325: return(0);
326: com = lex(word);
1.26 millert 327: if (com == NULL) {
1.1 deraadt 328: printf("Unknown command: \"%s\"\n", word);
329: goto out;
330: }
331:
332: /*
333: * See if we should execute the command -- if a conditional
334: * we always execute it, otherwise, check the state of cond.
335: */
336: if ((com->c_argtype & F) == 0)
1.3 deraadt 337: if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode))
1.1 deraadt 338: return(0);
339:
340: /*
341: * Process the arguments to the command, depending
342: * on the type he expects. Default to an error.
343: * If we are sourcing an interactive command, it's
344: * an error.
345: */
346: if (!rcvmode && (com->c_argtype & M) == 0) {
347: printf("May not execute \"%s\" while sending\n",
348: com->c_name);
349: goto out;
350: }
351: if (sourcing && com->c_argtype & I) {
352: printf("May not execute \"%s\" while sourcing\n",
353: com->c_name);
354: goto out;
355: }
356: if (readonly && com->c_argtype & W) {
357: printf("May not execute \"%s\" -- message file is read only\n",
358: com->c_name);
359: goto out;
360: }
361: if (contxt && com->c_argtype & R) {
362: printf("Cannot recursively invoke \"%s\"\n", com->c_name);
363: goto out;
364: }
365: switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
1.22 millert 366: case MSGLIST|STRLIST:
367: /*
368: * A message list defaulting to nearest forward
369: * legal message.
370: */
371: if (msgvec == 0) {
372: puts("Illegal use of \"message list\"");
373: break;
374: }
375: /*
376: * remove leading blanks.
377: */
1.36 okan 378: while (isspace((unsigned char)*cp))
1.22 millert 379: cp++;
380:
1.36 okan 381: if (isdigit((unsigned char)*cp) || *cp == ':') {
1.22 millert 382: if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
383: break;
384: /* position to next space - past the message list */
1.36 okan 385: while (!isspace((unsigned char)*cp))
1.22 millert 386: cp++;
387: /* position to next non-space */
1.36 okan 388: while (isspace((unsigned char)*cp))
1.22 millert 389: cp++;
390: } else {
391: c = 0; /* no message list */
392: }
393:
394: if (c == 0) {
395: *msgvec = first(com->c_msgflag,
396: com->c_msgmask);
1.34 miod 397: msgvec[1] = 0;
1.22 millert 398: }
1.30 avsm 399: if (*msgvec == 0) {
1.22 millert 400: puts("No applicable messages");
401: break;
402: }
403: /*
404: * Just the straight string, with
405: * leading blanks removed.
406: */
1.36 okan 407: while (isspace((unsigned char)*cp))
1.22 millert 408: cp++;
409:
410: e = (*com->c_func2)(msgvec, cp);
411: break;
412:
1.1 deraadt 413: case MSGLIST:
414: /*
415: * A message list defaulting to nearest forward
416: * legal message.
417: */
1.31 millert 418: if (msgvec == NULL) {
1.6 millert 419: puts("Illegal use of \"message list\"");
1.1 deraadt 420: break;
421: }
422: if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
423: break;
424: if (c == 0) {
425: *msgvec = first(com->c_msgflag,
426: com->c_msgmask);
1.34 miod 427: msgvec[1] = 0;
1.1 deraadt 428: }
1.30 avsm 429: if (*msgvec == 0) {
1.6 millert 430: puts("No applicable messages");
1.1 deraadt 431: break;
432: }
433: e = (*com->c_func)(msgvec);
434: break;
435:
436: case NDMLIST:
437: /*
438: * A message list with no defaults, but no error
439: * if none exist.
440: */
441: if (msgvec == 0) {
1.6 millert 442: puts("Illegal use of \"message list\"");
1.1 deraadt 443: break;
444: }
445: if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
446: break;
447: e = (*com->c_func)(msgvec);
448: break;
449:
450: case STRLIST:
451: /*
452: * Just the straight string, with
453: * leading blanks removed.
454: */
1.36 okan 455: while (isspace((unsigned char)*cp))
1.1 deraadt 456: cp++;
457: e = (*com->c_func)(cp);
458: break;
459:
460: case RAWLIST:
461: /*
462: * A vector of strings, in shell style.
463: */
464: if ((c = getrawlist(cp, arglist,
1.6 millert 465: sizeof(arglist) / sizeof(*arglist))) < 0)
1.1 deraadt 466: break;
467: if (c < com->c_minargs) {
468: printf("%s requires at least %d arg(s)\n",
469: com->c_name, com->c_minargs);
470: break;
471: }
472: if (c > com->c_maxargs) {
473: printf("%s takes no more than %d arg(s)\n",
474: com->c_name, com->c_maxargs);
475: break;
476: }
477: e = (*com->c_func)(arglist);
478: break;
479:
480: case NOLIST:
481: /*
482: * Just the constant zero, for exiting,
483: * eg.
484: */
485: e = (*com->c_func)(0);
486: break;
487:
488: default:
1.15 millert 489: errx(1, "Unknown argtype");
1.1 deraadt 490: }
491:
492: out:
493: /*
494: * Exit the current source file on
495: * error.
496: */
497: if (e) {
498: if (e < 0)
1.6 millert 499: return(1);
1.1 deraadt 500: if (loading)
1.6 millert 501: return(1);
1.1 deraadt 502: if (sourcing)
503: unstack();
1.6 millert 504: return(0);
1.1 deraadt 505: }
1.3 deraadt 506: if (com == NULL)
507: return(0);
1.8 millert 508: if (value("autoprint") != NULL && com->c_argtype & P)
1.1 deraadt 509: if ((dot->m_flag & MDELETED) == 0) {
510: muvec[0] = dot - &message[0] + 1;
511: muvec[1] = 0;
512: type(muvec);
513: }
514: if (!sourcing && (com->c_argtype & T) == 0)
515: sawcom = 1;
516: return(0);
517: }
518:
519: /*
520: * Set the size of the message vector used to construct argument
521: * lists to message list functions.
522: */
523: void
1.27 millert 524: setmsize(int n)
1.1 deraadt 525: {
1.29 tedu 526: int *msgvec2;
1.27 millert 527: size_t msize;
1.1 deraadt 528:
1.27 millert 529: msize = (n + 1) * sizeof(*msgvec);
1.29 tedu 530: if ((msgvec2 = realloc(msgvec, msize)) == NULL)
1.39 ! mmcc 531: err(1, "realloc");
1.29 tedu 532: msgvec = msgvec2;
1.27 millert 533: memset(msgvec, 0, msize);
1.1 deraadt 534: }
535:
536: /*
537: * Find the correct command in the command table corresponding
538: * to the passed command "word"
539: */
540:
1.2 niklas 541: const struct cmd *
1.26 millert 542: lex(char *word)
1.1 deraadt 543: {
1.2 niklas 544: extern const struct cmd cmdtab[];
1.15 millert 545: const struct cmd *cp;
1.1 deraadt 546:
1.18 millert 547: if (word[0] == '#')
548: word = "#";
1.8 millert 549: for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
1.1 deraadt 550: if (isprefix(word, cp->c_name))
551: return(cp);
1.26 millert 552: return(NULL);
1.1 deraadt 553: }
554:
555: /*
556: * Determine if as1 is a valid prefix of as2.
557: * Return true if yep.
558: */
559: int
1.26 millert 560: isprefix(char *as1, char *as2)
1.1 deraadt 561: {
1.15 millert 562: char *s1, *s2;
1.1 deraadt 563:
564: s1 = as1;
565: s2 = as2;
566: while (*s1++ == *s2)
567: if (*s2++ == '\0')
568: return(1);
569: return(*--s1 == '\0');
570: }
571:
572: /*
573: * The following gets called on receipt of an interrupt. This is
574: * to abort printout of a command, mainly.
575: * Dispatching here when command() is inactive crashes rcv.
576: * Close all open files except 0, 1, 2, and the temporary.
577: * Also, unstack all source files.
578: */
579: int inithdr; /* am printing startup headers */
580:
581: void
1.26 millert 582: dointr(void)
1.1 deraadt 583: {
584:
585: noreset = 0;
586: if (!inithdr)
587: sawcom++;
588: inithdr = 0;
589: while (sourcing)
590: unstack();
591:
592: close_all_files();
593:
594: if (image >= 0) {
1.6 millert 595: (void)close(image);
1.1 deraadt 596: image = -1;
597: }
1.6 millert 598: fputs("Interrupt\n", stderr);
1.1 deraadt 599: }
600:
601: /*
602: * Announce the presence of the current Mail version,
603: * give the message count, and print a header listing.
604: */
605: void
1.26 millert 606: announce(void)
1.1 deraadt 607: {
608: int vec[2], mdot;
609:
1.6 millert 610: mdot = newfileinfo(0);
1.1 deraadt 611: vec[0] = mdot;
612: vec[1] = 0;
613: dot = &message[mdot - 1];
1.8 millert 614: if (msgCount > 0 && value("noheader") == NULL) {
1.1 deraadt 615: inithdr++;
616: headers(vec);
617: inithdr = 0;
618: }
619: }
620:
621: /*
622: * Announce information about the file we are editing.
623: * Return a likely place to set dot.
624: */
625: int
1.26 millert 626: newfileinfo(int omsgCount)
1.1 deraadt 627: {
1.15 millert 628: struct message *mp;
629: int u, n, mdot, d, s;
1.6 millert 630: char fname[PATHSIZE], zname[PATHSIZE], *ename;
1.1 deraadt 631:
1.6 millert 632: for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
1.1 deraadt 633: if (mp->m_flag & MNEW)
634: break;
635: if (mp >= &message[msgCount])
1.6 millert 636: for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
1.1 deraadt 637: if ((mp->m_flag & MREAD) == 0)
638: break;
639: if (mp < &message[msgCount])
640: mdot = mp - &message[0] + 1;
641: else
1.6 millert 642: mdot = omsgCount + 1;
1.1 deraadt 643: s = d = 0;
644: for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
645: if (mp->m_flag & MNEW)
646: n++;
647: if ((mp->m_flag & MREAD) == 0)
648: u++;
649: if (mp->m_flag & MDELETED)
650: d++;
651: if (mp->m_flag & MSAVED)
652: s++;
653: }
654: ename = mailname;
1.6 millert 655: if (getfold(fname, sizeof(fname)) >= 0) {
1.26 millert 656: strlcat(fname, "/", sizeof(fname));
1.1 deraadt 657: if (strncmp(fname, mailname, strlen(fname)) == 0) {
1.12 millert 658: (void)snprintf(zname, sizeof(zname), "+%s",
1.6 millert 659: mailname + strlen(fname));
1.1 deraadt 660: ename = zname;
661: }
662: }
663: printf("\"%s\": ", ename);
664: if (msgCount == 1)
1.6 millert 665: fputs("1 message", stdout);
1.1 deraadt 666: else
667: printf("%d messages", msgCount);
668: if (n > 0)
669: printf(" %d new", n);
670: if (u-n > 0)
671: printf(" %d unread", u);
672: if (d > 0)
673: printf(" %d deleted", d);
674: if (s > 0)
675: printf(" %d saved", s);
676: if (readonly)
1.6 millert 677: fputs(" [Read only]", stdout);
678: putchar('\n');
1.1 deraadt 679: return(mdot);
680: }
681:
682: /*
683: * Print the current version number.
684: */
685: /*ARGSUSED*/
686: int
1.26 millert 687: pversion(void *v)
1.1 deraadt 688: {
1.26 millert 689: extern const char version[];
1.1 deraadt 690:
691: printf("Version %s\n", version);
692: return(0);
693: }
694:
695: /*
696: * Load a file of user definitions.
697: */
698: void
1.26 millert 699: load(char *name)
1.1 deraadt 700: {
1.15 millert 701: FILE *in, *oldin;
1.1 deraadt 702:
703: if ((in = Fopen(name, "r")) == NULL)
704: return;
705: oldin = input;
706: input = in;
707: loading = 1;
708: sourcing = 1;
709: commands();
710: loading = 0;
711: sourcing = 0;
712: input = oldin;
1.6 millert 713: (void)Fclose(in);
1.1 deraadt 714: }