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