1 /*
2  * Decode MIME parts.
3  */
4 /* (C) Copyright 1993,1994 by Carnegie Mellon University
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, distribute, and sell this software
8  * and its documentation for any purpose is hereby granted without
9  * fee, provided that the above copyright notice appear in all copies
10  * and that both that copyright notice and this permission notice
11  * appear in supporting documentation, and that the name of Carnegie
12  * Mellon University not be used in advertising or publicity
13  * pertaining to distribution of the software without specific,
14  * written prior permission.  Carnegie Mellon University makes no
15  * representations about the suitability of this software for any
16  * purpose.  It is provided "as is" without express or implied
17  * warranty.
18  *
19  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
20  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
21  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
22  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
23  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
24  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
25  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
26  * SOFTWARE.  */
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include <unistd.h>
33 #include "xmalloc.h"
34 #include "common.h"
35 #include "part.h"
36 #include "md5.h"
37 
38 extern char *os_idtodir(char *id);
39 extern FILE *os_newtypedfile(char *fname, char *contentType, int flags, params contentParams);
40 extern FILE *os_createfile(char *fname);
41 extern FILE *os_createnewfile(char *fname);
42 extern char *md5contextTo64(MD5_CTX *context);
43 extern void warn(char *s);
44 extern void os_perror(char *str);
45 extern void chat(char *s);
46 extern void os_donewithdir(char *dir);
47 extern void os_warnMD5mismatch(void);
48 extern void os_closetypedfile(FILE *outfile);
49 
50 extern int part_depth(struct part *part);
51 extern void part_ungets(char *s, struct part *part);
52 extern void part_close(struct part *part);
53 extern int part_fill(struct part *part);
54 extern void part_addboundary(struct part *part, char *boundary);
55 extern int part_readboundary(struct part *part);
56 
57 /* The possible content transfer encodings */
58 enum encoding { enc_none, enc_qp, enc_base64 };
59 
60 char *ParseHeaders(struct part *inpart, char **subjectp, char **contentTypep, enum encoding *contentEncodingp, char **contentDispositionp, char **contentMD5p);
61 enum encoding parseEncoding(char *s);
62 params ParseContent(char **headerp);
63 char *getParam(params cParams, char *key);
64 char *getDispositionFilename(char *disposition);
65 void from64(struct part *inpart, FILE *outfile, char **digestp, int suppressCR);
66 void fromqp(struct part *inpart, FILE *outfile, char **digestp);
67 void fromnone(struct part *inpart, FILE *outfile, char **digestp);
68 int handlePartial(struct part *inpart, char *headers, params contentParams,
69 		  int extractText);
70 int ignoreMessage(struct part *inpart);
71 int handleMultipart(struct part *inpart, char *contentType,
72 		    params contentParams, int extractText);
73 int handleUuencode(struct part *inpart, char *subject, int extractText);
74 int handleText(struct part *inpart, enum encoding contentEncoding);
75 int saveToFile(struct part *inpart, int inAppleDouble, char *contentType,
76 	       params contentParams, enum encoding contentEncoding,
77 	       char *contentDisposition, char *contentMD5);
78 
79 /*
80  * Read and handle an RFC 822 message from the body-part 'inpart'.
81  */
handleMessage(struct part * inpart,char * defaultContentType,int inAppleDouble,int extractText)82 int handleMessage(struct part *inpart, char *defaultContentType, int inAppleDouble, int extractText)
83 {
84     char *headers, *subject, *contentType, *contentDisposition, *contentMD5;
85     enum encoding contentEncoding;
86     params contentParams;
87 
88     /* Parse the headers, getting the ones we're interested in */
89     headers = ParseHeaders(inpart, &subject, &contentType, &contentEncoding,
90 			   &contentDisposition, &contentMD5);
91     if (!headers) return 1;
92 
93     /* If no content type, or a non-MIME content type, use the default */
94     if (!contentType || !strchr(contentType, '/')) {
95 	contentType = defaultContentType;
96     }
97     contentParams = ParseContent(&contentType);
98 
99     if (!strcasecmp(contentType, "message/rfc822")) {
100 	if (contentEncoding != enc_none) {
101 	    warn("ignoring invalid content encoding on message/rfc822");
102 	}
103 
104 	/* Simple recursion */
105 	return handleMessage(inpart, "text/plain", 0, extractText);
106     }
107     else if (!strcasecmp(contentType, "message/partial")) {
108 	if (contentEncoding != enc_none) {
109 	    warn("ignoring invalid content encoding on message/partial");
110 	}
111 	return handlePartial(inpart, headers, contentParams, extractText);
112     }
113     else if (!strncasecmp(contentType, "message/", 8)) {
114 	/* Probably message/external.  We don't care--toss it */
115 	return ignoreMessage(inpart);
116     }
117     else if (!strncasecmp(contentType, "multipart/", 10)) {
118 	if (contentEncoding != enc_none) {
119 	    warn("ignoring invalid content encoding on multipart");
120 	}
121 	return handleMultipart(inpart, contentType, contentParams,
122 			       extractText);
123     }
124     else if (part_depth(inpart) == 0 &&
125 	     !strncasecmp(contentType, "text/", 5) &&
126 	     contentEncoding == enc_none &&
127 	     !getDispositionFilename(contentDisposition) &&
128 	     !getParam(contentParams, "name")) {
129 	/* top-level text message, handle as possible uuencoded file */
130 	return handleUuencode(inpart, subject, extractText);
131     }
132     else if (!extractText && !inAppleDouble &&
133 	     !strncasecmp(contentType, "text/", 5) &&
134 	     !getDispositionFilename(contentDisposition) &&
135 	     !getParam(contentParams, "name")) {
136 	return handleText(inpart, contentEncoding);
137     }
138     else {
139 	/* Some sort of attachment, extract it */
140 	return saveToFile(inpart, inAppleDouble, contentType, contentParams,
141 			  contentEncoding, contentDisposition, contentMD5);
142     }
143 }
144 
145 /*
146  * Skip whitespace and RFC-822 comments.
147  */
SkipWhitespace(char ** s)148 void SkipWhitespace(char **s)
149 {
150     char *p = *s;
151     int commentlevel = 0;
152 
153     while (*p && (isspace(*p) || *p == '(')) {
154 	if (*p == '\n') {
155 	    p++;
156 	    if (*p != ' ' && *p != '\t') {
157 		*s = 0;
158 		return;
159 	    }
160 	}
161 	else if (*p == '(') {
162 	    p++;
163 	    commentlevel++;
164 	    while (commentlevel) {
165 		switch (*p) {
166 		case '\n':
167 		    p++;
168 		    if (*p == ' ' || *p == '\t') break;
169 		    /* FALL THROUGH */
170 		case '\0':
171 		    *s = 0;
172 		    return;
173 
174 		case '\\':
175 		    p++;
176 		    break;
177 
178 		case '(':
179 		    commentlevel++;
180 		    break;
181 
182 		case ')':
183 		    commentlevel--;
184 		    break;
185 		}
186 		p++;
187 	    }
188 	}
189 	else p++;
190     }
191     if (*p == 0) {
192 	*s = 0;
193     }
194     else {
195 	*s = p;
196     }
197 }
198 
199 /*
200  * Read and parse the headers of an RFC 822 message, returning them in
201  * a pointer to a static buffer.  The headers are read from 'inpart'.
202  * A pointer to the value of any Subject:, Content-Type:,
203  * Content-Disposition:, or Content-MD5: header is stored in the space
204  * pointed to by 'subjectp', 'contentTypep', contentDispositionp, and
205  * contentMD5p, respectively.  The Content-Transfer-Encoding is stored
206  * in the enum pointed to by 'contentEncodingp'.
207  */
208 #define HEADGROWSIZE 1000
ParseHeaders(struct part * inpart,char ** subjectp,char ** contentTypep,enum encoding * contentEncodingp,char ** contentDispositionp,char ** contentMD5p)209 char *ParseHeaders(struct part *inpart, char **subjectp, char **contentTypep, enum encoding *contentEncodingp, char **contentDispositionp, char **contentMD5p)
210 {
211     static int alloced = 0;
212     static char *headers;
213     int left, len, i;
214     char *next, *val;
215 
216     /* Read headers into buffer pointed to by "headers" */
217     if (!alloced) {
218 	headers = xmalloc(alloced = HEADGROWSIZE);
219     }
220     next = headers;
221     *next++ = '\n';		/* Leading newline to make matching header names easier */
222     left = alloced - 2;		/* Allow room for terminating null */
223 
224     while (part_gets(next, left, inpart) && (*next != '\n' || next[-1] != '\n')) {
225 	len = strlen(next);
226 
227 	if (next[-1] == '\n') {
228 	    /* Check for valid header-ness of "next" */
229 	    for (i = 0; i < len; i++) {
230 		if (next[i] == ':' ||
231 		    next[i] <= ' ' || next[i] >= '\177') break;
232 	    }
233 	    if (i == 0 || next[i] != ':') {
234 		/* Check for header continuation line */
235 		if (next == headers+1 || (next[0] != ' ' && next[0] != '\t')) {
236 		    /*
237 		     * Not a valid header, push back on input stream
238 		     * and stop reading input.
239 		     */
240 		    part_ungets(next, inpart);
241 		    break;
242 		}
243 	    }
244 	}
245 
246 	left -= len;
247 	next += len;
248 
249 	if (left < 100) {
250 	    len = next - headers;
251 	    alloced += HEADGROWSIZE;
252 	    left += HEADGROWSIZE;
253 	    headers = xrealloc(headers, alloced);
254 	    next = headers + len;
255 	}
256     }
257 
258     *next = '\0';
259 
260     /* Look for the headers we find particularly interesting */
261     *subjectp = *contentTypep = *contentDispositionp = *contentMD5p = 0;
262     *contentEncodingp = enc_none;
263     for (next = headers; *next; next++) {
264 	if (*next == '\n') {
265 	    switch(next[1]) {
266 	    case 's':
267 	    case 'S':
268 		if (!strncasecmp(next+2, "ubject:", 7)) {
269 		    val = next+9;
270 		    SkipWhitespace(&val);
271 		    if (val) *subjectp = val;
272 		}
273 		break;
274 
275 	    case 'c':
276 	    case 'C':
277 		if (!strncasecmp(next+2, "ontent-type:", 12)) {
278 		    val = next+14;
279 		    SkipWhitespace(&val);
280 		    if (val) *contentTypep = val;
281 		}
282 		else if (!strncasecmp(next+2, "ontent-transfer-encoding:", 25)) {
283 		    *contentEncodingp = parseEncoding(next+27);
284 		}
285 		else if (!strncasecmp(next+2, "ontent-disposition:", 19)) {
286 		    val = next+21;
287 		    SkipWhitespace(&val);
288 		    if (val) *contentDispositionp = val;
289 		}
290 		else if (!strncasecmp(next+2, "ontent-md5:", 11)) {
291 		    val = next+13;
292 		    SkipWhitespace(&val);
293 		    if (val) *contentMD5p = val;
294 		}
295 	    }
296 	}
297     }
298     return headers;
299 }
300 
301 /*
302  * Parse the Content-Transfer-Encoding: value pointed to by 's'.
303  * Returns the appropriate encoding enum.
304  */
parseEncoding(char * s)305 enum encoding parseEncoding(char *s)
306 {
307     SkipWhitespace(&s);
308     if (s) {
309 	switch (*s) {
310 	case 'q':
311 	case 'Q':
312 	    if (!strncasecmp(s+1, "uoted-printable", 15) &&
313 		(isspace(s[16]) || s[16] == '(')) {
314 		return enc_qp;
315 	    }
316 	    break;
317 
318 	case '7':
319 	case '8':
320 	    if (!strncasecmp(s+1, "bit", 3) &&
321 		(isspace(s[4]) || s[4] == '(')) {
322 		return enc_none;
323 	    }
324 	    break;
325 
326 	case 'b':
327 	case 'B':
328 	    if (!strncasecmp(s+1, "ase64", 5) &&
329 		(isspace(s[6]) || s[6] == '(')) {
330 		return enc_base64;
331 	    }
332 	    if (!strncasecmp(s+1, "inary", 5) &&
333 		(isspace(s[6]) || s[6] == '(')) {
334 		return enc_none;
335 	    }
336 	}
337 	warn("ignoring unknown content transfer encoding\n");
338     }
339     return enc_none;
340 }
341 
342 /*
343  * Parse the value of a Content-Type: header.
344  * 'headerp' points to a pointer to the input string.
345  * The pointer pointed to by 'headerp' is changed to point to
346  * a static buffer containing the content type stripped of whitespace
347  * and parameters.  The parameters are converted to a type suitable for
348  * getParm() and returned.
349  */
350 #define PARAMGROWSIZE 10
ParseContent(char ** headerp)351 params ParseContent(char **headerp)
352 {
353     char *header;
354     static int palloced = 0;
355     static char **param;
356     static int calloced = 0;
357     static char *cbuf;
358     char *p;
359     int nparam;
360 
361     p = header = *headerp;
362 
363     /* Find end of header, including continuation lines */
364     do {
365 	p = strchr(p+1, '\n');
366     } while (p && isspace(p[1]));
367     if (!p) {
368 	p = header + strlen(header);
369     }
370 
371     /* If necessary, allocate/grow cbuf to hold header. */
372     if (p - header >= calloced) {
373 	calloced = p - header + 1;
374 	if (calloced < 200) calloced = 200;
375 	cbuf = xrealloc(cbuf, calloced);
376     }
377 
378     /* Copy header to cbuf */
379     strncpy(cbuf, header, p - header);
380     cbuf[p - header] = 0;
381     header = *headerp = cbuf;
382 
383     nparam = 0;
384 
385     /* Strip whitespace from content type */
386     /* ParseHeaders() stripped leading whitespace */
387     p = header;
388     while (header && *header && *header != ';') {
389 	while (*header && !isspace(*header) && *header != '(' &&
390 	       *header != ';') {
391 	    *p++ = *header++;
392 	}
393 	SkipWhitespace(&header);
394     }
395     if (!header || !*header) return 0;
396     header++;
397     *p = '\0';
398 
399     /* Parse the parameters */
400     while (*header) {
401 	SkipWhitespace(&header);
402 	if (!header) break;
403 
404 	if (nparam+1 >= palloced) {
405 	    palloced += PARAMGROWSIZE;
406 	    param = (char **) xrealloc((char *)param, palloced * sizeof(char *));
407 	}
408 	param[nparam++] = header;
409 
410 	/* Find any separating semicolon.  Pay attention to quoted-strings */
411 	while (*header && *header != ';') {
412 	    if (*header == '\"') {
413 		++header;
414 		while (*header && *header != '\"') {
415 		    if (*header == '\\') {
416 			++header;
417 			if (!*header) break;
418 		    }
419 		    ++header;
420 		}
421 		if (!*header) break;
422 	    }
423 	    else if (*header == '(') {
424 		/* Convert comments to spaces */
425 		p = header;
426 		SkipWhitespace(&p);
427 		if (!p) {
428 		    break;
429 		}
430 		while (header < p) *header++ = ' ';
431 		header--;
432 	    }
433 	    header++;
434 	}
435 	if (*header) *header++ = '\0';
436     }
437 
438     if (nparam == 0)
439        return 0;
440 
441     param[nparam] = 0;
442     return param;
443 }
444 
445 /*
446  * Get the value of the parameter named 'key' from the content-type
447  * parameters 'cParams'.  Returns a pointer to a static bufer which
448  * contains the value, or null if no such parameter was found.
449  */
450 #define VALUEGROWSIZE 100
getParam(params cParams,char * key)451 char *getParam(params cParams, char *key)
452 {
453     static char *value;
454     static int alloced = 0;
455     int left;
456     int keylen = strlen(key);
457     char *from, *to;
458 
459     if (!cParams) return 0;
460 
461     if (!alloced) {
462 	value = xmalloc(alloced = VALUEGROWSIZE);
463     }
464 
465     /* Find the named parameter */
466     while (*cParams) {
467 	if (!strncasecmp(key, *cParams, keylen) &&
468 	    ((*cParams)[keylen] == '=' || isspace((*cParams)[keylen]))) break;
469 	cParams++;
470     }
471     if (!*cParams) return 0;
472 
473     /* Skip over the "=" and any surrounding whitespace */
474     from = *cParams + keylen;
475     while (*from && isspace(*from)) from++;
476     if (*from++ != '=') return 0;
477     while (*from && isspace(*from)) from++;
478     if (!*from) return 0;
479 
480     /* Copy value into buffer */
481     to = value;
482     left = alloced - 1;
483     if (*from == '\"') {
484 	/* Quoted-string */
485 	from++;
486 	while (*from && *from != '\"') {
487 	    if (!--left) {
488 		alloced += VALUEGROWSIZE;
489 		left += VALUEGROWSIZE;
490 		value = xrealloc(value, alloced);
491 		to = value + alloced - left - 2;
492 	    }
493 	    if (*from == '\\') {
494 		from++;
495 		if (!*from) return 0;
496 	    }
497 	    *to++ = *from++;
498 	}
499 	if (!*from) return 0;
500     }
501     else {
502 	/* Just a token */
503 	while (*from && !isspace(*from)) {
504 	    if (!--left) {
505 		alloced += VALUEGROWSIZE;
506 		left += VALUEGROWSIZE;
507 		value = xrealloc(value, alloced);
508 		to = value + alloced - left - 2;
509 	    }
510 	    *to++ = *from++;
511 	}
512     }
513     *to = '\0';
514     return value;
515 }
516 
517 /*
518  * Get the value of the "filename" parameter in a Content-Disposition:
519  * header.  Returns a pointer to a static buffer containing the value, or
520  * a null pointer if there was no such parameter.
521  */
522 char *
getDispositionFilename(char * disposition)523 getDispositionFilename(char *disposition)
524 {
525     static char *value;
526     static int alloced = 0;
527     int left;
528     char *to;
529 
530     if (!disposition) return 0;
531 
532     /* Skip until we find ";" "filename" "=" tokens. */
533     for (;;) {
534 	/* Skip until we find ";" */
535 	while (*disposition != ';') {
536 	    if (!*disposition) return 0;
537 	    else if (*disposition == '\"') {
538 		++disposition;
539 		while (*disposition && *disposition != '\"') {
540 		    if (*disposition == '\\') {
541 			++disposition;
542 			if (!*disposition) return 0;
543 		    }
544 		    ++disposition;
545 		}
546 		if (!*disposition) return 0;
547 	    }
548 	    else if (*disposition == '(') {
549 		SkipWhitespace(&disposition);
550 		if (!disposition) return 0;
551 		disposition--;
552 	    }
553 	    disposition++;
554 	}
555 
556 	/* Skip over ";" and trailing whitespace */
557 	disposition++;
558 	SkipWhitespace(&disposition);
559 	if (!disposition) return 0;
560 
561 	/*
562 	 * If we're not looking at a "filename" token, go back
563 	 * and look for another ";".  Otherwise skip it and
564 	 * trailing whitespace.
565 	 */
566 	if (strncasecmp(disposition, "filename", 8) != 0) continue;
567 	disposition += 8;
568 	if (!isspace(*disposition) && *disposition != '=' &&
569 	    *disposition != '(') {
570 	    continue;
571 	}
572 	SkipWhitespace(&disposition);
573 	if (!disposition) return 0;
574 
575 	/* If we're looking at a ";", we found what we're looking for */
576 	if (*disposition++ == ';') break;
577     }
578 
579     SkipWhitespace(&disposition);
580     if (!disposition) return 0;
581 
582     if (!alloced) {
583 	value = xmalloc(alloced = VALUEGROWSIZE);
584     }
585 
586     /* Copy value into buffer */
587     to = value;
588     left = alloced - 1;
589     if (*disposition == '\"') {
590 	/* Quoted-string */
591 	disposition++;
592 	while (*disposition && *disposition != '\"') {
593 	    if (!--left) {
594 		alloced += VALUEGROWSIZE;
595 		left += VALUEGROWSIZE;
596 		value = xrealloc(value, alloced);
597 		to = value + alloced - left - 2;
598 	    }
599 	    if (*disposition == '\\') {
600 		disposition++;
601 		if (!*disposition) return 0;
602 	    }
603 	    *to++ = *disposition++;
604 	}
605 	if (!*disposition) return 0;
606     }
607     else {
608 	/* Just a token */
609 	while (*disposition && !isspace(*disposition) &&
610 	       *disposition != '(') {
611 	    if (!--left) {
612 		alloced += VALUEGROWSIZE;
613 		left += VALUEGROWSIZE;
614 		value = xrealloc(value, alloced);
615 		to = value + alloced - left - 2;
616 	    }
617 	    *to++ = *disposition++;
618 	}
619     }
620     *to = '\0';
621     return value;
622 }
623 
624 /*
625  * Read and handle a message/partial object from the file 'inpart'.
626  */
handlePartial(struct part * inpart,char * headers,params contentParams,int extractText)627 int handlePartial(struct part *inpart, char *headers, params contentParams, int extractText)
628 {
629     char *id, *dir, *p;
630     int thispart;
631     int nparts = 0;
632     char buf[1024];
633     FILE *partfile, *outfile;
634     struct part *outpart;
635     int i, docopy;
636 
637     id = getParam(contentParams, "id");
638     if (!id) {
639 	warn("partial message has no id parameter");
640 	goto ignore;
641     }
642 
643     /* Get directory to store the parts being reassembled */
644     dir = os_idtodir(id);
645     if (!dir) goto ignore;
646 
647     p = getParam(contentParams, "number");
648     if (!p) {
649 	warn("partial message doesn't have number parameter");
650 	goto ignore;
651     }
652     thispart = atoi(p);
653 
654     if ((p = getParam(contentParams, "total"))) {
655 	nparts = atoi(p);
656 	if (nparts <= 0) {
657 	    warn("partial message has invalid number of parts");
658 	    goto ignore;
659 	}
660 	/* Store number of parts in reassembly directory */
661 	sprintf(buf, "%sCT", dir);
662 	partfile = os_createfile(buf);
663 	if (!partfile) {
664 	    os_perror(buf);
665 	    goto ignore;
666 	}
667 	fprintf(partfile, "%d\n", nparts);
668 	fclose(partfile);
669     }
670     else {
671 	/* Try to retrieve number of parts from reassembly directory */
672 	sprintf(buf, "%sCT", dir);
673 	if ((partfile = fopen(buf, "r"))) {
674 	    if (fgets(buf, sizeof(buf), partfile)) {
675 		nparts = atoi(buf);
676 		if (nparts < 0) nparts = 0;
677 	    }
678 	    fclose(partfile);
679 	}
680     }
681 
682     /* Sanity check */
683     if (thispart <= 0 || (nparts && thispart > nparts)) {
684 	warn("partial message has invalid number");
685 	goto ignore;
686     }
687 
688     sprintf(buf, "Saving part %d ", thispart);
689     if (nparts) sprintf(buf+strlen(buf), "of %d ", nparts);
690     strcat(buf, getParam(contentParams, "id"));
691     chat(buf);
692 
693     /* Create file to store this part */
694     sprintf(buf, "%s%d", dir, thispart);
695     partfile = os_createnewfile(buf);
696     if (!partfile) {
697 	os_perror(buf);
698 	goto ignore;
699     }
700 
701     /* Do special-case header handling for first part */
702     if (thispart == 1) {
703 	int skippedfirstbyte = 0;
704 
705 	while (*headers) {
706 	    if (*headers == '\n' &&
707 		(!strncasecmp(headers, "\ncontent-", 9) ||
708 		 !strncasecmp(headers, "\nmessage-id:", 12))) {
709 		/* Special case, skip header */
710 		headers++;
711 		while (*headers && (*headers != '\n' || isspace(headers[1]))) {
712 		    headers++;
713 		}
714 	    }
715 	    else {
716 		/* First byte of headers is extra newline, don't write it to file */
717 		if (skippedfirstbyte++)	putc(*headers, partfile);
718 		headers++;
719 	    }
720 	}
721 	docopy = 0;
722 	/* Handle headers in the multipart/partial body */
723 	while (part_gets(buf, sizeof(buf), inpart)) {
724 	    if (*buf == '\n') {
725 		putc('\n', partfile);
726 		break;
727 	    }
728 	    if (!strncasecmp(buf, "content-", 8) || !strncasecmp(buf, "message-id:", 11)) {
729 		docopy = 1;
730 	    }
731 	    else if (!isspace(*buf)) {
732 		docopy = 0;
733 	    }
734 
735 	    if (docopy) fputs(buf, partfile);
736 	    while(buf[strlen(buf)-1] != '\n' && part_gets(buf, sizeof(buf), inpart)) {
737 		if (docopy) fputs(buf, partfile);
738 	    }
739 	}
740     }
741 
742     /* Copy the contents to the file */
743     while (part_gets(buf, sizeof(buf), inpart)) {
744 	fputs(buf, partfile);
745     }
746     fclose(partfile);
747 
748     /* Check to see if we have all parts.  Start from the highest numbers
749      * as we are more likely not to have them.
750      */
751     for (i = nparts; i; i--) {
752 	sprintf(buf, "%s%d", dir, i);
753 	partfile = fopen(buf, "r");
754 	if (partfile) {
755 	    fclose(partfile);
756 	}
757 	else {
758 	    break;
759 	}
760     }
761 
762     if (i || !nparts) {
763 	/* We don't have all the parts yet */
764 	return 0;
765     }
766 
767     /* We have everything, concatenate all the parts into a single file */
768     sprintf(buf, "%sFULL", dir);
769     outfile = os_createnewfile(buf);
770     if (!outfile) {
771 	os_perror(buf);
772 	return 1;
773     }
774     for (i=1; i<=nparts; i++) {
775 	sprintf(buf, "%s%d", dir, i);
776 	partfile = fopen(buf, "r");
777 	if (!partfile) {
778 	    os_perror(buf);
779 	    return 1;
780 	}
781 	while (fgets(buf, sizeof(buf), partfile)) {
782 	    fputs(buf, outfile);
783 	}
784 	fclose(partfile);
785 
786 	/* Done with message part file, delete it */
787 	sprintf(buf, "%s%d", dir, i);
788 	remove(buf);
789     }
790 
791     /* Open the concatenated file for reading and handle it */
792     fclose(outfile);
793     sprintf(buf, "%sFULL", dir);
794     outfile = fopen(buf, "r");
795     if (!outfile) {
796 	os_perror(buf);
797 	return 1;
798     }
799     outpart = part_init(outfile);
800     handleMessage(outpart, "text/plain", 0, extractText);
801     part_close(outpart);
802 
803     /* Clean up the rest of the reassembly directory */
804     sprintf(buf, "%sFULL", dir);
805     remove(buf);
806     sprintf(buf, "%sCT", dir);
807     remove(buf);
808     os_donewithdir(dir);
809 
810     return 0;
811 
812  ignore:
813     ignoreMessage(inpart);
814     return 1;
815 }
816 
817 /*
818  * Skip over a message object from the file 'inpart'.
819  */
ignoreMessage(struct part * inpart)820 int ignoreMessage(struct part *inpart)
821 {
822     while (part_getc(inpart) != EOF);
823     return 0;
824 }
825 
826 /*
827  * Read and handle a multipart object from 'inpart'.
828  */
handleMultipart(struct part * inpart,char * contentType,params contentParams,int extractText)829 int handleMultipart(struct part *inpart, char *contentType, params contentParams, int extractText)
830 {
831     char *id;
832     char *defaultContentType = "text/plain";
833     int isAppleDouble = 0;
834 
835     /* Components of multipart/digest have a different default content-type */
836     if (!strcasecmp(contentType, "multipart/digest")) {
837 	defaultContentType = "message/rfc822";
838     }
839     if (!strcasecmp(contentType, "multipart/appledouble")) {
840 	isAppleDouble++;
841     }
842 
843     if (!(id = getParam(contentParams, "boundary"))) {
844 	warn("multipart message has no boundary parameter");
845 	id="";
846     }
847 
848     /* Add the new boundary id */
849     part_addboundary(inpart, id);
850 
851 #ifdef __riscos
852     /*
853      * "Marcel" encodes RISCOS directory structure in the multipart
854      * structure.  That is the Wrong Way to do it, but we hold our
855      * nose and pass the information to the OS layer.
856      */
857     os_boundaryhookopen(part_depth(inpart));
858 #endif
859 
860     /*
861      * Skip over preamble.
862      * HACK: The initial boundary doesn't have to start with a newline,
863      * so we deal with this by stuffing an initial newline into the input
864      * stream
865      */
866     part_ungetc('\n', inpart);
867     ignoreMessage(inpart);
868 
869     /* Handle the component messages */
870     while (!part_readboundary(inpart)) {
871 	handleMessage(inpart, defaultContentType, isAppleDouble, extractText);
872     }
873 
874 #ifdef __riscos
875     os_boundaryhookclose(part_depth(inpart));
876 #endif
877 
878     /* Skip over postamble */
879     ignoreMessage(inpart);
880 
881     /* Remove any lingering unused description file */
882     (void) remove(TEMPFILENAME);
883 
884     return 0;
885 }
886 
887 /*
888  * Handle a text message object from 'inpart' by saving it to
889  * the temporary description file.
890  */
handleText(struct part * inpart,enum encoding contentEncoding)891 int handleText(struct part *inpart, enum encoding contentEncoding)
892 {
893     FILE *descfile;
894 
895     descfile = os_createnewfile(TEMPFILENAME);
896     if (!descfile) {
897 	os_perror(TEMPFILENAME);
898 	ignoreMessage(inpart);
899 	return 1;
900     }
901 
902     /* Write the file, handling the appropriate encoding */
903     switch (contentEncoding) {
904     case enc_none:
905 	fromnone(inpart, descfile, (char **)0);
906 	break;
907 
908     case enc_qp:
909 	fromqp(inpart, descfile, (char **)0);
910 	break;
911 
912     case enc_base64:
913 	from64(inpart, descfile, (char **)0, 1);
914 	break;
915     }
916 
917     fclose(descfile);
918     return 0;
919 }
920 
921 /*
922  * Read a message object from 'inpart' and save it to a file.
923  */
saveToFile(struct part * inpart,int inAppleDouble,char * contentType,params contentParams,enum encoding contentEncoding,char * contentDisposition,char * contentMD5)924 int saveToFile(struct part *inpart, int inAppleDouble, char *contentType, params contentParams, enum encoding contentEncoding, char *contentDisposition, char *contentMD5)
925 {
926     FILE *outfile = 0;
927     int flags = 0;
928     int suppressCR = 0;
929     char *outputmd5;
930     char *fname;
931 
932     if (!strncasecmp(contentType, "text/", 5)) {
933 	suppressCR = 1;
934     }
935     else if (contentEncoding == enc_base64) {
936 	/*
937 	 * HEURISTIC: It is not in general possible to determine whether
938 	 * any non-text content type is line-oriented.  We guess
939 	 * the "binary" status of a part from the composer's choice
940 	 * of content transfer encoding.
941 	 *
942 	 * If the content transfer encoding is "binary" and the input is
943 	 * not line-oriented, we're screwed anyway--the input file has
944 	 * been opened in text mode.  So the "binary output file" heuristic
945 	 * is not applied in this case.
946 	 */
947 	flags |= FILE_BINARY;
948     }
949 
950     if (inAppleDouble) flags |= FILE_INAPPLEDOUBLE;
951 
952     /* Find an appropriate filename and create the output file */
953     fname = getDispositionFilename(contentDisposition);
954     if (!fname) fname = getParam(contentParams, "name");
955     if (fname) fname = strsave(fname);
956     outfile = os_newtypedfile(fname, contentType, flags, contentParams);
957     if (fname) free(fname);
958     if (!outfile) {
959 	ignoreMessage(inpart);
960 	return 1;
961     }
962 
963     /* Write the file, handling the appropriate encoding */
964     switch (contentEncoding) {
965     case enc_none:
966 	fromnone(inpart, outfile, &outputmd5);
967 	break;
968 
969     case enc_qp:
970 	fromqp(inpart, outfile, &outputmd5);
971 	break;
972 
973     case enc_base64:
974 	from64(inpart, outfile, &outputmd5, suppressCR);
975 	break;
976     }
977     rewind(outfile);
978 
979     /* Check the MD5 digest if it was supplied */
980     if (contentMD5) {
981 	if (strncmp(outputmd5, contentMD5, strlen(outputmd5)) != 0) {
982 	    os_warnMD5mismatch();
983 	}
984     }
985     free(outputmd5);
986 
987     os_closetypedfile(outfile);
988     return 0;
989 }
990 
991 #define XX 127
992 /*
993  * Table for decoding hexadecimal in quoted-printable
994  */
995 static char index_hex[256] = {
996     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
997     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
998     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
999      0, 1, 2, 3,  4, 5, 6, 7,  8, 9,XX,XX, XX,XX,XX,XX,
1000     XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1001     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1002     XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1003     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1004     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1005     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1006     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1007     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1008     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1009     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1010     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1011     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1012 };
1013 #define HEXCHAR(c)  (index_hex[(unsigned char)(c)])
1014 
1015 /*
1016  * Table for decoding base64
1017  */
1018 static char index_64[256] = {
1019     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1020     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1021     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
1022     52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
1023     XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
1024     15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
1025     XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
1026     41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
1027     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1028     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1029     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1030     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1031     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1032     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1033     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1034     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1035 };
1036 #define CHAR64(c)  (index_64[(unsigned char)(c)])
1037 
from64(struct part * inpart,FILE * outfile,char ** digestp,int suppressCR)1038 void from64(struct part *inpart, FILE *outfile, char **digestp, int suppressCR)
1039 {
1040     int c1, c2, c3, c4;
1041     int DataDone = 0;
1042     char buf[3];
1043     MD5_CTX context;
1044 
1045     if (digestp) MD5Init(&context);
1046     while ((c1 = part_getc(inpart)) != EOF) {
1047         if (c1 != '=' && CHAR64(c1) == XX) {
1048             continue;
1049         }
1050         if (DataDone) continue;
1051         do {
1052             c2 = part_getc(inpart);
1053         } while (c2 != EOF && c2 != '=' && CHAR64(c2) == XX);
1054         do {
1055             c3 = part_getc(inpart);
1056         } while (c3 != EOF && c3 != '=' && CHAR64(c3) == XX);
1057         do {
1058             c4 = part_getc(inpart);
1059         } while (c4 != EOF && c4 != '=' && CHAR64(c4) == XX);
1060         if (c2 == EOF || c3 == EOF || c4 == EOF) {
1061             warn("Premature EOF");
1062             break;
1063         }
1064         if (c1 == '=' || c2 == '=') {
1065             DataDone=1;
1066             continue;
1067         }
1068         c1 = CHAR64(c1);
1069         c2 = CHAR64(c2);
1070 	buf[0] = ((c1<<2) | ((c2&0x30)>>4));
1071         if (!suppressCR || buf[0] != '\r') putc(buf[0], outfile);
1072         if (c3 == '=') {
1073 	    if (digestp) MD5Update(&context, buf, 1);
1074             DataDone = 1;
1075         } else {
1076             c3 = CHAR64(c3);
1077 	    buf[1] = (((c2&0x0F) << 4) | ((c3&0x3C) >> 2));
1078             if (!suppressCR || buf[1] != '\r') putc(buf[1], outfile);
1079             if (c4 == '=') {
1080 		if (digestp) MD5Update(&context, buf, 2);
1081                 DataDone = 1;
1082             } else {
1083                 c4 = CHAR64(c4);
1084 		buf[2] = (((c3&0x03) << 6) | c4);
1085                 if (!suppressCR || buf[2] != '\r') putc(buf[2], outfile);
1086 		if (digestp) MD5Update(&context, buf, 3);
1087             }
1088         }
1089     }
1090     if (digestp) *digestp = md5contextTo64(&context);
1091 }
1092 
fromqp(struct part * inpart,FILE * outfile,char ** digestp)1093 void fromqp(struct part *inpart, FILE *outfile, char **digestp)
1094 {
1095     int c1, c2;
1096     MD5_CTX context;
1097     char c;
1098 
1099     if (digestp) MD5Init(&context);
1100 
1101     while ((c1 = part_getc(inpart)) != EOF) {
1102 	if (c1 == '=') {
1103 	    c1 = part_getc(inpart);
1104 	    if (c1 != '\n') {
1105 		c1 = HEXCHAR(c1);
1106 		c2 = part_getc(inpart);
1107 		c2 = HEXCHAR(c2);
1108 		c = c1<<4 | c2;
1109 		if (c != '\r') putc(c, outfile);
1110 		if (digestp) MD5Update(&context, &c, 1);
1111 	    }
1112 	} else {
1113 	    putc(c1, outfile);
1114 	    if (c1 == '\n') {
1115 		if (digestp) MD5Update(&context, "\r", 1);
1116 	    }
1117 	    c = c1;
1118 	    if (digestp) MD5Update(&context, &c, 1);
1119 	}
1120     }
1121     if (digestp) *digestp=md5contextTo64(&context);
1122 }
1123 
fromnone(struct part * inpart,FILE * outfile,char ** digestp)1124 void fromnone(struct part *inpart, FILE *outfile, char **digestp)
1125 {
1126     int c;
1127     char ch;
1128     MD5_CTX context;
1129 
1130     if (digestp) MD5Init(&context);
1131 
1132     while ((c = part_getc(inpart)) != EOF) {
1133 	putc(c, outfile);
1134 	if (c == '\n') {
1135 	    if (digestp) MD5Update(&context, "\r", 1);
1136 	}
1137 	ch = c;
1138 	if (digestp) MD5Update(&context, &ch, 1);
1139     }
1140     if (digestp) *digestp=md5contextTo64(&context);
1141 }
1142 
1143