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