xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/mime_state.c (revision 87d689fb734c654d2486f87f7be32f1b53ecdbec)
1 /*	$NetBSD: mime_state.c,v 1.2 2017/02/14 01:16:45 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	mime_state 3
6 /* SUMMARY
7 /*	MIME parser state machine
8 /* SYNOPSIS
9 /*	#include <mime_state.h>
10 /*
11 /*	MIME_STATE *mime_state_alloc(flags, head_out, head_end,
12 /*					 body_out, body_end,
13 /*					 err_print, context)
14 /*	int	flags;
15 /*	void	(*head_out)(void *ptr, int header_class,
16 /*				const HEADER_OPTS *header_info,
17 /*				VSTRING *buf, off_t offset);
18 /*	void	(*head_end)(void *ptr);
19 /*	void	(*body_out)(void *ptr, int rec_type,
20 /*				const char *buf, ssize_t len,
21 /*				off_t offset);
22 /*	void	(*body_end)(void *ptr);
23 /*	void	(*err_print)(void *ptr, int err_flag, const char *text)
24 /*	void	*context;
25 /*
26 /*	int	mime_state_update(state, rec_type, buf, len)
27 /*	MIME_STATE *state;
28 /*	int	rec_type;
29 /*	const char *buf;
30 /*	ssize_t	len;
31 /*
32 /*	MIME_STATE *mime_state_free(state)
33 /*	MIME_STATE *state;
34 /*
35 /*	const char *mime_state_error(error_code)
36 /*	int	error_code;
37 /*
38 /*	typedef struct {
39 /* .in +4
40 /*		const int code;		/* internal error code */
41 /*		const char *dsn;	/* RFC 3463 */
42 /*		const char *text;	/* descriptive text */
43 /* .in -4
44 /*	} MIME_STATE_DETAIL;
45 /*
46 /*	const MIME_STATE_DETAIL *mime_state_detail(error_code)
47 /*	int	error_code;
48 /* DESCRIPTION
49 /*	This module implements a one-pass MIME processor with optional
50 /*	8-bit to quoted-printable conversion.
51 /*
52 /*	In order to fend off denial of service attacks, message headers
53 /*	are truncated at or above var_header_limit bytes, message boundary
54 /*	strings are truncated at var_mime_bound_len bytes, and the multipart
55 /*	nesting level is limited to var_mime_maxdepth levels.
56 /*
57 /*	mime_state_alloc() creates a MIME state machine. The machine
58 /*	is delivered in its initial state, expecting content type
59 /*	text/plain, 7-bit data.
60 /*
61 /*	mime_state_update() updates the MIME state machine according
62 /*	to the input record type and the record content.
63 /*	The result value is the bit-wise OR of zero or more of the following:
64 /* .IP MIME_ERR_TRUNC_HEADER
65 /*	A message header was longer than var_header_limit bytes.
66 /* .IP MIME_ERR_NESTING
67 /*	The MIME structure was nested more than var_mime_maxdepth levels.
68 /* .IP MIME_ERR_8BIT_IN_HEADER
69 /*	A message header contains 8-bit data. This is always illegal.
70 /* .IP MIME_ERR_8BIT_IN_7BIT_BODY
71 /*	A MIME header specifies (or defaults to) 7-bit content, but the
72 /*	corresponding message body or body parts contain 8-bit content.
73 /* .IP MIME_ERR_ENCODING_DOMAIN
74 /*	An entity of type "message" or "multipart" specifies the wrong
75 /*	content transfer encoding domain, or specifies a transformation
76 /*	(quoted-printable, base64) instead of a domain (7bit, 8bit,
77 /*	or binary).
78 /* .PP
79 /*	mime_state_free() releases storage for a MIME state machine,
80 /*	and conveniently returns a null pointer.
81 /*
82 /*	mime_state_error() returns a string representation for the
83 /*	specified error code. When multiple errors are specified it
84 /*	reports what it deems the most serious one.
85 /*
86 /*	mime_state_detail() returns a table entry with error
87 /*	information for the specified error code. When multiple
88 /*	errors are specified it reports what it deems the most
89 /*	serious one.
90 /*
91 /*	Arguments:
92 /* .IP body_out
93 /*	The output routine for body lines. It receives unmodified input
94 /*	records, or the result of 8-bit -> 7-bit conversion.
95 /* .IP body_end
96 /*	A null pointer, or a pointer to a routine that is called after
97 /*	the last input record is processed.
98 /* .IP buf
99 /*	Buffer with the content of a logical or physical message record.
100 /* .IP context
101 /*	Caller context that is passed on to the head_out and body_out
102 /*	routines.
103 /* .IP enc_type
104 /*	The content encoding: MIME_ENC_7BIT or MIME_ENC_8BIT.
105 /* .IP err_print
106 /*	Null pointer, or pointer to a function that is called with
107 /*	arguments: the application context, the error type, and the
108 /*	offending input. Only one instance per error type is reported.
109 /* .IP flags
110 /*	Special processing options. Specify the bit-wise OR of zero or
111 /*	more of the following:
112 /* .RS
113 /* .IP MIME_OPT_DISABLE_MIME
114 /*	Pay no attention to Content-* message headers, and switch to
115 /*	message body state at the end of the primary message headers.
116 /* .IP MIME_OPT_REPORT_TRUNC_HEADER
117 /*	Report errors that set the MIME_ERR_TRUNC_HEADER error flag
118 /*	(see above).
119 /* .IP MIME_OPT_REPORT_8BIT_IN_HEADER
120 /*	Report errors that set the MIME_ERR_8BIT_IN_HEADER error
121 /*	flag (see above). This rarely stops legitimate mail.
122 /* .IP MIME_OPT_REPORT_8BIT_IN_7BIT_BODY
123 /*	Report errors that set the MIME_ERR_8BIT_IN_7BIT_BODY error
124 /*	flag (see above). This currently breaks Majordomo mail that is
125 /*	forwarded for approval, because Majordomo does not propagate
126 /*	MIME type information from the enclosed message to the message
127 /*	headers of the request for approval.
128 /* .IP MIME_OPT_REPORT_ENCODING_DOMAIN
129 /*	Report errors that set the MIME_ERR_ENCODING_DOMAIN error
130 /*	flag (see above).
131 /* .IP MIME_OPT_REPORT_NESTING
132 /*	Report errors that set the MIME_ERR_NESTING error flag
133 /*	(see above).
134 /* .IP MIME_OPT_DOWNGRADE
135 /*	Transform content that claims to be 8-bit into quoted-printable.
136 /*	Where appropriate, update Content-Transfer-Encoding: message
137 /*	headers.
138 /* .RE
139 /* .sp
140 /*	For convenience, MIME_OPT_NONE requests no special processing.
141 /* .IP header_class
142 /*	Specifies where a message header is located.
143 /* .RS
144 /* .IP MIME_HDR_PRIMARY
145 /*	In the primary message header section.
146 /* .IP MIME_HDR_MULTIPART
147 /*	In the header section after a multipart boundary string.
148 /* .IP MIME_HDR_NESTED
149 /*	At the start of a nested (e.g., message/rfc822) message.
150 /* .RE
151 /* .sp
152 /*	For convenience, the macros MIME_HDR_FIRST and MIME_HDR_LAST
153 /*	specify the range of MIME_HDR_MUMBLE macros.
154 /* .sp
155 /*	To find out if something is a MIME header at the beginning
156 /*	of an RFC 822 message or an attached message, look at the
157 /*	header_info argument.
158 /* .IP header_info
159 /*	Null pointer or information about the message header, see
160 /*	header_opts(3).
161 /* .IP head_out
162 /*	The output routine that is invoked for outputting a message header.
163 /*	A multi-line header is passed as one chunk of text with embedded
164 /*	newlines.
165 /*	It is the responsibility of the output routine to break the text
166 /*	at embedded newlines, and to break up long text between newlines
167 /*	into multiple output records.
168 /*	Note: an output routine is explicitly allowed to modify the text.
169 /* .IP head_end
170 /*	A null pointer, or a pointer to a routine that is called after
171 /*	the last message header in the first header block is processed.
172 /* .IP len
173 /*	Length of non-VSTRING input buffer.
174 /* .IP offset
175 /*	The offset in bytes from the start of the current block of message
176 /*	headers or body lines. Line boundaries are counted as one byte.
177 /* .IP rec_type
178 /*	The input record type as defined in rec_type(3h). State is
179 /*	updated for text records (REC_TYPE_NORM or REC_TYPE_CONT).
180 /*	Some input records are stored internally in order to reconstruct
181 /*	multi-line input.  Upon receipt of any non-text record type, all
182 /*	stored input is flushed and the state is set to "body".
183 /* .IP state
184 /*	MIME parser state created with mime_state_alloc().
185 /* BUGS
186 /*	NOTE: when the end of headers is reached, mime_state_update()
187 /*	may execute up to three call-backs before returning to the
188 /*	caller: head_out(), head_end(), and body_out() or body_end().
189 /*	As long as call-backs return no result, it is up to the
190 /*	call-back routines to check if a previous call-back experienced
191 /*	an error.
192 /*
193 /*	Different mail user agents treat malformed message boundary
194 /*	strings in different ways. The Postfix MIME processor cannot
195 /*	be bug-compatible with everything.
196 /*
197 /*	This module will not glue together multipart boundary strings that
198 /*	span multiple input records.
199 /*
200 /*	This module will not glue together RFC 2231 formatted (boundary)
201 /*	parameter values. RFC 2231 claims compatibility with existing
202 /*	MIME processors. Splitting boundary strings is not backwards
203 /*	compatible.
204 /*
205 /*	The "8-bit data inside 7-bit body" test is myopic. It is not aware
206 /*	of any enclosing (message or multipart) encoding information.
207 /*
208 /*	If the input ends in data other than a hard line break, this module
209 /*	will add a hard line break of its own. No line break is added to
210 /*	empty input.
211 /*
212 /*	This code recognizes the obsolete form "headername :" but will
213 /*	normalize it to the canonical form "headername:". Leaving the
214 /*	obsolete form alone would cause too much trouble with existing code
215 /*	that expects only the normalized form.
216 /* SEE ALSO
217 /*	msg(3) diagnostics interface
218 /*	header_opts(3) header information lookup
219 /*	RFC 822 (ARPA Internet Text Messages)
220 /*	RFC 2045 (MIME: Format of internet message bodies)
221 /*	RFC 2046 (MIME: Media types)
222 /* DIAGNOSTICS
223 /*	Fatal errors: memory allocation problem.
224 /* LICENSE
225 /* .ad
226 /* .fi
227 /*	The Secure Mailer license must be distributed with this software.
228 /* HISTORY
229 /* .ad
230 /* .fi
231 /*	This code was implemented from scratch after reading the RFC
232 /*	documents. This was a relatively straightforward effort with
233 /*	few if any surprises. Victor Duchovni of Morgan Stanley shared
234 /*	his experiences with ambiguities in real-life MIME implementations.
235 /*	Liviu Daia of the Romanian Academy shared his insights in some
236 /*	of the darker corners.
237 /* AUTHOR(S)
238 /*	Wietse Venema
239 /*	IBM T.J. Watson Research
240 /*	P.O. Box 704
241 /*	Yorktown Heights, NY 10598, USA
242 /*--*/
243 
244 /* System library. */
245 
246 #include <sys_defs.h>
247 #include <stdarg.h>
248 #include <ctype.h>
249 #include <string.h>
250 
251 #ifdef STRCASECMP_IN_STRINGS_H
252 #include <strings.h>
253 #endif
254 
255 /* Utility library. */
256 
257 #include <mymalloc.h>
258 #include <msg.h>
259 #include <vstring.h>
260 
261 /* Global library. */
262 
263 #include <rec_type.h>
264 #include <is_header.h>
265 #include <header_opts.h>
266 #include <mail_params.h>
267 #include <header_token.h>
268 #include <lex_822.h>
269 #include <mime_state.h>
270 
271 /* Application-specific. */
272 
273  /*
274   * Mime parser stack element for multipart content.
275   */
276 typedef struct MIME_STACK {
277     int     def_ctype;			/* default content type */
278     int     def_stype;			/* default content subtype */
279     char   *boundary;			/* boundary string */
280     ssize_t bound_len;			/* boundary length */
281     struct MIME_STACK *next;		/* linkage */
282 } MIME_STACK;
283 
284  /*
285   * Mime parser state.
286   */
287 #define MIME_MAX_TOKEN		3	/* tokens per attribute */
288 
289 struct MIME_STATE {
290 
291     /*
292      * Volatile members.
293      */
294     int     curr_state;			/* header/body state */
295     int     curr_ctype;			/* last or default content type */
296     int     curr_stype;			/* last or default content subtype */
297     int     curr_encoding;		/* last or default content encoding */
298     int     curr_domain;		/* last or default encoding unit */
299     VSTRING *output_buffer;		/* headers, quoted-printable body */
300     int     prev_rec_type;		/* previous input record type */
301     int     nesting_level;		/* safety */
302     MIME_STACK *stack;			/* for composite types */
303     HEADER_TOKEN token[MIME_MAX_TOKEN];	/* header token array */
304     VSTRING *token_buffer;		/* header parser scratch buffer */
305     int     err_flags;			/* processing errors */
306     off_t   head_offset;		/* offset in header block */
307     off_t   body_offset;		/* offset in body block */
308 
309     /*
310      * Static members.
311      */
312     int     static_flags;		/* static processing options */
313     MIME_STATE_HEAD_OUT head_out;	/* header output routine */
314     MIME_STATE_ANY_END head_end;	/* end of primary header routine */
315     MIME_STATE_BODY_OUT body_out;	/* body output routine */
316     MIME_STATE_ANY_END body_end;	/* end of body output routine */
317     MIME_STATE_ERR_PRINT err_print;	/* error report */
318     void   *app_context;		/* application context */
319 };
320 
321  /*
322   * Content types and subtypes that we care about, either because we have to,
323   * or because we want to filter out broken MIME messages.
324   */
325 #define MIME_CTYPE_OTHER	0
326 #define MIME_CTYPE_TEXT		1
327 #define MIME_CTYPE_MESSAGE	2
328 #define MIME_CTYPE_MULTIPART	3
329 
330 #define MIME_STYPE_OTHER	0
331 #define MIME_STYPE_PLAIN	1
332 #define MIME_STYPE_RFC822	2
333 #define MIME_STYPE_PARTIAL	3
334 #define MIME_STYPE_EXTERN_BODY	4
335 #define MIME_STYPE_GLOBAL	5
336 
337  /*
338   * MIME parser states. We steal from the public interface.
339   */
340 #define MIME_STATE_PRIMARY	MIME_HDR_PRIMARY	/* primary headers */
341 #define MIME_STATE_MULTIPART	MIME_HDR_MULTIPART	/* after --boundary */
342 #define MIME_STATE_NESTED	MIME_HDR_NESTED	/* message/rfc822 */
343 #define MIME_STATE_BODY		(MIME_HDR_NESTED + 1)
344 
345 #define SET_MIME_STATE(ptr, state, ctype, stype, encoding, domain) do { \
346 	(ptr)->curr_state = (state); \
347 	(ptr)->curr_ctype = (ctype); \
348 	(ptr)->curr_stype = (stype); \
349 	(ptr)->curr_encoding = (encoding); \
350 	(ptr)->curr_domain = (domain); \
351 	if ((state) == MIME_STATE_BODY) \
352 	    (ptr)->body_offset = 0; \
353 	else \
354 	    (ptr)->head_offset = 0; \
355     } while (0)
356 
357 #define SET_CURR_STATE(ptr, state) do { \
358 	(ptr)->curr_state = (state); \
359 	if ((state) == MIME_STATE_BODY) \
360 	    (ptr)->body_offset = 0; \
361 	else \
362 	    (ptr)->head_offset = 0; \
363     } while (0)
364 
365  /*
366   * MIME encodings and domains. We intentionally use the same codes for
367   * encodings and domains, so that we can easily find out whether a content
368   * transfer encoding header specifies a domain or whether it specifies
369   * domain+encoding, which is illegal for multipart/any and message/any.
370   */
371 typedef struct MIME_ENCODING {
372     const char *name;			/* external representation */
373     int     encoding;			/* internal representation */
374     int     domain;			/* subset of encoding */
375 } MIME_ENCODING;
376 
377 #define MIME_ENC_QP		1	/* encoding + domain */
378 #define MIME_ENC_BASE64		2	/* encoding + domain */
379  /* These are defined in mime_state.h as part of the external interface. */
380 #ifndef MIME_ENC_7BIT
381 #define MIME_ENC_7BIT		7	/* domain only */
382 #define MIME_ENC_8BIT		8	/* domain only */
383 #define MIME_ENC_BINARY		9	/* domain only */
384 #endif
385 
386 static const MIME_ENCODING mime_encoding_map[] = {	/* RFC 2045 */
387     "7bit", MIME_ENC_7BIT, MIME_ENC_7BIT,	/* domain */
388     "8bit", MIME_ENC_8BIT, MIME_ENC_8BIT,	/* domain */
389     "binary", MIME_ENC_BINARY, MIME_ENC_BINARY,	/* domain */
390     "base64", MIME_ENC_BASE64, MIME_ENC_7BIT,	/* encoding */
391     "quoted-printable", MIME_ENC_QP, MIME_ENC_7BIT,	/* encoding */
392     0,
393 };
394 
395  /*
396   * Silly Little Macros.
397   */
398 #define STR(x)		vstring_str(x)
399 #define LEN(x)		VSTRING_LEN(x)
400 #define END(x)		vstring_end(x)
401 #define CU_CHAR_PTR(x)	((const unsigned char *) (x))
402 
403 #define REPORT_ERROR_LEN(state, err_type, text, len) do { \
404 	if ((state->err_flags & err_type) == 0) { \
405 	    if (state->err_print != 0) \
406 		state->err_print(state->app_context, err_type, text, len); \
407 	    state->err_flags |= err_type; \
408 	} \
409     } while (0)
410 
411 #define REPORT_ERROR(state, err_type, text) do { \
412 	const char *_text = text; \
413 	ssize_t _len = strlen(text); \
414 	REPORT_ERROR_LEN(state, err_type, _text, _len); \
415     } while (0)
416 
417 #define REPORT_ERROR_BUF(state, err_type, buf) \
418     REPORT_ERROR_LEN(state, err_type, STR(buf), LEN(buf))
419 
420 
421  /*
422   * Outputs and state changes are interleaved, so we must maintain separate
423   * offsets for header and body segments.
424   */
425 #define HEAD_OUT(ptr, info, len) do { \
426 	if ((ptr)->head_out) { \
427 	    (ptr)->head_out((ptr)->app_context, (ptr)->curr_state, \
428 			    (info), (ptr)->output_buffer, (ptr)->head_offset); \
429 	    (ptr)->head_offset += (len) + 1; \
430 	} \
431     } while(0)
432 
433 #define BODY_OUT(ptr, rec_type, text, len) do { \
434 	if ((ptr)->body_out) { \
435 	    (ptr)->body_out((ptr)->app_context, (rec_type), \
436 			    (text), (len), (ptr)->body_offset); \
437 	    (ptr)->body_offset += (len) + 1; \
438 	} \
439     } while(0)
440 
441 /* mime_state_push - push boundary onto stack */
442 
443 static void mime_state_push(MIME_STATE *state, int def_ctype, int def_stype,
444 			            const char *boundary)
445 {
446     MIME_STACK *stack;
447 
448     /*
449      * RFC 2046 mandates that a boundary string be up to 70 characters long.
450      * Some MTAs, including Postfix, include the fully-qualified MTA name
451      * which can be longer, so we are willing to handle boundary strings that
452      * exceed the RFC specification. We allow for message headers of up to
453      * var_header_limit characters. In order to avoid denial of service, we
454      * have to impose a configurable limit on the amount of text that we are
455      * willing to store as a boundary string. Despite this truncation way we
456      * will still correctly detect all intermediate boundaries and all the
457      * message headers that follow those boundaries.
458      */
459     state->nesting_level += 1;
460     stack = (MIME_STACK *) mymalloc(sizeof(*stack));
461     stack->def_ctype = def_ctype;
462     stack->def_stype = def_stype;
463     if ((stack->bound_len = strlen(boundary)) > var_mime_bound_len)
464 	stack->bound_len = var_mime_bound_len;
465     stack->boundary = mystrndup(boundary, stack->bound_len);
466     stack->next = state->stack;
467     state->stack = stack;
468     if (msg_verbose)
469 	msg_info("PUSH boundary %s", stack->boundary);
470 }
471 
472 /* mime_state_pop - pop boundary from stack */
473 
474 static void mime_state_pop(MIME_STATE *state)
475 {
476     MIME_STACK *stack;
477 
478     if ((stack = state->stack) == 0)
479 	msg_panic("mime_state_pop: there is no stack");
480     if (msg_verbose)
481 	msg_info("POP boundary %s", stack->boundary);
482     state->nesting_level -= 1;
483     state->stack = stack->next;
484     myfree(stack->boundary);
485     myfree((void *) stack);
486 }
487 
488 /* mime_state_alloc - create MIME state machine */
489 
490 MIME_STATE *mime_state_alloc(int flags,
491 			             MIME_STATE_HEAD_OUT head_out,
492 			             MIME_STATE_ANY_END head_end,
493 			             MIME_STATE_BODY_OUT body_out,
494 			             MIME_STATE_ANY_END body_end,
495 			             MIME_STATE_ERR_PRINT err_print,
496 			             void *context)
497 {
498     MIME_STATE *state;
499 
500     state = (MIME_STATE *) mymalloc(sizeof(*state));
501 
502     /* Volatile members. */
503     state->err_flags = 0;
504     state->body_offset = 0;			/* XXX */
505     SET_MIME_STATE(state, MIME_STATE_PRIMARY,
506 		   MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
507 		   MIME_ENC_7BIT, MIME_ENC_7BIT);
508     state->output_buffer = vstring_alloc(100);
509     state->prev_rec_type = 0;
510     state->stack = 0;
511     state->token_buffer = vstring_alloc(1);
512 
513     /* Static members. */
514     state->static_flags = flags;
515     state->head_out = head_out;
516     state->head_end = head_end;
517     state->body_out = body_out;
518     state->body_end = body_end;
519     state->err_print = err_print;
520     state->app_context = context;
521     return (state);
522 }
523 
524 /* mime_state_free - destroy MIME state machine */
525 
526 MIME_STATE *mime_state_free(MIME_STATE *state)
527 {
528     vstring_free(state->output_buffer);
529     while (state->stack)
530 	mime_state_pop(state);
531     if (state->token_buffer)
532 	vstring_free(state->token_buffer);
533     myfree((void *) state);
534     return (0);
535 }
536 
537 /* mime_state_content_type - process content-type header */
538 
539 static void mime_state_content_type(MIME_STATE *state,
540 				            const HEADER_OPTS *header_info)
541 {
542     const char *cp;
543     ssize_t tok_count;
544     int     def_ctype;
545     int     def_stype;
546 
547 #define TOKEN_MATCH(tok, text) \
548     ((tok).type == HEADER_TOK_TOKEN && strcasecmp((tok).u.value, (text)) == 0)
549 
550 #define RFC2045_TSPECIALS	"()<>@,;:\\\"/[]?="
551 
552 #define PARSE_CONTENT_TYPE_HEADER(state, ptr) \
553     header_token(state->token, MIME_MAX_TOKEN, \
554 	state->token_buffer, ptr, RFC2045_TSPECIALS, ';')
555 
556     cp = STR(state->output_buffer) + strlen(header_info->name) + 1;
557     if ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) > 0) {
558 
559 	/*
560 	 * text/whatever. Right now we don't really care if it is plain or
561 	 * not, but we may want to recognize subtypes later, and then this
562 	 * code can serve as an example.
563 	 */
564 	if (TOKEN_MATCH(state->token[0], "text")) {
565 	    state->curr_ctype = MIME_CTYPE_TEXT;
566 	    if (tok_count >= 3
567 		&& state->token[1].type == '/'
568 		&& TOKEN_MATCH(state->token[2], "plain"))
569 		state->curr_stype = MIME_STYPE_PLAIN;
570 	    else
571 		state->curr_stype = MIME_STYPE_OTHER;
572 	    return;
573 	}
574 
575 	/*
576 	 * message/whatever body parts start with another block of message
577 	 * headers that we may want to look at. The partial and external-body
578 	 * subtypes cannot be subjected to 8-bit -> 7-bit conversion, so we
579 	 * must properly recognize them.
580 	 */
581 	if (TOKEN_MATCH(state->token[0], "message")) {
582 	    state->curr_ctype = MIME_CTYPE_MESSAGE;
583 	    state->curr_stype = MIME_STYPE_OTHER;
584 	    if (tok_count >= 3
585 		&& state->token[1].type == '/') {
586 		if (TOKEN_MATCH(state->token[2], "rfc822"))
587 		    state->curr_stype = MIME_STYPE_RFC822;
588 		else if (TOKEN_MATCH(state->token[2], "partial"))
589 		    state->curr_stype = MIME_STYPE_PARTIAL;
590 		else if (TOKEN_MATCH(state->token[2], "external-body"))
591 		    state->curr_stype = MIME_STYPE_EXTERN_BODY;
592 		else if (TOKEN_MATCH(state->token[2], "global"))
593 		    state->curr_stype = MIME_STYPE_GLOBAL;
594 	    }
595 	    return;
596 	}
597 
598 	/*
599 	 * multipart/digest has default content type message/rfc822,
600 	 * multipart/whatever has default content type text/plain.
601 	 */
602 	if (TOKEN_MATCH(state->token[0], "multipart")) {
603 	    state->curr_ctype = MIME_CTYPE_MULTIPART;
604 	    if (tok_count >= 3
605 		&& state->token[1].type == '/'
606 		&& TOKEN_MATCH(state->token[2], "digest")) {
607 		def_ctype = MIME_CTYPE_MESSAGE;
608 		def_stype = MIME_STYPE_RFC822;
609 	    } else {
610 		def_ctype = MIME_CTYPE_TEXT;
611 		def_stype = MIME_STYPE_PLAIN;
612 	    }
613 
614 	    /*
615 	     * Yes, this is supposed to capture multiple boundary strings,
616 	     * which are illegal and which could be used to hide content in
617 	     * an implementation dependent manner. The code below allows us
618 	     * to find embedded message headers as long as the sender uses
619 	     * only one of these same-level boundary strings.
620 	     *
621 	     * Yes, this is supposed to ignore the boundary value type.
622 	     */
623 	    while ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) >= 0) {
624 		if (tok_count >= 3
625 		    && TOKEN_MATCH(state->token[0], "boundary")
626 		    && state->token[1].type == '=') {
627 		    if (state->nesting_level > var_mime_maxdepth) {
628 			if (state->static_flags & MIME_OPT_REPORT_NESTING)
629 			    REPORT_ERROR_BUF(state, MIME_ERR_NESTING,
630 					     state->output_buffer);
631 		    } else {
632 			mime_state_push(state, def_ctype, def_stype,
633 					state->token[2].u.value);
634 		    }
635 		}
636 	    }
637 	}
638 	return;
639     }
640 
641     /*
642      * other/whatever.
643      */
644     else {
645 	state->curr_ctype = MIME_CTYPE_OTHER;
646 	return;
647     }
648 }
649 
650 /* mime_state_content_encoding - process content-transfer-encoding header */
651 
652 static void mime_state_content_encoding(MIME_STATE *state,
653 				             const HEADER_OPTS *header_info)
654 {
655     const char *cp;
656     const MIME_ENCODING *cmp;
657 
658 #define PARSE_CONTENT_ENCODING_HEADER(state, ptr) \
659     header_token(state->token, 1, state->token_buffer, ptr, (char *) 0, 0)
660 
661     /*
662      * Do content-transfer-encoding header. Never set the encoding domain to
663      * something other than 7bit, 8bit or binary, even if we don't recognize
664      * the input.
665      */
666     cp = STR(state->output_buffer) + strlen(header_info->name) + 1;
667     if (PARSE_CONTENT_ENCODING_HEADER(state, &cp) > 0
668 	&& state->token[0].type == HEADER_TOK_TOKEN) {
669 	for (cmp = mime_encoding_map; cmp->name != 0; cmp++) {
670 	    if (strcasecmp(state->token[0].u.value, cmp->name) == 0) {
671 		state->curr_encoding = cmp->encoding;
672 		state->curr_domain = cmp->domain;
673 		break;
674 	    }
675 	}
676     }
677 }
678 
679 /* mime_state_enc_name - encoding to printable form */
680 
681 static const char *mime_state_enc_name(int encoding)
682 {
683     const MIME_ENCODING *cmp;
684 
685     for (cmp = mime_encoding_map; cmp->name != 0; cmp++)
686 	if (encoding == cmp->encoding)
687 	    return (cmp->name);
688     return ("unknown");
689 }
690 
691 /* mime_state_downgrade - convert 8-bit data to quoted-printable */
692 
693 static void mime_state_downgrade(MIME_STATE *state, int rec_type,
694 				         const char *text, ssize_t len)
695 {
696     static char hexchars[] = "0123456789ABCDEF";
697     const unsigned char *cp;
698     int     ch;
699 
700 #define QP_ENCODE(buffer, ch) { \
701 	VSTRING_ADDCH(buffer, '='); \
702 	VSTRING_ADDCH(buffer, hexchars[(ch >> 4) & 0xff]); \
703 	VSTRING_ADDCH(buffer, hexchars[ch & 0xf]); \
704     }
705 
706     /*
707      * Insert a soft line break when the output reaches a critical length
708      * before we reach a hard line break.
709      */
710     for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) {
711 	/* Critical length before hard line break. */
712 	if (LEN(state->output_buffer) > 72) {
713 	    VSTRING_ADDCH(state->output_buffer, '=');
714 	    VSTRING_TERMINATE(state->output_buffer);
715 	    BODY_OUT(state, REC_TYPE_NORM,
716 		     STR(state->output_buffer),
717 		     LEN(state->output_buffer));
718 	    VSTRING_RESET(state->output_buffer);
719 	}
720 	/* Append the next character. */
721 	ch = *cp;
722 	if ((ch < 32 && ch != '\t') || ch == '=' || ch > 126) {
723 	    QP_ENCODE(state->output_buffer, ch);
724 	} else {
725 	    VSTRING_ADDCH(state->output_buffer, ch);
726 	}
727     }
728 
729     /*
730      * Flush output after a hard line break (i.e. the end of a REC_TYPE_NORM
731      * record). Fix trailing whitespace as per the RFC: in the worst case,
732      * the output length will grow from 73 characters to 75 characters.
733      */
734     if (rec_type == REC_TYPE_NORM) {
735 	if (LEN(state->output_buffer) > 0
736 	    && ((ch = END(state->output_buffer)[-1]) == ' ' || ch == '\t')) {
737 	    vstring_truncate(state->output_buffer,
738 			     LEN(state->output_buffer) - 1);
739 	    QP_ENCODE(state->output_buffer, ch);
740 	}
741 	VSTRING_TERMINATE(state->output_buffer);
742 	BODY_OUT(state, REC_TYPE_NORM,
743 		 STR(state->output_buffer),
744 		 LEN(state->output_buffer));
745 	VSTRING_RESET(state->output_buffer);
746     }
747 }
748 
749 /* mime_state_update - update MIME state machine */
750 
751 int     mime_state_update(MIME_STATE *state, int rec_type,
752 			          const char *text, ssize_t len)
753 {
754     int     input_is_text = (rec_type == REC_TYPE_NORM
755 			     || rec_type == REC_TYPE_CONT);
756     MIME_STACK *sp;
757     const HEADER_OPTS *header_info;
758     const unsigned char *cp;
759 
760 #define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) do { \
761 	state->prev_rec_type = rec_type; \
762 	return (state->err_flags); \
763     } while (0)
764 
765     /*
766      * Be sure to flush any partial output line that might still be buffered
767      * up before taking any other "end of input" actions.
768      */
769     if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT)
770 	mime_state_update(state, REC_TYPE_NORM, "", 0);
771 
772     /*
773      * This message state machine is kept simple for the sake of robustness.
774      * Standards evolve over time, and we want to be able to correctly
775      * process messages that are not yet defined. This state machine knows
776      * about headers and bodies, understands that multipart/whatever has
777      * multiple body parts with a header and body, and that message/whatever
778      * has message headers at the start of a body part.
779      */
780     switch (state->curr_state) {
781 
782 	/*
783 	 * First, deal with header information that we have accumulated from
784 	 * previous input records. Discard text that does not fit in a header
785 	 * buffer. Our limit is quite generous; Sendmail will refuse mail
786 	 * with only 32kbyte in all the message headers combined.
787 	 */
788     case MIME_STATE_PRIMARY:
789     case MIME_STATE_MULTIPART:
790     case MIME_STATE_NESTED:
791 	if (LEN(state->output_buffer) > 0) {
792 	    if (input_is_text) {
793 		if (state->prev_rec_type == REC_TYPE_CONT) {
794 		    if (LEN(state->output_buffer) < var_header_limit) {
795 			vstring_strncat(state->output_buffer, text, len);
796 		    } else {
797 			if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
798 			    REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER,
799 					     state->output_buffer);
800 		    }
801 		    SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
802 		}
803 		if (IS_SPACE_TAB(*text)) {
804 		    if (LEN(state->output_buffer) < var_header_limit) {
805 			vstring_strcat(state->output_buffer, "\n");
806 			vstring_strncat(state->output_buffer, text, len);
807 		    } else {
808 			if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
809 			    REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER,
810 					     state->output_buffer);
811 		    }
812 		    SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
813 		}
814 	    }
815 
816 	    /*
817 	     * The input is (the beginning of) another message header, or is
818 	     * not a message header, or is not even a text record. With no
819 	     * more input to append to this saved header, do output
820 	     * processing and reset the saved header buffer. Hold on to the
821 	     * content transfer encoding header if we have to do a 8->7
822 	     * transformation, because the proper information depends on the
823 	     * content type header: message and multipart require a domain,
824 	     * leaf entities have either a transformation or a domain.
825 	     */
826 	    if (LEN(state->output_buffer) > 0) {
827 		header_info = header_opts_find(STR(state->output_buffer));
828 		if (!(state->static_flags & MIME_OPT_DISABLE_MIME)
829 		    && header_info != 0) {
830 		    if (header_info->type == HDR_CONTENT_TYPE)
831 			mime_state_content_type(state, header_info);
832 		    if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING)
833 			mime_state_content_encoding(state, header_info);
834 		}
835 		if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0
836 		    && (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) {
837 		    for (cp = CU_CHAR_PTR(STR(state->output_buffer));
838 			 cp < CU_CHAR_PTR(END(state->output_buffer)); cp++)
839 			if (*cp & 0200) {
840 			    REPORT_ERROR_BUF(state, MIME_ERR_8BIT_IN_HEADER,
841 					     state->output_buffer);
842 			    break;
843 			}
844 		}
845 		/* Output routine is explicitly allowed to change the data. */
846 		if (header_info == 0
847 		    || header_info->type != HDR_CONTENT_TRANSFER_ENCODING
848 		    || (state->static_flags & MIME_OPT_DOWNGRADE) == 0
849 		    || state->curr_domain == MIME_ENC_7BIT)
850 		    HEAD_OUT(state, header_info, len);
851 		state->prev_rec_type = 0;
852 		VSTRING_RESET(state->output_buffer);
853 	    }
854 	}
855 
856 	/*
857 	 * With past header information moved out of the way, proceed with a
858 	 * clean slate.
859 	 */
860 	if (input_is_text) {
861 	    ssize_t header_len;
862 
863 	    /*
864 	     * See if this input is (the beginning of) a message header.
865 	     *
866 	     * Normalize obsolete "name space colon" syntax to "name colon".
867 	     * Things would be too confusing otherwise.
868 	     *
869 	     * Don't assume that the input is null terminated.
870 	     */
871 	    if ((header_len = is_header_buf(text, len)) > 0) {
872 		vstring_strncpy(state->output_buffer, text, header_len);
873 		for (text += header_len, len -= header_len;
874 		     len > 0 && IS_SPACE_TAB(*text);
875 		     text++, len--)
876 		     /* void */ ;
877 		vstring_strncat(state->output_buffer, text, len);
878 		SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
879 	    }
880 	}
881 
882 	/*
883 	 * This input terminates a block of message headers. When converting
884 	 * 8-bit to 7-bit mail, this is the right place to emit the correct
885 	 * content-transfer-encoding header. With message or multipart we
886 	 * specify 7bit, with leaf entities we specify quoted-printable.
887 	 *
888 	 * We're not going to convert non-text data into base 64. If they send
889 	 * arbitrary binary data as 8-bit text, then the data is already
890 	 * broken beyond recovery, because the Postfix SMTP server sanitizes
891 	 * record boundaries, treating broken record boundaries as CRLF.
892 	 *
893 	 * Clear the output buffer, we will need it for storage of the
894 	 * conversion result.
895 	 */
896 	if ((state->static_flags & MIME_OPT_DOWNGRADE)
897 	    && state->curr_domain != MIME_ENC_7BIT) {
898 	    if ((state->curr_ctype == MIME_CTYPE_MESSAGE
899 		 && state->curr_stype != MIME_STYPE_GLOBAL)
900 		|| state->curr_ctype == MIME_CTYPE_MULTIPART)
901 		cp = CU_CHAR_PTR("7bit");
902 	    else
903 		cp = CU_CHAR_PTR("quoted-printable");
904 	    vstring_sprintf(state->output_buffer,
905 			    "Content-Transfer-Encoding: %s", cp);
906 	    HEAD_OUT(state, (HEADER_OPTS *) 0, len);
907 	    VSTRING_RESET(state->output_buffer);
908 	}
909 
910 	/*
911 	 * This input terminates a block of message headers. Call the
912 	 * optional header end routine at the end of the first header block.
913 	 */
914 	if (state->curr_state == MIME_STATE_PRIMARY && state->head_end)
915 	    state->head_end(state->app_context);
916 
917 	/*
918 	 * This is the right place to check if the sender specified an
919 	 * appropriate identity encoding (7bit, 8bit, binary) for multipart
920 	 * and for message.
921 	 */
922 	if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) {
923 	    if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
924 		if (state->curr_stype == MIME_STYPE_PARTIAL
925 		    || state->curr_stype == MIME_STYPE_EXTERN_BODY) {
926 		    if (state->curr_domain != MIME_ENC_7BIT)
927 			REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
928 				 mime_state_enc_name(state->curr_encoding));
929 		}
930 		/* EAI: message/global allows non-identity encoding. */
931 		else if (state->curr_stype == MIME_STYPE_RFC822) {
932 		    if (state->curr_encoding != state->curr_domain)
933 			REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
934 				 mime_state_enc_name(state->curr_encoding));
935 		}
936 	    } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
937 		if (state->curr_encoding != state->curr_domain)
938 		    REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
939 				 mime_state_enc_name(state->curr_encoding));
940 	    }
941 	}
942 
943 	/*
944 	 * Find out if the next body starts with its own message headers. In
945 	 * agressive mode, examine headers of partial and external-body
946 	 * messages. Otherwise, treat such headers as part of the "body". Set
947 	 * the proper encoding information for the multipart prolog.
948 	 *
949 	 * XXX We parse headers inside message/* content even when the encoding
950 	 * is invalid (encoding != domain). With base64 we won't recognize
951 	 * any headers, and with quoted-printable we won't recognize MIME
952 	 * boundary strings, but the MIME processor will still resynchronize
953 	 * when it runs into the higher-level boundary string at the end of
954 	 * the message/* content. Although we will treat some headers as body
955 	 * text, we will still do a better job than if we were treating the
956 	 * entire message/* content as body text.
957 	 *
958 	 * XXX This changes state to MIME_STATE_NESTED and then outputs a body
959 	 * line, so that the body offset is not properly reset.
960 	 *
961 	 * Don't assume that the input is null terminated.
962 	 */
963 	if (input_is_text) {
964 	    if (len == 0) {
965 		state->body_offset = 0;		/* XXX */
966 		if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
967 		    if (state->curr_stype == MIME_STYPE_RFC822)
968 			SET_MIME_STATE(state, MIME_STATE_NESTED,
969 				       MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
970 				       MIME_ENC_7BIT, MIME_ENC_7BIT);
971 		    else if (state->curr_stype == MIME_STYPE_GLOBAL
972 			 && ((state->static_flags & MIME_OPT_DOWNGRADE) == 0
973 			     || state->curr_domain == MIME_ENC_7BIT))
974 			/* XXX EAI: inspect encoded message/global. */
975 			SET_MIME_STATE(state, MIME_STATE_NESTED,
976 				       MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
977 				       MIME_ENC_7BIT, MIME_ENC_7BIT);
978 		    else
979 			SET_CURR_STATE(state, MIME_STATE_BODY);
980 		} else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
981 		    SET_MIME_STATE(state, MIME_STATE_BODY,
982 				   MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
983 				   MIME_ENC_7BIT, MIME_ENC_7BIT);
984 		} else {
985 		    SET_CURR_STATE(state, MIME_STATE_BODY);
986 		}
987 	    }
988 
989 	    /*
990 	     * Invalid input. Force output of one blank line and jump to the
991 	     * body state, leaving all other state alone.
992 	     *
993 	     * We don't break legitimate mail by inserting a blank line
994 	     * separator between primary headers and a non-empty body. Many
995 	     * MTA's don't even record the presence or absence of this
996 	     * separator, nor does the Milter protocol pass it on to Milter
997 	     * applications.
998 	     *
999 	     * XXX We don't insert a blank line separator into attachments, to
1000 	     * avoid breaking digital signatures. Postfix shall not do a
1001 	     * worse mail delivery job than MTAs that can't even parse MIME.
1002 	     * We switch to body state anyway, to avoid treating body text as
1003 	     * header text, and mis-interpreting or truncating it. The code
1004 	     * below for initial From_ lines is for educational purposes.
1005 	     *
1006 	     * Sites concerned about MIME evasion can use a MIME normalizer.
1007 	     * Postfix has a different mission.
1008 	     */
1009 	    else {
1010 		if (msg_verbose)
1011 		    msg_info("garbage in %s header",
1012 		    state->curr_state == MIME_STATE_MULTIPART ? "multipart" :
1013 		       state->curr_state == MIME_STATE_PRIMARY ? "primary" :
1014 			 state->curr_state == MIME_STATE_NESTED ? "nested" :
1015 			     "other");
1016 		switch (state->curr_state) {
1017 		case MIME_STATE_PRIMARY:
1018 		    BODY_OUT(state, REC_TYPE_NORM, "", 0);
1019 		    SET_CURR_STATE(state, MIME_STATE_BODY);
1020 		    break;
1021 #if 0
1022 		case MIME_STATE_NESTED:
1023 		    if (state->body_offset <= 1
1024 			&& rec_type == REC_TYPE_NORM
1025 			&& len > 7
1026 			&& (strncmp(text + (*text == '>'), "From ", 5) == 0
1027 			    || strncmp(text, "=46rom ", 7) == 0))
1028 			break;
1029 		    /* FALLTHROUGH */
1030 #endif
1031 		default:
1032 		    SET_CURR_STATE(state, MIME_STATE_BODY);
1033 		    break;
1034 		}
1035 	    }
1036 	}
1037 
1038 	/*
1039 	 * This input is not text. Go to body state, unconditionally.
1040 	 */
1041 	else {
1042 	    SET_CURR_STATE(state, MIME_STATE_BODY);
1043 	}
1044 	/* FALLTHROUGH */
1045 
1046 	/*
1047 	 * Body text. Look for message boundaries, and recover from missing
1048 	 * boundary strings. Missing boundaries can happen in agressive mode
1049 	 * with text/rfc822-headers or with message/partial. Ignore non-space
1050 	 * cruft after --boundary or --boundary--, because some MUAs do, and
1051 	 * because only perverse software would take advantage of this to
1052 	 * escape detection. We have to ignore trailing cruft anyway, because
1053 	 * our saved copy of the boundary string may have been truncated for
1054 	 * safety reasons.
1055 	 *
1056 	 * Optionally look for 8-bit data in content that was announced as, or
1057 	 * that defaults to, 7-bit. Unfortunately, we cannot turn this on by
1058 	 * default. Majordomo sends requests for approval that do not
1059 	 * propagate the MIME information from the enclosed message to the
1060 	 * message headers of the approval request.
1061 	 *
1062 	 * Set the proper state information after processing a message boundary
1063 	 * string.
1064 	 *
1065 	 * Don't look for boundary strings at the start of a continued record.
1066 	 *
1067 	 * Don't assume that the input is null terminated.
1068 	 */
1069     case MIME_STATE_BODY:
1070 	if (input_is_text) {
1071 	    if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0
1072 		&& state->curr_encoding == MIME_ENC_7BIT
1073 		&& (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) {
1074 		for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++)
1075 		    if (*cp & 0200) {
1076 			REPORT_ERROR_LEN(state, MIME_ERR_8BIT_IN_7BIT_BODY,
1077 					 text, len);
1078 			break;
1079 		    }
1080 	    }
1081 	    if (state->stack && state->prev_rec_type != REC_TYPE_CONT
1082 		&& len > 2 && text[0] == '-' && text[1] == '-') {
1083 		for (sp = state->stack; sp != 0; sp = sp->next) {
1084 		    if (len >= 2 + sp->bound_len &&
1085 		      strncmp(text + 2, sp->boundary, sp->bound_len) == 0) {
1086 			while (sp != state->stack)
1087 			    mime_state_pop(state);
1088 			if (len >= 4 + sp->bound_len &&
1089 			  strncmp(text + 2 + sp->bound_len, "--", 2) == 0) {
1090 			    mime_state_pop(state);
1091 			    SET_MIME_STATE(state, MIME_STATE_BODY,
1092 					 MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
1093 					   MIME_ENC_7BIT, MIME_ENC_7BIT);
1094 			} else {
1095 			    SET_MIME_STATE(state, MIME_STATE_MULTIPART,
1096 					   sp->def_ctype, sp->def_stype,
1097 					   MIME_ENC_7BIT, MIME_ENC_7BIT);
1098 			}
1099 			break;
1100 		    }
1101 		}
1102 	    }
1103 	    /* Put last for consistency with header output routine. */
1104 	    if ((state->static_flags & MIME_OPT_DOWNGRADE)
1105 		&& state->curr_domain != MIME_ENC_7BIT)
1106 		mime_state_downgrade(state, rec_type, text, len);
1107 	    else
1108 		BODY_OUT(state, rec_type, text, len);
1109 	}
1110 
1111 	/*
1112 	 * The input is not a text record. Inform the application that this
1113 	 * is the last opportunity to send any pending output.
1114 	 */
1115 	else {
1116 	    if (state->body_end)
1117 		state->body_end(state->app_context);
1118 	}
1119 	SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
1120 
1121 	/*
1122 	 * Oops. This can't happen.
1123 	 */
1124     default:
1125 	msg_panic("mime_state_update: unknown state: %d", state->curr_state);
1126     }
1127 }
1128 
1129  /*
1130   * Mime error to (DSN, text) mapping. Order matters; more serious errors
1131   * must precede less serious errors, because the error-to-text conversion
1132   * can report only one error.
1133   */
1134 static const MIME_STATE_DETAIL mime_err_detail[] = {
1135     MIME_ERR_NESTING, "5.6.0", "MIME nesting exceeds safety limit",
1136     MIME_ERR_TRUNC_HEADER, "5.6.0", "message header length exceeds safety limit",
1137     MIME_ERR_8BIT_IN_HEADER, "5.6.0", "improper use of 8-bit data in message header",
1138     MIME_ERR_8BIT_IN_7BIT_BODY, "5.6.0", "improper use of 8-bit data in message body",
1139     MIME_ERR_ENCODING_DOMAIN, "5.6.0", "invalid message/* or multipart/* encoding domain",
1140     0,
1141 };
1142 
1143 /* mime_state_error - error code to string */
1144 
1145 const char *mime_state_error(int error_code)
1146 {
1147     const MIME_STATE_DETAIL *mp;
1148 
1149     if (error_code == 0)
1150 	msg_panic("mime_state_error: there is no error");
1151     for (mp = mime_err_detail; mp->code; mp++)
1152 	if (mp->code & error_code)
1153 	    return (mp->text);
1154     msg_panic("mime_state_error: unknown error code %d", error_code);
1155 }
1156 
1157 /* mime_state_detail - error code to table entry with assorted data */
1158 
1159 const MIME_STATE_DETAIL *mime_state_detail(int error_code)
1160 {
1161     const MIME_STATE_DETAIL *mp;
1162 
1163     if (error_code == 0)
1164 	msg_panic("mime_state_detail: there is no error");
1165     for (mp = mime_err_detail; mp->code; mp++)
1166 	if (mp->code & error_code)
1167 	    return (mp);
1168     msg_panic("mime_state_detail: unknown error code %d", error_code);
1169 }
1170 
1171 #ifdef TEST
1172 
1173 #include <stdlib.h>
1174 #include <stringops.h>
1175 #include <vstream.h>
1176 #include <msg_vstream.h>
1177 #include <rec_streamlf.h>
1178 
1179  /*
1180   * Stress test the REC_TYPE_CONT/NORM handling, but don't break header
1181   * labels.
1182   */
1183 /*#define REC_LEN	40*/
1184 
1185 #define REC_LEN	1024
1186 
1187 static void head_out(void *context, int class, const HEADER_OPTS *unused_info,
1188 		             VSTRING *buf, off_t offset)
1189 {
1190     VSTREAM *stream = (VSTREAM *) context;
1191 
1192     vstream_fprintf(stream, "%s %ld\t|%s\n",
1193 		    class == MIME_HDR_PRIMARY ? "MAIN" :
1194 		    class == MIME_HDR_MULTIPART ? "MULT" :
1195 		    class == MIME_HDR_NESTED ? "NEST" :
1196 		    "ERROR", (long) offset, STR(buf));
1197 }
1198 
1199 static void head_end(void *context)
1200 {
1201     VSTREAM *stream = (VSTREAM *) context;
1202 
1203     vstream_fprintf(stream, "HEADER END\n");
1204 }
1205 
1206 static void body_out(void *context, int rec_type, const char *buf, ssize_t len,
1207 		             off_t offset)
1208 {
1209     VSTREAM *stream = (VSTREAM *) context;
1210 
1211     vstream_fprintf(stream, "BODY %c %ld\t|", rec_type, (long) offset);
1212     vstream_fwrite(stream, buf, len);
1213     if (rec_type == REC_TYPE_NORM)
1214 	VSTREAM_PUTC('\n', stream);
1215 }
1216 
1217 static void body_end(void *context)
1218 {
1219     VSTREAM *stream = (VSTREAM *) context;
1220 
1221     vstream_fprintf(stream, "BODY END\n");
1222 }
1223 
1224 static void err_print(void *unused_context, int err_flag,
1225 		              const char *text, ssize_t len)
1226 {
1227     msg_warn("%s: %.*s", mime_state_error(err_flag),
1228 	     len < 100 ? (int) len : 100, text);
1229 }
1230 
1231 int     var_header_limit = 2000;
1232 int     var_mime_maxdepth = 20;
1233 int     var_mime_bound_len = 2000;
1234 char   *var_drop_hdrs = DEF_DROP_HDRS;
1235 
1236 int     main(int unused_argc, char **argv)
1237 {
1238     int     rec_type;
1239     int     last = 0;
1240     VSTRING *buf;
1241     MIME_STATE *state;
1242     int     err;
1243 
1244     /*
1245      * Initialize.
1246      */
1247 #define MIME_OPTIONS \
1248 	    (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
1249 	    | MIME_OPT_REPORT_8BIT_IN_HEADER \
1250 	    | MIME_OPT_REPORT_ENCODING_DOMAIN \
1251 	    | MIME_OPT_REPORT_TRUNC_HEADER \
1252 	    | MIME_OPT_REPORT_NESTING \
1253 	    | MIME_OPT_DOWNGRADE)
1254 
1255     msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
1256     msg_verbose = 1;
1257     buf = vstring_alloc(10);
1258     state = mime_state_alloc(MIME_OPTIONS,
1259 			     head_out, head_end,
1260 			     body_out, body_end,
1261 			     err_print,
1262 			     (void *) VSTREAM_OUT);
1263 
1264     /*
1265      * Main loop.
1266      */
1267     do {
1268 	rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
1269 	VSTRING_TERMINATE(buf);
1270 	err = mime_state_update(state, last = rec_type, STR(buf), LEN(buf));
1271 	vstream_fflush(VSTREAM_OUT);
1272     } while (rec_type > 0);
1273 
1274     /*
1275      * Error reporting.
1276      */
1277     if (err & MIME_ERR_TRUNC_HEADER)
1278 	msg_warn("message header length exceeds safety limit");
1279     if (err & MIME_ERR_NESTING)
1280 	msg_warn("MIME nesting exceeds safety limit");
1281     if (err & MIME_ERR_8BIT_IN_HEADER)
1282 	msg_warn("improper use of 8-bit data in message header");
1283     if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
1284 	msg_warn("improper use of 8-bit data in message body");
1285     if (err & MIME_ERR_ENCODING_DOMAIN)
1286 	msg_warn("improper message/* or multipart/* encoding domain");
1287 
1288     /*
1289      * Cleanup.
1290      */
1291     mime_state_free(state);
1292     vstring_free(buf);
1293     exit(0);
1294 }
1295 
1296 #endif
1297