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