xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/header_body_checks.c (revision 33881f779a77dce6440bdc44610d94de75bebefe)
1 /*	$NetBSD: header_body_checks.c,v 1.3 2020/03/18 19:05:16 christos 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 /*				ssize_t 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 /*	Wietse Venema
161 /*	Google, Inc.
162 /*	111 8th Avenue
163 /*	New York, NY 10011, USA
164 /*--*/
165 
166 /* System library. */
167 
168 #include <sys_defs.h>
169 #include <ctype.h>
170 #include <string.h>
171 #ifdef STRCASECMP_IN_STRINGS_H
172 #include <strings.h>
173 #endif
174 
175 /* Utility library. */
176 
177 #include <msg.h>
178 #include <mymalloc.h>
179 
180 /* Global library. */
181 
182 #include <mime_state.h>
183 #include <rec_type.h>
184 #include <is_header.h>
185 #include <cleanup_user.h>
186 #include <dsn_util.h>
187 #include <header_body_checks.h>
188 
189 /* Application-specific. */
190 
191  /*
192   * Something that is guaranteed to be different from a real string result
193   * from header/body_checks.
194   */
195 char    hbc_checks_error;
196 const char hbc_checks_unknown;
197 
198  /*
199   * Header checks are stored as an array of HBC_MAP_INFO structures, one
200   * structure for each header class (MIME_HDR_PRIMARY, MIME_HDR_MULTIPART, or
201   * MIME_HDR_NESTED).
202   *
203   * Body checks are stored as one single HBC_MAP_INFO structure, because we make
204   * no distinction between body segments.
205   */
206 #define HBC_HEADER_INDEX(class)	((class) - MIME_HDR_FIRST)
207 #define HBC_BODY_INDEX	(0)
208 
209 #define HBC_INIT(hbc, index, name, value) do { \
210 	HBC_MAP_INFO *_mp = (hbc)->map_info + (index); \
211 	if (*(value) != 0) { \
212 	    _mp->map_class = (name); \
213 	    _mp->maps = maps_create((name), (value), DICT_FLAG_LOCK); \
214 	} else { \
215 	    _mp->map_class = 0; \
216 	    _mp->maps = 0; \
217 	} \
218     } while (0)
219 
220 /* How does the action routine know where we are? */
221 
222 #define	HBC_CTXT_HEADER	"header"
223 #define HBC_CTXT_BODY	"body"
224 
225 /* Silly little macros. */
226 
227 #define STR(x)	vstring_str(x)
228 #define LEN(x)	VSTRING_LEN(x)
229 
230 /* hbc_action - act upon a header/body match */
231 
hbc_action(void * context,HBC_CALL_BACKS * cb,const char * map_class,const char * where,const char * cmd,const char * line,ssize_t line_len,off_t offset)232 static char *hbc_action(void *context, HBC_CALL_BACKS *cb,
233 			        const char *map_class, const char *where,
234 			        const char *cmd, const char *line,
235 			        ssize_t line_len, off_t offset)
236 {
237     const char *cmd_args = cmd + strcspn(cmd, " \t");
238     ssize_t cmd_len = cmd_args - cmd;
239     char   *ret;
240 
241     /*
242      * XXX We don't use a hash table for action lookup. Mail rarely triggers
243      * an action, and mail that triggers multiple actions is even rarer.
244      * Setting up the hash table costs more than we would gain from using it.
245      */
246     while (*cmd_args && ISSPACE(*cmd_args))
247 	cmd_args++;
248 
249 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
250 
251     if (cb->extend
252 	&& (ret = cb->extend(context, cmd, cmd_len, cmd_args, where, line,
253 			     line_len, offset)) != HBC_CHECKS_STAT_UNKNOWN)
254 	return (ret);
255 
256     if (STREQUAL(cmd, "WARN", cmd_len)) {
257 	cb->logger(context, "warning", where, line, cmd_args);
258 	return ((char *) line);
259     }
260     if (STREQUAL(cmd, "INFO", cmd_len)) {
261 	cb->logger(context, "info", where, line, cmd_args);
262 	return ((char *) line);
263     }
264     if (STREQUAL(cmd, "REPLACE", cmd_len)) {
265 	if (*cmd_args == 0) {
266 	    msg_warn("REPLACE action without text in %s map", map_class);
267 	    return ((char *) line);
268 	} else if (strcmp(where, HBC_CTXT_HEADER) == 0
269 		   && !is_header(cmd_args)) {
270 	    msg_warn("bad REPLACE header text \"%s\" in %s map -- "
271 		   "need \"headername: headervalue\"", cmd_args, map_class);
272 	    return ((char *) line);
273 	} else {
274 	    cb->logger(context, "replace", where, line, cmd_args);
275 	    return (mystrdup(cmd_args));
276 	}
277     }
278     if (cb->prepend && STREQUAL(cmd, "PREPEND", cmd_len)) {
279 	if (*cmd_args == 0) {
280 	    msg_warn("PREPEND action without text in %s map", map_class);
281 	} else if (strcmp(where, HBC_CTXT_HEADER) == 0
282 		   && !is_header(cmd_args)) {
283 	    msg_warn("bad PREPEND header text \"%s\" in %s map -- "
284 		   "need \"headername: headervalue\"", cmd_args, map_class);
285 	} else {
286 	    cb->logger(context, "prepend", where, line, cmd_args);
287 	    cb->prepend(context, REC_TYPE_NORM, cmd_args, strlen(cmd_args), offset);
288 	}
289 	return ((char *) line);
290     }
291     if (STREQUAL(cmd, "STRIP", cmd_len)) {
292 	cb->logger(context, "strip", where, line, cmd_args);
293 	return (HBC_CHECKS_STAT_IGNORE);
294     }
295     /* Allow and ignore optional text after the action. */
296 
297     if (STREQUAL(cmd, "IGNORE", cmd_len))
298 	/* XXX Not logged for compatibility with cleanup(8). */
299 	return (HBC_CHECKS_STAT_IGNORE);
300 
301     if (STREQUAL(cmd, "DUNNO", cmd_len)		/* preferred */
302 	||STREQUAL(cmd, "OK", cmd_len))		/* compatibility */
303 	return ((char *) line);
304 
305     msg_warn("unsupported command in %s map: %s", map_class, cmd);
306     return ((char *) line);
307 }
308 
309 /* hbc_header_checks - process one complete header line */
310 
hbc_header_checks(void * context,HBC_CHECKS * hbc,int header_class,const HEADER_OPTS * hdr_opts,VSTRING * header,off_t offset)311 char   *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class,
312 			          const HEADER_OPTS *hdr_opts,
313 			          VSTRING *header, off_t offset)
314 {
315     const char *myname = "hbc_header_checks";
316     const char *action;
317     HBC_MAP_INFO *mp;
318 
319     if (msg_verbose)
320 	msg_info("%s: '%.30s'", myname, STR(header));
321 
322     /*
323      * XXX This is for compatibility with the cleanup(8) server.
324      */
325     if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
326 	header_class = MIME_HDR_MULTIPART;
327 
328     mp = hbc->map_info + HBC_HEADER_INDEX(header_class);
329 
330     if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) {
331 	return (hbc_action(context, hbc->call_backs,
332 			   mp->map_class, HBC_CTXT_HEADER, action,
333 			   STR(header), LEN(header), offset));
334     } else if (mp->maps && mp->maps->error) {
335 	return (HBC_CHECKS_STAT_ERROR);
336     } else {
337 	return (STR(header));
338     }
339 }
340 
341 /* hbc_body_checks - inspect one body record */
342 
hbc_body_checks(void * context,HBC_CHECKS * hbc,const char * line,ssize_t len,off_t offset)343 char   *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line,
344 			        ssize_t len, off_t offset)
345 {
346     const char *myname = "hbc_body_checks";
347     const char *action;
348     HBC_MAP_INFO *mp;
349 
350     if (msg_verbose)
351 	msg_info("%s: '%.30s'", myname, line);
352 
353     mp = hbc->map_info;
354 
355     if ((action = maps_find(mp->maps, line, 0)) != 0) {
356 	return (hbc_action(context, hbc->call_backs,
357 			   mp->map_class, HBC_CTXT_BODY, action,
358 			   line, len, offset));
359     } else if (mp->maps->error) {
360 	return (HBC_CHECKS_STAT_ERROR);
361     } else {
362 	return ((char *) line);
363     }
364 }
365 
366 /* hbc_header_checks_create - create header checking context */
367 
hbc_header_checks_create(const char * header_checks_name,const char * header_checks_value,const char * mime_header_checks_name,const char * mime_header_checks_value,const char * nested_header_checks_name,const char * nested_header_checks_value,HBC_CALL_BACKS * call_backs)368 HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name,
369 				             const char *header_checks_value,
370 				        const char *mime_header_checks_name,
371 			               const char *mime_header_checks_value,
372 			              const char *nested_header_checks_name,
373 			             const char *nested_header_checks_value,
374 				             HBC_CALL_BACKS *call_backs)
375 {
376     HBC_CHECKS *hbc;
377 
378     /*
379      * Optimize for the common case.
380      */
381     if (*header_checks_value == 0 && *mime_header_checks_value == 0
382 	&& *nested_header_checks_value == 0) {
383 	return (0);
384     } else {
385 	hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)
386 		 + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO));
387 	hbc->call_backs = call_backs;
388 	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY),
389 		 header_checks_name, header_checks_value);
390 	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART),
391 		 mime_header_checks_name, mime_header_checks_value);
392 	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED),
393 		 nested_header_checks_name, nested_header_checks_value);
394 	return (hbc);
395     }
396 }
397 
398 /* hbc_body_checks_create - create body checking context */
399 
hbc_body_checks_create(const char * body_checks_name,const char * body_checks_value,HBC_CALL_BACKS * call_backs)400 HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name,
401 				           const char *body_checks_value,
402 				           HBC_CALL_BACKS *call_backs)
403 {
404     HBC_CHECKS *hbc;
405 
406     /*
407      * Optimize for the common case.
408      */
409     if (*body_checks_value == 0) {
410 	return (0);
411     } else {
412 	hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc));
413 	hbc->call_backs = call_backs;
414 	HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value);
415 	return (hbc);
416     }
417 }
418 
419 /* _hbc_checks_free - destroy header/body checking context */
420 
_hbc_checks_free(HBC_CHECKS * hbc,ssize_t len)421 void    _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len)
422 {
423     HBC_MAP_INFO *mp;
424 
425     for (mp = hbc->map_info; mp < hbc->map_info + len; mp++)
426 	if (mp->maps)
427 	    maps_free(mp->maps);
428     myfree((void *) hbc);
429 }
430 
431  /*
432   * Test program. Specify the four maps on the command line, and feed a
433   * MIME-formatted message on stdin.
434   */
435 
436 #ifdef TEST
437 
438 #include <stdlib.h>
439 #include <stringops.h>
440 #include <vstream.h>
441 #include <msg_vstream.h>
442 #include <rec_streamlf.h>
443 #include <mail_params.h>
444 
445 typedef struct {
446     HBC_CHECKS *header_checks;
447     HBC_CHECKS *body_checks;
448     HBC_CALL_BACKS *call_backs;
449     VSTREAM *fp;
450     VSTRING *buf;
451     const char *queueid;
452     int     recno;
453 } HBC_TEST_CONTEXT;
454 
455 /*#define REC_LEN	40*/
456 #define REC_LEN	1024
457 
458 /* log_cb - log action with context */
459 
log_cb(void * context,const char * action,const char * where,const char * content,const char * text)460 static void log_cb(void *context, const char *action, const char *where,
461 		           const char *content, const char *text)
462 {
463     const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
464 
465     if (*text) {
466 	msg_info("%s: %s: %s %.200s: %s",
467 		 dp->queueid, action, where, content, text);
468     } else {
469 	msg_info("%s: %s: %s %.200s",
470 		 dp->queueid, action, where, content);
471     }
472 }
473 
474 /* out_cb - output call-back */
475 
out_cb(void * context,int rec_type,const char * buf,ssize_t len,off_t offset)476 static void out_cb(void *context, int rec_type, const char *buf,
477 		           ssize_t len, off_t offset)
478 {
479     const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
480 
481     vstream_fwrite(dp->fp, buf, len);
482     VSTREAM_PUTC('\n', dp->fp);
483     vstream_fflush(dp->fp);
484 }
485 
486 /* head_out - MIME_STATE header call-back */
487 
head_out(void * context,int header_class,const HEADER_OPTS * header_info,VSTRING * buf,off_t offset)488 static void head_out(void *context, int header_class,
489 		             const HEADER_OPTS *header_info,
490 		             VSTRING *buf, off_t offset)
491 {
492     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
493     char   *out;
494 
495     if (dp->header_checks == 0
496 	|| (out = hbc_header_checks(context, dp->header_checks, header_class,
497 				    header_info, buf, offset)) == STR(buf)) {
498 	vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
499 			dp->recno,
500 			header_class == MIME_HDR_PRIMARY ? "MAIN" :
501 			header_class == MIME_HDR_MULTIPART ? "MULT" :
502 			header_class == MIME_HDR_NESTED ? "NEST" :
503 			"ERROR", (long) offset, STR(buf));
504 	out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
505     } else if (out != 0) {
506 	vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
507 			dp->recno,
508 			header_class == MIME_HDR_PRIMARY ? "MAIN" :
509 			header_class == MIME_HDR_MULTIPART ? "MULT" :
510 			header_class == MIME_HDR_NESTED ? "NEST" :
511 			"ERROR", (long) offset, out);
512 	out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
513 	myfree(out);
514     }
515     dp->recno += 1;
516 }
517 
518 /* header_end - MIME_STATE end-of-header call-back */
519 
head_end(void * context)520 static void head_end(void *context)
521 {
522     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
523 
524     out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0);
525 }
526 
527 /* body_out - MIME_STATE body line call-back */
528 
body_out(void * context,int rec_type,const char * buf,ssize_t len,off_t offset)529 static void body_out(void *context, int rec_type, const char *buf,
530 		             ssize_t len, off_t offset)
531 {
532     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
533     char   *out;
534 
535     if (dp->body_checks == 0
536 	|| (out = hbc_body_checks(context, dp->body_checks,
537 				  buf, len, offset)) == buf) {
538 	vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
539 			dp->recno, rec_type, (long) offset, buf);
540 	out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
541     } else if (out != 0) {
542 	vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
543 			dp->recno, rec_type, (long) offset, out);
544 	out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
545 	myfree(out);
546     }
547     dp->recno += 1;
548 }
549 
550 /* body_end - MIME_STATE end-of-message call-back */
551 
body_end(void * context)552 static void body_end(void *context)
553 {
554     HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
555 
556     out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0);
557 }
558 
559 /* err_print - print MIME_STATE errors */
560 
err_print(void * unused_context,int err_flag,const char * text,ssize_t len)561 static void err_print(void *unused_context, int err_flag,
562 		              const char *text, ssize_t len)
563 {
564     msg_warn("%s: %.*s", mime_state_error(err_flag),
565 	     len < 100 ? (int) len : 100, text);
566 }
567 
568 int     var_header_limit = 2000;
569 int     var_mime_maxdepth = 20;
570 int     var_mime_bound_len = 2000;
571 char   *var_drop_hdrs = DEF_DROP_HDRS;
572 
main(int argc,char ** argv)573 int     main(int argc, char **argv)
574 {
575     int     rec_type;
576     VSTRING *buf;
577     int     err;
578     MIME_STATE *mime_state;
579     HBC_TEST_CONTEXT context;
580     static HBC_CALL_BACKS call_backs[1] = {
581 	log_cb,				/* logger */
582 	out_cb,				/* prepend */
583     };
584 
585     /*
586      * Sanity check.
587      */
588     if (argc != 5)
589 	msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]);
590 
591     /*
592      * Initialize.
593      */
594 #define MIME_OPTIONS \
595             (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
596             | MIME_OPT_REPORT_8BIT_IN_HEADER \
597             | MIME_OPT_REPORT_ENCODING_DOMAIN \
598             | MIME_OPT_REPORT_TRUNC_HEADER \
599             | MIME_OPT_REPORT_NESTING \
600             | MIME_OPT_DOWNGRADE)
601     msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
602     buf = vstring_alloc(10);
603     mime_state = mime_state_alloc(MIME_OPTIONS,
604 				  head_out, head_end,
605 				  body_out, body_end,
606 				  err_print,
607 				  (void *) &context);
608     context.header_checks =
609 	hbc_header_checks_create("header_checks", argv[1],
610 				 "mime_header_checks", argv[2],
611 				 "nested_header_checks", argv[3],
612 				 call_backs);
613     context.body_checks =
614 	hbc_body_checks_create("body_checks", argv[4], call_backs);
615     context.buf = vstring_alloc(100);
616     context.fp = VSTREAM_OUT;
617     context.queueid = "test-queueID";
618     context.recno = 0;
619 
620     /*
621      * Main loop.
622      */
623     do {
624 	rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
625 	VSTRING_TERMINATE(buf);
626 	err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf));
627 	vstream_fflush(VSTREAM_OUT);
628     } while (rec_type > 0);
629 
630     /*
631      * Error reporting.
632      */
633     if (err & MIME_ERR_TRUNC_HEADER)
634 	msg_warn("message header length exceeds safety limit");
635     if (err & MIME_ERR_NESTING)
636 	msg_warn("MIME nesting exceeds safety limit");
637     if (err & MIME_ERR_8BIT_IN_HEADER)
638 	msg_warn("improper use of 8-bit data in message header");
639     if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
640 	msg_warn("improper use of 8-bit data in message body");
641     if (err & MIME_ERR_ENCODING_DOMAIN)
642 	msg_warn("improper message/* or multipart/* encoding domain");
643 
644     /*
645      * Cleanup.
646      */
647     if (context.header_checks)
648 	hbc_header_checks_free(context.header_checks);
649     if (context.body_checks)
650 	hbc_body_checks_free(context.body_checks);
651     vstring_free(context.buf);
652     mime_state_free(mime_state);
653     vstring_free(buf);
654     exit(0);
655 }
656 
657 #endif
658