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