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