1 /* $OpenBSD: list.c,v 1.10 2001/01/16 05:36:08 millert Exp $ */ 2 /* $NetBSD: list.c,v 1.7 1997/07/09 05:23:36 mikel 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. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 #if 0 39 static char sccsid[] = "@(#)list.c 8.4 (Berkeley) 5/1/95"; 40 #else 41 static char rcsid[] = "$OpenBSD: list.c,v 1.10 2001/01/16 05:36:08 millert Exp $"; 42 #endif 43 #endif /* not lint */ 44 45 #include "rcv.h" 46 #include <ctype.h> 47 #include "extern.h" 48 49 int matchto __P((char *, int)); 50 51 /* 52 * Mail -- a mail program 53 * 54 * Message list handling. 55 */ 56 57 /* 58 * Convert the user string of message numbers and 59 * store the numbers into vector. 60 * 61 * Returns the count of messages picked up or -1 on error. 62 */ 63 int 64 getmsglist(buf, vector, flags) 65 char *buf; 66 int *vector, flags; 67 { 68 int *ip; 69 struct message *mp; 70 71 if (msgCount == 0) { 72 *vector = 0; 73 return(0); 74 } 75 if (markall(buf, flags) < 0) 76 return(-1); 77 ip = vector; 78 for (mp = &message[0]; mp < &message[msgCount]; mp++) 79 if (mp->m_flag & MMARK) 80 *ip++ = mp - &message[0] + 1; 81 *ip = 0; 82 return(ip - vector); 83 } 84 85 /* 86 * Mark all messages that the user wanted from the command 87 * line in the message structure. Return 0 on success, -1 88 * on error. 89 */ 90 91 /* 92 * Bit values for colon modifiers. 93 */ 94 95 #define CMNEW 01 /* New messages */ 96 #define CMOLD 02 /* Old messages */ 97 #define CMUNREAD 04 /* Unread messages */ 98 #define CMDELETED 010 /* Deleted messages */ 99 #define CMREAD 020 /* Read messages */ 100 101 /* 102 * The following table describes the letters which can follow 103 * the colon and gives the corresponding modifier bit. 104 */ 105 106 struct coltab { 107 char co_char; /* What to find past : */ 108 int co_bit; /* Associated modifier bit */ 109 int co_mask; /* m_status bits to mask */ 110 int co_equal; /* ... must equal this */ 111 } coltab[] = { 112 { 'n', CMNEW, MNEW, MNEW }, 113 { 'o', CMOLD, MNEW, 0 }, 114 { 'u', CMUNREAD, MREAD, 0 }, 115 { 'd', CMDELETED, MDELETED, MDELETED }, 116 { 'r', CMREAD, MREAD, MREAD }, 117 { 0, 0, 0, 0 } 118 }; 119 120 static int lastcolmod; 121 122 int 123 markall(buf, f) 124 char buf[]; 125 int f; 126 { 127 char **np; 128 int i; 129 struct message *mp; 130 char *namelist[NMLSIZE], *bufp; 131 int tok, beg, mc, star, other, valdot, colmod, colresult; 132 133 valdot = dot - &message[0] + 1; 134 colmod = 0; 135 for (i = 1; i <= msgCount; i++) 136 unmark(i); 137 bufp = buf; 138 mc = 0; 139 np = &namelist[0]; 140 scaninit(); 141 tok = scan(&bufp); 142 star = 0; 143 other = 0; 144 beg = 0; 145 while (tok != TEOL) { 146 switch (tok) { 147 case TNUMBER: 148 number: 149 if (star) { 150 puts("No numbers mixed with *"); 151 return(-1); 152 } 153 mc++; 154 other++; 155 if (beg != 0) { 156 if (check(lexnumber, f)) 157 return(-1); 158 for (i = beg; i <= lexnumber; i++) 159 if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0) 160 mark(i); 161 beg = 0; 162 break; 163 } 164 beg = lexnumber; 165 if (check(beg, f)) 166 return(-1); 167 tok = scan(&bufp); 168 regret(tok); 169 if (tok != TDASH) { 170 mark(beg); 171 beg = 0; 172 } 173 break; 174 175 case TPLUS: 176 if (beg != 0) { 177 puts("Non-numeric second argument"); 178 return(-1); 179 } 180 i = valdot; 181 do { 182 i++; 183 if (i > msgCount) { 184 puts("Referencing beyond EOF"); 185 return(-1); 186 } 187 } while ((message[i - 1].m_flag & MDELETED) != f); 188 mark(i); 189 break; 190 191 case TDASH: 192 if (beg == 0) { 193 i = valdot; 194 do { 195 i--; 196 if (i <= 0) { 197 puts("Referencing before 1"); 198 return(-1); 199 } 200 } while ((message[i - 1].m_flag & MDELETED) != f); 201 mark(i); 202 } 203 break; 204 205 case TSTRING: 206 if (beg != 0) { 207 puts("Non-numeric second argument"); 208 return(-1); 209 } 210 other++; 211 if (lexstring[0] == ':') { 212 colresult = evalcol(lexstring[1]); 213 if (colresult == 0) { 214 printf("Unknown colon modifier \"%s\"\n", 215 lexstring); 216 return(-1); 217 } 218 colmod |= colresult; 219 } else { 220 if ((com->c_argtype & ~(F|P|I|M|T|W|R)) 221 != (MSGLIST|STRLIST)) 222 *np++ = savestr(lexstring); 223 } 224 break; 225 226 case TDOLLAR: 227 case TUP: 228 case TDOT: 229 lexnumber = metamess(lexstring[0], f); 230 if (lexnumber == -1) 231 return(-1); 232 goto number; 233 234 case TSTAR: 235 if (other) { 236 puts("Can't mix \"*\" with anything"); 237 return(-1); 238 } 239 star++; 240 break; 241 242 case TERROR: 243 return(-1); 244 } 245 tok = scan(&bufp); 246 } 247 lastcolmod = colmod; 248 *np = NULL; 249 mc = 0; 250 if (star) { 251 for (i = 0; i < msgCount; i++) 252 if ((message[i].m_flag & MDELETED) == f) { 253 mark(i+1); 254 mc++; 255 } 256 if (mc == 0) { 257 puts("No applicable messages."); 258 return(-1); 259 } 260 return(0); 261 } 262 263 /* 264 * If no numbers were given, mark all of the messages, 265 * so that we can unmark any whose sender was not selected 266 * if any user names were given. 267 */ 268 269 if ((np > namelist || colmod != 0) && mc == 0) 270 for (i = 1; i <= msgCount; i++) 271 if ((message[i-1].m_flag & MDELETED) == f) 272 mark(i); 273 274 /* 275 * If any names were given, go through and eliminate any 276 * messages whose senders were not requested. 277 */ 278 279 if (np > namelist) { 280 for (i = 1; i <= msgCount; i++) { 281 for (mc = 0, np = &namelist[0]; *np != NULL; np++) 282 if (**np == '/') { 283 if (matchsubj(*np, i)) { 284 mc++; 285 break; 286 } 287 } 288 else { 289 if (matchsender(*np, i)) { 290 mc++; 291 break; 292 } 293 } 294 if (mc == 0) 295 unmark(i); 296 } 297 298 /* 299 * Make sure we got some decent messages. 300 */ 301 302 mc = 0; 303 for (i = 1; i <= msgCount; i++) 304 if (message[i-1].m_flag & MMARK) { 305 mc++; 306 break; 307 } 308 if (mc == 0) { 309 printf("No applicable messages from {%s", 310 namelist[0]); 311 for (np = &namelist[1]; *np != NULL; np++) 312 printf(", %s", *np); 313 puts("}"); 314 return(-1); 315 } 316 } 317 318 /* 319 * If any colon modifiers were given, go through and 320 * unmark any messages which do not satisfy the modifiers. 321 */ 322 323 if (colmod != 0) { 324 for (i = 1; i <= msgCount; i++) { 325 struct coltab *colp; 326 327 mp = &message[i - 1]; 328 for (colp = &coltab[0]; colp->co_char; colp++) 329 if (colp->co_bit & colmod) 330 if ((mp->m_flag & colp->co_mask) 331 != colp->co_equal) 332 unmark(i); 333 334 } 335 for (mp = &message[0]; mp < &message[msgCount]; mp++) 336 if (mp->m_flag & MMARK) 337 break; 338 if (mp >= &message[msgCount]) { 339 struct coltab *colp; 340 341 fputs("No messages satisfy", stdout); 342 for (colp = &coltab[0]; colp->co_char; colp++) 343 if (colp->co_bit & colmod) 344 printf(" :%c", colp->co_char); 345 putchar('\n'); 346 return(-1); 347 } 348 } 349 return(0); 350 } 351 352 /* 353 * Turn the character after a colon modifier into a bit 354 * value. 355 */ 356 int 357 evalcol(col) 358 int col; 359 { 360 struct coltab *colp; 361 362 if (col == 0) 363 return(lastcolmod); 364 for (colp = &coltab[0]; colp->co_char; colp++) 365 if (colp->co_char == col) 366 return(colp->co_bit); 367 return(0); 368 } 369 370 /* 371 * Check the passed message number for legality and proper flags. 372 * If f is MDELETED, then either kind will do. Otherwise, the message 373 * has to be undeleted. 374 */ 375 int 376 check(mesg, f) 377 int mesg, f; 378 { 379 struct message *mp; 380 381 if (mesg < 1 || mesg > msgCount) { 382 printf("%d: Invalid message number\n", mesg); 383 return(-1); 384 } 385 mp = &message[mesg-1]; 386 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) { 387 printf("%d: Inappropriate message\n", mesg); 388 return(-1); 389 } 390 return(0); 391 } 392 393 /* 394 * Scan out the list of string arguments, shell style 395 * for a RAWLIST. 396 */ 397 int 398 getrawlist(line, argv, argc) 399 char line[]; 400 char **argv; 401 int argc; 402 { 403 char c, *cp, *cp2, quotec; 404 int argn; 405 char *linebuf; 406 size_t linebufsize = BUFSIZ; 407 408 if ((linebuf = (char *)malloc(linebufsize)) == NULL) 409 errx(1, "Out of memory"); 410 411 argn = 0; 412 cp = line; 413 for (;;) { 414 for (; *cp == ' ' || *cp == '\t'; cp++) 415 ; 416 if (*cp == '\0') 417 break; 418 if (argn >= argc - 1) { 419 puts("Too many elements in the list; excess discarded."); 420 break; 421 } 422 cp2 = linebuf; 423 quotec = '\0'; 424 while ((c = *cp) != '\0') { 425 /* Alloc more space if necessary */ 426 if (cp2 - linebuf == linebufsize - 1) { 427 linebufsize += BUFSIZ; 428 linebuf = (char *)realloc(linebuf, linebufsize); 429 if (linebuf == NULL) 430 errx(1, "Out of memory"); 431 cp2 = linebuf + linebufsize - BUFSIZ - 1; 432 } 433 cp++; 434 if (quotec != '\0') { 435 if (c == quotec) 436 quotec = '\0'; 437 else if (c == '\\') 438 switch (c = *cp++) { 439 case '\0': 440 *cp2++ = '\\'; 441 cp--; 442 break; 443 case '0': case '1': case '2': case '3': 444 case '4': case '5': case '6': case '7': 445 c -= '0'; 446 if (*cp >= '0' && *cp <= '7') 447 c = c * 8 + *cp++ - '0'; 448 if (*cp >= '0' && *cp <= '7') 449 c = c * 8 + *cp++ - '0'; 450 *cp2++ = c; 451 break; 452 case 'b': 453 *cp2++ = '\b'; 454 break; 455 case 'f': 456 *cp2++ = '\f'; 457 break; 458 case 'n': 459 *cp2++ = '\n'; 460 break; 461 case 'r': 462 *cp2++ = '\r'; 463 break; 464 case 't': 465 *cp2++ = '\t'; 466 break; 467 case 'v': 468 *cp2++ = '\v'; 469 break; 470 default: 471 *cp2++ = c; 472 } 473 else if (c == '^') { 474 c = *cp++; 475 if (c == '?') 476 *cp2++ = '\177'; 477 /* null doesn't show up anyway */ 478 else if ((c >= 'A' && c <= '_') || 479 (c >= 'a' && c <= 'z')) 480 *cp2++ = c & 037; 481 else { 482 *cp2++ = '^'; 483 cp--; 484 } 485 } else 486 *cp2++ = c; 487 } else if (c == '"' || c == '\'') 488 quotec = c; 489 else if (c == ' ' || c == '\t') 490 break; 491 else 492 *cp2++ = c; 493 } 494 *cp2 = '\0'; 495 argv[argn++] = savestr(linebuf); 496 } 497 argv[argn] = NULL; 498 (void)free(linebuf); 499 return(argn); 500 } 501 502 /* 503 * scan out a single lexical item and return its token number, 504 * updating the string pointer passed **p. Also, store the value 505 * of the number or string scanned in lexnumber or lexstring as 506 * appropriate. In any event, store the scanned `thing' in lexstring. 507 */ 508 509 struct lex { 510 char l_char; 511 char l_token; 512 } singles[] = { 513 { '$', TDOLLAR }, 514 { '.', TDOT }, 515 { '^', TUP }, 516 { '*', TSTAR }, 517 { '-', TDASH }, 518 { '+', TPLUS }, 519 { '(', TOPEN }, 520 { ')', TCLOSE }, 521 { 0, 0 } 522 }; 523 524 int 525 scan(sp) 526 char **sp; 527 { 528 char *cp, *cp2; 529 int c; 530 struct lex *lp; 531 int quotec; 532 533 if (regretp >= 0) { 534 strcpy(lexstring, string_stack[regretp]); 535 lexnumber = numberstack[regretp]; 536 return(regretstack[regretp--]); 537 } 538 cp = *sp; 539 cp2 = lexstring; 540 c = *cp++; 541 542 /* 543 * strip away leading white space. 544 */ 545 546 while (c == ' ' || c == '\t') 547 c = *cp++; 548 549 /* 550 * If no characters remain, we are at end of line, 551 * so report that. 552 */ 553 554 if (c == '\0') { 555 *sp = --cp; 556 return(TEOL); 557 } 558 559 /* 560 * If the leading character is a digit, scan 561 * the number and convert it on the fly. 562 * Return TNUMBER when done. 563 */ 564 565 if (isdigit(c)) { 566 lexnumber = 0; 567 while (isdigit(c)) { 568 lexnumber = lexnumber*10 + c - '0'; 569 *cp2++ = c; 570 c = *cp++; 571 } 572 *cp2 = '\0'; 573 *sp = --cp; 574 return(TNUMBER); 575 } 576 577 /* 578 * Check for single character tokens; return such 579 * if found. 580 */ 581 582 for (lp = &singles[0]; lp->l_char != 0; lp++) 583 if (c == lp->l_char) { 584 lexstring[0] = c; 585 lexstring[1] = '\0'; 586 *sp = cp; 587 return(lp->l_token); 588 } 589 590 /* 591 * We've got a string! Copy all the characters 592 * of the string into lexstring, until we see 593 * a null, space, or tab. 594 * If the lead character is a " or ', save it 595 * and scan until you get another. 596 */ 597 598 quotec = 0; 599 if (c == '\'' || c == '"') { 600 quotec = c; 601 c = *cp++; 602 } 603 while (c != '\0') { 604 if (c == quotec) { 605 cp++; 606 break; 607 } 608 if (quotec == 0 && (c == ' ' || c == '\t')) 609 break; 610 if (cp2 - lexstring < STRINGLEN-1) 611 *cp2++ = c; 612 c = *cp++; 613 } 614 if (quotec && c == 0) { 615 fprintf(stderr, "Missing %c\n", quotec); 616 return(TERROR); 617 } 618 *sp = --cp; 619 *cp2 = '\0'; 620 return(TSTRING); 621 } 622 623 /* 624 * Unscan the named token by pushing it onto the regret stack. 625 */ 626 void 627 regret(token) 628 int token; 629 { 630 if (++regretp >= REGDEP) 631 errx(1, "Too many regrets"); 632 regretstack[regretp] = token; 633 lexstring[STRINGLEN-1] = '\0'; 634 string_stack[regretp] = savestr(lexstring); 635 numberstack[regretp] = lexnumber; 636 } 637 638 /* 639 * Reset all the scanner global variables. 640 */ 641 void 642 scaninit() 643 { 644 regretp = -1; 645 } 646 647 /* 648 * Find the first message whose flags & m == f and return 649 * its message number. 650 */ 651 int 652 first(f, m) 653 int f, m; 654 { 655 struct message *mp; 656 657 if (msgCount == 0) 658 return(0); 659 f &= MDELETED; 660 m &= MDELETED; 661 for (mp = dot; mp < &message[msgCount]; mp++) 662 if ((mp->m_flag & m) == f) 663 return(mp - message + 1); 664 for (mp = dot-1; mp >= &message[0]; mp--) 665 if ((mp->m_flag & m) == f) 666 return(mp - message + 1); 667 return(0); 668 } 669 670 /* 671 * See if the passed name sent the passed message number. Return true 672 * if so. 673 */ 674 int 675 matchsender(str, mesg) 676 char *str; 677 int mesg; 678 { 679 char *cp, *cp2, *backup; 680 681 if (!*str) /* null string matches nothing instead of everything */ 682 return(0); 683 backup = cp2 = nameof(&message[mesg - 1], 0); 684 cp = str; 685 while (*cp2) { 686 if (*cp == 0) 687 return(1); 688 if (raise(*cp++) != raise(*cp2++)) { 689 cp2 = ++backup; 690 cp = str; 691 } 692 } 693 return(*cp == 0); 694 } 695 696 /* 697 * See if the passed name received the passed message number. Return true 698 * if so. 699 */ 700 701 static char *to_fields[] = { "to", "cc", "bcc", NULL }; 702 703 int 704 matchto(str, mesg) 705 char *str; 706 { 707 struct message *mp; 708 char *cp, *cp2, *backup, **to; 709 710 str++; 711 712 if (*str == 0) /* null string matches nothing instead of everything */ 713 return(0); 714 715 mp = &message[mesg-1]; 716 717 for (to = to_fields; *to; to++) { 718 cp = str; 719 cp2 = hfield(*to, mp); 720 if (cp2 != NULL) { 721 backup = cp2; 722 while (*cp2) { 723 if (*cp == 0) 724 return(1); 725 if (raise(*cp++) != raise(*cp2++)) { 726 cp2 = ++backup; 727 cp = str; 728 } 729 } 730 if (*cp == 0) 731 return(1); 732 } 733 } 734 return(0); 735 } 736 737 /* 738 * See if the given string matches inside the subject field of the 739 * given message. For the purpose of the scan, we ignore case differences. 740 * If it does, return true. The string search argument is assumed to 741 * have the form "/search-string." If it is of the form "/," we use the 742 * previous search string. 743 */ 744 745 char lastscan[STRINGLEN]; 746 int 747 matchsubj(str, mesg) 748 char *str; 749 int mesg; 750 { 751 struct message *mp; 752 char *cp, *cp2, *backup; 753 754 str++; 755 if (*str == '\0') 756 str = lastscan; 757 else { 758 strncpy(lastscan, str, sizeof(lastscan) - 1); 759 lastscan[sizeof(lastscan) - 1] = '\0'; 760 } 761 mp = &message[mesg-1]; 762 763 /* 764 * Now look, ignoring case, for the word in the string. 765 */ 766 767 if (value("searchheaders") && (cp = strchr(str, ':'))) { 768 /* Check for special case "/To:" */ 769 if (raise(str[0]) == 'T' && raise(str[1]) == 'O' && 770 str[2] == ':') 771 return(matchto(cp, mesg)); 772 *cp++ = '\0'; 773 cp2 = hfield(*str ? str : "subject", mp); 774 cp[-1] = ':'; 775 str = cp; 776 } else { 777 cp = str; 778 cp2 = hfield("subject", mp); 779 } 780 if (cp2 == NULL) 781 return(0); 782 backup = cp2; 783 while (*cp2) { 784 if (*cp == 0) 785 return(1); 786 if (raise(*cp++) != raise(*cp2++)) { 787 cp2 = ++backup; 788 cp = str; 789 } 790 } 791 return(*cp == 0); 792 } 793 794 /* 795 * Mark the named message by setting its mark bit. 796 */ 797 void 798 mark(mesg) 799 int mesg; 800 { 801 int i; 802 803 i = mesg; 804 if (i < 1 || i > msgCount) 805 errx(1, "Bad message number to mark"); 806 message[i-1].m_flag |= MMARK; 807 } 808 809 /* 810 * Unmark the named message. 811 */ 812 void 813 unmark(mesg) 814 int mesg; 815 { 816 int i; 817 818 i = mesg; 819 if (i < 1 || i > msgCount) 820 errx(1, "Bad message number to unmark"); 821 message[i-1].m_flag &= ~MMARK; 822 } 823 824 /* 825 * Return the message number corresponding to the passed meta character. 826 */ 827 int 828 metamess(meta, f) 829 int meta, f; 830 { 831 int c, m; 832 struct message *mp; 833 834 c = meta; 835 switch (c) { 836 case '^': 837 /* 838 * First 'good' message left. 839 */ 840 for (mp = &message[0]; mp < &message[msgCount]; mp++) 841 if ((mp->m_flag & MDELETED) == f) 842 return(mp - &message[0] + 1); 843 puts("No applicable messages"); 844 return(-1); 845 846 case '$': 847 /* 848 * Last 'good message left. 849 */ 850 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--) 851 if ((mp->m_flag & MDELETED) == f) 852 return(mp - &message[0] + 1); 853 puts("No applicable messages"); 854 return(-1); 855 856 case '.': 857 /* 858 * Current message. 859 */ 860 m = dot - &message[0] + 1; 861 if ((dot->m_flag & MDELETED) != f) { 862 printf("%d: Inappropriate message\n", m); 863 return(-1); 864 } 865 return(m); 866 867 default: 868 printf("Unknown metachar (%c)\n", c); 869 return(-1); 870 } 871 } 872