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