1 /* $OpenBSD: lex.c,v 1.43 2023/03/08 04:43:11 guenther Exp $ */ 2 /* $NetBSD: lex.c,v 1.10 1997/05/17 19:55:13 pk Exp $ */ 3 4 /* 5 * Copyright (c) 1980, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include "rcv.h" 34 #include <errno.h> 35 #include <fcntl.h> 36 #include "extern.h" 37 38 /* 39 * Mail -- a mail program 40 * 41 * Lexical processing of commands. 42 */ 43 44 char *prompt = "& "; 45 46 const struct cmd *com; /* command we are running */ 47 48 /* 49 * Set up editing on the given file name. 50 * If the first character of name is %, we are considered to be 51 * editing the file, otherwise we are reading our mail which has 52 * signficance for mbox and so forth. 53 */ 54 int 55 setfile(char *name) 56 { 57 FILE *ibuf; 58 int i, fd; 59 struct stat stb; 60 char isedit = *name != '%'; 61 const char *who = name[1] ? name + 1 : myname; 62 char tempname[PATHSIZE]; 63 static int shudclob; 64 65 if ((name = expand(name)) == NULL) 66 return(-1); 67 68 if ((ibuf = Fopen(name, "r")) == NULL) { 69 if (!isedit && errno == ENOENT) 70 goto nomail; 71 warn("%s", name); 72 return(-1); 73 } 74 75 if (fstat(fileno(ibuf), &stb) == -1) { 76 warn("fstat"); 77 (void)Fclose(ibuf); 78 return(-1); 79 } 80 81 switch (stb.st_mode & S_IFMT) { 82 case S_IFDIR: 83 (void)Fclose(ibuf); 84 warnc(EISDIR, "%s", name); 85 return(-1); 86 87 case S_IFREG: 88 break; 89 90 default: 91 (void)Fclose(ibuf); 92 warnc(EINVAL, "%s", name); 93 return(-1); 94 } 95 96 /* 97 * Looks like all will be well. We must now relinquish our 98 * hold on the current set of stuff. Must hold signals 99 * while we are reading the new file, else we will ruin 100 * the message[] data structure. 101 */ 102 holdsigs(); 103 if (shudclob) 104 quit(); 105 106 /* 107 * Copy the messages into /tmp 108 * and set pointers. 109 */ 110 readonly = 0; 111 if ((i = open(name, O_WRONLY)) == -1) 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, PATHSIZE); 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 = mkostemp(tempname, O_CLOEXEC)) == -1 || 128 (otf = fdopen(fd, "w")) == NULL) 129 err(1, "%s", tempname); 130 if ((itf = fopen(tempname, "re")) == NULL) 131 err(1, "%s", tempname); 132 (void)rm(tempname); 133 setptr(ibuf, (off_t)0); 134 setmsize(msgCount); 135 /* 136 * New mail may have arrived while we were reading 137 * the mail file, so reset mailsize to be where 138 * we really are in the file... 139 */ 140 mailsize = ftell(ibuf); 141 (void)Fclose(ibuf); 142 relsesigs(); 143 sawcom = 0; 144 if (!edit && msgCount == 0) { 145 nomail: 146 fprintf(stderr, "No mail for %s\n", who); 147 return(-1); 148 } 149 return(0); 150 } 151 152 /* 153 * Incorporate any new mail that has arrived since we first 154 * started reading mail. 155 */ 156 int 157 incfile(void) 158 { 159 int newsize; 160 int omsgCount = msgCount; 161 FILE *ibuf; 162 163 ibuf = Fopen(mailname, "r"); 164 if (ibuf == NULL) 165 return(-1); 166 holdsigs(); 167 if (!spool_lock()) { 168 (void)Fclose(ibuf); 169 relsesigs(); 170 return(-1); 171 } 172 newsize = fsize(ibuf); 173 /* make sure mail box has grown and is non-empty */ 174 if (newsize == 0 || newsize <= mailsize) { 175 (void)Fclose(ibuf); 176 spool_unlock(); 177 relsesigs(); 178 return(newsize == mailsize ? 0 : -1); 179 } 180 setptr(ibuf, mailsize); 181 setmsize(msgCount); 182 mailsize = ftell(ibuf); 183 (void)Fclose(ibuf); 184 spool_unlock(); 185 relsesigs(); 186 return(msgCount - omsgCount); 187 } 188 189 190 int *msgvec; 191 int reset_on_stop; /* reset prompt if stopped */ 192 193 /* 194 * Interpret user commands one by one. If standard input is not a tty, 195 * print no prompt. 196 */ 197 void 198 commands(void) 199 { 200 int n, sig, *sigp; 201 int eofloop = 0; 202 char linebuf[LINESIZE]; 203 204 prompt: 205 for (;;) { 206 /* 207 * Print the prompt, if needed. Clear out 208 * string space, and flush the output. 209 */ 210 if (!sourcing && value("interactive") != NULL) { 211 if ((value("autoinc") != NULL) && (incfile() > 0)) 212 puts("New mail has arrived."); 213 reset_on_stop = 1; 214 printf("%s", prompt); 215 } 216 fflush(stdout); 217 sreset(); 218 /* 219 * Read a line of commands from the current input 220 * and handle end of file specially. 221 */ 222 n = 0; 223 sig = 0; 224 sigp = sourcing ? NULL : &sig; 225 for (;;) { 226 if (readline(input, &linebuf[n], LINESIZE - n, sigp) < 0) { 227 if (sig) { 228 if (sig == SIGINT) 229 dointr(); 230 else if (sig == SIGHUP) 231 /* nothing to do? */ 232 exit(1); 233 else { 234 /* Stopped by job control */ 235 (void)kill(0, sig); 236 if (reset_on_stop) 237 reset_on_stop = 0; 238 } 239 goto prompt; 240 } 241 if (n == 0) 242 n = -1; 243 break; 244 } 245 if ((n = strlen(linebuf)) == 0) 246 break; 247 n--; 248 if (linebuf[n] != '\\') 249 break; 250 linebuf[n++] = ' '; 251 } 252 reset_on_stop = 0; 253 if (n < 0) { 254 /* eof */ 255 if (loading) 256 break; 257 if (sourcing) { 258 unstack(); 259 continue; 260 } 261 if (value("interactive") != NULL && 262 value("ignoreeof") != NULL && 263 ++eofloop < 25) { 264 puts("Use \"quit\" to quit."); 265 continue; 266 } 267 break; 268 } 269 eofloop = 0; 270 if (execute(linebuf, 0)) 271 break; 272 } 273 } 274 275 /* 276 * Execute a single command. 277 * Command functions return 0 for success, 1 for error, and -1 278 * for abort. A 1 or -1 aborts a load or source. A -1 aborts 279 * the interactive command loop. 280 * Contxt is non-zero if called while composing mail. 281 */ 282 int 283 execute(char *linebuf, int contxt) 284 { 285 char word[LINESIZE]; 286 char *arglist[MAXARGC]; 287 char *cp, *cp2; 288 int c, muvec[2]; 289 int e = 1; 290 291 com = NULL; 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 for (cp = linebuf; isspace((unsigned char)*cp); cp++) 302 ; 303 if (*cp == '!') { 304 if (sourcing) { 305 puts("Can't \"!\" while sourcing"); 306 goto out; 307 } 308 shell(cp+1); 309 return(0); 310 } 311 cp2 = word; 312 while (*cp && 313 strchr(" \t0123456789$^.:/-+*'\"", (unsigned char)*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 if (sourcing && *word == '\0') 325 return(0); 326 com = lex(word); 327 if (com == NULL) { 328 printf("Unknown command: \"%s\"\n", word); 329 goto out; 330 } 331 332 /* 333 * See if we should execute the command -- if a conditional 334 * we always execute it, otherwise, check the state of cond. 335 */ 336 if ((com->c_argtype & F) == 0) 337 if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode)) 338 return(0); 339 340 /* 341 * Process the arguments to the command, depending 342 * on the type he expects. Default to an error. 343 * If we are sourcing an interactive command, it's 344 * an error. 345 */ 346 if (!rcvmode && (com->c_argtype & M) == 0) { 347 printf("May not execute \"%s\" while sending\n", 348 com->c_name); 349 goto out; 350 } 351 if (sourcing && com->c_argtype & I) { 352 printf("May not execute \"%s\" while sourcing\n", 353 com->c_name); 354 goto out; 355 } 356 if (readonly && com->c_argtype & W) { 357 printf("May not execute \"%s\" -- message file is read only\n", 358 com->c_name); 359 goto out; 360 } 361 if (contxt && com->c_argtype & R) { 362 printf("Cannot recursively invoke \"%s\"\n", com->c_name); 363 goto out; 364 } 365 switch (com->c_argtype & ~(F|P|I|M|T|W|R)) { 366 case MSGLIST|STRLIST: 367 /* 368 * A message list defaulting to nearest forward 369 * legal message. 370 */ 371 if (msgvec == 0) { 372 puts("Illegal use of \"message list\""); 373 break; 374 } 375 /* 376 * remove leading blanks. 377 */ 378 while (isspace((unsigned char)*cp)) 379 cp++; 380 381 if (isdigit((unsigned char)*cp) || *cp == ':') { 382 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0) 383 break; 384 /* position to next space - past the message list */ 385 while (!isspace((unsigned char)*cp)) 386 cp++; 387 /* position to next non-space */ 388 while (isspace((unsigned char)*cp)) 389 cp++; 390 } else { 391 c = 0; /* no message list */ 392 } 393 394 if (c == 0) { 395 *msgvec = first(com->c_msgflag, 396 com->c_msgmask); 397 msgvec[1] = 0; 398 } 399 if (*msgvec == 0) { 400 puts("No applicable messages"); 401 break; 402 } 403 /* 404 * Just the straight string, with 405 * leading blanks removed. 406 */ 407 while (isspace((unsigned char)*cp)) 408 cp++; 409 410 e = (*com->c_func2)(msgvec, cp); 411 break; 412 413 case MSGLIST: 414 /* 415 * A message list defaulting to nearest forward 416 * legal message. 417 */ 418 if (msgvec == NULL) { 419 puts("Illegal use of \"message list\""); 420 break; 421 } 422 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0) 423 break; 424 if (c == 0) { 425 *msgvec = first(com->c_msgflag, 426 com->c_msgmask); 427 msgvec[1] = 0; 428 } 429 if (*msgvec == 0) { 430 puts("No applicable messages"); 431 break; 432 } 433 e = (*com->c_func)(msgvec); 434 break; 435 436 case NDMLIST: 437 /* 438 * A message list with no defaults, but no error 439 * if none exist. 440 */ 441 if (msgvec == 0) { 442 puts("Illegal use of \"message list\""); 443 break; 444 } 445 if (getmsglist(cp, msgvec, com->c_msgflag) < 0) 446 break; 447 e = (*com->c_func)(msgvec); 448 break; 449 450 case STRLIST: 451 /* 452 * Just the straight string, with 453 * leading blanks removed. 454 */ 455 while (isspace((unsigned char)*cp)) 456 cp++; 457 e = (*com->c_func)(cp); 458 break; 459 460 case RAWLIST: 461 /* 462 * A vector of strings, in shell style. 463 */ 464 if ((c = getrawlist(cp, arglist, 465 sizeof(arglist) / sizeof(*arglist))) < 0) 466 break; 467 if (c < com->c_minargs) { 468 printf("%s requires at least %d arg(s)\n", 469 com->c_name, com->c_minargs); 470 break; 471 } 472 if (c > com->c_maxargs) { 473 printf("%s takes no more than %d arg(s)\n", 474 com->c_name, com->c_maxargs); 475 break; 476 } 477 e = (*com->c_func)(arglist); 478 break; 479 480 case NOLIST: 481 /* 482 * Just the constant zero, for exiting, 483 * eg. 484 */ 485 e = (*com->c_func)(0); 486 break; 487 488 default: 489 errx(1, "Unknown argtype"); 490 } 491 492 out: 493 /* 494 * Exit the current source file on 495 * error. 496 */ 497 if (e) { 498 if (e < 0) 499 return(1); 500 if (loading) 501 return(1); 502 if (sourcing) 503 unstack(); 504 return(0); 505 } 506 if (com == NULL) 507 return(0); 508 if (value("autoprint") != NULL && com->c_argtype & P) 509 if ((dot->m_flag & MDELETED) == 0) { 510 muvec[0] = dot - &message[0] + 1; 511 muvec[1] = 0; 512 type(muvec); 513 } 514 if (!sourcing && (com->c_argtype & T) == 0) 515 sawcom = 1; 516 return(0); 517 } 518 519 /* 520 * Set the size of the message vector used to construct argument 521 * lists to message list functions. 522 */ 523 void 524 setmsize(int n) 525 { 526 int *msgvec2; 527 size_t msize; 528 529 msize = (n + 1) * sizeof(*msgvec); 530 if ((msgvec2 = realloc(msgvec, msize)) == NULL) 531 err(1, "realloc"); 532 msgvec = msgvec2; 533 memset(msgvec, 0, msize); 534 } 535 536 /* 537 * Find the correct command in the command table corresponding 538 * to the passed command "word" 539 */ 540 541 const struct cmd * 542 lex(char *word) 543 { 544 extern const struct cmd cmdtab[]; 545 const struct cmd *cp; 546 547 if (word[0] == '#') 548 word = "#"; 549 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++) 550 if (isprefix(word, cp->c_name)) 551 return(cp); 552 return(NULL); 553 } 554 555 /* 556 * Determine if as1 is a valid prefix of as2. 557 * Return true if yep. 558 */ 559 int 560 isprefix(char *as1, char *as2) 561 { 562 char *s1, *s2; 563 564 s1 = as1; 565 s2 = as2; 566 while (*s1++ == *s2) 567 if (*s2++ == '\0') 568 return(1); 569 return(*--s1 == '\0'); 570 } 571 572 /* 573 * The following gets called on receipt of an interrupt. This is 574 * to abort printout of a command, mainly. 575 * Dispatching here when command() is inactive crashes rcv. 576 * Close all open files except 0, 1, 2, and the temporary. 577 * Also, unstack all source files. 578 */ 579 int inithdr; /* am printing startup headers */ 580 581 void 582 dointr(void) 583 { 584 585 noreset = 0; 586 if (!inithdr) 587 sawcom++; 588 inithdr = 0; 589 while (sourcing) 590 unstack(); 591 592 close_all_files(); 593 594 if (image >= 0) { 595 (void)close(image); 596 image = -1; 597 } 598 fputs("Interrupt\n", stderr); 599 } 600 601 /* 602 * Announce the presence of the current Mail version, 603 * give the message count, and print a header listing. 604 */ 605 void 606 announce(void) 607 { 608 int vec[2], mdot; 609 610 mdot = newfileinfo(0); 611 vec[0] = mdot; 612 vec[1] = 0; 613 dot = &message[mdot - 1]; 614 if (msgCount > 0 && value("noheader") == NULL) { 615 inithdr++; 616 headers(vec); 617 inithdr = 0; 618 } 619 } 620 621 /* 622 * Announce information about the file we are editing. 623 * Return a likely place to set dot. 624 */ 625 int 626 newfileinfo(int omsgCount) 627 { 628 struct message *mp; 629 int u, n, mdot, d, s; 630 char fname[PATHSIZE], zname[PATHSIZE], *ename; 631 632 for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++) 633 if (mp->m_flag & MNEW) 634 break; 635 if (mp >= &message[msgCount]) 636 for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++) 637 if ((mp->m_flag & MREAD) == 0) 638 break; 639 if (mp < &message[msgCount]) 640 mdot = mp - &message[0] + 1; 641 else 642 mdot = omsgCount + 1; 643 s = d = 0; 644 for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) { 645 if (mp->m_flag & MNEW) 646 n++; 647 if ((mp->m_flag & MREAD) == 0) 648 u++; 649 if (mp->m_flag & MDELETED) 650 d++; 651 if (mp->m_flag & MSAVED) 652 s++; 653 } 654 ename = mailname; 655 if (getfold(fname, sizeof(fname)) >= 0) { 656 strlcat(fname, "/", sizeof(fname)); 657 if (strncmp(fname, mailname, strlen(fname)) == 0) { 658 (void)snprintf(zname, sizeof(zname), "+%s", 659 mailname + strlen(fname)); 660 ename = zname; 661 } 662 } 663 printf("\"%s\": ", ename); 664 if (msgCount == 1) 665 fputs("1 message", stdout); 666 else 667 printf("%d messages", msgCount); 668 if (n > 0) 669 printf(" %d new", n); 670 if (u-n > 0) 671 printf(" %d unread", u); 672 if (d > 0) 673 printf(" %d deleted", d); 674 if (s > 0) 675 printf(" %d saved", s); 676 if (readonly) 677 fputs(" [Read only]", stdout); 678 putchar('\n'); 679 return(mdot); 680 } 681 682 /* 683 * Print the current version number. 684 */ 685 int 686 pversion(void *v) 687 { 688 extern const char version[]; 689 690 printf("Version %s\n", version); 691 return(0); 692 } 693 694 /* 695 * Load a file of user definitions. 696 */ 697 void 698 load(char *name) 699 { 700 FILE *in, *oldin; 701 702 if ((in = Fopen(name, "r")) == NULL) 703 return; 704 oldin = input; 705 input = in; 706 loading = 1; 707 sourcing = 1; 708 commands(); 709 loading = 0; 710 sourcing = 0; 711 input = oldin; 712 (void)Fclose(in); 713 } 714