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