[PATCH 17/20] cli/reply: add --protected-subject boolean flag

Daniel Kahn Gillmor dkg at fifthhorseman.net
Thu May 10 22:55:41 PDT 2018


This flag indicates the intent of the client to protect the subject
line, which allows "notmuch reply" to safely emit the earlier
message's encrypted subject without risking leaking it in the clear in
the reply.

Obviously, it should only be used by a client that *will* protect the
subject line.  This feels clumsier than i'd like, but we really don't
want to be the ones who leak data on the wire that had been protected
otherwise, and this seems like a safe way to ensure that the MUA is
capable.
---
 doc/man1/notmuch-reply.rst     | 12 ++++++++++++
 notmuch-client.h               |  4 +++-
 notmuch-reply.c                | 20 ++++++++++++--------
 notmuch-show.c                 |  9 ++++++++-
 test/T356-protected-headers.sh |  7 +++++++
 5 files changed, 42 insertions(+), 10 deletions(-)

diff --git a/doc/man1/notmuch-reply.rst b/doc/man1/notmuch-reply.rst
index c893ba04..08aadba6 100644
--- a/doc/man1/notmuch-reply.rst
+++ b/doc/man1/notmuch-reply.rst
@@ -70,6 +70,18 @@ Supported options for **reply** include
         order, and copy values from the first that contains something
         other than only the user's addresses.
 
+``--protected-subject=(true|false)``
+
+    Indicates that the replying client plans to protect (hide) the
+    subject in the subsequent reply.  When replying to an encrypted
+    message that itself has an encrypted subject, **notmuch**
+    **reply** needs to propose a subject for the new reply e-mail.  If
+    the client can handle protected subjects safely (if this flag is
+    set to ``true``), then the cleartext subject will be proposed.
+    Otherwise, the external (dummy) subject is proposed, to avoid
+    leaking the previously protected subject on reply. Defaults to
+    ``false``.
+
 ``--decrypt=(false|auto|true)``
 
     If ``true``, decrypt any MIME encrypted parts found in the
diff --git a/notmuch-client.h b/notmuch-client.h
index 0af96986..014fa064 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -235,7 +235,9 @@ typedef enum {
     /* typical "notmuch show" or other standard output: */
     HEADERS_FORMAT_NORMAL = 0,
     /* set only if this is being generated as a reply: */
-    HEADERS_FORMAT_REPLY = 1 << 0
+    HEADERS_FORMAT_REPLY = 1 << 0,
+    /* set only if the invoking MUA will responsibly protect the subject line */
+    HEADERS_FORMAT_PROTECTED_SUBJECT = 1 << 1
 } notmuch_headers_format_flags;
 
 
diff --git a/notmuch-reply.c b/notmuch-reply.c
index 749eac6d..d1092ce9 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -612,7 +612,8 @@ static int do_reply(notmuch_config_t *config,
 		    notmuch_query_t *query,
 		    notmuch_show_params_t *params,
 		    int format,
-		    bool reply_all)
+		    bool reply_all,
+		    bool protected_subject)
 {
     GMimeMessage *reply;
     mime_node_t *node;
@@ -659,18 +660,19 @@ static int do_reply(notmuch_config_t *config,
 	    return 1;
 
 	if (format == FORMAT_JSON || format == FORMAT_SEXP) {
+	    notmuch_headers_format_flags flags = HEADERS_FORMAT_REPLY;
 	    sp->begin_map (sp);
 
-	    /* The headers of the reply message we've created */
-	    sp->map_key (sp, "reply-headers");
-	    /* FIXME: send msg_crypto here to avoid killing the
-	     * subject line on reply to encrypted messages! */
-	    format_headers_sprinter (sp, reply, HEADERS_FORMAT_REPLY, NULL);
-
 	    /* Start the original */
 	    sp->map_key (sp, "original");
 	    format_part_sprinter (config, sp, node, true, false);
 
+	    /* The headers of the reply message we've created */
+	    sp->map_key (sp, "reply-headers");
+	    if (protected_subject)
+		flags |= HEADERS_FORMAT_PROTECTED_SUBJECT;
+	    format_headers_sprinter (sp, reply, flags, mime_node_get_message_crypto_status (node));
+
 	    /* End */
 	    sp->end (sp);
 	} else {
@@ -699,6 +701,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_database_t *notmuch;
     notmuch_query_t *query;
     char *query_string;
+    bool protected_subject = false;
     int opt_index;
     notmuch_show_params_t params = {
 	.part = -1,
@@ -715,6 +718,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
 				  { "headers-only", FORMAT_HEADERS_ONLY },
 				  { 0, 0 } } },
 	{ .opt_int = &notmuch_format_version, .name = "format-version" },
+	{ .opt_bool = &protected_subject, .name = "protected-subject" },
 	{ .opt_keyword = &reply_all, .name = "reply-to", .keywords =
 	  (notmuch_keyword_t []){ { "all", true },
 				  { "sender", false },
@@ -764,7 +768,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
 	return EXIT_FAILURE;
     }
 
-    if (do_reply (config, query, &params, format, reply_all) != 0)
+    if (do_reply (config, query, &params, format, reply_all, protected_subject) != 0)
 	return EXIT_FAILURE;
 
     _notmuch_crypto_cleanup (&params.crypto);
diff --git a/notmuch-show.c b/notmuch-show.c
index 799940f8..88e1be7a 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -209,8 +209,15 @@ format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
     sp->begin_map (sp);
 
     sp->map_key (sp, "Subject");
-    if (msg_crypto && msg_crypto->payload_subject) {
+    if (msg_crypto && msg_crypto->payload_subject &&
+	!(flags & HEADERS_FORMAT_REPLY))
 	sp->string (sp, msg_crypto->payload_subject);
+    else if ((msg_crypto && msg_crypto->payload_subject &&
+	      (flags & HEADERS_FORMAT_PROTECTED_SUBJECT))) {
+	if (strncasecmp (msg_crypto->payload_subject, "Re:", 3) == 0)
+	    sp->string (sp, msg_crypto->payload_subject);
+	else
+	    sp->string (sp, talloc_asprintf (local, "Re: %s", msg_crypto->payload_subject));
     } else
 	sp->string (sp, g_mime_message_get_subject (message));
 
diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh
index 687681ff..a77dae6d 100755
--- a/test/T356-protected-headers.sh
+++ b/test/T356-protected-headers.sh
@@ -85,4 +85,11 @@ test_json_nodes <<<"$output" \
                 'subject:["original"]["headers"]["Subject"]="This is a protected header"' \
                 'reply-subject:["reply-headers"]["Subject"]="Re: encrypted message"'
 
+test_begin_subtest "emit protected subject in reply when client is safe"
+output=$(notmuch reply --decrypt=true --format=json --protected-subject id:protected-header at crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+                'crypto:["original"]["crypto"]={"decrypted": {"status": "full", "masked-headers": {"Subject": "encrypted message"}}}' \
+                'subject:["original"]["headers"]["Subject"]="This is a protected header"' \
+                'reply-subject:["reply-headers"]["Subject"]="Re: This is a protected header"'
+
 test_done
-- 
2.17.0



More information about the notmuch mailing list