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