--- /dev/null
+Return-Path: <neil@linux.intel.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 32007431FAE\r
+ for <notmuch@notmuchmail.org>; Sun, 7 Jul 2013 04:13:36 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -5\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-5 tagged_above=-999 required=5\r
+ tests=[RCVD_IN_DNSWL_HI=-5] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id DbiC0UVgXAAf for <notmuch@notmuchmail.org>;\r
+ Sun, 7 Jul 2013 04:13:30 -0700 (PDT)\r
+Received: from mga09.intel.com (mga09.intel.com [134.134.136.24])\r
+ by olra.theworths.org (Postfix) with ESMTP id A7AB6431FAF\r
+ for <notmuch@notmuchmail.org>; Sun, 7 Jul 2013 04:13:29 -0700 (PDT)\r
+Received: from orsmga002.jf.intel.com ([10.7.209.21])\r
+ by orsmga102.jf.intel.com with ESMTP; 07 Jul 2013 04:10:54 -0700\r
+X-ExtLoop1: 1\r
+X-IronPort-AV: E=Sophos;i="4.87,1013,1363158000"; d="scan'208";a="365957323"\r
+Received: from unknown (HELO neilpc.config) ([10.252.122.25])\r
+ by orsmga002.jf.intel.com with ESMTP; 07 Jul 2013 04:13:19 -0700\r
+From: Neil Roberts <neil@linux.intel.com>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH 1/2] cli: crypto: Support requesting passwords via FDs from\r
+ the caller\r
+Date: Sun, 7 Jul 2013 12:14:31 +0100\r
+Message-Id: <1373195672-9338-2-git-send-email-neil@linux.intel.com>\r
+X-Mailer: git-send-email 1.7.11.3.g3c3efa5\r
+In-Reply-To: <1373195672-9338-1-git-send-email-neil@linux.intel.com>\r
+References: <1373195672-9338-1-git-send-email-neil@linux.intel.com>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Sun, 07 Jul 2013 11:13:36 -0000\r
+\r
+This adds --status-fd and --command-fd options to the show and reply\r
+commands which are used to specify a file descriptor for communicating\r
+with notmuch as it is running. Notmuch will write status messages to\r
+the status-fd and expect responses to them on the command-fd. These\r
+options are inspired by similar options for controlling gpg.\r
+\r
+Currently the only use for this is to have a way for the CLI program\r
+to query passwords from the calling application. When the client needs\r
+a password it will write a string in the following format to the\r
+status-fd:\r
+\r
+[NOTMUCH:] GET_HIDDEN <user_id> <prompt> <reprompt>\r
+\r
+The user_id and prompt are string arguments that are encoded like C\r
+strings. Ie, they will be surrounded by "quotes" and will contain\r
+backslash escape sequences. These can also be parsed directly by the\r
+read function in Emacs. The last argument is either a 1 or 0 to\r
+specify whether the password is being prompted for a second time.\r
+\r
+When the application sees this status it is expected to write the\r
+password followed by a newline to the command-fd.\r
+---\r
+ crypto.c | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++----\r
+ notmuch-client.h | 2 +\r
+ notmuch-reply.c | 6 +-\r
+ notmuch-show.c | 6 +-\r
+ 4 files changed, 185 insertions(+), 13 deletions(-)\r
+\r
+diff --git a/crypto.c b/crypto.c\r
+index 9736517..eded53f 100644\r
+--- a/crypto.c\r
++++ b/crypto.c\r
+@@ -22,28 +22,188 @@\r
+ \r
+ #ifdef GMIME_ATLEAST_26\r
+ \r
++#define NOTMUCH_PASSWORD_DATA_KEY "notmuch-password-data"\r
++\r
++typedef struct\r
++{\r
++ int status_fd;\r
++ GIOChannel *command_channel;\r
++} notmuch_password_data_t;\r
++\r
++static void\r
++free_password_data_cb (void *data_ptr)\r
++{\r
++ notmuch_password_data_t *data = data_ptr;\r
++\r
++ g_io_channel_unref (data->command_channel);\r
++ free (data);\r
++}\r
++\r
++static void\r
++escape_string (GString *buf,\r
++ const char *str)\r
++{\r
++ const char *p;\r
++\r
++ g_string_append_c (buf, '"');\r
++\r
++ for (p = str; *p; p++)\r
++ switch (*p) {\r
++ case '\n':\r
++ g_string_append (buf, "\\n");\r
++ break;\r
++ case '\t':\r
++ g_string_append (buf, "\\t");\r
++ break;\r
++ case '\r':\r
++ g_string_append (buf, "\\t");\r
++ break;\r
++ case '"':\r
++ g_string_append (buf, "\\\"");\r
++ break;\r
++ default:\r
++ if (*p < 32)\r
++ g_string_append_printf (buf, "\\0%o", *p);\r
++ else\r
++ g_string_append_c (buf, *p);\r
++ break;\r
++ }\r
++\r
++ g_string_append_c (buf, '"');\r
++}\r
++\r
++static gboolean\r
++write_all (const char *buf,\r
++ size_t length,\r
++ int fd)\r
++{\r
++ ssize_t wrote;\r
++\r
++ while (length > 0) {\r
++ wrote = write (fd, buf, length);\r
++ if (wrote == -1)\r
++ return FALSE;\r
++ buf += wrote;\r
++ length -= wrote;\r
++ }\r
++\r
++ return TRUE;\r
++}\r
++\r
++static gboolean\r
++password_request_cb (GMimeCryptoContext *ctx,\r
++ const char *user_id,\r
++ const char *prompt_ctx,\r
++ gboolean reprompt,\r
++ GMimeStream *response,\r
++ GError **err)\r
++{\r
++ notmuch_password_data_t *data;\r
++ GString *buf;\r
++ gboolean write_result;\r
++ char *line = NULL;\r
++ gsize line_length;\r
++ ssize_t wrote;\r
++\r
++ data = g_object_get_data (G_OBJECT (ctx), NOTMUCH_PASSWORD_DATA_KEY);\r
++\r
++ buf = g_string_new ("[NOTMUCH:] GET_HIDDEN ");\r
++ escape_string (buf, user_id);\r
++ g_string_append_c (buf, ' ');\r
++ escape_string (buf, prompt_ctx);\r
++ g_string_append_c (buf, ' ');\r
++ g_string_append_c (buf, (!!reprompt) + '0');\r
++ g_string_append_c (buf, '\n');\r
++\r
++ write_result = write_all (buf->str, buf->len, data->status_fd);\r
++\r
++ g_string_free (buf, TRUE);\r
++\r
++ if (!write_result) {\r
++ g_set_error_literal (err,\r
++ G_FILE_ERROR,\r
++ g_file_error_from_errno (errno),\r
++ strerror (errno));\r
++ return FALSE;\r
++ }\r
++\r
++ if (!g_io_channel_read_line (data->command_channel,\r
++ &line,\r
++ &line_length,\r
++ NULL, /* terminator_pos */\r
++ err))\r
++ return FALSE;\r
++\r
++ wrote = g_mime_stream_write (response, line, line_length);\r
++\r
++ /* TODO: is there a more reliable way to clear the memory\r
++ * containing a password? */\r
++ memset (line, 0, line_length);\r
++\r
++ g_free (line);\r
++\r
++ if (wrote < (ssize_t) line_length) {\r
++ g_set_error_literal (err,\r
++ G_FILE_ERROR,\r
++ G_FILE_ERROR_FAILED,\r
++ "Failed to write password response");\r
++ return FALSE;\r
++ }\r
++\r
++ return TRUE;\r
++}\r
++\r
+ /* Create a GPG context (GMime 2.6) */\r
+-static notmuch_crypto_context_t *\r
+-create_gpg_context (void)\r
++static notmuch_bool_t\r
++create_gpg_context (notmuch_crypto_t *crypto)\r
+ {\r
+ notmuch_crypto_context_t *gpgctx;\r
++ GMimePasswordRequestFunc password_func;\r
++ notmuch_password_data_t *password_data;\r
++\r
++ /* If the --status-fd and --command-fd options were specified then\r
++ * we can handle password requests by forwarding them on to the\r
++ * application that invoked notmuch */\r
++ if (crypto->status_fd != -1 && crypto->command_fd != -1) {\r
++ password_func = password_request_cb;\r
++ password_data = malloc (sizeof (*password_data));\r
++ if (password_data == NULL)\r
++ return FALSE;\r
++ password_data->status_fd = crypto->status_fd;\r
++ password_data->command_channel =\r
++ g_io_channel_unix_new (crypto->command_fd);\r
++ } else {\r
++ password_func = NULL;\r
++ password_data = NULL;\r
++ }\r
+ \r
+- /* TODO: GMimePasswordRequestFunc */\r
+- gpgctx = g_mime_gpg_context_new (NULL, "gpg");\r
++ gpgctx = g_mime_gpg_context_new (password_func, "gpg");\r
+ if (! gpgctx)\r
+- return NULL;\r
++ return FALSE;\r
++\r
++ /* The password callback for GMime doesn't seem to provide any\r
++ * user data pointer so we'll work around it by attaching the data\r
++ * to the gpg context, which it does pass */\r
++ if (password_data) {\r
++ g_object_set_data_full (G_OBJECT (gpgctx),\r
++ NOTMUCH_PASSWORD_DATA_KEY,\r
++ password_data,\r
++ free_password_data_cb);\r
++ }\r
+ \r
+ g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) gpgctx, TRUE);\r
+ g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);\r
+ \r
+- return gpgctx;\r
++ crypto->gpgctx = gpgctx;\r
++\r
++ return TRUE;\r
+ }\r
+ \r
+ #else /* GMIME_ATLEAST_26 */\r
+ \r
+ /* Create a GPG context (GMime 2.4) */\r
+-static notmuch_crypto_context_t *\r
+-create_gpg_context (void)\r
++static notmuch_bool_t\r
++create_gpg_context (notmuch_crypto_t *crypto)\r
+ {\r
+ GMimeSession *session;\r
+ notmuch_crypto_context_t *gpgctx;\r
+@@ -53,11 +213,13 @@ create_gpg_context (void)\r
+ g_object_unref (session);\r
+ \r
+ if (! gpgctx)\r
+- return NULL;\r
++ return FALSE;\r
+ \r
+ g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);\r
+ \r
+- return gpgctx;\r
++ crypto->gpgctx = gpgctx;\r
++\r
++ return TRUE;\r
+ }\r
+ \r
+ #endif /* GMIME_ATLEAST_26 */\r
+@@ -78,7 +240,7 @@ notmuch_crypto_get_context (notmuch_crypto_t *crypto, const char *protocol)\r
+ if (strcasecmp (protocol, "application/pgp-signature") == 0 ||\r
+ strcasecmp (protocol, "application/pgp-encrypted") == 0) {\r
+ if (! crypto->gpgctx) {\r
+- crypto->gpgctx = create_gpg_context ();\r
++ create_gpg_context (crypto);\r
+ if (! crypto->gpgctx)\r
+ fprintf (stderr, "Failed to construct gpg context.\n");\r
+ }\r
+diff --git a/notmuch-client.h b/notmuch-client.h\r
+index 5f2a6d0..edf47ce 100644\r
+--- a/notmuch-client.h\r
++++ b/notmuch-client.h\r
+@@ -80,6 +80,8 @@ typedef struct notmuch_crypto {\r
+ notmuch_crypto_context_t* gpgctx;\r
+ notmuch_bool_t verify;\r
+ notmuch_bool_t decrypt;\r
++ int status_fd;\r
++ int command_fd;\r
+ } notmuch_crypto_t;\r
+ \r
+ typedef struct notmuch_show_params {\r
+diff --git a/notmuch-reply.c b/notmuch-reply.c\r
+index e151f78..d46bec4 100644\r
+--- a/notmuch-reply.c\r
++++ b/notmuch-reply.c\r
+@@ -718,7 +718,9 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])\r
+ .part = -1,\r
+ .crypto = {\r
+ .verify = FALSE,\r
+- .decrypt = FALSE\r
++ .decrypt = FALSE,\r
++ .status_fd = -1,\r
++ .command_fd = -1\r
+ }\r
+ };\r
+ int format = FORMAT_DEFAULT;\r
+@@ -738,6 +740,8 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])\r
+ { "sender", FALSE },\r
+ { 0, 0 } } },\r
+ { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 },\r
++ { NOTMUCH_OPT_INT, ¶ms.crypto.status_fd, "status-fd", 0, 0 },\r
++ { NOTMUCH_OPT_INT, ¶ms.crypto.command_fd, "command-fd", 0, 0 },\r
+ { 0, 0, 0, 0, 0 }\r
+ };\r
+ \r
+diff --git a/notmuch-show.c b/notmuch-show.c\r
+index 62178f7..0a67807 100644\r
+--- a/notmuch-show.c\r
++++ b/notmuch-show.c\r
+@@ -1076,7 +1076,9 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])\r
+ .output_body = TRUE,\r
+ .crypto = {\r
+ .verify = FALSE,\r
+- .decrypt = FALSE\r
++ .decrypt = FALSE,\r
++ .status_fd = -1,\r
++ .command_fd = -1\r
+ }\r
+ };\r
+ int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED;\r
+@@ -1104,6 +1106,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])\r
+ { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 },\r
+ { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 },\r
+ { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.verify, "verify", 'v', 0 },\r
++ { NOTMUCH_OPT_INT, ¶ms.crypto.status_fd, "status-fd", 0, 0 },\r
++ { NOTMUCH_OPT_INT, ¶ms.crypto.command_fd, "command-fd", 0, 0 },\r
+ { NOTMUCH_OPT_BOOLEAN, ¶ms.output_body, "body", 'b', 0 },\r
+ { 0, 0, 0, 0, 0 }\r
+ };\r
+-- \r
+1.7.11.3.g3c3efa5\r
+\r