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