1/*++ 2/* NAME 3/* header_body_checks 3 4/* SUMMARY 5/* header/body checks 6/* SYNOPSIS 7/* #include <header_body_checks.h> 8/* 9/* typedef struct { 10/* void (*logger) (void *context, const char *action, 11/* const char *where, const char *line, 12/* const char *optional_text); 13/* void (*prepend) (void *context, int rec_type, 14/* const char *buf, ssize_t len, off_t offset); 15/* char *(*extend) (void *context, const char *command, 16/* int cmd_len, const char *cmd_args, 17/* const char *where, const char *line, 18/* ssize_t line_len, off_t offset); 19/* } HBC_CALL_BACKS; 20/* 21/* HBC_CHECKS *hbc_header_checks_create( 22/* header_checks_name, header_checks_value 23/* mime_header_checks_name, mime_header_checks_value, 24/* nested_header_checks_name, nested_header_checks_value, 25/* call_backs) 26/* const char *header_checks_name; 27/* const char *header_checks_value; 28/* const char *mime_header_checks_name; 29/* const char *mime_header_checks_value; 30/* const char *nested_header_checks_name; 31/* const char *nested_header_checks_value; 32/* HBC_CALL_BACKS *call_backs; 33/* 34/* HBC_CHECKS *hbc_body_checks_create( 35/* body_checks_name, body_checks_value, 36/* call_backs) 37/* const char *body_checks_name; 38/* const char *body_checks_value; 39/* HBC_CALL_BACKS *call_backs; 40/* 41/* char *hbc_header_checks(context, hbc, header_class, hdr_opts, header) 42/* void *context; 43/* HBC_CHECKS *hbc; 44/* int header_class; 45/* const HEADER_OPTS *hdr_opts; 46/* VSTRING *header; 47/* 48/* char *hbc_body_checks(context, hbc, body_line, body_line_len) 49/* void *context; 50/* HBC_CHECKS *hbc; 51/* const char *body_line; 52/* ssize_t body_line_len; 53/* 54/* void hbc_header_checks_free(hbc) 55/* HBC_CHECKS *hbc; 56/* 57/* void hbc_body_checks_free(hbc) 58/* HBC_CHECKS *hbc; 59/* DESCRIPTION 60/* This module implements header_checks and body_checks. 61/* Actions are executed while mail is being delivered. The 62/* following actions are recognized: INFO, WARN, REPLACE, 63/* PREPEND, IGNORE, DUNNO, and OK. These actions are safe for 64/* use in delivery agents. 65/* 66/* Other actions may be supplied via the extension mechanism 67/* described below. For example, actions that change the 68/* message delivery time or destination. Such actions do not 69/* make sense in delivery agents, but they can be appropriate 70/* in, for example, before-queue filters. 71/* 72/* hbc_header_checks_create() creates a context for header 73/* inspection. This function is typically called once during 74/* program initialization. The result is a null pointer when 75/* all _value arguments specify zero-length strings; in this 76/* case, hbc_header_checks() and hbc_header_checks_free() must 77/* not be called. 78/* 79/* hbc_header_checks() inspects the specified logical header. 80/* The result is either the original header, HBC_CHECKS_STAT_IGNORE 81/* (meaning: discard the header), HBC_CHECKS_STAT_ERROR, or a 82/* new header (meaning: replace the header and destroy the new 83/* header with myfree()). 84/* 85/* hbc_header_checks_free() returns memory to the pool. 86/* 87/* hbc_body_checks_create(), hbc_body_checks(), hbc_body_free() 88/* perform similar functions for body lines. 89/* 90/* Arguments: 91/* .IP body_line 92/* One line of body text. 93/* .IP body_line_len 94/* Body line length. 95/* .IP call_backs 96/* Table with call-back function pointers. This argument is 97/* not copied. Note: the description below is not necessarily 98/* in data structure order. 99/* .RS 100/* .IP logger 101/* Call-back function for logging an action with the action's 102/* name in lower case, a location within a message ("header" 103/* or "body"), the content of the header or body line that 104/* triggered the action, and optional text or a zero-length 105/* string. This call-back feature must be specified. 106/* .IP prepend 107/* Call-back function for the PREPEND action. The arguments 108/* are the same as those of mime_state(3) body output call-back 109/* functions. Specify a null pointer to disable this action. 110/* .IP extend 111/* Call-back function that logs and executes other actions. 112/* This function receives as arguments the command name and 113/* name length, the command arguments if any, the location 114/* within the message ("header" or "body"), the content and 115/* length of the header or body line that triggered the action, 116/* and the input byte offset within the current header or body 117/* segment. The result value is either the original line 118/* argument, HBC_CHECKS_STAT_IGNORE (delete the line from the 119/* input stream) or HBC_CHECKS_STAT_UNKNOWN (the command was 120/* not recognized). Specify a null pointer to disable this 121/* feature. 122/* .RE 123/* .IP context 124/* Application context for call-back functions specified with the 125/* call_backs argument. 126/* .IP header 127/* A logical message header. Lines within a multi-line header 128/* are separated by a newline character. 129/* .IP "header_checks_name, mime_header_checks_name" 130/* .IP "nested_header_checks_name, body_checks_name" 131/* The main.cf configuration parameter names for header and body 132/* map lists. 133/* .IP "header_checks_value, mime_header_checks_value" 134/* .IP "nested_header_checks_value, body_checks_value" 135/* The values of main.cf configuration parameters for header and body 136/* map lists. Specify a zero-length string to disable a specific list. 137/* .IP header_class 138/* A number in the range MIME_HDR_FIRST..MIME_HDR_LAST. 139/* .IP hbc 140/* A handle created with hbc_header_checks_create() or 141/* hbc_body_checks_create(). 142/* .IP hdr_opts 143/* Message header properties. 144/* SEE ALSO 145/* msg(3) diagnostics interface 146/* DIAGNOSTICS 147/* Fatal errors: memory allocation problem. 148/* LICENSE 149/* .ad 150/* .fi 151/* The Secure Mailer license must be distributed with this software. 152/* AUTHOR(S) 153/* Wietse Venema 154/* IBM T.J. Watson Research 155/* P.O. Box 704 156/* Yorktown Heights, NY 10598, USA 157/*--*/ 158 159/* System library. */ 160 161#include <sys_defs.h> 162#include <ctype.h> 163#include <string.h> 164#ifdef STRCASECMP_IN_STRINGS_H 165#include <strings.h> 166#endif 167 168/* Utility library. */ 169 170#include <msg.h> 171#include <mymalloc.h> 172 173/* Global library. */ 174 175#include <mime_state.h> 176#include <rec_type.h> 177#include <is_header.h> 178#include <cleanup_user.h> 179#include <dsn_util.h> 180#include <header_body_checks.h> 181 182/* Application-specific. */ 183 184 /* 185 * Something that is guaranteed to be different from a real string result 186 * from header/body_checks. 187 */ 188char hbc_checks_error; 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 if (mp->maps && mp->maps->error) { 324 return (HBC_CHECKS_STAT_ERROR); 325 } else { 326 return (STR(header)); 327 } 328} 329 330/* hbc_body_checks - inspect one body record */ 331 332char *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line, 333 ssize_t len, off_t offset) 334{ 335 const char *myname = "hbc_body_checks"; 336 const char *action; 337 HBC_MAP_INFO *mp; 338 339 if (msg_verbose) 340 msg_info("%s: '%.30s'", myname, line); 341 342 mp = hbc->map_info; 343 344 if ((action = maps_find(mp->maps, line, 0)) != 0) { 345 return (hbc_action(context, hbc->call_backs, 346 mp->map_class, HBC_CTXT_BODY, action, 347 line, len, offset)); 348 } else if (mp->maps->error) { 349 return (HBC_CHECKS_STAT_ERROR); 350 } else { 351 return ((char *) line); 352 } 353} 354 355/* hbc_header_checks_create - create header checking context */ 356 357HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name, 358 const char *header_checks_value, 359 const char *mime_header_checks_name, 360 const char *mime_header_checks_value, 361 const char *nested_header_checks_name, 362 const char *nested_header_checks_value, 363 HBC_CALL_BACKS *call_backs) 364{ 365 HBC_CHECKS *hbc; 366 367 /* 368 * Optimize for the common case. 369 */ 370 if (*header_checks_value == 0 && *mime_header_checks_value == 0 371 && *nested_header_checks_value == 0) { 372 return (0); 373 } else { 374 hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc) 375 + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO)); 376 hbc->call_backs = call_backs; 377 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY), 378 header_checks_name, header_checks_value); 379 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART), 380 mime_header_checks_name, mime_header_checks_value); 381 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED), 382 nested_header_checks_name, nested_header_checks_value); 383 return (hbc); 384 } 385} 386 387/* hbc_body_checks_create - create body checking context */ 388 389HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name, 390 const char *body_checks_value, 391 HBC_CALL_BACKS *call_backs) 392{ 393 HBC_CHECKS *hbc; 394 395 /* 396 * Optimize for the common case. 397 */ 398 if (*body_checks_value == 0) { 399 return (0); 400 } else { 401 hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)); 402 hbc->call_backs = call_backs; 403 HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value); 404 return (hbc); 405 } 406} 407 408/* _hbc_checks_free - destroy header/body checking context */ 409 410void _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len) 411{ 412 HBC_MAP_INFO *mp; 413 414 for (mp = hbc->map_info; mp < hbc->map_info + len; mp++) 415 if (mp->maps) 416 maps_free(mp->maps); 417 myfree((char *) hbc); 418} 419 420 /* 421 * Test program. Specify the four maps on the command line, and feed a 422 * MIME-formatted message on stdin. 423 */ 424 425#ifdef TEST 426 427#include <stdlib.h> 428#include <stringops.h> 429#include <vstream.h> 430#include <msg_vstream.h> 431#include <rec_streamlf.h> 432 433typedef struct { 434 HBC_CHECKS *header_checks; 435 HBC_CHECKS *body_checks; 436 HBC_CALL_BACKS *call_backs; 437 VSTREAM *fp; 438 VSTRING *buf; 439 const char *queueid; 440 int recno; 441} HBC_TEST_CONTEXT; 442 443/*#define REC_LEN 40*/ 444#define REC_LEN 1024 445 446/* log_cb - log action with context */ 447 448static void log_cb(void *context, const char *action, const char *where, 449 const char *content, const char *text) 450{ 451 const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 452 453 if (*text) { 454 msg_info("%s: %s: %s %.200s: %s", 455 dp->queueid, action, where, content, text); 456 } else { 457 msg_info("%s: %s: %s %.200s", 458 dp->queueid, action, where, content); 459 } 460} 461 462/* out_cb - output call-back */ 463 464static void out_cb(void *context, int rec_type, const char *buf, 465 ssize_t len, off_t offset) 466{ 467 const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 468 469 vstream_fwrite(dp->fp, buf, len); 470 VSTREAM_PUTC('\n', dp->fp); 471 vstream_fflush(dp->fp); 472} 473 474/* head_out - MIME_STATE header call-back */ 475 476static void head_out(void *context, int header_class, 477 const HEADER_OPTS *header_info, 478 VSTRING *buf, off_t offset) 479{ 480 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 481 char *out; 482 483 if (dp->header_checks == 0 484 || (out = hbc_header_checks(context, dp->header_checks, header_class, 485 header_info, buf, offset)) == STR(buf)) { 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, STR(buf)); 492 out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset); 493 } else if (out != 0) { 494 vstring_sprintf(dp->buf, "%d %s %ld\t|%s", 495 dp->recno, 496 header_class == MIME_HDR_PRIMARY ? "MAIN" : 497 header_class == MIME_HDR_MULTIPART ? "MULT" : 498 header_class == MIME_HDR_NESTED ? "NEST" : 499 "ERROR", (long) offset, out); 500 out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset); 501 myfree(out); 502 } 503 dp->recno += 1; 504} 505 506/* header_end - MIME_STATE end-of-header call-back */ 507 508static void head_end(void *context) 509{ 510 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 511 512 out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0); 513} 514 515/* body_out - MIME_STATE body line call-back */ 516 517static void body_out(void *context, int rec_type, const char *buf, 518 ssize_t len, off_t offset) 519{ 520 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 521 char *out; 522 523 if (dp->body_checks == 0 524 || (out = hbc_body_checks(context, dp->body_checks, 525 buf, len, offset)) == buf) { 526 vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s", 527 dp->recno, rec_type, (long) offset, buf); 528 out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset); 529 } else if (out != 0) { 530 vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s", 531 dp->recno, rec_type, (long) offset, out); 532 out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset); 533 myfree(out); 534 } 535 dp->recno += 1; 536} 537 538/* body_end - MIME_STATE end-of-message call-back */ 539 540static void body_end(void *context) 541{ 542 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context; 543 544 out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0); 545} 546 547/* err_print - print MIME_STATE errors */ 548 549static void err_print(void *unused_context, int err_flag, 550 const char *text, ssize_t len) 551{ 552 msg_warn("%s: %.*s", mime_state_error(err_flag), 553 len < 100 ? (int) len : 100, text); 554} 555 556int var_header_limit = 2000; 557int var_mime_maxdepth = 20; 558int var_mime_bound_len = 2000; 559 560int main(int argc, char **argv) 561{ 562 int rec_type; 563 VSTRING *buf; 564 int err; 565 MIME_STATE *mime_state; 566 HBC_TEST_CONTEXT context; 567 static HBC_CALL_BACKS call_backs[1] = { 568 log_cb, /* logger */ 569 out_cb, /* prepend */ 570 }; 571 572 /* 573 * Sanity check. 574 */ 575 if (argc != 5) 576 msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]); 577 578 /* 579 * Initialize. 580 */ 581#define MIME_OPTIONS \ 582 (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \ 583 | MIME_OPT_REPORT_8BIT_IN_HEADER \ 584 | MIME_OPT_REPORT_ENCODING_DOMAIN \ 585 | MIME_OPT_REPORT_TRUNC_HEADER \ 586 | MIME_OPT_REPORT_NESTING \ 587 | MIME_OPT_DOWNGRADE) 588 msg_vstream_init(basename(argv[0]), VSTREAM_OUT); 589 buf = vstring_alloc(10); 590 mime_state = mime_state_alloc(MIME_OPTIONS, 591 head_out, head_end, 592 body_out, body_end, 593 err_print, 594 (void *) &context); 595 context.header_checks = 596 hbc_header_checks_create("header_checks", argv[1], 597 "mime_header_checks", argv[2], 598 "nested_header_checks", argv[3], 599 call_backs); 600 context.body_checks = 601 hbc_body_checks_create("body_checks", argv[4], call_backs); 602 context.buf = vstring_alloc(100); 603 context.fp = VSTREAM_OUT; 604 context.queueid = "test-queueID"; 605 context.recno = 0; 606 607 /* 608 * Main loop. 609 */ 610 do { 611 rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN); 612 VSTRING_TERMINATE(buf); 613 err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf)); 614 vstream_fflush(VSTREAM_OUT); 615 } while (rec_type > 0); 616 617 /* 618 * Error reporting. 619 */ 620 if (err & MIME_ERR_TRUNC_HEADER) 621 msg_warn("message header length exceeds safety limit"); 622 if (err & MIME_ERR_NESTING) 623 msg_warn("MIME nesting exceeds safety limit"); 624 if (err & MIME_ERR_8BIT_IN_HEADER) 625 msg_warn("improper use of 8-bit data in message header"); 626 if (err & MIME_ERR_8BIT_IN_7BIT_BODY) 627 msg_warn("improper use of 8-bit data in message body"); 628 if (err & MIME_ERR_ENCODING_DOMAIN) 629 msg_warn("improper message/* or multipart/* encoding domain"); 630 631 /* 632 * Cleanup. 633 */ 634 if (context.header_checks) 635 hbc_header_checks_free(context.header_checks); 636 if (context.body_checks) 637 hbc_body_checks_free(context.body_checks); 638 vstring_free(context.buf); 639 mime_state_free(mime_state); 640 vstring_free(buf); 641 exit(0); 642} 643 644#endif 645