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