list.c revision 1.22
1/*	$NetBSD: list.c,v 1.22 2007/10/30 02:28:31 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
35static char sccsid[] = "@(#)list.c	8.4 (Berkeley) 5/1/95";
36#else
37__RCSID("$NetBSD: list.c,v 1.22 2007/10/30 02:28:31 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 */
61enum 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
83static int	lexnumber;		/* Number of TNUMBER from scan() */
84static char	lexstring[STRINGLEN];	/* String from TSTRING, scan() */
85static int	regretp;		/* Pointer to TOS of regret tokens */
86static int	regretstack[REGDEP];	/* Stack of regretted tokens */
87static char	*string_stack[REGDEP];	/* Stack of regretted strings */
88static int	numberstack[REGDEP];	/* Stack of regretted numbers */
89
90/*
91 * Scan out the list of string arguments, shell style
92 * for a RAWLIST.
93 */
94PUBLIC int
95getrawlist(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 (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
211static 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
231static	int	lastcolmod;
232
233static int
234ignore_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 */
253static int
254evalcol(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
266static int
267get_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
287static int
288syntax_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 */
300static enum token_e
301scan(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 */
426static void
427regret(int token)
428{
429	if (++regretp >= REGDEP)
430		errx(1, "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 */
440static void
441scaninit(void)
442{
443	regretp = -1;
444}
445
446#define DELIM " \t,"  /* list of string delimiters */
447static int
448is_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 */
467static int
468regexcmp(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 */
509static int
510substrcmp(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 */
536static int
537hasfieldcmp(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
546static regex_t preg;
547/*
548 * Determine the compare function and its argument based on the
549 * "regex-search" variable.
550 */
551static int (*
552get_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 */
596static void
597free_cmparg(void *pattern)
598{
599	if (pattern == &preg)
600		regfree(&preg);
601}
602
603/*
604 * Check the message body for the pattern.
605 */
606static int
607matchbody(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, "w+")) == 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 */
679static int
680matchto(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 */
704static int
705matchfield(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 */
720static int
721matchfrom(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)mail_readline(setinput(mp), headline, sizeof(headline));
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 */
741static int
742matchsender(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 */
767static int
768match_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 */
868static int
869metamess(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 */
922static int
923check(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
939static int
940markall_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;	/* temporarly 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
1219static int
1220markall(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 */
1272PUBLIC int
1273getmsglist(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 ip - vector;
1290}
1291
1292/*
1293 * Find the first message whose flags & m == f  and return
1294 * its message number.
1295 */
1296PUBLIC int
1297first(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__attribute__((__noreturn__))
1318PUBLIC int
1319show_headers_and_exit(int flags)
1320{
1321	struct message *mp;
1322
1323	flags &= CMMASK;
1324	for (mp = get_message(1); mp; mp = next_message(mp))
1325		if (flags == 0 || !ignore_message(mp->m_flag, flags))
1326			printhead(get_msgnum(mp));
1327
1328	exit(0);
1329	/* NOTREACHED */
1330}
1331
1332/*
1333 * A hack so -H can have an optional modifier as -H[:flags].
1334 *
1335 * This depends a bit on the internals of getopt().  In particular,
1336 * for flags expecting an argument, argv[optind-1] must contain the
1337 * optarg and optarg must point to a substring of argv[optind-1] not a
1338 * copy of it.
1339 */
1340PUBLIC int
1341get_Hflag(char **argv)
1342{
1343	int flags;
1344
1345	flags = ~CMMASK;
1346
1347	if (optarg == NULL)  /* We had an error, just get the flags. */
1348		return flags;
1349
1350	if (*optarg != ':' || optarg == argv[optind - 1]) {
1351		optind--;
1352		optreset = 1;
1353		if (optarg != argv[optind]) {
1354			static char temparg[LINESIZE];
1355			size_t optlen;
1356			size_t arglen;
1357			char *p;
1358
1359			optlen = strlen(optarg);
1360			arglen = strlen(argv[optind]);
1361			p = argv[optind] + arglen - optlen;
1362			optlen = MIN(optlen, sizeof(temparg) - 1);
1363			temparg[0] = '-';
1364			(void)memmove(temparg + 1, p, optlen + 1);
1365			argv[optind] = temparg;
1366		}
1367	}
1368	else {
1369		flags = get_colmod(flags, optarg + 1);
1370	}
1371	return flags;
1372}
1373