1 /* spool.c -- Routines for spooling/parsing messages from a prot stream
2  *
3  * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  */
42 
43 #include <config.h>
44 
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>
47 #endif
48 #include <stdio.h>
49 #include <string.h>
50 
51 #include "assert.h"
52 #include "spool.h"
53 #include "util.h"
54 #include "xmalloc.h"
55 #include "global.h"
56 #include "ptrarray.h"
57 #include "hash.h"
58 
59 /* generated headers are not necessarily in current directory */
60 #include "imap/imap_err.h"
61 
62 struct header_t {
63     char *name;
64     char *body;
65     char *raw;
66     struct header_t *next;
67     struct header_t *prev;
68 };
69 
70 struct hdrcache_t {
71     hash_table cache;       /* hash table of headers for quick retrieval     */
72     struct header_t *head;  /* head of double-linked list of ordered headers */
73     struct header_t *tail;  /* tail of double-linked list of ordered headers */
74     ptrarray_t getheader_cache;  /* header bodies returned by spool_getheader()   */
75 };
76 
spool_new_hdrcache(void)77 EXPORTED hdrcache_t spool_new_hdrcache(void)
78 {
79     hdrcache_t cache = xzmalloc(sizeof(struct hdrcache_t));
80 
81     if (!construct_hash_table(&cache->cache, 4000, 0)) {
82         free(cache);
83         cache = NULL;
84     }
85 
86     return cache;
87 }
88 
89 /* take a list of headers, pull the first one out and return it in
90    name and contents.
91 
92    copies fin to fout, massaging
93 
94    returns 0 on success, negative on failure */
95 typedef enum {
96     NAME_START,
97     NAME,
98     COLON,
99     BODY_START,
100     BODY
101 } state;
102 
103 /* we don't have to worry about dotstuffing here, since it's illegal
104    for a header to begin with a dot!
105 
106    returns 0 on success, filling in 'headname' and 'contents' with a static
107    pointer (blech).
108    on end of headers, returns 0 with NULL 'headname' and NULL 'contents'
109 
110    on error, returns < 0
111 */
parseheader(struct protstream * fin,FILE * fout,char ** headname,char ** contents,char ** rawvalue,const char ** skipheaders)112 static int parseheader(struct protstream *fin, FILE *fout,
113                        char **headname, char **contents,
114                        char **rawvalue,
115                        const char **skipheaders)
116 {
117     int c;
118     static struct buf name = BUF_INITIALIZER;
119     static struct buf body = BUF_INITIALIZER;
120     static struct buf raw = BUF_INITIALIZER;
121     state s = NAME_START;
122     int r = 0;
123     int reject8bit = config_getswitch(IMAPOPT_REJECT8BIT);
124     int munge8bit = config_getswitch(IMAPOPT_MUNGE8BIT);
125     const char **skip = NULL;
126 
127     buf_reset(&name);
128     buf_reset(&body);
129     buf_reset(&raw);
130 
131     /* there are two ways out of this loop, both via gotos:
132        either we successfully read a header (got_header)
133        or we hit an error (ph_error) */
134     while ((c = prot_getc(fin)) != EOF) { /* examine each character */
135         /* reject \0 */
136         if (!c) {
137             r = IMAP_MESSAGE_CONTAINSNULL;
138             goto ph_error;
139         }
140 
141         switch (s) {
142         case NAME_START:
143             if (c == '.') {
144                 int peek;
145 
146                 peek = prot_getc(fin);
147                 prot_ungetc(peek, fin);
148 
149                 if (peek == '\r' || peek == '\n') {
150                     /* just reached the end of message */
151                     r = 0;
152                     goto ph_error;
153                 }
154             }
155             if (c == '\r' || c == '\n') {
156                 /* just reached the end of headers */
157                 r = 0;
158                 goto ph_error;
159             }
160             /* field-name      =       1*ftext
161                ftext           =       %d33-57 / %d59-126
162                                        ; Any character except
163                                        ;  controls, SP, and
164                                        ;  ":". */
165             if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
166                 /* invalid header name */
167                 r = IMAP_MESSAGE_BADHEADER;
168                 goto ph_error;
169             }
170             buf_putc(&name, c);
171             s = NAME;
172             break;
173 
174         case NAME:
175             if (c == ' ' || c == '\t' || c == ':') {
176                 buf_cstring(&name);
177                 /* see if this header is in our skip list */
178                 for (skip = skipheaders;
179                      skip && *skip && strcasecmp(name.s, *skip); skip++);
180                 if (!skip || !*skip) {
181                     /* write the header name to the output */
182                     buf_appendcstr(&raw, name.s);
183                     skip = NULL;
184                 }
185                 s = (c == ':' ? BODY_START : COLON);
186                 break;
187             }
188             if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
189                 r = IMAP_MESSAGE_BADHEADER;
190                 goto ph_error;
191             }
192             buf_putc(&name, c);
193             break;
194 
195         case COLON:
196             if (c == ':') {
197                 s = BODY_START;
198             } else if (c != ' ' && c != '\t') {
199                 /* i want to avoid confusing dot-stuffing later */
200                 while (c == '.') {
201                     if (!skip) buf_putc(&raw, c);
202                     c = prot_getc(fin);
203                 }
204                 r = IMAP_MESSAGE_BADHEADER;
205                 goto ph_error;
206             }
207             break;
208 
209         case BODY_START:
210             if (c == ' ' || c == '\t') /* eat the whitespace */
211                 break;
212             buf_reset(&body);
213             s = BODY;
214             /* falls through! */
215         case BODY:
216             /* now we want to convert all newlines into \r\n */
217             if (c == '\r' || c == '\n') {
218                 int peek;
219 
220                 peek = prot_getc(fin);
221 
222                 if (!skip) buf_appendcstr(&raw, "\r\n");
223                 /* we should peek ahead to see if it's folded whitespace */
224                 if (c == '\r' && peek == '\n') {
225                     c = prot_getc(fin);
226                 } else {
227                     c = peek; /* single newline separator */
228                 }
229                 if (c != ' ' && c != '\t') {
230                     /* this is the end of the header */
231                     buf_cstring(&body);
232                     buf_cstring(&raw);
233                     prot_ungetc(c, fin);
234                     goto got_header;
235                 }
236             }
237             if (c >= 0x80) {
238                 if (reject8bit) {
239                     /* We have been configured to reject all mail of this
240                        form. */
241                     r = IMAP_MESSAGE_CONTAINS8BIT;
242                     goto ph_error;
243                 } else if (munge8bit) {
244                     /* We have been configured to munge all mail of this
245                        form. */
246                     c = 'X';
247                 }
248             }
249             /* just an ordinary character */
250             buf_putc(&body, c);
251         }
252 
253         /* copy this to the output */
254         if (s != NAME && !skip) buf_putc(&raw, c);
255     }
256 
257     /* if we fall off the end of the loop, we hit some sort of error
258        condition */
259 
260  ph_error:
261     /* we still output on error */
262     if (fout) fputs(buf_cstring(&raw), fout);
263 
264     /* put the last character back; we'll copy it later */
265     if (c != EOF) prot_ungetc(c, fin);
266 
267     /* and we didn't get a header */
268     if (headname != NULL) *headname = NULL;
269     if (contents != NULL) *contents = NULL;
270     if (rawvalue != NULL) *rawvalue = NULL;
271 
272     return r;
273 
274  got_header:
275     if (fout) fputs(buf_cstring(&raw), fout);
276 
277     /* Note: xstrdup()ing the string ensures we return
278      * a minimal length string with no allocation slack
279      * at the end */
280     if (headname != NULL) *headname = xstrdup(name.s);
281     if (contents != NULL) *contents = xstrdup(body.s);
282     if (rawvalue != NULL) *rawvalue = xstrdup(raw.s);
283 
284     return 0;
285 }
286 
__spool_cache_header(char * name,char * body,char * raw,hash_table * table)287 static struct header_t *__spool_cache_header(char *name, char *body, char *raw,
288                                              hash_table *table)
289 {
290     ptrarray_t *contents;
291     struct header_t *hdr = xzmalloc(sizeof(struct header_t));
292 
293     hdr->name = name;
294     hdr->body = body;
295     hdr->raw = raw;
296 
297     /* add header to hash table */
298     char *lcname = lcase(xstrdup(name));
299     contents = (ptrarray_t *) hash_lookup(lcname, table);
300 
301     if (!contents) contents = hash_insert(lcname, ptrarray_new(), table);
302     ptrarray_append(contents, hdr);
303 
304     free(lcname);
305 
306     return hdr;
307 }
308 
spool_prepend_header_raw(char * name,char * body,char * raw,hdrcache_t cache)309 EXPORTED void spool_prepend_header_raw(char *name, char *body, char *raw, hdrcache_t cache)
310 {
311     struct header_t *hdr = __spool_cache_header(name, body, raw, &cache->cache);
312 
313     /* link header at head of list */
314     hdr->next = cache->head;
315 
316     if (cache->head) cache->head->prev = hdr;
317     else cache->tail = hdr;
318 
319     cache->head = hdr;
320 }
321 
322 
spool_prepend_header(char * name,char * body,hdrcache_t cache)323 EXPORTED void spool_prepend_header(char *name, char *body, hdrcache_t cache)
324 {
325     spool_prepend_header_raw(name, body, NULL, cache);
326 }
327 
spool_append_header_raw(char * name,char * body,char * raw,hdrcache_t cache)328 EXPORTED void spool_append_header_raw(char *name, char *body, char *raw, hdrcache_t cache)
329 {
330     struct header_t *hdr = __spool_cache_header(name, body, raw, &cache->cache);
331 
332     /* link header at tail of list */
333     hdr->prev = cache->tail;
334 
335     if (cache->tail) cache->tail->next = hdr;
336     else cache->head = hdr;
337 
338     cache->tail = hdr;
339 }
340 
spool_append_header(char * name,char * body,hdrcache_t cache)341 EXPORTED void spool_append_header(char *name, char *body, hdrcache_t cache)
342 {
343     spool_append_header_raw(name, body, NULL, cache);
344 }
345 
spool_replace_header(char * name,char * body,hdrcache_t cache)346 EXPORTED void spool_replace_header(char *name, char *body, hdrcache_t cache)
347 {
348     spool_remove_header(xstrdup(name), cache);
349     spool_append_header(name, body, cache);
350 }
351 
__spool_remove_header(char * name,int first,int last,hdrcache_t cache)352 static void __spool_remove_header(char *name, int first, int last,
353                                   hdrcache_t cache)
354 {
355     ptrarray_t *contents =
356         (ptrarray_t *) hash_lookup(lcase(name), &cache->cache);
357 
358     if (contents) {
359         int idx;
360 
361         /* normalize indices */
362         if (first < 0) first += ptrarray_size(contents);
363         if (last < 0) {
364             last += ptrarray_size(contents);
365             if (last < 0) first = 0;
366         }
367         else if (last >= ptrarray_size(contents)) first = last + 1;
368 
369         for (idx = last; idx >= first; idx--) {
370             /* remove header from ptrarray */
371             struct header_t *hdr = ptrarray_remove(contents, idx);
372 
373             /* unlink header from list */
374             if (hdr->prev) hdr->prev->next = hdr->next;
375             else cache->head = hdr->next;
376             if (hdr->next) hdr->next->prev = hdr->prev;
377             else cache->tail = hdr->prev;
378 
379             /* free header_t */
380             free(hdr->name);
381             free(hdr->body);
382             free(hdr->raw);
383             free(hdr);
384         }
385     }
386 
387     free(name);
388 }
389 
spool_remove_header(char * name,hdrcache_t cache)390 EXPORTED void spool_remove_header(char *name, hdrcache_t cache)
391 {
392     __spool_remove_header(name, 0, -1, cache);
393 }
394 
spool_remove_header_instance(char * name,int n,hdrcache_t cache)395 EXPORTED void spool_remove_header_instance(char *name, int n, hdrcache_t cache)
396 {
397     if (!n) return;
398     if (n > 0) n--; /* normalize to zero */
399 
400     __spool_remove_header(name, n, n, cache);
401 }
402 
spool_fill_hdrcache(struct protstream * fin,FILE * fout,hdrcache_t cache,const char ** skipheaders)403 EXPORTED int spool_fill_hdrcache(struct protstream *fin, FILE *fout,
404                                  hdrcache_t cache, const char **skipheaders)
405 {
406     int r = 0;
407 
408     /* let's fill that header cache */
409     for (;;) {
410         char *name = NULL, *body = NULL, *raw = NULL;
411 
412         if ((r = parseheader(fin, fout, &name, &body, &raw, skipheaders)) < 0) {
413             break;
414         }
415         if (!name) {
416             /* reached the end of headers */
417             free(body);
418             free(raw);
419             break;
420         }
421 
422         /* put it in the hash table */
423         spool_append_header_raw(name, body, raw, cache);
424     }
425 
426     return r;
427 }
428 
spool_getheader(hdrcache_t cache,const char * phead)429 EXPORTED const char **spool_getheader(hdrcache_t cache, const char *phead)
430 {
431     char *head;
432     ptrarray_t *contents;
433 
434     assert(cache && phead);
435 
436     head = xstrdup(phead);
437     lcase(head);
438 
439     /* check the cache */
440     contents = (ptrarray_t *) hash_lookup(head, &cache->cache);
441 
442     free(head);
443 
444     if (contents && ptrarray_size(contents)) {
445         strarray_t *array = strarray_new();
446         /* build read-only array of header bodies */
447 
448         int i;
449         for (i = 0; i < ptrarray_size(contents); i++) {
450             struct header_t *hdr = ptrarray_nth(contents, i);
451             strarray_append(array, hdr->body);
452         }
453 
454         /* cache the response so we clean it up later */
455         ptrarray_append(&cache->getheader_cache, array);
456 
457         return (const char **) array->data;
458     }
459 
460     return NULL;
461 }
462 
__spool_free_hdrcache(ptrarray_t * pa)463 static void __spool_free_hdrcache(ptrarray_t *pa)
464 {
465     int idx;
466 
467     for (idx = ptrarray_size(pa) - 1; idx >= 0; idx--) {
468         struct header_t *hdr = ptrarray_nth(pa, idx);
469 
470         free(hdr->name);
471         free(hdr->body);
472         free(hdr->raw);
473         free(hdr);
474     }
475     ptrarray_free(pa);
476 }
477 
spool_free_hdrcache(hdrcache_t cache)478 EXPORTED void spool_free_hdrcache(hdrcache_t cache)
479 {
480     int i;
481 
482     if (!cache) return;
483 
484     free_hash_table(&cache->cache, (void (*)(void *)) __spool_free_hdrcache);
485 
486     for (i = 0; i < cache->getheader_cache.count; i++) {
487         strarray_t *item = ptrarray_nth(&cache->getheader_cache, i);
488         strarray_free(item);
489     }
490     ptrarray_fini(&cache->getheader_cache);
491 
492     free(cache);
493 }
494 
spool_enum_hdrcache(hdrcache_t cache,void (* proc)(const char *,const char *,const char *,void *),void * rock)495 EXPORTED void spool_enum_hdrcache(hdrcache_t cache,
496                          void (*proc)(const char *, const char *, const char *, void *),
497                          void *rock)
498 {
499     struct header_t *hdr;
500 
501     if (!cache) return;
502 
503     for (hdr = cache->head; hdr; hdr = hdr->next) {
504         proc(hdr->name, hdr->body, hdr->raw, rock);
505     }
506 }
507 
508 /* copies the message from fin to fout, massaging accordingly:
509    . newlines are fiddled to \r\n
510    . "." terminates
511    . embedded NULs are rejected
512    . bare \r are removed
513 */
spool_copy_msg(struct protstream * fin,FILE * fout)514 EXPORTED int spool_copy_msg(struct protstream *fin, FILE *fout)
515 {
516     char buf[8192], *p;
517     int r = 0;
518 
519     /* -2: Might need room to add a \r\n\0 set */
520     while (prot_fgets(buf, sizeof(buf)-2, fin)) {
521         p = buf + strlen(buf) - 1;
522         if (p < buf) {
523             /* buffer start with a \0 */
524             r = IMAP_MESSAGE_CONTAINSNULL;
525             continue; /* need to eat the rest of the message */
526         }
527         else if (buf[0] == '\r' && buf[1] == '\0') {
528             /* The message contained \r\0, and fgets is confusing us. */
529             r = IMAP_MESSAGE_CONTAINSNULL;
530             continue; /* need to eat the rest of the message */
531         }
532         else if (p[0] == '\r') {
533             /*
534              * We were unlucky enough to get a CR just before we ran
535              * out of buffer--put it back.
536              */
537             prot_ungetc('\r', fin);
538             *p = '\0';
539         }
540         else if (p[0] == '\n' && (p == buf || p[-1] != '\r')) {
541             /* found an \n without a \r */
542             p[0] = '\r';
543             p[1] = '\n';
544             p[2] = '\0';
545         }
546         else if (p[0] != '\n' && (strlen(buf) < sizeof(buf)-3)) {
547             /* line contained a \0 not at the end */
548             r = IMAP_MESSAGE_CONTAINSNULL;
549             continue;
550         }
551 
552         /* Remove any lone CR characters */
553         while ((p = strchr(buf, '\r')) && p[1] != '\n') {
554             /* Src/Target overlap, use memmove */
555             /* strlen(p) will result in copying the NUL byte as well */
556             memmove(p, p+1, strlen(p));
557         }
558 
559         if (buf[0] == '.') {
560             if (buf[1] == '\r' && buf[2] == '\n') {
561                 /* End of message */
562                 goto dot;
563             }
564             /* Remove the dot-stuffing */
565             if (fout) fputs(buf+1, fout);
566         } else {
567             if (fout) fputs(buf, fout);
568         }
569     }
570 
571     /* wow, serious error---got a premature EOF. */
572     return IMAP_IOERROR;
573 
574   dot:
575     return r;
576 }
577