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