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