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