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