[PATCH 1/2] cli: crypto: Support requesting passwords via FDs from the caller

Neil Roberts neil at linux.intel.com
Sun Jul 7 04:14:31 PDT 2013


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 <user_id> <prompt> <reprompt>

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, &params.crypto.decrypt, "decrypt", 'd', 0 },
+	{ NOTMUCH_OPT_INT, &params.crypto.status_fd, "status-fd", 0, 0 },
+	{ NOTMUCH_OPT_INT, &params.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, &params.part, "part", 'p', 0 },
 	{ NOTMUCH_OPT_BOOLEAN, &params.crypto.decrypt, "decrypt", 'd', 0 },
 	{ NOTMUCH_OPT_BOOLEAN, &params.crypto.verify, "verify", 'v', 0 },
+	{ NOTMUCH_OPT_INT, &params.crypto.status_fd, "status-fd", 0, 0 },
+	{ NOTMUCH_OPT_INT, &params.crypto.command_fd, "command-fd", 0, 0 },
 	{ NOTMUCH_OPT_BOOLEAN, &params.output_body, "body", 'b', 0 },
 	{ 0, 0, 0, 0, 0 }
     };
-- 
1.7.11.3.g3c3efa5



More information about the notmuch mailing list