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