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