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