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