/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "mempool.h" #include "array.h" #include "str.h" #include "str-sanitize.h" #include "istream.h" #include "time-util.h" #include "rfc822-parser.h" #include "message-date.h" #include "message-parser.h" #include "message-decoder.h" #include "message-header-decode.h" #include "mail-html2text.h" #include "mail-storage.h" #include "mail-user.h" #include "smtp-params.h" #include "master-service.h" #include "master-service-settings.h" #include "raw-storage.h" #include "edit-mail.h" #include "sieve-common.h" #include "sieve-stringlist.h" #include "sieve-error.h" #include "sieve-extensions.h" #include "sieve-code.h" #include "sieve-address.h" #include "sieve-address-parts.h" #include "sieve-runtime.h" #include "sieve-runtime-trace.h" #include "sieve-match.h" #include "sieve-interpreter.h" #include "sieve-message.h" /* * Message transmission */ const char *sieve_message_get_new_id(const struct sieve_instance *svinst) { static int count = 0; return t_strdup_printf("", dec2str(ioloop_timeval.tv_sec), dec2str(ioloop_timeval.tv_usec), count++, svinst->hostname); } /* * Message context */ struct sieve_message_header { const char *name; const unsigned char *value, *utf8_value; size_t value_len, utf8_value_len; }; struct sieve_message_part { struct sieve_message_part *parent, *next, *children; ARRAY(struct sieve_message_header) headers; const char *content_type; const char *content_disposition; const char *decoded_body; const char *text_body; size_t decoded_body_size; size_t text_body_size; bool have_body:1; /* there's the empty end-of-headers line */ bool epilogue:1; /* this is a multipart epilogue */ }; struct sieve_message_version { struct mail *mail; struct mailbox *box; struct mailbox_transaction_context *trans; struct edit_mail *edit_mail; }; struct sieve_message_context { pool_t pool; pool_t context_pool; int refcount; struct sieve_instance *svinst; struct timeval time; struct mail_user *mail_user; const struct sieve_message_data *msgdata; /* Message versioning */ struct mail_user *raw_mail_user; ARRAY(struct sieve_message_version) versions; /* Context data for extensions */ ARRAY(void *) ext_contexts; /* Body */ ARRAY(struct sieve_message_part *) cached_body_parts; ARRAY(struct sieve_message_part_data) return_body_parts; buffer_t *raw_body; bool edit_snapshot:1; bool substitute_snapshot:1; }; /* * Message versions */ static inline struct sieve_message_version *sieve_message_version_new (struct sieve_message_context *msgctx) { return array_append_space(&msgctx->versions); } static inline struct sieve_message_version *sieve_message_version_get (struct sieve_message_context *msgctx) { struct sieve_message_version *versions; unsigned int count; versions = array_get_modifiable(&msgctx->versions, &count); if ( count == 0 ) return array_append_space(&msgctx->versions); return &versions[count-1]; } static inline void sieve_message_version_free (struct sieve_message_version *version) { if ( version->edit_mail != NULL ) { edit_mail_unwrap(&version->edit_mail); version->edit_mail = NULL; } if ( version->mail != NULL ) { mail_free(&version->mail); mailbox_transaction_rollback(&version->trans); mailbox_free(&version->box); version->mail = NULL; } } /* * Message context object */ struct sieve_message_context *sieve_message_context_create (struct sieve_instance *svinst, struct mail_user *mail_user, const struct sieve_message_data *msgdata) { struct sieve_message_context *msgctx; msgctx = i_new(struct sieve_message_context, 1); msgctx->refcount = 1; msgctx->svinst = svinst; msgctx->mail_user = mail_user; msgctx->msgdata = msgdata; i_gettimeofday(&msgctx->time); sieve_message_context_reset(msgctx); return msgctx; } void sieve_message_context_ref(struct sieve_message_context *msgctx) { msgctx->refcount++; } static void sieve_message_context_clear(struct sieve_message_context *msgctx) { struct sieve_message_version *versions; unsigned int count, i; if ( msgctx->pool != NULL ) { versions = array_get_modifiable(&msgctx->versions, &count); for ( i = 0; i < count; i++ ) { sieve_message_version_free(&versions[i]); } pool_unref(&(msgctx->pool)); } } void sieve_message_context_unref(struct sieve_message_context **msgctx) { i_assert((*msgctx)->refcount > 0); if (--(*msgctx)->refcount != 0) return; if ( (*msgctx)->raw_mail_user != NULL ) mail_user_unref(&(*msgctx)->raw_mail_user); sieve_message_context_clear(*msgctx); if ( (*msgctx)->context_pool != NULL ) pool_unref(&((*msgctx)->context_pool)); i_free(*msgctx); *msgctx = NULL; } static void sieve_message_context_flush(struct sieve_message_context *msgctx) { pool_t pool; if ( msgctx->context_pool != NULL ) pool_unref(&(msgctx->context_pool)); msgctx->context_pool = pool = pool_alloconly_create("sieve_message_context_data", 2048); p_array_init(&msgctx->ext_contexts, pool, sieve_extensions_get_count(msgctx->svinst)); p_array_init(&msgctx->cached_body_parts, pool, 8); p_array_init(&msgctx->return_body_parts, pool, 8); msgctx->raw_body = NULL; } void sieve_message_context_reset(struct sieve_message_context *msgctx) { sieve_message_context_clear(msgctx); msgctx->pool = pool_alloconly_create("sieve_message_context", 1024); p_array_init(&msgctx->versions, msgctx->pool, 4); sieve_message_context_flush(msgctx); } pool_t sieve_message_context_pool(struct sieve_message_context *msgctx) { return msgctx->context_pool; } void sieve_message_context_time(struct sieve_message_context *msgctx, struct timeval *time) { *time = msgctx->time; } /* Extension support */ void sieve_message_context_extension_set (struct sieve_message_context *msgctx, const struct sieve_extension *ext, void *context) { if ( ext->id < 0 ) return; array_idx_set(&msgctx->ext_contexts, (unsigned int) ext->id, &context); } const void *sieve_message_context_extension_get (struct sieve_message_context *msgctx, const struct sieve_extension *ext) { void * const *ctx; if ( ext->id < 0 || ext->id >= (int) array_count(&msgctx->ext_contexts) ) return NULL; ctx = array_idx(&msgctx->ext_contexts, (unsigned int) ext->id); return *ctx; } /* Envelope */ const struct smtp_address *sieve_message_get_orig_recipient (struct sieve_message_context *msgctx) { const struct sieve_message_data *msgdata = msgctx->msgdata; const struct smtp_address *orcpt_to = NULL; if ( msgdata->envelope.rcpt_params != NULL ) { orcpt_to = msgdata->envelope.rcpt_params->orcpt.addr; if ( !smtp_address_isnull(orcpt_to) ) return orcpt_to; } orcpt_to = msgdata->envelope.rcpt_to; return ( !smtp_address_isnull(orcpt_to) ? orcpt_to : NULL ); } const struct smtp_address *sieve_message_get_final_recipient (struct sieve_message_context *msgctx) { const struct sieve_message_data *msgdata = msgctx->msgdata; const struct smtp_address *rcpt_to = msgdata->envelope.rcpt_to; return ( !smtp_address_isnull(rcpt_to) ? rcpt_to : NULL); } const struct smtp_address *sieve_message_get_sender (struct sieve_message_context *msgctx) { const struct sieve_message_data *msgdata = msgctx->msgdata; const struct smtp_address *mail_from = msgdata->envelope.mail_from; return ( !smtp_address_isnull(mail_from) ? mail_from : NULL); } /* * Mail */ int sieve_message_substitute (struct sieve_message_context *msgctx, struct istream *input) { static const char *wanted_headers[] = { "From", "Message-ID", "Subject", "Return-Path", NULL }; static const struct smtp_address default_sender = { .localpart = DEFAULT_ENVELOPE_SENDER, .domain = NULL, }; struct mail_user *mail_user = msgctx->mail_user; struct sieve_message_version *version; struct mailbox_header_lookup_ctx *headers_ctx; struct mailbox *box = NULL; const struct smtp_address *sender; int ret; i_assert(input->blocking); if ( msgctx->raw_mail_user == NULL ) { void **sets = master_service_settings_get_others(master_service); msgctx->raw_mail_user = raw_storage_create_from_set(mail_user->set_info, sets[0]); } i_stream_seek(input, 0); sender = sieve_message_get_sender(msgctx); sender = (sender == NULL ? &default_sender : sender); ret = raw_mailbox_alloc_stream(msgctx->raw_mail_user, input, (time_t)-1, smtp_address_encode(sender), &box); if ( ret < 0 ) { e_error(msgctx->svinst->event, "can't open substituted mail as raw: %s", mailbox_get_last_internal_error(box, NULL)); return -1; } if ( msgctx->substitute_snapshot ) { version = sieve_message_version_new(msgctx); } else { version = sieve_message_version_get(msgctx); sieve_message_version_free(version); } version->box = box; version->trans = mailbox_transaction_begin(box, 0, __func__); headers_ctx = mailbox_header_lookup_init(box, wanted_headers); version->mail = mail_alloc(version->trans, 0, headers_ctx); mailbox_header_lookup_unref(&headers_ctx); mail_set_seq(version->mail, 1); sieve_message_context_flush(msgctx); msgctx->substitute_snapshot = FALSE; msgctx->edit_snapshot = FALSE; return 1; } struct mail *sieve_message_get_mail (struct sieve_message_context *msgctx) { const struct sieve_message_version *versions; unsigned int count; versions = array_get(&msgctx->versions, &count); if ( count == 0 ) return msgctx->msgdata->mail; if ( versions[count-1].edit_mail != NULL ) return edit_mail_get_mail(versions[count-1].edit_mail); return versions[count-1].mail; } struct edit_mail *sieve_message_edit (struct sieve_message_context *msgctx) { struct sieve_message_version *version; version = sieve_message_version_get(msgctx); if ( version->edit_mail == NULL ) { version->edit_mail = edit_mail_wrap (( version->mail == NULL ? msgctx->msgdata->mail : version->mail )); } else if ( msgctx->edit_snapshot ) { version->edit_mail = edit_mail_snapshot(version->edit_mail); } msgctx->edit_snapshot = FALSE; return version->edit_mail; } void sieve_message_snapshot (struct sieve_message_context *msgctx) { msgctx->edit_snapshot = TRUE; msgctx->substitute_snapshot = TRUE; } /* * Message header list */ /* Forward declarations */ static int sieve_message_header_list_next_item (struct sieve_header_list *_hdrlist, const char **name_r, string_t **value_r); static int sieve_message_header_list_next_value (struct sieve_stringlist *_strlist, string_t **value_r); static void sieve_message_header_list_reset (struct sieve_stringlist *_strlist); /* String list object */ struct sieve_message_header_list { struct sieve_header_list hdrlist; struct sieve_stringlist *field_names; const char *header_name; const char *const *headers; int headers_index; bool mime_decode:1; }; struct sieve_header_list *sieve_message_header_list_create (const struct sieve_runtime_env *renv, struct sieve_stringlist *field_names, bool mime_decode) { struct sieve_message_header_list *hdrlist; hdrlist = t_new(struct sieve_message_header_list, 1); hdrlist->hdrlist.strlist.runenv = renv; hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK; hdrlist->hdrlist.strlist.next_item = sieve_message_header_list_next_value; hdrlist->hdrlist.strlist.reset = sieve_message_header_list_reset; hdrlist->hdrlist.next_item = sieve_message_header_list_next_item; hdrlist->field_names = field_names; hdrlist->mime_decode = mime_decode; return &hdrlist->hdrlist; } // NOTE: get rid of this once we have a proper Sieve string type static inline string_t *_header_right_trim(const char *raw) { string_t *result; const char *p, *pend; pend = raw + strlen(raw); if (raw == pend) { result = t_str_new(1); } else { for ( p = pend-1; p >= raw; p-- ) { if ( *p != ' ' && *p != '\t' ) break; } result = t_str_new(p - raw + 1); str_append_data(result, raw, p - raw + 1); } return result; } /* String list implementation */ static int sieve_message_header_list_next_item (struct sieve_header_list *_hdrlist, const char **name_r, string_t **value_r) { struct sieve_message_header_list *hdrlist = (struct sieve_message_header_list *) _hdrlist; const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv; struct mail *mail = sieve_message_get_mail(renv->msgctx); if ( name_r != NULL ) *name_r = NULL; *value_r = NULL; /* Check for end of current header list */ if ( hdrlist->headers == NULL ) { hdrlist->headers_index = 0; } else if ( hdrlist->headers[hdrlist->headers_index] == NULL ) { hdrlist->headers = NULL; hdrlist->headers_index = 0; } /* Fetch next header */ while ( hdrlist->headers == NULL ) { string_t *hdr_item = NULL; int ret; /* Read next header name from source list */ if ( (ret=sieve_stringlist_next_item (hdrlist->field_names, &hdr_item)) <= 0 ) return ret; hdrlist->header_name = str_c(hdr_item); if ( _hdrlist->strlist.trace ) { sieve_runtime_trace(renv, 0, "extracting `%s' headers from message", str_sanitize(str_c(hdr_item), 80)); } /* Fetch all matching headers from the e-mail */ if ( hdrlist->mime_decode ) { ret = mail_get_headers_utf8(mail, str_c(hdr_item), &hdrlist->headers); } else { ret = mail_get_headers(mail, str_c(hdr_item), &hdrlist->headers); } if (ret < 0) { _hdrlist->strlist.exec_status = sieve_runtime_mail_error(renv, mail, "failed to read header field `%s'", str_c(hdr_item)); return -1; } if ( ret == 0 || hdrlist->headers[0] == NULL ) { /* Try next item when no headers found */ hdrlist->headers = NULL; } } /* Return next item */ if ( name_r != NULL ) *name_r = hdrlist->header_name; *value_r = _header_right_trim(hdrlist->headers[hdrlist->headers_index++]); return 1; } static int sieve_message_header_list_next_value (struct sieve_stringlist *_strlist, string_t **value_r) { struct sieve_header_list *hdrlist = (struct sieve_header_list *) _strlist; return sieve_message_header_list_next_item (hdrlist, NULL, value_r); } static void sieve_message_header_list_reset (struct sieve_stringlist *strlist) { struct sieve_message_header_list *hdrlist = (struct sieve_message_header_list *) strlist; hdrlist->headers = NULL; hdrlist->headers_index = 0; sieve_stringlist_reset(hdrlist->field_names); } /* * Header override operand */ const struct sieve_operand_class sieve_message_override_operand_class = { "header-override" }; bool sieve_opr_message_override_dump (const struct sieve_dumptime_env *denv, sieve_size_t *address) { struct sieve_message_override svmo; const struct sieve_message_override_def *hodef; if ( !sieve_opr_object_dump (denv, &sieve_message_override_operand_class, address, &svmo.object) ) return FALSE; hodef = svmo.def = (const struct sieve_message_override_def *) svmo.object.def; if ( hodef->dump_context != NULL ) { sieve_code_descend(denv); if ( !hodef->dump_context(&svmo, denv, address) ) { return FALSE; } sieve_code_ascend(denv); } return TRUE; } int sieve_opr_message_override_read (const struct sieve_runtime_env *renv, sieve_size_t *address, struct sieve_message_override *svmo) { const struct sieve_message_override_def *hodef; int ret; svmo->context = NULL; if ( !sieve_opr_object_read (renv, &sieve_message_override_operand_class, address, &svmo->object) ) return SIEVE_EXEC_BIN_CORRUPT; hodef = svmo->def = (const struct sieve_message_override_def *) svmo->object.def; if ( hodef->read_context != NULL && (ret=hodef->read_context(svmo, renv, address, &svmo->context)) <= 0 ) return ret; return SIEVE_EXEC_OK; } /* * Optional operands */ int sieve_message_opr_optional_dump (const struct sieve_dumptime_env *denv, sieve_size_t *address, signed int *opt_code) { signed int _opt_code = 0; bool final = FALSE, opok = TRUE; if ( opt_code == NULL ) { opt_code = &_opt_code; final = TRUE; } while ( opok ) { int opt; if ( (opt=sieve_addrmatch_opr_optional_dump (denv, address, opt_code)) <= 0 ) return opt; if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) { opok = sieve_opr_message_override_dump(denv, address); } else { return ( final ? -1 : 1 ); } } return -1; } int sieve_message_opr_optional_read (const struct sieve_runtime_env *renv, sieve_size_t *address, signed int *opt_code, int *exec_status, struct sieve_address_part *addrp, struct sieve_match_type *mcht, struct sieve_comparator *cmp, ARRAY_TYPE(sieve_message_override) *svmos) { signed int _opt_code = 0; bool final = FALSE; int ret; if ( opt_code == NULL ) { opt_code = &_opt_code; final = TRUE; } if ( exec_status != NULL ) *exec_status = SIEVE_EXEC_OK; for ( ;; ) { int opt; if ( (opt=sieve_addrmatch_opr_optional_read (renv, address, opt_code, exec_status, addrp, mcht, cmp)) <= 0 ) return opt; if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) { struct sieve_message_override svmo; const struct sieve_message_override *svmo_idx; unsigned int count, i; if ( (ret=sieve_opr_message_override_read (renv, address, &svmo)) <= 0 ) { if ( exec_status != NULL ) *exec_status = ret; return -1; } if ( !array_is_created(svmos) ) t_array_init(svmos, 8); /* insert in sorted sequence */ svmo_idx = array_get(svmos, &count); for (i = 0; i < count; i++) { if (svmo.def->sequence < svmo_idx[i].def->sequence) { array_insert(svmos, i, &svmo, 1); break; } } if (count == i) array_append(svmos, &svmo, 1); } else { if ( final ) { sieve_runtime_trace_error(renv, "invalid optional operand"); if ( exec_status != NULL ) *exec_status = SIEVE_EXEC_BIN_CORRUPT; return -1; } return 1; } } i_unreached(); return -1; } /* * Message header */ int sieve_message_get_header_fields (const struct sieve_runtime_env *renv, struct sieve_stringlist *field_names, ARRAY_TYPE(sieve_message_override) *svmos, bool mime_decode, struct sieve_stringlist **fields_r) { const struct sieve_message_override *svmo; unsigned int count, i; int ret; if ( svmos == NULL || !array_is_created(svmos) || array_count(svmos) == 0 ) { struct sieve_header_list *headers; headers = sieve_message_header_list_create (renv, field_names, mime_decode); *fields_r = &headers->strlist; return SIEVE_EXEC_OK; } svmo = array_get(svmos, &count); if ( svmo[0].def->sequence == 0 && svmo[0].def->header_override != NULL ) { *fields_r = field_names; } else { struct sieve_header_list *headers; headers = sieve_message_header_list_create (renv, field_names, mime_decode); *fields_r = &headers->strlist; } for ( i = 0; i < count; i++ ) { if ( svmo[i].def->header_override != NULL && (ret=svmo[i].def->header_override (&svmo[i], renv, mime_decode, fields_r)) <= 0 ) return ret; } return SIEVE_EXEC_OK; } /* * Message part */ struct sieve_message_part *sieve_message_part_parent (struct sieve_message_part *mpart) { return mpart->parent; } struct sieve_message_part *sieve_message_part_next (struct sieve_message_part *mpart) { return mpart->next; } struct sieve_message_part *sieve_message_part_children (struct sieve_message_part *mpart) { return mpart->children; } const char *sieve_message_part_content_type (struct sieve_message_part *mpart) { return mpart->content_type; } const char *sieve_message_part_content_disposition (struct sieve_message_part *mpart) { return mpart->content_disposition; } int sieve_message_part_get_first_header (struct sieve_message_part *mpart, const char *field, const char **value_r) { const struct sieve_message_header *headers; unsigned int i, count; headers = array_get(&mpart->headers, &count); for ( i = 0; i < count; i++ ) { if ( strcasecmp( headers[i].name, field) == 0 ) { i_assert( headers[i].value[headers[i].value_len] == '\0' ); *value_r = (const char *)headers[i].value; return 1; } } *value_r = NULL; return 0; } void sieve_message_part_get_data (struct sieve_message_part *mpart, struct sieve_message_part_data *data, bool text) { i_zero(data); data->content_type = mpart->content_type; data->content_disposition = mpart->content_disposition; if ( !text ) { data->content = mpart->decoded_body; data->size = mpart->decoded_body_size; } else if ( mpart->children != NULL ) { data->content = ""; data->size = 0; } else { data->content = mpart->text_body; data->size = mpart->text_body_size; } } /* * Message body */ static void str_replace_nuls(string_t *str) { char *data = str_c_modifiable(str); unsigned int i, len = str_len(str); for (i = 0; i < len; i++) { if (data[i] == '\0') data[i] = ' '; } } static bool _is_wanted_content_type (const char * const *wanted_types, const char *content_type) ATTR_NULL(1) { const char *subtype; size_t type_len; if ( wanted_types == NULL ) return TRUE; subtype = strchr(content_type, '/'); type_len = ( subtype == NULL ? strlen(content_type) : (size_t)(subtype - content_type) ); i_assert( wanted_types != NULL ); for (; *wanted_types != NULL; wanted_types++) { const char *wanted_subtype; if (**wanted_types == '\0') { /* empty string matches everything */ return TRUE; } wanted_subtype = strchr(*wanted_types, '/'); if (wanted_subtype == NULL) { /* match only main type */ if (strlen(*wanted_types) == type_len && strncasecmp(*wanted_types, content_type, type_len) == 0) return TRUE; } else { /* match whole type/subtype */ if (strcasecmp(*wanted_types, content_type) == 0) return TRUE; } } return FALSE; } static bool sieve_message_body_get_return_parts (const struct sieve_runtime_env *renv, const char * const *wanted_types, bool extract_text) { struct sieve_message_context *msgctx = renv->msgctx; struct sieve_message_part *const *body_parts; unsigned int i, count; struct sieve_message_part_data *return_part; /* Check whether any body parts are cached already */ body_parts = array_get(&msgctx->cached_body_parts, &count); if ( count == 0 ) return FALSE; /* Clear result array */ array_clear(&msgctx->return_body_parts); /* Fill result array with requested content_types */ for (i = 0; i < count; i++) { if (!body_parts[i]->have_body) { /* Part has no body; according to RFC this MUST not match to anything and * therefore it is not included in the result. */ continue; } /* Skip content types that are not requested */ if (!_is_wanted_content_type (wanted_types, body_parts[i]->content_type)) continue; /* Add new item to the result */ return_part = array_append_space(&msgctx->return_body_parts); return_part->content_type = body_parts[i]->content_type; return_part->content_disposition = body_parts[i]->content_disposition; /* Depending on whether a decoded body part is requested, the appropriate * cache item is read. If it is missing, this function fails and the cache * needs to be completed by sieve_message_parts_add_missing(). */ if (extract_text) { if (body_parts[i]->text_body == NULL) return FALSE; return_part->content = body_parts[i]->text_body; return_part->size = body_parts[i]->text_body_size; } else { if (body_parts[i]->decoded_body == NULL) return FALSE; return_part->content = body_parts[i]->decoded_body; return_part->size = body_parts[i]->decoded_body_size; } } return TRUE; } static void sieve_message_part_save (const struct sieve_runtime_env *renv, buffer_t *buf, struct sieve_message_part *body_part, bool extract_text) { struct sieve_message_context *msgctx = renv->msgctx; pool_t pool = msgctx->context_pool; buffer_t *result_buf, *text_buf = NULL; char *part_data; size_t part_size; /* Extract text if requested */ result_buf = buf; if ( extract_text && body_part->children == NULL && !body_part->epilogue ) { if ( buf->used > 0 && mail_html2text_content_type_match (body_part->content_type) ) { struct mail_html2text *html2text; text_buf = buffer_create_dynamic(default_pool, 4096); /* Remove HTML markup */ html2text = mail_html2text_init(0); mail_html2text_more(html2text, buf->data, buf->used, text_buf); mail_html2text_deinit(&html2text); result_buf = text_buf; } } /* Add terminating NUL to the body part buffer */ buffer_append_c(result_buf, '\0'); /* Make copy of the buffer */ part_data = p_malloc(pool, result_buf->used); memcpy(part_data, result_buf->data, result_buf->used); part_size = result_buf->used - 1; /* Free text buffer if used */ if ( text_buf != NULL) buffer_free(&text_buf); /* Depending on whether the part is processed into text, store message * body in the appropriate cache location. */ if ( !extract_text ) { body_part->decoded_body = part_data; body_part->decoded_body_size = part_size; } else { body_part->text_body = part_data; body_part->text_body_size = part_size; } /* Clear buffer */ buffer_set_used_size(buf, 0); } static const char * _parse_content_type(const struct message_header_line *hdr) { struct rfc822_parser_context parser; string_t *content_type; /* Initialize parsing */ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL); (void)rfc822_skip_lwsp(&parser); /* Parse content type */ content_type = t_str_new(64); if (rfc822_parse_content_type(&parser, content_type) < 0) return ""; /* Content-type value must end here, otherwise it is invalid after all */ (void)rfc822_skip_lwsp(&parser); if ( parser.data != parser.end && *parser.data != ';' ) return ""; /* Success */ return str_c(content_type); } static const char * _parse_content_disposition(const struct message_header_line *hdr) { struct rfc822_parser_context parser; string_t *content_disp; /* Initialize parsing */ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL); (void)rfc822_skip_lwsp(&parser); /* Parse content type */ content_disp = t_str_new(64); if (rfc822_parse_mime_token(&parser, content_disp) < 0) return ""; /* Content-type value must end here, otherwise it is invalid after all */ (void)rfc822_skip_lwsp(&parser); if ( parser.data != parser.end && *parser.data != ';' ) return ""; /* Success */ return str_c(content_disp); } /* sieve_message_parts_add_missing(): * Add requested message body parts to the cache that are missing. */ static int sieve_message_parts_add_missing (const struct sieve_runtime_env *renv, const char *const *content_types, bool extract_text, bool iter_all) ATTR_NULL(2) { struct sieve_message_context *msgctx = renv->msgctx; pool_t pool = msgctx->context_pool; struct mail *mail = sieve_message_get_mail(renv->msgctx); struct message_parser_settings mparser_set = { .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP, .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS, }; ARRAY(struct sieve_message_header) headers; struct sieve_message_part *body_part, *header_part, *last_part; struct message_parser_ctx *parser; struct message_decoder_context *decoder; struct message_block block, decoded; struct message_part *mparts, *prev_mpart = NULL; buffer_t *buf; struct istream *input; unsigned int idx = 0; bool save_body = FALSE, have_all; string_t *hdr_content = NULL; /* First check whether any are missing */ if ( !iter_all && sieve_message_body_get_return_parts (renv, content_types, extract_text) ) { /* Cache hit; all are present */ return SIEVE_EXEC_OK; } /* Get the message stream */ if ( mail_get_stream(mail, NULL, NULL, &input) < 0 ) { return sieve_runtime_mail_error(renv, mail, "failed to open input message"); } if (mail_get_parts(mail, &mparts) < 0) { return sieve_runtime_mail_error(renv, mail, "failed to parse input message parts"); } buf = buffer_create_dynamic(default_pool, 4096); body_part = header_part = last_part = NULL; if (iter_all) { t_array_init(&headers, 64); hdr_content = t_str_new(512); mparser_set.hdr_flags |= MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE; } else { i_zero(&headers); } /* Initialize body decoder */ decoder = message_decoder_init(NULL, 0); // FIXME: currently not tested with edit-mail. //parser = message_parser_init_from_parts(parts, input, // hparser_flags, mparser_flags); parser = message_parser_init(pool_datastack_create(), input, &mparser_set); while ( message_parser_parse_next_block(parser, &block) > 0 ) { struct sieve_message_part **body_part_idx; struct message_header_line *hdr = block.hdr; struct sieve_message_header *header; unsigned char *data; if ( block.part != prev_mpart ) { bool message_rfc822 = FALSE; /* Save previous body part */ if ( body_part != NULL ) { /* Treat message/rfc822 separately; headers become content */ if ( block.part->parent == prev_mpart && strcmp(body_part->content_type, "message/rfc822") == 0 ) { message_rfc822 = TRUE; } else { if ( save_body ) { sieve_message_part_save (renv, buf, body_part, extract_text); } } if ( iter_all && !array_is_created(&body_part->headers) && array_count(&headers) > 0 ) { p_array_init(&body_part->headers, pool, array_count(&headers)); array_copy(&body_part->headers.arr, 0, &headers.arr, 0, array_count(&headers)); } } /* Start processing next part */ body_part_idx = array_idx_get_space (&msgctx->cached_body_parts, idx); if ( *body_part_idx == NULL ) *body_part_idx = p_new(pool, struct sieve_message_part, 1); body_part = *body_part_idx; body_part->content_type = "text/plain"; if ( iter_all ) array_clear(&headers); /* Copy tree structure */ if ( block.part->context != NULL ) { struct sieve_message_part *epipart = (struct sieve_message_part *)block.part->context; i_assert(epipart != NULL); /* multipart epilogue */ body_part->content_type = epipart->content_type; body_part->have_body = TRUE; body_part->epilogue = TRUE; save_body = iter_all || _is_wanted_content_type (content_types, body_part->content_type); } else { struct sieve_message_part *parent = NULL; if ( block.part->parent != NULL ) { body_part->parent = parent = (struct sieve_message_part *) block.part->parent->context; } /* new part */ block.part->context = (void*)body_part; if ( last_part != NULL ) { i_assert( parent != NULL ); if ( last_part->parent == parent ) { last_part->next = body_part; } else if (parent->children == NULL) { parent->children = body_part; } else { struct sieve_message_part *child = parent->children; while (child->next != NULL && child != body_part) child = child->next; if (child != body_part) child->next = body_part; } } } last_part = body_part; /* If this is message/rfc822 content, retain the enveloping part for * storing headers as content. */ if ( message_rfc822 ) { i_assert(idx > 0); body_part_idx = array_idx_modifiable (&msgctx->cached_body_parts, idx-1); header_part = *body_part_idx; } else { header_part = NULL; } prev_mpart = block.part; idx++; } if ( hdr != NULL || block.size == 0 ) { enum { _HDR_CONTENT_TYPE, _HDR_CONTENT_DISPOSITION, _HDR_OTHER } hdr_field; /* Reading headers */ i_assert( body_part != NULL ); /* Decode block */ (void)message_decoder_decode_next_block (decoder, &block, &decoded); /* Check for end of headers */ if ( hdr == NULL ) { /* Save headers for message/rfc822 part */ if ( header_part != NULL ) { sieve_message_part_save (renv, buf, header_part, FALSE); header_part = NULL; } /* Save bodies only if we have a wanted content-type */ save_body = iter_all || _is_wanted_content_type (content_types, body_part->content_type); continue; } /* Encountered the empty line that indicates the end of the headers and * the start of the body */ if ( hdr->eoh ) { body_part->have_body = TRUE; continue; } else if ( header_part != NULL ) { /* Save message/rfc822 header as part content */ if ( hdr->continued ) { buffer_append(buf, hdr->value, hdr->value_len); } else { buffer_append(buf, hdr->name, hdr->name_len); buffer_append(buf, hdr->middle, hdr->middle_len); buffer_append(buf, hdr->value, hdr->value_len); } if ( !hdr->no_newline ) { buffer_append(buf, "\r\n", 2); } } if ( strcasecmp(hdr->name, "Content-Type" ) == 0 ) hdr_field = _HDR_CONTENT_TYPE; else if ( strcasecmp(hdr->name, "Content-Disposition" ) == 0 ) hdr_field = _HDR_CONTENT_DISPOSITION; else if ( iter_all && !array_is_created(&body_part->headers) ) hdr_field = _HDR_OTHER; else { /* Not interested in this header */ continue; } /* Header can have folding whitespace. Acquire the full value before * continuing */ if ( hdr->continues ) { hdr->use_full_value = TRUE; continue; } if ( iter_all && !array_is_created(&body_part->headers) ) { const unsigned char *value, *vp; size_t vlen; /* Add header */ header = array_append_space(&headers); header->name = p_strdup(pool, hdr->name); /* Trim end of field value (not done by parser) */ value = hdr->full_value; vp = value + hdr->full_value_len; while ( vp > value && (vp[-1] == '\t' || vp[-1] == ' ') ) vp--; vlen = (size_t)(vp - value); /* Decode MIME encoded-words. */ str_truncate(hdr_content, 0); message_header_decode_utf8 (value, vlen, hdr_content, NULL); if ( vlen != str_len(hdr_content) || strncmp(str_c(hdr_content), (const char *)value, vlen) != 0 ) { if ( strlen(str_c(hdr_content)) != str_len(hdr_content) ) { /* replace NULs with spaces */ str_replace_nuls(hdr_content); } /* store raw */ data = p_malloc(pool, vlen + 1); data[vlen] = '\0'; header->value = memcpy(data, value, vlen); header->value_len = vlen; /* store decoded */ data = p_malloc(pool, str_len(hdr_content) + 1); data[str_len(hdr_content)] = '\0'; header->utf8_value = memcpy(data, str_data(hdr_content), str_len(hdr_content)); header->utf8_value_len = str_len(hdr_content); } else { /* raw == decoded */ data = p_malloc(pool, vlen + 1); data[vlen] = '\0'; header->value = header->utf8_value = memcpy(data, value, vlen); header->value_len = header->utf8_value_len = vlen; } if ( hdr_field == _HDR_OTHER ) continue; } /* Parse the content type from the Content-type header */ T_BEGIN { switch ( hdr_field ) { case _HDR_CONTENT_TYPE: body_part->content_type = p_strdup(pool, _parse_content_type(block.hdr)); break; case _HDR_CONTENT_DISPOSITION: body_part->content_disposition = p_strdup(pool, _parse_content_disposition(block.hdr)); break; default: i_unreached(); } } T_END; continue; } /* Reading body */ if ( save_body ) { (void)message_decoder_decode_next_block (decoder, &block, &decoded); buffer_append(buf, decoded.data, decoded.size); } } /* even with an empty message there was at least the "end of headers" block, which set the body_part. */ i_assert( body_part != NULL ); /* Save last body part if necessary */ if ( header_part != NULL ) { sieve_message_part_save (renv, buf, header_part, FALSE); } else if ( save_body ) { sieve_message_part_save (renv, buf, body_part, extract_text); } if ( iter_all && !array_is_created(&body_part->headers) && array_count(&headers) > 0 ) { p_array_init(&body_part->headers, pool, array_count(&headers)); array_copy(&body_part->headers.arr, 0, &headers.arr, 0, array_count(&headers)); } /* Try to fill the return_body_parts array once more */ have_all = iter_all || sieve_message_body_get_return_parts (renv, content_types, extract_text); /* This time, failure is a bug */ i_assert(have_all); /* Cleanup */ (void)message_parser_deinit(&parser, &mparts); message_decoder_deinit(&decoder); buffer_free(&buf); /* Return status */ if ( input->stream_errno != 0 ) { sieve_runtime_critical(renv, NULL, "failed to read input message", "read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); return SIEVE_EXEC_TEMP_FAILURE; } return SIEVE_EXEC_OK; } int sieve_message_body_get_content (const struct sieve_runtime_env *renv, const char * const *content_types, struct sieve_message_part_data **parts_r) { struct sieve_message_context *msgctx = renv->msgctx; int status; T_BEGIN { /* Fill the return_body_parts array */ status = sieve_message_parts_add_missing (renv, content_types, FALSE, FALSE); } T_END; /* Check status */ if ( status <= 0 ) return status; /* Return the array of body items */ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0); return status; } int sieve_message_body_get_text (const struct sieve_runtime_env *renv, struct sieve_message_part_data **parts_r) { static const char * const _text_content_types[] = { "application/xhtml+xml", "text", NULL }; struct sieve_message_context *msgctx = renv->msgctx; int status; /* We currently only support extracting plain text from: - text/html -> HTML - application/xhtml+xml -> XHTML Other text types are read as is. Any non-text types are skipped. */ T_BEGIN { /* Fill the return_body_parts array */ status = sieve_message_parts_add_missing (renv, _text_content_types, TRUE, FALSE); } T_END; /* Check status */ if ( status <= 0 ) return status; /* Return the array of body items */ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0); return status; } int sieve_message_body_get_raw (const struct sieve_runtime_env *renv, struct sieve_message_part_data **parts_r) { struct sieve_message_context *msgctx = renv->msgctx; struct sieve_message_part_data *return_part; buffer_t *buf; if ( msgctx->raw_body == NULL ) { struct mail *mail = sieve_message_get_mail(renv->msgctx); struct istream *input; struct message_size hdr_size, body_size; const unsigned char *data; size_t size; int ret; msgctx->raw_body = buf = buffer_create_dynamic (msgctx->context_pool, 1024*64); /* Get stream for message */ if ( mail_get_stream(mail, &hdr_size, &body_size, &input) < 0 ) { return sieve_runtime_mail_error(renv, mail, "failed to open input message"); } /* Skip stream to beginning of body */ i_stream_skip(input, hdr_size.physical_size); /* Read raw message body */ while ( (ret=i_stream_read_more(input, &data, &size)) > 0 ) { buffer_append(buf, data, size); i_stream_skip(input, size); } if ( ret < 0 && input->stream_errno != 0 ) { sieve_runtime_critical(renv, NULL, "failed to read input message", "read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); return SIEVE_EXEC_TEMP_FAILURE; } /* Add terminating NUL to the body part buffer */ buffer_append_c(buf, '\0'); } else { buf = msgctx->raw_body; } /* Clear result array */ array_clear(&msgctx->return_body_parts); if ( buf->used > 1 ) { const char *data = (const char *)buf->data; size_t size = buf->used - 1; i_assert( data[size] == '\0' ); /* Add single item to the result */ return_part = array_append_space(&msgctx->return_body_parts); return_part->content = data; return_part->size = size; } /* Return the array of body items */ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0); return SIEVE_EXEC_OK; } /* * Message part iterator */ int sieve_message_part_iter_init (struct sieve_message_part_iter *iter, const struct sieve_runtime_env *renv) { struct sieve_message_context *msgctx = renv->msgctx; struct sieve_message_part *const *parts; unsigned int count; int status; T_BEGIN { /* Fill the return_body_parts array */ status = sieve_message_parts_add_missing (renv, NULL, TRUE, TRUE); } T_END; /* Check status */ if ( status <= 0 ) return status; i_zero(iter); iter->renv = renv; iter->index = 0; iter->offset = 0; parts = array_get(&msgctx->cached_body_parts, &count); if (count == 0) iter->root = NULL; else iter->root = parts[0]; return SIEVE_EXEC_OK; } void sieve_message_part_iter_subtree(struct sieve_message_part_iter *iter, struct sieve_message_part_iter *subtree) { const struct sieve_runtime_env *renv = iter->renv; struct sieve_message_context *msgctx = renv->msgctx; struct sieve_message_part *const *parts; unsigned int count; *subtree = *iter; parts = array_get(&msgctx->cached_body_parts, &count); if ( subtree->index >= count) subtree->root = NULL; else subtree->root = parts[subtree->index]; subtree->offset = subtree->index; } void sieve_message_part_iter_children(struct sieve_message_part_iter *iter, struct sieve_message_part_iter *child) { const struct sieve_runtime_env *renv = iter->renv; struct sieve_message_context *msgctx = renv->msgctx; struct sieve_message_part *const *parts; unsigned int count; *child = *iter; parts = array_get(&msgctx->cached_body_parts, &count); if ( (child->index+1) >= count || parts[child->index]->children == NULL) child->root = NULL; else child->root = parts[child->index++]; child->offset = child->index; } struct sieve_message_part *sieve_message_part_iter_current (struct sieve_message_part_iter *iter) { const struct sieve_runtime_env *renv = iter->renv; struct sieve_message_context *msgctx = renv->msgctx; struct sieve_message_part *const *parts; unsigned int count; if ( iter->root == NULL ) return NULL; parts = array_get(&msgctx->cached_body_parts, &count); if ( iter->index >= count ) return NULL; do { if ( parts[iter->index] == iter->root->next ) return NULL; if ( parts[iter->index] == iter->root->parent ) return NULL; } while ( parts[iter->index]->epilogue && ++iter->index < count ); if ( iter->index >= count ) return NULL; return parts[iter->index]; } struct sieve_message_part *sieve_message_part_iter_next (struct sieve_message_part_iter *iter) { const struct sieve_runtime_env *renv = iter->renv; struct sieve_message_context *msgctx = renv->msgctx; if ( iter->index >= array_count(&msgctx->cached_body_parts) ) return NULL; iter->index++; return sieve_message_part_iter_current(iter); } void sieve_message_part_iter_reset (struct sieve_message_part_iter *iter) { iter->index = iter->offset; } /* * MIME header list */ /* Forward declarations */ static int sieve_mime_header_list_next_item (struct sieve_header_list *_hdrlist, const char **name_r, string_t **value_r); static int sieve_mime_header_list_next_value (struct sieve_stringlist *_strlist, string_t **value_r); static void sieve_mime_header_list_reset (struct sieve_stringlist *_strlist); /* Header list object */ struct sieve_mime_header_list { struct sieve_header_list hdrlist; struct sieve_stringlist *field_names; struct sieve_message_part_iter part_iter; const char *header_name; const struct sieve_message_header *headers; unsigned int headers_index, headers_count; bool mime_decode:1; bool children:1; }; struct sieve_header_list *sieve_mime_header_list_create (const struct sieve_runtime_env *renv, struct sieve_stringlist *field_names, struct sieve_message_part_iter *part_iter, bool mime_decode, bool children) { struct sieve_mime_header_list *hdrlist; hdrlist = t_new(struct sieve_mime_header_list, 1); hdrlist->hdrlist.strlist.runenv = renv; hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK; hdrlist->hdrlist.strlist.next_item = sieve_mime_header_list_next_value; hdrlist->hdrlist.strlist.reset = sieve_mime_header_list_reset; hdrlist->hdrlist.next_item = sieve_mime_header_list_next_item; hdrlist->field_names = field_names; hdrlist->mime_decode = mime_decode; hdrlist->children = children; sieve_message_part_iter_subtree(part_iter, &hdrlist->part_iter); return &hdrlist->hdrlist; } /* MIME list implementation */ static void sieve_mime_header_list_next_name (struct sieve_mime_header_list *hdrlist) { struct sieve_message_part *mpart; sieve_message_part_iter_reset(&hdrlist->part_iter); mpart = sieve_message_part_iter_current(&hdrlist->part_iter); if ( mpart != NULL && array_is_created(&mpart->headers) ) { hdrlist->headers = array_get (&mpart->headers, &hdrlist->headers_count); hdrlist->headers_index = 0; } } static int sieve_mime_header_list_next_item (struct sieve_header_list *_hdrlist, const char **name_r, string_t **value_r) { struct sieve_mime_header_list *hdrlist = (struct sieve_mime_header_list *) _hdrlist; const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv; if ( name_r != NULL ) *name_r = NULL; *value_r = NULL; for (;;) { /* Check for end of current header list */ if ( hdrlist->headers_count == 0 || hdrlist->headers_index >= hdrlist->headers_count ) { hdrlist->headers_count = 0; hdrlist->headers_index = 0; hdrlist->headers = NULL; } /* Fetch more headers */ while ( hdrlist->headers_count == 0 ) { string_t *hdr_item = NULL; int ret; if ( hdrlist->header_name != NULL && hdrlist->children ) { struct sieve_message_part *mpart; mpart = sieve_message_part_iter_next(&hdrlist->part_iter); if ( mpart != NULL && array_is_created(&mpart->headers) ) { hdrlist->headers = array_get (&mpart->headers, &hdrlist->headers_count); hdrlist->headers_index = 0; } if ( hdrlist->headers_count > 0 ) { if ( _hdrlist->strlist.trace ) { sieve_runtime_trace(renv, 0, "moving to next message part"); } break; } } /* Read next header name from source list */ if ( (ret=sieve_stringlist_next_item (hdrlist->field_names, &hdr_item)) <= 0 ) return ret; hdrlist->header_name = str_c(hdr_item); if ( _hdrlist->strlist.trace ) { sieve_runtime_trace(renv, 0, "extracting `%s' headers from message part", str_sanitize(str_c(hdr_item), 80)); } sieve_mime_header_list_next_name(hdrlist); } for ( ; hdrlist->headers_index < hdrlist->headers_count; hdrlist->headers_index++ ) { const struct sieve_message_header *header = &hdrlist->headers[hdrlist->headers_index]; if ( strcasecmp(header->name, hdrlist->header_name) == 0 ) { if ( name_r != NULL ) *name_r = hdrlist->header_name; if ( hdrlist->mime_decode ) { *value_r = t_str_new_const ((const char *)header->utf8_value, header->utf8_value_len); } else { *value_r = t_str_new_const ((const char *)header->value, header->value_len); } hdrlist->headers_index++; return 1; } } } i_unreached(); return -1; } static int sieve_mime_header_list_next_value (struct sieve_stringlist *_strlist, string_t **value_r) { struct sieve_header_list *hdrlist = (struct sieve_header_list *) _strlist; return sieve_mime_header_list_next_item (hdrlist, NULL, value_r); } static void sieve_mime_header_list_reset (struct sieve_stringlist *strlist) { struct sieve_mime_header_list *hdrlist = (struct sieve_mime_header_list *) strlist; sieve_stringlist_reset(hdrlist->field_names); hdrlist->header_name = NULL; }