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