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