xref: /freebsd/usr.bin/mail/list.c (revision 315ee00f)
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
34 static char sccsid[] = "@(#)list.c	8.4 (Berkeley) 5/1/95";
35 #endif
36 #endif /* not lint */
37 #include <sys/cdefs.h>
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  */
54 int
55 getmsglist(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 
95 static 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 
109 static	int	lastcolmod;
110 
111 int
112 markall(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:
135 number:
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  */
341 int
342 evalcol(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  */
359 int
360 check(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  */
380 int
381 getrawlist(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 
489 static 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 
504 int
505 scan(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  */
605 void
606 regret(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  */
619 void
620 scaninit(void)
621 {
622 	regretp = -1;
623 }
624 
625 /*
626  * Find the first message whose flags & m == f  and return
627  * its message number.
628  */
629 int
630 first(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  */
651 int
652 matchsender(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 
669 static char *to_fields[] = { "to", "cc", "bcc", NULL };
670 
671 static int
672 matchto(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 
706 static char lastscan[STRINGLEN];
707 int
708 matchfield(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  */
745 void
746 mark(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  */
759 void
760 unmark(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  */
773 int
774 metamess(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