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