[PATCH 1/2] cli: crypto: Support requesting passwords via FDs from the caller
authorNeil Roberts <neil@linux.intel.com>
Sun, 7 Jul 2013 11:14:31 +0000 (12:14 +0100)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:55:55 +0000 (09:55 -0800)
a8/3356504690e635f5aeaabb0d79a297a8241931 [new file with mode: 0644]

diff --git a/a8/3356504690e635f5aeaabb0d79a297a8241931 b/a8/3356504690e635f5aeaabb0d79a297a8241931
new file mode 100644 (file)
index 0000000..f265387
--- /dev/null
@@ -0,0 +1,366 @@
+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, &params.crypto.decrypt, "decrypt", 'd', 0 },\r
++      { NOTMUCH_OPT_INT, &params.crypto.status_fd, "status-fd", 0, 0 },\r
++      { NOTMUCH_OPT_INT, &params.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, &params.part, "part", 'p', 0 },\r
+       { NOTMUCH_OPT_BOOLEAN, &params.crypto.decrypt, "decrypt", 'd', 0 },\r
+       { NOTMUCH_OPT_BOOLEAN, &params.crypto.verify, "verify", 'v', 0 },\r
++      { NOTMUCH_OPT_INT, &params.crypto.status_fd, "status-fd", 0, 0 },\r
++      { NOTMUCH_OPT_INT, &params.crypto.command_fd, "command-fd", 0, 0 },\r
+       { NOTMUCH_OPT_BOOLEAN, &params.output_body, "body", 'b', 0 },\r
+       { 0, 0, 0, 0, 0 }\r
+     };\r
+-- \r
+1.7.11.3.g3c3efa5\r
+\r