http_mime::http_mime(const char* boundary, const char* local_charset /* = "gb2312" */) { static const char ctype_pre[] = "Content-Type: multipart/form-data; boundary="; if (boundary == NULL || strlen(boundary) < 2) { logger_error("boundary invalid"); boundary = NULL; mime_state_ = NULL; save_path_ = NULL; return; } // HTTP 的 MIME 格式与 邮件的 MIME 的分隔符有所不同, // HTTP 的分隔符要比邮件分隔符多两个 '-',而目前 HTTP // 的 MIME 解析器用的是邮件的 MIME 解析器,目前邮件的 // MIME 解析器会自动加上两个 '-' 前缀,所以需要去掉 // 开头的两个 '-' if (*boundary == '-') boundary++; if (*boundary == '-') boundary++; boundary_ = acl_mystrdup(boundary); if (local_charset && *local_charset) safe_snprintf(local_charset_, sizeof(local_charset_), "%s", local_charset); else local_charset_[0] = 0; decode_on_ = true; save_path_ = NULL; mime_state_ = mime_state_alloc(); // 为了使用邮件的 mime 解析器,需要模拟出一个头部字段 mime_state_update(mime_state_, ctype_pre, sizeof(ctype_pre) - 1); size_t len = strlen(boundary); mime_state_update(mime_state_, boundary, len); mime_state_update(mime_state_, "\r\n\r\n", 4); // 因为该头作为解析器的主头是额外加进去的,所以会造成实际的偏移量, // 通过 off_ 来进行偏移量补偿 off_ = 0 - (sizeof(ctype_pre) - 1 + len + 4); parsed_ = false; }
int main(int argc, char **argv) { int rec_type; VSTRING *buf; int err; MIME_STATE *mime_state; HBC_TEST_CONTEXT context; static HBC_CALL_BACKS call_backs[1] = { log_cb, /* logger */ out_cb, /* prepend */ }; /* * Sanity check. */ if (argc != 5) msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]); /* * Initialize. */ #define MIME_OPTIONS \ (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \ | MIME_OPT_REPORT_8BIT_IN_HEADER \ | MIME_OPT_REPORT_ENCODING_DOMAIN \ | MIME_OPT_REPORT_TRUNC_HEADER \ | MIME_OPT_REPORT_NESTING \ | MIME_OPT_DOWNGRADE) msg_vstream_init(basename(argv[0]), VSTREAM_OUT); buf = vstring_alloc(10); mime_state = mime_state_alloc(MIME_OPTIONS, head_out, head_end, body_out, body_end, err_print, (void *) &context); context.header_checks = hbc_header_checks_create("header_checks", argv[1], "mime_header_checks", argv[2], "nested_header_checks", argv[3], call_backs); context.body_checks = hbc_body_checks_create("body_checks", argv[4], call_backs); context.buf = vstring_alloc(100); context.fp = VSTREAM_OUT; context.queueid = "test-queueID"; context.recno = 0; /* * Main loop. */ do { rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN); VSTRING_TERMINATE(buf); err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf)); vstream_fflush(VSTREAM_OUT); } while (rec_type > 0); /* * Error reporting. */ if (err & MIME_ERR_TRUNC_HEADER) msg_warn("message header length exceeds safety limit"); if (err & MIME_ERR_NESTING) msg_warn("MIME nesting exceeds safety limit"); if (err & MIME_ERR_8BIT_IN_HEADER) msg_warn("improper use of 8-bit data in message header"); if (err & MIME_ERR_8BIT_IN_7BIT_BODY) msg_warn("improper use of 8-bit data in message body"); if (err & MIME_ERR_ENCODING_DOMAIN) msg_warn("improper message/* or multipart/* encoding domain"); #ifdef __APPLE_OS_X_SERVER__ if (err & MIME_ERR_BODY_TOO_LARGE) msg_warn("MIME body part too large"); #endif /* * Cleanup. */ if (context.header_checks) hbc_header_checks_free(context.header_checks); if (context.body_checks) hbc_body_checks_free(context.body_checks); vstring_free(context.buf); mime_state_free(mime_state); vstring_free(buf); exit(0); }
static void enqueue(const int flags, const char *encoding, const char *dsn_envid, int dsn_ret, int dsn_notify, const char *rewrite_context, const char *sender, const char *full_name, char **recipients) { VSTRING *buf; VSTREAM *dst; char *saved_sender; char **cpp; int type; char *start; int skip_from_; TOK822 *tree; TOK822 *tp; int rcpt_count = 0; enum { STRIP_CR_DUNNO, STRIP_CR_DO, STRIP_CR_DONT, STRIP_CR_ERROR } strip_cr; MAIL_STREAM *handle; VSTRING *postdrop_command; uid_t uid = getuid(); int status; int naddr; int prev_type; MIME_STATE *mime_state = 0; SM_STATE state; int mime_errs; const char *errstr; int addr_count; int level; static NAME_CODE sm_fix_eol_table[] = { SM_FIX_EOL_ALWAYS, STRIP_CR_DO, SM_FIX_EOL_STRICT, STRIP_CR_DUNNO, SM_FIX_EOL_NEVER, STRIP_CR_DONT, 0, STRIP_CR_ERROR, }; /* * Access control is enforced in the postdrop command. The code here * merely produces a more user-friendly interface. */ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Initialize. */ buf = vstring_alloc(100); /* * Stop run-away process accidents by limiting the queue file size. This * is not a defense against DOS attack. */ if (var_message_limit > 0 && get_file_limit() > var_message_limit) set_file_limit((off_t) var_message_limit); /* * The sender name is provided by the user. In principle, the mail pickup * service could deduce the sender name from queue file ownership, but: * pickup would not be able to run chrooted, and it may not be desirable * to use login names at all. */ if (sender != 0) { VSTRING_RESET(buf); VSTRING_TERMINATE(buf); tree = tok822_parse(sender); for (naddr = 0, tp = tree; tp != 0; tp = tp->next) if (tp->type == TOK822_ADDR && naddr++ == 0) tok822_internalize(buf, tp->head, TOK822_STR_DEFL); tok822_free_tree(tree); saved_sender = mystrdup(STR(buf)); if (naddr > 1) msg_warn("-f option specified malformed sender: %s", sender); } else { if ((sender = username()) == 0) msg_fatal_status(EX_OSERR, "no login name found for user ID %lu", (unsigned long) uid); saved_sender = mystrdup(sender); } /* * Let the postdrop command open the queue file for us, and sanity check * the content. XXX Make postdrop a manifest constant. */ errno = 0; postdrop_command = vstring_alloc(1000); vstring_sprintf(postdrop_command, "%s/postdrop -r", var_command_dir); for (level = 0; level < msg_verbose; level++) vstring_strcat(postdrop_command, " -v"); if ((handle = mail_stream_command(STR(postdrop_command))) == 0) msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m", saved_sender, (long) uid, STR(postdrop_command)); vstring_free(postdrop_command); dst = handle->stream; /* * First, write envelope information to the output stream. * * For sendmail compatibility, parse each command-line recipient as if it * were an RFC 822 message header; some MUAs specify comma-separated * recipient lists; and some MUAs even specify "word word <address>". * * Sort-uniq-ing the recipient list is done after address canonicalization, * before recipients are written to queue file. That's cleaner than * having the queue manager nuke duplicate recipient status records. * * XXX Should limit the size of envelope records. * * With "sendmail -N", instead of a per-message NOTIFY record we store one * per recipient so that we can simplify the implementation somewhat. */ if (dsn_envid) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_DSN_ENVID, dsn_envid); if (dsn_ret) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_RET, dsn_ret); rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_RWR_CONTEXT, rewrite_context); if (full_name || (full_name = fullname()) != 0) rec_fputs(dst, REC_TYPE_FULL, full_name); rec_fputs(dst, REC_TYPE_FROM, saved_sender); if (verp_delims && *saved_sender == 0) msg_fatal_status(EX_USAGE, "%s(%ld): -V option requires non-null sender address", saved_sender, (long) uid); if (encoding) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding); if (DEL_REQ_TRACE_FLAGS(flags)) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, DEL_REQ_TRACE_FLAGS(flags)); if (verp_delims) rec_fputs(dst, REC_TYPE_VERP, verp_delims); if (recipients) { for (cpp = recipients; *cpp != 0; cpp++) { tree = tok822_parse(*cpp); for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { if (tp->type == TOK822_ADDR) { tok822_internalize(buf, tp->head, TOK822_STR_DEFL); if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; ++addr_count; } } tok822_free_tree(tree); if (addr_count == 0) { if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } } } /* * Append the message contents to the queue file. Write chunks of at most * 1kbyte. Internally, we use different record types for data ending in * LF and for data that doesn't, so we can actually be binary transparent * for local mail. Unfortunately, SMTP has no record continuation * convention, so there is no guarantee that arbitrary data will be * delivered intact via SMTP. Strip leading From_ lines. For the benefit * of UUCP environments, also get rid of leading >>>From_ lines. */ rec_fputs(dst, REC_TYPE_MESG, ""); if (DEL_REQ_TRACE_ONLY(flags) != 0) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "%s(%ld): -t option cannot be used with -bv", saved_sender, (long) uid); if (*saved_sender) rec_fprintf(dst, REC_TYPE_NORM, "From: %s", saved_sender); rec_fprintf(dst, REC_TYPE_NORM, "Subject: probe"); if (recipients) { rec_fprintf(dst, REC_TYPE_CONT, "To:"); for (cpp = recipients; *cpp != 0; cpp++) { rec_fprintf(dst, REC_TYPE_NORM, " %s%s", *cpp, cpp[1] ? "," : ""); } } } else { /* * Initialize the MIME processor and set up the callback context. */ if (flags & SM_FLAG_XRCPT) { state.dst = dst; state.recipients = argv_alloc(2); state.resent_recip = argv_alloc(2); state.resent = 0; state.saved_sender = saved_sender; state.uid = uid; state.temp = vstring_alloc(10); mime_state = mime_state_alloc(MIME_OPT_DISABLE_MIME | MIME_OPT_REPORT_TRUNC_HEADER, output_header, (MIME_STATE_ANY_END) 0, output_text, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) &state); } /* * Process header/body lines. */ skip_from_ = 1; strip_cr = name_code(sm_fix_eol_table, NAME_CODE_FLAG_STRICT_CASE, var_sm_fix_eol); if (strip_cr == STRIP_CR_ERROR) msg_fatal_status(EX_USAGE, "invalid %s value: %s", VAR_SM_FIX_EOL, var_sm_fix_eol); for (prev_type = 0; (type = rec_streamlf_get(VSTREAM_IN, buf, var_line_limit)) != REC_TYPE_EOF; prev_type = type) { if (strip_cr == STRIP_CR_DUNNO && type == REC_TYPE_NORM) { if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') strip_cr = STRIP_CR_DO; else strip_cr = STRIP_CR_DONT; } if (skip_from_) { if (type == REC_TYPE_NORM) { start = STR(buf); if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) continue; } skip_from_ = 0; } if (strip_cr == STRIP_CR_DO && type == REC_TYPE_NORM) while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') vstring_truncate(buf, VSTRING_LEN(buf) - 1); if ((flags & SM_FLAG_AEOF) && prev_type != REC_TYPE_CONT && VSTRING_LEN(buf) == 1 && *STR(buf) == '.') break; if (mime_state) { mime_errs = mime_state_update(mime_state, type, STR(buf), VSTRING_LEN(buf)); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); } else { if (REC_PUT_BUF(dst, type, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); } } } /* * Finish MIME processing. We need a final mime_state_update() call in * order to flush text that is still buffered. That can happen when the * last line did not end in newline. */ if (mime_state) { mime_errs = mime_state_update(mime_state, REC_TYPE_EOF, "", 0); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); mime_state = mime_state_free(mime_state); } /* * Append recipient addresses that were extracted from message headers. */ rec_fputs(dst, REC_TYPE_XTRA, ""); if (flags & SM_FLAG_XRCPT) { for (cpp = state.resent ? state.resent_recip->argv : state.recipients->argv; *cpp; cpp++) { if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (rec_put(dst, REC_TYPE_RCPT, *cpp, strlen(*cpp)) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } argv_free(state.recipients); argv_free(state.resent_recip); vstring_free(state.temp); } if (rcpt_count == 0) msg_fatal_status(EX_USAGE, (flags & SM_FLAG_XRCPT) ? "%s(%ld): No recipient addresses found in message header" : "%s(%ld): Recipient addresses must be specified on" " the command line or via the -t option", saved_sender, (long) uid); /* * Identify the end of the queue file. */ rec_fputs(dst, REC_TYPE_END, ""); /* * Make sure that the message makes it to the file system. Once we have * terminated with successful exit status we cannot lose the message due * to "frivolous reasons". If all goes well, prevent the run-time error * handler from removing the file. */ if (vstream_ferror(VSTREAM_IN)) msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m", saved_sender, (long) uid); if ((status = mail_stream_finish(handle, (VSTRING *) 0)) != 0) msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE : (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL : EX_UNAVAILABLE, "%s(%ld): %s", saved_sender, (long) uid, cleanup_strerror(status)); /* * Don't leave them in the dark. */ if (DEL_REQ_TRACE_FLAGS(flags)) { vstream_printf("Mail Delivery Status Report will be mailed to <%s>.\n", saved_sender); vstream_fflush(VSTREAM_OUT); } /* * Cleanup. Not really necessary as we're about to exit, but good for * debugging purposes. */ vstring_free(buf); myfree(saved_sender); }
bool http_mime::update(const char* data, size_t len) { return mime_state_update(mime_state_, data, len) == 1 ? true : false; }
static int postmap_queries(VSTREAM *in, char **maps, const int map_count, const int postmap_flags, const int dict_flags) { int found = 0; VSTRING *keybuf = vstring_alloc(100); DICT **dicts; const char *map_name; const char *value; int n; /* * Sanity check. */ if (map_count <= 0) msg_panic("postmap_queries: bad map count"); /* * Prepare to open maps lazily. */ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); for (n = 0; n < map_count; n++) dicts[n] = 0; /* * Perform all queries. Open maps on the fly, to avoid opening unecessary * maps. */ if ((postmap_flags & POSTMAP_FLAG_HB_KEY) == 0) { while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { for (n = 0; n < map_count; n++) { if (dicts[n] == 0) dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? dict_open3(maps[n], map_name, O_RDONLY, dict_flags) : dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags)); if ((value = dict_get(dicts[n], STR(keybuf))) != 0) { if (*value == 0) { msg_warn("table %s:%s: key %s: empty string result is not allowed", dicts[n]->type, dicts[n]->name, STR(keybuf)); msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", dicts[n]->type, dicts[n]->name); } vstream_printf("%s %s\n", STR(keybuf), value); found = 1; break; } if (dicts[n]->error) msg_fatal("table %s:%s: query error: %m", dicts[n]->type, dicts[n]->name); } } } else { POSTMAP_KEY_STATE key_state; MIME_STATE *mime_state; int mime_errs = 0; /* * Bundle up the request and instantiate a MIME parsing engine. */ key_state.dicts = dicts; key_state.maps = maps; key_state.map_count = map_count; key_state.dict_flags = dict_flags; key_state.header_done = 0; key_state.found = 0; mime_state = mime_state_alloc((postmap_flags & POSTMAP_FLAG_MIME_KEY) ? 0 : MIME_OPT_DISABLE_MIME, (postmap_flags & POSTMAP_FLAG_HEADER_KEY) ? postmap_header : (MIME_STATE_HEAD_OUT) 0, (postmap_flags & POSTMAP_FLAG_FULL_KEY) ? (MIME_STATE_ANY_END) 0 : postmap_head_end, (postmap_flags & POSTMAP_FLAG_BODY_KEY) ? postmap_body : (MIME_STATE_BODY_OUT) 0, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) &key_state); /* * Process the input message. */ while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF && key_state.header_done == 0 && mime_errs == 0) mime_errs = mime_state_update(mime_state, REC_TYPE_NORM, STR(keybuf), LEN(keybuf)); /* * Flush the MIME engine output buffer and tidy up loose ends. */ if (mime_errs == 0) mime_errs = mime_state_update(mime_state, REC_TYPE_END, "", 0); if (mime_errs) msg_fatal("message format error: %s", mime_state_detail(mime_errs)->text); mime_state_free(mime_state); found = key_state.found; } if (found) vstream_fflush(VSTREAM_OUT); /* * Cleanup. */ for (n = 0; n < map_count; n++) if (dicts[n]) dict_close(dicts[n]); myfree((void *) dicts); vstring_free(keybuf); return (found); }
int mime_state_update(MIME_STATE *state, int rec_type, const char *text, int len) { int input_is_text = (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT); MIME_STACK *sp; HEADER_OPTS *header_info; const unsigned char *cp; #define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) do { \ state->prev_rec_type = rec_type; \ return (state->err_flags); \ } while (0) /* * Be sure to flush any partial output line that might still be buffered * up before taking any other "end of input" actions. */ if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT) mime_state_update(state, REC_TYPE_NORM, "", 0); /* * This message state machine is kept simple for the sake of robustness. * Standards evolve over time, and we want to be able to correctly * process messages that are not yet defined. This state machine knows * about headers and bodies, understands that multipart/whatever has * multiple body parts with a header and body, and that message/whatever * has message headers at the start of a body part. */ switch (state->curr_state) { /* * First, deal with header information that we have accumulated from * previous input records. Discard text that does not fit in a header * buffer. Our limit is quite generous; Sendmail will refuse mail * with only 32kbyte in all the message headers combined. */ case MIME_STATE_PRIMARY: case MIME_STATE_MULTIPART: case MIME_STATE_NESTED: if (LEN(state->output_buffer) > 0) { if (input_is_text) { if (state->prev_rec_type == REC_TYPE_CONT) { if (LEN(state->output_buffer) < var_header_limit) { vstring_strcat(state->output_buffer, text); } else { if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER, STR(state->output_buffer)); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } if (IS_SPACE_TAB(*text)) { if (LEN(state->output_buffer) < var_header_limit) { vstring_strcat(state->output_buffer, "\n"); vstring_strcat(state->output_buffer, text); } else { if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER, STR(state->output_buffer)); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } } /* * The input is (the beginning of) another message header, or is * not a message header, or is not even a text record. With no * more input to append to this saved header, do output * processing and reset the saved header buffer. Hold on to the * content transfer encoding header if we have to do a 8->7 * transformation, because the proper information depends on the * content type header: message and multipart require a domain, * leaf entities have either a transformation or a domain. */ if (LEN(state->output_buffer) > 0) { header_info = header_opts_find(STR(state->output_buffer)); if (!(state->static_flags & MIME_OPT_DISABLE_MIME) && header_info != 0) { if (header_info->type == HDR_CONTENT_TYPE) mime_state_content_type(state, header_info); if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING) mime_state_content_encoding(state, header_info); } if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0 && (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) { for (cp = CU_CHAR_PTR(STR(state->output_buffer)); cp < CU_CHAR_PTR(END(state->output_buffer)); cp++) if (*cp & 0200) { REPORT_ERROR(state, MIME_ERR_8BIT_IN_HEADER, STR(state->output_buffer)); break; } } /* Output routine is explicitly allowed to change the data. */ if (header_info == 0 || header_info->type != HDR_CONTENT_TRANSFER_ENCODING || (state->static_flags & MIME_OPT_DOWNGRADE) == 0 || state->curr_domain == MIME_ENC_7BIT) HEAD_OUT(state, header_info, len); state->prev_rec_type = 0; VSTRING_RESET(state->output_buffer); } } /* * With past header information moved out of the way, proceed with a * clean slate. */ if (input_is_text) { int header_len; /* * See if this input is (the beginning of) a message header. * Normalize obsolete "name space colon" syntax to "name colon". * Things would be too confusing otherwise. */ if ((header_len = is_header(text)) > 0) { vstring_strncpy(state->output_buffer, text, header_len); for (text += header_len; IS_SPACE_TAB(*text); text++) /* void */ ; vstring_strcat(state->output_buffer, text); SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } } /* * This input terminates a block of message headers. When converting * 8-bit to 7-bit mail, this is the right place to emit the correct * content-transfer-encoding header. With message or multipart we * specify 7bit, with leaf entities we specify quoted-printable. * * We're not going to convert non-text data into base 64. If they send * arbitrary binary data as 8-bit text, then the data is already * broken beyond recovery, because the Postfix SMTP server sanitizes * record boundaries, treating broken record boundaries as CRLF. * * Clear the output buffer, we will need it for storage of the * conversion result. */ if ((state->static_flags & MIME_OPT_DOWNGRADE) && state->curr_domain != MIME_ENC_7BIT) { if (state->curr_ctype == MIME_CTYPE_MESSAGE || state->curr_ctype == MIME_CTYPE_MULTIPART) cp = CU_CHAR_PTR("7bit"); else cp = CU_CHAR_PTR("quoted-printable"); vstring_sprintf(state->output_buffer, "Content-Transfer-Encoding: %s", cp); HEAD_OUT(state, (HEADER_OPTS *) 0, len); VSTRING_RESET(state->output_buffer); } /* * This input terminates a block of message headers. Call the * optional header end routine at the end of the first header block. */ if (state->curr_state == MIME_STATE_PRIMARY && state->head_end) state->head_end(state->app_context); /* * This is the right place to check if the sender specified an * appropriate identity encoding (7bit, 8bit, binary) for multipart * and for message. */ if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) { if (state->curr_ctype == MIME_CTYPE_MESSAGE) { if (state->curr_stype == MIME_STYPE_PARTIAL || state->curr_stype == MIME_STYPE_EXTERN_BODY) { if (state->curr_domain != MIME_ENC_7BIT) REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, mime_state_enc_name(state->curr_encoding)); } else { if (state->curr_encoding != state->curr_domain) REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, mime_state_enc_name(state->curr_encoding)); } } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) { if (state->curr_encoding != state->curr_domain) REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, mime_state_enc_name(state->curr_encoding)); } } /* * Find out if the next body starts with its own message headers. In * agressive mode, examine headers of partial and external-body * messages. Otherwise, treat such headers as part of the "body". Set * the proper encoding information for the multipart prolog. * * XXX This changes state to MIME_STATE_NESTED and then outputs a body * line, so that the body offset is not properly reset. */ if (input_is_text) { if (*text == 0) { state->body_offset = 0; /* XXX */ if (state->curr_ctype == MIME_CTYPE_MESSAGE) { if (state->curr_stype == MIME_STYPE_RFC822 || (state->static_flags & MIME_OPT_RECURSE_ALL_MESSAGE)) SET_MIME_STATE(state, MIME_STATE_NESTED, MIME_CTYPE_TEXT, MIME_STYPE_PLAIN, MIME_ENC_7BIT, MIME_ENC_7BIT); else SET_CURR_STATE(state, MIME_STATE_BODY); } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) { SET_MIME_STATE(state, MIME_STATE_BODY, MIME_CTYPE_OTHER, MIME_STYPE_OTHER, MIME_ENC_7BIT, MIME_ENC_7BIT); } else { SET_CURR_STATE(state, MIME_STATE_BODY); } } /* * Invalid input. Force output of one blank line and jump to the * body state, leaving all other state alone. */ else { SET_CURR_STATE(state, MIME_STATE_BODY); BODY_OUT(state, REC_TYPE_NORM, "", 0); } } /* * This input is not text. Go to body state, unconditionally. */ else { SET_CURR_STATE(state, MIME_STATE_BODY); } /* FALLTHROUGH */ /* * Body text. Look for message boundaries, and recover from missing * boundary strings. Missing boundaries can happen in agressive mode * with text/rfc822-headers or with message/partial. Ignore non-space * cruft after --boundary or --boundary--, because some MUAs do, and * because only perverse software would take advantage of this to * escape detection. We have to ignore trailing cruft anyway, because * our saved copy of the boundary string may have been truncated for * safety reasons. * * Optionally look for 8-bit data in content that was announced as, or * that defaults to, 7-bit. Unfortunately, we cannot turn this on by * default. Majordomo sends requests for approval that do not * propagate the MIME information from the enclosed message to the * message headers of the approval request. * * Set the proper state information after processing a message boundary * string. * * Don't look for boundary strings at the start of a continued record. */ case MIME_STATE_BODY: if (input_is_text) { if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0 && state->curr_encoding == MIME_ENC_7BIT && (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) { for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) if (*cp & 0200) { REPORT_ERROR(state, MIME_ERR_8BIT_IN_7BIT_BODY, text); break; } } if (state->stack && state->prev_rec_type != REC_TYPE_CONT && text[0] == '-' && text[1] == '-') { for (sp = state->stack; sp != 0; sp = sp->next) { if (strncmp(text + 2, sp->boundary, sp->bound_len) == 0) { while (sp != state->stack) mime_state_pop(state); if (strncmp(text + 2 + sp->bound_len, "--", 2) == 0) { mime_state_pop(state); SET_MIME_STATE(state, MIME_STATE_BODY, MIME_CTYPE_OTHER, MIME_STYPE_OTHER, MIME_ENC_7BIT, MIME_ENC_7BIT); } else { SET_MIME_STATE(state, MIME_STATE_MULTIPART, sp->def_ctype, sp->def_stype, MIME_ENC_7BIT, MIME_ENC_7BIT); } break; } } } /* Put last for consistency with header output routine. */ if ((state->static_flags & MIME_OPT_DOWNGRADE) && state->curr_domain != MIME_ENC_7BIT) mime_state_downgrade(state, rec_type, text, len); else BODY_OUT(state, rec_type, text, len); } /* * The input is not a text record. Inform the application that this * is the last opportunity to send any pending output. */ else { if (state->body_end) state->body_end(state->app_context); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); /* * Oops. This can't happen. */ default: msg_panic("mime_state_update: unknown state: %d", state->curr_state); } }