xref: /openbsd/usr.bin/mail/list.c (revision cd9ca5b7)
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 
37 int 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  */
51 int
getmsglist(char * buf,int * vector,int flags)52 getmsglist(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  */
90 struct 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 
104 static int lastcolmod;
105 
106 int
markall(char * buf,int f)107 markall(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:
130 number:
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  */
333 int
evalcol(int col)334 evalcol(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  */
351 int
check(int mesg,int f)352 check(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  */
372 int
getrawlist(char * line,char ** argv,int argc)373 getrawlist(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  */
482 struct 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 
497 int
scan(char ** sp)498 scan(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  */
594 void
regret(int token)595 regret(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  */
609 void
scaninit(void)610 scaninit(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  */
620 int
first(int f,int m)621 first(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  */
642 int
matchsender(char * str,int mesg)643 matchsender(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  */
657 static char *to_fields[] = { "to", "cc", "bcc", NULL };
658 
659 int
matchto(char * str,int mesg)660 matchto(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  */
687 char lastscan[STRINGLEN];
688 
689 int
matchsubj(char * str,int mesg)690 matchsubj(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  */
726 void
mark(int mesg)727 mark(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  */
740 void
unmark(int mesg)741 unmark(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  */
754 int
metamess(int meta,int f)755 metamess(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