1 /* $NetBSD: list.c,v 1.29 2021/12/07 21:37:37 andvar Exp $ */
2
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 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)list.c 8.4 (Berkeley) 5/1/95";
36 #else
37 __RCSID("$NetBSD: list.c,v 1.29 2021/12/07 21:37:37 andvar Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include <assert.h>
42 #include <regex.h>
43 #include <util.h>
44
45 #include "rcv.h"
46 #include "extern.h"
47 #include "format.h"
48 #include "thread.h"
49 #include "mime.h"
50
51 /*
52 * Mail -- a mail program
53 *
54 * Message list handling.
55 */
56
57 /*
58 * Token values returned by the scanner used for argument lists.
59 * Also, sizes of scanner-related things.
60 */
61 enum token_e {
62 TEOL, /* End of the command line */
63 TNUMBER, /* A message number or range of numbers */
64 TDASH, /* A simple dash */
65 TSTRING, /* A string (possibly containing '-') */
66 TDOT, /* A "." */
67 TUP, /* An "^" */
68 TDOLLAR, /* A "$" */
69 TSTAR, /* A "*" */
70 TOPEN, /* An '(' */
71 TCLOSE, /* A ')' */
72 TPLUS, /* A '+' */
73 TAND, /* A '&' */
74 TOR, /* A '|' */
75 TXOR, /* A logical '^' */
76 TNOT, /* A '!' */
77 TERROR /* A lexical error */
78 };
79
80 #define REGDEP 2 /* Maximum regret depth. */
81 #define STRINGLEN 1024 /* Maximum length of string token */
82
83 static int lexnumber; /* Number of TNUMBER from scan() */
84 static char lexstring[STRINGLEN]; /* String from TSTRING, scan() */
85 static int regretp; /* Pointer to TOS of regret tokens */
86 static int regretstack[REGDEP]; /* Stack of regretted tokens */
87 static char *string_stack[REGDEP]; /* Stack of regretted strings */
88 static int numberstack[REGDEP]; /* Stack of regretted numbers */
89
90 /*
91 * Scan out the list of string arguments, shell style
92 * for a RAWLIST.
93 */
94 PUBLIC int
getrawlist(const char line[],char ** argv,int argc)95 getrawlist(const char line[], char **argv, int argc)
96 {
97 char c, *cp2, quotec;
98 const char *cp;
99 int argn;
100 char linebuf[LINESIZE];
101
102 argn = 0;
103 cp = line;
104 for (;;) {
105 cp = skip_WSP(cp);
106 if (*cp == '\0')
107 break;
108 if (argn >= argc - 1) {
109 (void)printf(
110 "Too many elements in the list; excess discarded.\n");
111 break;
112 }
113 cp2 = linebuf;
114 quotec = '\0';
115 while ((c = *cp) != '\0') {
116 cp++;
117 if (quotec != '\0') {
118 if (c == quotec)
119 quotec = '\0';
120 else if (quotec != '\'' && c == '\\')
121 switch (c = *cp++) {
122 case '\0':
123 *cp2++ = '\\';
124 cp--;
125 break;
126 case '0': case '1': case '2': case '3':
127 case '4': case '5': case '6': case '7':
128 c -= '0';
129 if (*cp >= '0' && *cp <= '7')
130 c = c * 8 + *cp++ - '0';
131 if (*cp >= '0' && *cp <= '7')
132 c = c * 8 + *cp++ - '0';
133 *cp2++ = c;
134 break;
135 case 'b':
136 *cp2++ = '\b';
137 break;
138 case 'f':
139 *cp2++ = '\f';
140 break;
141 case 'n':
142 *cp2++ = '\n';
143 break;
144 case 'r':
145 *cp2++ = '\r';
146 break;
147 case 't':
148 *cp2++ = '\t';
149 break;
150 case 'v':
151 *cp2++ = '\v';
152 break;
153 default:
154 *cp2++ = c;
155 }
156 else if (c == '^') {
157 c = *cp++;
158 if (c == '?')
159 *cp2++ = '\177';
160 /* null doesn't show up anyway */
161 else if ((c >= 'A' && c <= '_') ||
162 (c >= 'a' && c <= 'z'))
163 *cp2++ = c & 037;
164 else {
165 *cp2++ = '^';
166 cp--;
167 }
168 } else
169 *cp2++ = c;
170 } else if (c == '"' || c == '\'')
171 quotec = c;
172 else if (is_WSP(c))
173 break;
174 else
175 *cp2++ = c;
176 }
177 *cp2 = '\0';
178 argv[argn++] = savestr(linebuf);
179 }
180 argv[argn] = NULL;
181 return argn;
182 }
183
184 /*
185 * Mark all messages that the user wanted from the command
186 * line in the message structure. Return 0 on success, -1
187 * on error.
188 */
189
190 /*
191 * Bit values for colon modifiers.
192 */
193 #define CMBOX 0x001 /* Unread messages */
194 #define CMDELETED 0x002 /* Deleted messages */
195 #define CMMODIFY 0x004 /* Unread messages */
196 #define CMNEW 0x008 /* New messages */
197 #define CMOLD 0x010 /* Old messages */
198 #define CMPRESERVE 0x020 /* Unread messages */
199 #define CMREAD 0x040 /* Read messages */
200 #define CMSAVED 0x080 /* Saved messages */
201 #define CMTAGGED 0x100 /* Tagged messages */
202 #define CMUNREAD 0x200 /* Unread messages */
203 #define CMNEGATE 0x400 /* Negate the match */
204 #define CMMASK 0x7ff /* Mask the valid bits */
205
206 /*
207 * The following table describes the letters which can follow
208 * the colon and gives the corresponding modifier bit.
209 */
210
211 static const struct coltab {
212 char co_char; /* What to find past : */
213 int co_bit; /* Associated modifier bit */
214 int co_mask; /* m_status bits to mask */
215 int co_equal; /* ... must equal this */
216 } coltab[] = {
217 { '!', CMNEGATE, 0, 0 },
218 { 'd', CMDELETED, MDELETED, MDELETED },
219 { 'e', CMMODIFY, MMODIFY, MMODIFY },
220 { 'm', CMBOX, MBOX, MBOX },
221 { 'n', CMNEW, MNEW, MNEW },
222 { 'o', CMOLD, MNEW, 0 },
223 { 'p', CMPRESERVE, MPRESERVE, MPRESERVE },
224 { 'r', CMREAD, MREAD, MREAD },
225 { 's', CMSAVED, MSAVED, MSAVED },
226 { 't', CMTAGGED, MTAGGED, MTAGGED },
227 { 'u', CMUNREAD, MREAD|MNEW, 0 },
228 { 0, 0, 0, 0 }
229 };
230
231 static int lastcolmod;
232
233 static int
ignore_message(int m_flag,int colmod)234 ignore_message(int m_flag, int colmod)
235 {
236 int ignore_msg;
237 const struct coltab *colp;
238
239 ignore_msg = !(colmod & CMNEGATE);
240 colmod &= (~CMNEGATE & CMMASK);
241
242 for (colp = &coltab[0]; colp->co_char; colp++)
243 if (colp->co_bit & colmod &&
244 (m_flag & colp->co_mask) == colp->co_equal)
245 return !ignore_msg;
246 return ignore_msg;
247 }
248
249 /*
250 * Turn the character after a colon modifier into a bit
251 * value.
252 */
253 static int
evalcol(int col)254 evalcol(int col)
255 {
256 const struct coltab *colp;
257
258 if (col == 0)
259 return lastcolmod;
260 for (colp = &coltab[0]; colp->co_char; colp++)
261 if (colp->co_char == col)
262 return colp->co_bit;
263 return 0;
264 }
265
266 static int
get_colmod(int colmod,char * cp)267 get_colmod(int colmod, char *cp)
268 {
269 if ((cp[0] == '\0') ||
270 (cp[0] == '!' && cp[1] == '\0'))
271 colmod |= lastcolmod;
272
273 for (/*EMPTY*/; *cp; cp++) {
274 int colresult;
275 if ((colresult = evalcol(*cp)) == 0) {
276 (void)printf("Unknown colon modifier \"%s\"\n", lexstring);
277 return -1;
278 }
279 if (colresult == CMNEGATE)
280 colmod ^= CMNEGATE;
281 else
282 colmod |= colresult;
283 }
284 return colmod;
285 }
286
287 static int
syntax_error(const char * msg)288 syntax_error(const char *msg)
289 {
290 (void)printf("Syntax error: %s\n", msg);
291 return -1;
292 }
293
294 /*
295 * scan out a single lexical item and return its token number,
296 * updating the string pointer passed **p. Also, store the value
297 * of the number or string scanned in lexnumber or lexstring as
298 * appropriate. In any event, store the scanned `thing' in lexstring.
299 */
300 static enum token_e
scan(char ** sp)301 scan(char **sp)
302 {
303 static const struct lex {
304 char l_char;
305 enum token_e l_token;
306 } singles[] = {
307 { '$', TDOLLAR },
308 { '.', TDOT },
309 { '^', TUP },
310 { '*', TSTAR },
311 { '-', TDASH },
312 { '+', TPLUS },
313 { '(', TOPEN },
314 { ')', TCLOSE },
315 { '&', TAND },
316 { '|', TOR },
317 { '!', TNOT },
318 { 0, 0 }
319 };
320 const struct lex *lp;
321 char *cp, *cp2;
322 int c;
323 int quotec;
324
325 if (regretp >= 0) {
326 (void)strcpy(lexstring, string_stack[regretp]);
327 lexnumber = numberstack[regretp];
328 return regretstack[regretp--];
329 }
330 cp = *sp;
331 cp2 = lexstring;
332 lexstring[0] = '\0';
333
334 /*
335 * strip away leading white space.
336 */
337 cp = skip_WSP(cp);
338
339 /*
340 * If no characters remain, we are at end of line,
341 * so report that.
342 */
343 if (*cp == '\0') {
344 *sp = cp;
345 return TEOL;
346 }
347
348 /*
349 * If the leading character is a digit, scan
350 * the number and convert it on the fly.
351 * Return TNUMBER when done.
352 */
353 c = (unsigned char)*cp++;
354 if (isdigit(c)) {
355 lexnumber = 0;
356 while (isdigit(c)) {
357 lexnumber = lexnumber * 10 + c - '0';
358 *cp2++ = c;
359 c = (unsigned char)*cp++;
360 }
361 *cp2 = '\0';
362 *sp = --cp;
363 return TNUMBER;
364 }
365
366 /*
367 * Check for single character tokens; return such
368 * if found.
369 */
370 for (lp = &singles[0]; lp->l_char != 0; lp++)
371 if (c == lp->l_char) {
372 lexstring[0] = c;
373 lexstring[1] = '\0';
374 *sp = cp;
375 return lp->l_token;
376 }
377
378 /*
379 * We've got a string! Copy all the characters
380 * of the string into lexstring, until we see
381 * a null, space, or tab.
382 * Respect quoting and quoted pairs.
383 */
384 quotec = 0;
385 while (c != '\0') {
386 if (c == quotec) {
387 quotec = 0;
388 c = *cp++;
389 continue;
390 }
391 if (quotec) {
392 if (c == '\\' && (*cp == quotec || *cp == '\\'))
393 c = *cp++;
394 }
395 else {
396 switch (c) {
397 case '\'':
398 case '"':
399 quotec = c;
400 c = *cp++;
401 continue;
402 case ' ':
403 case '\t':
404 c = '\0'; /* end of token! */
405 continue;
406 default:
407 break;
408 }
409 }
410 if (cp2 - lexstring < STRINGLEN - 1)
411 *cp2++ = c;
412 c = *cp++;
413 }
414 if (quotec && c == 0) {
415 (void)fprintf(stderr, "Missing %c\n", quotec);
416 return TERROR;
417 }
418 *sp = --cp;
419 *cp2 = '\0';
420 return TSTRING;
421 }
422
423 /*
424 * Unscan the named token by pushing it onto the regret stack.
425 */
426 static void
regret(int token)427 regret(int token)
428 {
429 if (++regretp >= REGDEP)
430 errx(EXIT_FAILURE, "Too many regrets");
431 regretstack[regretp] = token;
432 lexstring[sizeof(lexstring) - 1] = '\0';
433 string_stack[regretp] = savestr(lexstring);
434 numberstack[regretp] = lexnumber;
435 }
436
437 /*
438 * Reset all the scanner global variables.
439 */
440 static void
scaninit(void)441 scaninit(void)
442 {
443 regretp = -1;
444 }
445
446 #define DELIM " \t," /* list of string delimiters */
447 static int
is_substr(const char * big,const char * little)448 is_substr(const char *big, const char *little)
449 {
450 const char *cp;
451 if ((cp = strstr(big, little)) == NULL)
452 return 0;
453
454 return strchr(DELIM, cp[strlen(little)]) != 0 &&
455 (cp == big || strchr(DELIM, cp[-1]) != 0);
456 }
457 #undef DELIM
458
459
460 /*
461 * Look for (compiled regex) pattern in a line.
462 * Returns:
463 * 1 if match found.
464 * 0 if no match found.
465 * -1 on error
466 */
467 static int
regexcmp(void * pattern,char * line,size_t len)468 regexcmp(void *pattern, char *line, size_t len)
469 {
470 regmatch_t pmatch[1];
471 regmatch_t *pmp;
472 int eflags;
473 int rval;
474 regex_t *preg;
475
476 preg = pattern;
477
478 if (line == NULL)
479 return 0;
480
481 if (len == 0) {
482 pmp = NULL;
483 eflags = 0;
484 }
485 else {
486 pmatch[0].rm_so = 0;
487 pmatch[0].rm_eo = line[len - 1] == '\n' ? len - 1 : len;
488 pmp = pmatch;
489 eflags = REG_STARTEND;
490 }
491
492 switch ((rval = regexec(preg, line, 0, pmp, eflags))) {
493 case 0:
494 case REG_NOMATCH:
495 return rval == 0;
496
497 default: {
498 char errbuf[LINESIZE];
499 (void)regerror(rval, preg, errbuf, sizeof(errbuf));
500 (void)printf("regexec failed: '%s': %s\n", line, errbuf);
501 return -1;
502 }}
503 }
504
505 /*
506 * Look for (string) pattern in line.
507 * Return 1 if match found.
508 */
509 static int
substrcmp(void * pattern,char * line,size_t len)510 substrcmp(void *pattern, char *line, size_t len)
511 {
512 char *substr;
513 substr = pattern;
514
515 if (line == NULL)
516 return 0;
517
518 if (len) {
519 if (line[len - 1] == '\n') {
520 line[len - 1] = '\0';
521 }
522 else {
523 char *cp;
524 cp = salloc(len + 1);
525 (void)strlcpy(cp, line, len + 1);
526 line = cp;
527 }
528 }
529 return strcasestr(line, substr) != NULL;
530 }
531
532 /*
533 * Look for NULL line. Used to find non-existent fields.
534 * Return 1 if match found.
535 */
536 static int
hasfieldcmp(void * pattern __unused,char * line,size_t len __unused)537 hasfieldcmp(void *pattern __unused, char *line, size_t len __unused)
538 {
539 #ifdef __lint__
540 pattern = pattern;
541 len = len;
542 #endif
543 return line != NULL;
544 }
545
546 static regex_t preg;
547 /*
548 * Determine the compare function and its argument based on the
549 * "regex-search" variable.
550 */
551 static int (*
get_cmpfn(void ** pattern,char * str)552 get_cmpfn(void **pattern, char *str)
553 )(void *, char *, size_t)
554 {
555 char *val;
556 int cflags;
557 int e;
558
559 if (*str == 0) {
560 *pattern = NULL;
561 return hasfieldcmp;
562 }
563
564 if ((val = value(ENAME_REGEX_SEARCH)) != NULL) {
565 cflags = REG_NOSUB;
566 val = skip_WSP(val);
567 if (*val) {
568 if (is_substr(val, "icase"))
569 cflags |= REG_ICASE;
570 if (is_substr(val, "extended"))
571 cflags |= REG_EXTENDED;
572 /*
573 * NOTE: regcomp() will fail if "nospec" and
574 * "extended" are used together.
575 */
576 if (is_substr(val, "nospec"))
577 cflags |= REG_NOSPEC;
578 }
579 if ((e = regcomp(&preg, str, cflags)) != 0) {
580 char errbuf[LINESIZE];
581 (void)regerror(e, &preg, errbuf, sizeof(errbuf));
582 (void)printf("regcomp failed: '%s': %s\n", str, errbuf);
583 return NULL;
584 }
585 *pattern = &preg;
586 return regexcmp;
587 }
588
589 *pattern = str;
590 return substrcmp;
591 }
592
593 /*
594 * Free any memory allocated by get_cmpfn()
595 */
596 static void
free_cmparg(void * pattern)597 free_cmparg(void *pattern)
598 {
599 if (pattern == &preg)
600 regfree(&preg);
601 }
602
603 /*
604 * Check the message body for the pattern.
605 */
606 static int
matchbody(int (* cmpfn)(void *,char *,size_t),void * pattern,struct message * mp,char const * fieldname __unused)607 matchbody(int (*cmpfn)(void *, char *, size_t),
608 void *pattern, struct message *mp, char const *fieldname __unused)
609 {
610 FILE *fp;
611 char *line;
612 size_t len;
613 int gotmatch;
614
615 #ifdef __lint__
616 fieldname = fieldname;
617 #endif
618 /*
619 * Get a temporary file.
620 */
621 {
622 char *tempname;
623 int fd;
624
625 (void)sasprintf(&tempname, "%s/mail.RbXXXXXXXXXX", tmpdir);
626 fp = NULL;
627 if ((fd = mkstemp(tempname)) != -1) {
628 (void)unlink(tempname);
629 if ((fp = Fdopen(fd, "wef+")) == NULL)
630 (void)close(fd);
631 }
632 if (fp == NULL) {
633 warn("%s", tempname);
634 return -1;
635 }
636 }
637
638 /*
639 * Pump the (decoded) message body into the temp file.
640 */
641 {
642 #ifdef MIME_SUPPORT
643 struct mime_info *mip;
644 int retval;
645
646 mip = value(ENAME_MIME_DECODE_MSG) ? mime_decode_open(mp)
647 : NULL;
648
649 retval = mime_sendmessage(mp, fp, ignoreall, NULL, mip);
650 mime_decode_close(mip);
651 if (retval == -1)
652 #else
653 if (sendmessage(mp, fp, ignoreall, NULL, NULL) == -1)
654 #endif
655 {
656 warn("matchbody: mesg=%d", get_msgnum(mp));
657 return -1;
658 }
659 }
660 /*
661 * XXX - should we read the entire body into a buffer so we
662 * can search across lines?
663 */
664 rewind(fp);
665 gotmatch = 0;
666 while ((line = fgetln(fp, &len)) != NULL && len > 0) {
667 gotmatch = cmpfn(pattern, line, len);
668 if (gotmatch)
669 break;
670 }
671 (void)Fclose(fp);
672
673 return gotmatch;
674 }
675
676 /*
677 * Check the "To:", "Cc:", and "Bcc" fields for the pattern.
678 */
679 static int
matchto(int (* cmpfn)(void *,char *,size_t),void * pattern,struct message * mp,char const * fieldname __unused)680 matchto(int (*cmpfn)(void *, char *, size_t),
681 void *pattern, struct message *mp, char const *fieldname __unused)
682 {
683 static const char *to_fields[] = { "to", "cc", "bcc", 0 };
684 const char **to;
685 int gotmatch;
686
687 #ifdef __lint__
688 fieldname = fieldname;
689 #endif
690 gotmatch = 0;
691 for (to = to_fields; *to; to++) {
692 char *field;
693 field = hfield(*to, mp);
694 gotmatch = cmpfn(pattern, field, 0);
695 if (gotmatch)
696 break;
697 }
698 return gotmatch;
699 }
700
701 /*
702 * Check a field for the pattern.
703 */
704 static int
matchfield(int (* cmpfn)(void *,char *,size_t),void * pattern,struct message * mp,char const * fieldname)705 matchfield(int (*cmpfn)(void *, char *, size_t),
706 void *pattern, struct message *mp, char const *fieldname)
707 {
708 char *field;
709
710 #ifdef __lint__
711 fieldname = fieldname;
712 #endif
713 field = hfield(fieldname, mp);
714 return cmpfn(pattern, field, 0);
715 }
716
717 /*
718 * Check the headline for the pattern.
719 */
720 static int
matchfrom(int (* cmpfn)(void *,char *,size_t),void * pattern,struct message * mp,char const * fieldname __unused)721 matchfrom(int (*cmpfn)(void *, char *, size_t),
722 void *pattern, struct message *mp, char const *fieldname __unused)
723 {
724 char headline[LINESIZE];
725 char *field;
726
727 #ifdef __lint__
728 fieldname = fieldname;
729 #endif
730 (void)readline(setinput(mp), headline, (int)sizeof(headline), 0);
731 field = savestr(headline);
732 if (strncmp(field, "From ", 5) != 0)
733 return 1;
734
735 return cmpfn(pattern, field + 5, 0);
736 }
737
738 /*
739 * Check the sender for the pattern.
740 */
741 static int
matchsender(int (* cmpfn)(void *,char *,size_t),void * pattern,struct message * mp,char const * fieldname __unused)742 matchsender(int (*cmpfn)(void *, char *, size_t),
743 void *pattern, struct message *mp, char const *fieldname __unused)
744 {
745 char *field;
746
747 #ifdef __lint__
748 fieldname = fieldname;
749 #endif
750 field = nameof(mp, 0);
751 return cmpfn(pattern, field, 0);
752 }
753
754 /*
755 * Interpret 'str' and check each message (1 thru 'msgCount') for a match.
756 * The 'str' has the format: [/[[x]:]y with the following meanings:
757 *
758 * y pattern 'y' is compared against the senders address.
759 * /y pattern 'y' is compared with the subject field. If 'y' is empty,
760 * the last search 'str' is used.
761 * /:y pattern 'y' is compared with the subject field.
762 * /x:y pattern 'y' is compared with the specified header field 'x' or
763 * the message body if 'x' == "body".
764 *
765 * The last two forms require "searchheaders" to be defined.
766 */
767 static int
match_string(int * markarray,char * str,int msgCount)768 match_string(int *markarray, char *str, int msgCount)
769 {
770 int i;
771 int rval;
772 int (*matchfn)(int (*)(void *, char *, size_t),
773 void *, struct message *, char const *);
774 int (*cmpfn)(void *, char *, size_t);
775 void *cmparg;
776 char const *fieldname;
777
778 if (*str != '/') {
779 matchfn = matchsender;
780 fieldname = NULL;
781 }
782 else {
783 static char lastscan[STRINGLEN];
784 char *cp;
785
786 str++;
787 if (*str == '\0')
788 str = lastscan;
789 else
790 (void)strlcpy(lastscan, str, sizeof(lastscan));
791
792 if (value(ENAME_SEARCHHEADERS) == NULL ||
793 (cp = strchr(str, ':')) == NULL) {
794 matchfn = matchfield;
795 fieldname = "subject";
796 /* str = str; */
797 }
798 else {
799 static const struct matchtbl_s {
800 char const *key;
801 size_t len;
802 char const *fieldname;
803 int (*matchfn)(int (*)(void *, char *, size_t),
804 void *, struct message *, char const *);
805 } matchtbl[] = {
806 #define X(a) a, sizeof(a) - 1
807 #define X_NULL NULL, 0
808 { X(":"), "subject", matchfield },
809 { X("body:"), NULL, matchbody },
810 { X("from:"), NULL, matchfrom },
811 { X("to:"), NULL, matchto },
812 { X_NULL, NULL, matchfield }
813 #undef X_NULL
814 #undef X
815 };
816 const struct matchtbl_s *mtp;
817 size_t len;
818 /*
819 * Check for special cases!
820 * These checks are case sensitive so the true fields
821 * can be grabbed as mentioned in the manpage.
822 */
823 cp++;
824 len = cp - str;
825 for (mtp = matchtbl; mtp->key; mtp++) {
826 if (len == mtp->len &&
827 strncmp(str, mtp->key, len) == 0)
828 break;
829 }
830 matchfn = mtp->matchfn;
831 if (mtp->key)
832 fieldname = mtp->fieldname;
833 else {
834 char *p;
835 p = salloc(len);
836 (void)strlcpy(p, str, len);
837 fieldname = p;
838 }
839 str = cp;
840 }
841 }
842
843 cmpfn = get_cmpfn(&cmparg, str);
844 if (cmpfn == NULL)
845 return -1;
846
847 rval = 0;
848 for (i = 1; i <= msgCount; i++) {
849 struct message *mp;
850 mp = get_message(i);
851 rval = matchfn(cmpfn, cmparg, mp, fieldname);
852 if (rval == -1)
853 break;
854 if (rval)
855 markarray[i - 1] = 1;
856 rval = 0;
857 }
858
859 free_cmparg(cmparg); /* free any memory allocated by get_cmpfn() */
860
861 return rval;
862 }
863
864
865 /*
866 * Return the message number corresponding to the passed meta character.
867 */
868 static int
metamess(int meta,int f)869 metamess(int meta, int f)
870 {
871 int c, m;
872 struct message *mp;
873
874 c = meta;
875 switch (c) {
876 case '^':
877 /*
878 * First 'good' message left.
879 */
880 for (mp = get_message(1); mp; mp = next_message(mp))
881 if ((mp->m_flag & MDELETED) == f)
882 return get_msgnum(mp);
883 (void)printf("No applicable messages\n");
884 return -1;
885
886 case '$':
887 /*
888 * Last 'good message left.
889 */
890 for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp))
891 if ((mp->m_flag & MDELETED) == f)
892 return get_msgnum(mp);
893 (void)printf("No applicable messages\n");
894 return -1;
895
896 case '.':
897 /*
898 * Current message.
899 */
900 if (dot == NULL) {
901 (void)printf("No applicable messages\n");
902 return -1;
903 }
904 m = get_msgnum(dot);
905 if ((dot->m_flag & MDELETED) != f) {
906 (void)printf("%d: Inappropriate message\n", m);
907 return -1;
908 }
909 return m;
910
911 default:
912 (void)printf("Unknown metachar (%c)\n", c);
913 return -1;
914 }
915 }
916
917 /*
918 * Check the passed message number for legality and proper flags.
919 * If f is MDELETED, then either kind will do. Otherwise, the message
920 * has to be undeleted.
921 */
922 static int
check(int mesg,int f)923 check(int mesg, int f)
924 {
925 struct message *mp;
926
927 if ((mp = get_message(mesg)) == NULL) {
928 (void)printf("%d: Invalid message number\n", mesg);
929 return -1;
930 }
931 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
932 (void)printf("%d: Inappropriate message\n", mesg);
933 return -1;
934 }
935 return 0;
936 }
937
938
939 static int
markall_core(int * markarray,char ** bufp,int f,int level)940 markall_core(int *markarray, char **bufp, int f, int level)
941 {
942 enum token_e tok;
943 enum logic_op_e {
944 LOP_AND,
945 LOP_OR,
946 LOP_XOR
947 } logic_op; /* binary logic operation */
948 int logic_invert; /* invert the result */
949 int *tmparray; /* temporary array with result */
950 int msgCount; /* tmparray length and message count */
951 int beg; /* first value of a range */
952 int colmod; /* the colon-modifier for this group */
953 int got_not; /* for syntax checking of '!' */
954 int got_one; /* we have a message spec, valid or not */
955 int got_bin; /* we have a pending binary operation */
956 int i;
957
958 logic_op = LOP_OR;
959 logic_invert = 0;
960 colmod = 0;
961
962 msgCount = get_msgCount();
963 tmparray = csalloc((size_t)msgCount, sizeof(*tmparray));
964
965 beg = 0;
966 got_one = 0;
967 got_not = 0;
968 got_bin = 0;
969
970 while ((tok = scan(bufp)) != TEOL) {
971 if (tok == TERROR)
972 return -1;
973
974 /*
975 * Do some syntax checking.
976 */
977 switch (tok) {
978 case TDASH:
979 case TPLUS:
980 case TDOLLAR:
981 case TUP:
982 case TDOT:
983 case TNUMBER:
984 break;
985
986 case TAND:
987 case TOR:
988 case TXOR:
989 if (!got_one)
990 return syntax_error("missing left operand");
991 /*FALLTHROUGH*/
992 default:
993 if (beg)
994 return syntax_error("end of range missing");
995 break;
996 }
997
998 /*
999 * The main tok switch.
1000 */
1001 switch (tok) {
1002 struct message *mp;
1003
1004 case TERROR: /* trapped above */
1005 case TEOL:
1006 assert(/*CONSTCOND*/0);
1007 break;
1008
1009 case TUP:
1010 if (got_one) { /* a possible logical xor */
1011 enum token_e t;
1012 t = scan(bufp); /* peek ahead */
1013 regret(t);
1014 lexstring[0] = '^'; /* restore lexstring */
1015 lexstring[1] = '\0';
1016 if (t != TDASH && t != TEOL && t != TCLOSE) {
1017 /* convert tok to TXOR and put
1018 * it back on the stack so we
1019 * can handle it consistently */
1020 tok = TXOR;
1021 regret(tok);
1022 continue;
1023 }
1024 }
1025 /* FALLTHROUGH */
1026 case TDOLLAR:
1027 case TDOT:
1028 lexnumber = metamess(lexstring[0], f);
1029 if (lexnumber == -1)
1030 return -1;
1031 /* FALLTHROUGH */
1032 case TNUMBER:
1033 if (check(lexnumber, f))
1034 return -1;
1035 number:
1036 got_one = 1;
1037 if (beg != 0) {
1038 if (lexnumber < beg) {
1039 (void)printf("invalid range: %d-%d\n", beg, lexnumber);
1040 return -1;
1041 }
1042 for (i = beg; i <= lexnumber; i++)
1043 tmparray[i - 1] = 1;
1044
1045 beg = 0;
1046 break;
1047 }
1048 beg = lexnumber; /* start of a range */
1049 tok = scan(bufp);
1050 if (tok == TDASH) {
1051 continue;
1052 }
1053 else {
1054 regret(tok);
1055 tmparray[beg - 1] = 1;
1056 beg = 0;
1057 }
1058 break;
1059
1060 case TDASH:
1061 for (mp = prev_message(dot); mp; mp = prev_message(mp)) {
1062 if ((mp->m_flag & MDELETED) == 0)
1063 break;
1064 }
1065 if (mp == NULL) {
1066 (void)printf("Referencing before 1\n");
1067 return -1;
1068 }
1069 lexnumber = get_msgnum(mp);
1070 goto number;
1071
1072 case TPLUS:
1073 for (mp = next_message(dot); mp; mp = next_message(mp)) {
1074 if ((mp->m_flag & MDELETED) == 0)
1075 break;
1076 }
1077 if (mp == NULL) {
1078 (void)printf("Referencing beyond EOF\n");
1079 return -1;
1080 }
1081 lexnumber = get_msgnum(mp);
1082 goto number;
1083
1084 case TSTRING:
1085 if (lexstring[0] == ':') { /* colon modifier! */
1086 colmod = get_colmod(colmod, lexstring + 1);
1087 if (colmod == -1)
1088 return -1;
1089 continue;
1090 }
1091 got_one = 1;
1092 if (match_string(tmparray, lexstring, msgCount) == -1)
1093 return -1;
1094 break;
1095
1096 case TSTAR:
1097 got_one = 1;
1098 for (i = 1; i <= msgCount; i++)
1099 tmparray[i - 1] = 1;
1100 break;
1101
1102
1103 /**************
1104 * Parentheses.
1105 */
1106 case TOPEN:
1107 if (markall_core(tmparray, bufp, f, level + 1) == -1)
1108 return -1;
1109 break;
1110
1111 case TCLOSE:
1112 if (level == 0)
1113 return syntax_error("extra ')'");
1114 goto done;
1115
1116
1117 /*********************
1118 * Logical operations.
1119 */
1120 case TNOT:
1121 got_not = 1;
1122 logic_invert = ! logic_invert;
1123 continue;
1124
1125 /*
1126 * Binary operations.
1127 */
1128 case TAND:
1129 if (got_not)
1130 return syntax_error("'!' precedes '&'");
1131 got_bin = 1;
1132 logic_op = LOP_AND;
1133 continue;
1134
1135 case TOR:
1136 if (got_not)
1137 return syntax_error("'!' precedes '|'");
1138 got_bin = 1;
1139 logic_op = LOP_OR;
1140 continue;
1141
1142 case TXOR:
1143 if (got_not)
1144 return syntax_error("'!' precedes logical '^'");
1145 got_bin = 1;
1146 logic_op = LOP_XOR;
1147 continue;
1148 }
1149
1150 /*
1151 * Do the logic operations.
1152 */
1153 if (logic_invert)
1154 for (i = 0; i < msgCount; i++)
1155 tmparray[i] = ! tmparray[i];
1156
1157 switch (logic_op) {
1158 case LOP_AND:
1159 for (i = 0; i < msgCount; i++)
1160 markarray[i] &= tmparray[i];
1161 break;
1162
1163 case LOP_OR:
1164 for (i = 0; i < msgCount; i++)
1165 markarray[i] |= tmparray[i];
1166 break;
1167
1168 case LOP_XOR:
1169 for (i = 0; i < msgCount; i++)
1170 markarray[i] ^= tmparray[i];
1171 break;
1172 }
1173
1174 /*
1175 * Clear the temporary array and reset the logic
1176 * operations.
1177 */
1178 for (i = 0; i < msgCount; i++)
1179 tmparray[i] = 0;
1180
1181 logic_op = LOP_OR;
1182 logic_invert = 0;
1183 got_not = 0;
1184 got_bin = 0;
1185 }
1186
1187 if (beg)
1188 return syntax_error("end of range missing");
1189
1190 if (level)
1191 return syntax_error("missing ')'");
1192
1193 done:
1194 if (got_not)
1195 return syntax_error("trailing '!'");
1196
1197 if (got_bin)
1198 return syntax_error("missing right operand");
1199
1200 if (colmod != 0) {
1201 /*
1202 * If we have colon-modifiers but no messages
1203 * specifiec, then assume '*' was given.
1204 */
1205 if (got_one == 0)
1206 for (i = 1; i <= msgCount; i++)
1207 markarray[i - 1] = 1;
1208
1209 for (i = 1; i <= msgCount; i++) {
1210 struct message *mp;
1211 if ((mp = get_message(i)) != NULL &&
1212 ignore_message(mp->m_flag, colmod))
1213 markarray[i - 1] = 0;
1214 }
1215 }
1216 return 0;
1217 }
1218
1219 static int
markall(char buf[],int f)1220 markall(char buf[], int f)
1221 {
1222 int i;
1223 int mc;
1224 int *markarray;
1225 int msgCount;
1226 struct message *mp;
1227
1228 msgCount = get_msgCount();
1229
1230 /*
1231 * Clear all the previous message marks.
1232 */
1233 for (i = 1; i <= msgCount; i++)
1234 if ((mp = get_message(i)) != NULL)
1235 mp->m_flag &= ~MMARK;
1236
1237 buf = skip_WSP(buf);
1238 if (*buf == '\0')
1239 return 0;
1240
1241 scaninit();
1242 markarray = csalloc((size_t)msgCount, sizeof(*markarray));
1243 if (markall_core(markarray, &buf, f, 0) == -1)
1244 return -1;
1245
1246 /*
1247 * Transfer the markarray values to the messages.
1248 */
1249 mc = 0;
1250 for (i = 1; i <= msgCount; i++) {
1251 if (markarray[i - 1] &&
1252 (mp = get_message(i)) != NULL &&
1253 (f == MDELETED || (mp->m_flag & MDELETED) == 0)) {
1254 mp->m_flag |= MMARK;
1255 mc++;
1256 }
1257 }
1258
1259 if (mc == 0) {
1260 (void)printf("No applicable messages.\n");
1261 return -1;
1262 }
1263 return 0;
1264 }
1265
1266 /*
1267 * Convert the user string of message numbers and
1268 * store the numbers into vector.
1269 *
1270 * Returns the count of messages picked up or -1 on error.
1271 */
1272 PUBLIC int
getmsglist(char * buf,int * vector,int flags)1273 getmsglist(char *buf, int *vector, int flags)
1274 {
1275 int *ip;
1276 struct message *mp;
1277
1278 if (get_msgCount() == 0) {
1279 *vector = 0;
1280 return 0;
1281 }
1282 if (markall(buf, flags) < 0)
1283 return -1;
1284 ip = vector;
1285 for (mp = get_message(1); mp; mp = next_message(mp))
1286 if (mp->m_flag & MMARK)
1287 *ip++ = get_msgnum(mp);
1288 *ip = 0;
1289 return (int)(ip - vector);
1290 }
1291
1292 /*
1293 * Find the first message whose flags & m == f and return
1294 * its message number.
1295 */
1296 PUBLIC int
first(int f,int m)1297 first(int f, int m)
1298 {
1299 struct message *mp;
1300
1301 if (get_msgCount() == 0)
1302 return 0;
1303 f &= MDELETED;
1304 m &= MDELETED;
1305 for (mp = dot; mp; mp = next_message(mp))
1306 if ((mp->m_flag & m) == f)
1307 return get_msgnum(mp);
1308 for (mp = prev_message(dot); mp; mp = prev_message(mp))
1309 if ((mp->m_flag & m) == f)
1310 return get_msgnum(mp);
1311 return 0;
1312 }
1313
1314 /*
1315 * Show all headers without paging. (-H flag)
1316 */
1317 __dead
1318 PUBLIC int
show_headers_and_exit(int flags)1319 show_headers_and_exit(int flags)
1320 {
1321 struct message *mp;
1322
1323 /* We are exiting anyway, so use the default signal handler. */
1324 if (signal(SIGINT, SIG_DFL) == SIG_IGN)
1325 (void)signal(SIGINT, SIG_IGN);
1326
1327 flags &= CMMASK;
1328 for (mp = get_message(1); mp; mp = next_message(mp))
1329 if (flags == 0 || !ignore_message(mp->m_flag, flags))
1330 printhead(get_msgnum(mp));
1331
1332 exit(0);
1333 /* NOTREACHED */
1334 }
1335
1336 /*
1337 * A hack so -H can have an optional modifier as -H[:flags].
1338 *
1339 * This depends a bit on the internals of getopt(). In particular,
1340 * for flags expecting an argument, argv[optind-1] must contain the
1341 * optarg and optarg must point to a substring of argv[optind-1] not a
1342 * copy of it.
1343 */
1344 PUBLIC int
get_Hflag(char ** argv)1345 get_Hflag(char **argv)
1346 {
1347 int flags;
1348
1349 flags = ~CMMASK;
1350
1351 if (optarg == NULL) /* We had an error, just get the flags. */
1352 return flags;
1353
1354 if (*optarg != ':' || optarg == argv[optind - 1]) {
1355 optind--;
1356 optreset = 1;
1357 if (optarg != argv[optind]) {
1358 static char temparg[LINESIZE];
1359 size_t optlen;
1360 size_t arglen;
1361 char *p;
1362
1363 optlen = strlen(optarg);
1364 arglen = strlen(argv[optind]);
1365 p = argv[optind] + arglen - optlen;
1366 optlen = MIN(optlen, sizeof(temparg) - 1);
1367 temparg[0] = '-';
1368 (void)memmove(temparg + 1, p, optlen + 1);
1369 argv[optind] = temparg;
1370 }
1371 }
1372 else {
1373 flags = get_colmod(flags, optarg + 1);
1374 }
1375 return flags;
1376 }
1377