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
35static 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 */
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		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
212static 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
232static	int	lastcolmod;
233
234static int
235ignore_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 */
254static int
255evalcol(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
267static int
268get_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
288static int
289syntax_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 */
301static enum token_e
302scan(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 */
427static void
428regret(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 */
441static void
442scaninit(void)
443{
444	regretp = -1;
445}
446
447#define DELIM " \t,"  /* list of string delimiters */
448static int
449is_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 */
468static int
469regexcmp(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 */
510static int
511substrcmp(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
533static regex_t preg;
534/*
535 * Determine the compare function and its argument based on the
536 * "regex-search" variable.
537 */
538static int (*
539get_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 */
580static void
581free_cmparg(void *pattern)
582{
583	if (pattern == &preg)
584		regfree(&preg);
585}
586
587/*
588 * Check the message body for the pattern.
589 */
590static int
591matchbody(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 */
663static int
664matchto(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 */
688static int
689matchfield(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 */
704static int
705matchfrom(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 */
725static int
726matchsender(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 */
751static int
752match_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 */
855static int
856metamess(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 */
909static int
910check(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
926static int
927markall_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
1206static int
1207markall(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 */
1259PUBLIC int
1260getmsglist(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 */
1283PUBLIC int
1284first(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__))
1305PUBLIC int
1306show_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 */
1327PUBLIC int
1328get_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