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 #if 0 36 static char sccsid[] = "@(#)lex.c 8.2 (Berkeley) 4/20/95"; 37 #endif 38 #endif /* not lint */ 39 #include <sys/cdefs.h> 40 __FBSDID("$FreeBSD$"); 41 42 #include "rcv.h" 43 #include <errno.h> 44 #include <fcntl.h> 45 #include "extern.h" 46 47 /* 48 * Mail -- a mail program 49 * 50 * Lexical processing of commands. 51 */ 52 53 static const char *prompt = "& "; 54 55 extern const struct cmd cmdtab[]; 56 extern const char *version; 57 58 /* 59 * Set up editing on the given file name. 60 * If the first character of name is %, we are considered to be 61 * editing the file, otherwise we are reading our mail which has 62 * signficance for mbox and so forth. 63 * 64 * If the -e option is being passed to mail, this function has a 65 * tri-state return code: -1 on error, 0 on no mail, 1 if there is 66 * mail. 67 */ 68 int 69 setfile(name) 70 char *name; 71 { 72 FILE *ibuf; 73 int checkmode, i, fd; 74 struct stat stb; 75 char isedit = *name != '%' || getuserid(myname) != getuid(); 76 char *who = name[1] ? name + 1 : myname; 77 char tempname[PATHSIZE]; 78 static int shudclob; 79 80 checkmode = value("checkmode") != NULL; 81 if ((name = expand(name)) == NULL) 82 return (-1); 83 84 if ((ibuf = Fopen(name, "r")) == NULL) { 85 if (!isedit && errno == ENOENT) 86 goto nomail; 87 warn("%s", name); 88 return (-1); 89 } 90 91 if (fstat(fileno(ibuf), &stb) < 0) { 92 warn("fstat"); 93 (void)Fclose(ibuf); 94 return (-1); 95 } 96 97 if (S_ISDIR(stb.st_mode) || !S_ISREG(stb.st_mode)) { 98 (void)Fclose(ibuf); 99 errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL; 100 warn("%s", 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 (void)close(i); 125 if (shudclob) { 126 (void)fclose(itf); 127 (void)fclose(otf); 128 } 129 shudclob = 1; 130 edit = isedit; 131 strlcpy(prevfile, mailname, sizeof(prevfile)); 132 if (name != mailname) 133 strlcpy(mailname, name, sizeof(mailname)); 134 mailsize = fsize(ibuf); 135 (void)snprintf(tempname, sizeof(tempname), 136 "%s/mail.RxXXXXXXXXXX", tmpdir); 137 if ((fd = mkstemp(tempname)) == -1 || (otf = fdopen(fd, "w")) == NULL) 138 err(1, "%s", tempname); 139 (void)fcntl(fileno(otf), F_SETFD, 1); 140 if ((itf = fopen(tempname, "r")) == NULL) 141 err(1, "%s", tempname); 142 (void)fcntl(fileno(itf), F_SETFD, 1); 143 (void)rm(tempname); 144 setptr(ibuf, 0); 145 setmsize(msgCount); 146 /* 147 * New mail may have arrived while we were reading 148 * the mail file, so reset mailsize to be where 149 * we really are in the file... 150 */ 151 mailsize = ftello(ibuf); 152 (void)Fclose(ibuf); 153 relsesigs(); 154 sawcom = 0; 155 156 if ((checkmode || !edit) && msgCount == 0) { 157 nomail: 158 if (!checkmode) { 159 fprintf(stderr, "No mail for %s\n", who); 160 return (-1); 161 } else 162 return (0); 163 } 164 return (checkmode ? 1 : 0); 165 } 166 167 /* 168 * Incorporate any new mail that has arrived since we first 169 * started reading mail. 170 */ 171 int 172 incfile() 173 { 174 off_t newsize; 175 int omsgCount = msgCount; 176 FILE *ibuf; 177 178 ibuf = Fopen(mailname, "r"); 179 if (ibuf == NULL) 180 return (-1); 181 holdsigs(); 182 newsize = fsize(ibuf); 183 if (newsize == 0) 184 return (-1); /* mail box is now empty??? */ 185 if (newsize < mailsize) 186 return (-1); /* mail box has shrunk??? */ 187 if (newsize == mailsize) 188 return (0); /* no new mail */ 189 setptr(ibuf, mailsize); 190 setmsize(msgCount); 191 mailsize = ftello(ibuf); 192 (void)Fclose(ibuf); 193 relsesigs(); 194 return (msgCount - omsgCount); 195 } 196 197 static int *msgvec; 198 static int reset_on_stop; /* do a reset() if stopped */ 199 200 /* 201 * Interpret user commands one by one. If standard input is not a tty, 202 * print no prompt. 203 */ 204 void 205 commands() 206 { 207 int n, eofloop = 0; 208 char linebuf[LINESIZE]; 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 printf("New mail has arrived.\n"); 228 reset_on_stop = 1; 229 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 unstack(); 258 continue; 259 } 260 if (value("interactive") != NULL && 261 value("ignoreeof") != NULL && 262 ++eofloop < 25) { 263 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(linebuf, contxt) 283 char linebuf[]; 284 int contxt; 285 { 286 char word[LINESIZE]; 287 char *arglist[MAXARGC]; 288 const struct cmd *com; 289 char *cp, *cp2; 290 int c, muvec[2]; 291 int e = 1; 292 293 /* 294 * Strip the white space away from the beginning 295 * of the command, then scan out a word, which 296 * consists of anything except digits and white space. 297 * 298 * Handle ! escapes differently to get the correct 299 * lexical conventions. 300 */ 301 302 for (cp = linebuf; isspace((unsigned char)*cp); cp++) 303 ; 304 if (*cp == '!') { 305 if (sourcing) { 306 printf("Can't \"!\" while sourcing\n"); 307 goto out; 308 } 309 shell(cp+1); 310 return (0); 311 } 312 cp2 = word; 313 while (*cp != '\0' && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL) 314 *cp2++ = *cp++; 315 *cp2 = '\0'; 316 317 /* 318 * Look up the command; if not found, bitch. 319 * Normally, a blank command would map to the 320 * first command in the table; while sourcing, 321 * however, we ignore blank lines to eliminate 322 * confusion. 323 */ 324 325 if (sourcing && *word == '\0') 326 return (0); 327 com = lex(word); 328 if (com == NULL) { 329 printf("Unknown command: \"%s\"\n", word); 330 goto out; 331 } 332 333 /* 334 * See if we should execute the command -- if a conditional 335 * we always execute it, otherwise, check the state of cond. 336 */ 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 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: 370 /* 371 * A message list defaulting to nearest forward 372 * legal message. 373 */ 374 if (msgvec == 0) { 375 printf("Illegal use of \"message list\"\n"); 376 break; 377 } 378 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0) 379 break; 380 if (c == 0) { 381 *msgvec = first(com->c_msgflag, com->c_msgmask); 382 msgvec[1] = 0; 383 } 384 if (*msgvec == 0) { 385 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 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 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 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 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 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(sz) 480 int sz; 481 { 482 483 if (msgvec != NULL) 484 (void)free(msgvec); 485 msgvec = calloc((unsigned)(sz + 1), sizeof(*msgvec)); 486 } 487 488 /* 489 * Find the correct command in the command table corresponding 490 * to the passed command "word" 491 */ 492 493 __const struct cmd * 494 lex(word) 495 char word[]; 496 { 497 const struct cmd *cp; 498 499 /* 500 * ignore trailing chars after `#' 501 * 502 * lines with beginning `#' are comments 503 * spaces before `#' are ignored in execute() 504 */ 505 506 if (*word == '#') 507 *(word+1) = '\0'; 508 509 510 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++) 511 if (isprefix(word, cp->c_name)) 512 return (cp); 513 return (NULL); 514 } 515 516 /* 517 * Determine if as1 is a valid prefix of as2. 518 * Return true if yep. 519 */ 520 int 521 isprefix(as1, as2) 522 const char *as1, *as2; 523 { 524 const char *s1, *s2; 525 526 s1 = as1; 527 s2 = as2; 528 while (*s1++ == *s2) 529 if (*s2++ == '\0') 530 return (1); 531 return (*--s1 == '\0'); 532 } 533 534 /* 535 * The following gets called on receipt of an interrupt. This is 536 * to abort printout of a command, mainly. 537 * Dispatching here when command() is inactive crashes rcv. 538 * Close all open files except 0, 1, 2, and the temporary. 539 * Also, unstack all source files. 540 */ 541 542 static int inithdr; /* am printing startup headers */ 543 544 /*ARGSUSED*/ 545 void 546 intr(s) 547 int s; 548 { 549 550 noreset = 0; 551 if (!inithdr) 552 sawcom++; 553 inithdr = 0; 554 while (sourcing) 555 unstack(); 556 557 close_all_files(); 558 559 if (image >= 0) { 560 (void)close(image); 561 image = -1; 562 } 563 fprintf(stderr, "Interrupt\n"); 564 reset(0); 565 } 566 567 /* 568 * When we wake up after ^Z, reprint the prompt. 569 */ 570 void 571 stop(s) 572 int s; 573 { 574 sig_t old_action = signal(s, SIG_DFL); 575 sigset_t nset; 576 577 (void)sigemptyset(&nset); 578 (void)sigaddset(&nset, s); 579 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL); 580 (void)kill(0, s); 581 (void)sigprocmask(SIG_BLOCK, &nset, NULL); 582 (void)signal(s, old_action); 583 if (reset_on_stop) { 584 reset_on_stop = 0; 585 reset(0); 586 } 587 } 588 589 /* 590 * Branch here on hangup signal and simulate "exit". 591 */ 592 /*ARGSUSED*/ 593 void 594 hangup(s) 595 int s; 596 { 597 598 /* nothing to do? */ 599 exit(1); 600 } 601 602 /* 603 * Announce the presence of the current Mail version, 604 * give the message count, and print a header listing. 605 */ 606 void 607 announce() 608 { 609 int vec[2], mdot; 610 611 mdot = newfileinfo(0); 612 vec[0] = mdot; 613 vec[1] = 0; 614 dot = &message[mdot - 1]; 615 if (msgCount > 0 && value("noheader") == NULL) { 616 inithdr++; 617 headers(vec); 618 inithdr = 0; 619 } 620 } 621 622 /* 623 * Announce information about the file we are editing. 624 * Return a likely place to set dot. 625 */ 626 int 627 newfileinfo(omsgCount) 628 int omsgCount; 629 { 630 struct message *mp; 631 int u, n, mdot, d, s; 632 char fname[PATHSIZE+1], zname[PATHSIZE+1], *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) - 1) >= 0) { 658 strcat(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 printf("1 message"); 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 printf(" [Read only]"); 680 printf("\n"); 681 return (mdot); 682 } 683 684 /* 685 * Print the current version number. 686 */ 687 688 /*ARGSUSED*/ 689 int 690 pversion(e) 691 int e; 692 { 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(name) 703 char *name; 704 { 705 FILE *in, *oldin; 706 707 if ((in = Fopen(name, "r")) == NULL) 708 return; 709 oldin = input; 710 input = in; 711 loading = 1; 712 sourcing = 1; 713 commands(); 714 loading = 0; 715 sourcing = 0; 716 input = oldin; 717 (void)Fclose(in); 718 } 719