1 /*:ts=8*/
2 /*****************************************************************************
3  * FIDOGATE --- Gateway UNIX Mail/News <-> FTN NetMail/EchoMail
4  *
5  *
6  * MIME stuff
7  *
8  *****************************************************************************
9  * Copyright (C) 1990-2002
10  *  _____ _____
11  * |     |___  |   Martin Junius             <mj@fidogate.org>
12  * | | | |   | |   Radiumstr. 18
13  * |_|_|_|@home|   D-51069 Koeln, Germany
14  *
15  * This file is part of FIDOGATE.
16  *
17  * FIDOGATE is free software; you can redistribute it and/or modify it
18  * under the terms of the GNU General Public License as published by the
19  * Free Software Foundation; either version 2, or (at your option) any
20  * later version.
21  *
22  * FIDOGATE is distributed in the hope that it will be useful, but
23  * WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with FIDOGATE; see the file COPYING.  If not, write to the Free
29  * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
30  *****************************************************************************/
31 
32 #include "fidogate.h"
33 
34 static int is_qpx(int);
35 static int x2toi(char *);
36 void mime_free(void);
37 
38 static MIMEInfo *mime_list = NULL;
39 
mime_get_main_charset(RFCHeader * header)40 static char *mime_get_main_charset(RFCHeader *header)
41 {
42     MIMEInfo *mime;
43     char *charset;
44     mime = get_mime(s_header_getcomplete(header, "MIME-Version"),
45                     s_header_getcomplete(header, "Content-Type"),
46                     s_header_getcomplete(header, "Content-Transfer-Encoding"));
47     charset = strsave(mime->type_charset);
48     mime_free();
49     return charset;
50 }
51 
is_qpx(int c)52 static int is_qpx(int c)
53 {
54     return isxdigit(c) /**is_digit(c) || (c>='A' && c<='F')**/ ;
55 }
56 
x2toi(char * s)57 static int x2toi(char *s)
58 {
59     int val = 0;
60     int n;
61 
62     n = toupper(*s) - (isalpha(*s) ? 'A' - 10 : '0');
63     val = val * 16 + n;
64     s++;
65     n = toupper(*s) - (isalpha(*s) ? 'A' - 10 : '0');
66     val = val * 16 + n;
67 
68     return val;
69 }
70 
71 /*
72  * Dequote string with MIME-style quoted-printable =XX
73  */
mime_dequote(char * d,size_t n,char * s)74 char *mime_dequote(char *d, size_t n, char *s)
75 {
76     int i, c = 0;
77 
78     for (i = 0; i < n - 1 && *s; i++, s++) {
79         if (s[0] == '=') {      /* MIME quoted printable */
80             if (is_qpx(s[1]) && is_qpx(s[2])) { /* =XX */
81                 c = x2toi(s + 1);
82                 s += 2;
83             } else if (s[1] == '\n' ||  /* =<LF> and */
84                        (s[1] == '\r' && s[2] == '\n')) {    /* =<CR><LF> */
85                 break;
86             }
87         } else if (s[0] == '_') {   /* Underscore */
88             c = ' ';
89         } else {                /* Nothing special to do */
90             c = *s;
91         }
92         d[i] = c;
93     }
94     d[i] = 0;
95 
96     return d;
97 }
98 
99 /*
100  * Decode MIME RFC1522 header
101  *
102  */
103 #define MIME_HEADER_CODE_START	"=?"
104 #define MIME_HEADER_CODE_MIDDLE_QP	"?Q?"
105 #define MIME_HEADER_CODE_MIDDLE_B64	"?B?"
106 #define MIME_HEADER_CODE_END	"?="
107 /*
108  * no \r, see
109  * https://github.com/ykaliuta/fidogate/commit/a727f4bae629905392e8e174c003bfa38bcbf386
110  */
111 #define MIME_HEADER_STR_DELIM	"\n"
112 #define MIME_ENC_STRING_LIMIT 80
113 #define MIME_MAX_ENC_LEN 31
114 
115 #define MAX_UTF8_LEN 4
116 
117 /* A..Z -- 0x00..0x19
118  * a..z -- 0x1a..0x33
119  * 0..9 -- 0x34..0x3d
120  * +    -- 0x3e
121  * /    -- 0x3f
122  * =    -- special
123  */
mime_b64toint(char c)124 int mime_b64toint(char c)
125 {
126     if (('A' <= c) && ('Z' >= c))
127         return (c - 'A');
128     else if (('a' <= c) && ('z' >= c))
129         return (0x1a + c - 'a');
130     else if (('0' <= c) && ('9' >= c))
131         return (0x34 + c - '0');
132     else if ('+' == c)
133         return 0x3e;
134     else if ('/' == c)
135         return 0x3f;
136     else if ('=' == c)
137         return 0x40;
138     else
139         return ERROR;
140 }
141 
mime_inttob64(int a)142 char mime_inttob64(int a)
143 {
144     char c = (char)(a & 0x0000003f);
145     if (c <= 0x19)
146         return c + 'A';
147     if (c <= 0x33)
148         return c - 0x1a + 'a';
149     if (c <= 0x3d)
150         return c - 0x34 + '0';
151     if (c == 0x3e)
152         return '+';
153     else
154         return '/';
155 }
156 
mime_qptoint(char c)157 int mime_qptoint(char c)
158 {
159     if (('0' <= c) && ('9' >= c))
160         return (c - '0');
161     else if (('A' <= c) && ('F' >= c))
162         return (0x0a + c - 'A');
163     else if (('a' <= c) && ('f' >= c))
164         return (0x0a + c - 'a');
165     else
166         return ERROR;
167 }
168 
169 /* rfc 2045 */
mime_qp_is_plain(int c)170 static bool mime_qp_is_plain(int c)
171 {
172     return ((c >= '%') && (c < '@') && (c != '='))
173         || ((c >= 'A') && (c <= 'Z'))
174         || ((c >= 'a') && (c <= 'z'));
175 }
176 
177 #define B64_ENC_CHUNK 3
178 #define B64_NLET_PER_CHUNK 4
179 
180 /*
181  * dst must have space for at least 4 octets
182  * dst will not be NUL-terminated
183  * Max 3 source octets encoded
184  */
mime_b64_encode_chunk(char * dst,unsigned char * src,unsigned len)185 static size_t mime_b64_encode_chunk(char *dst, unsigned char *src, unsigned len)
186 {
187     int padding;
188 
189     if (len == 0)
190         return 0;
191 
192     if (len > B64_ENC_CHUNK)
193         len = B64_ENC_CHUNK;
194 
195     padding = B64_ENC_CHUNK - len;
196 
197     dst[0] = mime_inttob64(src[0] >> 2);
198     dst[1] = mime_inttob64(src[0] << 4);
199 
200     if (len < 2)
201         goto out;
202 
203     dst[1] = mime_inttob64((src[0] << 4) | (src[1] >> 4));
204     dst[2] = mime_inttob64(src[1] << 2);
205 
206     if (len < 3)
207         goto out;
208 
209     dst[2] = mime_inttob64((src[1] << 2) | (src[2] >> 6));
210     dst[3] = mime_inttob64(src[2]);
211 
212  out:
213     for (; padding > 0; padding--)
214         dst[B64_NLET_PER_CHUNK - padding] = '=';
215 
216     return len;
217 }
218 
219 struct mime_word_enc_state {
220     char *encoded_line;
221     char *charset;
222     size_t charset_len;
223     size_t size;
224     size_t inc;
225     size_t pos;                 /* position in the string */
226     size_t vpos;                /* visual position in the line */
227     size_t limit;               /* in 7 bit chars, no line endings */
228     size_t rem_len;
229     char *encoding;
230     size_t max_mb_chunks;       /* max decoder's chunks to flush mb seq */
231     size_t (*calc_len)(char *token, size_t len);
232     size_t (*encode)(struct mime_word_enc_state * state, char *p, size_t len);
233     char rem[B64_ENC_CHUNK];
234     bool is_mime;               /* previous word is not plain */
235 };
236 
mime_b64_calc_len(char * t,size_t len)237 static size_t mime_b64_calc_len(char *t, size_t len)
238 {
239     size_t res;
240 
241     /* only b64 now */
242 
243     res = len / 3 * 4;
244     res += len % 3 ? 4 : 0;
245 
246     return res;
247 }
248 
mime_inc_size(struct mime_word_enc_state * state,size_t inc)249 static void mime_inc_size(struct mime_word_enc_state *state, size_t inc)
250 {
251     state->size += inc;
252     state->encoded_line = xrealloc(state->encoded_line, state->size);
253 }
254 
mime_check_and_inc_size(struct mime_word_enc_state * state,size_t inc)255 static void mime_check_and_inc_size(struct mime_word_enc_state *state,
256                                     size_t inc)
257 {
258     if (state->pos + inc <= state->size)
259         return;
260 
261     mime_inc_size(state, inc);
262 }
263 
mime_strcat(struct mime_word_enc_state * state,char * str)264 static void mime_strcat(struct mime_word_enc_state *state, char *str)
265 {
266     size_t len;
267 
268     strcpy(state->encoded_line + state->pos, str);
269     len = strlen(str);
270     state->pos += len;
271     state->vpos += len;
272 }
273 
mime_strlcpy(struct mime_word_enc_state * state,char * str,size_t len)274 static void mime_strlcpy(struct mime_word_enc_state *state,
275                          char *str, size_t len)
276 {
277     strncpy(state->encoded_line + state->pos, str, len);
278 
279     state->pos += len;
280     state->vpos += len;
281 
282     mime_check_and_inc_size(state, 1);
283     *(state->encoded_line + state->pos) = '\0';
284 }
285 
286 /*
287  * Calculates length of the word if it have been encoded.
288  * Takes into account the previous state:
289  * - if previous state mime-encoded and adding 7 bit, that must be
290  *   closed;
291  * - if previous state mime-encoded and adding mime, that is just
292  *   connected to the code, space encoded, no mime-start;
293  * - if previous state is 7 bit and adding 7 bit -- just connect as
294  *   well;
295  * - if previous state is 7 bit and adding mime, it should start with
296  *   new mime-start;
297  * If current last word is mime, account 2 chars for mime-end before
298  * the new line.
299  */
mime_word_calc_len(struct mime_word_enc_state * state,char * token,size_t len,char ** start)300 static size_t mime_word_calc_len(struct mime_word_enc_state *state,
301                                  char *token, size_t len, char **start)
302 {                               /* first non-space */
303     size_t encoded_len;
304     char *p;
305     bool prev_mime = state->is_mime;
306     size_t charset_len = state->charset_len;
307     bool is_7bit;
308 
309     for (p = token; p - token < len; p++) {
310         if (!isspace(*p))
311             break;
312     }
313 
314     is_7bit = charset_is_7bit(token, len);
315 
316     if (prev_mime) {
317         if (is_7bit) {
318             encoded_len = strlen(MIME_HEADER_CODE_END) + len;
319         } else {
320             encoded_len = state->calc_len(token, len);  /* with spaces */
321         }
322     } else {
323         if (is_7bit) {
324             encoded_len = len;
325         } else {
326             encoded_len = 1 +   /* space */
327                 strlen(MIME_HEADER_CODE_START) + charset_len + strlen(MIME_HEADER_CODE_MIDDLE_B64) + state->calc_len(p, len);   /* without spaces */
328         }
329     }
330 
331     encoded_len += state->rem_len ? B64_NLET_PER_CHUNK : 0;
332 
333     *start = p;
334     return encoded_len;
335 }
336 
mime_flush_reminder(struct mime_word_enc_state * state)337 static void mime_flush_reminder(struct mime_word_enc_state *state)
338 {
339     if (state->rem_len == 0)
340         return;
341 
342     mime_b64_encode_chunk(state->encoded_line + state->pos,
343                           (unsigned char *)state->rem, state->rem_len);
344 
345     state->pos += B64_NLET_PER_CHUNK;
346     state->vpos += B64_NLET_PER_CHUNK;
347     state->rem_len = 0;
348 }
349 
mime_add_reminder(struct mime_word_enc_state * state,char ** p,size_t * len,size_t limit)350 static size_t mime_add_reminder(struct mime_word_enc_state *state,
351                                 char **p, size_t *len, size_t limit)
352 {
353     size_t ret;
354     size_t rem_left;
355 
356     if (state->rem_len == 0)
357         return 0;
358 
359     if (state->rem_len + *len < B64_ENC_CHUNK) {
360         memcpy(&state->rem[state->rem_len], *p, *len);
361 
362         state->rem_len += *len;
363         (*p) += *len;
364 
365         ret = *len;
366         *len = 0;
367         return ret;
368     }
369 
370     if (state->vpos + B64_NLET_PER_CHUNK > limit)
371         return 0;
372 
373     rem_left = sizeof(state->rem) - state->rem_len;
374 
375     memcpy(&state->rem[state->rem_len], *p, rem_left);
376     (*p) += rem_left;
377     *len -= rem_left;
378     state->rem_len += rem_left;
379 
380     mime_flush_reminder(state);
381 
382     return rem_left;
383 }
384 
mime_save_reminder(struct mime_word_enc_state * state,char * p,size_t len)385 static size_t mime_save_reminder(struct mime_word_enc_state *state,
386                                  char *p, size_t len)
387 {
388     if (len > sizeof(state->rem)) {
389         fprintf(stderr, "ERROR: too big reminder: %zu\n", len);
390         abort();
391     }
392 
393     memcpy(state->rem, p, len);
394     state->rem_len = len;
395 
396     return len;
397 }
398 
mime_word_end(struct mime_word_enc_state * state)399 static void mime_word_end(struct mime_word_enc_state *state)
400 {
401     if (!state->is_mime)
402         return;
403 
404     mime_strcat(state, MIME_HEADER_CODE_END);
405 
406     state->is_mime = false;
407 }
408 
mime_line_break(struct mime_word_enc_state * state)409 static void mime_line_break(struct mime_word_enc_state *state)
410 {
411     state->size += state->inc;
412     state->encoded_line = xrealloc(state->encoded_line, state->size);
413 
414     mime_word_end(state);
415 
416     mime_strcat(state, MIME_HEADER_STR_DELIM);
417 
418     state->vpos = 0;
419     state->is_mime = false;
420 }
421 
mime_word_start(struct mime_word_enc_state * state)422 static void mime_word_start(struct mime_word_enc_state *state)
423 {
424     size_t len;
425 
426     len = 1 + strlen(MIME_HEADER_CODE_START)
427         + state->charset_len + strlen(MIME_HEADER_CODE_MIDDLE_B64)
428         + B64_NLET_PER_CHUNK    /* at least one */
429         + strlen(MIME_HEADER_CODE_END);
430 
431     if (state->vpos + len > state->limit)
432         mime_line_break(state);
433 
434     mime_strcat(state, " ");
435     mime_strcat(state, MIME_HEADER_CODE_START);
436     mime_strcat(state, state->charset);
437     mime_strcat(state, state->encoding);
438 
439     state->is_mime = true;
440 }
441 
mime_switch_to_plain(struct mime_word_enc_state * state)442 static void mime_switch_to_plain(struct mime_word_enc_state *state)
443 {
444     size_t end_len = strlen(MIME_HEADER_CODE_END);
445     size_t extra_len = state->rem_len ? B64_NLET_PER_CHUNK : 0;
446 
447     if (!state->is_mime)
448         return;
449 
450     if (state->vpos + extra_len + end_len > state->limit) {
451         mime_line_break(state);
452         mime_word_start(state);
453     }
454 
455     mime_flush_reminder(state);
456     mime_word_end(state);
457 }
458 
459 /* Get size of unfinished multibyte sequence. Only utf-8 supported */
mime_get_mb_tail(struct mime_word_enc_state * state,char * p)460 static size_t mime_get_mb_tail(struct mime_word_enc_state *state, char *p)
461 {
462     unsigned char c;
463     char *save_p;
464 
465     if (state->max_mb_chunks == 0)
466 	return 0;
467 
468     if (!strieq(state->charset, "utf-8"))
469 	return 0;
470 
471     save_p = p;
472     for (c = *p;
473 	 ((c & 0x80) == 0x80) && ((c & 0x40) == 0);
474 	 c = *++p)
475 	;
476     return p - save_p;
477 }
478 
479 /*
480  * Encodes part of word, that fits the current line
481  */
mime_word_enc_b64(struct mime_word_enc_state * state,char * p,size_t len)482 static size_t mime_word_enc_b64(struct mime_word_enc_state *state,
483                                 char *p, size_t len)
484 {
485     size_t limit;
486     size_t left;
487     size_t done;
488     size_t chunk;
489     size_t to_encode;
490     bool end_of_line = false;
491 
492     limit = state->limit - strlen(MIME_HEADER_CODE_END);
493 
494     done = mime_add_reminder(state, &p, &len, limit);
495 
496     left = len;
497 
498     while ((left >= B64_ENC_CHUNK) && !end_of_line) {
499 
500 	/* space to flush multibyte and encode current */
501 	if (state->vpos
502 	    + (state->max_mb_chunks + 1) * B64_NLET_PER_CHUNK > limit) {
503 
504 	    end_of_line = true;
505 
506             /* flush multibyte sequence */
507 	    to_encode = mime_get_mb_tail(state, p);
508 	} else {
509 	    to_encode = left;
510 	}
511 
512         chunk = mime_b64_encode_chunk(state->encoded_line + state->pos,
513                                       (unsigned char *)p, to_encode);
514 
515         left -= chunk;
516         done += chunk;
517         p += chunk;
518 
519 	if (chunk > 0) {
520 	    state->pos += B64_NLET_PER_CHUNK;
521 	    state->vpos += B64_NLET_PER_CHUNK;
522 	}
523 
524     }
525 
526     *(state->encoded_line + state->pos) = '\0';
527 
528     if (left < B64_ENC_CHUNK)
529         done += mime_save_reminder(state, p, left);
530 
531     return done;
532 }
533 
mime_max_mb_chunks_b64(char * charset)534 static size_t mime_max_mb_chunks_b64(char *charset)
535 {
536     if (!strieq(charset, "utf-8"))
537 	return 0;
538 
539     return ((MAX_UTF8_LEN - 1) + (B64_ENC_CHUNK - 1)) / B64_ENC_CHUNK;
540 }
541 
mime_7bit_try(struct mime_word_enc_state * state,char * token,size_t len)542 static int mime_7bit_try(struct mime_word_enc_state *state,
543                          char *token, size_t len)
544 {
545     size_t encoded_len;
546     char *p = NULL;
547     int i;
548 
549     /* flushes reminder */
550     mime_switch_to_plain(state);
551 
552     if (len > state->limit)
553         return ERROR;
554 
555     encoded_len = mime_word_calc_len(state, token, len, &p);
556 
557     if (state->vpos + encoded_len > state->limit)
558         mime_line_break(state);
559 
560     for (i = 0; i < p - token; i++) {
561         if (token[i] != '\n')
562             continue;
563 
564         /* replace original spaces if there is \n */
565         mime_strcat(state, " ");
566         len -= p - token;
567         token = p;
568     }
569 
570     mime_strlcpy(state, token, len);
571     return OK;
572 }
573 
574 #define QP_NLET_MAX 3           /* =XX */
575 
mime_qp_calc_len(char * s,size_t len)576 static size_t mime_qp_calc_len(char *s, size_t len)
577 {
578     size_t sum = 0;
579 
580     for (; len > 0; --len, s++) {
581         if (mime_qp_is_plain(*s))
582             sum += 1;
583         else
584             sum += QP_NLET_MAX;
585     }
586 
587     return sum;
588 }
589 
590 /*
591  * Encodes one octet, @out should point to a buffer enough to store 3
592  * bytes;
593  *
594  * Returns amount of bytes written
595  */
_mime_qp_encode_octet(char * out,unsigned char in)596 static size_t _mime_qp_encode_octet(char *out, unsigned char in)
597 {
598     int len;
599     char buf[QP_NLET_MAX + 1];  /* max encoded length =XX\0 */
600 
601     if (mime_qp_is_plain(in)) {
602         *out = in;
603         return sizeof(*out);
604     }
605 
606     len = snprintf(buf, sizeof(buf), "=%02X", in);
607     memcpy(out, buf, len);
608     return len;
609 }
610 
mime_qp_encode_octet(struct mime_word_enc_state * state,unsigned char in)611 static void mime_qp_encode_octet(struct mime_word_enc_state *state,
612 				 unsigned char in)
613 {
614     size_t produced;
615 
616     produced = _mime_qp_encode_octet(state->encoded_line + state->pos, in);
617     state->pos += produced;
618     state->vpos += produced;
619 }
620 
mime_word_enc_qp_flush_mb(struct mime_word_enc_state * state,char * p)621 static size_t mime_word_enc_qp_flush_mb(struct mime_word_enc_state *state,
622 					char *p)
623 {
624     size_t to_encode = mime_get_mb_tail(state, p);
625     int i;
626 
627     for (i = 0; i < to_encode; i++)
628         mime_qp_encode_octet(state, p[i]);
629 
630     return to_encode;
631 }
632 
633 /*
634  * Encodes the word, extending the line if necessary
635  */
mime_word_enc_qp(struct mime_word_enc_state * state,char * p,size_t len)636 static size_t mime_word_enc_qp(struct mime_word_enc_state *state,
637                                char *p, size_t len)
638 {
639     size_t limit;
640     size_t left;
641 
642     limit = state->limit - strlen(MIME_HEADER_CODE_END);
643 
644     for (left = len; left > 0; left--, p++) {
645 	/* space to flush multibyte and encode current */
646         if (state->vpos + (state->max_mb_chunks + 1) * QP_NLET_MAX > limit) {
647 	    /* flush multibyte sequence */
648 	    left -= mime_word_enc_qp_flush_mb(state, p);
649             break;
650 	}
651 
652         mime_qp_encode_octet(state, *p);
653     }
654 
655     *(state->encoded_line + state->pos) = '\0';
656 
657     return len - left;
658 }
659 
mime_max_mb_chunks_qp(char * charset)660 static size_t mime_max_mb_chunks_qp(char *charset)
661 {
662     if (!strieq(charset, "utf-8"))
663 	return 0;
664 
665     /* QP encoding chunk is one byte, encoded is QP_NLET_MAX */
666     return MAX_UTF8_LEN - 1;
667 }
668 
mime_8bit_calc_len(char * s,size_t len)669 static size_t mime_8bit_calc_len(char *s, size_t len)
670 {
671     return len;
672 }
673 
674 /*
675  * Encodes word if necessary (non-7bit) and adds to the header line.
676  * Makes extented line if needed.
677  * Takes token with the leading spaces when exist
678  */
mime_word_enc(struct mime_word_enc_state * state,char * token,size_t len)679 static void mime_word_enc(struct mime_word_enc_state *state,
680                           char *token, size_t len)
681 {
682     char *p;
683     size_t left;
684     size_t consumed;
685 
686     /* simple case, no encoding */
687     if (charset_is_7bit(token, len)) {
688 
689         /* switches to plain mode with flushing reminder */
690         if (mime_7bit_try(state, token, len) == OK)
691             return;
692     }
693 
694     /* encode if non-7bit or very long */
695     mime_word_calc_len(state, token, len, &p);
696 
697     left = len;
698     if (!state->is_mime)
699         left -= (p - token);    /* skip spaces */
700     else
701         p = token;              /* encode with the spaces */
702 
703     for (;;) {
704         if (!state->is_mime) {
705             mime_word_start(state);
706         }
707 
708         consumed = state->encode(state, p, left);
709 
710         p += consumed;
711         left -= consumed;
712         if (left == 0)
713             break;
714 
715         mime_line_break(state);
716     }
717 }
718 
mime_header_enc_start(struct mime_word_enc_state * state,char * charset,int type)719 static void mime_header_enc_start(struct mime_word_enc_state *state,
720                                   char *charset, int type)
721 {
722     size_t size = MIME_STRING_LIMIT + sizeof(MIME_HEADER_STR_DELIM);    /* \0 counted */
723     char *p;
724 
725     p = xmalloc(size);
726     *p = '\0';
727     state->size = size;
728     state->encoded_line = p;
729     state->charset = charset;
730     state->charset_len = strlen(charset);
731     state->inc = size - 1;      /* only one \0 */
732     state->pos = 0;
733     state->vpos = 0;
734     state->limit = MIME_STRING_LIMIT;
735     state->rem_len = 0;
736     state->is_mime = false;
737 
738     switch (type) {
739     case MIME_QP:
740         state->encoding = MIME_HEADER_CODE_MIDDLE_QP;
741         state->calc_len = mime_qp_calc_len;
742         state->encode = mime_word_enc_qp;
743 	state->max_mb_chunks = mime_max_mb_chunks_qp(charset);
744         break;
745     case MIME_B64:
746         state->encoding = MIME_HEADER_CODE_MIDDLE_B64;
747         state->calc_len = mime_b64_calc_len;
748         state->encode = mime_word_enc_b64;
749 	state->max_mb_chunks = mime_max_mb_chunks_b64(charset);
750         break;
751     default:
752         state->encoding = NULL;
753         state->calc_len = mime_8bit_calc_len;
754         state->encode = NULL;
755     }
756 }
757 
mime_header_enc_end(struct mime_word_enc_state * state)758 static char *mime_header_enc_end(struct mime_word_enc_state *state)
759 {
760     size_t len;
761     size_t extra_len; /* for \n and \0 */
762 
763     if (state->is_mime) {
764         len = strlen(MIME_HEADER_CODE_END);
765         if (state->rem_len)
766             len += B64_NLET_PER_CHUNK;
767 
768         if (state->vpos + len > state->limit) {
769             mime_line_break(state);
770             mime_word_start(state);
771         }
772 
773         mime_flush_reminder(state);
774         mime_word_end(state);
775     }
776 
777     extra_len = strlen(MIME_HEADER_STR_DELIM) + 1;
778     mime_check_and_inc_size(state, extra_len);
779 
780     if (state->encoded_line[state->pos - 1] != '\n') {
781         strcat(state->encoded_line, MIME_HEADER_STR_DELIM);
782         state->pos += strlen(MIME_HEADER_STR_DELIM);
783     }
784 
785     state->encoded_line[state->pos++] = '\0';
786 
787     return state->encoded_line;
788 }
789 
790 /*
791  * Mime encodes 8bit header, splitting lines when necessary.
792  * src must be NUL-terminated
793  *
794  * Result is \n terminated
795  *
796  * Encoding is done word by word, 7bit words are not encoded
797  */
mime_header_enc(char ** dst,char * src,char * charset,int enc)798 int mime_header_enc(char **dst, char *src, char *charset, int enc)
799 {
800     struct mime_word_enc_state state;
801     char *token;
802     char *token_end;
803 
804     debug(6, "MIME: %s: to encode (%s): %s", __func__, charset, src);
805 
806     mime_header_enc_start(&state, charset, enc);
807 
808     /* strtok does not work on RO strings */
809 
810     token = src;
811     token_end = strchr(src, ' ');
812     if (token_end == NULL) {
813         fglog("ERROR: misformatted header: %s\n", src);
814         return ERROR;
815     }
816 
817     while (token != NULL) {
818         mime_word_enc(&state, token, token_end - token);
819 
820         token = token_end;
821 
822         /* first skip spaces */
823         while ((*token_end == ' ' || *token_end == '\t' || *token == '\n')
824                && *token_end != '\0')
825             token_end++;
826 
827         if (*token_end == '\0') {
828             token = NULL;
829             continue;
830         }
831 
832         /* then find next space or EOL */
833         while (*token_end != ' ' && *token_end != '\t' && *token != '\n'
834                && *token_end != '\0')
835             token_end++;
836     }
837 
838     mime_header_enc_end(&state);
839     *dst = state.encoded_line;
840 
841     debug(6, "MIME: %s: encoded: %s", __func__, *dst);
842 
843     return OK;
844 }
845 
mime_b64_encode_tl(Textlist * in,Textlist * out)846 void mime_b64_encode_tl(Textlist * in, Textlist * out)
847 {
848     TextlistCharIterator iter;
849     /* + \n\0, no \r needed */
850     char buf[MIME_STRING_LIMIT + 2];
851     char ibuf[B64_ENC_CHUNK];
852     size_t len;
853     size_t pos;
854 
855     tl_init(out);
856     tl_char_iterator_start(&iter, in);
857     pos = 0;
858 
859     len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
860 
861     while (len > 0) {
862         if (pos + B64_NLET_PER_CHUNK > MIME_STRING_LIMIT) {
863             buf[pos++] = '\n';
864             buf[pos++] = '\0';
865 
866             tl_append(out, buf);
867             pos = 0;
868         }
869 
870         mime_b64_encode_chunk(buf + pos, (unsigned char *)ibuf, len);
871         pos += B64_NLET_PER_CHUNK;
872         len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
873     }
874 
875     buf[pos++] = '\n';
876     buf[pos++] = '\0';
877 
878     tl_append(out, buf);
879 }
880 
mime_b64_decode(char ** dst,char * src,size_t len)881 int mime_b64_decode(char **dst, char *src, size_t len)
882 {
883     char *buf;
884     size_t buflen;
885     size_t i;
886     int v1, v2, v3, v4;
887     char *d;
888     int rc = OK;
889 
890     if (0 != (len % 4))
891         return ERROR;
892 
893     buflen = ((len / 4) * 3) + 1;
894     buf = xmalloc(buflen);
895     memset(buf, 0, buflen);
896     i = 0;
897     d = buf;
898     while (len > i) {
899         v1 = mime_b64toint(src[i++]);
900         if (ERROR == v1) {
901             rc = ERROR;
902             break;
903         }
904 
905         v2 = mime_b64toint(src[i++]);
906         if (ERROR == v2) {
907             rc = ERROR;
908             break;
909         }
910 
911         v3 = mime_b64toint(src[i++]);
912         if (ERROR == v3) {
913             rc = ERROR;
914             break;
915         }
916 
917         v4 = mime_b64toint(src[i++]);
918         if (ERROR == v4) {
919             rc = ERROR;
920             break;
921         }
922 
923         *d++ = (v1 << 2) | (v2 >> 4);
924         if (0x40 > v3) {
925             *d++ = ((v2 << 4) & 0xf0) | (v3 >> 2);
926             if (0x40 > v4)
927                 *d++ = ((v3 << 6) & 0xc0) | v4;
928         } else if (0x40 > v4) {
929             rc = ERROR;
930             break;
931         }
932     }
933 
934     if (ERROR == rc)
935         xfree(buf);
936     else
937         *dst = buf;
938     return rc;
939 }
940 
mime_qp_encode_tl(Textlist * in,Textlist * out)941 void mime_qp_encode_tl(Textlist * in, Textlist * out)
942 {
943     TextlistCharIterator iter;
944     /* + =\n\0 */
945     char buf[MIME_STRING_LIMIT + 4];
946     char ibuf[1];
947     size_t len;
948     size_t pos;
949     size_t len_encoded;
950 
951     tl_init(out);
952     tl_char_iterator_start(&iter, in);
953     pos = 0;
954 
955     len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
956 
957     while (len > 0) {
958         if (pos + QP_NLET_MAX > MIME_STRING_LIMIT) {
959             buf[pos++] = '=';
960             buf[pos++] = '\n';
961             buf[pos++] = '\0';
962 
963             tl_append(out, buf);
964             pos = 0;
965         }
966 
967         len_encoded = _mime_qp_encode_octet(buf + pos, ibuf[0]);
968         pos += len_encoded;
969         len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
970     }
971 
972     buf[pos++] = '\n';
973     buf[pos++] = '\0';
974 
975     tl_append(out, buf);
976 }
977 
mime_qp_decode(char ** dst,char * src,size_t len)978 int mime_qp_decode(char **dst, char *src, size_t len)
979 {
980     char *buf;
981     size_t i;
982     int vh, vl;
983     char *d;
984     char *p1, *p2;
985     int j;
986     int rc = OK;
987 
988     p1 = src;
989     j = 0;
990     while (NULL != (p2 = strchr(p1, '_'))) {
991         p1 = ++p2;
992         j++;
993     }
994 
995     buf = xmalloc(len + 1);
996     memset(buf, 0, len + 1);
997 
998     i = 0;
999     d = buf;
1000     while (len > i) {
1001         if (src[i] == '\n' || src[i] == '\r') {
1002             *d = '\n';
1003             break;
1004         }
1005 
1006         if ('_' == src[i]) {
1007             *d++ = ' ';
1008             i++;
1009             continue;
1010         }
1011 
1012         if ('=' != src[i]) {
1013             *d++ = src[i++] & 0x7F;
1014             continue;
1015         }
1016 
1017         i++;
1018 
1019         if (src[i] == '\n' || src[i] == '\r') {
1020             *d = '\n';
1021             break;
1022         }
1023 
1024         vh = mime_qptoint(src[i++]);
1025         if (ERROR == vh) {
1026             rc = ERROR;
1027             break;
1028         }
1029 
1030         vl = mime_qptoint(src[i++]);
1031         if (ERROR == vl) {
1032             rc = ERROR;
1033             break;
1034         }
1035 
1036         *d++ = (char)(((vh << 4) & 0xf0) | (vl & 0x0f));
1037     }
1038 
1039     if (ERROR == rc)
1040         xfree(buf);
1041     else
1042         *dst = buf;
1043     return rc;
1044 }
1045 
1046 /*
1047  * mime word is a mime encoded string, separated by space
1048  * from other mime words.
1049  * The function takes non-whitespace starting string
1050  * and decodes it from mime (quoted-printable or base64),
1051  * if it is properly encoded.
1052  * Leaves the string as is, if cannot recode.
1053  * Allocates buffer,
1054  * return the buffer,
1055  *        the byte length of the decoded string (no final '\0'),
1056  *        and the charset (from the mime info)
1057  */
mime_handle_mimed_word(char * s,char ** out,size_t * out_len,bool * is_mime,char * charset,size_t ch_size,char * to)1058 static int mime_handle_mimed_word(char *s, char **out, size_t *out_len,
1059                                   bool *is_mime,
1060                                   char *charset, size_t ch_size, char *to)
1061 {
1062     char *p;
1063     size_t len;
1064     size_t tmp_len;
1065     char *buf;
1066     char *beg;
1067     char *end;
1068     int (*decoder)(char **dst, char *src, size_t len) = NULL;
1069 
1070     /* May be MIME (b64 or qp) */
1071     p = strchr(s + strlen(MIME_HEADER_CODE_START), '?');
1072     if (p == NULL)
1073         goto fallback;
1074 
1075     tmp_len = p - (s + strlen(MIME_HEADER_CODE_START));
1076     if (tmp_len > MIME_MAX_ENC_LEN) {
1077         fglog("ERROR: charset name's length too long, %zd. Do not recode",
1078               tmp_len);
1079         goto fallback;
1080     }
1081 
1082     tmp_len = MIN(tmp_len + 1, ch_size);
1083     snprintf(charset, tmp_len, "%s", s + strlen(MIME_HEADER_CODE_START));
1084 
1085     /* Check if b64 or qp */
1086     if (strnieq(p,
1087                 MIME_HEADER_CODE_MIDDLE_QP,
1088                 strlen(MIME_HEADER_CODE_MIDDLE_QP))) {
1089         /* May be qp */
1090         decoder = mime_qp_decode;
1091         beg = p + strlen(MIME_HEADER_CODE_MIDDLE_QP);
1092 
1093     } else if (strnieq(p,
1094                        MIME_HEADER_CODE_MIDDLE_B64,
1095                        strlen(MIME_HEADER_CODE_MIDDLE_B64))) {
1096         /* May be b64 */
1097         decoder = mime_b64_decode;
1098         beg = p + strlen(MIME_HEADER_CODE_MIDDLE_B64);
1099     } else {
1100         fglog
1101             ("ERROR: subject looks like mime, but does not have proper header");
1102         goto fallback;
1103     }
1104 
1105     end = strchr(beg, '?');
1106 
1107     if (end == NULL ||
1108         !strnieq(end, MIME_HEADER_CODE_END, strlen(MIME_HEADER_CODE_END))) {
1109         fglog
1110             ("ERROR: subject looks like mime, but does not have proper ending");
1111         goto fallback;
1112     }
1113     if (decoder(&buf, beg, end - beg) == ERROR) {
1114         fglog("ERROR: subject mime deconding failed");
1115         goto fallback;
1116     }
1117 
1118     *out_len = strlen(buf);
1119     *out = buf;
1120     *is_mime = true;
1121     return end + strlen(MIME_HEADER_CODE_END) - s;
1122 
1123  fallback:
1124     /*
1125      * Keep it as is.
1126      * Will work correctly with utf-8 plain headers, iconv and utf-8
1127      * internal charset.
1128      */
1129     debug(6, "Could not unmime subject '%s', leaving as is", s);
1130     len = strlen(s);
1131     *out = xmalloc(len);
1132     memcpy(*out, s, len);       /* no '\0' */
1133     snprintf(charset, ch_size, "%s", to);
1134     *is_mime = false;
1135     *out_len = len;
1136 
1137     return len;
1138 }
1139 
mime_handle_plain_word(char * s,char ** out,size_t * out_len,char * charset,size_t ch_size,char * to,char * ch_fallback,RFCHeader * header)1140 static int mime_handle_plain_word(char *s, char **out, size_t *out_len,
1141                                   char *charset, size_t ch_size, char *to,
1142 				  char *ch_fallback, RFCHeader *header)
1143 {
1144     char *plain_charset = ch_fallback;
1145     char *mime_charset = NULL;
1146     char *p;
1147     size_t len;
1148     char *res;
1149 
1150     /*
1151      * If there are several words, handle only one.
1152      * Reading code should combine splitted lines, so no \n.
1153      * TODO: \t can be checked as well
1154      */
1155     p = strchr(s, ' ');
1156     if (p != NULL)
1157         len = p - s;
1158     else
1159         len = strlen(s);
1160 
1161     /*
1162      * There must be no plain 8 bit headers except utf-8.
1163      * If there are, do some heuristics:
1164      * - if body charset supplied and is not 7 bit us-ascii, use that;
1165      * - othewise it's broken most probably, assume the precompiled
1166      *   default.
1167      */
1168     if (!charset_is_7bit(s, len) && charset_is_valid_utf8(s, len)) {
1169         plain_charset = "utf-8";
1170     } else {
1171         mime_charset = mime_get_main_charset(header);
1172 
1173         if ((mime_charset != NULL) &&
1174             (strcmp(mime_charset, CHARSET_STD7BIT) != 0)) {
1175             plain_charset = mime_charset;
1176         }
1177     }
1178 
1179     strncpy(charset, plain_charset, ch_size - 1);
1180     charset[ch_size - 1] = '\0';
1181 
1182     free(mime_charset);
1183 
1184     res = xmalloc(len + 1);
1185     str_copy(res, len + 1, s);
1186 
1187     *out_len = len;
1188     *out = res;
1189 
1190     return *out_len;
1191 }
1192 
mime_handle_brace(char * s,char ** out,size_t * out_len,bool * is_mime,char * charset,size_t ch_size,char * to)1193 static int mime_handle_brace(char *s, char **out, size_t *out_len,
1194 			     bool *is_mime, char *charset, size_t ch_size,
1195 			     char *to)
1196 {
1197     char *b;
1198     char *brace = "(";
1199 
1200     /* client code requires malloc()ed buffer */
1201     b = strsave(brace);
1202     snprintf(charset, ch_size, "%s", "us-ascii");
1203 
1204     *out = b;
1205     *out_len = strlen(b);
1206     *is_mime = false;
1207 
1208     return strlen(brace);
1209 }
1210 
1211 /*
1212  * Fetches the mime word from the string. It can be mime-encoded
1213  * or plain part, separated with "space", or mime in comment (rfc2047 5(2))
1214  * _Must_ start with non-space.
1215  * For mime-encoded parts decode it and fetch the charset.
1216  * For plain parts copy them as is, take the global charset.
1217  * Allocates the final buffer.
1218  * @returns the number of bytes, handled in the source string.
1219  */
mime_handle_word(char * s,char ** out,size_t * out_len,bool * is_mime,char * charset,size_t ch_size,char * to,char * ch_fallback,RFCHeader * header)1220 static int mime_handle_word(char *s, char **out, size_t *out_len,
1221                             bool *is_mime, char *charset, size_t ch_size,
1222                             char *to, char *ch_fallback, RFCHeader *header)
1223 {
1224     /* mime word can be inside "comment" not separated by whitespace */
1225     if (strnieq(s,
1226 		"(" MIME_HEADER_CODE_START,
1227 		strlen("(" MIME_HEADER_CODE_START))) {
1228 	return mime_handle_brace(s, out, out_len,
1229 				 is_mime, charset, ch_size, to);
1230     }
1231 
1232     if (strnieq(s, MIME_HEADER_CODE_START, strlen(MIME_HEADER_CODE_START))) {
1233         return mime_handle_mimed_word(s,
1234                                       out, out_len,
1235                                       is_mime, charset, ch_size, to);
1236     }
1237 
1238     *is_mime = false;
1239     return mime_handle_plain_word(s, out, out_len,
1240                                   charset, ch_size, to, ch_fallback, header);
1241 }
1242 
1243 /* source @s must be '\0'-terminated */
mime_header_dec(char * d,size_t d_max,char * s,char * to,char * ch_fallback,RFCHeader * header)1244 char *mime_header_dec(char *d, size_t d_max, char *s, char *to,
1245                       char *ch_fallback, RFCHeader *header)
1246 {
1247     char *save_d = d;
1248     bool is_mime = false;
1249     bool is_prev_mime = false;
1250     char *buf;
1251     size_t len;
1252     char charset[MIME_MAX_ENC_LEN + 1];
1253     int rc;
1254     size_t d_left = d_max - 1;
1255     size_t s_handled;
1256     char *recoded;
1257     size_t recoded_len;
1258 
1259     while (d_left != 0 && *s != '\0') {
1260         if (isspace(*s)) {
1261             /* just skip spaces between mime words */
1262             if (!is_mime) {
1263                 *d++ = *s;
1264                 d_left--;
1265             }
1266             s++;
1267             continue;
1268         }
1269 
1270         is_prev_mime = is_mime;
1271 
1272         /* it means mime "word" -- encoded or plain part */
1273         s_handled = mime_handle_word(s, &buf, &len, &is_mime,
1274                                      charset, sizeof(charset), to, ch_fallback,
1275                                      header);
1276 
1277         /* keep at least one space between mime and non-mime words */
1278         if (is_prev_mime
1279             && !is_mime
1280             /* and not comment's `()` end */
1281             && !((s_handled == 1) && (*s == ')'))) {
1282 
1283             *d++ = ' ';
1284             d_left--;
1285 
1286             if (d_left == 0)
1287                 break;
1288         }
1289 
1290         debug(6, "header charset: %s", charset);
1291         rc = charset_recode_buf(&recoded, &recoded_len, buf, len, charset, to);
1292         if (rc != OK) {
1293             debug(6, "Could not recode header %s", buf);
1294             goto out;
1295         }
1296 
1297         free(buf);
1298 
1299         if (recoded_len > d_left) {
1300             fglog("ERROR: header buffer too small");
1301             recoded_len = d_left;
1302         }
1303 
1304         memcpy(d, recoded, recoded_len);
1305 	free(recoded);
1306 
1307         d += recoded_len;
1308         d_left -= recoded_len;
1309 
1310         s += s_handled;
1311     }
1312 
1313  out:
1314     *d = '\0';
1315     return save_d;
1316 }
1317 
1318 /* takes space-stripped string */
1319 /* Now is not used */
1320 /*
1321 
1322 static char* mime_fetch_attribute(char *str, char *attr)
1323 {
1324      int attr_len = strlen(attr);
1325      char *tmp_str = NULL;
1326 
1327      do
1328      {
1329 	  if(strnieq(str, attr, attr_len))
1330 	  {
1331 	       tmp_str = xmalloc(attr_len + 1);
1332 	       strncpy(tmp_str, str, attr_len);
1333 	       tmp_str[attr_len] = '\0';
1334 	       break;
1335 	  }
1336 	  str = strchr(str, ';');
1337 	  if(str != NULL)
1338 	       str++;
1339      } while (str != NULL);
1340 
1341      return tmp_str;
1342 }
1343 */
1344 
mime_parse_header(Textlist * line,char * str)1345 static int mime_parse_header(Textlist * line, char *str)
1346 {
1347     char *p = NULL;
1348 
1349     if (line == NULL || str == NULL)
1350         return ERROR;
1351 
1352     debug(6, "Parsing header %s", str);
1353     for (p = strtok(str, ";"); p != NULL; p = strtok(NULL, ";")) {
1354         debug(6, "Recording header attribute %s", p);
1355         p = strip_space(p);
1356         tl_append(line, p);
1357     }
1358     return OK;
1359 }
1360 
mime_attr_value(char * str)1361 static char *mime_attr_value(char *str)
1362 {
1363     char *p, *q = NULL;
1364 
1365     if (str == NULL)
1366         return NULL;
1367 
1368     p = strchr(str, '=');
1369     if (p != NULL) {
1370         if (*(++p) == '\"')
1371             p++;
1372         for (q = p; *q != '\0'; q++)
1373             if (*q == '\"' || is_space(*q))
1374                 break;
1375         *q = 0;
1376         p = strsave(p);
1377     }
1378     return p;
1379 }
1380 
1381 /*
1382  * Return MIME header
1383  */
1384 
get_mime_disposition(char * ver,char * type,char * enc,char * disp)1385 static MIMEInfo *get_mime_disposition(char *ver, char *type, char *enc,
1386                                       char *disp)
1387 {
1388     MIMEInfo *mime;
1389 
1390     Textlist header_line = { NULL, NULL };
1391     Textline *tmp_line;
1392     char *tmp_str = NULL;
1393 
1394     mime = (MIMEInfo *) s_alloc(sizeof(*mime));
1395 
1396     mime->version = ver;
1397     mime->type = type;
1398     mime->type_type = NULL;
1399     mime->type_charset = NULL;
1400     mime->type_boundary = NULL;
1401     mime->encoding = enc;
1402     mime->disposition = disp;
1403     mime->disposition_filename = NULL;
1404 
1405     if (type != NULL) {
1406         tmp_str = s_copy(type);
1407         mime_parse_header(&header_line, tmp_str);
1408         if (header_line.first != NULL && header_line.first->line != NULL)
1409             mime->type_type = s_copy(header_line.first->line);
1410 
1411         tmp_line = tl_get(&header_line, "charset", strlen("charset"));
1412         if (tmp_line != NULL) {
1413             tmp_str = mime_attr_value(tmp_line->line);
1414             mime->type_charset = s_copy(tmp_str);
1415             xfree(tmp_str);
1416         }
1417         tmp_line = tl_get(&header_line, "boundary", strlen("boundary"));
1418         if (tmp_line != NULL) {
1419             tmp_str = mime_attr_value(tmp_line->line);
1420             mime->type_boundary = s_copy(tmp_str);
1421             xfree(tmp_str);
1422         }
1423         tl_clear(&header_line);
1424     }
1425 
1426     if (disp != NULL) {
1427         tmp_str = s_copy(disp);
1428         /* tl_append() inside makes copies */
1429         mime_parse_header(&header_line, tmp_str);
1430 
1431         tmp_line = tl_get(&header_line, "filename", strlen("filename"));
1432         if (tmp_line != NULL) {
1433             tmp_str = mime_attr_value(tmp_line->line);
1434             mime->disposition_filename = s_copy(tmp_str);
1435             xfree(tmp_str);
1436         }
1437 
1438         tl_clear(&header_line);
1439     }
1440 
1441     debug(6, "RFC MIME-Version:              %s",
1442           mime->version ? mime->version : "-NONE-");
1443     debug(6, "RFC Content-Type:              %s",
1444           mime->type ? mime->type : "-NONE-");
1445     debug(6, "                        type = %s",
1446           mime->type_type ? mime->type_type : "");
1447     debug(6, "                     charset = %s",
1448           mime->type_charset ? mime->type_charset : "");
1449     debug(6, "                    boundary = %s",
1450           mime->type_boundary ? mime->type_boundary : "");
1451     debug(6, "RFC Content-Transfer-Encoding: %s",
1452           mime->encoding ? mime->encoding : "-NONE-");
1453     debug(6, "RFC Content-Disposition: %s",
1454           mime->disposition ? mime->disposition : "-NONE-");
1455     debug(6, "                    filename = %s",
1456           mime->disposition_filename ? mime->disposition_filename : "");
1457 
1458     return mime;
1459 }
1460 
get_mime(char * ver,char * type,char * enc)1461 MIMEInfo *get_mime(char *ver, char *type, char *enc)
1462 {
1463     return get_mime_disposition(ver, type, enc, NULL);
1464 }
1465 
1466 /* TODO smth like s_header_getcomplete() */
1467 
get_mime_from_header(RFCHeader * header)1468 MIMEInfo *get_mime_from_header(RFCHeader *header)
1469 {
1470         return get_mime_disposition(header_get(header, "Mime-Version"),
1471                                     header_get(header, "Content-Type"),
1472                                     header_get(header,
1473                                                   "Content-Transfer-Encoding"),
1474                                     header_get(header,
1475                                                   "Content-Disposition"));
1476 }
1477 
1478 /* Garantee [^\r]\n\0 in the end of string since our
1479    output subroutine adds '\r'
1480    str should have space for 1 extra symbol */
1481 
mime_debody_flush_str(Textlist * body,char * str)1482 static char *mime_debody_flush_str(Textlist * body, char *str)
1483 {
1484     int len;
1485     if (str == NULL || body == NULL)
1486         return NULL;
1487 
1488     len = strlen(str);
1489     if (len < 2)
1490         return NULL;
1491 
1492     if (str[len - 2] == '\r') {
1493         str[len - 2] = '\n';
1494         str[len - 1] = '\0';
1495     } else if (str[len - 1] != '\n') {
1496         str[len] = '\n';
1497         str[len + 1] = '\0';
1498     }
1499 
1500     tl_append(body, str);
1501     str[0] = '\0';
1502 
1503     return str;
1504 }
1505 
mime_debody_qp(Textlist * body)1506 static Textlist *mime_debody_qp(Textlist * body)
1507 {
1508     char *dec_str, *tmp_str;
1509     long len;
1510 
1511     Textlist *dec_body;
1512     Textlist *full_line;
1513 
1514     Textline *line, *subline;
1515 
1516     dec_body = xmalloc(sizeof(*dec_body));
1517     memset(dec_body, 0, sizeof(*dec_body));
1518 
1519     full_line = xmalloc(sizeof(*full_line));
1520     memset(full_line, 0, sizeof(*full_line));
1521 
1522     for (line = body->first; line != NULL; line = line->next) {
1523         len = strlen(line->line);
1524         if (mime_qp_decode(&dec_str, line->line, len) == ERROR)
1525             goto fail;
1526 
1527         strip_crlf(dec_str);
1528         tl_append(full_line, dec_str);
1529         xfree(dec_str);
1530 
1531         if (streq(line->line + (len - 2), "=\n")
1532             || streq(line->line + (len - 3), "=\r\n"))
1533             continue;
1534 
1535         len = tl_size(full_line);
1536 
1537         tmp_str = xmalloc(len + 2);
1538         memset(tmp_str, 0, len + 2);
1539 
1540         for (subline = full_line->first; subline != NULL;
1541              subline = subline->next)
1542             strcat(tmp_str, subline->line);
1543 
1544         tmp_str[len] = '\n';
1545 
1546         tl_append(dec_body, tmp_str);
1547         xfree(tmp_str);
1548         tl_clear(full_line);
1549     }
1550 
1551     tl_clear(full_line);
1552     xfree(full_line);
1553     return dec_body;
1554 
1555  fail:
1556     fglog("ERROR: decoding quoted-printable, skipping section");
1557     tl_clear(dec_body);
1558     xfree(dec_body);
1559     tl_clear(full_line);
1560     xfree(full_line);
1561     return NULL;
1562 }
1563 
1564 /* decode base64 body */
1565 
mime_debody_base64(Textlist * body)1566 static Textlist *mime_debody_base64(Textlist * body)
1567 {
1568     char *out_str;
1569     char *dec_str;
1570     char *out_ptr;
1571     char *dec_ptr, *dec_prev_ptr;
1572     char *to_free;
1573 
1574     Textlist *dec_body = NULL;
1575     Textline *line;
1576 
1577     int max_len = MAX_LINE_LENGTH;
1578     int left;
1579     int enc_len = 0, dec_str_len = 0;
1580 
1581     dec_body = xmalloc(sizeof(*dec_body));
1582     memset(dec_body, 0, sizeof(*dec_body));
1583 
1584     /* len + \n + \0 */
1585     out_ptr = out_str = xmalloc(max_len + 2);
1586     out_str[0] = '\0';
1587     left = max_len;
1588 
1589     for (line = body->first; line != NULL; line = line->next) {
1590 
1591         enc_len = (xstrnlen(line->line, MIME_ENC_STRING_LIMIT) / 4) * 4;
1592         if (mime_b64_decode(&dec_str, line->line, enc_len) == ERROR)
1593             goto exit;
1594 
1595 	to_free = dec_str;
1596 
1597         dec_prev_ptr = dec_str;
1598         while ((dec_ptr = strchr(dec_prev_ptr, '\n')) != NULL) {
1599             dec_str_len = dec_ptr - dec_prev_ptr + 1;
1600             if (dec_str_len <= left) {
1601                 strncat(out_str, dec_prev_ptr, dec_str_len);
1602                 dec_prev_ptr = dec_ptr + 1;
1603             } else {
1604                 strncat(out_str, dec_prev_ptr, left);
1605                 dec_prev_ptr += left;
1606             }
1607 
1608             mime_debody_flush_str(dec_body, out_str);
1609             left = max_len;
1610         }
1611 
1612         dec_str_len = strlen(dec_str) - (dec_prev_ptr - dec_str);
1613         do {
1614             if (dec_str_len < left) {
1615                 strncat(out_str, dec_prev_ptr, dec_str_len);
1616                 out_ptr += dec_str_len;
1617                 left -= dec_str_len;
1618                 dec_str_len = 0;
1619             } else {
1620                 strncat(out_str, dec_prev_ptr, left);
1621                 dec_prev_ptr += left;
1622                 dec_str_len -= left;
1623 
1624                 mime_debody_flush_str(dec_body, out_str);
1625                 left = max_len;
1626 
1627             }
1628         } while (dec_str_len > 0);
1629 
1630 	free(to_free);
1631     }
1632     if (left != max_len)
1633         mime_debody_flush_str(dec_body, out_str);
1634 
1635  exit:
1636     xfree(out_str);
1637     return dec_body;
1638 }
1639 
1640 static Textlist *mime_debody_section(Textlist *body, RFCHeader* header,
1641                                      char *to, char *ch_fallback);
1642 
mime_debody_multipart(Textlist * body,MIMEInfo * mime,char * to,char * ch_fallback)1643 static Textlist *mime_debody_multipart(Textlist * body, MIMEInfo * mime,
1644                                        char *to, char *ch_fallback)
1645 {
1646     RFCHeader *header;
1647     Textline *line;
1648 
1649     Textlist *dec_body, *ptr_body, tmp_body = { NULL, NULL };
1650 
1651     char *boundary = NULL;
1652     char *fin_boundary = NULL;
1653 
1654     if (mime->type_boundary == NULL)
1655         return NULL;
1656 
1657     dec_body = xmalloc(sizeof(*dec_body));
1658     dec_body->first = NULL;
1659     dec_body->last = NULL;
1660 
1661     /* --boundary\0 */
1662     boundary = xmalloc(strlen(mime->type_boundary) + 3);
1663     strcpy(boundary, "--");
1664     strcat(boundary, mime->type_boundary);
1665 
1666     /* --boundary--\0 */
1667     fin_boundary = xmalloc(strlen(boundary) + 3);
1668     strcpy(fin_boundary, boundary);
1669     strcat(fin_boundary, "--");
1670 
1671     for (line = body->first; line != NULL; line = line->next)
1672         if (strneq(line->line, boundary, strlen(boundary)))
1673             break;
1674 
1675     for (line = line->next; line != NULL; line = line->next) {
1676         if (!strneq(line->line, boundary, strlen(boundary))) {
1677             tl_append(&tmp_body, line->line);
1678         } else {
1679             header = header_read_list(&tmp_body);
1680             header_delete_from_body(&tmp_body);
1681 
1682             ptr_body = mime_debody_section(&tmp_body, header, to, ch_fallback);
1683             if (ptr_body != NULL) {
1684                 tl_addtl_move(dec_body, ptr_body);
1685                 if (ptr_body != &tmp_body)
1686                     xfree(ptr_body);
1687             }
1688             tl_clear(&tmp_body);
1689             header_free(header);
1690 
1691             if (strneq(line->line, fin_boundary, strlen(fin_boundary)))
1692                 break;
1693         }
1694     }
1695 
1696     xfree(boundary);
1697     xfree(fin_boundary);
1698     return dec_body;
1699 }
1700 
mime_decharset_section(Textlist * body,MIMEInfo * mime,char * to)1701 static int mime_decharset_section(Textlist * body, MIMEInfo * mime, char *to)
1702 {
1703 
1704     char *res_str = NULL;
1705     size_t res_len, src_len;
1706     int rc;
1707     Textline *line;
1708     char *tmp;
1709 
1710     if (body == NULL || mime == NULL)
1711         return ERROR;
1712 
1713     /* if no charset, us-ascii assumed */
1714     if (mime->type_charset == NULL || strieq(mime->type_charset, to))
1715         return OK;
1716 
1717     for (line = body->first; line != NULL; line = line->next) {
1718         src_len = strlen(line->line) + 1;   /* recode final \0 also */
1719 
1720         rc = charset_recode_buf(&res_str, &res_len, line->line, src_len,
1721                                 mime->type_charset, to);
1722 
1723         if (rc == ERROR) {
1724             goto exit;          /* something really wrong */
1725         }
1726 
1727         tmp = line->line;
1728         line->line = res_str;
1729         free(tmp);
1730     }
1731 
1732     rc = OK;
1733  exit:
1734     return rc;
1735 }
1736 
mime_debody_section(Textlist * body,RFCHeader * header,char * to,char * ch_fallback)1737 static Textlist *mime_debody_section(Textlist *body, RFCHeader *header,
1738                                      char *to, char *ch_fallback)
1739 {
1740 
1741     Textlist *dec_body;
1742     MIMEInfo *mime;
1743 
1744     mime = get_mime_from_header(header);
1745     if (mime->type_charset == NULL)
1746         mime->type_charset = ch_fallback;
1747 
1748     if ((mime->type_type == NULL) || strieq(mime->type_type, "text/plain")
1749         || (mime->type == NULL)) {
1750         if ((mime->encoding == NULL) || strieq(mime->encoding, "8bit")
1751             || strieq(mime->encoding, "7bit")) {
1752 
1753             dec_body = tl_dup(body);
1754 
1755         } else if (strieq(mime->encoding, "base64")) {
1756             dec_body = mime_debody_base64(body);
1757         } else if (strieq(mime->encoding, "quoted-printable")) {
1758             dec_body = mime_debody_qp(body);
1759         } else {
1760             fglog("WARNING: Skipped unsupported transfer encoding %s",
1761                   mime->encoding);
1762             dec_body = NULL;
1763             goto exit;
1764         }
1765         mime_decharset_section(dec_body, mime, to);
1766     } else if(strnieq(mime->type_type,
1767 		      "multipart/", sizeof("multipart/") - 1)) {
1768         dec_body = mime_debody_multipart(body, mime, to, ch_fallback);
1769     } else {
1770         fglog("WARNING: Skipped unsupported mime type  %s", mime->type_type);
1771         dec_body = NULL;
1772         goto exit;
1773     }
1774  exit:
1775     mime_free();
1776     return dec_body;
1777 
1778 }
1779 
mime_body_dec(Textlist * body,RFCHeader * header,char * to,char * ch_fallback)1780 Textlist *mime_body_dec(Textlist *body, RFCHeader *header,
1781                         char *to, char *ch_fallback)
1782 {
1783     Textlist *dec_body;
1784 
1785     dec_body = mime_debody_section(body, header, to, ch_fallback);
1786     if (dec_body == NULL)
1787         return NULL;
1788 
1789     if (dec_body->first == NULL) {
1790         fglog("ERROR: could not decode mime body");
1791         xfree(dec_body);
1792         return NULL;
1793     }
1794 
1795     return dec_body;
1796 }
1797 
mime_free(void)1798 void mime_free(void)
1799 {
1800     MIMEInfo *mime, *n;
1801 
1802     for (mime = mime_list; mime; mime = n) {
1803         n = mime->next;
1804 
1805         xfree(mime->version);
1806         xfree(mime->type);
1807         xfree(mime->type_type);
1808         xfree(mime->type_charset);
1809         xfree(mime->type_boundary);
1810         xfree(mime->encoding);
1811         xfree(mime->disposition);
1812         xfree(mime->disposition_filename);
1813         xfree(mime);
1814     }
1815 
1816 }
1817