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