1 Return-Path: <neil@linux.intel.com>
\r
2 X-Original-To: notmuch@notmuchmail.org
\r
3 Delivered-To: notmuch@notmuchmail.org
\r
4 Received: from localhost (localhost [127.0.0.1])
\r
5 by olra.theworths.org (Postfix) with ESMTP id 32007431FAE
\r
6 for <notmuch@notmuchmail.org>; Sun, 7 Jul 2013 04:13:36 -0700 (PDT)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
11 X-Spam-Status: No, score=-5 tagged_above=-999 required=5
\r
12 tests=[RCVD_IN_DNSWL_HI=-5] autolearn=disabled
\r
13 Received: from olra.theworths.org ([127.0.0.1])
\r
14 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
15 with ESMTP id DbiC0UVgXAAf for <notmuch@notmuchmail.org>;
\r
16 Sun, 7 Jul 2013 04:13:30 -0700 (PDT)
\r
17 Received: from mga09.intel.com (mga09.intel.com [134.134.136.24])
\r
18 by olra.theworths.org (Postfix) with ESMTP id A7AB6431FAF
\r
19 for <notmuch@notmuchmail.org>; Sun, 7 Jul 2013 04:13:29 -0700 (PDT)
\r
20 Received: from orsmga002.jf.intel.com ([10.7.209.21])
\r
21 by orsmga102.jf.intel.com with ESMTP; 07 Jul 2013 04:10:54 -0700
\r
23 X-IronPort-AV: E=Sophos;i="4.87,1013,1363158000"; d="scan'208";a="365957323"
\r
24 Received: from unknown (HELO neilpc.config) ([10.252.122.25])
\r
25 by orsmga002.jf.intel.com with ESMTP; 07 Jul 2013 04:13:19 -0700
\r
26 From: Neil Roberts <neil@linux.intel.com>
\r
27 To: notmuch@notmuchmail.org
\r
28 Subject: [PATCH 1/2] cli: crypto: Support requesting passwords via FDs from
\r
30 Date: Sun, 7 Jul 2013 12:14:31 +0100
\r
31 Message-Id: <1373195672-9338-2-git-send-email-neil@linux.intel.com>
\r
32 X-Mailer: git-send-email 1.7.11.3.g3c3efa5
\r
33 In-Reply-To: <1373195672-9338-1-git-send-email-neil@linux.intel.com>
\r
34 References: <1373195672-9338-1-git-send-email-neil@linux.intel.com>
\r
35 X-BeenThere: notmuch@notmuchmail.org
\r
36 X-Mailman-Version: 2.1.13
\r
38 List-Id: "Use and development of the notmuch mail system."
\r
39 <notmuch.notmuchmail.org>
\r
40 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
41 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
42 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
43 List-Post: <mailto:notmuch@notmuchmail.org>
\r
44 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
45 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
46 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
47 X-List-Received-Date: Sun, 07 Jul 2013 11:13:36 -0000
\r
49 This adds --status-fd and --command-fd options to the show and reply
\r
50 commands which are used to specify a file descriptor for communicating
\r
51 with notmuch as it is running. Notmuch will write status messages to
\r
52 the status-fd and expect responses to them on the command-fd. These
\r
53 options are inspired by similar options for controlling gpg.
\r
55 Currently the only use for this is to have a way for the CLI program
\r
56 to query passwords from the calling application. When the client needs
\r
57 a password it will write a string in the following format to the
\r
60 [NOTMUCH:] GET_HIDDEN <user_id> <prompt> <reprompt>
\r
62 The user_id and prompt are string arguments that are encoded like C
\r
63 strings. Ie, they will be surrounded by "quotes" and will contain
\r
64 backslash escape sequences. These can also be parsed directly by the
\r
65 read function in Emacs. The last argument is either a 1 or 0 to
\r
66 specify whether the password is being prompted for a second time.
\r
68 When the application sees this status it is expected to write the
\r
69 password followed by a newline to the command-fd.
\r
71 crypto.c | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++----
\r
72 notmuch-client.h | 2 +
\r
73 notmuch-reply.c | 6 +-
\r
74 notmuch-show.c | 6 +-
\r
75 4 files changed, 185 insertions(+), 13 deletions(-)
\r
77 diff --git a/crypto.c b/crypto.c
\r
78 index 9736517..eded53f 100644
\r
81 @@ -22,28 +22,188 @@
\r
83 #ifdef GMIME_ATLEAST_26
\r
85 +#define NOTMUCH_PASSWORD_DATA_KEY "notmuch-password-data"
\r
90 + GIOChannel *command_channel;
\r
91 +} notmuch_password_data_t;
\r
94 +free_password_data_cb (void *data_ptr)
\r
96 + notmuch_password_data_t *data = data_ptr;
\r
98 + g_io_channel_unref (data->command_channel);
\r
103 +escape_string (GString *buf,
\r
108 + g_string_append_c (buf, '"');
\r
110 + for (p = str; *p; p++)
\r
113 + g_string_append (buf, "\\n");
\r
116 + g_string_append (buf, "\\t");
\r
119 + g_string_append (buf, "\\t");
\r
122 + g_string_append (buf, "\\\"");
\r
126 + g_string_append_printf (buf, "\\0%o", *p);
\r
128 + g_string_append_c (buf, *p);
\r
132 + g_string_append_c (buf, '"');
\r
136 +write_all (const char *buf,
\r
142 + while (length > 0) {
\r
143 + wrote = write (fd, buf, length);
\r
154 +password_request_cb (GMimeCryptoContext *ctx,
\r
155 + const char *user_id,
\r
156 + const char *prompt_ctx,
\r
157 + gboolean reprompt,
\r
158 + GMimeStream *response,
\r
161 + notmuch_password_data_t *data;
\r
163 + gboolean write_result;
\r
164 + char *line = NULL;
\r
165 + gsize line_length;
\r
168 + data = g_object_get_data (G_OBJECT (ctx), NOTMUCH_PASSWORD_DATA_KEY);
\r
170 + buf = g_string_new ("[NOTMUCH:] GET_HIDDEN ");
\r
171 + escape_string (buf, user_id);
\r
172 + g_string_append_c (buf, ' ');
\r
173 + escape_string (buf, prompt_ctx);
\r
174 + g_string_append_c (buf, ' ');
\r
175 + g_string_append_c (buf, (!!reprompt) + '0');
\r
176 + g_string_append_c (buf, '\n');
\r
178 + write_result = write_all (buf->str, buf->len, data->status_fd);
\r
180 + g_string_free (buf, TRUE);
\r
182 + if (!write_result) {
\r
183 + g_set_error_literal (err,
\r
185 + g_file_error_from_errno (errno),
\r
186 + strerror (errno));
\r
190 + if (!g_io_channel_read_line (data->command_channel,
\r
193 + NULL, /* terminator_pos */
\r
197 + wrote = g_mime_stream_write (response, line, line_length);
\r
199 + /* TODO: is there a more reliable way to clear the memory
\r
200 + * containing a password? */
\r
201 + memset (line, 0, line_length);
\r
205 + if (wrote < (ssize_t) line_length) {
\r
206 + g_set_error_literal (err,
\r
208 + G_FILE_ERROR_FAILED,
\r
209 + "Failed to write password response");
\r
216 /* Create a GPG context (GMime 2.6) */
\r
217 -static notmuch_crypto_context_t *
\r
218 -create_gpg_context (void)
\r
219 +static notmuch_bool_t
\r
220 +create_gpg_context (notmuch_crypto_t *crypto)
\r
222 notmuch_crypto_context_t *gpgctx;
\r
223 + GMimePasswordRequestFunc password_func;
\r
224 + notmuch_password_data_t *password_data;
\r
226 + /* If the --status-fd and --command-fd options were specified then
\r
227 + * we can handle password requests by forwarding them on to the
\r
228 + * application that invoked notmuch */
\r
229 + if (crypto->status_fd != -1 && crypto->command_fd != -1) {
\r
230 + password_func = password_request_cb;
\r
231 + password_data = malloc (sizeof (*password_data));
\r
232 + if (password_data == NULL)
\r
234 + password_data->status_fd = crypto->status_fd;
\r
235 + password_data->command_channel =
\r
236 + g_io_channel_unix_new (crypto->command_fd);
\r
238 + password_func = NULL;
\r
239 + password_data = NULL;
\r
242 - /* TODO: GMimePasswordRequestFunc */
\r
243 - gpgctx = g_mime_gpg_context_new (NULL, "gpg");
\r
244 + gpgctx = g_mime_gpg_context_new (password_func, "gpg");
\r
249 + /* The password callback for GMime doesn't seem to provide any
\r
250 + * user data pointer so we'll work around it by attaching the data
\r
251 + * to the gpg context, which it does pass */
\r
252 + if (password_data) {
\r
253 + g_object_set_data_full (G_OBJECT (gpgctx),
\r
254 + NOTMUCH_PASSWORD_DATA_KEY,
\r
256 + free_password_data_cb);
\r
259 g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) gpgctx, TRUE);
\r
260 g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);
\r
263 + crypto->gpgctx = gpgctx;
\r
268 #else /* GMIME_ATLEAST_26 */
\r
270 /* Create a GPG context (GMime 2.4) */
\r
271 -static notmuch_crypto_context_t *
\r
272 -create_gpg_context (void)
\r
273 +static notmuch_bool_t
\r
274 +create_gpg_context (notmuch_crypto_t *crypto)
\r
276 GMimeSession *session;
\r
277 notmuch_crypto_context_t *gpgctx;
\r
278 @@ -53,11 +213,13 @@ create_gpg_context (void)
\r
279 g_object_unref (session);
\r
285 g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);
\r
288 + crypto->gpgctx = gpgctx;
\r
293 #endif /* GMIME_ATLEAST_26 */
\r
294 @@ -78,7 +240,7 @@ notmuch_crypto_get_context (notmuch_crypto_t *crypto, const char *protocol)
\r
295 if (strcasecmp (protocol, "application/pgp-signature") == 0 ||
\r
296 strcasecmp (protocol, "application/pgp-encrypted") == 0) {
\r
297 if (! crypto->gpgctx) {
\r
298 - crypto->gpgctx = create_gpg_context ();
\r
299 + create_gpg_context (crypto);
\r
300 if (! crypto->gpgctx)
\r
301 fprintf (stderr, "Failed to construct gpg context.\n");
\r
303 diff --git a/notmuch-client.h b/notmuch-client.h
\r
304 index 5f2a6d0..edf47ce 100644
\r
305 --- a/notmuch-client.h
\r
306 +++ b/notmuch-client.h
\r
307 @@ -80,6 +80,8 @@ typedef struct notmuch_crypto {
\r
308 notmuch_crypto_context_t* gpgctx;
\r
309 notmuch_bool_t verify;
\r
310 notmuch_bool_t decrypt;
\r
313 } notmuch_crypto_t;
\r
315 typedef struct notmuch_show_params {
\r
316 diff --git a/notmuch-reply.c b/notmuch-reply.c
\r
317 index e151f78..d46bec4 100644
\r
318 --- a/notmuch-reply.c
\r
319 +++ b/notmuch-reply.c
\r
320 @@ -718,7 +718,9 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
\r
325 + .decrypt = FALSE,
\r
330 int format = FORMAT_DEFAULT;
\r
331 @@ -738,6 +740,8 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
\r
332 { "sender", FALSE },
\r
334 { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 },
\r
335 + { NOTMUCH_OPT_INT, ¶ms.crypto.status_fd, "status-fd", 0, 0 },
\r
336 + { NOTMUCH_OPT_INT, ¶ms.crypto.command_fd, "command-fd", 0, 0 },
\r
340 diff --git a/notmuch-show.c b/notmuch-show.c
\r
341 index 62178f7..0a67807 100644
\r
342 --- a/notmuch-show.c
\r
343 +++ b/notmuch-show.c
\r
344 @@ -1076,7 +1076,9 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
\r
345 .output_body = TRUE,
\r
349 + .decrypt = FALSE,
\r
354 int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED;
\r
355 @@ -1104,6 +1106,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
\r
356 { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 },
\r
357 { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 },
\r
358 { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.verify, "verify", 'v', 0 },
\r
359 + { NOTMUCH_OPT_INT, ¶ms.crypto.status_fd, "status-fd", 0, 0 },
\r
360 + { NOTMUCH_OPT_INT, ¶ms.crypto.command_fd, "command-fd", 0, 0 },
\r
361 { NOTMUCH_OPT_BOOLEAN, ¶ms.output_body, "body", 'b', 0 },
\r