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