xref: /csrg-svn/usr.sbin/sendmail/src/mime.c (revision 68862)
167545Seric /*
267545Seric  * Copyright (c) 1994 Eric P. Allman
367545Seric  * Copyright (c) 1994
467545Seric  *	The Regents of the University of California.  All rights reserved.
567545Seric  *
667545Seric  * %sccs.include.redist.c%
767545Seric  */
867545Seric 
967545Seric # include "sendmail.h"
1067545Seric # include <string.h>
1167545Seric 
1267545Seric #ifndef lint
13*68862Seric static char sccsid[] = "@(#)mime.c	8.17 (Berkeley) 04/23/95";
1467545Seric #endif /* not lint */
1567545Seric 
1667545Seric /*
1767545Seric **  MIME support.
1867545Seric **
1967545Seric **	I am indebted to John Beck of Hewlett-Packard, who contributed
2067545Seric **	his code to me for inclusion.  As it turns out, I did not use
2167545Seric **	his code since he used a "minimum change" approach that used
2267545Seric **	several temp files, and I wanted a "minimum impact" approach
2367545Seric **	that would avoid copying.  However, looking over his code
2467545Seric **	helped me cement my understanding of the problem.
2567545Seric **
2667545Seric **	I also looked at, but did not directly use, Nathaniel
2767545Seric **	Borenstein's "code.c" module.  Again, it functioned as
2867545Seric **	a file-to-file translator, which did not fit within my
2967545Seric **	design bounds, but it was a useful base for understanding
3067545Seric **	the problem.
3167545Seric */
3267545Seric 
3367545Seric 
3467545Seric /* character set for hex and base64 encoding */
3567545Seric char	Base16Code[] =	"0123456789ABCDEF";
3667545Seric char	Base64Code[] =	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3767545Seric 
3867545Seric /* types of MIME boundaries */
3967545Seric #define MBT_SYNTAX	0	/* syntax error */
4067545Seric #define MBT_NOTSEP	1	/* not a boundary */
4167545Seric #define MBT_INTERMED	2	/* intermediate boundary (no trailing --) */
4267545Seric #define MBT_FINAL	3	/* final boundary (trailing -- included) */
4367547Seric 
4468717Seric static char	*MimeBoundaryNames[] =
4568717Seric {
4668717Seric 	"SYNTAX",	"NOTSEP",	"INTERMED",	"FINAL"
4768717Seric };
4867545Seric /*
4967545Seric **  MIME8TO7 -- output 8 bit body in 7 bit format
5067545Seric **
5167545Seric **	The header has already been output -- this has to do the
5267545Seric **	8 to 7 bit conversion.  It would be easy if we didn't have
5367545Seric **	to deal with nested formats (multipart/xxx and message/rfc822).
5467545Seric **
5567545Seric **	We won't be called if we don't have to do a conversion, and
5667545Seric **	appropriate MIME-Version: and Content-Type: fields have been
5767545Seric **	output.  Any Content-Transfer-Encoding: field has not been
5867545Seric **	output, and we can add it here.
5967545Seric **
6067545Seric **	Parameters:
6167545Seric **		mci -- mailer connection information.
6267545Seric **		header -- the header for this body part.
6367545Seric **		e -- envelope.
6468517Seric **		boundaries -- the currently pending message boundaries.
6568517Seric **			NULL if we are processing the outer portion.
6668517Seric **		flags -- to tweak processing.
6767545Seric **
6867545Seric **	Returns:
6967545Seric **		An indicator of what terminated the message part:
7067545Seric **		  MBT_FINAL -- the final boundary
7167545Seric **		  MBT_INTERMED -- an intermediate boundary
7267545Seric **		  MBT_NOTSEP -- an end of file
7367545Seric */
7467545Seric 
7568517Seric struct args
7668517Seric {
7768517Seric 	char	*field;		/* name of field */
7868517Seric 	char	*value;		/* value of that field */
7968517Seric };
8068517Seric 
8167545Seric int
8268517Seric mime8to7(mci, header, e, boundaries, flags)
8367545Seric 	register MCI *mci;
8468717Seric 	HDR *header;
8568717Seric 	register ENVELOPE *e;
8668517Seric 	char **boundaries;
8768517Seric 	int flags;
8867545Seric {
8967545Seric 	register char *p;
9067545Seric 	int linelen;
9167545Seric 	int bt;
9267545Seric 	off_t offset;
9367545Seric 	size_t sectionsize, sectionhighbits;
9468517Seric 	int i;
9568517Seric 	char *type;
9668517Seric 	char *subtype;
9768847Seric 	char *cte;
9868517Seric 	char **pvp;
9968517Seric 	int argc = 0;
10068517Seric 	struct args argv[MAXMIMEARGS];
10167545Seric 	char bbuf[128];
10267545Seric 	char buf[MAXLINE];
10368517Seric 	char pvpbuf[MAXLINE];
10468711Seric 	extern char MimeTokenTab[256];
10567545Seric 
10667545Seric 	if (tTd(43, 1))
10767545Seric 	{
10868717Seric 		printf("mime8to7: flags = %x, boundaries =", flags);
10968717Seric 		if (boundaries[0] == NULL)
11068717Seric 			printf(" <none>");
11168717Seric 		else
11268717Seric 		{
11368717Seric 			for (i = 0; boundaries[i] != NULL; i++)
11468717Seric 				printf(" %s", boundaries[i]);
11568717Seric 		}
11668717Seric 		printf("\n");
11767545Seric 	}
11868847Seric 	type = subtype = NULL;
11967545Seric 	p = hvalue("Content-Type", header);
12068847Seric 	if (p == NULL)
12168847Seric 	{
12268847Seric 		if (bitset(M87F_DIGEST, flags))
12368847Seric 			p = "message/rfc822";
12468847Seric 		else
12568847Seric 			p = "text/plain";
12668847Seric 	}
12768517Seric 	if (p != NULL &&
12868711Seric 	    (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
12968711Seric 			   MimeTokenTab)) != NULL &&
13068517Seric 	    pvp[0] != NULL)
13167545Seric 	{
13268717Seric 		if (tTd(43, 40))
13368717Seric 		{
13468717Seric 			for (i = 0; pvp[i] != NULL; i++)
13568717Seric 				printf("pvp[%d] = \"%s\"\n", i, pvp[i]);
13668717Seric 		}
13768517Seric 		type = *pvp++;
13868517Seric 		if (*pvp != NULL && strcmp(*pvp, "/") == 0 &&
13968517Seric 		    *++pvp != NULL)
14068517Seric 		{
14168517Seric 			subtype = *pvp++;
14268517Seric 		}
14368517Seric 
14468517Seric 		/* break out parameters */
14568517Seric 		while (*pvp != NULL && argc < MAXMIMEARGS)
14668517Seric 		{
14768517Seric 			/* skip to semicolon separator */
14868517Seric 			while (*pvp != NULL && strcmp(*pvp, ";") != 0)
14968517Seric 				pvp++;
15068517Seric 			if (*pvp++ == NULL || *pvp == NULL)
15168517Seric 				break;
15268517Seric 
15368517Seric 			/* extract field name */
15468517Seric 			argv[argc].field = *pvp++;
15568517Seric 
15668517Seric 			/* see if there is a value */
15768517Seric 			if (*pvp != NULL && strcmp(*pvp, "=") == 0 &&
15868517Seric 			    (*++pvp == NULL || strcmp(*pvp, ";") != 0))
15968517Seric 			{
16068517Seric 				argv[argc].value = *pvp;
16168517Seric 				argc++;
16268517Seric 			}
16368517Seric 		}
16468517Seric 	}
16568717Seric 
16668847Seric 	/* check for disaster cases */
16768847Seric 	if (type == NULL)
16868847Seric 		type = "-none-";
16968847Seric 	if (subtype == NULL)
17068847Seric 		subtype = "-none-";
17168847Seric 
17268847Seric 	/*
17368847Seric 	**  Check for cases that can not be encoded.
17468847Seric 	**
17568847Seric 	**	For example, you can't encode certain kinds of types
17668847Seric 	**	or already-encoded messages.  If we find this case,
17768847Seric 	**	just copy it through.
17868847Seric 	*/
17968847Seric 
18068847Seric 	cte = hvalue("content-transfer-encoding", header);
18168717Seric 	sprintf(buf, "%s/%s", type, subtype);
18268847Seric 	if (wordinclass(buf, 'n') || (cte != NULL && !wordinclass(cte, 'e')))
18368717Seric 		flags |= M87F_NO8BIT;
18468717Seric 
18568717Seric 	/*
18668717Seric 	**  Multipart requires special processing.
18768717Seric 	**
18868717Seric 	**	Do a recursive descent into the message.
18968717Seric 	*/
19068717Seric 
19168517Seric 	if (strcasecmp(type, "multipart") == 0)
19268517Seric 	{
19367545Seric 		register char *q;
19467545Seric 
19568847Seric 		if (strcasecmp(subtype, "digest") == 0)
19668847Seric 			flags |= M87F_DIGEST;
19768847Seric 
19868517Seric 		for (i = 0; i < argc; i++)
19967545Seric 		{
20068517Seric 			if (strcasecmp(argv[i].field, "boundary") == 0)
20168517Seric 				break;
20268517Seric 		}
20368517Seric 		if (i >= argc)
20468517Seric 		{
20567545Seric 			syserr("mime8to7: Content-Type: %s missing boundary", p);
20667545Seric 			p = "---";
20767545Seric 		}
20867545Seric 		else
20968517Seric 			p = argv[i].value;
21067545Seric 		if (*p == '"')
21168711Seric 			q = strchr(++p, '"');
21267545Seric 		else
21367545Seric 			q = p + strlen(p);
21467545Seric 		if (q - p > sizeof bbuf - 1)
21567545Seric 		{
21667545Seric 			syserr("mime8to7: multipart boundary \"%.*s\" too long",
21767545Seric 				q - p, p);
21867545Seric 			q = p + sizeof bbuf - 1;
21967545Seric 		}
22067545Seric 		strncpy(bbuf, p, q - p);
22167545Seric 		bbuf[q - p] = '\0';
22267545Seric 		if (tTd(43, 1))
22367545Seric 			printf("mime8to7: multipart boundary \"%s\"\n", bbuf);
22468517Seric 		for (i = 0; i < MAXMIMENESTING; i++)
22568517Seric 			if (boundaries[i] == NULL)
22668517Seric 				break;
22768517Seric 		if (i >= MAXMIMENESTING)
22868517Seric 			syserr("mime8to7: multipart nesting boundary too deep");
22968517Seric 		else
23068517Seric 		{
23168517Seric 			boundaries[i] = bbuf;
23268517Seric 			boundaries[i + 1] = NULL;
23368517Seric 		}
23467545Seric 
23567545Seric 		/* skip the early "comment" prologue */
23668717Seric 		putline("", mci);
23767545Seric 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
23867545Seric 		{
23968517Seric 			bt = mimeboundary(buf, boundaries);
24067545Seric 			if (bt != MBT_NOTSEP)
24167545Seric 				break;
24268847Seric 			putxline(buf, mci, PXLF_MAPFROM|PXLF_STRIP8BIT);
24368717Seric 			if (tTd(43, 99))
24468717Seric 				printf("  ...%s", buf);
24567545Seric 		}
24668717Seric 		if (feof(e->e_dfp))
24768717Seric 			bt = MBT_FINAL;
24867545Seric 		while (bt != MBT_FINAL)
24967545Seric 		{
25067545Seric 			auto HDR *hdr = NULL;
25167545Seric 
25267545Seric 			sprintf(buf, "--%s", bbuf);
25367545Seric 			putline(buf, mci);
25468717Seric 			if (tTd(43, 35))
25568717Seric 				printf("  ...%s\n", buf);
25667545Seric 			collect(e->e_dfp, FALSE, FALSE, &hdr, e);
25768717Seric 			if (tTd(43, 101))
25868717Seric 				putline("+++after collect", mci);
25967936Seric 			putheader(mci, hdr, e, 0);
26068717Seric 			if (tTd(43, 101))
26168717Seric 				putline("+++after putheader", mci);
26268517Seric 			bt = mime8to7(mci, hdr, e, boundaries, flags);
26367545Seric 		}
26467545Seric 		sprintf(buf, "--%s--", bbuf);
26567545Seric 		putline(buf, mci);
26668717Seric 		if (tTd(43, 35))
26768717Seric 			printf("  ...%s\n", buf);
26868717Seric 		boundaries[i] = NULL;
26967545Seric 
27067545Seric 		/* skip the late "comment" epilogue */
27167545Seric 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
27267545Seric 		{
27368517Seric 			bt = mimeboundary(buf, boundaries);
27467545Seric 			if (bt != MBT_NOTSEP)
27567545Seric 				break;
27668847Seric 			putxline(buf, mci, PXLF_MAPFROM|PXLF_STRIP8BIT);
27768717Seric 			if (tTd(43, 99))
27868717Seric 				printf("  ...%s", buf);
27967545Seric 		}
28068717Seric 		if (feof(e->e_dfp))
28168717Seric 			bt = MBT_FINAL;
28268717Seric 		if (tTd(43, 3))
28368717Seric 			printf("\t\t\tmime8to7=>%s (multipart)\n",
28468717Seric 				MimeBoundaryNames[bt]);
28567545Seric 		return bt;
28667545Seric 	}
28767545Seric 
28867545Seric 	/*
28968847Seric 	**  Message/* types -- recurse exactly once.
29068847Seric 	*/
29168847Seric 
29268847Seric 	if (strcasecmp(type, "message") == 0)
29368847Seric 	{
29468847Seric 		register char *q;
29568847Seric 		auto HDR *hdr = NULL;
29668847Seric 
29768847Seric 		putline("", mci);
29868847Seric 
29968847Seric 		collect(e->e_dfp, FALSE, FALSE, &hdr, e);
30068847Seric 		if (tTd(43, 101))
30168847Seric 			putline("+++after collect", mci);
30268847Seric 		putheader(mci, hdr, e, 0);
30368847Seric 		if (tTd(43, 101))
30468847Seric 			putline("+++after putheader", mci);
30568847Seric 		bt = mime8to7(mci, hdr, e, boundaries, flags);
30668847Seric 		return bt;
30768847Seric 	}
30868847Seric 
30968847Seric 	/*
31067545Seric 	**  Non-compound body type
31167545Seric 	**
31267545Seric 	**	Compute the ratio of seven to eight bit characters;
31367545Seric 	**	use that as a heuristic to decide how to do the
31467545Seric 	**	encoding.
31567545Seric 	*/
31667545Seric 
31767545Seric 	sectionsize = sectionhighbits = 0;
31868517Seric 	if (!bitset(M87F_NO8BIT, flags))
31967545Seric 	{
32068515Seric 		/* remember where we were */
32168515Seric 		offset = ftell(e->e_dfp);
32268515Seric 		if (offset == -1)
32368564Seric 			syserr("mime8to7: cannot ftell on df%s", e->e_id);
32468515Seric 
32568515Seric 		/* do a scan of this body type to count character types */
32668515Seric 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
32767545Seric 		{
32868717Seric 			if (mimeboundary(buf, boundaries) != MBT_NOTSEP)
32968515Seric 				break;
33068515Seric 			for (p = buf; *p != '\0'; p++)
33168515Seric 			{
33268515Seric 				/* count bytes with the high bit set */
33368515Seric 				sectionsize++;
33468515Seric 				if (bitset(0200, *p))
33568515Seric 					sectionhighbits++;
33668515Seric 			}
33768515Seric 
33868515Seric 			/*
33968515Seric 			**  Heuristic: if 1/4 of the first 4K bytes are 8-bit,
34068515Seric 			**  assume base64.  This heuristic avoids double-reading
34168515Seric 			**  large graphics or video files.
34268515Seric 			*/
34368515Seric 
34468515Seric 			if (sectionsize >= 4096 &&
34568515Seric 			    sectionhighbits > sectionsize / 4)
34668515Seric 				break;
34767545Seric 		}
34867547Seric 
34968515Seric 		/* return to the original offset for processing */
35068515Seric 		/* XXX use relative seeks to handle >31 bit file sizes? */
35168515Seric 		if (fseek(e->e_dfp, offset, SEEK_SET) < 0)
35268564Seric 			syserr("mime8to7: cannot fseek on df%s", e->e_id);
35368717Seric 		else
35468717Seric 			clearerr(e->e_dfp);
35567545Seric 	}
35667545Seric 
35767547Seric 	/*
35867547Seric 	**  Heuristically determine encoding method.
35967547Seric 	**	If more than 1/8 of the total characters have the
36067547Seric 	**	eighth bit set, use base64; else use quoted-printable.
36168860Seric 	**	However, only encode binary encoded data as base64,
36268860Seric 	**	since otherwise the NL=>CRLF mapping will be a problem.
36367547Seric 	*/
36467547Seric 
36567545Seric 	if (tTd(43, 8))
36667545Seric 	{
36768860Seric 		printf("mime8to7: %ld high bit(s) in %ld byte(s), cte=%s\n",
36868860Seric 			sectionhighbits, sectionsize,
36968860Seric 			cte == NULL ? "[none]" : cte);
37067545Seric 	}
37168860Seric 	if (cte != NULL && strcasecmp(cte, "binary") == 0)
37268860Seric 		sectionsize = sectionhighbits;
37368717Seric 	linelen = 0;
37467554Seric 	if (sectionhighbits == 0)
37567545Seric 	{
37667554Seric 		/* no encoding necessary */
37768847Seric 		if (cte != NULL)
37867695Seric 		{
37968847Seric 			sprintf(buf, "Content-Transfer-Encoding: %s", cte);
38067695Seric 			putline(buf, mci);
38168717Seric 			if (tTd(43, 36))
38268717Seric 				printf("  ...%s\n", buf);
38367695Seric 		}
38467554Seric 		putline("", mci);
38567554Seric 		mci->mci_flags &= ~MCIF_INHEADER;
38667554Seric 		while (fgets(buf, sizeof buf, e->e_dfp) != NULL)
38767554Seric 		{
38868517Seric 			bt = mimeboundary(buf, boundaries);
38967554Seric 			if (bt != MBT_NOTSEP)
39067554Seric 				break;
39167554Seric 			putline(buf, mci);
39267554Seric 		}
39368717Seric 		if (feof(e->e_dfp))
39468717Seric 			bt = MBT_FINAL;
39567554Seric 	}
39667554Seric 	else if (sectionsize / 8 < sectionhighbits)
39767554Seric 	{
39867545Seric 		/* use base64 encoding */
39967545Seric 		int c1, c2;
40068847Seric 		int (*getcharf) __P((FILE *, char **, int *));
40168847Seric 		extern int mime_getchar __P((FILE *, char **, int *));
40268847Seric 		extern int mime_getchar_crlf __P((FILE *, char **, int *));
40367545Seric 
40468860Seric 		if (cte != NULL && strcasecmp(cte, "binary") == 0)
40568860Seric 			getcharf = mime_getchar;
40668860Seric 		else
40768847Seric 			getcharf = mime_getchar_crlf;
40867545Seric 		putline("Content-Transfer-Encoding: base64", mci);
40968717Seric 		if (tTd(43, 36))
41068717Seric 			printf("  ...Content-Transfer-Encoding: base64\n");
41167545Seric 		putline("", mci);
41267545Seric 		mci->mci_flags &= ~MCIF_INHEADER;
41368847Seric 		while ((c1 = (*getcharf)(e->e_dfp, boundaries, &bt)) != EOF)
41467545Seric 		{
41567545Seric 			if (linelen > 71)
41667545Seric 			{
41767545Seric 				fputs(mci->mci_mailer->m_eol, mci->mci_out);
41867545Seric 				linelen = 0;
41967545Seric 			}
42067545Seric 			linelen += 4;
42168847Seric 			fputc(Base64Code[(c1 >> 2) & 0x3f], mci->mci_out);
42267545Seric 			c1 = (c1 & 0x03) << 4;
42368847Seric 			c2 = (*getcharf)(e->e_dfp, boundaries, &bt);
42467545Seric 			if (c2 == EOF)
42567545Seric 			{
42668847Seric 				fputc(Base64Code[c1 & 0x3f], mci->mci_out);
42767545Seric 				fputc('=', mci->mci_out);
42867545Seric 				fputc('=', mci->mci_out);
42967545Seric 				break;
43067545Seric 			}
43167545Seric 			c1 |= (c2 >> 4) & 0x0f;
43268847Seric 			fputc(Base64Code[c1 & 0x3f], mci->mci_out);
43367545Seric 			c1 = (c2 & 0x0f) << 2;
43468847Seric 			c2 = (*getcharf)(e->e_dfp, boundaries, &bt);
43567545Seric 			if (c2 == EOF)
43667545Seric 			{
43768847Seric 				fputc(Base64Code[c1 & 0x3f], mci->mci_out);
43867545Seric 				fputc('=', mci->mci_out);
43967545Seric 				break;
44067545Seric 			}
44167545Seric 			c1 |= (c2 >> 6) & 0x03;
44268847Seric 			fputc(Base64Code[c1 & 0x3f], mci->mci_out);
44367545Seric 			fputc(Base64Code[c2 & 0x3f], mci->mci_out);
44467545Seric 		}
44567545Seric 	}
44667545Seric 	else
44767545Seric 	{
44867545Seric 		/* use quoted-printable encoding */
44967545Seric 		int c1, c2;
45068515Seric 		int fromstate;
451*68862Seric 		BITMAP badchars;
45267545Seric 
453*68862Seric 		/* set up map of characters that must be mapped */
454*68862Seric 		clrbitmap(badchars);
455*68862Seric 		for (c1 = 0x00; c1 < 0x20; c1++)
456*68862Seric 			setbitn(c1, badchars);
457*68862Seric 		clrbitn('\t', badchars);
458*68862Seric 		for (c1 = 0x7f; c1 < 0x100; c1++)
459*68862Seric 			setbitn(c1, badchars);
460*68862Seric 		setbitn('=', badchars);
461*68862Seric 		if (bitnset(M_EBCDIC, mci->mci_mailer->m_flags))
462*68862Seric 			for (p = "!\"#$@[\\]^`{|}~"; *p != '\0'; p++)
463*68862Seric 				setbitn(*p, badchars);
464*68862Seric 
46567545Seric 		putline("Content-Transfer-Encoding: quoted-printable", mci);
46668717Seric 		if (tTd(43, 36))
46768717Seric 			printf("  ...Content-Transfer-Encoding: quoted-printable\n");
46867545Seric 		putline("", mci);
46967545Seric 		mci->mci_flags &= ~MCIF_INHEADER;
47068717Seric 		fromstate = 0;
47167554Seric 		c2 = '\n';
47268717Seric 		while ((c1 = mime_getchar(e->e_dfp, boundaries, &bt)) != EOF)
47367545Seric 		{
47467545Seric 			if (c1 == '\n')
47567545Seric 			{
47667545Seric 				if (c2 == ' ' || c2 == '\t')
47767545Seric 				{
47867545Seric 					fputc('=', mci->mci_out);
47967840Seric 					fputc(Base16Code[(c2 >> 4) & 0x0f],
48067840Seric 								mci->mci_out);
48167840Seric 					fputc(Base16Code[c2 & 0x0f],
48267840Seric 								mci->mci_out);
48367840Seric 					fputs(mci->mci_mailer->m_eol,
48467840Seric 								mci->mci_out);
48567545Seric 				}
48667545Seric 				fputs(mci->mci_mailer->m_eol, mci->mci_out);
48768515Seric 				linelen = fromstate = 0;
48867545Seric 				c2 = c1;
48967545Seric 				continue;
49067545Seric 			}
49168515Seric 			if (c2 == ' ' && linelen == 4 && fromstate == 4 &&
49268515Seric 			    bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
49367840Seric 			{
49468515Seric 				fputs("=20", mci->mci_out);
49568515Seric 				linelen += 3;
49668515Seric 			}
49768515Seric 			else if (c2 == ' ' || c2 == '\t')
49868515Seric 			{
49967840Seric 				fputc(c2, mci->mci_out);
50067840Seric 				linelen++;
50167840Seric 			}
50267545Seric 			if (linelen > 72)
50367545Seric 			{
50467545Seric 				fputc('=', mci->mci_out);
50567545Seric 				fputs(mci->mci_mailer->m_eol, mci->mci_out);
50668515Seric 				linelen = fromstate = 0;
50767554Seric 				c2 = '\n';
50867545Seric 			}
50967761Seric 			if (c2 == '\n' && c1 == '.' &&
51067761Seric 				 bitnset(M_XDOT, mci->mci_mailer->m_flags))
51167761Seric 			{
51267761Seric 				fputc('.', mci->mci_out);
51367761Seric 				linelen++;
51467761Seric 			}
515*68862Seric 			if (bitnset(c1 & 0xff, badchars))
51667545Seric 			{
51767545Seric 				fputc('=', mci->mci_out);
51867545Seric 				fputc(Base16Code[(c1 >> 4) & 0x0f], mci->mci_out);
51967545Seric 				fputc(Base16Code[c1 & 0x0f], mci->mci_out);
52067545Seric 				linelen += 3;
52167545Seric 			}
52267840Seric 			else if (c1 != ' ' && c1 != '\t')
52367545Seric 			{
52468515Seric 				if (linelen < 4 && c1 == "From"[linelen])
52568515Seric 					fromstate++;
52667545Seric 				fputc(c1, mci->mci_out);
52767545Seric 				linelen++;
52867545Seric 			}
52967545Seric 			c2 = c1;
53067545Seric 		}
53167840Seric 
53267840Seric 		/* output any saved character */
53367840Seric 		if (c2 == ' ' || c2 == '\t')
53467840Seric 		{
53568515Seric 			fputc('=', mci->mci_out);
53668515Seric 			fputc(Base16Code[(c2 >> 4) & 0x0f], mci->mci_out);
53768515Seric 			fputc(Base16Code[c2 & 0x0f], mci->mci_out);
53868515Seric 			linelen += 3;
53967840Seric 		}
54067545Seric 	}
54167545Seric 	if (linelen > 0)
54267545Seric 		fputs(mci->mci_mailer->m_eol, mci->mci_out);
54368717Seric 	if (tTd(43, 3))
54468717Seric 		printf("\t\t\tmime8to7=>%s (basic)\n", MimeBoundaryNames[bt]);
54568717Seric 	return bt;
54667545Seric }
54768515Seric /*
54868515Seric **  MIME_GETCHAR -- get a character for MIME processing
54968515Seric **
55068515Seric **	Treats boundaries as EOF.
55168515Seric **
55268515Seric **	Parameters:
55368515Seric **		fp -- the input file.
55468517Seric **		boundaries -- the current MIME boundaries.
55568717Seric **		btp -- if the return value is EOF, *btp is set to
55668717Seric **			the type of the boundary.
55768515Seric **
55868515Seric **	Returns:
55968515Seric **		The next character in the input stream.
56068515Seric */
56167545Seric 
56267545Seric int
56368717Seric mime_getchar(fp, boundaries, btp)
56467545Seric 	register FILE *fp;
56568517Seric 	char **boundaries;
56668717Seric 	int *btp;
56767545Seric {
56867545Seric 	int c;
56967545Seric 	static char *bp = NULL;
57067545Seric 	static int buflen = 0;
57167545Seric 	static bool atbol = TRUE;	/* at beginning of line */
57268717Seric 	static int bt = MBT_SYNTAX;	/* boundary type of next EOF */
57367545Seric 	static char buf[128];		/* need not be a full line */
57467545Seric 
57567545Seric 	if (buflen > 0)
57667545Seric 	{
57767545Seric 		buflen--;
57867545Seric 		return *bp++;
57967545Seric 	}
58068515Seric 	bp = buf;
58168515Seric 	buflen = 0;
58267545Seric 	c = fgetc(fp);
58368515Seric 	if (c == '\n')
58468515Seric 	{
58568515Seric 		/* might be part of a MIME boundary */
58668515Seric 		*bp++ = c;
58768515Seric 		atbol = TRUE;
58868515Seric 		c = fgetc(fp);
58968515Seric 	}
59068515Seric 	if (c != EOF)
59168515Seric 		*bp++ = c;
59268717Seric 	else
59368717Seric 		bt = MBT_FINAL;
59468517Seric 	if (atbol && c == '-')
59567545Seric 	{
59667545Seric 		/* check for a message boundary */
59767545Seric 		c = fgetc(fp);
59867545Seric 		if (c != '-')
59967545Seric 		{
60067545Seric 			if (c != EOF)
60168515Seric 				*bp++ = c;
60268717Seric 			else
60368717Seric 				bt = MBT_FINAL;
60468515Seric 			buflen = bp - buf - 1;
60568515Seric 			bp = buf;
60668515Seric 			return *bp++;
60767545Seric 		}
60867545Seric 
60967545Seric 		/* got "--", now check for rest of separator */
61067545Seric 		*bp++ = '-';
61168847Seric 		while (bp < &buf[sizeof buf - 2] &&
61267545Seric 		       (c = fgetc(fp)) != EOF && c != '\n')
61367545Seric 		{
61467545Seric 			*bp++ = c;
61567545Seric 		}
61667545Seric 		*bp = '\0';
61768717Seric 		bt = mimeboundary(&buf[1], boundaries);
61868717Seric 		switch (bt)
61967545Seric 		{
62067545Seric 		  case MBT_FINAL:
62167545Seric 		  case MBT_INTERMED:
62267545Seric 			/* we have a message boundary */
62367545Seric 			buflen = 0;
62468717Seric 			*btp = bt;
62567545Seric 			return EOF;
62667545Seric 		}
62767545Seric 
62867545Seric 		atbol = c == '\n';
62967545Seric 		if (c != EOF)
63067545Seric 			*bp++ = c;
63167545Seric 	}
63267545Seric 
63368515Seric 	buflen = bp - buf - 1;
63468515Seric 	if (buflen < 0)
63568717Seric 	{
63668717Seric 		*btp = bt;
63768515Seric 		return EOF;
63868717Seric 	}
63968515Seric 	bp = buf;
64068515Seric 	return *bp++;
64167545Seric }
64267545Seric /*
64368847Seric **  MIME_GETCHAR_CRLF -- do mime_getchar, but translate NL => CRLF
64468847Seric **
64568847Seric **	Parameters:
64668847Seric **		fp -- the input file.
64768847Seric **		boundaries -- the current MIME boundaries.
64868847Seric **		btp -- if the return value is EOF, *btp is set to
64968847Seric **			the type of the boundary.
65068847Seric **
65168847Seric **	Returns:
65268847Seric **		The next character in the input stream.
65368847Seric */
65468847Seric 
65568847Seric int
65668847Seric mime_getchar_crlf(fp, boundaries, btp)
65768847Seric 	register FILE *fp;
65868847Seric 	char **boundaries;
65968847Seric 	int *btp;
66068847Seric {
66168847Seric 	static bool sendlf = FALSE;
66268847Seric 	int c;
66368847Seric 
66468847Seric 	if (sendlf)
66568847Seric 	{
66668847Seric 		sendlf = FALSE;
66768847Seric 		return '\n';
66868847Seric 	}
66968847Seric 	c = mime_getchar(fp, boundaries, btp);
67068847Seric 	if (c == '\n')
67168847Seric 	{
67268847Seric 		sendlf = TRUE;
67368847Seric 		return '\r';
67468847Seric 	}
67568847Seric 	return c;
67668847Seric }
67768847Seric /*
67867545Seric **  MIMEBOUNDARY -- determine if this line is a MIME boundary & its type
67967545Seric **
68067545Seric **	Parameters:
68167545Seric **		line -- the input line.
68268517Seric **		boundaries -- the set of currently pending boundaries.
68367545Seric **
68467545Seric **	Returns:
68567545Seric **		MBT_NOTSEP -- if this is not a separator line
68667545Seric **		MBT_INTERMED -- if this is an intermediate separator
68767545Seric **		MBT_FINAL -- if this is a final boundary
68867545Seric **		MBT_SYNTAX -- if this is a boundary for the wrong
68967545Seric **			enclosure -- i.e., a syntax error.
69067545Seric */
69167545Seric 
69267545Seric int
69368517Seric mimeboundary(line, boundaries)
69467545Seric 	register char *line;
69568517Seric 	char **boundaries;
69667545Seric {
69767545Seric 	int type;
69867545Seric 	int i;
69968517Seric 	int savec;
70067545Seric 
70168517Seric 	if (line[0] != '-' || line[1] != '-' || boundaries == NULL)
70267545Seric 		return MBT_NOTSEP;
70367545Seric 	i = strlen(line);
70467545Seric 	if (line[i - 1] == '\n')
70567545Seric 		i--;
70668717Seric 	if (tTd(43, 5))
70768717Seric 		printf("mimeboundary: line=\"%.*s\"... ", i, line);
70868515Seric 	while (line[i - 1] == ' ' || line[i - 1] == '\t')
70968515Seric 		i--;
71067545Seric 	if (i > 2 && strncmp(&line[i - 2], "--", 2) == 0)
71167545Seric 	{
71267545Seric 		type = MBT_FINAL;
71367545Seric 		i -= 2;
71467545Seric 	}
71567545Seric 	else
71667545Seric 		type = MBT_INTERMED;
71767545Seric 
71868517Seric 	savec = line[i];
71968517Seric 	line[i] = '\0';
72067545Seric 	/* XXX should check for improper nesting here */
72168517Seric 	if (isboundary(&line[2], boundaries) < 0)
72267545Seric 		type = MBT_NOTSEP;
72368517Seric 	line[i] = savec;
72467545Seric 	if (tTd(43, 5))
72568717Seric 		printf("%s\n", MimeBoundaryNames[type]);
72667545Seric 	return type;
72767545Seric }
72867896Seric /*
72967896Seric **  DEFCHARSET -- return default character set for message
73067896Seric **
73167896Seric **	The first choice for character set is for the mailer
73267896Seric **	corresponding to the envelope sender.  If neither that
73367896Seric **	nor the global configuration file has a default character
73467896Seric **	set defined, return "unknown-8bit" as recommended by
73567896Seric **	RFC 1428 section 3.
73667896Seric **
73767896Seric **	Parameters:
73867896Seric **		e -- the envelope for this message.
73967896Seric **
74067896Seric **	Returns:
74167896Seric **		The default character set for that mailer.
74267896Seric */
74367896Seric 
74467896Seric char *
74567896Seric defcharset(e)
74667896Seric 	register ENVELOPE *e;
74767896Seric {
74867896Seric 	if (e != NULL && e->e_from.q_mailer != NULL &&
74967896Seric 	    e->e_from.q_mailer->m_defcharset != NULL)
75067896Seric 		return e->e_from.q_mailer->m_defcharset;
75167896Seric 	if (DefaultCharSet != NULL)
75267896Seric 		return DefaultCharSet;
75367896Seric 	return "unknown-8bit";
75467896Seric }
75568517Seric /*
75668517Seric **  ISBOUNDARY -- is a given string a currently valid boundary?
75768517Seric **
75868517Seric **	Parameters:
75968517Seric **		line -- the current input line.
76068517Seric **		boundaries -- the list of valid boundaries.
76168517Seric **
76268517Seric **	Returns:
76368517Seric **		The index number in boundaries if the line is found.
76468517Seric **		-1 -- otherwise.
76568517Seric **
76668517Seric */
76768517Seric 
76868517Seric int
76968517Seric isboundary(line, boundaries)
77068517Seric 	char *line;
77168517Seric 	char **boundaries;
77268517Seric {
77368517Seric 	register int i;
77468517Seric 
77568711Seric 	for (i = 0; boundaries[i] != NULL; i++)
77668517Seric 	{
77768517Seric 		if (strcmp(line, boundaries[i]) == 0)
77868517Seric 			return i;
77968517Seric 	}
78068517Seric 	return -1;
78168517Seric }
782