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