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