1 /* $OpenBSD: lex.c,v 1.34 2011/04/06 11:36:26 miod Exp $ */ 2 /* $NetBSD: lex.c,v 1.10 1997/05/17 19:55:13 pk Exp $ */ 3 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. 16 * 3. Neither the name of the University nor the names of its contributors 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 46 const struct cmd *com; /* command we are running */ 47 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 55 setfile(char *name) 56 { 57 FILE *ibuf; 58 int i, fd; 59 struct stat stb; 60 char isedit = *name != '%'; 61 char *who = name[1] ? name + 1 : myname; 62 char tempname[PATHSIZE]; 63 static int shudclob; 64 65 if ((name = expand(name)) == NULL) 66 return(-1); 67 68 if ((ibuf = Fopen(name, "r")) == NULL) { 69 if (!isedit && errno == ENOENT) 70 goto nomail; 71 warn("%s", name); 72 return(-1); 73 } 74 75 if (fstat(fileno(ibuf), &stb) < 0) { 76 warn("fstat"); 77 (void)Fclose(ibuf); 78 return(-1); 79 } 80 81 switch (stb.st_mode & S_IFMT) { 82 case S_IFDIR: 83 (void)Fclose(ibuf); 84 errno = EISDIR; 85 warn("%s", name); 86 return(-1); 87 88 case S_IFREG: 89 break; 90 91 default: 92 (void)Fclose(ibuf); 93 errno = EINVAL; 94 warn("%s", name); 95 return(-1); 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; 113 if ((i = open(name, O_WRONLY, 0)) < 0) 114 readonly++; 115 else 116 (void)close(i); 117 if (shudclob) { 118 (void)fclose(itf); 119 (void)fclose(otf); 120 } 121 shudclob = 1; 122 edit = isedit; 123 strlcpy(prevfile, mailname, PATHSIZE); 124 if (name != mailname) 125 strlcpy(mailname, name, sizeof(mailname)); 126 mailsize = fsize(ibuf); 127 (void)snprintf(tempname, sizeof(tempname), 128 "%s/mail.RxXXXXXXXXXX", tmpdir); 129 if ((fd = mkstemp(tempname)) == -1 || 130 (otf = fdopen(fd, "w")) == NULL) 131 err(1, "%s", tempname); 132 (void)fcntl(fileno(otf), F_SETFD, 1); 133 if ((itf = fopen(tempname, "r")) == NULL) 134 err(1, "%s", tempname); 135 (void)fcntl(fileno(itf), F_SETFD, 1); 136 (void)rm(tempname); 137 setptr(ibuf, (off_t)0); 138 setmsize(msgCount); 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); 146 relsesigs(); 147 sawcom = 0; 148 if (!edit && msgCount == 0) { 149 nomail: 150 fprintf(stderr, "No mail for %s\n", who); 151 return(-1); 152 } 153 return(0); 154 } 155 156 /* 157 * Incorporate any new mail that has arrived since we first 158 * started reading mail. 159 */ 160 int 161 incfile(void) 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(); 171 if (!spool_lock()) { 172 (void)Fclose(ibuf); 173 relsesigs(); 174 return(-1); 175 } 176 newsize = fsize(ibuf); 177 /* make sure mail box has grown and is non-empty */ 178 if (newsize == 0 || newsize <= mailsize) { 179 (void)Fclose(ibuf); 180 spool_unlock(); 181 relsesigs(); 182 return(newsize == mailsize ? 0 : -1); 183 } 184 setptr(ibuf, mailsize); 185 setmsize(msgCount); 186 mailsize = ftell(ibuf); 187 (void)Fclose(ibuf); 188 spool_unlock(); 189 relsesigs(); 190 return(msgCount - omsgCount); 191 } 192 193 194 int *msgvec; 195 int reset_on_stop; /* reset prompt if stopped */ 196 197 /* 198 * Interpret user commands one by one. If standard input is not a tty, 199 * print no prompt. 200 */ 201 void 202 commands(void) 203 { 204 int n, sig, *sigp; 205 int eofloop = 0; 206 char linebuf[LINESIZE]; 207 208 prompt: 209 for (;;) { 210 /* 211 * Print the prompt, if needed. Clear out 212 * string space, and flush the output. 213 */ 214 if (!sourcing && value("interactive") != NULL) { 215 if ((value("autoinc") != NULL) && (incfile() > 0)) 216 puts("New mail has arrived."); 217 reset_on_stop = 1; 218 printf("%s", prompt); 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; 227 sig = 0; 228 sigp = sourcing ? NULL : &sig; 229 for (;;) { 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 } 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 } 265 if (value("interactive") != NULL && 266 value("ignoreeof") != NULL && 267 ++eofloop < 25) { 268 puts("Use \"quit\" to quit."); 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 287 execute(char *linebuf, int contxt) 288 { 289 char word[LINESIZE]; 290 char *arglist[MAXARGC]; 291 char *cp, *cp2; 292 int c, muvec[2]; 293 int e = 1; 294 295 com = NULL; 296 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) { 309 puts("Can't \"!\" while sourcing"); 310 goto out; 311 } 312 shell(cp+1); 313 return(0); 314 } 315 cp2 = word; 316 while (*cp && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL) 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); 330 if (com == NULL) { 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) 340 if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode)) 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)) { 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); 400 msgvec[1] = 0; 401 } 402 if (*msgvec == 0) { 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 416 case MSGLIST: 417 /* 418 * A message list defaulting to nearest forward 419 * legal message. 420 */ 421 if (msgvec == NULL) { 422 puts("Illegal use of \"message list\""); 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); 430 msgvec[1] = 0; 431 } 432 if (*msgvec == 0) { 433 puts("No applicable messages"); 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) { 445 puts("Illegal use of \"message list\""); 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, 468 sizeof(arglist) / sizeof(*arglist))) < 0) 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: 492 errx(1, "Unknown argtype"); 493 } 494 495 out: 496 /* 497 * Exit the current source file on 498 * error. 499 */ 500 if (e) { 501 if (e < 0) 502 return(1); 503 if (loading) 504 return(1); 505 if (sourcing) 506 unstack(); 507 return(0); 508 } 509 if (com == NULL) 510 return(0); 511 if (value("autoprint") != NULL && com->c_argtype & P) 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 527 setmsize(int n) 528 { 529 int *msgvec2; 530 size_t msize; 531 532 msize = (n + 1) * sizeof(*msgvec); 533 if ((msgvec2 = realloc(msgvec, msize)) == NULL) 534 errx(1, "Out of memory"); 535 msgvec = msgvec2; 536 memset(msgvec, 0, msize); 537 } 538 539 /* 540 * Find the correct command in the command table corresponding 541 * to the passed command "word" 542 */ 543 544 const struct cmd * 545 lex(char *word) 546 { 547 extern const struct cmd cmdtab[]; 548 const struct cmd *cp; 549 550 if (word[0] == '#') 551 word = "#"; 552 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++) 553 if (isprefix(word, cp->c_name)) 554 return(cp); 555 return(NULL); 556 } 557 558 /* 559 * Determine if as1 is a valid prefix of as2. 560 * Return true if yep. 561 */ 562 int 563 isprefix(char *as1, char *as2) 564 { 565 char *s1, *s2; 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 585 dointr(void) 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) { 598 (void)close(image); 599 image = -1; 600 } 601 fputs("Interrupt\n", stderr); 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 609 announce(void) 610 { 611 int vec[2], mdot; 612 613 mdot = newfileinfo(0); 614 vec[0] = mdot; 615 vec[1] = 0; 616 dot = &message[mdot - 1]; 617 if (msgCount > 0 && value("noheader") == NULL) { 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 629 newfileinfo(int omsgCount) 630 { 631 struct message *mp; 632 int u, n, mdot, d, s; 633 char fname[PATHSIZE], zname[PATHSIZE], *ename; 634 635 for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++) 636 if (mp->m_flag & MNEW) 637 break; 638 if (mp >= &message[msgCount]) 639 for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++) 640 if ((mp->m_flag & MREAD) == 0) 641 break; 642 if (mp < &message[msgCount]) 643 mdot = mp - &message[0] + 1; 644 else 645 mdot = omsgCount + 1; 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; 658 if (getfold(fname, sizeof(fname)) >= 0) { 659 strlcat(fname, "/", sizeof(fname)); 660 if (strncmp(fname, mailname, strlen(fname)) == 0) { 661 (void)snprintf(zname, sizeof(zname), "+%s", 662 mailname + strlen(fname)); 663 ename = zname; 664 } 665 } 666 printf("\"%s\": ", ename); 667 if (msgCount == 1) 668 fputs("1 message", stdout); 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) 680 fputs(" [Read only]", stdout); 681 putchar('\n'); 682 return(mdot); 683 } 684 685 /* 686 * Print the current version number. 687 */ 688 /*ARGSUSED*/ 689 int 690 pversion(void *v) 691 { 692 extern const char version[]; 693 694 printf("Version %s\n", version); 695 return(0); 696 } 697 698 /* 699 * Load a file of user definitions. 700 */ 701 void 702 load(char *name) 703 { 704 FILE *in, *oldin; 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; 716 (void)Fclose(in); 717 } 718