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