xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/header_body_checks.c (revision 63aea4bd5b445e491ff0389fe27ec78b3099dba3)
1 /*	$NetBSD: header_body_checks.c,v 1.1.1.3 2013/01/02 18:58:56 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	header_body_checks 3
6 /* SUMMARY
7 /*	header/body checks
8 /* SYNOPSIS
9 /*	#include <header_body_checks.h>
10 /*
11 /*	typedef struct {
12 /*		void	(*logger) (void *context, const char *action,
13 /*				const char *where, const char *line,
14 /*				const char *optional_text);
15 /*		void	(*prepend) (void *context, int rec_type,
16 /*				const char *buf, ssize_t len, off_t offset);
17 /*		char	*(*extend) (void *context, const char *command,
18 /*				int cmd_len, const char *cmd_args,
19 /*				const char *where, const char *line,
20 /*				ssize_t line_len, off_t offset);
21 /*	} HBC_CALL_BACKS;
22 /*
23 /*	HBC_CHECKS *hbc_header_checks_create(
24 /*			header_checks_name, header_checks_value
25 /*			mime_header_checks_name, mime_header_checks_value,
26 /*			nested_header_checks_name, nested_header_checks_value,
27 /*			call_backs)
28 /*	const char *header_checks_name;
29 /*	const char *header_checks_value;
30 /*	const char *mime_header_checks_name;
31 /*	const char *mime_header_checks_value;
32 /*	const char *nested_header_checks_name;
33 /*	const char *nested_header_checks_value;
34 /*	HBC_CALL_BACKS *call_backs;
35 /*
36 /*	HBC_CHECKS *hbc_body_checks_create(
37 /*			body_checks_name, body_checks_value,
38 /*			call_backs)
39 /*	const char *body_checks_name;
40 /*	const char *body_checks_value;
41 /*	HBC_CALL_BACKS *call_backs;
42 /*
43 /*	char	*hbc_header_checks(context, hbc, header_class, hdr_opts, header)
44 /*	void	*context;
45 /*	HBC_CHECKS *hbc;
46 /*	int	header_class;
47 /*	const HEADER_OPTS *hdr_opts;
48 /*	VSTRING *header;
49 /*
50 /*	char	*hbc_body_checks(context, hbc, body_line, body_line_len)
51 /*	void	*context;
52 /*	HBC_CHECKS *hbc;
53 /*	const char *body_line;
54 /*	ssize_t	body_line_len;
55 /*
56 /*	void	hbc_header_checks_free(hbc)
57 /*	HBC_CHECKS *hbc;
58 /*
59 /*	void	hbc_body_checks_free(hbc)
60 /*	HBC_CHECKS *hbc;
61 /* DESCRIPTION
62 /*	This module implements header_checks and body_checks.
63 /*	Actions are executed while mail is being delivered. The
64 /*	following actions are recognized: INFO, WARN, REPLACE,
65 /*	PREPEND, IGNORE, DUNNO, and OK. These actions are safe for
66 /*	use in delivery agents.
67 /*
68 /*	Other actions may be supplied via the extension mechanism
69 /*	described below.  For example, actions that change the
70 /*	message delivery time or destination. Such actions do not
71 /*	make sense in delivery agents, but they can be appropriate
72 /*	in, for example, before-queue filters.
73 /*
74 /*	hbc_header_checks_create() creates a context for header
75 /*	inspection. This function is typically called once during
76 /*	program initialization.  The result is a null pointer when
77 /*	all _value arguments specify zero-length strings; in this
78 /*	case, hbc_header_checks() and hbc_header_checks_free() must
79 /*	not be called.
80 /*
81 /*	hbc_header_checks() inspects the specified logical header.
82 /*	The result is either the original header, HBC_CHECKS_STAT_IGNORE
83 /*	(meaning: discard the header), HBC_CHECKS_STAT_ERROR, or a
84 /*	new header (meaning: replace the header and destroy the new
85 /*	header with myfree()).
86 /*
87 /*	hbc_header_checks_free() returns memory to the pool.
88 /*
89 /*	hbc_body_checks_create(), hbc_body_checks(), hbc_body_free()
90 /*	perform similar functions for body lines.
91 /*
92 /*	Arguments:
93 /* .IP body_line
94 /*	One line of body text.
95 /* .IP body_line_len
96 /*	Body line length.
97 /* .IP call_backs
98 /*	Table with call-back function pointers. This argument is
99 /*	not copied.  Note: the description below is not necessarily
100 /*	in data structure order.
101 /* .RS
102 /* .IP logger
103 /*	Call-back function for logging an action with the action's
104 /*	name in lower case, a location within a message ("header"
105 /*	or "body"), the content of the header or body line that
106 /*	triggered the action, and optional text or a zero-length
107 /*	string. This call-back feature must be specified.
108 /* .IP prepend
109 /*	Call-back function for the PREPEND action. The arguments
110 /*	are the same as those of mime_state(3) body output call-back
111 /*	functions.  Specify a null pointer to disable this action.
112 /* .IP extend
113 /*	Call-back function that logs and executes other actions.
114 /*	This function receives as arguments the command name and
115 /*	name length, the command arguments if any, the location
116 /*	within the message ("header" or "body"), the content and
117 /*	length of the header or body line that triggered the action,
118 /*	and the input byte offset within the current header or body
119 /*	segment.  The result value is either the original line
120 /*	argument, HBC_CHECKS_STAT_IGNORE (delete the line from the
121 /*	input stream) or HBC_CHECKS_STAT_UNKNOWN (the command was
122 /*	not recognized).  Specify a null pointer to disable this
123 /*	feature.
124 /* .RE
125 /* .IP context
126 /*	Application context for call-back functions specified with the
127 /*	call_backs argument.
128 /* .IP header
129 /*	A logical message header. Lines within a multi-line header
130 /*	are separated by a newline character.
131 /* .IP "header_checks_name, mime_header_checks_name"
132 /* .IP "nested_header_checks_name, body_checks_name"
133 /*	The main.cf configuration parameter names for header and body
134 /*	map lists.
135 /* .IP "header_checks_value, mime_header_checks_value"
136 /* .IP "nested_header_checks_value, body_checks_value"
137 /*	The values of main.cf configuration parameters for header and body
138 /*	map lists. Specify a zero-length string to disable a specific list.
139 /* .IP header_class
140 /*	A number in the range MIME_HDR_FIRST..MIME_HDR_LAST.
141 /* .IP hbc
142 /*	A handle created with hbc_header_checks_create() or
143 /*	hbc_body_checks_create().
144 /* .IP hdr_opts
145 /*	Message header properties.
146 /* SEE ALSO
147 /*	msg(3) diagnostics interface
148 /* DIAGNOSTICS
149 /*	Fatal errors: memory allocation problem.
150 /* LICENSE
151 /* .ad
152 /* .fi
153 /*	The Secure Mailer license must be distributed with this software.
154 /* AUTHOR(S)
155 /*	Wietse Venema
156 /*	IBM T.J. Watson Research
157 /*	P.O. Box 704
158 /*	Yorktown Heights, NY 10598, USA
159 /*--*/
160 
161 /* System library. */
162 
163 #include <sys_defs.h>
164 #include <ctype.h>
165 #include <string.h>
166 #ifdef STRCASECMP_IN_STRINGS_H
167 #include <strings.h>
168 #endif
169 
170 /* Utility library. */
171 
172 #include <msg.h>
173 #include <mymalloc.h>
174 
175 /* Global library. */
176 
177 #include <mime_state.h>
178 #include <rec_type.h>
179 #include <is_header.h>
180 #include <cleanup_user.h>
181 #include <dsn_util.h>
182 #include <header_body_checks.h>
183 
184 /* Application-specific. */
185 
186  /*
187   * Something that is guaranteed to be different from a real string result
188   * from header/body_checks.
189   */
190 char hbc_checks_error;
191 const char hbc_checks_unknown;
192 
193  /*
194   * Header checks are stored as an array of HBC_MAP_INFO structures, one
195   * structure for each header class (MIME_HDR_PRIMARY, MIME_HDR_MULTIPART, or
196   * MIME_HDR_NESTED).
197   *
198   * Body checks are stored as one single HBC_MAP_INFO structure, because we make
199   * no distinction between body segments.
200   */
201 #define HBC_HEADER_INDEX(class)	((class) - MIME_HDR_FIRST)
202 #define HBC_BODY_INDEX	(0)
203 
204 #define HBC_INIT(hbc, index, name, value) do { \
205 	HBC_MAP_INFO *_mp = (hbc)->map_info + (index); \
206 	if (*(value) != 0) { \
207 	    _mp->map_class = (name); \
208 	    _mp->maps = maps_create((name), (value), DICT_FLAG_LOCK); \
209 	} else { \
210 	    _mp->map_class = 0; \
211 	    _mp->maps = 0; \
212 	} \
213     } while (0)
214 
215 /* How does the action routine know where we are? */
216 
217 #define	HBC_CTXT_HEADER	"header"
218 #define HBC_CTXT_BODY	"body"
219 
220 /* Silly little macros. */
221 
222 #define STR(x)	vstring_str(x)
223 #define LEN(x)	VSTRING_LEN(x)
224 
225 /* hbc_action - act upon a header/body match */
226 
227 static char *hbc_action(void *context, HBC_CALL_BACKS *cb,
228 			        const char *map_class, const char *where,
229 			        const char *cmd, const char *line,
230 			        ssize_t line_len, off_t offset)
231 {
232     const char *cmd_args = cmd + strcspn(cmd, " \t");
233     int     cmd_len = cmd_args - cmd;
234     char   *ret;
235 
236     /*
237      * XXX We don't use a hash table for action lookup. Mail rarely triggers
238      * an action, and mail that triggers multiple actions is even rarer.
239      * Setting up the hash table costs more than we would gain from using it.
240      */
241     while (*cmd_args && ISSPACE(*cmd_args))
242 	cmd_args++;
243 
244 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
245 
246     if (cb->extend
247 	&& (ret = cb->extend(context, cmd, cmd_len, cmd_args, where, line,
248 			     line_len, offset)) != HBC_CHECKS_STAT_UNKNOWN)
249 	return (ret);
250 
251     if (STREQUAL(cmd, "WARN", cmd_len)) {
252 	cb->logger(context, "warning", where, line, cmd_args);
253 	return ((char *) line);
254     }
255     if (STREQUAL(cmd, "INFO", cmd_len)) {
256 	cb->logger(context, "info", where, line, cmd_args);
257 	return ((char *) line);
258     }
259     if (STREQUAL(cmd, "REPLACE", cmd_len)) {
260 	if (*cmd_args == 0) {
261 	    msg_warn("REPLACE action without text in %s map", map_class);
262 	    return ((char *) line);
263 	} else if (strcmp(where, HBC_CTXT_HEADER) == 0
264 		   && !is_header(cmd_args)) {
265 	    msg_warn("bad REPLACE header text \"%s\" in %s map -- "
266 		   "need \"headername: headervalue\"", cmd_args, map_class);
267 	    return ((char *) line);
268 	} else {
269 	    cb->logger(context, "replace", where, line, cmd_args);
270 	    return (mystrdup(cmd_args));
271 	}
272     }
273     if (cb->prepend && STREQUAL(cmd, "PREPEND", cmd_len)) {
274 	if (*cmd_args == 0) {
275 	    msg_warn("PREPEND action without text in %s map", map_class);
276 	} else if (strcmp(where, HBC_CTXT_HEADER) == 0
277 		   && !is_header(cmd_args)) {
278 	    msg_warn("bad PREPEND header text \"%s\" in %s map -- "
279 		   "need \"headername: headervalue\"", cmd_args, map_class);
280 	} else {
281 	    cb->logger(context, "prepend", where, line, cmd_args);
282 	    cb->prepend(context, REC_TYPE_NORM, cmd_args, strlen(cmd_args), offset);
283 	}
284 	return ((char *) line);
285     }
286     /* Allow and ignore optional text after the action. */
287 
288     if (STREQUAL(cmd, "IGNORE", cmd_len))
289 	/* XXX Not logged for compatibility with cleanup(8). */
290 	return (HBC_CHECKS_STAT_IGNORE);
291 
292     if (STREQUAL(cmd, "DUNNO", cmd_len)		/* preferred */
293 	||STREQUAL(cmd, "OK", cmd_len))		/* compatibility */
294 	return ((char *) line);
295 
296     msg_warn("unsupported command in %s map: %s", map_class, cmd);
297     return ((char *) line);
298 }
299 
300 /* hbc_header_checks - process one complete header line */
301 
302 char   *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class,
303 			          const HEADER_OPTS *hdr_opts,
304 			          VSTRING *header, off_t offset)
305 {
306     const char *myname = "hbc_header_checks";
307     const char *action;
308     HBC_MAP_INFO *mp;
309 
310     if (msg_verbose)
311 	msg_info("%s: '%.30s'", myname, STR(header));
312 
313     /*
314      * XXX This is for compatibility with the cleanup(8) server.
315      */
316     if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
317 	header_class = MIME_HDR_MULTIPART;
318 
319     mp = hbc->map_info + HBC_HEADER_INDEX(header_class);
320 
321     if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) {
322 	return (hbc_action(context, hbc->call_backs,
323 			   mp->map_class, HBC_CTXT_HEADER, action,
324 			   STR(header), LEN(header), offset));
325     } else if (mp->maps && mp->maps->error) {
326 	return (HBC_CHECKS_STAT_ERROR);
327     } else {
328 	return (STR(header));
329     }
330 }
331 
332 /* hbc_body_checks - inspect one body record */
333 
334 char   *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line,
335 			        ssize_t len, off_t offset)
336 {
337     const char *myname = "hbc_body_checks";
338     const char *action;
339     HBC_MAP_INFO *mp;
340 
341     if (msg_verbose)
342 	msg_info("%s: '%.30s'", myname, line);
343 
344     mp = hbc->map_info;
345 
346     if ((action = maps_find(mp->maps, line, 0)) != 0) {
347 	return (hbc_action(context, hbc->call_backs,
348 			   mp->map_class, HBC_CTXT_BODY, action,
349 			   line, len, offset));
350     } else if (mp->maps->error) {
351 	return (HBC_CHECKS_STAT_ERROR);
352     } else {
353 	return ((char *) line);
354     }
355 }
356 
357 /* hbc_header_checks_create - create header checking context */
358 
359 HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name,
360 				             const char *header_checks_value,
361 				        const char *mime_header_checks_name,
362 			               const char *mime_header_checks_value,
363 			              const char *nested_header_checks_name,
364 			             const char *nested_header_checks_value,
365 				             HBC_CALL_BACKS *call_backs)
366 {
367     HBC_CHECKS *hbc;
368 
369     /*
370      * Optimize for the common case.
371      */
372     if (*header_checks_value == 0 && *mime_header_checks_value == 0
373 	&& *nested_header_checks_value == 0) {
374 	return (0);
375     } else {
376 	hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)
377 		 + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO));
378 	hbc->call_backs = call_backs;
379 	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY),
380 		 header_checks_name, header_checks_value);
381 	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART),
382 		 mime_header_checks_name, mime_header_checks_value);
383 	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED),
384 		 nested_header_checks_name, nested_header_checks_value);
385 	return (hbc);
386     }
387 }
388 
389 /* hbc_body_checks_create - create body checking context */
390 
391 HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name,
392 				           const char *body_checks_value,
393 				           HBC_CALL_BACKS *call_backs)
394 {
395     HBC_CHECKS *hbc;
396 
397     /*
398      * Optimize for the common case.
399      */
400     if (*body_checks_value == 0) {
401 	return (0);
402     } else {
403 	hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc));
404 	hbc->call_backs = call_backs;
405 	HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value);
406 	return (hbc);
407     }
408 }
409 
410 /* _hbc_checks_free - destroy header/body checking context */
411 
412 void    _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len)
413 {
414     HBC_MAP_INFO *mp;
415 
416     for (mp = hbc->map_info; mp < hbc->map_info + len; mp++)
417 	if (mp->maps)
418 	    maps_free(mp->maps);
419     myfree((char *) hbc);
420 }
421 
422  /*
423   * Test program. Specify the four maps on the command line, and feed a
424   * MIME-formatted message on stdin.
425   */
426 
427 #ifdef TEST
428 
429 #include <stdlib.h>
430 #include <stringops.h>
431 #include <vstream.h>
432 #include <msg_vstream.h>
433 #include <rec_streamlf.h>
434 
435 typedef struct {
436     HBC_CHECKS *header_checks;
437     HBC_CHECKS *body_checks;
438     HBC_CALL_BACKS *call_backs;
439     VSTREAM *fp;
440     VSTRING *buf;
441     const char *queueid;
442     int     recno;
443 } HBC_TEST_CONTEXT;
444 
445 /*#define REC_LEN	40*/
446 #define REC_LEN	1024
447 
448 /* log_cb - log action with context */
449 
450 static void log_cb(void *context, const char *action, const char *where,
451 		           const char *content, const char *text)
452 {
453     const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
454 
455     if (*text) {
456 	msg_info("%s: %s: %s %.200s: %s",
457 		 dp->queueid, action, where, content, text);
458     } else {
459 	msg_info("%s: %s: %s %.200s",
460 		 dp->queueid, action, where, content);
461     }
462 }
463 
464 /* out_cb - output call-back */
465 
466 static void out_cb(void *context, int rec_type, const char *buf,
467 		           ssize_t len, off_t offset)
468 {
469     const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
470 
471     vstream_fwrite(dp->fp, buf, len);
472     VSTREAM_PUTC('\n', dp->fp);
473     vstream_fflush(dp->fp);
474 }
475 
476 /* head_out - MIME_STATE header call-back */
477 
478 static void head_out(void *context, int header_class,
479 		             const HEADER_OPTS *header_info,
480 		             VSTRING *buf, off_t offset)
481 {
482     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
483     char   *out;
484 
485     if (dp->header_checks == 0
486 	|| (out = hbc_header_checks(context, dp->header_checks, header_class,
487 				    header_info, buf, offset)) == STR(buf)) {
488 	vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
489 			dp->recno,
490 			header_class == MIME_HDR_PRIMARY ? "MAIN" :
491 			header_class == MIME_HDR_MULTIPART ? "MULT" :
492 			header_class == MIME_HDR_NESTED ? "NEST" :
493 			"ERROR", (long) offset, STR(buf));
494 	out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
495     } else if (out != 0) {
496 	vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
497 			dp->recno,
498 			header_class == MIME_HDR_PRIMARY ? "MAIN" :
499 			header_class == MIME_HDR_MULTIPART ? "MULT" :
500 			header_class == MIME_HDR_NESTED ? "NEST" :
501 			"ERROR", (long) offset, out);
502 	out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
503 	myfree(out);
504     }
505     dp->recno += 1;
506 }
507 
508 /* header_end - MIME_STATE end-of-header call-back */
509 
510 static void head_end(void *context)
511 {
512     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
513 
514     out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0);
515 }
516 
517 /* body_out - MIME_STATE body line call-back */
518 
519 static void body_out(void *context, int rec_type, const char *buf,
520 		             ssize_t len, off_t offset)
521 {
522     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
523     char   *out;
524 
525     if (dp->body_checks == 0
526 	|| (out = hbc_body_checks(context, dp->body_checks,
527 				  buf, len, offset)) == buf) {
528 	vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
529 			dp->recno, rec_type, (long) offset, buf);
530 	out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
531     } else if (out != 0) {
532 	vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
533 			dp->recno, rec_type, (long) offset, out);
534 	out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
535 	myfree(out);
536     }
537     dp->recno += 1;
538 }
539 
540 /* body_end - MIME_STATE end-of-message call-back */
541 
542 static void body_end(void *context)
543 {
544     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
545 
546     out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0);
547 }
548 
549 /* err_print - print MIME_STATE errors */
550 
551 static void err_print(void *unused_context, int err_flag,
552 		              const char *text, ssize_t len)
553 {
554     msg_warn("%s: %.*s", mime_state_error(err_flag),
555 	     len < 100 ? (int) len : 100, text);
556 }
557 
558 int     var_header_limit = 2000;
559 int     var_mime_maxdepth = 20;
560 int     var_mime_bound_len = 2000;
561 
562 int     main(int argc, char **argv)
563 {
564     int     rec_type;
565     VSTRING *buf;
566     int     err;
567     MIME_STATE *mime_state;
568     HBC_TEST_CONTEXT context;
569     static HBC_CALL_BACKS call_backs[1] = {
570 	log_cb,				/* logger */
571 	out_cb,				/* prepend */
572     };
573 
574     /*
575      * Sanity check.
576      */
577     if (argc != 5)
578 	msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]);
579 
580     /*
581      * Initialize.
582      */
583 #define MIME_OPTIONS \
584             (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
585             | MIME_OPT_REPORT_8BIT_IN_HEADER \
586             | MIME_OPT_REPORT_ENCODING_DOMAIN \
587             | MIME_OPT_REPORT_TRUNC_HEADER \
588             | MIME_OPT_REPORT_NESTING \
589             | MIME_OPT_DOWNGRADE)
590     msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
591     buf = vstring_alloc(10);
592     mime_state = mime_state_alloc(MIME_OPTIONS,
593 				  head_out, head_end,
594 				  body_out, body_end,
595 				  err_print,
596 				  (void *) &context);
597     context.header_checks =
598 	hbc_header_checks_create("header_checks", argv[1],
599 				 "mime_header_checks", argv[2],
600 				 "nested_header_checks", argv[3],
601 				 call_backs);
602     context.body_checks =
603 	hbc_body_checks_create("body_checks", argv[4], call_backs);
604     context.buf = vstring_alloc(100);
605     context.fp = VSTREAM_OUT;
606     context.queueid = "test-queueID";
607     context.recno = 0;
608 
609     /*
610      * Main loop.
611      */
612     do {
613 	rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
614 	VSTRING_TERMINATE(buf);
615 	err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf));
616 	vstream_fflush(VSTREAM_OUT);
617     } while (rec_type > 0);
618 
619     /*
620      * Error reporting.
621      */
622     if (err & MIME_ERR_TRUNC_HEADER)
623 	msg_warn("message header length exceeds safety limit");
624     if (err & MIME_ERR_NESTING)
625 	msg_warn("MIME nesting exceeds safety limit");
626     if (err & MIME_ERR_8BIT_IN_HEADER)
627 	msg_warn("improper use of 8-bit data in message header");
628     if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
629 	msg_warn("improper use of 8-bit data in message body");
630     if (err & MIME_ERR_ENCODING_DOMAIN)
631 	msg_warn("improper message/* or multipart/* encoding domain");
632 
633     /*
634      * Cleanup.
635      */
636     if (context.header_checks)
637 	hbc_header_checks_free(context.header_checks);
638     if (context.body_checks)
639 	hbc_body_checks_free(context.body_checks);
640     vstring_free(context.buf);
641     mime_state_free(mime_state);
642     vstring_free(buf);
643     exit(0);
644 }
645 
646 #endif
647