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