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