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