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