1/*
2 * Copyright (c) 1980, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#ifndef lint
31#if 0
32static char sccsid[] = "@(#)list.c	8.4 (Berkeley) 5/1/95";
33#endif
34#endif /* not lint */
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD$");
37
38#include "rcv.h"
39#include <ctype.h>
40#include "extern.h"
41
42/*
43 * Mail -- a mail program
44 *
45 * Message list handling.
46 */
47
48/*
49 * Convert the user string of message numbers and
50 * store the numbers into vector.
51 *
52 * Returns the count of messages picked up or -1 on error.
53 */
54int
55getmsglist(char *buf, int *vector, int flags)
56{
57	int *ip;
58	struct message *mp;
59
60	if (msgCount == 0) {
61		*vector = 0;
62		return (0);
63	}
64	if (markall(buf, flags) < 0)
65		return (-1);
66	ip = vector;
67	for (mp = &message[0]; mp < &message[msgCount]; mp++)
68		if (mp->m_flag & MMARK)
69			*ip++ = mp - &message[0] + 1;
70	*ip = 0;
71	return (ip - vector);
72}
73
74/*
75 * Mark all messages that the user wanted from the command
76 * line in the message structure.  Return 0 on success, -1
77 * on error.
78 */
79
80/*
81 * Bit values for colon modifiers.
82 */
83
84#define	CMNEW		01		/* New messages */
85#define	CMOLD		02		/* Old messages */
86#define	CMUNREAD	04		/* Unread messages */
87#define	CMDELETED	010		/* Deleted messages */
88#define	CMREAD		020		/* Read messages */
89
90/*
91 * The following table describes the letters which can follow
92 * the colon and gives the corresponding modifier bit.
93 */
94
95static struct coltab {
96	char	co_char;		/* What to find past : */
97	int	co_bit;			/* Associated modifier bit */
98	int	co_mask;		/* m_status bits to mask */
99	int	co_equal;		/* ... must equal this */
100} coltab[] = {
101	{ 'n',		CMNEW,		MNEW,		MNEW	},
102	{ 'o',		CMOLD,		MNEW,		0	},
103	{ 'u',		CMUNREAD,	MREAD,		0	},
104	{ 'd',		CMDELETED,	MDELETED,	MDELETED},
105	{ 'r',		CMREAD,		MREAD,		MREAD	},
106	{ 0,		0,		0,		0	}
107};
108
109static	int	lastcolmod;
110
111int
112markall(char buf[], int f)
113{
114	char **np;
115	int i;
116	struct message *mp;
117	char *namelist[NMLSIZE], *bufp;
118	int tok, beg, mc, star, other, valdot, colmod, colresult;
119
120	valdot = dot - &message[0] + 1;
121	colmod = 0;
122	for (i = 1; i <= msgCount; i++)
123		unmark(i);
124	bufp = buf;
125	mc = 0;
126	np = &namelist[0];
127	scaninit();
128	tok = scan(&bufp);
129	star = 0;
130	other = 0;
131	beg = 0;
132	while (tok != TEOL) {
133		switch (tok) {
134		case TNUMBER:
135number:
136			if (star) {
137				printf("No numbers mixed with *\n");
138				return (-1);
139			}
140			mc++;
141			other++;
142			if (beg != 0) {
143				if (check(lexnumber, f))
144					return (-1);
145				for (i = beg; i <= lexnumber; i++)
146					if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
147						mark(i);
148				beg = 0;
149				break;
150			}
151			beg = lexnumber;
152			if (check(beg, f))
153				return (-1);
154			tok = scan(&bufp);
155			regret(tok);
156			if (tok != TDASH) {
157				mark(beg);
158				beg = 0;
159			}
160			break;
161
162		case TPLUS:
163			if (beg != 0) {
164				printf("Non-numeric second argument\n");
165				return (-1);
166			}
167			i = valdot;
168			do {
169				i++;
170				if (i > msgCount) {
171					printf("Referencing beyond EOF\n");
172					return (-1);
173				}
174			} while ((message[i - 1].m_flag & MDELETED) != f);
175			mark(i);
176			break;
177
178		case TDASH:
179			if (beg == 0) {
180				i = valdot;
181				do {
182					i--;
183					if (i <= 0) {
184						printf("Referencing before 1\n");
185						return (-1);
186					}
187				} while ((message[i - 1].m_flag & MDELETED) != f);
188				mark(i);
189			}
190			break;
191
192		case TSTRING:
193			if (beg != 0) {
194				printf("Non-numeric second argument\n");
195				return (-1);
196			}
197			other++;
198			if (lexstring[0] == ':') {
199				colresult = evalcol(lexstring[1]);
200				if (colresult == 0) {
201					printf("Unknown colon modifier \"%s\"\n",
202					    lexstring);
203					return (-1);
204				}
205				colmod |= colresult;
206			}
207			else
208				*np++ = savestr(lexstring);
209			break;
210
211		case TDOLLAR:
212		case TUP:
213		case TDOT:
214			lexnumber = metamess(lexstring[0], f);
215			if (lexnumber == -1)
216				return (-1);
217			goto number;
218
219		case TSTAR:
220			if (other) {
221				printf("Can't mix \"*\" with anything\n");
222				return (-1);
223			}
224			star++;
225			break;
226
227		case TERROR:
228			return (-1);
229		}
230		tok = scan(&bufp);
231	}
232	lastcolmod = colmod;
233	*np = NULL;
234	mc = 0;
235	if (star) {
236		for (i = 0; i < msgCount; i++)
237			if ((message[i].m_flag & MDELETED) == f) {
238				mark(i+1);
239				mc++;
240			}
241		if (mc == 0) {
242			printf("No applicable messages.\n");
243			return (-1);
244		}
245		return (0);
246	}
247
248	/*
249	 * If no numbers were given, mark all of the messages,
250	 * so that we can unmark any whose sender was not selected
251	 * if any user names were given.
252	 */
253
254	if ((np > namelist || colmod != 0) && mc == 0)
255		for (i = 1; i <= msgCount; i++)
256			if ((message[i-1].m_flag & MDELETED) == f)
257				mark(i);
258
259	/*
260	 * If any names were given, go through and eliminate any
261	 * messages whose senders were not requested.
262	 */
263
264	if (np > namelist) {
265		for (i = 1; i <= msgCount; i++) {
266			for (mc = 0, np = &namelist[0]; *np != NULL; np++)
267				if (**np == '/') {
268					if (matchfield(*np, i)) {
269						mc++;
270						break;
271					}
272				}
273				else {
274					if (matchsender(*np, i)) {
275						mc++;
276						break;
277					}
278				}
279			if (mc == 0)
280				unmark(i);
281		}
282
283		/*
284		 * Make sure we got some decent messages.
285		 */
286
287		mc = 0;
288		for (i = 1; i <= msgCount; i++)
289			if (message[i-1].m_flag & MMARK) {
290				mc++;
291				break;
292			}
293		if (mc == 0) {
294			printf("No applicable messages from {%s",
295				namelist[0]);
296			for (np = &namelist[1]; *np != NULL; np++)
297				printf(", %s", *np);
298			printf("}\n");
299			return (-1);
300		}
301	}
302
303	/*
304	 * If any colon modifiers were given, go through and
305	 * unmark any messages which do not satisfy the modifiers.
306	 */
307
308	if (colmod != 0) {
309		for (i = 1; i <= msgCount; i++) {
310			struct coltab *colp;
311
312			mp = &message[i - 1];
313			for (colp = &coltab[0]; colp->co_char != '\0'; colp++)
314				if (colp->co_bit & colmod)
315					if ((mp->m_flag & colp->co_mask)
316					    != colp->co_equal)
317						unmark(i);
318
319		}
320		for (mp = &message[0]; mp < &message[msgCount]; mp++)
321			if (mp->m_flag & MMARK)
322				break;
323		if (mp >= &message[msgCount]) {
324			struct coltab *colp;
325
326			printf("No messages satisfy");
327			for (colp = &coltab[0]; colp->co_char != '\0'; colp++)
328				if (colp->co_bit & colmod)
329					printf(" :%c", colp->co_char);
330			printf("\n");
331			return (-1);
332		}
333	}
334	return (0);
335}
336
337/*
338 * Turn the character after a colon modifier into a bit
339 * value.
340 */
341int
342evalcol(int col)
343{
344	struct coltab *colp;
345
346	if (col == 0)
347		return (lastcolmod);
348	for (colp = &coltab[0]; colp->co_char != '\0'; colp++)
349		if (colp->co_char == col)
350			return (colp->co_bit);
351	return (0);
352}
353
354/*
355 * Check the passed message number for legality and proper flags.
356 * If f is MDELETED, then either kind will do.  Otherwise, the message
357 * has to be undeleted.
358 */
359int
360check(int mesg, int f)
361{
362	struct message *mp;
363
364	if (mesg < 1 || mesg > msgCount) {
365		printf("%d: Invalid message number\n", mesg);
366		return (-1);
367	}
368	mp = &message[mesg-1];
369	if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
370		printf("%d: Inappropriate message\n", mesg);
371		return (-1);
372	}
373	return (0);
374}
375
376/*
377 * Scan out the list of string arguments, shell style
378 * for a RAWLIST.
379 */
380int
381getrawlist(char line[], char **argv, int argc)
382{
383	char c, *cp, *cp2, quotec;
384	int argn;
385	char *linebuf;
386	size_t linebufsize = BUFSIZ;
387
388	if ((linebuf = malloc(linebufsize)) == NULL)
389		err(1, "Out of memory");
390
391	argn = 0;
392	cp = line;
393	for (;;) {
394		for (; *cp == ' ' || *cp == '\t'; cp++)
395			;
396		if (*cp == '\0')
397			break;
398		if (argn >= argc - 1) {
399			printf(
400			"Too many elements in the list; excess discarded.\n");
401			break;
402		}
403		cp2 = linebuf;
404		quotec = '\0';
405		while ((c = *cp) != '\0') {
406			/* Allocate more space if necessary */
407			if (cp2 - linebuf == linebufsize - 1) {
408				linebufsize += BUFSIZ;
409				if ((linebuf = realloc(linebuf, linebufsize)) == NULL)
410					err(1, "Out of memory");
411				cp2 = linebuf + linebufsize - BUFSIZ - 1;
412			}
413			cp++;
414			if (quotec != '\0') {
415				if (c == quotec)
416					quotec = '\0';
417				else if (c == '\\')
418					switch (c = *cp++) {
419					case '\0':
420						*cp2++ = '\\';
421						cp--;
422						break;
423					case '0': case '1': case '2': case '3':
424					case '4': case '5': case '6': case '7':
425						c -= '0';
426						if (*cp >= '0' && *cp <= '7')
427							c = c * 8 + *cp++ - '0';
428						if (*cp >= '0' && *cp <= '7')
429							c = c * 8 + *cp++ - '0';
430						*cp2++ = c;
431						break;
432					case 'b':
433						*cp2++ = '\b';
434						break;
435					case 'f':
436						*cp2++ = '\f';
437						break;
438					case 'n':
439						*cp2++ = '\n';
440						break;
441					case 'r':
442						*cp2++ = '\r';
443						break;
444					case 't':
445						*cp2++ = '\t';
446						break;
447					case 'v':
448						*cp2++ = '\v';
449						break;
450					default:
451						*cp2++ = c;
452					}
453				else if (c == '^') {
454					c = *cp++;
455					if (c == '?')
456						*cp2++ = '\177';
457					/* null doesn't show up anyway */
458					else if ((c >= 'A' && c <= '_') ||
459					    (c >= 'a' && c <= 'z'))
460						*cp2++ = c & 037;
461					else {
462						*cp2++ = '^';
463						cp--;
464					}
465				} else
466					*cp2++ = c;
467			} else if (c == '"' || c == '\'')
468				quotec = c;
469			else if (c == ' ' || c == '\t')
470				break;
471			else
472				*cp2++ = c;
473		}
474		*cp2 = '\0';
475		argv[argn++] = savestr(linebuf);
476	}
477	argv[argn] = NULL;
478	(void)free(linebuf);
479	return (argn);
480}
481
482/*
483 * scan out a single lexical item and return its token number,
484 * updating the string pointer passed **p.  Also, store the value
485 * of the number or string scanned in lexnumber or lexstring as
486 * appropriate.  In any event, store the scanned `thing' in lexstring.
487 */
488
489static struct lex {
490	char	l_char;
491	char	l_token;
492} singles[] = {
493	{ '$',	TDOLLAR	},
494	{ '.',	TDOT	},
495	{ '^',	TUP 	},
496	{ '*',	TSTAR 	},
497	{ '-',	TDASH 	},
498	{ '+',	TPLUS 	},
499	{ '(',	TOPEN 	},
500	{ ')',	TCLOSE 	},
501	{ 0,	0 	}
502};
503
504int
505scan(char **sp)
506{
507	char *cp, *cp2;
508	int c;
509	struct lex *lp;
510	int quotec;
511
512	if (regretp >= 0) {
513		strcpy(lexstring, string_stack[regretp]);
514		lexnumber = numberstack[regretp];
515		return (regretstack[regretp--]);
516	}
517	cp = *sp;
518	cp2 = lexstring;
519	c = *cp++;
520
521	/*
522	 * strip away leading white space.
523	 */
524
525	while (c == ' ' || c == '\t')
526		c = *cp++;
527
528	/*
529	 * If no characters remain, we are at end of line,
530	 * so report that.
531	 */
532
533	if (c == '\0') {
534		*sp = --cp;
535		return (TEOL);
536	}
537
538	/*
539	 * If the leading character is a digit, scan
540	 * the number and convert it on the fly.
541	 * Return TNUMBER when done.
542	 */
543
544	if (isdigit((unsigned char)c)) {
545		lexnumber = 0;
546		while (isdigit((unsigned char)c)) {
547			lexnumber = lexnumber*10 + c - '0';
548			*cp2++ = c;
549			c = *cp++;
550		}
551		*cp2 = '\0';
552		*sp = --cp;
553		return (TNUMBER);
554	}
555
556	/*
557	 * Check for single character tokens; return such
558	 * if found.
559	 */
560
561	for (lp = &singles[0]; lp->l_char != '\0'; lp++)
562		if (c == lp->l_char) {
563			lexstring[0] = c;
564			lexstring[1] = '\0';
565			*sp = cp;
566			return (lp->l_token);
567		}
568
569	/*
570	 * We've got a string!  Copy all the characters
571	 * of the string into lexstring, until we see
572	 * a null, space, or tab.
573	 * If the lead character is a " or ', save it
574	 * and scan until you get another.
575	 */
576
577	quotec = 0;
578	if (c == '\'' || c == '"') {
579		quotec = c;
580		c = *cp++;
581	}
582	while (c != '\0') {
583		if (c == quotec) {
584			cp++;
585			break;
586		}
587		if (quotec == 0 && (c == ' ' || c == '\t'))
588			break;
589		if (cp2 - lexstring < STRINGLEN-1)
590			*cp2++ = c;
591		c = *cp++;
592	}
593	if (quotec && c == '\0') {
594		fprintf(stderr, "Missing %c\n", quotec);
595		return (TERROR);
596	}
597	*sp = --cp;
598	*cp2 = '\0';
599	return (TSTRING);
600}
601
602/*
603 * Unscan the named token by pushing it onto the regret stack.
604 */
605void
606regret(int token)
607{
608	if (++regretp >= REGDEP)
609		errx(1, "Too many regrets");
610	regretstack[regretp] = token;
611	lexstring[STRINGLEN-1] = '\0';
612	string_stack[regretp] = savestr(lexstring);
613	numberstack[regretp] = lexnumber;
614}
615
616/*
617 * Reset all the scanner global variables.
618 */
619void
620scaninit(void)
621{
622	regretp = -1;
623}
624
625/*
626 * Find the first message whose flags & m == f  and return
627 * its message number.
628 */
629int
630first(int f, int m)
631{
632	struct message *mp;
633
634	if (msgCount == 0)
635		return (0);
636	f &= MDELETED;
637	m &= MDELETED;
638	for (mp = dot; mp < &message[msgCount]; mp++)
639		if ((mp->m_flag & m) == f)
640			return (mp - message + 1);
641	for (mp = dot-1; mp >= &message[0]; mp--)
642		if ((mp->m_flag & m) == f)
643			return (mp - message + 1);
644	return (0);
645}
646
647/*
648 * See if the passed name sent the passed message number.  Return true
649 * if so.
650 */
651int
652matchsender(char *str, int mesg)
653{
654	char *cp;
655
656	/* null string matches nothing instead of everything */
657	if (*str == '\0')
658		return (0);
659
660	cp = nameof(&message[mesg - 1], 0);
661	return (strcasestr(cp, str) != NULL);
662}
663
664/*
665 * See if the passed name received the passed message number.  Return true
666 * if so.
667 */
668
669static char *to_fields[] = { "to", "cc", "bcc", NULL };
670
671static int
672matchto(char *str, int mesg)
673{
674	struct message *mp;
675	char *cp, **to;
676
677	str++;
678
679	/* null string matches nothing instead of everything */
680	if (*str == '\0')
681		return (0);
682
683	mp = &message[mesg - 1];
684
685	for (to = to_fields; *to != NULL; to++) {
686		cp = hfield(*to, mp);
687		if (cp != NULL && strcasestr(cp, str) != NULL)
688			return (1);
689	}
690	return (0);
691}
692
693/*
694 * See if the given substring is contained within the specified field. If
695 * 'searchheaders' is set, then the form '/x:y' will be accepted and matches
696 * any message with the substring 'y' in field 'x'. If 'x' is omitted or
697 * 'searchheaders' is not set, then the search matches any messages
698 * with the substring 'y' in the 'Subject'. The search is case insensitive.
699 *
700 * The form '/to:y' is a special case, and will match all messages
701 * containing the substring 'y' in the 'To', 'Cc', or 'Bcc' header
702 * fields. The search for 'to' is case sensitive, so that '/To:y' can
703 * be used to limit the search to just the 'To' field.
704 */
705
706static char lastscan[STRINGLEN];
707int
708matchfield(char *str, int mesg)
709{
710	struct message *mp;
711	char *cp, *cp2;
712
713	str++;
714	if (*str == '\0')
715		str = lastscan;
716	else
717		strlcpy(lastscan, str, sizeof(lastscan));
718	mp = &message[mesg-1];
719
720	/*
721	 * Now look, ignoring case, for the word in the string.
722	 */
723
724	if (value("searchheaders") && (cp = strchr(str, ':')) != NULL) {
725		/* Check for special case "/to:" */
726		if (strncmp(str, "to:", 3) == 0)
727			return (matchto(cp, mesg));
728		*cp++ = '\0';
729		cp2 = hfield(*str != '\0' ? str : "subject", mp);
730		cp[-1] = ':';
731		str = cp;
732		cp = cp2;
733	} else
734		cp = hfield("subject", mp);
735
736	if (cp == NULL)
737		return (0);
738
739	return (strcasestr(cp, str) != NULL);
740}
741
742/*
743 * Mark the named message by setting its mark bit.
744 */
745void
746mark(int mesg)
747{
748	int i;
749
750	i = mesg;
751	if (i < 1 || i > msgCount)
752		errx(1, "Bad message number to mark");
753	message[i-1].m_flag |= MMARK;
754}
755
756/*
757 * Unmark the named message.
758 */
759void
760unmark(int mesg)
761{
762	int i;
763
764	i = mesg;
765	if (i < 1 || i > msgCount)
766		errx(1, "Bad message number to unmark");
767	message[i-1].m_flag &= ~MMARK;
768}
769
770/*
771 * Return the message number corresponding to the passed meta character.
772 */
773int
774metamess(int meta, int f)
775{
776	int c, m;
777	struct message *mp;
778
779	c = meta;
780	switch (c) {
781	case '^':
782		/*
783		 * First 'good' message left.
784		 */
785		for (mp = &message[0]; mp < &message[msgCount]; mp++)
786			if ((mp->m_flag & MDELETED) == f)
787				return (mp - &message[0] + 1);
788		printf("No applicable messages\n");
789		return (-1);
790
791	case '$':
792		/*
793		 * Last 'good message left.
794		 */
795		for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
796			if ((mp->m_flag & MDELETED) == f)
797				return (mp - &message[0] + 1);
798		printf("No applicable messages\n");
799		return (-1);
800
801	case '.':
802		/*
803		 * Current message.
804		 */
805		m = dot - &message[0] + 1;
806		if ((dot->m_flag & MDELETED) != f) {
807			printf("%d: Inappropriate message\n", m);
808			return (-1);
809		}
810		return (m);
811
812	default:
813		printf("Unknown metachar (%c)\n", c);
814		return (-1);
815	}
816}
817