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