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
52 static int check(int mesg, int f);
53 static int evalcol(int col);
54 static int isinteger(char *buf);
55 static void mark(int mesg);
56 static int markall(char buf[], int f);
57 static int matchsubj(char *str, int mesg);
58 static int metamess(int meta, int f);
59 static void regret(int token);
60 static int scan(char **sp);
61 static void scaninit(void);
62 static int sender(char *str, int mesg);
63 static 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 */
72 int
getmessage(char * buf,int * vector,int flags)73 getmessage(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 */
115 static int
isinteger(char * buf)116 isinteger(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
145 int
getmsglist(char * buf,int * vector,int flags)146 getmsglist(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
183 static 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
197 static int lastcolmod;
198
199 static int
markall(char buf[],int f)200 markall(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:
222 number:
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"),
353 namelist[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 */
399 static int
evalcol(int col)400 evalcol(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 */
415 static int
check(int mesg,int f)416 check(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
437 int
getrawlist(char line[],char ** argv,int argc)438 getrawlist(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
499 static 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
514 static int
scan(char ** sp)515 scan(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
622 static void
regret(int token)623 regret(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
637 static void
scaninit(void)638 scaninit(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
648 int
first(int f,int m)649 first(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 */
675 static int
sender(char * str,int mesg)676 sender(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
689 static char lastscan[128];
690
691 static int
matchsubj(char * str,int mesg)692 matchsubj(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
728 static void
mark(int mesg)729 mark(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
743 static void
unmark(int mesg)744 unmark(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 */
757 static int
metamess(int meta,int f)758 metamess(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