Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: header_body_checks.c,v 1.4 2025/02/25 19:15:45 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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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