xref: /freebsd/contrib/sendmail/src/mime.c (revision 4cf49a43559ed9fdad601bdcccd2c55963008675)
1 /*
2  * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
3  * Copyright (c) 1994, 1996-1997 Eric P. Allman.  All rights reserved.
4  * Copyright (c) 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * By using this file, you agree to the terms and conditions set
8  * forth in the LICENSE file which can be found at the top level of
9  * the sendmail distribution.
10  *
11  */
12 
13 # include "sendmail.h"
14 # include <string.h>
15 
16 #ifndef lint
17 static char sccsid[] = "@(#)mime.c	8.71 (Berkeley) 1/18/1999";
18 #endif /* not lint */
19 
20 /*
21 **  MIME support.
22 **
23 **	I am indebted to John Beck of Hewlett-Packard, who contributed
24 **	his code to me for inclusion.  As it turns out, I did not use
25 **	his code since he used a "minimum change" approach that used
26 **	several temp files, and I wanted a "minimum impact" approach
27 **	that would avoid copying.  However, looking over his code
28 **	helped me cement my understanding of the problem.
29 **
30 **	I also looked at, but did not directly use, Nathaniel
31 **	Borenstein's "code.c" module.  Again, it functioned as
32 **	a file-to-file translator, which did not fit within my
33 **	design bounds, but it was a useful base for understanding
34 **	the problem.
35 */
36 
37 #if MIME8TO7
38 
39 /* character set for hex and base64 encoding */
40 char	Base16Code[] =	"0123456789ABCDEF";
41 char	Base64Code[] =	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
42 
43 /* types of MIME boundaries */
44 #define MBT_SYNTAX	0	/* syntax error */
45 #define MBT_NOTSEP	1	/* not a boundary */
46 #define MBT_INTERMED	2	/* intermediate boundary (no trailing --) */
47 #define MBT_FINAL	3	/* final boundary (trailing -- included) */
48 
49 static char	*MimeBoundaryNames[] =
50 {
51 	"SYNTAX",	"NOTSEP",	"INTERMED",	"FINAL"
52 };
53 
54 bool	MapNLtoCRLF;
55 
56 extern int	mimeboundary __P((char *, char **));
57 /*
58 **  MIME8TO7 -- output 8 bit body in 7 bit format
59 **
60 **	The header has already been output -- this has to do the
61 **	8 to 7 bit conversion.  It would be easy if we didn't have
62 **	to deal with nested formats (multipart/xxx and message/rfc822).
63 **
64 **	We won't be called if we don't have to do a conversion, and
65 **	appropriate MIME-Version: and Content-Type: fields have been
66 **	output.  Any Content-Transfer-Encoding: field has not been
67 **	output, and we can add it here.
68 **
69 **	Parameters:
70 **		mci -- mailer connection information.
71 **		header -- the header for this body part.
72 **		e -- envelope.
73 **		boundaries -- the currently pending message boundaries.
74 **			NULL if we are processing the outer portion.
75 **		flags -- to tweak processing.
76 **
77 **	Returns:
78 **		An indicator of what terminated the message part:
79 **		  MBT_FINAL -- the final boundary
80 **		  MBT_INTERMED -- an intermediate boundary
81 **		  MBT_NOTSEP -- an end of file
82 */
83 
84 struct args
85 {
86 	char	*field;		/* name of field */
87 	char	*value;		/* value of that field */
88 };
89 
90 int
91 mime8to7(mci, header, e, boundaries, flags)
92 	register MCI *mci;
93 	HDR *header;
94 	register ENVELOPE *e;
95 	char **boundaries;
96 	int flags;
97 {
98 	register char *p;
99 	int linelen;
100 	int bt;
101 	off_t offset;
102 	size_t sectionsize, sectionhighbits;
103 	int i;
104 	char *type;
105 	char *subtype;
106 	char *cte;
107 	char **pvp;
108 	int argc = 0;
109 	char *bp;
110 	bool use_qp = FALSE;
111 	struct args argv[MAXMIMEARGS];
112 	char bbuf[128];
113 	char buf[MAXLINE];
114 	char pvpbuf[MAXLINE];
115 	extern u_char MimeTokenTab[256];
116 	extern int mime_getchar __P((FILE *, char **, int *));
117 	extern int mime_getchar_crlf __P((FILE *, char **, int *));
118 
119 	if (tTd(43, 1))
120 	{
121 		printf("mime8to7: flags = %x, boundaries =", flags);
122 		if (boundaries[0] == NULL)
123 			printf(" <none>");
124 		else
125 		{
126 			for (i = 0; boundaries[i] != NULL; i++)
127 				printf(" %s", boundaries[i]);
128 		}
129 		printf("\n");
130 	}
131 	MapNLtoCRLF = TRUE;
132 	p = hvalue("Content-Transfer-Encoding", header);
133 	if (p == NULL ||
134 	    (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
135 			   MimeTokenTab)) == NULL ||
136 	    pvp[0] == NULL)
137 	{
138 		cte = NULL;
139 	}
140 	else
141 	{
142 		cataddr(pvp, NULL, buf, sizeof buf, '\0');
143 		cte = newstr(buf);
144 	}
145 
146 	type = subtype = NULL;
147 	p = hvalue("Content-Type", header);
148 	if (p == NULL)
149 	{
150 		if (bitset(M87F_DIGEST, flags))
151 			p = "message/rfc822";
152 		else
153 			p = "text/plain";
154 	}
155 	if (p != NULL &&
156 	    (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
157 			   MimeTokenTab)) != NULL &&
158 	    pvp[0] != NULL)
159 	{
160 		if (tTd(43, 40))
161 		{
162 			for (i = 0; pvp[i] != NULL; i++)
163 				printf("pvp[%d] = \"%s\"\n", i, pvp[i]);
164 		}
165 		type = *pvp++;
166 		if (*pvp != NULL && strcmp(*pvp, "/") == 0 &&
167 		    *++pvp != NULL)
168 		{
169 			subtype = *pvp++;
170 		}
171 
172 		/* break out parameters */
173 		while (*pvp != NULL && argc < MAXMIMEARGS)
174 		{
175 			/* skip to semicolon separator */
176 			while (*pvp != NULL && strcmp(*pvp, ";") != 0)
177 				pvp++;
178 			if (*pvp++ == NULL || *pvp == NULL)
179 				break;
180 
181 			/* extract field name */
182 			argv[argc].field = *pvp++;
183 
184 			/* see if there is a value */
185 			if (*pvp != NULL && strcmp(*pvp, "=") == 0 &&
186 			    (*++pvp == NULL || strcmp(*pvp, ";") != 0))
187 			{
188 				argv[argc].value = *pvp;
189 				argc++;
190 			}
191 		}
192 	}
193 
194 	/* check for disaster cases */
195 	if (type == NULL)
196 		type = "-none-";
197 	if (subtype == NULL)
198 		subtype = "-none-";
199 
200 	/* don't propogate some flags more than one level into the message */
201 	flags &= ~M87F_DIGEST;
202 
203 	/*
204 	**  Check for cases that can not be encoded.
205 	**
206 	**	For example, you can't encode certain kinds of types
207 	**	or already-encoded messages.  If we find this case,
208 	**	just copy it through.
209 	*/
210 
211 	snprintf(buf, sizeof buf, "%.100s/%.100s", type, subtype);
212 	if (wordinclass(buf, 'n') || (cte != NULL && !wordinclass(cte, 'e')))
213 		flags |= M87F_NO8BIT;
214 
215 #ifdef USE_B_CLASS
216 	if (wordinclass(buf, 'b') || wordinclass(type, 'b'))
217 		MapNLtoCRLF = FALSE;
218 #endif
219 	if (wordinclass(buf, 'q') || wordinclass(type, 'q'))
220 		use_qp = TRUE;
221 
222 	/*
223 	**  Multipart requires special processing.
224 	**
225 	**	Do a recursive descent into the message.
226 	*/
227 
228 	if (strcasecmp(type, "multipart") == 0 &&
229 	    (!bitset(M87F_NO8BIT, flags) || bitset(M87F_NO8TO7, flags)))
230 	{
231 		int blen;
232 
233 		if (strcasecmp(subtype, "digest") == 0)
234 			flags |= M87F_DIGEST;
235 
236 		for (i = 0; i < argc; i++)
237 		{
238 			if (strcasecmp(argv[i].field, "boundary") == 0)
239 				break;
240 		}
241 		if (i >= argc || argv[i].value == NULL)
242 		{
243 			syserr("mime8to7: Content-Type: \"%s\": %s boundary",
244 				i >= argc ? "missing" : "bogus", p);
245 			p = "---";
246 
247 			/* avoid bounce loops */
248 			e->e_flags |= EF_DONT_MIME;
249 		}
250 		else
251 		{
252 			p = argv[i].value;
253 			stripquotes(p);
254 		}
255 		blen = strlen(p);
256 		if (blen > sizeof bbuf - 1)
257 		{
258 			syserr("mime8to7: multipart boundary \"%s\" too long",
259 				p);
260 			blen = sizeof bbuf - 1;
261 
262 			/* avoid bounce loops */
263 			e->e_flags |= EF_DONT_MIME;
264 		}
265 		strncpy(bbuf, p, blen);
266 		bbuf[blen] = '\0';
267 		if (tTd(43, 1))
268 			printf("mime8to7: multipart boundary \"%s\"\n", bbuf);
269 		for (i = 0; i < MAXMIMENESTING; i++)
270 			if (boundaries[i] == NULL)
271 				break;
272 		if (i >= MAXMIMENESTING)
273 		{
274 			syserr("mime8to7: multipart nesting boundary too deep");
275 
276 			/* avoid bounce loops */
277 			e->e_flags |= EF_DONT_MIME;
278 		}
279 		else
280 		{
281 			boundaries[i] = bbuf;
282 			boundaries[i + 1] = NULL;
283 		}
284 		mci->mci_flags |= MCIF_INMIME;
285 
286 		/* skip the early "comment" prologue */
287 		putline("", mci);
288 		mci->mci_flags &= ~MCIF_INHEADER;
289 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
290 		{
291 			bt = mimeboundary(buf, boundaries);
292 			if (bt != MBT_NOTSEP)
293 				break;
294 			putxline(buf, strlen(buf), mci, PXLF_MAPFROM|PXLF_STRIP8BIT);
295 			if (tTd(43, 99))
296 				printf("  ...%s", buf);
297 		}
298 		if (feof(e->e_dfp))
299 			bt = MBT_FINAL;
300 		while (bt != MBT_FINAL)
301 		{
302 			auto HDR *hdr = NULL;
303 
304 			snprintf(buf, sizeof buf, "--%s", bbuf);
305 			putline(buf, mci);
306 			if (tTd(43, 35))
307 				printf("  ...%s\n", buf);
308 			collect(e->e_dfp, FALSE, &hdr, e);
309 			if (tTd(43, 101))
310 				putline("+++after collect", mci);
311 			putheader(mci, hdr, e, flags);
312 			if (tTd(43, 101))
313 				putline("+++after putheader", mci);
314 			bt = mime8to7(mci, hdr, e, boundaries, flags);
315 		}
316 		snprintf(buf, sizeof buf, "--%s--", bbuf);
317 		putline(buf, mci);
318 		if (tTd(43, 35))
319 			printf("  ...%s\n", buf);
320 		boundaries[i] = NULL;
321 		mci->mci_flags &= ~MCIF_INMIME;
322 
323 		/* skip the late "comment" epilogue */
324 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
325 		{
326 			bt = mimeboundary(buf, boundaries);
327 			if (bt != MBT_NOTSEP)
328 				break;
329 			putxline(buf, strlen(buf), mci, PXLF_MAPFROM|PXLF_STRIP8BIT);
330 			if (tTd(43, 99))
331 				printf("  ...%s", buf);
332 		}
333 		if (feof(e->e_dfp))
334 			bt = MBT_FINAL;
335 		if (tTd(43, 3))
336 			printf("\t\t\tmime8to7=>%s (multipart)\n",
337 				MimeBoundaryNames[bt]);
338 		return bt;
339 	}
340 
341 	/*
342 	**  Message/xxx types -- recurse exactly once.
343 	**
344 	**	Class 's' is predefined to have "rfc822" only.
345 	*/
346 
347 	if (strcasecmp(type, "message") == 0)
348 	{
349 		if (!wordinclass(subtype, 's'))
350 		{
351 			flags |= M87F_NO8BIT;
352 		}
353 		else
354 		{
355 			auto HDR *hdr = NULL;
356 
357 			putline("", mci);
358 
359 			mci->mci_flags |= MCIF_INMIME;
360 			collect(e->e_dfp, FALSE, &hdr, e);
361 			if (tTd(43, 101))
362 				putline("+++after collect", mci);
363 			putheader(mci, hdr, e, flags);
364 			if (tTd(43, 101))
365 				putline("+++after putheader", mci);
366 			if (hvalue("MIME-Version", hdr) == NULL)
367 				putline("MIME-Version: 1.0", mci);
368 			bt = mime8to7(mci, hdr, e, boundaries, flags);
369 			mci->mci_flags &= ~MCIF_INMIME;
370 			return bt;
371 		}
372 	}
373 
374 	/*
375 	**  Non-compound body type
376 	**
377 	**	Compute the ratio of seven to eight bit characters;
378 	**	use that as a heuristic to decide how to do the
379 	**	encoding.
380 	*/
381 
382 	sectionsize = sectionhighbits = 0;
383 	if (!bitset(M87F_NO8BIT|M87F_NO8TO7, flags))
384 	{
385 		/* remember where we were */
386 		offset = ftell(e->e_dfp);
387 		if (offset == -1)
388 			syserr("mime8to7: cannot ftell on df%s", e->e_id);
389 
390 		/* do a scan of this body type to count character types */
391 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
392 		{
393 			if (mimeboundary(buf, boundaries) != MBT_NOTSEP)
394 				break;
395 			for (p = buf; *p != '\0'; p++)
396 			{
397 				/* count bytes with the high bit set */
398 				sectionsize++;
399 				if (bitset(0200, *p))
400 					sectionhighbits++;
401 			}
402 
403 			/*
404 			**  Heuristic: if 1/4 of the first 4K bytes are 8-bit,
405 			**  assume base64.  This heuristic avoids double-reading
406 			**  large graphics or video files.
407 			*/
408 
409 			if (sectionsize >= 4096 &&
410 			    sectionhighbits > sectionsize / 4)
411 				break;
412 		}
413 
414 		/* return to the original offset for processing */
415 		/* XXX use relative seeks to handle >31 bit file sizes? */
416 		if (fseek(e->e_dfp, offset, SEEK_SET) < 0)
417 			syserr("mime8to7: cannot fseek on df%s", e->e_id);
418 		else
419 			clearerr(e->e_dfp);
420 	}
421 
422 	/*
423 	**  Heuristically determine encoding method.
424 	**	If more than 1/8 of the total characters have the
425 	**	eighth bit set, use base64; else use quoted-printable.
426 	**	However, only encode binary encoded data as base64,
427 	**	since otherwise the NL=>CRLF mapping will be a problem.
428 	*/
429 
430 	if (tTd(43, 8))
431 	{
432 		printf("mime8to7: %ld high bit(s) in %ld byte(s), cte=%s, type=%s/%s\n",
433 			(long) sectionhighbits, (long) sectionsize,
434 			cte == NULL ? "[none]" : cte,
435 			type == NULL ? "[none]" : type,
436 			subtype == NULL ? "[none]" : subtype);
437 	}
438 	if (cte != NULL && strcasecmp(cte, "binary") == 0)
439 		sectionsize = sectionhighbits;
440 	linelen = 0;
441 	bp = buf;
442 	if (sectionhighbits == 0)
443 	{
444 		/* no encoding necessary */
445 		if (cte != NULL &&
446 		    bitset(MCIF_INMIME, mci->mci_flags) &&
447 		    !bitset(M87F_NO8TO7, flags))
448 		{
449 			/*
450 			**  Skip _unless_ in MIME mode and potentially
451 			**  converting from 8 bit to 7 bit MIME.  See
452 			**  putheader() for the counterpart where the
453 			**  CTE header is skipped in the opposite
454 			**  situation.
455 			*/
456 
457 			snprintf(buf, sizeof buf,
458 				"Content-Transfer-Encoding: %.200s", cte);
459 			putline(buf, mci);
460 			if (tTd(43, 36))
461 				printf("  ...%s\n", buf);
462 		}
463 		putline("", mci);
464 		mci->mci_flags &= ~MCIF_INHEADER;
465 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
466 		{
467 			bt = mimeboundary(buf, boundaries);
468 			if (bt != MBT_NOTSEP)
469 				break;
470 			putline(buf, mci);
471 		}
472 		if (feof(e->e_dfp))
473 			bt = MBT_FINAL;
474 	}
475 	else if (!MapNLtoCRLF ||
476 		 (sectionsize / 8 < sectionhighbits && !use_qp))
477 	{
478 		/* use base64 encoding */
479 		int c1, c2;
480 
481 		if (tTd(43, 36))
482 			printf("  ...Content-Transfer-Encoding: base64\n");
483 		putline("Content-Transfer-Encoding: base64", mci);
484 		snprintf(buf, sizeof buf,
485 			"X-MIME-Autoconverted: from 8bit to base64 by %s id %s",
486 			MyHostName, e->e_id);
487 		putline(buf, mci);
488 		putline("", mci);
489 		mci->mci_flags &= ~MCIF_INHEADER;
490 		while ((c1 = mime_getchar_crlf(e->e_dfp, boundaries, &bt)) != EOF)
491 		{
492 			if (linelen > 71)
493 			{
494 				*bp = '\0';
495 				putline(buf, mci);
496 				linelen = 0;
497 				bp = buf;
498 			}
499 			linelen += 4;
500 			*bp++ = Base64Code[(c1 >> 2)];
501 			c1 = (c1 & 0x03) << 4;
502 			c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
503 			if (c2 == EOF)
504 			{
505 				*bp++ = Base64Code[c1];
506 				*bp++ = '=';
507 				*bp++ = '=';
508 				break;
509 			}
510 			c1 |= (c2 >> 4) & 0x0f;
511 			*bp++ = Base64Code[c1];
512 			c1 = (c2 & 0x0f) << 2;
513 			c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
514 			if (c2 == EOF)
515 			{
516 				*bp++ = Base64Code[c1];
517 				*bp++ = '=';
518 				break;
519 			}
520 			c1 |= (c2 >> 6) & 0x03;
521 			*bp++ = Base64Code[c1];
522 			*bp++ = Base64Code[c2 & 0x3f];
523 		}
524 		*bp = '\0';
525 		putline(buf, mci);
526 	}
527 	else
528 	{
529 		/* use quoted-printable encoding */
530 		int c1, c2;
531 		int fromstate;
532 		BITMAP badchars;
533 
534 		/* set up map of characters that must be mapped */
535 		clrbitmap(badchars);
536 		for (c1 = 0x00; c1 < 0x20; c1++)
537 			setbitn(c1, badchars);
538 		clrbitn('\t', badchars);
539 		for (c1 = 0x7f; c1 < 0x100; c1++)
540 			setbitn(c1, badchars);
541 		setbitn('=', badchars);
542 		if (bitnset(M_EBCDIC, mci->mci_mailer->m_flags))
543 			for (p = "!\"#$@[\\]^`{|}~"; *p != '\0'; p++)
544 				setbitn(*p, badchars);
545 
546 		if (tTd(43, 36))
547 			printf("  ...Content-Transfer-Encoding: quoted-printable\n");
548 		putline("Content-Transfer-Encoding: quoted-printable", mci);
549 		snprintf(buf, sizeof buf,
550 			"X-MIME-Autoconverted: from 8bit to quoted-printable by %s id %s",
551 			MyHostName, e->e_id);
552 		putline(buf, mci);
553 		putline("", mci);
554 		mci->mci_flags &= ~MCIF_INHEADER;
555 		fromstate = 0;
556 		c2 = '\n';
557 		while ((c1 = mime_getchar(e->e_dfp, boundaries, &bt)) != EOF)
558 		{
559 			if (c1 == '\n')
560 			{
561 				if (c2 == ' ' || c2 == '\t')
562 				{
563 					*bp++ = '=';
564 					*bp++ = Base16Code[(c2 >> 4) & 0x0f];
565 					*bp++ = Base16Code[c2 & 0x0f];
566 				}
567 				if (buf[0] == '.' && bp == &buf[1])
568 				{
569 					buf[0] = '=';
570 					*bp++ = Base16Code[('.' >> 4) & 0x0f];
571 					*bp++ = Base16Code['.' & 0x0f];
572 				}
573 				*bp = '\0';
574 				putline(buf, mci);
575 				linelen = fromstate = 0;
576 				bp = buf;
577 				c2 = c1;
578 				continue;
579 			}
580 			if (c2 == ' ' && linelen == 4 && fromstate == 4 &&
581 			    bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
582 			{
583 				*bp++ = '=';
584 				*bp++ = '2';
585 				*bp++ = '0';
586 				linelen += 3;
587 			}
588 			else if (c2 == ' ' || c2 == '\t')
589 			{
590 				*bp++ = c2;
591 				linelen++;
592 			}
593 			if (linelen > 72 &&
594 			    (linelen > 75 || c1 != '.' ||
595 			     (linelen > 73 && c2 == '.')))
596 			{
597 				if (linelen > 73 && c2 == '.')
598 					bp--;
599 				else
600 					c2 = '\n';
601 				*bp++ = '=';
602 				*bp = '\0';
603 				putline(buf, mci);
604 				linelen = fromstate = 0;
605 				bp = buf;
606 				if (c2 == '.')
607 				{
608 					*bp++ = '.';
609 					linelen++;
610 				}
611 			}
612 			if (bitnset(c1 & 0xff, badchars))
613 			{
614 				*bp++ = '=';
615 				*bp++ = Base16Code[(c1 >> 4) & 0x0f];
616 				*bp++ = Base16Code[c1 & 0x0f];
617 				linelen += 3;
618 			}
619 			else if (c1 != ' ' && c1 != '\t')
620 			{
621 				if (linelen < 4 && c1 == "From"[linelen])
622 					fromstate++;
623 				*bp++ = c1;
624 				linelen++;
625 			}
626 			c2 = c1;
627 		}
628 
629 		/* output any saved character */
630 		if (c2 == ' ' || c2 == '\t')
631 		{
632 			*bp++ = '=';
633 			*bp++ = Base16Code[(c2 >> 4) & 0x0f];
634 			*bp++ = Base16Code[c2 & 0x0f];
635 			linelen += 3;
636 		}
637 
638 		if (linelen > 0 || boundaries[0] != NULL)
639 		{
640 			*bp = '\0';
641 			putline(buf, mci);
642 		}
643 
644 	}
645 	if (tTd(43, 3))
646 		printf("\t\t\tmime8to7=>%s (basic)\n", MimeBoundaryNames[bt]);
647 	return bt;
648 }
649 /*
650 **  MIME_GETCHAR -- get a character for MIME processing
651 **
652 **	Treats boundaries as EOF.
653 **
654 **	Parameters:
655 **		fp -- the input file.
656 **		boundaries -- the current MIME boundaries.
657 **		btp -- if the return value is EOF, *btp is set to
658 **			the type of the boundary.
659 **
660 **	Returns:
661 **		The next character in the input stream.
662 */
663 
664 int
665 mime_getchar(fp, boundaries, btp)
666 	register FILE *fp;
667 	char **boundaries;
668 	int *btp;
669 {
670 	int c;
671 	static u_char *bp = NULL;
672 	static int buflen = 0;
673 	static bool atbol = TRUE;	/* at beginning of line */
674 	static int bt = MBT_SYNTAX;	/* boundary type of next EOF */
675 	static u_char buf[128];		/* need not be a full line */
676 	int start = 0;			/* indicates position of - in buffer */
677 
678 	if (buflen == 1 && *bp == '\n')
679 	{
680 		/* last \n in buffer may be part of next MIME boundary */
681 		c = *bp;
682 	}
683 	else if (buflen > 0)
684 	{
685 		buflen--;
686 		return *bp++;
687 	}
688 	else
689 		c = getc(fp);
690 	bp = buf;
691 	buflen = 0;
692 	if (c == '\n')
693 	{
694 		/* might be part of a MIME boundary */
695 		*bp++ = c;
696 		atbol = TRUE;
697 		c = getc(fp);
698 		if (c == '\n')
699 		{
700 			ungetc(c, fp);
701 			return c;
702 		}
703 		start = 1;
704 	}
705 	if (c != EOF)
706 		*bp++ = c;
707 	else
708 		bt = MBT_FINAL;
709 	if (atbol && c == '-')
710 	{
711 		/* check for a message boundary */
712 		c = getc(fp);
713 		if (c != '-')
714 		{
715 			if (c != EOF)
716 				*bp++ = c;
717 			else
718 				bt = MBT_FINAL;
719 			buflen = bp - buf - 1;
720 			bp = buf;
721 			return *bp++;
722 		}
723 
724 		/* got "--", now check for rest of separator */
725 		*bp++ = '-';
726 		while (bp < &buf[sizeof buf - 2] &&
727 		       (c = getc(fp)) != EOF && c != '\n')
728 		{
729 			*bp++ = c;
730 		}
731 		*bp = '\0';
732 		bt = mimeboundary((char *) &buf[start], boundaries);
733 		switch (bt)
734 		{
735 		  case MBT_FINAL:
736 		  case MBT_INTERMED:
737 			/* we have a message boundary */
738 			buflen = 0;
739 			*btp = bt;
740 			return EOF;
741 		}
742 
743 		atbol = c == '\n';
744 		if (c != EOF)
745 			*bp++ = c;
746 	}
747 
748 	buflen = bp - buf - 1;
749 	if (buflen < 0)
750 	{
751 		*btp = bt;
752 		return EOF;
753 	}
754 	bp = buf;
755 	return *bp++;
756 }
757 /*
758 **  MIME_GETCHAR_CRLF -- do mime_getchar, but translate NL => CRLF
759 **
760 **	Parameters:
761 **		fp -- the input file.
762 **		boundaries -- the current MIME boundaries.
763 **		btp -- if the return value is EOF, *btp is set to
764 **			the type of the boundary.
765 **
766 **	Returns:
767 **		The next character in the input stream.
768 */
769 
770 int
771 mime_getchar_crlf(fp, boundaries, btp)
772 	register FILE *fp;
773 	char **boundaries;
774 	int *btp;
775 {
776 	static bool sendlf = FALSE;
777 	int c;
778 
779 	if (sendlf)
780 	{
781 		sendlf = FALSE;
782 		return '\n';
783 	}
784 	c = mime_getchar(fp, boundaries, btp);
785 	if (c == '\n' && MapNLtoCRLF)
786 	{
787 		sendlf = TRUE;
788 		return '\r';
789 	}
790 	return c;
791 }
792 /*
793 **  MIMEBOUNDARY -- determine if this line is a MIME boundary & its type
794 **
795 **	Parameters:
796 **		line -- the input line.
797 **		boundaries -- the set of currently pending boundaries.
798 **
799 **	Returns:
800 **		MBT_NOTSEP -- if this is not a separator line
801 **		MBT_INTERMED -- if this is an intermediate separator
802 **		MBT_FINAL -- if this is a final boundary
803 **		MBT_SYNTAX -- if this is a boundary for the wrong
804 **			enclosure -- i.e., a syntax error.
805 */
806 
807 int
808 mimeboundary(line, boundaries)
809 	register char *line;
810 	char **boundaries;
811 {
812 	int type = MBT_NOTSEP;
813 	int i;
814 	int savec;
815 	extern int isboundary __P((char *, char **));
816 
817 	if (line[0] != '-' || line[1] != '-' || boundaries == NULL)
818 		return MBT_NOTSEP;
819 	i = strlen(line);
820 	if (line[i - 1] == '\n')
821 		i--;
822 
823 	/* strip off trailing whitespace */
824 	while (line[i - 1] == ' ' || line[i - 1] == '\t')
825 		i--;
826 	savec = line[i];
827 	line[i] = '\0';
828 
829 	if (tTd(43, 5))
830 		printf("mimeboundary: line=\"%s\"... ", line);
831 
832 	/* check for this as an intermediate boundary */
833 	if (isboundary(&line[2], boundaries) >= 0)
834 		type = MBT_INTERMED;
835 	else if (i > 2 && strncmp(&line[i - 2], "--", 2) == 0)
836 	{
837 		/* check for a final boundary */
838 		line[i - 2] = '\0';
839 		if (isboundary(&line[2], boundaries) >= 0)
840 			type = MBT_FINAL;
841 		line[i - 2] = '-';
842 	}
843 
844 	line[i] = savec;
845 	if (tTd(43, 5))
846 		printf("%s\n", MimeBoundaryNames[type]);
847 	return type;
848 }
849 /*
850 **  DEFCHARSET -- return default character set for message
851 **
852 **	The first choice for character set is for the mailer
853 **	corresponding to the envelope sender.  If neither that
854 **	nor the global configuration file has a default character
855 **	set defined, return "unknown-8bit" as recommended by
856 **	RFC 1428 section 3.
857 **
858 **	Parameters:
859 **		e -- the envelope for this message.
860 **
861 **	Returns:
862 **		The default character set for that mailer.
863 */
864 
865 char *
866 defcharset(e)
867 	register ENVELOPE *e;
868 {
869 	if (e != NULL && e->e_from.q_mailer != NULL &&
870 	    e->e_from.q_mailer->m_defcharset != NULL)
871 		return e->e_from.q_mailer->m_defcharset;
872 	if (DefaultCharSet != NULL)
873 		return DefaultCharSet;
874 	return "unknown-8bit";
875 }
876 /*
877 **  ISBOUNDARY -- is a given string a currently valid boundary?
878 **
879 **	Parameters:
880 **		line -- the current input line.
881 **		boundaries -- the list of valid boundaries.
882 **
883 **	Returns:
884 **		The index number in boundaries if the line is found.
885 **		-1 -- otherwise.
886 **
887 */
888 
889 int
890 isboundary(line, boundaries)
891 	char *line;
892 	char **boundaries;
893 {
894 	register int i;
895 
896 	for (i = 0; boundaries[i] != NULL; i++)
897 	{
898 		if (strcmp(line, boundaries[i]) == 0)
899 			return i;
900 	}
901 	return -1;
902 }
903 
904 #endif /* MIME8TO7 */
905 
906 #if MIME7TO8
907 
908 /*
909 **  MIME7TO8 -- output 7 bit encoded MIME body in 8 bit format
910 **
911 **  This is a hack. Supports translating the two 7-bit body-encodings
912 **  (quoted-printable and base64) to 8-bit coded bodies.
913 **
914 **  There is not much point in supporting multipart here, as the UA
915 **  will be able to deal with encoded MIME bodies if it can parse MIME
916 **  multipart messages.
917 **
918 **  Note also that we wont be called unless it is a text/plain MIME
919 **  message, encoded base64 or QP and mailer flag '9' has been defined
920 **  on mailer.
921 **
922 **  Contributed by Marius Olaffson <marius@rhi.hi.is>.
923 **
924 **	Parameters:
925 **		mci -- mailer connection information.
926 **		header -- the header for this body part.
927 **		e -- envelope.
928 **
929 **	Returns:
930 **		none.
931 */
932 
933 extern int	mime_fromqp __P((u_char *, u_char **, int, int));
934 
935 static char index_64[128] =
936 {
937 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
938 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
939 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
940 	52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
941 	-1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
942 	15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
943 	-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
944 	41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
945 };
946 
947 #define CHAR64(c)  (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
948 
949 
950 void
951 mime7to8(mci, header, e)
952 	register MCI *mci;
953 	HDR *header;
954 	register ENVELOPE *e;
955 {
956 	register char *p;
957 	char *cte;
958 	char **pvp;
959 	u_char *fbufp;
960 	char buf[MAXLINE];
961 	u_char fbuf[MAXLINE + 1];
962 	char pvpbuf[MAXLINE];
963 	extern u_char MimeTokenTab[256];
964 
965 	p = hvalue("Content-Transfer-Encoding", header);
966 	if (p == NULL ||
967 	    (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
968 			   MimeTokenTab)) == NULL ||
969 	    pvp[0] == NULL)
970 	{
971 		/* "can't happen" -- upper level should have caught this */
972 		syserr("mime7to8: unparsable CTE %s", p == NULL ? "<NULL>" : p);
973 
974 		/* avoid bounce loops */
975 		e->e_flags |= EF_DONT_MIME;
976 
977 		/* cheap failsafe algorithm -- should work on text/plain */
978 		if (p != NULL)
979 		{
980 			snprintf(buf, sizeof buf,
981 				"Content-Transfer-Encoding: %s", p);
982 			putline(buf, mci);
983 		}
984 		putline("", mci);
985 		mci->mci_flags &= ~MCIF_INHEADER;
986 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
987 			putline(buf, mci);
988 		return;
989 	}
990 	cataddr(pvp, NULL, buf, sizeof buf, '\0');
991 	cte = newstr(buf);
992 
993 	mci->mci_flags |= MCIF_INHEADER;
994 	putline("Content-Transfer-Encoding: 8bit", mci);
995 	snprintf(buf, sizeof buf,
996 		"X-MIME-Autoconverted: from %.200s to 8bit by %s id %s",
997 		cte, MyHostName, e->e_id);
998 	putline(buf, mci);
999 	putline("", mci);
1000 	mci->mci_flags &= ~MCIF_INHEADER;
1001 
1002 	/*
1003 	**  Translate body encoding to 8-bit.  Supports two types of
1004 	**  encodings; "base64" and "quoted-printable". Assume qp if
1005 	**  it is not base64.
1006 	*/
1007 
1008 	if (strcasecmp(cte, "base64") == 0)
1009 	{
1010 		int c1, c2, c3, c4;
1011 
1012 		fbufp = fbuf;
1013 		while ((c1 = fgetc(e->e_dfp)) != EOF)
1014 		{
1015 			if (isascii(c1) && isspace(c1))
1016 				continue;
1017 
1018 			do
1019 			{
1020 				c2 = fgetc(e->e_dfp);
1021 			} while (isascii(c2) && isspace(c2));
1022 			if (c2 == EOF)
1023 				break;
1024 
1025 			do
1026 			{
1027 				c3 = fgetc(e->e_dfp);
1028 			} while (isascii(c3) && isspace(c3));
1029 			if (c3 == EOF)
1030 				break;
1031 
1032 			do
1033 			{
1034 				c4 = fgetc(e->e_dfp);
1035 			} while (isascii(c4) && isspace(c4));
1036 			if (c4 == EOF)
1037 				break;
1038 
1039 			if (c1 == '=' || c2 == '=')
1040 				continue;
1041 			c1 = CHAR64(c1);
1042 			c2 = CHAR64(c2);
1043 
1044 			*fbufp = (c1 << 2) | ((c2 & 0x30) >> 4);
1045 			if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
1046 			{
1047 				if (*--fbufp != '\n' ||
1048 				    (fbufp > fbuf && *--fbufp != '\r'))
1049 					fbufp++;
1050 				putxline((char *) fbuf, fbufp - fbuf,
1051 					 mci, PXLF_MAPFROM);
1052 				fbufp = fbuf;
1053 			}
1054 			if (c3 == '=')
1055 				continue;
1056 			c3 = CHAR64(c3);
1057 			*fbufp = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2);
1058 			if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
1059 			{
1060 				if (*--fbufp != '\n' ||
1061 				    (fbufp > fbuf && *--fbufp != '\r'))
1062 					fbufp++;
1063 				putxline((char *) fbuf, fbufp - fbuf,
1064 					 mci, PXLF_MAPFROM);
1065 				fbufp = fbuf;
1066 			}
1067 			if (c4 == '=')
1068 				continue;
1069 			c4 = CHAR64(c4);
1070 			*fbufp = ((c3 & 0x03) << 6) | c4;
1071 			if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
1072 			{
1073 				if (*--fbufp != '\n' ||
1074 				    (fbufp > fbuf && *--fbufp != '\r'))
1075 					fbufp++;
1076 				putxline((char *) fbuf, fbufp - fbuf,
1077 					 mci, PXLF_MAPFROM);
1078 				fbufp = fbuf;
1079 			}
1080 		}
1081 	}
1082 	else
1083 	{
1084 		/* quoted-printable */
1085 		fbufp = fbuf;
1086 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
1087 		{
1088 			if (mime_fromqp((u_char *) buf, &fbufp, 0,
1089 					&fbuf[MAXLINE] - fbufp) == 0)
1090 				continue;
1091 
1092 			if (fbufp - fbuf > 0)
1093 				putxline((char *) fbuf, fbufp - fbuf - 1, mci,
1094 					 PXLF_MAPFROM);
1095 			fbufp = fbuf;
1096 		}
1097 	}
1098 
1099 	/* force out partial last line */
1100 	if (fbufp > fbuf)
1101 	{
1102 		*fbufp = '\0';
1103 		putxline((char *) fbuf, fbufp - fbuf, mci, PXLF_MAPFROM);
1104 	}
1105 	if (tTd(43, 3))
1106 		printf("\t\t\tmime7to8 => %s to 8bit done\n", cte);
1107 }
1108 /*
1109 **  The following is based on Borenstein's "codes.c" module, with simplifying
1110 **  changes as we do not deal with multipart, and to do the translation in-core,
1111 **  with an attempt to prevent overrun of output buffers.
1112 **
1113 **  What is needed here are changes to defned this code better against
1114 **  bad encodings. Questionable to always return 0xFF for bad mappings.
1115 */
1116 
1117 static char index_hex[128] =
1118 {
1119 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1120 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1121 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1122 	0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
1123 	-1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1124 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1125 	-1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1126 	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
1127 };
1128 
1129 #define HEXCHAR(c)  (((c) < 0 || (c) > 127) ? -1 : index_hex[(c)])
1130 
1131 int
1132 mime_fromqp(infile, outfile, state, maxlen)
1133 	u_char *infile;
1134 	u_char **outfile;
1135 	int state;		/* Decoding body (0) or header (1) */
1136 	int maxlen;		/* Max # of chars allowed in outfile */
1137 {
1138 	int c1, c2;
1139 	int nchar = 0;
1140 
1141 	while ((c1 = *infile++) != '\0')
1142 	{
1143 		if (c1 == '=')
1144 		{
1145 			if ((c1 = *infile++) == 0)
1146 				break;
1147 
1148 			if (c1 == '\n' || (c1 = HEXCHAR(c1)) == -1)
1149 			{
1150 				/* ignore it */
1151 				if (state == 0)
1152 					return 0;
1153 			}
1154 			else
1155 			{
1156 				do
1157 				{
1158 					if ((c2 = *infile++) == '\0')
1159 					{
1160 						c2 = -1;
1161 						break;
1162 					}
1163 				} while ((c2 = HEXCHAR(c2)) == -1);
1164 
1165 				if (c2 == -1 || ++nchar > maxlen)
1166 					break;
1167 
1168 				*(*outfile)++ = c1 << 4 | c2;
1169 			}
1170 		}
1171 		else
1172 		{
1173 			if (state == 1 && c1 == '_')
1174 				c1 = ' ';
1175 
1176 			if (++nchar > maxlen)
1177 				break;
1178 
1179 			*(*outfile)++ = c1;
1180 
1181 			if (c1 == '\n')
1182 				break;
1183 		}
1184 	}
1185 	*(*outfile)++ = '\0';
1186 	return 1;
1187 }
1188 
1189 
1190 #endif /* MIME7TO8 */
1191