1 /* $NetBSD: mime_state.c,v 1.2 2017/02/14 01:16:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* mime_state 3 6 /* SUMMARY 7 /* MIME parser state machine 8 /* SYNOPSIS 9 /* #include <mime_state.h> 10 /* 11 /* MIME_STATE *mime_state_alloc(flags, head_out, head_end, 12 /* body_out, body_end, 13 /* err_print, context) 14 /* int flags; 15 /* void (*head_out)(void *ptr, int header_class, 16 /* const HEADER_OPTS *header_info, 17 /* VSTRING *buf, off_t offset); 18 /* void (*head_end)(void *ptr); 19 /* void (*body_out)(void *ptr, int rec_type, 20 /* const char *buf, ssize_t len, 21 /* off_t offset); 22 /* void (*body_end)(void *ptr); 23 /* void (*err_print)(void *ptr, int err_flag, const char *text) 24 /* void *context; 25 /* 26 /* int mime_state_update(state, rec_type, buf, len) 27 /* MIME_STATE *state; 28 /* int rec_type; 29 /* const char *buf; 30 /* ssize_t len; 31 /* 32 /* MIME_STATE *mime_state_free(state) 33 /* MIME_STATE *state; 34 /* 35 /* const char *mime_state_error(error_code) 36 /* int error_code; 37 /* 38 /* typedef struct { 39 /* .in +4 40 /* const int code; /* internal error code */ 41 /* const char *dsn; /* RFC 3463 */ 42 /* const char *text; /* descriptive text */ 43 /* .in -4 44 /* } MIME_STATE_DETAIL; 45 /* 46 /* const MIME_STATE_DETAIL *mime_state_detail(error_code) 47 /* int error_code; 48 /* DESCRIPTION 49 /* This module implements a one-pass MIME processor with optional 50 /* 8-bit to quoted-printable conversion. 51 /* 52 /* In order to fend off denial of service attacks, message headers 53 /* are truncated at or above var_header_limit bytes, message boundary 54 /* strings are truncated at var_mime_bound_len bytes, and the multipart 55 /* nesting level is limited to var_mime_maxdepth levels. 56 /* 57 /* mime_state_alloc() creates a MIME state machine. The machine 58 /* is delivered in its initial state, expecting content type 59 /* text/plain, 7-bit data. 60 /* 61 /* mime_state_update() updates the MIME state machine according 62 /* to the input record type and the record content. 63 /* The result value is the bit-wise OR of zero or more of the following: 64 /* .IP MIME_ERR_TRUNC_HEADER 65 /* A message header was longer than var_header_limit bytes. 66 /* .IP MIME_ERR_NESTING 67 /* The MIME structure was nested more than var_mime_maxdepth levels. 68 /* .IP MIME_ERR_8BIT_IN_HEADER 69 /* A message header contains 8-bit data. This is always illegal. 70 /* .IP MIME_ERR_8BIT_IN_7BIT_BODY 71 /* A MIME header specifies (or defaults to) 7-bit content, but the 72 /* corresponding message body or body parts contain 8-bit content. 73 /* .IP MIME_ERR_ENCODING_DOMAIN 74 /* An entity of type "message" or "multipart" specifies the wrong 75 /* content transfer encoding domain, or specifies a transformation 76 /* (quoted-printable, base64) instead of a domain (7bit, 8bit, 77 /* or binary). 78 /* .PP 79 /* mime_state_free() releases storage for a MIME state machine, 80 /* and conveniently returns a null pointer. 81 /* 82 /* mime_state_error() returns a string representation for the 83 /* specified error code. When multiple errors are specified it 84 /* reports what it deems the most serious one. 85 /* 86 /* mime_state_detail() returns a table entry with error 87 /* information for the specified error code. When multiple 88 /* errors are specified it reports what it deems the most 89 /* serious one. 90 /* 91 /* Arguments: 92 /* .IP body_out 93 /* The output routine for body lines. It receives unmodified input 94 /* records, or the result of 8-bit -> 7-bit conversion. 95 /* .IP body_end 96 /* A null pointer, or a pointer to a routine that is called after 97 /* the last input record is processed. 98 /* .IP buf 99 /* Buffer with the content of a logical or physical message record. 100 /* .IP context 101 /* Caller context that is passed on to the head_out and body_out 102 /* routines. 103 /* .IP enc_type 104 /* The content encoding: MIME_ENC_7BIT or MIME_ENC_8BIT. 105 /* .IP err_print 106 /* Null pointer, or pointer to a function that is called with 107 /* arguments: the application context, the error type, and the 108 /* offending input. Only one instance per error type is reported. 109 /* .IP flags 110 /* Special processing options. Specify the bit-wise OR of zero or 111 /* more of the following: 112 /* .RS 113 /* .IP MIME_OPT_DISABLE_MIME 114 /* Pay no attention to Content-* message headers, and switch to 115 /* message body state at the end of the primary message headers. 116 /* .IP MIME_OPT_REPORT_TRUNC_HEADER 117 /* Report errors that set the MIME_ERR_TRUNC_HEADER error flag 118 /* (see above). 119 /* .IP MIME_OPT_REPORT_8BIT_IN_HEADER 120 /* Report errors that set the MIME_ERR_8BIT_IN_HEADER error 121 /* flag (see above). This rarely stops legitimate mail. 122 /* .IP MIME_OPT_REPORT_8BIT_IN_7BIT_BODY 123 /* Report errors that set the MIME_ERR_8BIT_IN_7BIT_BODY error 124 /* flag (see above). This currently breaks Majordomo mail that is 125 /* forwarded for approval, because Majordomo does not propagate 126 /* MIME type information from the enclosed message to the message 127 /* headers of the request for approval. 128 /* .IP MIME_OPT_REPORT_ENCODING_DOMAIN 129 /* Report errors that set the MIME_ERR_ENCODING_DOMAIN error 130 /* flag (see above). 131 /* .IP MIME_OPT_REPORT_NESTING 132 /* Report errors that set the MIME_ERR_NESTING error flag 133 /* (see above). 134 /* .IP MIME_OPT_DOWNGRADE 135 /* Transform content that claims to be 8-bit into quoted-printable. 136 /* Where appropriate, update Content-Transfer-Encoding: message 137 /* headers. 138 /* .RE 139 /* .sp 140 /* For convenience, MIME_OPT_NONE requests no special processing. 141 /* .IP header_class 142 /* Specifies where a message header is located. 143 /* .RS 144 /* .IP MIME_HDR_PRIMARY 145 /* In the primary message header section. 146 /* .IP MIME_HDR_MULTIPART 147 /* In the header section after a multipart boundary string. 148 /* .IP MIME_HDR_NESTED 149 /* At the start of a nested (e.g., message/rfc822) message. 150 /* .RE 151 /* .sp 152 /* For convenience, the macros MIME_HDR_FIRST and MIME_HDR_LAST 153 /* specify the range of MIME_HDR_MUMBLE macros. 154 /* .sp 155 /* To find out if something is a MIME header at the beginning 156 /* of an RFC 822 message or an attached message, look at the 157 /* header_info argument. 158 /* .IP header_info 159 /* Null pointer or information about the message header, see 160 /* header_opts(3). 161 /* .IP head_out 162 /* The output routine that is invoked for outputting a message header. 163 /* A multi-line header is passed as one chunk of text with embedded 164 /* newlines. 165 /* It is the responsibility of the output routine to break the text 166 /* at embedded newlines, and to break up long text between newlines 167 /* into multiple output records. 168 /* Note: an output routine is explicitly allowed to modify the text. 169 /* .IP head_end 170 /* A null pointer, or a pointer to a routine that is called after 171 /* the last message header in the first header block is processed. 172 /* .IP len 173 /* Length of non-VSTRING input buffer. 174 /* .IP offset 175 /* The offset in bytes from the start of the current block of message 176 /* headers or body lines. Line boundaries are counted as one byte. 177 /* .IP rec_type 178 /* The input record type as defined in rec_type(3h). State is 179 /* updated for text records (REC_TYPE_NORM or REC_TYPE_CONT). 180 /* Some input records are stored internally in order to reconstruct 181 /* multi-line input. Upon receipt of any non-text record type, all 182 /* stored input is flushed and the state is set to "body". 183 /* .IP state 184 /* MIME parser state created with mime_state_alloc(). 185 /* BUGS 186 /* NOTE: when the end of headers is reached, mime_state_update() 187 /* may execute up to three call-backs before returning to the 188 /* caller: head_out(), head_end(), and body_out() or body_end(). 189 /* As long as call-backs return no result, it is up to the 190 /* call-back routines to check if a previous call-back experienced 191 /* an error. 192 /* 193 /* Different mail user agents treat malformed message boundary 194 /* strings in different ways. The Postfix MIME processor cannot 195 /* be bug-compatible with everything. 196 /* 197 /* This module will not glue together multipart boundary strings that 198 /* span multiple input records. 199 /* 200 /* This module will not glue together RFC 2231 formatted (boundary) 201 /* parameter values. RFC 2231 claims compatibility with existing 202 /* MIME processors. Splitting boundary strings is not backwards 203 /* compatible. 204 /* 205 /* The "8-bit data inside 7-bit body" test is myopic. It is not aware 206 /* of any enclosing (message or multipart) encoding information. 207 /* 208 /* If the input ends in data other than a hard line break, this module 209 /* will add a hard line break of its own. No line break is added to 210 /* empty input. 211 /* 212 /* This code recognizes the obsolete form "headername :" but will 213 /* normalize it to the canonical form "headername:". Leaving the 214 /* obsolete form alone would cause too much trouble with existing code 215 /* that expects only the normalized form. 216 /* SEE ALSO 217 /* msg(3) diagnostics interface 218 /* header_opts(3) header information lookup 219 /* RFC 822 (ARPA Internet Text Messages) 220 /* RFC 2045 (MIME: Format of internet message bodies) 221 /* RFC 2046 (MIME: Media types) 222 /* DIAGNOSTICS 223 /* Fatal errors: memory allocation problem. 224 /* LICENSE 225 /* .ad 226 /* .fi 227 /* The Secure Mailer license must be distributed with this software. 228 /* HISTORY 229 /* .ad 230 /* .fi 231 /* This code was implemented from scratch after reading the RFC 232 /* documents. This was a relatively straightforward effort with 233 /* few if any surprises. Victor Duchovni of Morgan Stanley shared 234 /* his experiences with ambiguities in real-life MIME implementations. 235 /* Liviu Daia of the Romanian Academy shared his insights in some 236 /* of the darker corners. 237 /* AUTHOR(S) 238 /* Wietse Venema 239 /* IBM T.J. Watson Research 240 /* P.O. Box 704 241 /* Yorktown Heights, NY 10598, USA 242 /*--*/ 243 244 /* System library. */ 245 246 #include <sys_defs.h> 247 #include <stdarg.h> 248 #include <ctype.h> 249 #include <string.h> 250 251 #ifdef STRCASECMP_IN_STRINGS_H 252 #include <strings.h> 253 #endif 254 255 /* Utility library. */ 256 257 #include <mymalloc.h> 258 #include <msg.h> 259 #include <vstring.h> 260 261 /* Global library. */ 262 263 #include <rec_type.h> 264 #include <is_header.h> 265 #include <header_opts.h> 266 #include <mail_params.h> 267 #include <header_token.h> 268 #include <lex_822.h> 269 #include <mime_state.h> 270 271 /* Application-specific. */ 272 273 /* 274 * Mime parser stack element for multipart content. 275 */ 276 typedef struct MIME_STACK { 277 int def_ctype; /* default content type */ 278 int def_stype; /* default content subtype */ 279 char *boundary; /* boundary string */ 280 ssize_t bound_len; /* boundary length */ 281 struct MIME_STACK *next; /* linkage */ 282 } MIME_STACK; 283 284 /* 285 * Mime parser state. 286 */ 287 #define MIME_MAX_TOKEN 3 /* tokens per attribute */ 288 289 struct MIME_STATE { 290 291 /* 292 * Volatile members. 293 */ 294 int curr_state; /* header/body state */ 295 int curr_ctype; /* last or default content type */ 296 int curr_stype; /* last or default content subtype */ 297 int curr_encoding; /* last or default content encoding */ 298 int curr_domain; /* last or default encoding unit */ 299 VSTRING *output_buffer; /* headers, quoted-printable body */ 300 int prev_rec_type; /* previous input record type */ 301 int nesting_level; /* safety */ 302 MIME_STACK *stack; /* for composite types */ 303 HEADER_TOKEN token[MIME_MAX_TOKEN]; /* header token array */ 304 VSTRING *token_buffer; /* header parser scratch buffer */ 305 int err_flags; /* processing errors */ 306 off_t head_offset; /* offset in header block */ 307 off_t body_offset; /* offset in body block */ 308 309 /* 310 * Static members. 311 */ 312 int static_flags; /* static processing options */ 313 MIME_STATE_HEAD_OUT head_out; /* header output routine */ 314 MIME_STATE_ANY_END head_end; /* end of primary header routine */ 315 MIME_STATE_BODY_OUT body_out; /* body output routine */ 316 MIME_STATE_ANY_END body_end; /* end of body output routine */ 317 MIME_STATE_ERR_PRINT err_print; /* error report */ 318 void *app_context; /* application context */ 319 }; 320 321 /* 322 * Content types and subtypes that we care about, either because we have to, 323 * or because we want to filter out broken MIME messages. 324 */ 325 #define MIME_CTYPE_OTHER 0 326 #define MIME_CTYPE_TEXT 1 327 #define MIME_CTYPE_MESSAGE 2 328 #define MIME_CTYPE_MULTIPART 3 329 330 #define MIME_STYPE_OTHER 0 331 #define MIME_STYPE_PLAIN 1 332 #define MIME_STYPE_RFC822 2 333 #define MIME_STYPE_PARTIAL 3 334 #define MIME_STYPE_EXTERN_BODY 4 335 #define MIME_STYPE_GLOBAL 5 336 337 /* 338 * MIME parser states. We steal from the public interface. 339 */ 340 #define MIME_STATE_PRIMARY MIME_HDR_PRIMARY /* primary headers */ 341 #define MIME_STATE_MULTIPART MIME_HDR_MULTIPART /* after --boundary */ 342 #define MIME_STATE_NESTED MIME_HDR_NESTED /* message/rfc822 */ 343 #define MIME_STATE_BODY (MIME_HDR_NESTED + 1) 344 345 #define SET_MIME_STATE(ptr, state, ctype, stype, encoding, domain) do { \ 346 (ptr)->curr_state = (state); \ 347 (ptr)->curr_ctype = (ctype); \ 348 (ptr)->curr_stype = (stype); \ 349 (ptr)->curr_encoding = (encoding); \ 350 (ptr)->curr_domain = (domain); \ 351 if ((state) == MIME_STATE_BODY) \ 352 (ptr)->body_offset = 0; \ 353 else \ 354 (ptr)->head_offset = 0; \ 355 } while (0) 356 357 #define SET_CURR_STATE(ptr, state) do { \ 358 (ptr)->curr_state = (state); \ 359 if ((state) == MIME_STATE_BODY) \ 360 (ptr)->body_offset = 0; \ 361 else \ 362 (ptr)->head_offset = 0; \ 363 } while (0) 364 365 /* 366 * MIME encodings and domains. We intentionally use the same codes for 367 * encodings and domains, so that we can easily find out whether a content 368 * transfer encoding header specifies a domain or whether it specifies 369 * domain+encoding, which is illegal for multipart/any and message/any. 370 */ 371 typedef struct MIME_ENCODING { 372 const char *name; /* external representation */ 373 int encoding; /* internal representation */ 374 int domain; /* subset of encoding */ 375 } MIME_ENCODING; 376 377 #define MIME_ENC_QP 1 /* encoding + domain */ 378 #define MIME_ENC_BASE64 2 /* encoding + domain */ 379 /* These are defined in mime_state.h as part of the external interface. */ 380 #ifndef MIME_ENC_7BIT 381 #define MIME_ENC_7BIT 7 /* domain only */ 382 #define MIME_ENC_8BIT 8 /* domain only */ 383 #define MIME_ENC_BINARY 9 /* domain only */ 384 #endif 385 386 static const MIME_ENCODING mime_encoding_map[] = { /* RFC 2045 */ 387 "7bit", MIME_ENC_7BIT, MIME_ENC_7BIT, /* domain */ 388 "8bit", MIME_ENC_8BIT, MIME_ENC_8BIT, /* domain */ 389 "binary", MIME_ENC_BINARY, MIME_ENC_BINARY, /* domain */ 390 "base64", MIME_ENC_BASE64, MIME_ENC_7BIT, /* encoding */ 391 "quoted-printable", MIME_ENC_QP, MIME_ENC_7BIT, /* encoding */ 392 0, 393 }; 394 395 /* 396 * Silly Little Macros. 397 */ 398 #define STR(x) vstring_str(x) 399 #define LEN(x) VSTRING_LEN(x) 400 #define END(x) vstring_end(x) 401 #define CU_CHAR_PTR(x) ((const unsigned char *) (x)) 402 403 #define REPORT_ERROR_LEN(state, err_type, text, len) do { \ 404 if ((state->err_flags & err_type) == 0) { \ 405 if (state->err_print != 0) \ 406 state->err_print(state->app_context, err_type, text, len); \ 407 state->err_flags |= err_type; \ 408 } \ 409 } while (0) 410 411 #define REPORT_ERROR(state, err_type, text) do { \ 412 const char *_text = text; \ 413 ssize_t _len = strlen(text); \ 414 REPORT_ERROR_LEN(state, err_type, _text, _len); \ 415 } while (0) 416 417 #define REPORT_ERROR_BUF(state, err_type, buf) \ 418 REPORT_ERROR_LEN(state, err_type, STR(buf), LEN(buf)) 419 420 421 /* 422 * Outputs and state changes are interleaved, so we must maintain separate 423 * offsets for header and body segments. 424 */ 425 #define HEAD_OUT(ptr, info, len) do { \ 426 if ((ptr)->head_out) { \ 427 (ptr)->head_out((ptr)->app_context, (ptr)->curr_state, \ 428 (info), (ptr)->output_buffer, (ptr)->head_offset); \ 429 (ptr)->head_offset += (len) + 1; \ 430 } \ 431 } while(0) 432 433 #define BODY_OUT(ptr, rec_type, text, len) do { \ 434 if ((ptr)->body_out) { \ 435 (ptr)->body_out((ptr)->app_context, (rec_type), \ 436 (text), (len), (ptr)->body_offset); \ 437 (ptr)->body_offset += (len) + 1; \ 438 } \ 439 } while(0) 440 441 /* mime_state_push - push boundary onto stack */ 442 443 static void mime_state_push(MIME_STATE *state, int def_ctype, int def_stype, 444 const char *boundary) 445 { 446 MIME_STACK *stack; 447 448 /* 449 * RFC 2046 mandates that a boundary string be up to 70 characters long. 450 * Some MTAs, including Postfix, include the fully-qualified MTA name 451 * which can be longer, so we are willing to handle boundary strings that 452 * exceed the RFC specification. We allow for message headers of up to 453 * var_header_limit characters. In order to avoid denial of service, we 454 * have to impose a configurable limit on the amount of text that we are 455 * willing to store as a boundary string. Despite this truncation way we 456 * will still correctly detect all intermediate boundaries and all the 457 * message headers that follow those boundaries. 458 */ 459 state->nesting_level += 1; 460 stack = (MIME_STACK *) mymalloc(sizeof(*stack)); 461 stack->def_ctype = def_ctype; 462 stack->def_stype = def_stype; 463 if ((stack->bound_len = strlen(boundary)) > var_mime_bound_len) 464 stack->bound_len = var_mime_bound_len; 465 stack->boundary = mystrndup(boundary, stack->bound_len); 466 stack->next = state->stack; 467 state->stack = stack; 468 if (msg_verbose) 469 msg_info("PUSH boundary %s", stack->boundary); 470 } 471 472 /* mime_state_pop - pop boundary from stack */ 473 474 static void mime_state_pop(MIME_STATE *state) 475 { 476 MIME_STACK *stack; 477 478 if ((stack = state->stack) == 0) 479 msg_panic("mime_state_pop: there is no stack"); 480 if (msg_verbose) 481 msg_info("POP boundary %s", stack->boundary); 482 state->nesting_level -= 1; 483 state->stack = stack->next; 484 myfree(stack->boundary); 485 myfree((void *) stack); 486 } 487 488 /* mime_state_alloc - create MIME state machine */ 489 490 MIME_STATE *mime_state_alloc(int flags, 491 MIME_STATE_HEAD_OUT head_out, 492 MIME_STATE_ANY_END head_end, 493 MIME_STATE_BODY_OUT body_out, 494 MIME_STATE_ANY_END body_end, 495 MIME_STATE_ERR_PRINT err_print, 496 void *context) 497 { 498 MIME_STATE *state; 499 500 state = (MIME_STATE *) mymalloc(sizeof(*state)); 501 502 /* Volatile members. */ 503 state->err_flags = 0; 504 state->body_offset = 0; /* XXX */ 505 SET_MIME_STATE(state, MIME_STATE_PRIMARY, 506 MIME_CTYPE_TEXT, MIME_STYPE_PLAIN, 507 MIME_ENC_7BIT, MIME_ENC_7BIT); 508 state->output_buffer = vstring_alloc(100); 509 state->prev_rec_type = 0; 510 state->stack = 0; 511 state->token_buffer = vstring_alloc(1); 512 513 /* Static members. */ 514 state->static_flags = flags; 515 state->head_out = head_out; 516 state->head_end = head_end; 517 state->body_out = body_out; 518 state->body_end = body_end; 519 state->err_print = err_print; 520 state->app_context = context; 521 return (state); 522 } 523 524 /* mime_state_free - destroy MIME state machine */ 525 526 MIME_STATE *mime_state_free(MIME_STATE *state) 527 { 528 vstring_free(state->output_buffer); 529 while (state->stack) 530 mime_state_pop(state); 531 if (state->token_buffer) 532 vstring_free(state->token_buffer); 533 myfree((void *) state); 534 return (0); 535 } 536 537 /* mime_state_content_type - process content-type header */ 538 539 static void mime_state_content_type(MIME_STATE *state, 540 const HEADER_OPTS *header_info) 541 { 542 const char *cp; 543 ssize_t tok_count; 544 int def_ctype; 545 int def_stype; 546 547 #define TOKEN_MATCH(tok, text) \ 548 ((tok).type == HEADER_TOK_TOKEN && strcasecmp((tok).u.value, (text)) == 0) 549 550 #define RFC2045_TSPECIALS "()<>@,;:\\\"/[]?=" 551 552 #define PARSE_CONTENT_TYPE_HEADER(state, ptr) \ 553 header_token(state->token, MIME_MAX_TOKEN, \ 554 state->token_buffer, ptr, RFC2045_TSPECIALS, ';') 555 556 cp = STR(state->output_buffer) + strlen(header_info->name) + 1; 557 if ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) > 0) { 558 559 /* 560 * text/whatever. Right now we don't really care if it is plain or 561 * not, but we may want to recognize subtypes later, and then this 562 * code can serve as an example. 563 */ 564 if (TOKEN_MATCH(state->token[0], "text")) { 565 state->curr_ctype = MIME_CTYPE_TEXT; 566 if (tok_count >= 3 567 && state->token[1].type == '/' 568 && TOKEN_MATCH(state->token[2], "plain")) 569 state->curr_stype = MIME_STYPE_PLAIN; 570 else 571 state->curr_stype = MIME_STYPE_OTHER; 572 return; 573 } 574 575 /* 576 * message/whatever body parts start with another block of message 577 * headers that we may want to look at. The partial and external-body 578 * subtypes cannot be subjected to 8-bit -> 7-bit conversion, so we 579 * must properly recognize them. 580 */ 581 if (TOKEN_MATCH(state->token[0], "message")) { 582 state->curr_ctype = MIME_CTYPE_MESSAGE; 583 state->curr_stype = MIME_STYPE_OTHER; 584 if (tok_count >= 3 585 && state->token[1].type == '/') { 586 if (TOKEN_MATCH(state->token[2], "rfc822")) 587 state->curr_stype = MIME_STYPE_RFC822; 588 else if (TOKEN_MATCH(state->token[2], "partial")) 589 state->curr_stype = MIME_STYPE_PARTIAL; 590 else if (TOKEN_MATCH(state->token[2], "external-body")) 591 state->curr_stype = MIME_STYPE_EXTERN_BODY; 592 else if (TOKEN_MATCH(state->token[2], "global")) 593 state->curr_stype = MIME_STYPE_GLOBAL; 594 } 595 return; 596 } 597 598 /* 599 * multipart/digest has default content type message/rfc822, 600 * multipart/whatever has default content type text/plain. 601 */ 602 if (TOKEN_MATCH(state->token[0], "multipart")) { 603 state->curr_ctype = MIME_CTYPE_MULTIPART; 604 if (tok_count >= 3 605 && state->token[1].type == '/' 606 && TOKEN_MATCH(state->token[2], "digest")) { 607 def_ctype = MIME_CTYPE_MESSAGE; 608 def_stype = MIME_STYPE_RFC822; 609 } else { 610 def_ctype = MIME_CTYPE_TEXT; 611 def_stype = MIME_STYPE_PLAIN; 612 } 613 614 /* 615 * Yes, this is supposed to capture multiple boundary strings, 616 * which are illegal and which could be used to hide content in 617 * an implementation dependent manner. The code below allows us 618 * to find embedded message headers as long as the sender uses 619 * only one of these same-level boundary strings. 620 * 621 * Yes, this is supposed to ignore the boundary value type. 622 */ 623 while ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) >= 0) { 624 if (tok_count >= 3 625 && TOKEN_MATCH(state->token[0], "boundary") 626 && state->token[1].type == '=') { 627 if (state->nesting_level > var_mime_maxdepth) { 628 if (state->static_flags & MIME_OPT_REPORT_NESTING) 629 REPORT_ERROR_BUF(state, MIME_ERR_NESTING, 630 state->output_buffer); 631 } else { 632 mime_state_push(state, def_ctype, def_stype, 633 state->token[2].u.value); 634 } 635 } 636 } 637 } 638 return; 639 } 640 641 /* 642 * other/whatever. 643 */ 644 else { 645 state->curr_ctype = MIME_CTYPE_OTHER; 646 return; 647 } 648 } 649 650 /* mime_state_content_encoding - process content-transfer-encoding header */ 651 652 static void mime_state_content_encoding(MIME_STATE *state, 653 const HEADER_OPTS *header_info) 654 { 655 const char *cp; 656 const MIME_ENCODING *cmp; 657 658 #define PARSE_CONTENT_ENCODING_HEADER(state, ptr) \ 659 header_token(state->token, 1, state->token_buffer, ptr, (char *) 0, 0) 660 661 /* 662 * Do content-transfer-encoding header. Never set the encoding domain to 663 * something other than 7bit, 8bit or binary, even if we don't recognize 664 * the input. 665 */ 666 cp = STR(state->output_buffer) + strlen(header_info->name) + 1; 667 if (PARSE_CONTENT_ENCODING_HEADER(state, &cp) > 0 668 && state->token[0].type == HEADER_TOK_TOKEN) { 669 for (cmp = mime_encoding_map; cmp->name != 0; cmp++) { 670 if (strcasecmp(state->token[0].u.value, cmp->name) == 0) { 671 state->curr_encoding = cmp->encoding; 672 state->curr_domain = cmp->domain; 673 break; 674 } 675 } 676 } 677 } 678 679 /* mime_state_enc_name - encoding to printable form */ 680 681 static const char *mime_state_enc_name(int encoding) 682 { 683 const MIME_ENCODING *cmp; 684 685 for (cmp = mime_encoding_map; cmp->name != 0; cmp++) 686 if (encoding == cmp->encoding) 687 return (cmp->name); 688 return ("unknown"); 689 } 690 691 /* mime_state_downgrade - convert 8-bit data to quoted-printable */ 692 693 static void mime_state_downgrade(MIME_STATE *state, int rec_type, 694 const char *text, ssize_t len) 695 { 696 static char hexchars[] = "0123456789ABCDEF"; 697 const unsigned char *cp; 698 int ch; 699 700 #define QP_ENCODE(buffer, ch) { \ 701 VSTRING_ADDCH(buffer, '='); \ 702 VSTRING_ADDCH(buffer, hexchars[(ch >> 4) & 0xff]); \ 703 VSTRING_ADDCH(buffer, hexchars[ch & 0xf]); \ 704 } 705 706 /* 707 * Insert a soft line break when the output reaches a critical length 708 * before we reach a hard line break. 709 */ 710 for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) { 711 /* Critical length before hard line break. */ 712 if (LEN(state->output_buffer) > 72) { 713 VSTRING_ADDCH(state->output_buffer, '='); 714 VSTRING_TERMINATE(state->output_buffer); 715 BODY_OUT(state, REC_TYPE_NORM, 716 STR(state->output_buffer), 717 LEN(state->output_buffer)); 718 VSTRING_RESET(state->output_buffer); 719 } 720 /* Append the next character. */ 721 ch = *cp; 722 if ((ch < 32 && ch != '\t') || ch == '=' || ch > 126) { 723 QP_ENCODE(state->output_buffer, ch); 724 } else { 725 VSTRING_ADDCH(state->output_buffer, ch); 726 } 727 } 728 729 /* 730 * Flush output after a hard line break (i.e. the end of a REC_TYPE_NORM 731 * record). Fix trailing whitespace as per the RFC: in the worst case, 732 * the output length will grow from 73 characters to 75 characters. 733 */ 734 if (rec_type == REC_TYPE_NORM) { 735 if (LEN(state->output_buffer) > 0 736 && ((ch = END(state->output_buffer)[-1]) == ' ' || ch == '\t')) { 737 vstring_truncate(state->output_buffer, 738 LEN(state->output_buffer) - 1); 739 QP_ENCODE(state->output_buffer, ch); 740 } 741 VSTRING_TERMINATE(state->output_buffer); 742 BODY_OUT(state, REC_TYPE_NORM, 743 STR(state->output_buffer), 744 LEN(state->output_buffer)); 745 VSTRING_RESET(state->output_buffer); 746 } 747 } 748 749 /* mime_state_update - update MIME state machine */ 750 751 int mime_state_update(MIME_STATE *state, int rec_type, 752 const char *text, ssize_t len) 753 { 754 int input_is_text = (rec_type == REC_TYPE_NORM 755 || rec_type == REC_TYPE_CONT); 756 MIME_STACK *sp; 757 const HEADER_OPTS *header_info; 758 const unsigned char *cp; 759 760 #define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) do { \ 761 state->prev_rec_type = rec_type; \ 762 return (state->err_flags); \ 763 } while (0) 764 765 /* 766 * Be sure to flush any partial output line that might still be buffered 767 * up before taking any other "end of input" actions. 768 */ 769 if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT) 770 mime_state_update(state, REC_TYPE_NORM, "", 0); 771 772 /* 773 * This message state machine is kept simple for the sake of robustness. 774 * Standards evolve over time, and we want to be able to correctly 775 * process messages that are not yet defined. This state machine knows 776 * about headers and bodies, understands that multipart/whatever has 777 * multiple body parts with a header and body, and that message/whatever 778 * has message headers at the start of a body part. 779 */ 780 switch (state->curr_state) { 781 782 /* 783 * First, deal with header information that we have accumulated from 784 * previous input records. Discard text that does not fit in a header 785 * buffer. Our limit is quite generous; Sendmail will refuse mail 786 * with only 32kbyte in all the message headers combined. 787 */ 788 case MIME_STATE_PRIMARY: 789 case MIME_STATE_MULTIPART: 790 case MIME_STATE_NESTED: 791 if (LEN(state->output_buffer) > 0) { 792 if (input_is_text) { 793 if (state->prev_rec_type == REC_TYPE_CONT) { 794 if (LEN(state->output_buffer) < var_header_limit) { 795 vstring_strncat(state->output_buffer, text, len); 796 } else { 797 if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) 798 REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER, 799 state->output_buffer); 800 } 801 SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); 802 } 803 if (IS_SPACE_TAB(*text)) { 804 if (LEN(state->output_buffer) < var_header_limit) { 805 vstring_strcat(state->output_buffer, "\n"); 806 vstring_strncat(state->output_buffer, text, len); 807 } else { 808 if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) 809 REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER, 810 state->output_buffer); 811 } 812 SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); 813 } 814 } 815 816 /* 817 * The input is (the beginning of) another message header, or is 818 * not a message header, or is not even a text record. With no 819 * more input to append to this saved header, do output 820 * processing and reset the saved header buffer. Hold on to the 821 * content transfer encoding header if we have to do a 8->7 822 * transformation, because the proper information depends on the 823 * content type header: message and multipart require a domain, 824 * leaf entities have either a transformation or a domain. 825 */ 826 if (LEN(state->output_buffer) > 0) { 827 header_info = header_opts_find(STR(state->output_buffer)); 828 if (!(state->static_flags & MIME_OPT_DISABLE_MIME) 829 && header_info != 0) { 830 if (header_info->type == HDR_CONTENT_TYPE) 831 mime_state_content_type(state, header_info); 832 if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING) 833 mime_state_content_encoding(state, header_info); 834 } 835 if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0 836 && (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) { 837 for (cp = CU_CHAR_PTR(STR(state->output_buffer)); 838 cp < CU_CHAR_PTR(END(state->output_buffer)); cp++) 839 if (*cp & 0200) { 840 REPORT_ERROR_BUF(state, MIME_ERR_8BIT_IN_HEADER, 841 state->output_buffer); 842 break; 843 } 844 } 845 /* Output routine is explicitly allowed to change the data. */ 846 if (header_info == 0 847 || header_info->type != HDR_CONTENT_TRANSFER_ENCODING 848 || (state->static_flags & MIME_OPT_DOWNGRADE) == 0 849 || state->curr_domain == MIME_ENC_7BIT) 850 HEAD_OUT(state, header_info, len); 851 state->prev_rec_type = 0; 852 VSTRING_RESET(state->output_buffer); 853 } 854 } 855 856 /* 857 * With past header information moved out of the way, proceed with a 858 * clean slate. 859 */ 860 if (input_is_text) { 861 ssize_t header_len; 862 863 /* 864 * See if this input is (the beginning of) a message header. 865 * 866 * Normalize obsolete "name space colon" syntax to "name colon". 867 * Things would be too confusing otherwise. 868 * 869 * Don't assume that the input is null terminated. 870 */ 871 if ((header_len = is_header_buf(text, len)) > 0) { 872 vstring_strncpy(state->output_buffer, text, header_len); 873 for (text += header_len, len -= header_len; 874 len > 0 && IS_SPACE_TAB(*text); 875 text++, len--) 876 /* void */ ; 877 vstring_strncat(state->output_buffer, text, len); 878 SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); 879 } 880 } 881 882 /* 883 * This input terminates a block of message headers. When converting 884 * 8-bit to 7-bit mail, this is the right place to emit the correct 885 * content-transfer-encoding header. With message or multipart we 886 * specify 7bit, with leaf entities we specify quoted-printable. 887 * 888 * We're not going to convert non-text data into base 64. If they send 889 * arbitrary binary data as 8-bit text, then the data is already 890 * broken beyond recovery, because the Postfix SMTP server sanitizes 891 * record boundaries, treating broken record boundaries as CRLF. 892 * 893 * Clear the output buffer, we will need it for storage of the 894 * conversion result. 895 */ 896 if ((state->static_flags & MIME_OPT_DOWNGRADE) 897 && state->curr_domain != MIME_ENC_7BIT) { 898 if ((state->curr_ctype == MIME_CTYPE_MESSAGE 899 && state->curr_stype != MIME_STYPE_GLOBAL) 900 || state->curr_ctype == MIME_CTYPE_MULTIPART) 901 cp = CU_CHAR_PTR("7bit"); 902 else 903 cp = CU_CHAR_PTR("quoted-printable"); 904 vstring_sprintf(state->output_buffer, 905 "Content-Transfer-Encoding: %s", cp); 906 HEAD_OUT(state, (HEADER_OPTS *) 0, len); 907 VSTRING_RESET(state->output_buffer); 908 } 909 910 /* 911 * This input terminates a block of message headers. Call the 912 * optional header end routine at the end of the first header block. 913 */ 914 if (state->curr_state == MIME_STATE_PRIMARY && state->head_end) 915 state->head_end(state->app_context); 916 917 /* 918 * This is the right place to check if the sender specified an 919 * appropriate identity encoding (7bit, 8bit, binary) for multipart 920 * and for message. 921 */ 922 if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) { 923 if (state->curr_ctype == MIME_CTYPE_MESSAGE) { 924 if (state->curr_stype == MIME_STYPE_PARTIAL 925 || state->curr_stype == MIME_STYPE_EXTERN_BODY) { 926 if (state->curr_domain != MIME_ENC_7BIT) 927 REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, 928 mime_state_enc_name(state->curr_encoding)); 929 } 930 /* EAI: message/global allows non-identity encoding. */ 931 else if (state->curr_stype == MIME_STYPE_RFC822) { 932 if (state->curr_encoding != state->curr_domain) 933 REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, 934 mime_state_enc_name(state->curr_encoding)); 935 } 936 } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) { 937 if (state->curr_encoding != state->curr_domain) 938 REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, 939 mime_state_enc_name(state->curr_encoding)); 940 } 941 } 942 943 /* 944 * Find out if the next body starts with its own message headers. In 945 * agressive mode, examine headers of partial and external-body 946 * messages. Otherwise, treat such headers as part of the "body". Set 947 * the proper encoding information for the multipart prolog. 948 * 949 * XXX We parse headers inside message/* content even when the encoding 950 * is invalid (encoding != domain). With base64 we won't recognize 951 * any headers, and with quoted-printable we won't recognize MIME 952 * boundary strings, but the MIME processor will still resynchronize 953 * when it runs into the higher-level boundary string at the end of 954 * the message/* content. Although we will treat some headers as body 955 * text, we will still do a better job than if we were treating the 956 * entire message/* content as body text. 957 * 958 * XXX This changes state to MIME_STATE_NESTED and then outputs a body 959 * line, so that the body offset is not properly reset. 960 * 961 * Don't assume that the input is null terminated. 962 */ 963 if (input_is_text) { 964 if (len == 0) { 965 state->body_offset = 0; /* XXX */ 966 if (state->curr_ctype == MIME_CTYPE_MESSAGE) { 967 if (state->curr_stype == MIME_STYPE_RFC822) 968 SET_MIME_STATE(state, MIME_STATE_NESTED, 969 MIME_CTYPE_TEXT, MIME_STYPE_PLAIN, 970 MIME_ENC_7BIT, MIME_ENC_7BIT); 971 else if (state->curr_stype == MIME_STYPE_GLOBAL 972 && ((state->static_flags & MIME_OPT_DOWNGRADE) == 0 973 || state->curr_domain == MIME_ENC_7BIT)) 974 /* XXX EAI: inspect encoded message/global. */ 975 SET_MIME_STATE(state, MIME_STATE_NESTED, 976 MIME_CTYPE_TEXT, MIME_STYPE_PLAIN, 977 MIME_ENC_7BIT, MIME_ENC_7BIT); 978 else 979 SET_CURR_STATE(state, MIME_STATE_BODY); 980 } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) { 981 SET_MIME_STATE(state, MIME_STATE_BODY, 982 MIME_CTYPE_OTHER, MIME_STYPE_OTHER, 983 MIME_ENC_7BIT, MIME_ENC_7BIT); 984 } else { 985 SET_CURR_STATE(state, MIME_STATE_BODY); 986 } 987 } 988 989 /* 990 * Invalid input. Force output of one blank line and jump to the 991 * body state, leaving all other state alone. 992 * 993 * We don't break legitimate mail by inserting a blank line 994 * separator between primary headers and a non-empty body. Many 995 * MTA's don't even record the presence or absence of this 996 * separator, nor does the Milter protocol pass it on to Milter 997 * applications. 998 * 999 * XXX We don't insert a blank line separator into attachments, to 1000 * avoid breaking digital signatures. Postfix shall not do a 1001 * worse mail delivery job than MTAs that can't even parse MIME. 1002 * We switch to body state anyway, to avoid treating body text as 1003 * header text, and mis-interpreting or truncating it. The code 1004 * below for initial From_ lines is for educational purposes. 1005 * 1006 * Sites concerned about MIME evasion can use a MIME normalizer. 1007 * Postfix has a different mission. 1008 */ 1009 else { 1010 if (msg_verbose) 1011 msg_info("garbage in %s header", 1012 state->curr_state == MIME_STATE_MULTIPART ? "multipart" : 1013 state->curr_state == MIME_STATE_PRIMARY ? "primary" : 1014 state->curr_state == MIME_STATE_NESTED ? "nested" : 1015 "other"); 1016 switch (state->curr_state) { 1017 case MIME_STATE_PRIMARY: 1018 BODY_OUT(state, REC_TYPE_NORM, "", 0); 1019 SET_CURR_STATE(state, MIME_STATE_BODY); 1020 break; 1021 #if 0 1022 case MIME_STATE_NESTED: 1023 if (state->body_offset <= 1 1024 && rec_type == REC_TYPE_NORM 1025 && len > 7 1026 && (strncmp(text + (*text == '>'), "From ", 5) == 0 1027 || strncmp(text, "=46rom ", 7) == 0)) 1028 break; 1029 /* FALLTHROUGH */ 1030 #endif 1031 default: 1032 SET_CURR_STATE(state, MIME_STATE_BODY); 1033 break; 1034 } 1035 } 1036 } 1037 1038 /* 1039 * This input is not text. Go to body state, unconditionally. 1040 */ 1041 else { 1042 SET_CURR_STATE(state, MIME_STATE_BODY); 1043 } 1044 /* FALLTHROUGH */ 1045 1046 /* 1047 * Body text. Look for message boundaries, and recover from missing 1048 * boundary strings. Missing boundaries can happen in agressive mode 1049 * with text/rfc822-headers or with message/partial. Ignore non-space 1050 * cruft after --boundary or --boundary--, because some MUAs do, and 1051 * because only perverse software would take advantage of this to 1052 * escape detection. We have to ignore trailing cruft anyway, because 1053 * our saved copy of the boundary string may have been truncated for 1054 * safety reasons. 1055 * 1056 * Optionally look for 8-bit data in content that was announced as, or 1057 * that defaults to, 7-bit. Unfortunately, we cannot turn this on by 1058 * default. Majordomo sends requests for approval that do not 1059 * propagate the MIME information from the enclosed message to the 1060 * message headers of the approval request. 1061 * 1062 * Set the proper state information after processing a message boundary 1063 * string. 1064 * 1065 * Don't look for boundary strings at the start of a continued record. 1066 * 1067 * Don't assume that the input is null terminated. 1068 */ 1069 case MIME_STATE_BODY: 1070 if (input_is_text) { 1071 if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0 1072 && state->curr_encoding == MIME_ENC_7BIT 1073 && (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) { 1074 for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) 1075 if (*cp & 0200) { 1076 REPORT_ERROR_LEN(state, MIME_ERR_8BIT_IN_7BIT_BODY, 1077 text, len); 1078 break; 1079 } 1080 } 1081 if (state->stack && state->prev_rec_type != REC_TYPE_CONT 1082 && len > 2 && text[0] == '-' && text[1] == '-') { 1083 for (sp = state->stack; sp != 0; sp = sp->next) { 1084 if (len >= 2 + sp->bound_len && 1085 strncmp(text + 2, sp->boundary, sp->bound_len) == 0) { 1086 while (sp != state->stack) 1087 mime_state_pop(state); 1088 if (len >= 4 + sp->bound_len && 1089 strncmp(text + 2 + sp->bound_len, "--", 2) == 0) { 1090 mime_state_pop(state); 1091 SET_MIME_STATE(state, MIME_STATE_BODY, 1092 MIME_CTYPE_OTHER, MIME_STYPE_OTHER, 1093 MIME_ENC_7BIT, MIME_ENC_7BIT); 1094 } else { 1095 SET_MIME_STATE(state, MIME_STATE_MULTIPART, 1096 sp->def_ctype, sp->def_stype, 1097 MIME_ENC_7BIT, MIME_ENC_7BIT); 1098 } 1099 break; 1100 } 1101 } 1102 } 1103 /* Put last for consistency with header output routine. */ 1104 if ((state->static_flags & MIME_OPT_DOWNGRADE) 1105 && state->curr_domain != MIME_ENC_7BIT) 1106 mime_state_downgrade(state, rec_type, text, len); 1107 else 1108 BODY_OUT(state, rec_type, text, len); 1109 } 1110 1111 /* 1112 * The input is not a text record. Inform the application that this 1113 * is the last opportunity to send any pending output. 1114 */ 1115 else { 1116 if (state->body_end) 1117 state->body_end(state->app_context); 1118 } 1119 SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); 1120 1121 /* 1122 * Oops. This can't happen. 1123 */ 1124 default: 1125 msg_panic("mime_state_update: unknown state: %d", state->curr_state); 1126 } 1127 } 1128 1129 /* 1130 * Mime error to (DSN, text) mapping. Order matters; more serious errors 1131 * must precede less serious errors, because the error-to-text conversion 1132 * can report only one error. 1133 */ 1134 static const MIME_STATE_DETAIL mime_err_detail[] = { 1135 MIME_ERR_NESTING, "5.6.0", "MIME nesting exceeds safety limit", 1136 MIME_ERR_TRUNC_HEADER, "5.6.0", "message header length exceeds safety limit", 1137 MIME_ERR_8BIT_IN_HEADER, "5.6.0", "improper use of 8-bit data in message header", 1138 MIME_ERR_8BIT_IN_7BIT_BODY, "5.6.0", "improper use of 8-bit data in message body", 1139 MIME_ERR_ENCODING_DOMAIN, "5.6.0", "invalid message/* or multipart/* encoding domain", 1140 0, 1141 }; 1142 1143 /* mime_state_error - error code to string */ 1144 1145 const char *mime_state_error(int error_code) 1146 { 1147 const MIME_STATE_DETAIL *mp; 1148 1149 if (error_code == 0) 1150 msg_panic("mime_state_error: there is no error"); 1151 for (mp = mime_err_detail; mp->code; mp++) 1152 if (mp->code & error_code) 1153 return (mp->text); 1154 msg_panic("mime_state_error: unknown error code %d", error_code); 1155 } 1156 1157 /* mime_state_detail - error code to table entry with assorted data */ 1158 1159 const MIME_STATE_DETAIL *mime_state_detail(int error_code) 1160 { 1161 const MIME_STATE_DETAIL *mp; 1162 1163 if (error_code == 0) 1164 msg_panic("mime_state_detail: there is no error"); 1165 for (mp = mime_err_detail; mp->code; mp++) 1166 if (mp->code & error_code) 1167 return (mp); 1168 msg_panic("mime_state_detail: unknown error code %d", error_code); 1169 } 1170 1171 #ifdef TEST 1172 1173 #include <stdlib.h> 1174 #include <stringops.h> 1175 #include <vstream.h> 1176 #include <msg_vstream.h> 1177 #include <rec_streamlf.h> 1178 1179 /* 1180 * Stress test the REC_TYPE_CONT/NORM handling, but don't break header 1181 * labels. 1182 */ 1183 /*#define REC_LEN 40*/ 1184 1185 #define REC_LEN 1024 1186 1187 static void head_out(void *context, int class, const HEADER_OPTS *unused_info, 1188 VSTRING *buf, off_t offset) 1189 { 1190 VSTREAM *stream = (VSTREAM *) context; 1191 1192 vstream_fprintf(stream, "%s %ld\t|%s\n", 1193 class == MIME_HDR_PRIMARY ? "MAIN" : 1194 class == MIME_HDR_MULTIPART ? "MULT" : 1195 class == MIME_HDR_NESTED ? "NEST" : 1196 "ERROR", (long) offset, STR(buf)); 1197 } 1198 1199 static void head_end(void *context) 1200 { 1201 VSTREAM *stream = (VSTREAM *) context; 1202 1203 vstream_fprintf(stream, "HEADER END\n"); 1204 } 1205 1206 static void body_out(void *context, int rec_type, const char *buf, ssize_t len, 1207 off_t offset) 1208 { 1209 VSTREAM *stream = (VSTREAM *) context; 1210 1211 vstream_fprintf(stream, "BODY %c %ld\t|", rec_type, (long) offset); 1212 vstream_fwrite(stream, buf, len); 1213 if (rec_type == REC_TYPE_NORM) 1214 VSTREAM_PUTC('\n', stream); 1215 } 1216 1217 static void body_end(void *context) 1218 { 1219 VSTREAM *stream = (VSTREAM *) context; 1220 1221 vstream_fprintf(stream, "BODY END\n"); 1222 } 1223 1224 static void err_print(void *unused_context, int err_flag, 1225 const char *text, ssize_t len) 1226 { 1227 msg_warn("%s: %.*s", mime_state_error(err_flag), 1228 len < 100 ? (int) len : 100, text); 1229 } 1230 1231 int var_header_limit = 2000; 1232 int var_mime_maxdepth = 20; 1233 int var_mime_bound_len = 2000; 1234 char *var_drop_hdrs = DEF_DROP_HDRS; 1235 1236 int main(int unused_argc, char **argv) 1237 { 1238 int rec_type; 1239 int last = 0; 1240 VSTRING *buf; 1241 MIME_STATE *state; 1242 int err; 1243 1244 /* 1245 * Initialize. 1246 */ 1247 #define MIME_OPTIONS \ 1248 (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \ 1249 | MIME_OPT_REPORT_8BIT_IN_HEADER \ 1250 | MIME_OPT_REPORT_ENCODING_DOMAIN \ 1251 | MIME_OPT_REPORT_TRUNC_HEADER \ 1252 | MIME_OPT_REPORT_NESTING \ 1253 | MIME_OPT_DOWNGRADE) 1254 1255 msg_vstream_init(basename(argv[0]), VSTREAM_OUT); 1256 msg_verbose = 1; 1257 buf = vstring_alloc(10); 1258 state = mime_state_alloc(MIME_OPTIONS, 1259 head_out, head_end, 1260 body_out, body_end, 1261 err_print, 1262 (void *) VSTREAM_OUT); 1263 1264 /* 1265 * Main loop. 1266 */ 1267 do { 1268 rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN); 1269 VSTRING_TERMINATE(buf); 1270 err = mime_state_update(state, last = rec_type, STR(buf), LEN(buf)); 1271 vstream_fflush(VSTREAM_OUT); 1272 } while (rec_type > 0); 1273 1274 /* 1275 * Error reporting. 1276 */ 1277 if (err & MIME_ERR_TRUNC_HEADER) 1278 msg_warn("message header length exceeds safety limit"); 1279 if (err & MIME_ERR_NESTING) 1280 msg_warn("MIME nesting exceeds safety limit"); 1281 if (err & MIME_ERR_8BIT_IN_HEADER) 1282 msg_warn("improper use of 8-bit data in message header"); 1283 if (err & MIME_ERR_8BIT_IN_7BIT_BODY) 1284 msg_warn("improper use of 8-bit data in message body"); 1285 if (err & MIME_ERR_ENCODING_DOMAIN) 1286 msg_warn("improper message/* or multipart/* encoding domain"); 1287 1288 /* 1289 * Cleanup. 1290 */ 1291 mime_state_free(state); 1292 vstring_free(buf); 1293 exit(0); 1294 } 1295 1296 #endif 1297