[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, ¶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
More information about the notmuch
mailing list