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