1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28/* All Rights Reserved   */
29
30/*
31 * University Copyright- Copyright (c) 1982, 1986, 1988
32 * The Regents of the University of California
33 * All Rights Reserved
34 *
35 * University Acknowledgment- Portions of this document are derived from
36 * software developed by the University of California, Berkeley, and its
37 * contributors.
38 */
39
40#include "rcv.h"
41#include <locale.h>
42#include <stdlib.h>
43#include <string.h>
44
45/*
46 * mailx -- a modified version of a University of California at Berkeley
47 *	mail program
48 *
49 * Message list handling.
50 */
51
52static int	check(int mesg, int f);
53static int	evalcol(int col);
54static int	isinteger(char *buf);
55static void	mark(int mesg);
56static int	markall(char buf[], int f);
57static int	matchsubj(char *str, int mesg);
58static int	metamess(int meta, int f);
59static void	regret(int token);
60static int	scan(char **sp);
61static void	scaninit(void);
62static int	sender(char *str, int mesg);
63static void	unmark(int mesg);
64
65/*
66 * Process message operand list.
67 * Convert the user string of message numbers and
68 * store the numbers into vector.
69 *
70 * Returns the count of messages picked up or -1 on error.
71 */
72int
73getmessage(char *buf, int *vector, int flags)
74{
75	int *ip;
76	struct message *mp;
77	int firstmsg = -1;
78	char delims[] = "\t- ";
79	char *result  = NULL;
80
81	if (markall(buf, flags) < 0)
82		return (-1);
83	ip = vector;
84
85	/*
86	 * Check for first message number and make sure it is
87	 * at the beginning of the vector.
88	 */
89	result = strtok(buf, delims);
90	if (result != NULL && isinteger(result)) {
91		firstmsg = atoi(result);
92		*ip++ = firstmsg;
93	}
94
95	/*
96	 * Add marked messages to vector and skip first
97	 * message number because it is already at the
98	 * beginning of the vector
99	 */
100	for (mp = &message[0]; mp < &message[msgCount]; mp++) {
101		if (firstmsg == mp - &message[0] + 1)
102			continue;
103		if (mp->m_flag & MMARK)
104			*ip++ = mp - &message[0] + 1;
105	}
106	*ip = 0;
107	return (ip - vector);
108}
109
110/*
111 * Check to see if string is an integer
112 *
113 * Returns 1 if is an integer and 0 if it is not
114 */
115static int
116isinteger(char *buf)
117{
118	int i, result = 1;
119
120	/* check for empty string */
121	if (strcmp(buf, "") == 0) {
122		result = 0;
123		return (result);
124	}
125
126	i = 0;
127	while (buf[i] != '\0') {
128		if (!isdigit(buf[i])) {
129			result = 0;
130			break;
131		}
132		i++;
133	}
134	return (result);
135}
136
137/*
138 * Process msglist operand list.
139 * Convert the user string of message numbers and
140 * store the numbers into vector.
141 *
142 * Returns the count of messages picked up or -1 on error.
143 */
144
145int
146getmsglist(char *buf, int *vector, int flags)
147{
148	int *ip;
149	struct message *mp;
150
151	if (markall(buf, flags) < 0)
152		return (-1);
153	ip = vector;
154	for (mp = &message[0]; mp < &message[msgCount]; mp++)
155		if (mp->m_flag & MMARK)
156			*ip++ = mp - &message[0] + 1;
157	*ip = 0;
158	return (ip - vector);
159}
160
161
162/*
163 * Mark all messages that the user wanted from the command
164 * line in the message structure.  Return 0 on success, -1
165 * on error.
166 */
167
168/*
169 * Bit values for colon modifiers.
170 */
171
172#define	CMNEW		01		/* New messages */
173#define	CMOLD		02		/* Old messages */
174#define	CMUNREAD	04		/* Unread messages */
175#define	CMDELETED	010		/* Deleted messages */
176#define	CMREAD		020		/* Read messages */
177
178/*
179 * The following table describes the letters which can follow
180 * the colon and gives the corresponding modifier bit.
181 */
182
183static struct coltab {
184	char	co_char;		/* What to find past : */
185	int	co_bit;			/* Associated modifier bit */
186	int	co_mask;		/* m_status bits to mask */
187	int	co_equal;		/* ... must equal this */
188} coltab[] = {
189	'n',		CMNEW,		MNEW,		MNEW,
190	'o',		CMOLD,		MNEW,		0,
191	'u',		CMUNREAD,	MREAD,		0,
192	'd',		CMDELETED,	MDELETED,	MDELETED,
193	'r',		CMREAD,		MREAD,		MREAD,
194	0,		0,		0,		0
195};
196
197static	int	lastcolmod;
198
199static int
200markall(char buf[], int f)
201{
202	char **np;
203	int i;
204	struct message *mp;
205	char *namelist[NMLSIZE], *bufp;
206	int tok, beg, mc, star, other, colmod, colresult;
207
208	colmod = 0;
209	for (i = 1; i <= msgCount; i++)
210		unmark(i);
211	bufp = buf;
212	mc = 0;
213	np = &namelist[0];
214	scaninit();
215	tok = scan(&bufp);
216	star = 0;
217	other = 0;
218	beg = 0;
219	while (tok != TEOL) {
220		switch (tok) {
221		case TNUMBER:
222number:
223			if (star) {
224				printf(gettext("No numbers mixed with *\n"));
225				return (-1);
226			}
227			mc++;
228			other++;
229			if (beg != 0) {
230				if (check(lexnumber, f))
231					return (-1);
232				for (i = beg; i <= lexnumber; i++)
233					if ((message[i-1].m_flag&MDELETED) == f)
234						mark(i);
235				beg = 0;
236				break;
237			}
238			beg = lexnumber;
239			if (check(beg, f))
240				return (-1);
241			tok = scan(&bufp);
242			if (tok != TDASH) {
243				regret(tok);
244				mark(beg);
245				beg = 0;
246			}
247			break;
248
249		case TSTRING:
250			if (beg != 0) {
251				printf(gettext(
252				    "Non-numeric second argument\n"));
253				return (-1);
254			}
255			other++;
256			if (lexstring[0] == ':') {
257				colresult = evalcol(lexstring[1]);
258				if (colresult == 0) {
259					printf(gettext(
260					    "Unknown colon modifier \"%s\"\n"),
261					    lexstring);
262					return (-1);
263				}
264				colmod |= colresult;
265			}
266			else
267				*np++ = savestr(lexstring);
268			break;
269
270		case TDASH:
271		case TPLUS:
272		case TDOLLAR:
273		case TUP:
274		case TDOT:
275			lexnumber = metamess(lexstring[0], f);
276			if (lexnumber == -1)
277				return (-1);
278			goto number;
279
280		case TSTAR:
281			if (other) {
282				printf(gettext(
283				    "Can't mix \"*\" with anything\n"));
284				return (-1);
285			}
286			star++;
287			break;
288		}
289		tok = scan(&bufp);
290	}
291	lastcolmod = colmod;
292	*np = NOSTR;
293	mc = 0;
294	if (star) {
295		for (i = 0; i < msgCount; i++)
296			if ((message[i].m_flag & MDELETED) == f) {
297				mark(i+1);
298				mc++;
299			}
300		if (mc == 0) {
301			printf(gettext("No applicable messages\n"));
302			return (-1);
303		}
304		return (0);
305	}
306
307	/*
308	 * If no numbers were given, mark all of the messages,
309	 * so that we can unmark any whose sender was not selected
310	 * if any user names were given.
311	 */
312
313	if ((np > namelist || colmod != 0) && mc == 0)
314		for (i = 1; i <= msgCount; i++)
315			if ((message[i-1].m_flag & MDELETED) == f)
316				mark(i);
317
318	/*
319	 * If any names were given, go through and eliminate any
320	 * messages whose senders were not requested.
321	 */
322
323	if (np > namelist) {
324		for (i = 1; i <= msgCount; i++) {
325			for (mc = 0, np = &namelist[0]; *np != NOSTR; np++)
326				if (**np == '/') {
327					if (matchsubj(*np, i)) {
328						mc++;
329						break;
330					}
331				} else {
332					if (sender(*np, i)) {
333						mc++;
334						break;
335					}
336				}
337			if (mc == 0)
338				unmark(i);
339		}
340
341		/*
342		 * Make sure we got some decent messages.
343		 */
344
345		mc = 0;
346		for (i = 1; i <= msgCount; i++)
347			if (message[i-1].m_flag & MMARK) {
348				mc++;
349				break;
350			}
351		if (mc == 0) {
352			printf(gettext("No applicable messages from {%s"),
353namelist[0]);
354			for (np = &namelist[1]; *np != NOSTR; np++)
355				printf(", %s", *np);
356			printf("}\n");
357			return (-1);
358		}
359	}
360
361	/*
362	 * If any colon modifiers were given, go through and
363	 * unmark any messages which do not satisfy the modifiers.
364	 */
365
366	if (colmod != 0) {
367		for (i = 1; i <= msgCount; i++) {
368			struct coltab *colp;
369
370			mp = &message[i - 1];
371			for (colp = &coltab[0]; colp->co_char; colp++)
372				if (colp->co_bit & colmod)
373					if ((mp->m_flag & colp->co_mask)
374					    != colp->co_equal)
375						unmark(i);
376
377		}
378		for (mp = &message[0]; mp < &message[msgCount]; mp++)
379			if (mp->m_flag & MMARK)
380				break;
381		if (mp >= &message[msgCount]) {
382			struct coltab *colp;
383
384			printf(gettext("No messages satisfy"));
385			for (colp = &coltab[0]; colp->co_char; colp++)
386				if (colp->co_bit & colmod)
387					printf(" :%c", colp->co_char);
388			printf("\n");
389			return (-1);
390		}
391	}
392	return (0);
393}
394
395/*
396 * Turn the character after a colon modifier into a bit
397 * value.
398 */
399static int
400evalcol(int col)
401{
402	struct coltab *colp;
403
404	if (col == 0)
405		return (lastcolmod);
406	for (colp = &coltab[0]; colp->co_char; colp++)
407		if (colp->co_char == col)
408			return (colp->co_bit);
409	return (0);
410}
411
412/*
413 * Check the passed message number for legality and proper flags.
414 */
415static int
416check(int mesg, int f)
417{
418	struct message *mp;
419
420	if (mesg < 1 || mesg > msgCount) {
421		printf(gettext("%d: Invalid message number\n"), mesg);
422		return (-1);
423	}
424	mp = &message[mesg-1];
425	if ((mp->m_flag & MDELETED) != f) {
426		printf(gettext("%d: Inappropriate message\n"), mesg);
427		return (-1);
428	}
429	return (0);
430}
431
432/*
433 * Scan out the list of string arguments, shell style
434 * for a RAWLIST.
435 */
436
437int
438getrawlist(char line[], char **argv, int argc)
439{
440	char **ap, *cp, *cp2;
441	char linebuf[LINESIZE], quotec;
442	char **last;
443
444	ap = argv;
445	cp = line;
446	last = argv + argc - 1;
447	while (*cp != '\0') {
448		while (any(*cp, " \t"))
449			cp++;
450		cp2 = linebuf;
451		quotec = 0;
452		while (*cp != '\0') {
453			if (quotec) {
454				if (*cp == quotec) {
455					quotec = 0;
456					cp++;
457				} else
458					*cp2++ = *cp++;
459			} else {
460				if (*cp == '\\') {
461					if (*(cp+1) != '\0') {
462						*cp2++ = *++cp;
463						cp++;
464					} else {
465						printf(gettext(
466						    "Trailing \\; ignoring\n"));
467						break;
468					}
469				}
470				if (any(*cp, " \t"))
471					break;
472				if (any(*cp, "'\""))
473					quotec = *cp++;
474				else
475					*cp2++ = *cp++;
476			}
477		}
478		*cp2 = '\0';
479		if (cp2 == linebuf)
480			break;
481		if (ap >= last) {
482			printf(gettext("Too many elements in the list;"
483			    " excess discarded\n"));
484			break;
485		}
486		*ap++ = savestr(linebuf);
487	}
488	*ap = NOSTR;
489	return (ap-argv);
490}
491
492/*
493 * scan out a single lexical item and return its token number,
494 * updating the string pointer passed **p.  Also, store the value
495 * of the number or string scanned in lexnumber or lexstring as
496 * appropriate.  In any event, store the scanned `thing' in lexstring.
497 */
498
499static struct lex {
500	char	l_char;
501	char	l_token;
502} singles[] = {
503	'$',	TDOLLAR,
504	'.',	TDOT,
505	'^',	TUP,
506	'*',	TSTAR,
507	'-',	TDASH,
508	'+',	TPLUS,
509	'(',	TOPEN,
510	')',	TCLOSE,
511	0,	0
512};
513
514static int
515scan(char **sp)
516{
517	char *cp, *cp2;
518	char c;
519	struct lex *lp;
520	int quotec;
521
522	if (regretp >= 0) {
523		copy(stringstack[regretp], lexstring);
524		lexnumber = numberstack[regretp];
525		return (regretstack[regretp--]);
526	}
527	cp = *sp;
528	cp2 = lexstring;
529	c = *cp++;
530
531	/*
532	 * strip away leading white space.
533	 */
534
535	while (any(c, " \t"))
536		c = *cp++;
537
538	/*
539	 * If no characters remain, we are at end of line,
540	 * so report that.
541	 */
542
543	if (c == '\0') {
544		*sp = --cp;
545		return (TEOL);
546	}
547
548	/*
549	 * If the leading character is a digit, scan
550	 * the number and convert it on the fly.
551	 * Return TNUMBER when done.
552	 */
553
554	if (isdigit(c)) {
555		lexnumber = 0;
556		while (isdigit(c)) {
557			lexnumber = lexnumber*10 + c - '0';
558			*cp2++ = c;
559			c = *cp++;
560		}
561		*cp2 = '\0';
562		*sp = --cp;
563		return (TNUMBER);
564	}
565
566	/*
567	 * Check for single character tokens; return such
568	 * if found.
569	 */
570
571	for (lp = &singles[0]; lp->l_char != 0; lp++)
572		if (c == lp->l_char) {
573			lexstring[0] = c;
574			lexstring[1] = '\0';
575			*sp = cp;
576			return (lp->l_token);
577		}
578
579	/*
580	 * We've got a string!  Copy all the characters
581	 * of the string into lexstring, until we see
582	 * a null, space, or tab.
583	 * If the lead character is a " or ', save it
584	 * and scan until you get another.
585	 */
586
587	quotec = 0;
588	if (any(c, "'\"")) {
589		quotec = c;
590		c = *cp++;
591	}
592	while (c != '\0') {
593		if (quotec == 0 && c == '\\') {
594			if (*cp != '\0') {
595				c = *cp++;
596			} else {
597				fprintf(stderr, gettext("Trailing \\; "
598				    "ignoring\n"));
599			}
600		}
601		if (c == quotec) {
602			cp++;
603			break;
604		}
605		if (quotec == 0 && any(c, " \t"))
606			break;
607		if (cp2 - lexstring < STRINGLEN-1)
608			*cp2++ = c;
609		c = *cp++;
610	}
611	if (quotec && c == 0)
612		fprintf(stderr, gettext("Missing %c\n"), quotec);
613	*sp = --cp;
614	*cp2 = '\0';
615	return (TSTRING);
616}
617
618/*
619 * Unscan the named token by pushing it onto the regret stack.
620 */
621
622static void
623regret(int token)
624{
625	if (++regretp >= REGDEP)
626		panic("Too many regrets");
627	regretstack[regretp] = token;
628	lexstring[STRINGLEN-1] = '\0';
629	stringstack[regretp] = savestr(lexstring);
630	numberstack[regretp] = lexnumber;
631}
632
633/*
634 * Reset all the scanner global variables.
635 */
636
637static void
638scaninit(void)
639{
640	regretp = -1;
641}
642
643/*
644 * Find the first message whose flags & m == f  and return
645 * its message number.
646 */
647
648int
649first(int f, int m)
650{
651	int mesg;
652	struct message *mp;
653
654	mesg = dot - &message[0] + 1;
655	f &= MDELETED;
656	m &= MDELETED;
657	for (mp = dot; mp < &message[msgCount]; mp++) {
658		if ((mp->m_flag & m) == f)
659			return (mesg);
660		mesg++;
661	}
662	mesg = dot - &message[0];
663	for (mp = dot-1; mp >= &message[0]; mp--) {
664		if ((mp->m_flag & m) == f)
665			return (mesg);
666		mesg--;
667	}
668	return (0);
669}
670
671/*
672 * See if the passed name sent the passed message number.  Return true
673 * if so.
674 */
675static int
676sender(char *str, int mesg)
677{
678	return (samebody(str, skin(nameof(&message[mesg-1])), TRUE));
679}
680
681/*
682 * See if the given string matches inside the subject field of the
683 * given message.  For the purpose of the scan, we ignore case differences.
684 * If it does, return true.  The string search argument is assumed to
685 * have the form "/search-string."  If it is of the form "/," we use the
686 * previous search string.
687 */
688
689static char lastscan[128];
690
691static int
692matchsubj(char *str, int mesg)
693{
694	struct message *mp;
695	char *cp, *cp2, *backup;
696
697	str++;
698	if (strlen(str) == 0)
699		str = lastscan;
700	else
701		nstrcpy(lastscan, sizeof (lastscan), str);
702	mp = &message[mesg-1];
703
704	/*
705	 * Now look, ignoring case, for the word in the string.
706	 */
707
708	cp = str;
709	cp2 = hfield("subject", mp, addone);
710	if (cp2 == NOSTR)
711		return (0);
712	backup = cp2;
713	while (*cp2) {
714		if (*cp == 0)
715			return (1);
716		if (toupper(*cp++) != toupper(*cp2++)) {
717			cp2 = ++backup;
718			cp = str;
719		}
720	}
721	return (*cp == 0);
722}
723
724/*
725 * Mark the named message by setting its mark bit.
726 */
727
728static void
729mark(int mesg)
730{
731	int i;
732
733	i = mesg;
734	if (i < 1 || i > msgCount)
735		panic("Bad message number to mark");
736	message[i-1].m_flag |= MMARK;
737}
738
739/*
740 * Unmark the named message.
741 */
742
743static void
744unmark(int mesg)
745{
746	int i;
747
748	i = mesg;
749	if (i < 1 || i > msgCount)
750		panic("Bad message number to unmark");
751	message[i-1].m_flag &= ~MMARK;
752}
753
754/*
755 * Return the message number corresponding to the passed meta character.
756 */
757static int
758metamess(int meta, int f)
759{
760	int c, m;
761	struct message *mp;
762
763	c = meta;
764	switch (c) {
765	case '^':
766		/*
767		 * First 'good' message left.
768		 */
769		for (mp = &message[0]; mp < &message[msgCount]; mp++)
770			if ((mp->m_flag & MDELETED) == f)
771				return (mp - &message[0] + 1);
772		printf(gettext("No applicable messages\n"));
773		return (-1);
774
775	case '+':
776		/*
777		 * Next 'good' message left.
778		 */
779		for (mp = dot + 1; mp < &message[msgCount]; mp++)
780			if ((mp->m_flag & MDELETED) == f)
781				return (mp - &message[0] + 1);
782		printf(gettext("Referencing beyond last message\n"));
783		return (-1);
784
785	case '-':
786		/*
787		 * Previous 'good' message.
788		 */
789		for (mp = dot - 1; mp >= &message[0]; mp--)
790			if ((mp->m_flag & MDELETED) == f)
791				return (mp - &message[0] + 1);
792		printf(gettext("Referencing before first message\n"));
793		return (-1);
794
795	case '$':
796		/*
797		 * Last 'good message left.
798		 */
799		for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
800			if ((mp->m_flag & MDELETED) == f)
801				return (mp - &message[0] + 1);
802		printf(gettext("No applicable messages\n"));
803		return (-1);
804
805	case '.':
806		/*
807		 * Current message.
808		 */
809		m = dot - &message[0] + 1;
810		if ((dot->m_flag & MDELETED) != f) {
811			printf(gettext("%d: Inappropriate message\n"), m);
812			return (-1);
813		}
814		return (m);
815
816	default:
817		printf(gettext("Unknown metachar (%c)\n"), c);
818		return (-1);
819	}
820}
821