1/* $NetBSD$ */ 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_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_CHECKS_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 */ 189const 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 225static 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, "INFO", cmd_len)) { 254 cb->logger(context, "info", where, line, cmd_args); 255 return ((char *) line); 256 } 257 if (STREQUAL(cmd, "REPLACE", cmd_len)) { 258 if (*cmd_args == 0) { 259 msg_warn("REPLACE action without text in %s map", map_class); 260 return ((char *) line); 261 } else if (strcmp(where, HBC_CTXT_HEADER) == 0 262 && !is_header(cmd_args)) { 263 msg_warn("bad REPLACE header text \"%s\" in %s map -- " 264 "need \"headername: headervalue\"", cmd_args, map_class); 265 return ((char *) line); 266 } else { 267 cb->logger(context, "replace", where, line, cmd_args); 268 return (mystrdup(cmd_args)); 269 } 270 } 271 if (cb->prepend && STREQUAL(cmd, "PREPEND", cmd_len)) { 272 if (*cmd_args == 0) { 273 msg_warn("PREPEND action without text in %s map", map_class); 274 } else if (strcmp(where, HBC_CTXT_HEADER) == 0 275 && !is_header(cmd_args)) { 276 msg_warn("bad PREPEND header text \"%s\" in %s map -- " 277 "need \"headername: headervalue\"", cmd_args, map_class); 278 } else { 279 cb->logger(context, "prepend", where, line, cmd_args); 280 cb->prepend(context, REC_TYPE_NORM, cmd_args, strlen(cmd_args), offset); 281 } 282 return ((char *) line); 283 } 284 /* Allow and ignore optional text after the action. */ 285 286 if (STREQUAL(cmd, "IGNORE", cmd_len)) 287 /* XXX Not logged for compatibility with cleanup(8). */ 288 return (HBC_CHECKS_STAT_IGNORE); 289 290 if (STREQUAL(cmd, "DUNNO", cmd_len) /* preferred */ 291 ||STREQUAL(cmd, "OK", cmd_len)) /* compatibility */ 292 return ((char *) line); 293 294 msg_warn("unsupported command in %s map: %s", map_class, cmd); 295 return ((char *) line); 296} 297 298/* hbc_header_checks - process one complete header line */ 299 300char *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class, 301 const HEADER_OPTS *hdr_opts, 302 VSTRING *header, off_t offset) 303{ 304 const char *myname = "hbc_header_checks"; 305 const char *action; 306 HBC_MAP_INFO *mp; 307 308 if (msg_verbose) 309 msg_info("%s: '%.30s'", myname, STR(header)); 310 311 /* 312 * XXX This is for compatibility with the cleanup(8) server. 313 */ 314 if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME)) 315 header_class = MIME_HDR_MULTIPART; 316 317 mp = hbc->map_info + HBC_HEADER_INDEX(header_class); 318 319 if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) { 320 return (hbc_action(context, hbc->call_backs, 321 mp->map_class, HBC_CTXT_HEADER, action, 322 STR(header), LEN(header), offset)); 323 } else { 324 return (STR(header)); 325 } 326} 327 328/* hbc_body_checks - inspect one body record */ 329 330char *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line, 331 ssize_t len, off_t offset) 332{ 333 const char *myname = "hbc_body_checks"; 334 const char *action; 335 HBC_MAP_INFO *mp; 336 337 if (msg_verbose) 338 msg_info("%s: '%.30s'", myname, line); 339 340 mp = hbc->map_info; 341 342 if ((action = maps_find(mp->maps, line, 0)) != 0) { 343 return (hbc_action(context, hbc->call_backs, 344 mp->map_class, HBC_CTXT_BODY, action, 345 line, len, offset)); 346 } else { 347 return ((char *) line); 348 } 349} 350 351/* hbc_header_checks_create - create header checking context */ 352 353HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name, 354 const char *header_checks_value, 355 const char *mime_header_checks_name, 356 const char *mime_header_checks_value, 357 const char *nested_header_checks_name, 358 const char *nested_header_checks_value, 359 HBC_CALL_BACKS *call_backs) 360{ 361 HBC_CHECKS *hbc; 362 363 /* 364 * Optimize for the common case. 365 */ 366 if (*header_checks_value == 0 && *mime_header_checks_value == 0 367 && *nested_header_checks_value == 0) { 368 return (0); 369 } else { 370 hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc) 371 + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO)); 372 hbc->call_backs = call_backs; 373 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY), 374 header_checks_name, header_checks_value); 375 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART), 376 mime_header_checks_name, mime_header_checks_value); 377 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED), 378 nested_header_checks_name, nested_header_checks_value); 379 return (hbc); 380 } 381} 382 383/* hbc_body_checks_create - create body checking context */ 384 385HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name, 386 const char *body_checks_value, 387 HBC_CALL_BACKS *call_backs) 388{ 389 HBC_CHECKS *hbc; 390 391 /* 392 * Optimize for the common case. 393 */ 394 if (*body_checks_value == 0) { 395 return (0); 396 } else { 397 hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)); 398 hbc->call_backs = call_backs; 399 HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value); 400 return (hbc); 401 } 402} 403 404/* _hbc_checks_free - destroy header/body checking context */ 405 406void _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len) 407{ 408 HBC_MAP_INFO *mp; 409 410 for (mp = hbc->map_info; mp < hbc->map_info + len; mp++) 411 if (mp->maps) 412 maps_free(mp->maps); 413 myfree((char *) hbc); 414} 415 416 /* 417 * Test program. Specify the four maps on the command line, and feed a 418 * MIME-formatted message on stdin. 419 */ 420 421#ifdef TEST 422 423#include <stdlib.h> 424#include <stringops.h> 425#include <vstream.h> 426#include <msg_vstream.h> 427#include <rec_streamlf.h> 428 429typedef struct { 430 HBC_CHECKS *header_checks; 431 HBC_CHECKS *body_checks; 432 HBC_CALL_BACKS *call_backs; 433 VSTREAM *fp; 434 VSTRING *buf; 435 const char *queueid; 436 int recno; 437} HBC_TEST_CONTEXT; 438 439/*#define REC_LEN 40*/ 440#define REC_LEN 1024 441 442/* log_cb - log action with context */ 443 444static void log_cb(void *context, const char *action, const char *where, 445 const char *content, const char *text) 446{ 447 const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 448 449 if (*text) { 450 msg_info("%s: %s: %s %.200s: %s", 451 dp->queueid, action, where, content, text); 452 } else { 453 msg_info("%s: %s: %s %.200s", 454 dp->queueid, action, where, content); 455 } 456} 457 458/* out_cb - output call-back */ 459 460static void out_cb(void *context, int rec_type, const char *buf, 461 ssize_t len, off_t offset) 462{ 463 const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 464 465 vstream_fwrite(dp->fp, buf, len); 466 VSTREAM_PUTC('\n', dp->fp); 467 vstream_fflush(dp->fp); 468} 469 470/* head_out - MIME_STATE header call-back */ 471 472static void head_out(void *context, int header_class, 473 const HEADER_OPTS *header_info, 474 VSTRING *buf, off_t offset) 475{ 476 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 477 char *out; 478 479 if (dp->header_checks == 0 480 || (out = hbc_header_checks(context, dp->header_checks, header_class, 481 header_info, buf, offset)) == STR(buf)) { 482 vstring_sprintf(dp->buf, "%d %s %ld\t|%s", 483 dp->recno, 484 header_class == MIME_HDR_PRIMARY ? "MAIN" : 485 header_class == MIME_HDR_MULTIPART ? "MULT" : 486 header_class == MIME_HDR_NESTED ? "NEST" : 487 "ERROR", (long) offset, STR(buf)); 488 out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset); 489 } else if (out != 0) { 490 vstring_sprintf(dp->buf, "%d %s %ld\t|%s", 491 dp->recno, 492 header_class == MIME_HDR_PRIMARY ? "MAIN" : 493 header_class == MIME_HDR_MULTIPART ? "MULT" : 494 header_class == MIME_HDR_NESTED ? "NEST" : 495 "ERROR", (long) offset, out); 496 out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset); 497 myfree(out); 498 } 499 dp->recno += 1; 500} 501 502/* header_end - MIME_STATE end-of-header call-back */ 503 504static void head_end(void *context) 505{ 506 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 507 508 out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0); 509} 510 511/* body_out - MIME_STATE body line call-back */ 512 513static void body_out(void *context, int rec_type, const char *buf, 514 ssize_t len, off_t offset) 515{ 516 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 517 char *out; 518 519 if (dp->body_checks == 0 520 || (out = hbc_body_checks(context, dp->body_checks, 521 buf, len, offset)) == buf) { 522 vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s", 523 dp->recno, rec_type, (long) offset, buf); 524 out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset); 525 } else if (out != 0) { 526 vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s", 527 dp->recno, rec_type, (long) offset, out); 528 out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset); 529 myfree(out); 530 } 531 dp->recno += 1; 532} 533 534/* body_end - MIME_STATE end-of-message call-back */ 535 536static void body_end(void *context) 537{ 538 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 539 540 out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0); 541} 542 543/* err_print - print MIME_STATE errors */ 544 545static void err_print(void *unused_context, int err_flag, 546 const char *text, ssize_t len) 547{ 548 msg_warn("%s: %.*s", mime_state_error(err_flag), 549 len < 100 ? (int) len : 100, text); 550} 551 552int var_header_limit = 2000; 553int var_mime_maxdepth = 20; 554int var_mime_bound_len = 2000; 555 556int main(int argc, char **argv) 557{ 558 int rec_type; 559 VSTRING *buf; 560 int err; 561 MIME_STATE *mime_state; 562 HBC_TEST_CONTEXT context; 563 static HBC_CALL_BACKS call_backs[1] = { 564 log_cb, /* logger */ 565 out_cb, /* prepend */ 566 }; 567 568 /* 569 * Sanity check. 570 */ 571 if (argc != 5) 572 msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]); 573 574 /* 575 * Initialize. 576 */ 577#define MIME_OPTIONS \ 578 (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \ 579 | MIME_OPT_REPORT_8BIT_IN_HEADER \ 580 | MIME_OPT_REPORT_ENCODING_DOMAIN \ 581 | MIME_OPT_REPORT_TRUNC_HEADER \ 582 | MIME_OPT_REPORT_NESTING \ 583 | MIME_OPT_DOWNGRADE) 584 msg_vstream_init(basename(argv[0]), VSTREAM_OUT); 585 buf = vstring_alloc(10); 586 mime_state = mime_state_alloc(MIME_OPTIONS, 587 head_out, head_end, 588 body_out, body_end, 589 err_print, 590 (void *) &context); 591 context.header_checks = 592 hbc_header_checks_create("header_checks", argv[1], 593 "mime_header_checks", argv[2], 594 "nested_header_checks", argv[3], 595 call_backs); 596 context.body_checks = 597 hbc_body_checks_create("body_checks", argv[4], call_backs); 598 context.buf = vstring_alloc(100); 599 context.fp = VSTREAM_OUT; 600 context.queueid = "test-queueID"; 601 context.recno = 0; 602 603 /* 604 * Main loop. 605 */ 606 do { 607 rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN); 608 VSTRING_TERMINATE(buf); 609 err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf)); 610 vstream_fflush(VSTREAM_OUT); 611 } while (rec_type > 0); 612 613 /* 614 * Error reporting. 615 */ 616 if (err & MIME_ERR_TRUNC_HEADER) 617 msg_warn("message header length exceeds safety limit"); 618 if (err & MIME_ERR_NESTING) 619 msg_warn("MIME nesting exceeds safety limit"); 620 if (err & MIME_ERR_8BIT_IN_HEADER) 621 msg_warn("improper use of 8-bit data in message header"); 622 if (err & MIME_ERR_8BIT_IN_7BIT_BODY) 623 msg_warn("improper use of 8-bit data in message body"); 624 if (err & MIME_ERR_ENCODING_DOMAIN) 625 msg_warn("improper message/* or multipart/* encoding domain"); 626 627 /* 628 * Cleanup. 629 */ 630 if (context.header_checks) 631 hbc_header_checks_free(context.header_checks); 632 if (context.body_checks) 633 hbc_body_checks_free(context.body_checks); 634 vstring_free(context.buf); 635 mime_state_free(mime_state); 636 vstring_free(buf); 637 exit(0); 638} 639 640#endif 641