Home | History | Annotate | Line # | Download | only in mail
list.c revision 1.19
      1 /*	$NetBSD: list.c,v 1.19 2006/12/25 18:43:29 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.19 2006/12/25 18:43:29 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 			 * Support "nospec", but don't document it as
    556 			 * it may not be portable.
    557 			 * NOTE: regcomp() will fail if "nospec" and
    558 			 * "extended" are used together.
    559 			 */
    560 			if (is_substr(val, "nospec"))
    561 				cflags |= REG_NOSPEC;
    562 		}
    563 		if ((e = regcomp(&preg, str, cflags)) != 0) {
    564 			char errbuf[LINESIZE];
    565 			(void)regerror(e, &preg, errbuf, sizeof(errbuf));
    566 			(void)printf("regcomp failed: '%s': %s\n", str, errbuf);
    567 			return NULL;
    568 		}
    569 		*pattern = &preg;
    570 		return regexcmp;
    571 	}
    572 
    573 	*pattern = str;
    574 	return substrcmp;
    575 }
    576 
    577 /*
    578  * Free any memory allocated by get_cmpfn()
    579  */
    580 static void
    581 free_cmparg(void *pattern)
    582 {
    583 	if (pattern == &preg)
    584 		regfree(&preg);
    585 }
    586 
    587 /*
    588  * Check the message body for the pattern.
    589  */
    590 static int
    591 matchbody(int (*cmpfn)(void *, char *, size_t),
    592     void *pattern, struct message *mp, char const *fieldname __unused)
    593 {
    594 	FILE *fp;
    595 	char *line;
    596 	size_t len;
    597 	int gotmatch;
    598 
    599 #ifdef __lint__
    600 	fieldname = fieldname;
    601 #endif
    602 	/*
    603 	 * Get a temporary file.
    604 	 */
    605 	{
    606 		char *tempname;
    607 		int fd;
    608 
    609 		(void)sasprintf(&tempname, "%s/mail.RbXXXXXXXXXX", tmpdir);
    610 		fp = NULL;
    611 		if ((fd = mkstemp(tempname)) != -1) {
    612 			(void)unlink(tempname);
    613 			if ((fp = Fdopen(fd, "w+")) == NULL)
    614 				(void)close(fd);
    615 		}
    616 		if (fp == NULL) {
    617 			warn("%s", tempname);
    618 			return -1;
    619 		}
    620 	}
    621 
    622 	/*
    623 	 * Pump the (decoded) message body into the temp file.
    624 	 */
    625 	{
    626 #ifdef MIME_SUPPORT
    627 		struct mime_info *mip;
    628 		int retval;
    629 
    630 		mip = value(ENAME_MIME_DECODE_MSG) ? mime_decode_open(mp)
    631 		    : NULL;
    632 
    633 		retval = mime_sendmessage(mp, fp, ignoreall, NULL, mip);
    634 		mime_decode_close(mip);
    635 		if (retval == -1)
    636 #else
    637 		if (sendmessage(mp, fp, ignoreall, NULL, NULL) == -1)
    638 #endif
    639 		{
    640 			warn("matchbody: mesg=%d", get_msgnum(mp));
    641 			return -1;
    642 		}
    643 	}
    644 	/*
    645 	 * XXX - should we read the entire body into a buffer so we
    646 	 * can search across lines?
    647 	 */
    648 	rewind(fp);
    649 	gotmatch = 0;
    650 	while ((line = fgetln(fp, &len)) != NULL && len > 0) {
    651 		gotmatch = cmpfn(pattern, line, len);
    652 		if (gotmatch)
    653 			break;
    654 	}
    655 	(void)Fclose(fp);
    656 
    657 	return gotmatch;
    658 }
    659 
    660 /*
    661  * Check the "To:", "Cc:", and "Bcc" fields for the pattern.
    662  */
    663 static int
    664 matchto(int (*cmpfn)(void *, char *, size_t),
    665     void *pattern, struct message *mp, char const *fieldname __unused)
    666 {
    667 	static const char *to_fields[] = { "to", "cc", "bcc", 0 };
    668 	const char **to;
    669 	int gotmatch;
    670 
    671 #ifdef __lint__
    672 	fieldname = fieldname;
    673 #endif
    674 	gotmatch = 0;
    675 	for (to = to_fields; *to; to++) {
    676 		char *field;
    677 		field = hfield(*to, mp);
    678 		gotmatch = cmpfn(pattern, field, 0);
    679 		if (gotmatch)
    680 			break;
    681 	}
    682 	return gotmatch;
    683 }
    684 
    685 /*
    686  * Check a field for the pattern.
    687  */
    688 static int
    689 matchfield(int (*cmpfn)(void *, char *, size_t),
    690     void *pattern, struct message *mp, char const *fieldname)
    691 {
    692 	char *field;
    693 
    694 #ifdef __lint__
    695 	fieldname = fieldname;
    696 #endif
    697 	field = hfield(fieldname, mp);
    698 	return cmpfn(pattern, field, 0);
    699 }
    700 
    701 /*
    702  * Check the headline for the pattern.
    703  */
    704 static int
    705 matchfrom(int (*cmpfn)(void *, char *, size_t),
    706     void *pattern, struct message *mp, char const *fieldname __unused)
    707 {
    708 	char headline[LINESIZE];
    709 	char *field;
    710 
    711 #ifdef __lint__
    712 	fieldname = fieldname;
    713 #endif
    714 	(void)mail_readline(setinput(mp), headline, sizeof(headline));
    715 	field = savestr(headline);
    716 	if (strncmp(field, "From ", 5) != 0)
    717 		return 1;
    718 
    719 	return cmpfn(pattern, field + 5, 0);
    720 }
    721 
    722 /*
    723  * Check the sender for the pattern.
    724  */
    725 static int
    726 matchsender(int (*cmpfn)(void *, char *, size_t),
    727     void *pattern, struct message *mp, char const *fieldname __unused)
    728 {
    729 	char *field;
    730 
    731 #ifdef __lint__
    732 	fieldname = fieldname;
    733 #endif
    734 	field = nameof(mp, 0);
    735 	return cmpfn(pattern, field, 0);
    736 }
    737 
    738 /*
    739  * Interpret 'str' and check each message (1 thru 'msgCount') for a match.
    740  * The 'str' has the format: [/[[x]:]y with the following meanings:
    741  *
    742  * y	 pattern 'y' is compared against the senders address.
    743  * /y	 pattern 'y' is compared with the subject field.  If 'y' is empty,
    744  *       the last search 'str' is used.
    745  * /:y	 pattern 'y' is compared with the subject field.
    746  * /x:y	 pattern 'y' is compared with the specified header field 'x' or
    747  *       the message body if 'x' == "body".
    748  *
    749  * The last two forms require "searchheaders" to be defined.
    750  */
    751 static int
    752 match_string(int *markarray, char *str, int msgCount)
    753 {
    754 	int i;
    755 	int rval;
    756 	int (*matchfn)(int (*)(void *, char *, size_t),
    757 	    void *, struct message *, char const *);
    758 	int (*cmpfn)(void *, char *, size_t);
    759 	void *cmparg;
    760 	char const *fieldname;
    761 
    762 	if (*str != '/') {
    763 		matchfn = matchsender;
    764 		fieldname = NULL;
    765 	}
    766 	else {
    767 		static char lastscan[STRINGLEN];
    768 		char *cp;
    769 
    770 		str++;
    771 		if (*str == '\0')
    772 			str = lastscan;
    773 		else
    774 			(void)strlcpy(lastscan, str, sizeof(lastscan));
    775 
    776 		if (value(ENAME_SEARCHHEADERS) == NULL ||
    777 		    (cp = strchr(str, ':')) == NULL) {
    778 			matchfn = matchfield;
    779 			fieldname = "subject";
    780 		/*	str = str; */
    781 		}
    782 		else {
    783 			static const struct matchtbl_s {
    784 				char const *key;
    785 				size_t len;
    786 				char const *fieldname;
    787 				int (*matchfn)(int (*)(void *, char *, size_t),
    788 				    void *, struct message *, char const *);
    789 			} matchtbl[] = {
    790 				#define	X(a)	a,	sizeof(a) - 1
    791 				#define	X_NULL	NULL,	0
    792 				{ X(":"),	"subject",	matchfield },
    793 				{ X("body:"),	NULL,		matchbody },
    794 				{ X("from:"),	NULL,		matchfrom },
    795 				{ X("to:"),	NULL,		matchto },
    796 				{ X_NULL,	NULL,		matchfield }
    797 				#undef X_NULL
    798 				#undef X
    799 			};
    800 			const struct matchtbl_s *mtp;
    801 			size_t len;
    802 			/*
    803 			 * Check for special cases!
    804 			 * These checks are case sensitive so the true fields
    805 			 * can be grabbed as mentioned in the manpage.
    806 			 */
    807 			cp++;
    808 			len = cp - str;
    809 			for (mtp = matchtbl; mtp->key; mtp++) {
    810 				if (len == mtp->len &&
    811 				    strncmp(str, mtp->key, len) == 0)
    812 					break;
    813 			}
    814 			matchfn = mtp->matchfn;
    815 			if (mtp->key)
    816 				fieldname = mtp->fieldname;
    817 			else {
    818 				char *p;
    819 				p = salloc(len);
    820 				(void)strlcpy(p, str, len);
    821 				fieldname = p;
    822 			}
    823 			str = cp;
    824 		}
    825 	}
    826 
    827 	if (*str == '\0') /* an empty string matches nothing instead of everything */
    828 		return 0;
    829 
    830 	cmpfn = get_cmpfn(&cmparg, str);
    831 	if (cmpfn == NULL)
    832 		return -1;
    833 
    834 	rval = 0;
    835 	for (i = 1; i <= msgCount; i++) {
    836 		struct message *mp;
    837 		mp = get_message(i);
    838 		rval = matchfn(cmpfn, cmparg, mp, fieldname);
    839 		if (rval == -1)
    840 			break;
    841 		if (rval)
    842 			markarray[i - 1] = 1;
    843 		rval = 0;
    844 	}
    845 
    846 	free_cmparg(cmparg);	/* free any memory allocated by get_cmpfn() */
    847 
    848 	return rval;
    849 }
    850 
    851 
    852 /*
    853  * Return the message number corresponding to the passed meta character.
    854  */
    855 static int
    856 metamess(int meta, int f)
    857 {
    858 	int c, m;
    859 	struct message *mp;
    860 
    861 	c = meta;
    862 	switch (c) {
    863 	case '^':
    864 		/*
    865 		 * First 'good' message left.
    866 		 */
    867 		for (mp = get_message(1); mp; mp = next_message(mp))
    868 			if ((mp->m_flag & MDELETED) == f)
    869 				return get_msgnum(mp);
    870 		(void)printf("No applicable messages\n");
    871 		return -1;
    872 
    873 	case '$':
    874 		/*
    875 		 * Last 'good message left.
    876 		 */
    877 		for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp))
    878 			if ((mp->m_flag & MDELETED) == f)
    879 				return get_msgnum(mp);
    880 		(void)printf("No applicable messages\n");
    881 		return -1;
    882 
    883 	case '.':
    884 		/*
    885 		 * Current message.
    886 		 */
    887 		if (dot == NULL) {
    888 			(void)printf("No applicable messages\n");
    889 			return -1;
    890 		}
    891 		m = get_msgnum(dot);
    892 		if ((dot->m_flag & MDELETED) != f) {
    893 			(void)printf("%d: Inappropriate message\n", m);
    894 			return -1;
    895 		}
    896 		return m;
    897 
    898 	default:
    899 		(void)printf("Unknown metachar (%c)\n", c);
    900 		return -1;
    901 	}
    902 }
    903 
    904 /*
    905  * Check the passed message number for legality and proper flags.
    906  * If f is MDELETED, then either kind will do.  Otherwise, the message
    907  * has to be undeleted.
    908  */
    909 static int
    910 check(int mesg, int f)
    911 {
    912 	struct message *mp;
    913 
    914 	if ((mp = get_message(mesg)) == NULL) {
    915 		(void)printf("%d: Invalid message number\n", mesg);
    916 		return -1;
    917 	}
    918 	if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
    919 		(void)printf("%d: Inappropriate message\n", mesg);
    920 		return -1;
    921 	}
    922 	return 0;
    923 }
    924 
    925 
    926 static int
    927 markall_core(int *markarray, char **bufp, int f, int level)
    928 {
    929 	enum token_e tok;
    930 	enum logic_op_e {
    931 		LOP_AND,
    932 		LOP_OR,
    933 		LOP_XOR
    934 	} logic_op;		/* binary logic operation */
    935 	int logic_invert;	/* invert the result */
    936 	int *tmparray;	/* temporarly array with result */
    937 	int msgCount;	/* tmparray length and message count */
    938 	int beg;	/* first value of a range */
    939 	int colmod;	/* the colon-modifier for this group */
    940 	int got_not;	/* for syntax checking of '!' */
    941 	int got_one;	/* we have a message spec, valid or not */
    942 	int got_bin;	/* we have a pending binary operation */
    943 	int i;
    944 
    945 	logic_op = LOP_OR;
    946 	logic_invert = 0;
    947 	colmod = 0;
    948 
    949 	msgCount = get_msgCount();
    950 	tmparray = csalloc((size_t)msgCount, sizeof(*tmparray));
    951 
    952 	beg = 0;
    953 	got_one = 0;
    954 	got_not = 0;
    955 	got_bin = 0;
    956 
    957 	while ((tok = scan(bufp)) != TEOL) {
    958 		if (tok == TERROR)
    959 			return -1;
    960 
    961 		/*
    962 		 * Do some syntax checking.
    963 		 */
    964 		switch (tok) {
    965 		case TDASH:
    966 		case TPLUS:
    967 		case TDOLLAR:
    968 		case TUP:
    969 		case TDOT:
    970 		case TNUMBER:
    971 			break;
    972 
    973 		case TAND:
    974 		case TOR:
    975 		case TXOR:
    976 			if (!got_one)
    977 				return syntax_error("missing left operand");
    978 			/*FALLTHROUGH*/
    979 		default:
    980 			if (beg)
    981 				return syntax_error("end of range missing");
    982 			break;
    983 		}
    984 
    985 		/*
    986 		 * The main tok switch.
    987 		 */
    988 		switch (tok) {
    989 			struct message *mp;
    990 
    991 		case TERROR:	/* trapped above */
    992 		case TEOL:
    993 			assert(/*CONSTCOND*/0);
    994 			break;
    995 
    996 		case TUP:
    997 			if (got_one) {	/* a possible logical xor */
    998 				enum token_e t;
    999 				t = scan(bufp); /* peek ahead */
   1000 				regret(t);
   1001 				lexstring[0] = '^';  /* restore lexstring */
   1002 				lexstring[1] = '\0';
   1003 				if (t != TDASH && t != TEOL && t != TCLOSE) {
   1004 					/* convert tok to TXOR and put
   1005 					 * it back on the stack so we
   1006 					 * can handle it consistently */
   1007 					tok = TXOR;
   1008 					regret(tok);
   1009 					continue;
   1010 				}
   1011 			}
   1012 			/* FALLTHROUGH */
   1013 		case TDOLLAR:
   1014 		case TDOT:
   1015 			lexnumber = metamess(lexstring[0], f);
   1016 			if (lexnumber == -1)
   1017 				return -1;
   1018 			/* FALLTHROUGH */
   1019 		case TNUMBER:
   1020 			if (check(lexnumber, f))
   1021 				return -1;
   1022 	number:
   1023 			got_one = 1;
   1024 			if (beg != 0) {
   1025 				if (lexnumber < beg) {
   1026 					(void)printf("invalid range: %d-%d\n", beg, lexnumber);
   1027 					return -1;
   1028 				}
   1029 				for (i = beg; i <= lexnumber; i++)
   1030 					tmparray[i - 1] = 1;
   1031 
   1032 				beg = 0;
   1033 				break;
   1034 			}
   1035 			beg = lexnumber;	/* start of a range */
   1036 			tok = scan(bufp);
   1037 			if (tok == TDASH) {
   1038 				continue;
   1039 			}
   1040 			else {
   1041 				regret(tok);
   1042 				tmparray[beg - 1] = 1;
   1043 				beg = 0;
   1044 			}
   1045 			break;
   1046 
   1047 		case TDASH:
   1048 			for (mp = prev_message(dot); mp; mp = prev_message(mp)) {
   1049 				if ((mp->m_flag & MDELETED) == 0)
   1050 					break;
   1051 			}
   1052 			if (mp == NULL) {
   1053 				(void)printf("Referencing before 1\n");
   1054 				return -1;
   1055 			}
   1056 			lexnumber = get_msgnum(mp);
   1057 			goto number;
   1058 
   1059 		case TPLUS:
   1060 			for (mp = next_message(dot); mp; mp = next_message(mp)) {
   1061 				if ((mp->m_flag & MDELETED) == 0)
   1062 					break;
   1063 			}
   1064 			if (mp == NULL) {
   1065 				(void)printf("Referencing beyond EOF\n");
   1066 				return -1;
   1067 			}
   1068 			lexnumber = get_msgnum(mp);
   1069 			goto number;
   1070 
   1071 		case TSTRING:
   1072 			if (lexstring[0] == ':') { /* colon modifier! */
   1073 				colmod = get_colmod(colmod, lexstring + 1);
   1074 				if (colmod == -1)
   1075 					return -1;
   1076 				continue;
   1077 			}
   1078 			got_one = 1;
   1079 			if (match_string(tmparray, lexstring, msgCount) == -1)
   1080 				return -1;
   1081 			break;
   1082 
   1083 		case TSTAR:
   1084 			got_one = 1;
   1085 			for (i = 1; i <= msgCount; i++)
   1086 				tmparray[i - 1] = 1;
   1087 			break;
   1088 
   1089 
   1090 			/**************
   1091 			 * Parentheses.
   1092 			 */
   1093 		case TOPEN:
   1094 			if (markall_core(tmparray, bufp, f, level + 1) == -1)
   1095 				return -1;
   1096 			break;
   1097 
   1098 		case TCLOSE:
   1099 			if (level == 0)
   1100 				return syntax_error("extra ')'");
   1101 			goto done;
   1102 
   1103 
   1104 			/*********************
   1105 			 * Logical operations.
   1106 			 */
   1107 		case TNOT:
   1108 			got_not = 1;
   1109 			logic_invert = ! logic_invert;
   1110 			continue;
   1111 
   1112 			/*
   1113 			 * Binary operations.
   1114 			 */
   1115 		case TAND:
   1116 			if (got_not)
   1117 				return syntax_error("'!' precedes '&'");
   1118 			got_bin = 1;
   1119 			logic_op = LOP_AND;
   1120 			continue;
   1121 
   1122 		case TOR:
   1123 			if (got_not)
   1124 				return syntax_error("'!' precedes '|'");
   1125 			got_bin = 1;
   1126 			logic_op = LOP_OR;
   1127 			continue;
   1128 
   1129 		case TXOR:
   1130 			if (got_not)
   1131 				return syntax_error("'!' precedes logical '^'");
   1132 			got_bin = 1;
   1133 			logic_op = LOP_XOR;
   1134 			continue;
   1135 		}
   1136 
   1137 		/*
   1138 		 * Do the logic operations.
   1139 		 */
   1140 		if (logic_invert)
   1141 			for (i = 0; i < msgCount; i++)
   1142 				tmparray[i] = ! tmparray[i];
   1143 
   1144 		switch (logic_op) {
   1145 		case LOP_AND:
   1146 			for (i = 0; i < msgCount; i++)
   1147 				markarray[i] &= tmparray[i];
   1148 			break;
   1149 
   1150 		case LOP_OR:
   1151 			for (i = 0; i < msgCount; i++)
   1152 				markarray[i] |= tmparray[i];
   1153 			break;
   1154 
   1155 		case LOP_XOR:
   1156 			for (i = 0; i < msgCount; i++)
   1157 				markarray[i] ^= tmparray[i];
   1158 			break;
   1159 		}
   1160 
   1161 		/*
   1162 		 * Clear the temporary array and reset the logic
   1163 		 * operations.
   1164 		 */
   1165 		for (i = 0; i < msgCount; i++)
   1166 			tmparray[i] = 0;
   1167 
   1168 		logic_op = LOP_OR;
   1169 		logic_invert = 0;
   1170 		got_not = 0;
   1171 		got_bin = 0;
   1172 	}
   1173 
   1174 	if (beg)
   1175 		return syntax_error("end of range missing");
   1176 
   1177 	if (level)
   1178 		return syntax_error("missing ')'");
   1179 
   1180  done:
   1181 	if (got_not)
   1182 		return syntax_error("trailing '!'");
   1183 
   1184 	if (got_bin)
   1185 		return syntax_error("missing right operand");
   1186 
   1187 	if (colmod != 0) {
   1188 		/*
   1189 		 * If we have colon-modifiers but no messages
   1190 		 * specifiec, then assume '*' was given.
   1191 		 */
   1192 		if (got_one == 0)
   1193 			for (i = 1; i <= msgCount; i++)
   1194 				markarray[i - 1] = 1;
   1195 
   1196 		for (i = 1; i <= msgCount; i++) {
   1197 			struct message *mp;
   1198 			if ((mp = get_message(i)) != NULL &&
   1199 			    ignore_message(mp->m_flag, colmod))
   1200 				markarray[i - 1] = 0;
   1201 		}
   1202 	}
   1203 	return 0;
   1204 }
   1205 
   1206 static int
   1207 markall(char buf[], int f)
   1208 {
   1209 	int i;
   1210 	int mc;
   1211 	int *markarray;
   1212 	int msgCount;
   1213 	struct message *mp;
   1214 
   1215 	msgCount = get_msgCount();
   1216 
   1217 	/*
   1218 	 * Clear all the previous message marks.
   1219 	 */
   1220 	for (i = 1; i <= msgCount; i++)
   1221 		if ((mp = get_message(i)) != NULL)
   1222 			mp->m_flag &= ~MMARK;
   1223 
   1224 	buf = skip_blank(buf);
   1225 	if (*buf == '\0')
   1226 		return 0;
   1227 
   1228 	scaninit();
   1229 	markarray = csalloc((size_t)msgCount, sizeof(*markarray));
   1230 	if (markall_core(markarray, &buf, f, 0) == -1)
   1231 		return -1;
   1232 
   1233 	/*
   1234 	 * Transfer the markarray values to the messages.
   1235 	 */
   1236 	mc = 0;
   1237 	for (i = 1; i <= msgCount; i++) {
   1238 		if (markarray[i - 1] &&
   1239 		    (mp = get_message(i)) != NULL &&
   1240 		    (f == MDELETED || (mp->m_flag & MDELETED) == 0)) {
   1241 			mp->m_flag |= MMARK;
   1242 			mc++;
   1243 		}
   1244 	}
   1245 
   1246 	if (mc == 0) {
   1247 		(void)printf("No applicable messages.\n");
   1248 		return -1;
   1249 	}
   1250 	return 0;
   1251 }
   1252 
   1253 /*
   1254  * Convert the user string of message numbers and
   1255  * store the numbers into vector.
   1256  *
   1257  * Returns the count of messages picked up or -1 on error.
   1258  */
   1259 PUBLIC int
   1260 getmsglist(char *buf, int *vector, int flags)
   1261 {
   1262 	int *ip;
   1263 	struct message *mp;
   1264 
   1265 	if (get_msgCount() == 0) {
   1266 		*vector = 0;
   1267 		return 0;
   1268 	}
   1269 	if (markall(buf, flags) < 0)
   1270 		return -1;
   1271 	ip = vector;
   1272 	for (mp = get_message(1); mp; mp = next_message(mp))
   1273 		if (mp->m_flag & MMARK)
   1274 			*ip++ = get_msgnum(mp);
   1275 	*ip = 0;
   1276 	return ip - vector;
   1277 }
   1278 
   1279 /*
   1280  * Find the first message whose flags & m == f  and return
   1281  * its message number.
   1282  */
   1283 PUBLIC int
   1284 first(int f, int m)
   1285 {
   1286 	struct message *mp;
   1287 
   1288 	if (get_msgCount() == 0)
   1289 		return 0;
   1290 	f &= MDELETED;
   1291 	m &= MDELETED;
   1292 	for (mp = dot; mp; mp = next_message(mp))
   1293 		if ((mp->m_flag & m) == f)
   1294 			return get_msgnum(mp);
   1295 	for (mp = prev_message(dot); mp; mp = prev_message(mp))
   1296 		if ((mp->m_flag & m) == f)
   1297 			return get_msgnum(mp);
   1298 	return 0;
   1299 }
   1300 
   1301 /*
   1302  * Show all headers without paging.  (-H flag)
   1303  */
   1304 __attribute__((__noreturn__))
   1305 PUBLIC int
   1306 show_headers_and_exit(int flags)
   1307 {
   1308 	struct message *mp;
   1309 
   1310 	flags &= CMMASK;
   1311 	for (mp = get_message(1); mp; mp = next_message(mp))
   1312 		if (flags == 0 || !ignore_message(mp->m_flag, flags))
   1313 			printhead(get_msgnum(mp));
   1314 
   1315 	exit(0);
   1316 	/* NOTREACHED */
   1317 }
   1318 
   1319 /*
   1320  * A hack so -H can have an optional modifier as -H[:flags].
   1321  *
   1322  * This depends a bit on the internals of getopt().  In particular,
   1323  * for flags expecting an argument, argv[optind-1] must contain the
   1324  * optarg and optarg must point to a substring of argv[optind-1] not a
   1325  * copy of it.
   1326  */
   1327 PUBLIC int
   1328 get_Hflag(char **argv)
   1329 {
   1330 	int flags;
   1331 
   1332 	flags = ~CMMASK;
   1333 
   1334 	if (optarg == NULL)  /* We had an error, just get the flags. */
   1335 		return flags;
   1336 
   1337 	if (*optarg != ':' || optarg == argv[optind - 1]) {
   1338 		optind--;
   1339 		optreset = 1;
   1340 		if (optarg != argv[optind]) {
   1341 			static char temparg[LINESIZE];
   1342 			size_t optlen;
   1343 			size_t arglen;
   1344 			char *p;
   1345 
   1346 			optlen = strlen(optarg);
   1347 			arglen = strlen(argv[optind]);
   1348 			p = argv[optind] + arglen - optlen;
   1349 			optlen = MIN(optlen, sizeof(temparg) - 1);
   1350 			temparg[0] = '-';
   1351 			(void)memmove(temparg + 1, p, optlen + 1);
   1352 			argv[optind] = temparg;
   1353 		}
   1354 	}
   1355 	else {
   1356 		flags = get_colmod(flags, optarg + 1);
   1357 	}
   1358 	return flags;
   1359 }
   1360