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