[PATCH 2/2] emacs: crypto: Handle prompting for passwords

Subject: [PATCH 2/2] emacs: crypto: Handle prompting for passwords

Date: Sun, 7 Jul 2013 12:14:32 +0100

To: notmuch@notmuchmail.org

Cc:

From: Neil Roberts


This makes the Emacs client handle prompting for passwords when
decrypting PGP messages via the new --status-fd and --command-fd
options to the show and reply commands.

The prompting is handled directly in notmuch-call-notmuch-sexp.
Instead of calling call-process to invoke notmuch synchronously it is
now run asynchronously via notmuch-start-notmuch so that it can
install a process filter to handle the status messages. The function
then runs a loop calling accept-process-output so that it can block
until the process actually completes.

Emacs doesn't support having multiple file descriptors to talk to a
child process apart from stdin/out so the --status-fd is set to 1 and
the --command-fd is set to 0. This means that the status messages will
actually be interleaved with the sexp output. I think this shouldn't
be a problem because both the sexp output and the status messages are
split into lines and it shouldn't be possible for a sexp message to
begin with [NOTMUCH:] so it is easy to separate out the two types of
message.

The process filter collects the output into a temporary buffer.
Whenever it receives a line beginning with [NOTMUCH:] it will process
the command and remove the line so that the final buffer only contains
the sexp. The only handler currently is for GET_HIDDEN which just
calls read-passwd to prompt for the password and then writes it back
out to the stdin of the notmuch process.

This is based on similar functionality in epg.el which handles
invoking GPG.
---
 emacs/notmuch-lib.el   | 66 +++++++++++++++++++++++++++++++++++++++++++-------
 emacs/notmuch-mua.el   |  2 +-
 emacs/notmuch-query.el |  3 ++-
 3 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el
index 8deb7de..0cc23fe 100644
--- a/emacs/notmuch-lib.el
+++ b/emacs/notmuch-lib.el
@@ -467,6 +467,48 @@ You may need to restart Emacs or upgrade your notmuch package."))
 	;; `notmuch-logged-error' does not return.
 	))))
 
+(defun notmuch--status-GET_HIDDEN (process args)
+  ;; The args string should be readable by emacs if it is made into a list
+  (let* ((args-list (read (concat "(" args ")")))
+	 (prompt (nth 1 args-list))
+	 (pwd (read-passwd (if prompt
+			       (concat prompt ": ")
+			     "GPG password: "))))
+    (process-send-string process (concat pwd "\n"))
+    (clear-string pwd)))
+
+(defun notmuch--process-filter (process string)
+  "Callback used by `notmuch-call-notmuch-sexp` used to handle the status fd."
+
+  (when (buffer-live-p (process-buffer process))
+    (with-current-buffer (process-buffer process)
+
+      (goto-char (point-max))
+
+      (let ((insertion-point (point)))
+	(insert string)
+
+	;; Check if the string contains any status lines
+	(goto-char insertion-point)
+	(beginning-of-line)
+
+	(while (re-search-forward "^\\[NOTMUCH:\\].*\n" nil t)
+	  (let ((line-beginning (match-beginning 0))
+		(line-end (match-end 0)))
+	    (goto-char (+ line-beginning 10))
+	    (when (looking-at " *\\([A-Z_]+\\)")
+	      ;; If there is a function defined that looks like it is made
+	      ;; to handle this particular status then call it passing the
+	      ;; remainder of the line as an argument
+	      (let ((symbol (intern-soft (concat "notmuch--status-"
+						 (match-string 1)))))
+		(when (and symbol (fboundp symbol))
+		  (funcall symbol
+			   process
+			   (buffer-substring (match-end 0) line-end)))))
+	    ;; Remove the status so that it won't form part of the sexp
+	    (delete-region line-beginning line-end)))))))
+
 (defun notmuch-call-notmuch-sexp (&rest args)
   "Invoke `notmuch-command' with ARGS and return the parsed S-exp output.
 
@@ -474,15 +516,21 @@ If notmuch exits with a non-zero status, this will pop up a
 buffer containing notmuch's output and signal an error."
 
   (with-temp-buffer
-    (let ((err-file (make-temp-file "nmerr")))
-      (unwind-protect
-	  (let ((status (apply #'call-process
-			       notmuch-command nil (list t err-file) nil args)))
-	    (notmuch-check-exit-status status (cons notmuch-command args)
-				       (buffer-string) err-file)
-	    (goto-char (point-min))
-	    (read (current-buffer)))
-	(delete-file err-file)))))
+    (let ((proc (apply #'notmuch-start-notmuch
+		       "notmuch"
+		       (current-buffer) nil args)))
+      (set-process-filter proc #'notmuch--process-filter)
+
+      ;; Synchronously wait until the process completes
+      (while (eq (process-status proc) 'run)
+	(accept-process-output proc 1))
+
+      ;; According to similar code in epg-wait-for-completion, this is
+      ;; needed to run the process filter right now.
+      (sleep-for 0.1)
+
+      (goto-char (point-min))
+      (read (current-buffer)))))
 
 (defun notmuch-start-notmuch (name buffer sentinel &rest args)
   "Start and return an asynchronous notmuch command.
diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
index 329d342..af7dd03 100644
--- a/emacs/notmuch-mua.el
+++ b/emacs/notmuch-mua.el
@@ -150,7 +150,7 @@ list."
 	reply
 	original)
     (when notmuch-show-process-crypto
-      (setq args (append args '("--decrypt"))))
+      (setq args (append args '("--decrypt" "--status-fd=1" "--command-fd=0"))))
 
     (if reply-all
 	(setq args (append args '("--reply-to=all")))
diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el
index 51d427f..0788e8c 100644
--- a/emacs/notmuch-query.el
+++ b/emacs/notmuch-query.el
@@ -31,7 +31,8 @@ is a possibly empty forest of replies.
 "
   (let ((args '("show" "--format=sexp" "--format-version=1")))
     (if notmuch-show-process-crypto
-	(setq args (append args '("--decrypt"))))
+	(setq args (append args
+			   '("--decrypt" "--status-fd=1" "--command-fd=0"))))
     (setq args (append args search-terms))
     (apply #'notmuch-call-notmuch-sexp args)))
 
-- 
1.7.11.3.g3c3efa5


Thread: