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