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