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