Home | History | Annotate | Line # | Download | only in mail
      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
     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
    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
    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
    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
    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
    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
    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
    441 scaninit(void)
    442 {
    443 	regretp = -1;
    444 }
    445 
    446 #define DELIM " \t,"  /* list of string delimiters */
    447 static int
    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
    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
    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
    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 (*
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
   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
   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
   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
   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
   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