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