From 4dfcc8c9b2e1dbb965f69283dca50c7581c88050 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 20 Oct 2017 22:25:41 -0400 Subject: [PATCH] crypto: index encrypted parts when indexopts try_decrypt is set. If we see index options that ask us to decrypt when indexing a message, and we encounter an encrypted part, we'll try to descend into it. If we can decrypt, we add the property index.decryption=success. If we can't decrypt (or recognize the encrypted type of mail), we add the property index.decryption=failure. Note that a single message may have both values of the "index.decryption" property: "success" and "failure". For example, consider a message that includes multiple layers of encryption. If we manage to decrypt the outer layer ("index.decryption=success"), but fail on the inner layer ("index.decryption=failure"). Because of the property name, this will be automatically cleared (and possibly re-set) during re-indexing. This means it will subsequently correspond to the actual semantics of the stored index. --- doc/man7/notmuch-properties.rst | 26 ++++++++++ lib/add-message.cc | 2 +- lib/index.cc | 91 +++++++++++++++++++++++++++++---- lib/message.cc | 4 +- lib/notmuch-private.h | 1 + 5 files changed, 112 insertions(+), 12 deletions(-) diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst index f70ffb3c..4b47e8d7 100644 --- a/doc/man7/notmuch-properties.rst +++ b/doc/man7/notmuch-properties.rst @@ -47,6 +47,32 @@ CONVENTIONS Any property with a key that starts with "index." will be removed (and possibly re-set) upon reindexing (see **notmuch-reindex(1)**). +MESSAGE PROPERTIES +================== + +The following properties are set by notmuch internally in the course +of its normal activity. + +**index.decryption** + + If a message contains encrypted content, and notmuch tries to + decrypt that content during indexing, it will add the property + ``index.decryption=success`` when the cleartext was successfully + indexed. If notmuch attempts to decrypt any part of a message + during indexing and that decryption attempt fails, it will add the + property ``index.decryption=failure`` to the message. + + Note that it's possible for a single message to have both + ``index.decryption=success`` and ``index.decryption=failure``. + Consider an encrypted e-mail message that contains another + encrypted e-mail message as an attachment -- if the outer message + can be decrypted, but the attached part cannot, then both + properties will be set on the message as a whole. + + If notmuch never tried to decrypt an encrypted message during + indexing (which is the default), then this property will not be + set on that message. + SEE ALSO ======== diff --git a/lib/add-message.cc b/lib/add-message.cc index 34099ed5..f5fac8be 100644 --- a/lib/add-message.cc +++ b/lib/add-message.cc @@ -546,7 +546,7 @@ notmuch_database_index_file (notmuch_database_t *notmuch, indexopts = def_indexopts; } - ret = _notmuch_message_index_file (message, message_file); + ret = _notmuch_message_index_file (message, indexopts, message_file); if (ret) goto DONE; diff --git a/lib/index.cc b/lib/index.cc index e5ae2ba7..6e684f5f 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -364,9 +364,15 @@ _index_content_type (notmuch_message_t *message, GMimeObject *part) } } +static void +_index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts, + GMimeContentType *content_type, + GMimeMultipartEncrypted *part); + /* Callback to generate terms for each mime part of a message. */ static void _index_mime_part (notmuch_message_t *message, + notmuch_indexopts_t *indexopts, GMimeObject *part) { GMimeStream *stream, *filter; @@ -385,6 +391,7 @@ _index_mime_part (notmuch_message_t *message, } _index_content_type (message, part); + content_type = g_mime_object_get_content_type (part); if (GMIME_IS_MULTIPART (part)) { GMimeMultipart *multipart = GMIME_MULTIPART (part); @@ -409,17 +416,21 @@ _index_mime_part (notmuch_message_t *message, } } if (GMIME_IS_MULTIPART_ENCRYPTED (multipart)) { - /* Don't index encrypted parts, but index their content type. */ _index_content_type (message, g_mime_multipart_get_part (multipart, i)); - if ((i != GMIME_MULTIPART_ENCRYPTED_VERSION) && - (i != GMIME_MULTIPART_ENCRYPTED_CONTENT)) { - _notmuch_database_log (_notmuch_message_database (message), - "Warning: Unexpected extra parts of multipart/encrypted.\n"); + if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) { + _index_encrypted_mime_part(message, indexopts, + content_type, + GMIME_MULTIPART_ENCRYPTED (part)); + } else { + if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) { + _notmuch_database_log (_notmuch_message_database (message), + "Warning: Unexpected extra parts of multipart/encrypted.\n"); + } } continue; } - _index_mime_part (message, + _index_mime_part (message, indexopts, g_mime_multipart_get_part (multipart, i)); } return; @@ -430,7 +441,7 @@ _index_mime_part (notmuch_message_t *message, mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part)); - _index_mime_part (message, g_mime_message_get_mime_part (mime_message)); + _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message)); return; } @@ -464,7 +475,6 @@ _index_mime_part (notmuch_message_t *message, filter = g_mime_stream_filter_new (stream); - content_type = g_mime_object_get_content_type (part); discard_non_term_filter = notmuch_filter_discard_non_term_new (content_type); g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter), @@ -502,8 +512,71 @@ _index_mime_part (notmuch_message_t *message, } } +/* descend (if desired) into the cleartext part of an encrypted MIME + * part while indexing. */ +static void +_index_encrypted_mime_part (notmuch_message_t *message, + notmuch_indexopts_t *indexopts, + g_mime_3_unused(GMimeContentType *content_type), + GMimeMultipartEncrypted *encrypted_data) +{ + notmuch_status_t status; + GError *err = NULL; + notmuch_database_t * notmuch = NULL; + GMimeObject *clear = NULL; + + if (!indexopts || !notmuch_indexopts_get_try_decrypt (indexopts)) + return; + + notmuch = _notmuch_message_database (message); + +#if (GMIME_MAJOR_VERSION < 3) + { + GMimeCryptoContext* crypto_ctx = NULL; + const char *protocol = NULL; + protocol = g_mime_content_type_get_parameter (content_type, "protocol"); + status = _notmuch_crypto_get_gmime_ctx_for_protocol (&(indexopts->crypto), + protocol, &crypto_ctx); + if (status) { + _notmuch_database_log (notmuch, "Warning: setup failed for decrypting " + "during indexing. (%d)\n", status); + status = notmuch_message_add_property (message, "index.decryption", "failure"); + if (status) + _notmuch_database_log_append (notmuch, "failed to add index.decryption " + "property (%d)\n", status); + return; + } + clear = g_mime_multipart_encrypted_decrypt(encrypted_data, crypto_ctx, + NULL, &err); + } +#else + clear = g_mime_multipart_encrypted_decrypt(encrypted_data, GMIME_DECRYPT_NONE, NULL, + NULL, &err); +#endif + if (err) { + _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (%d:%d) [%s]\n", + err->domain, err->code, err->message); + g_error_free(err); + /* Indicate that we failed to decrypt during indexing */ + status = notmuch_message_add_property (message, "index.decryption", "failure"); + if (status) + _notmuch_database_log_append (notmuch, "failed to add index.decryption " + "property (%d)\n", status); + return; + } + _index_mime_part (message, indexopts, clear); + g_object_unref (clear); + + status = notmuch_message_add_property (message, "index.decryption", "success"); + if (status) + _notmuch_database_log (notmuch, "failed to add index.decryption " + "property (%d)\n", status); + +} + notmuch_status_t _notmuch_message_index_file (notmuch_message_t *message, + notmuch_indexopts_t *indexopts, notmuch_message_file_t *message_file) { GMimeMessage *mime_message; @@ -531,7 +604,7 @@ _notmuch_message_index_file (notmuch_message_t *message, subject = g_mime_message_get_subject (mime_message); _notmuch_message_gen_terms (message, "subject", subject); - _index_mime_part (message, g_mime_message_get_mime_part (mime_message)); + _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message)); return NOTMUCH_STATUS_SUCCESS; } diff --git a/lib/message.cc b/lib/message.cc index e819f27a..12743460 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1961,7 +1961,7 @@ _notmuch_message_frozen (notmuch_message_t *message) notmuch_status_t notmuch_message_reindex (notmuch_message_t *message, - notmuch_indexopts_t unused (*indexopts)) + notmuch_indexopts_t *indexopts) { notmuch_database_t *notmuch = NULL; notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; @@ -2042,7 +2042,7 @@ notmuch_message_reindex (notmuch_message_t *message, if (found == 0) _notmuch_message_set_header_values (message, date, from, subject); - ret = _notmuch_message_index_file (message, message_file); + ret = _notmuch_message_index_file (message, indexopts, message_file); if (ret == NOTMUCH_STATUS_FILE_ERROR) continue; diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 4c408396..1093429c 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -448,6 +448,7 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch, notmuch_status_t _notmuch_message_index_file (notmuch_message_t *message, + notmuch_indexopts_t *indexopts, notmuch_message_file_t *message_file); /* messages.c */ -- 2.43.0