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 
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