]> Pileus Git - ~andy/fetchmail/blob - rfc822.c
Fix groupnames bug.
[~andy/fetchmail] / rfc822.c
1 /*
2  * rfc822.c -- code for slicing and dicing RFC822 mail headers
3  *
4  * Copyright 1997 by Eric S. Raymond
5  * For license terms, see the file COPYING in this directory.
6  */
7
8 #include  <stdio.h>
9 #include  <ctype.h>
10 #include  <string.h>
11 #if defined(STDC_HEADERS)
12 #include  <stdlib.h>
13 #endif
14
15 #include "fetchmail.h"
16
17 #define HEADER_END(p)   ((p)[0] == '\n' && ((p)[1] != ' ' && (p)[1] != '\t'))
18
19 #ifdef TESTMAIN
20 static int verbose;
21 #endif /* TESTMAIN */
22
23 char *reply_hack(buf, host)
24 /* hack message headers so replies will work properly */
25 char *buf;              /* header to be hacked */
26 const char *host;       /* server hostname */
27 {
28     char *from, *cp, last_nws = '\0';
29     int parendepth, state, has_bare_name_part, has_host_part;
30     int addresscount = 1;
31
32     if (strncasecmp("From: ", buf, 6)
33         && strncasecmp("To: ", buf, 4)
34         && strncasecmp("Reply-To: ", buf, 10)
35         && strncasecmp("Return-Path: ", buf, 13)
36         && strncasecmp("Cc: ", buf, 4)
37         && strncasecmp("Bcc: ", buf, 5)) {
38         return(buf);
39     }
40
41 #ifndef TESTMAIN
42     /* make room to hack the address; buf must be malloced */
43     for (cp = buf; *cp; cp++)
44         if (*cp == ',' || isspace(*cp))
45             addresscount++;
46     buf = (char *)realloc(buf, strlen(buf) + addresscount * strlen(host) + 1);
47 #endif /* TESTMAIN */
48
49     parendepth = state = 0;
50     has_host_part = has_bare_name_part = FALSE;
51     for (from = buf; *from; from++)
52     {
53 #ifdef TESTMAIN
54         if (verbose)
55         {
56             printf("state %d: %s", state, buf);
57             printf("%*s^\n", from - buf + 10, " ");
58         }
59 #endif /* TESTMAIN */
60         if (state != 2)
61             if (*from == '(')
62                 ++parendepth;
63             else if (*from == ')')
64                 --parendepth;
65
66         if (!parendepth && !has_host_part)
67             switch (state)
68             {
69             case 0:     /* before header colon */
70                 if (*from == ':')
71                     state = 1;
72                 break;
73
74             case 1:     /* we've seen the colon, we're looking for addresses */
75                 if (!isspace(*from))
76                     last_nws = *from;
77                 if (*from == '<')
78                     state = 3;
79                 else if (*from == '@')
80                     has_host_part = TRUE;
81                 else if (*from == '"')
82                     state = 2;
83                 /*
84                  * Not expanding on last non-WS == ';' deals with groupnames,
85                  * an obscure misfeature described in sections
86                  * 6.1, 6.2.6, and A.1.5 of the RFC822 standard.
87                  */
88                 else if ((*from == ',' || HEADER_END(from) || from[1] == '(')
89                          && has_bare_name_part
90                          && !has_host_part
91                          && last_nws != ';')
92                 {
93                     int hostlen;
94
95                     while (isspace(*from) || (*from == ','))
96                         --from;
97                     from++;
98                     hostlen = strlen(host);
99                     for (cp = from + strlen(from); cp >= from; --cp)
100                         cp[hostlen+1] = *cp;
101                     *from++ = '@';
102                     memcpy(from, host, hostlen);
103                     from += hostlen;
104                     has_host_part = TRUE;
105                 } 
106                 else if (!isspace(*from))
107                     has_bare_name_part = TRUE;
108                 break;
109
110             case 2:     /* we're in a string */
111                 if (*from == '"')
112                     state = 1;
113                 break;
114
115             case 3:     /* we're in a <>-enclosed address */
116                 if (*from == '@')
117                     has_host_part = TRUE;
118                 else if (*from == '>')
119                 {
120                     state = 1;
121                     if (!has_host_part)
122                     {
123                         int hostlen;
124
125                         hostlen = strlen(host);
126                         for (cp = from + strlen(from); cp >= from; --cp)
127                             cp[hostlen+1] = *cp;
128                         *from++ = '@';
129                         memcpy(from, host, hostlen);
130                         from += hostlen;
131                         has_host_part = TRUE;
132                     }
133                 }
134                 break;
135             }
136
137         /*
138          * If we passed a comma, reset everything.
139          */
140         if (from[-1] == ',' && !parendepth) {
141           has_host_part = has_bare_name_part = FALSE;
142         }
143     }
144
145     return(buf);
146 }
147
148 char *nxtaddr(hdr)
149 /* parse addresses in succession out of a specified RFC822 header */
150 const char *hdr;        /* header to be parsed, NUL to continue previous hdr */
151 {
152     static char *tp, address[POPBUFSIZE+1];
153     static const char *hp;
154     static int  state, oldstate;
155 #ifdef TESTMAIN
156     static const char *orighdr;
157 #endif /* TESTMAIN */
158     int parendepth = 0;
159
160 #define START_HDR       0       /* before header colon */
161 #define SKIP_JUNK       1       /* skip whitespace, \n, and junk */
162 #define BARE_ADDRESS    2       /* collecting address without delimiters */
163 #define INSIDE_DQUOTE   3       /* inside double quotes */
164 #define INSIDE_PARENS   4       /* inside parentheses */
165 #define INSIDE_BRACKETS 5       /* inside bracketed address */
166 #define ENDIT_ALL       6       /* after last address */
167
168     if (hdr)
169     {
170         hp = hdr;
171         state = START_HDR;
172 #ifdef TESTMAIN
173         orighdr = hdr;
174 #endif /* TESTMAIN */
175         tp = address;
176     }
177
178     for (; *hp; hp++)
179     {
180 #ifdef TESTMAIN
181         if (verbose)
182         {
183             printf("state %d: %s", state, orighdr);
184             printf("%*s^\n", hp - orighdr + 10, " ");
185         }
186 #endif /* TESTMAIN */
187
188         if (state == ENDIT_ALL)         /* after last address */
189             return(NULL);
190         else if (HEADER_END(hp))
191         {
192             state = ENDIT_ALL;
193             if (tp > address)
194             {
195                 while (isspace(*--tp))
196                     continue;
197                 *++tp = '\0';
198             }
199             return(tp > address ? (tp = address) : (char *)NULL);
200         }
201         else if (*hp == '\\')           /* handle RFC822 escaping */
202         {
203             if (state != INSIDE_PARENS)
204             {
205                 *tp++ = *hp++;                  /* take the escape */
206                 *tp++ = *hp;                    /* take following char */
207             }
208         }
209         else switch (state)
210         {
211         case START_HDR:   /* before header colon */
212             if (*hp == ':')
213                 state = SKIP_JUNK;
214             break;
215
216         case SKIP_JUNK:         /* looking for address start */
217             if (*hp == '"')     /* quoted string */
218             {
219                 oldstate = SKIP_JUNK;
220                 state = INSIDE_DQUOTE;
221                 *tp++ = *hp;
222             }
223             else if (*hp == '(')        /* address comment -- ignore */
224             {
225                 parendepth = 1;
226                 oldstate = SKIP_JUNK;
227                 state = INSIDE_PARENS;    
228             }
229             else if (*hp == '<')        /* begin <address> */
230             {
231                 state = INSIDE_BRACKETS;
232                 tp = address;
233             }
234             else if (*hp != ',' && !isspace(*hp))
235             {
236                 --hp;
237                 state = BARE_ADDRESS;
238             }
239             break;
240
241         case BARE_ADDRESS:      /* collecting address without delimiters */
242             if (*hp == ',')     /* end of address */
243             {
244                 if (tp > address)
245                 {
246                     *tp++ = '\0';
247                     state = SKIP_JUNK;
248                     return(tp = address);
249                 }
250             }
251             else if (*hp == '(')        /* beginning of comment */
252             {
253                 parendepth = 1;
254                 oldstate = BARE_ADDRESS;
255                 state = INSIDE_PARENS;    
256             }
257             else if (*hp == '<')        /* beginning of real address */
258             {
259                 state = INSIDE_BRACKETS;
260                 tp = address;
261             }
262             else if (!isspace(*hp))     /* just take it, ignoring whitespace */
263                 *tp++ = *hp;
264             break;
265
266         case INSIDE_DQUOTE:     /* we're in a quoted string, copy verbatim */
267             if (*hp != '"')
268                 *tp++ = *hp;
269             else
270             {
271                 *tp++ = *hp;
272                 state = oldstate;
273             }
274             break;
275
276         case INSIDE_PARENS:     /* we're in a parenthesized comment, ignore */
277             if (*hp == '(')
278                 ++parendepth;
279             else if (*hp == ')')
280                 --parendepth;
281             if (parendepth == 0)
282                 state = oldstate;
283             break;
284
285         case INSIDE_BRACKETS:   /* possible <>-enclosed address */
286             if (*hp == '>')     /* end of address */
287             {
288                 *tp++ = '\0';
289                 state = SKIP_JUNK;
290                 ++hp;
291                 return(tp = address);
292             }
293             else if (*hp == '<')        /* nested <> */
294                 tp = address;
295             else if (*hp == '"')        /* quoted address */
296             {
297                 *tp++ = *hp;
298                 oldstate = INSIDE_BRACKETS;
299                 state = INSIDE_DQUOTE;
300             }
301             else                        /* just copy address */
302                 *tp++ = *hp;
303             break;
304         }
305     }
306
307     return(NULL);
308 }
309
310 #ifdef TESTMAIN
311 static void parsebuf(char *longbuf, int reply)
312 {
313     char        *cp;
314
315     if (reply)
316     {
317         reply_hack(longbuf, "HOSTNAME.NET");
318         printf("Rewritten buffer: %s", longbuf);
319     }
320     else
321         if ((cp = nxtaddr(longbuf)) != (char *)NULL)
322             do {
323                 printf("\t-> \"%s\"\n", cp);
324             } while
325                 ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
326 }
327
328
329
330 main(int argc, char *argv[])
331 {
332     char        buf[MSGBUFSIZE], longbuf[BUFSIZ];
333     int         ch, reply;
334     
335     verbose = reply = FALSE;
336     while ((ch = getopt(argc, argv, "rv")) != EOF)
337         switch(ch)
338         {
339         case 'r':
340             reply = TRUE;
341             break;
342
343         case 'v':
344             verbose = TRUE;
345             break;
346         }
347
348     while (fgets(buf, sizeof(buf)-1, stdin))
349     {
350         if (buf[0] == ' ' || buf[0] == '\t')
351             strcat(longbuf, buf);
352         else if (!strncasecmp("From: ", buf, 6)
353                     || !strncasecmp("To: ", buf, 4)
354                     || !strncasecmp("Reply-", buf, 6)
355                     || !strncasecmp("Cc: ", buf, 4)
356                     || !strncasecmp("Bcc: ", buf, 5))
357             strcpy(longbuf, buf);       
358         else if (longbuf[0])
359         {
360             if (verbose)
361                 fputs(longbuf, stdout);
362             parsebuf(longbuf, reply);
363             longbuf[0] = '\0';
364         }
365     }
366     if (longbuf[0])
367     {
368         if (verbose)
369             fputs(longbuf, stdout);
370         parsebuf(longbuf, reply);
371     }
372 }
373 #endif /* TESTMAIN */
374
375 /* rfc822.c end */