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