]> git.llucax.com Git - software/mutt-debian.git/blob - rfc822.c
Imported Upstream version 1.5.18
[software/mutt-debian.git] / rfc822.c
1 /*
2  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  * 
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  * 
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */ 
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include <string.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26
27 #ifndef TESTING
28 #include "mutt.h"
29 #else
30 #define safe_strdup strdup
31 #define safe_malloc malloc
32 #define SKIPWS(x) while(isspace(*x))x++
33 #define FREE(x) safe_free(x)
34 #define ISSPACE isspace
35 #define strfcpy(a,b,c) {if (c) {strncpy(a,b,c);a[c-1]=0;}}
36 #define STRING 128
37 #include "rfc822.h"
38 #endif
39
40 #include "mutt_idna.h"
41
42 #define terminate_string(a, b, c) do { if ((b) < (c)) a[(b)] = 0; else \
43         a[(c)] = 0; } while (0)
44
45 #define terminate_buffer(a, b) terminate_string(a, b, sizeof (a) - 1)
46
47
48 const char RFC822Specials[] = "@.,:;<>[]\\\"()";
49 #define is_special(x) strchr(RFC822Specials,x)
50
51 int RFC822Error = 0;
52
53 /* these must defined in the same order as the numerated errors given in rfc822.h */
54 const char *RFC822Errors[] = {
55   "out of memory",
56   "mismatched parenthesis",
57   "mismatched quotes",
58   "bad route in <>",
59   "bad address in <>",
60   "bad address spec"
61 };
62
63 void rfc822_dequote_comment (char *s)
64 {
65   char *w = s;
66
67   for (; *s; s++)
68   {
69     if (*s == '\\')
70     {
71       if (!*++s)
72         break; /* error? */
73       *w++ = *s;
74     }
75     else if (*s != '\"')
76     {
77       if (w != s)
78         *w = *s;
79       w++;
80     }
81   }
82   *w = 0;
83 }
84
85 void rfc822_free_address (ADDRESS **p)
86 {
87   ADDRESS *t;
88
89   while (*p)
90   {
91     t = *p;
92     *p = (*p)->next;
93 #ifdef EXACT_ADDRESS
94     FREE (&t->val);
95 #endif
96     FREE (&t->personal);
97     FREE (&t->mailbox);
98     FREE (&t);
99   }
100 }
101
102 static const char *
103 parse_comment (const char *s,
104                char *comment, size_t *commentlen, size_t commentmax)
105 {
106   int level = 1;
107   
108   while (*s && level)
109   {
110     if (*s == '(')
111       level++;
112     else if (*s == ')')
113     {
114       if (--level == 0)
115       {
116         s++;
117         break;
118       }
119     }
120     else if (*s == '\\')
121     {
122       if (!*++s)
123         break;
124     }
125     if (*commentlen < commentmax)
126       comment[(*commentlen)++] = *s;
127     s++;
128   }
129   if (level)
130   {
131     RFC822Error = ERR_MISMATCH_PAREN;
132     return NULL;
133   }
134   return s;
135 }
136
137 static const char *
138 parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
139 {
140   if (*tokenlen < tokenmax)
141     token[(*tokenlen)++] = '"';
142   while (*s)
143   {
144     if (*tokenlen < tokenmax)
145       token[*tokenlen] = *s;
146     if (*s == '"')
147     {
148       (*tokenlen)++;
149       return (s + 1);
150     }
151     if (*s == '\\')
152     {
153       if (!*++s)
154         break;
155
156       if (*tokenlen < tokenmax)
157         token[*tokenlen] = *s;
158     }
159     (*tokenlen)++;
160     s++;
161   }
162   RFC822Error = ERR_MISMATCH_QUOTE;
163   return NULL;
164 }
165
166 static const char *
167 next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
168 {
169   if (*s == '(')
170     return (parse_comment (s + 1, token, tokenlen, tokenmax));
171   if (*s == '"')
172     return (parse_quote (s + 1, token, tokenlen, tokenmax));
173   if (is_special (*s))
174   {
175     if (*tokenlen < tokenmax)
176       token[(*tokenlen)++] = *s;
177     return (s + 1);
178   }
179   while (*s)
180   {
181     if (ISSPACE ((unsigned char) *s) || is_special (*s))
182       break;
183     if (*tokenlen < tokenmax)
184       token[(*tokenlen)++] = *s;
185     s++;
186   }
187   return s;
188 }
189
190 static const char *
191 parse_mailboxdomain (const char *s, const char *nonspecial,
192                      char *mailbox, size_t *mailboxlen, size_t mailboxmax,
193                      char *comment, size_t *commentlen, size_t commentmax)
194 {
195   const char *ps;
196
197   while (*s)
198   {
199     SKIPWS (s);
200     if (strchr (nonspecial, *s) == NULL && is_special (*s))
201       return s;
202
203     if (*s == '(')
204     {
205       if (*commentlen && *commentlen < commentmax)
206         comment[(*commentlen)++] = ' ';
207       ps = next_token (s, comment, commentlen, commentmax);
208     }
209     else
210       ps = next_token (s, mailbox, mailboxlen, mailboxmax);
211     if (!ps)
212       return NULL;
213     s = ps;
214   }
215
216   return s;
217 }
218
219 static const char *
220 parse_address (const char *s,
221                char *token, size_t *tokenlen, size_t tokenmax,
222                char *comment, size_t *commentlen, size_t commentmax,
223                ADDRESS *addr)
224 {
225   s = parse_mailboxdomain (s, ".\"(\\",
226                            token, tokenlen, tokenmax,
227                            comment, commentlen, commentmax);
228   if (!s)
229     return NULL;
230
231   if (*s == '@')
232   {
233     if (*tokenlen < tokenmax)
234       token[(*tokenlen)++] = '@';
235     s = parse_mailboxdomain (s + 1, ".([]\\",
236                              token, tokenlen, tokenmax,
237                              comment, commentlen, commentmax);
238     if (!s)
239       return NULL;
240   }
241
242   terminate_string (token, *tokenlen, tokenmax);
243   addr->mailbox = safe_strdup (token);
244
245   if (*commentlen && !addr->personal)
246   {
247     terminate_string (comment, *commentlen, commentmax);
248     addr->personal = safe_strdup (comment);
249   }
250
251   return s;
252 }
253
254 static const char *
255 parse_route_addr (const char *s,
256                   char *comment, size_t *commentlen, size_t commentmax,
257                   ADDRESS *addr)
258 {
259   char token[STRING];
260   size_t tokenlen = 0;
261
262   SKIPWS (s);
263
264   /* find the end of the route */
265   if (*s == '@')
266   {
267     while (s && *s == '@')
268     {
269       if (tokenlen < sizeof (token) - 1)
270         token[tokenlen++] = '@';
271       s = parse_mailboxdomain (s + 1, ",.\\[](", token,
272                                &tokenlen, sizeof (token) - 1,
273                                comment, commentlen, commentmax);
274     }
275     if (!s || *s != ':')
276     {
277       RFC822Error = ERR_BAD_ROUTE;
278       return NULL; /* invalid route */
279     }
280
281     if (tokenlen < sizeof (token) - 1)
282       token[tokenlen++] = ':';
283     s++;
284   }
285
286   if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL)
287     return NULL;
288
289   if (*s != '>')
290   {
291     RFC822Error = ERR_BAD_ROUTE_ADDR;
292     return NULL;
293   }
294
295   if (!addr->mailbox)
296     addr->mailbox = safe_strdup ("@");
297
298   s++;
299   return s;
300 }
301
302 static const char *
303 parse_addr_spec (const char *s,
304                  char *comment, size_t *commentlen, size_t commentmax,
305                  ADDRESS *addr)
306 {
307   char token[STRING];
308   size_t tokenlen = 0;
309
310   s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr);
311   if (s && *s && *s != ',' && *s != ';')
312   {
313     RFC822Error = ERR_BAD_ADDR_SPEC;
314     return NULL;
315   }
316   return s;
317 }
318
319 static void
320 add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase,
321               char *comment, size_t *commentlen, size_t commentmax)
322 {
323   ADDRESS *cur = rfc822_new_address ();
324   
325   if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL)
326   {
327     rfc822_free_address (&cur);
328     return;
329   }
330
331   if (*last)
332     (*last)->next = cur;
333   else
334     *top = cur;
335   *last = cur;
336 }
337
338 ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s)
339 {
340   int ws_pending, nl;
341   const char *begin, *ps;
342   char comment[STRING], phrase[STRING];
343   size_t phraselen = 0, commentlen = 0;
344   ADDRESS *cur, *last = NULL;
345   
346   RFC822Error = 0;
347
348   last = top;
349   while (last && last->next)
350     last = last->next;
351
352   ws_pending = isspace ((unsigned char) *s);
353   if ((nl = mutt_strlen (s)))
354     nl = s[nl - 1] == '\n';
355   
356   SKIPWS (s);
357   begin = s;
358   while (*s)
359   {
360     if (*s == ',')
361     {
362       if (phraselen)
363       {
364         terminate_buffer (phrase, phraselen);
365         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
366       }
367       else if (commentlen && last && !last->personal)
368       {
369         terminate_buffer (comment, commentlen);
370         last->personal = safe_strdup (comment);
371       }
372
373 #ifdef EXACT_ADDRESS
374       if (last && !last->val)
375         last->val = mutt_substrdup (begin, s);
376 #endif
377       commentlen = 0;
378       phraselen = 0;
379       s++;
380       begin = s;
381       SKIPWS (begin);
382     }
383     else if (*s == '(')
384     {
385       if (commentlen && commentlen < sizeof (comment) - 1)
386         comment[commentlen++] = ' ';
387       if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL)
388       {
389         rfc822_free_address (&top);
390         return NULL;
391       }
392       s = ps;
393     }
394     else if (*s == ':')
395     {
396       cur = rfc822_new_address ();
397       terminate_buffer (phrase, phraselen);
398       cur->mailbox = safe_strdup (phrase);
399       cur->group = 1;
400
401       if (last)
402         last->next = cur;
403       else
404         top = cur;
405       last = cur;
406
407 #ifdef EXACT_ADDRESS
408       last->val = mutt_substrdup (begin, s);
409 #endif
410
411       phraselen = 0;
412       commentlen = 0;
413       s++;
414       begin = s;
415       SKIPWS (begin);
416     }
417     else if (*s == ';')
418     {
419       if (phraselen)
420       {
421         terminate_buffer (phrase, phraselen);
422         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
423       }
424       else if (commentlen && last && !last->personal)
425       {
426         terminate_buffer (comment, commentlen);
427         last->personal = safe_strdup (comment);
428       }
429 #ifdef EXACT_ADDRESS
430       if (last && !last->val)
431         last->val = mutt_substrdup (begin, s);
432 #endif
433
434       /* add group terminator */
435       cur = rfc822_new_address ();
436       if (last)
437       {
438         last->next = cur;
439         last = cur;
440       }
441
442       phraselen = 0;
443       commentlen = 0;
444       s++;
445       begin = s;
446       SKIPWS (begin);
447     }
448     else if (*s == '<')
449     {
450       terminate_buffer (phrase, phraselen);
451       cur = rfc822_new_address ();
452       if (phraselen)
453       {
454         if (cur->personal)
455           FREE (&cur->personal);
456         /* if we get something like "Michael R. Elkins" remove the quotes */
457         rfc822_dequote_comment (phrase);
458         cur->personal = safe_strdup (phrase);
459       }
460       if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL)
461       {
462         rfc822_free_address (&top);
463         rfc822_free_address (&cur);
464         return NULL;
465       }
466
467       if (last)
468         last->next = cur;
469       else
470         top = cur;
471       last = cur;
472
473       phraselen = 0;
474       commentlen = 0;
475       s = ps;
476     }
477     else
478     {
479       if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending)
480         phrase[phraselen++] = ' ';
481       if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL)
482       {
483         rfc822_free_address (&top);
484         return NULL;
485       }
486       s = ps;
487     }
488     ws_pending = isspace ((unsigned char) *s);
489     SKIPWS (s);
490   }
491   
492   if (phraselen)
493   {
494     terminate_buffer (phrase, phraselen);
495     terminate_buffer (comment, commentlen);
496     add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
497   }
498   else if (commentlen && last && !last->personal)
499   {
500     terminate_buffer (comment, commentlen);
501     last->personal = safe_strdup (comment);
502   }
503 #ifdef EXACT_ADDRESS
504   if (last)
505     last->val = mutt_substrdup (begin, s - nl < begin ? begin : s - nl);
506 #endif
507
508   return top;
509 }
510
511 void rfc822_qualify (ADDRESS *addr, const char *host)
512 {
513   char *p;
514
515   for (; addr; addr = addr->next)
516     if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL)
517     {
518       p = safe_malloc (mutt_strlen (addr->mailbox) + mutt_strlen (host) + 2);
519       sprintf (p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
520       FREE (&addr->mailbox);
521       addr->mailbox = p;
522     }
523 }
524
525 void
526 rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
527 {
528   if (strpbrk (value, specials))
529   {
530     char tmp[256], *pc = tmp;
531     size_t tmplen = sizeof (tmp) - 3;
532
533     *pc++ = '"';
534     for (; *value && tmplen > 1; value++)
535     {
536       if (*value == '\\' || *value == '"')
537       {
538         *pc++ = '\\';
539         tmplen--;
540       }
541       *pc++ = *value;
542       tmplen--;
543     }
544     *pc++ = '"';
545     *pc = 0;
546     strfcpy (buf, tmp, buflen);
547   }
548   else
549     strfcpy (buf, value, buflen);
550 }
551
552 void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr,
553                                   int display)
554 {
555   size_t len;
556   char *pbuf = buf;
557   char *pc;
558   
559   if (!addr)
560     return;
561
562   buflen--; /* save room for the terminal nul */
563
564 #ifdef EXACT_ADDRESS
565   if (addr->val)
566   {
567     if (!buflen)
568       goto done;
569     strfcpy (pbuf, addr->val, buflen);
570     len = mutt_strlen (pbuf);
571     pbuf += len;
572     buflen -= len;
573     if (addr->group)
574     {
575       if (!buflen)
576         goto done;
577       *pbuf++ = ':';
578       buflen--;
579       *pbuf = 0;
580     }
581     return;
582   }
583 #endif
584
585   if (addr->personal)
586   {
587     if (strpbrk (addr->personal, RFC822Specials))
588     {
589       if (!buflen)
590         goto done;
591       *pbuf++ = '"';
592       buflen--;
593       for (pc = addr->personal; *pc && buflen > 0; pc++)
594       {
595         if (*pc == '"' || *pc == '\\')
596         {
597           if (!buflen)
598             goto done;
599           *pbuf++ = '\\';
600           buflen--;
601         }
602         if (!buflen)
603           goto done;
604         *pbuf++ = *pc;
605         buflen--;
606       }
607       if (!buflen)
608         goto done;
609       *pbuf++ = '"';
610       buflen--;
611     }
612     else
613     {
614       if (!buflen)
615         goto done;
616       strfcpy (pbuf, addr->personal, buflen);
617       len = mutt_strlen (pbuf);
618       pbuf += len;
619       buflen -= len;
620     }
621
622     if (!buflen)
623       goto done;
624     *pbuf++ = ' ';
625     buflen--;
626   }
627
628   if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
629   {
630     if (!buflen)
631       goto done;
632     *pbuf++ = '<';
633     buflen--;
634   }
635
636   if (addr->mailbox)
637   {
638     if (!buflen)
639       goto done;
640     if (ascii_strcmp (addr->mailbox, "@") && !display)
641     {
642       strfcpy (pbuf, addr->mailbox, buflen);
643       len = mutt_strlen (pbuf);
644     }
645     else if (ascii_strcmp (addr->mailbox, "@") && display)
646     {
647       strfcpy (pbuf, mutt_addr_for_display (addr), buflen);
648       len = mutt_strlen (pbuf);
649     }
650     else
651     {
652       *pbuf = '\0';
653       len = 0;
654     }
655     pbuf += len;
656     buflen -= len;
657
658     if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
659     {
660       if (!buflen)
661         goto done;
662       *pbuf++ = '>';
663       buflen--;
664     }
665
666     if (addr->group)
667     {
668       if (!buflen)
669         goto done;
670       *pbuf++ = ':';
671       buflen--;
672       if (!buflen)
673         goto done;
674       *pbuf++ = ' ';
675       buflen--;
676     }
677   }
678   else
679   {
680     if (!buflen)
681       goto done;
682     *pbuf++ = ';';
683     buflen--;
684   }
685 done:
686   /* no need to check for length here since we already save space at the
687      beginning of this routine */
688   *pbuf = 0;
689 }
690
691 /* note: it is assumed that `buf' is nul terminated! */
692 int rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr, int display)
693 {
694   char *pbuf = buf;
695   size_t len = mutt_strlen (buf);
696   
697   buflen--; /* save room for the terminal nul */
698
699   if (len > 0)
700   {
701     if (len > buflen)
702       return pbuf - buf; /* safety check for bogus arguments */
703
704     pbuf += len;
705     buflen -= len;
706     if (!buflen)
707       goto done;
708     *pbuf++ = ',';
709     buflen--;
710     if (!buflen)
711       goto done;
712     *pbuf++ = ' ';
713     buflen--;
714   }
715
716   for (; addr && buflen > 0; addr = addr->next)
717   {
718     /* use buflen+1 here because we already saved space for the trailing
719        nul char, and the subroutine can make use of it */
720     rfc822_write_address_single (pbuf, buflen + 1, addr, display);
721
722     /* this should be safe since we always have at least 1 char passed into
723        the above call, which means `pbuf' should always be nul terminated */
724     len = mutt_strlen (pbuf);
725     pbuf += len;
726     buflen -= len;
727
728     /* if there is another address, and its not a group mailbox name or
729        group terminator, add a comma to separate the addresses */
730     if (addr->next && addr->next->mailbox && !addr->group)
731     {
732       if (!buflen)
733         goto done;
734       *pbuf++ = ',';
735       buflen--;
736       if (!buflen)
737         goto done;
738       *pbuf++ = ' ';
739       buflen--;
740     }
741   }
742 done:
743   *pbuf = 0;
744   return pbuf - buf;
745 }
746
747 /* this should be rfc822_cpy_adr */
748 ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr)
749 {
750   ADDRESS *p = rfc822_new_address ();
751
752 #ifdef EXACT_ADDRESS
753   p->val = safe_strdup (addr->val);
754 #endif
755   p->personal = safe_strdup (addr->personal);
756   p->mailbox = safe_strdup (addr->mailbox);
757   p->group = addr->group;
758   return p;
759 }
760
761 /* this should be rfc822_cpy_adrlist */
762 ADDRESS *rfc822_cpy_adr (ADDRESS *addr)
763 {
764   ADDRESS *top = NULL, *last = NULL;
765   
766   for (; addr; addr = addr->next)
767   {
768     if (last)
769     {
770       last->next = rfc822_cpy_adr_real (addr);
771       last = last->next;
772     }
773     else
774       top = last = rfc822_cpy_adr_real (addr);
775   }
776   return top;
777 }
778
779 /* append list 'b' to list 'a' and return the last element in the new list */
780 ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b)
781 {
782   ADDRESS *tmp = *a;
783
784   while (tmp && tmp->next)
785     tmp = tmp->next;
786   if (!b)
787     return tmp;
788   if (tmp)
789     tmp->next = rfc822_cpy_adr (b);
790   else
791     tmp = *a = rfc822_cpy_adr (b);
792   while (tmp && tmp->next)
793     tmp = tmp->next;
794   return tmp;
795 }
796
797 /* incomplete. Only used to thwart the APOP MD5 attack (#2846). */
798 int rfc822_valid_msgid (const char *msgid)
799 {
800   /* msg-id         = "<" addr-spec ">"
801    * addr-spec      = local-part "@" domain
802    * local-part     = word *("." word)
803    * word           = atom / quoted-string
804    * atom           = 1*<any CHAR except specials, SPACE and CTLs>
805    * CHAR           = ( 0.-127. )
806    * specials       = "(" / ")" / "<" / ">" / "@"
807                     / "," / ";" / ":" / "\" / <">
808                     / "." / "[" / "]"
809    * SPACE          = ( 32. )
810    * CTLS           = ( 0.-31., 127.)
811    * quoted-string  = <"> *(qtext/quoted-pair) <">
812    * qtext          = <any CHAR except <">, "\" and CR>
813    * CR             = ( 13. )
814    * quoted-pair    = "\" CHAR
815    * domain         = sub-domain *("." sub-domain)
816    * sub-domain     = domain-ref / domain-literal
817    * domain-ref     = atom
818    * domain-literal = "[" *(dtext / quoted-pair) "]"
819    */
820
821   char* dom;
822   unsigned int l, i;
823
824   if (!msgid || !*msgid)
825     return -1;
826
827   l = mutt_strlen (msgid);
828   if (l < 5) /* <atom@atom> */
829     return -1;
830   if (msgid[0] != '<' || msgid[l-1] != '>')
831     return -1;
832   if (!(dom = strrchr (msgid, '@')))
833     return -1;
834
835   /* TODO: complete parser */
836   for (i = 0; i < l; i++)
837     if ((unsigned char)msgid[i] > 127)
838       return -1;
839
840   return 0;
841 }
842
843 #ifdef TESTING
844 int safe_free (void **p)        /* __SAFE_FREE_CHECKED__ */
845 {
846   free(*p);             /* __MEM_CHECKED__ */
847   *p = 0;
848 }
849
850 int main (int argc, char **argv)
851 {
852   ADDRESS *list;
853   char buf[256];
854 # if 0
855   char *str = "michael, Michael Elkins <me@mutt.org>, testing a really complex address: this example <@contains.a.source.route,@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)";
856 # else
857   char *str = "a b c ";
858 # endif
859   
860   list = rfc822_parse_adrlist (NULL, str);
861   buf[0] = 0;
862   rfc822_write_address (buf, sizeof (buf), list);
863   rfc822_free_address (&list);
864   puts (buf);
865   exit (0);
866 }
867 #endif