emacs: Add support for PGP/MIME verification/decryption

A new emacs configuration variable "notmuch-crypto-process-mime"
controls the processing of PGP/MIME signatures and encrypted parts.
When this is set true, notmuch-query will use the notmuch show
--decrypt flag to decrypt encrypted messages and/or calculate the
sigstatus of signed messages.  If sigstatus is available, notmuch-show
will place a specially color-coded header at the begining of the
signed message.

Also included is the ability to switch decryption/verification on/off
on the fly, which is bound to M-RET in notmuch-search-mode.
This commit is contained in:
Jameson Graef Rollins 2011-05-25 18:01:19 -07:00 committed by Carl Worth
parent 2e653db38f
commit 45fe354745
7 changed files with 175 additions and 26 deletions

View file

@ -12,6 +12,7 @@ emacs_sources := \
$(dir)/notmuch-address.el \ $(dir)/notmuch-address.el \
$(dir)/notmuch-maildir-fcc.el \ $(dir)/notmuch-maildir-fcc.el \
$(dir)/notmuch-message.el \ $(dir)/notmuch-message.el \
$(dir)/notmuch-crypto.el \
$(dir)/coolj.el $(dir)/coolj.el
emacs_images := \ emacs_images := \

104
emacs/notmuch-crypto.el Normal file
View file

@ -0,0 +1,104 @@
;; notmuch-crypto.el --- functions for handling display of cryptographic metadata.
;;
;; Copyright © Jameson Rollins
;;
;; This file is part of Notmuch.
;;
;; Notmuch is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; Notmuch is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Notmuch. If not, see <http://www.gnu.org/licenses/>.
;;
;; Authors: Jameson Rollins <jrollins@finestructure.net>
(defcustom notmuch-crypto-process-mime nil
"Should cryptographic MIME parts be processed?
If this variable is non-nil signatures in multipart/signed
messages will be verified and multipart/encrypted parts will be
decrypted. The result of the crypto operation will be displayed
in a specially colored header button at the top of the processed
part. Signed parts will have variously colored headers depending
on the success or failure of the verification process and on the
validity of user ID of the signer.
The effect of setting this variable can be seen temporarily by
viewing a signed or encrypted message with M-RET in notmuch
search."
:group 'notmuch
:type 'boolean)
(define-button-type 'notmuch-crypto-status-button-type
'action '(lambda (button) (message (button-get button 'help-echo)))
'follow-link t
'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts."
'face '(:foreground "blue")
'mouse-face '(:foreground "blue"))
(defun notmuch-crypto-insert-sigstatus-button (sigstatus from)
(let* ((status (plist-get sigstatus :status))
(help-msg nil)
(label "multipart/signed: signature not processed")
(face '(:background "red" :foreground "black")))
(cond
((string= status "good")
; if userid present, userid has full or greater validity
(if (plist-member sigstatus :userid)
(let ((userid (plist-get sigstatus :userid)))
(setq label (concat "Good signature by: " userid))
(setq face '(:background "green" :foreground "black")))
(let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint))))
(setq label (concat "Good signature by key: " fingerprint))
(setq face '(:background "orange" :foreground "black")))))
((string= status "error")
(let ((keyid (concat "0x" (plist-get sigstatus :keyid))))
(setq label (concat "Unknown key ID " keyid " or unsupported algorithm"))
(setq face '(:background "red" :foreground "black"))))
((string= status "bad")
(let ((keyid (concat "0x" (plist-get sigstatus :keyid))))
(setq label (concat "Bad signature (claimed key ID " keyid ")"))
(setq face '(:background "red" :foreground "black"))))
(t
(setq label "Unknown signature status")
(if status (setq label (concat label " \"" status "\"")))))
(insert-button
(concat "[ " label " ]")
:type 'notmuch-crypto-status-button-type
'help-echo help-msg
'face face
'mouse-face face
:notmuch-sigstatus sigstatus
:notmuch-from from)
(insert "\n")))
(defun notmuch-crypto-insert-encstatus-button (encstatus)
(let* ((status (plist-get encstatus :status))
(help-msg nil)
(label "multipart/encrypted: decryption not attempted")
(face '(:background "purple" :foreground "black")))
(cond
((string= status "good")
(setq label "decryption successful"))
((string= status "bad")
(setq label "decryption error"))
(t
(setq label (concat "unknown encstatus \"" status "\""))))
(insert-button
(concat "[ multipart/encrypted: " label " ]")
:type 'notmuch-crypto-status-button-type
'help-echo help-msg
'face face
'mouse-face face)
(insert "\n")))
;;
(provide 'notmuch-crypto)

View file

@ -165,5 +165,10 @@ was called."
"Return non-nil if OBJECT is a mouse click event." "Return non-nil if OBJECT is a mouse click event."
(memq (event-basic-type object) '(mouse-1 mouse-2 mouse-3 mouse-movement)))) (memq (event-basic-type object) '(mouse-1 mouse-2 mouse-3 mouse-movement))))
;; This variable is used only buffer local, but it needs to be
;; declared globally first to avoid compiler warnings.
(defvar notmuch-show-process-crypto nil)
(make-variable-buffer-local 'notmuch-show-process-crypto)
(provide 'notmuch-lib) (provide 'notmuch-lib)

View file

@ -70,12 +70,17 @@ list."
notmuch-mua-hidden-headers)) notmuch-mua-hidden-headers))
(defun notmuch-mua-reply (query-string &optional sender) (defun notmuch-mua-reply (query-string &optional sender)
(let (headers body) (let (headers
body
(args '("reply")))
(if notmuch-show-process-crypto
(setq args (append args '("--decrypt"))))
(setq args (append args (list query-string)))
;; This make assumptions about the output of `notmuch reply', but ;; This make assumptions about the output of `notmuch reply', but
;; really only that the headers come first followed by a blank ;; really only that the headers come first followed by a blank
;; line and then the body. ;; line and then the body.
(with-temp-buffer (with-temp-buffer
(call-process notmuch-command nil t nil "reply" query-string) (apply 'call-process (append (list notmuch-command nil (list t t) nil) args))
(goto-char (point-min)) (goto-char (point-min))
(if (re-search-forward "^$" nil t) (if (re-search-forward "^$" nil t)
(save-excursion (save-excursion

View file

@ -22,17 +22,20 @@
(require 'notmuch-lib) (require 'notmuch-lib)
(require 'json) (require 'json)
(defun notmuch-query-get-threads (search-terms &rest options) (defun notmuch-query-get-threads (search-terms)
"Return a list of threads of messages matching SEARCH-TERMS. "Return a list of threads of messages matching SEARCH-TERMS.
A thread is a forest or list of trees. A tree is a two element A thread is a forest or list of trees. A tree is a two element
list where the first element is a message, and the second element list where the first element is a message, and the second element
is a possibly empty forest of replies. is a possibly empty forest of replies.
" "
(let ((args (append '("show" "--format=json") search-terms)) (let ((args '("show" "--format=json"))
(json-object-type 'plist) (json-object-type 'plist)
(json-array-type 'list) (json-array-type 'list)
(json-false 'nil)) (json-false 'nil))
(if notmuch-show-process-crypto
(setq args (append args '("--decrypt"))))
(setq args (append args search-terms))
(with-temp-buffer (with-temp-buffer
(progn (progn
(apply 'call-process (append (list notmuch-command nil (list t nil) nil) args)) (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args))

View file

@ -32,6 +32,7 @@
(require 'notmuch-query) (require 'notmuch-query)
(require 'notmuch-wash) (require 'notmuch-wash)
(require 'notmuch-mua) (require 'notmuch-mua)
(require 'notmuch-crypto)
(declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
(declare-function notmuch-fontify-headers "notmuch" nil) (declare-function notmuch-fontify-headers "notmuch" nil)
@ -295,7 +296,9 @@ message at DEPTH in the current thread."
;; Functions handling particular MIME parts. ;; Functions handling particular MIME parts.
(defun notmuch-show-save-part (message-id nth &optional filename) (defun notmuch-show-save-part (message-id nth &optional filename)
(let ((process-crypto notmuch-show-process-crypto))
(with-temp-buffer (with-temp-buffer
(setq notmuch-show-process-crypto process-crypto)
;; Always acquires the part via `notmuch part', even if it is ;; Always acquires the part via `notmuch part', even if it is
;; available in the JSON output. ;; available in the JSON output.
(insert (notmuch-show-get-bodypart-internal message-id nth)) (insert (notmuch-show-get-bodypart-internal message-id nth))
@ -306,7 +309,7 @@ message at DEPTH in the current thread."
filename)) filename))
(require-final-newline nil) (require-final-newline nil)
(coding-system-for-write 'no-conversion)) (coding-system-for-write 'no-conversion))
(write-region (point-min) (point-max) file)))) (write-region (point-min) (point-max) file)))))
(defun notmuch-show-mm-display-part-inline (msg part content-type content) (defun notmuch-show-mm-display-part-inline (msg part content-type content)
"Use the mm-decode/mm-view functions to display a part in the "Use the mm-decode/mm-view functions to display a part in the
@ -551,13 +554,20 @@ current buffer, if possible."
;; Helper for parts which are generally not included in the default ;; Helper for parts which are generally not included in the default
;; JSON output. ;; JSON output.
;; Uses the buffer-local variable notmuch-show-process-crypto to
;; determine if parts should be decrypted first.
(defun notmuch-show-get-bodypart-internal (message-id part-number) (defun notmuch-show-get-bodypart-internal (message-id part-number)
(let ((args '("show" "--format=raw"))
(part-arg (format "--part=%s" part-number)))
(setq args (append args (list part-arg)))
(if notmuch-show-process-crypto
(setq args (append args '("--decrypt"))))
(setq args (append args (list message-id)))
(with-temp-buffer (with-temp-buffer
(let ((coding-system-for-read 'no-conversion)) (let ((coding-system-for-read 'no-conversion))
(call-process notmuch-command nil t nil (progn
"show" "--format=raw" (format "--part=%s" part-number) message-id) (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args))
(buffer-string)))) (buffer-string))))))
(defun notmuch-show-get-bodypart-content (msg part nth) (defun notmuch-show-get-bodypart-content (msg part nth)
(or (plist-get part :content) (or (plist-get part :content)
@ -578,6 +588,16 @@ current buffer, if possible."
"Insert the body part PART at depth DEPTH in the current thread." "Insert the body part PART at depth DEPTH in the current thread."
(let ((content-type (downcase (plist-get part :content-type))) (let ((content-type (downcase (plist-get part :content-type)))
(nth (plist-get part :id))) (nth (plist-get part :id)))
;; add encryption status button if encstatus specified
(if (plist-member part :encstatus)
(let* ((encstatus (car (plist-get part :encstatus))))
(notmuch-crypto-insert-encstatus-button encstatus)))
;; add signature status button if sigstatus specified
(if (plist-member part :sigstatus)
(let* ((headers (plist-get msg :headers))
(from (plist-get headers :From))
(sigstatus (car (plist-get part :sigstatus))))
(notmuch-crypto-insert-sigstatus-button sigstatus from)))
(notmuch-show-insert-bodypart-internal msg part content-type nth depth content-type)) (notmuch-show-insert-bodypart-internal msg part content-type nth depth content-type))
;; Some of the body part handlers leave point somewhere up in the ;; Some of the body part handlers leave point somewhere up in the
;; part, so we make sure that we're down at the end. ;; part, so we make sure that we're down at the end.
@ -711,9 +731,10 @@ current buffer, if possible."
(mapc '(lambda (thread) (notmuch-show-insert-thread thread 0)) forest)) (mapc '(lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
(defvar notmuch-show-parent-buffer nil) (defvar notmuch-show-parent-buffer nil)
(make-variable-buffer-local 'notmuch-show-parent-buffer)
;;;###autoload ;;;###autoload
(defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name) (defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name crypto-switch)
"Run \"notmuch show\" with the given thread ID and display results. "Run \"notmuch show\" with the given thread ID and display results.
The optional PARENT-BUFFER is the notmuch-search buffer from The optional PARENT-BUFFER is the notmuch-search buffer from
@ -733,10 +754,14 @@ function is used. "
(let ((buffer (get-buffer-create (generate-new-buffer-name (let ((buffer (get-buffer-create (generate-new-buffer-name
(or buffer-name (or buffer-name
(concat "*notmuch-" thread-id "*"))))) (concat "*notmuch-" thread-id "*")))))
(process-crypto (if crypto-switch
(not notmuch-crypto-process-mime)
notmuch-crypto-process-mime))
(inhibit-read-only t)) (inhibit-read-only t))
(switch-to-buffer buffer) (switch-to-buffer buffer)
(notmuch-show-mode) (notmuch-show-mode)
(set (make-local-variable 'notmuch-show-parent-buffer) parent-buffer) (setq notmuch-show-parent-buffer parent-buffer)
(setq notmuch-show-process-crypto process-crypto)
(erase-buffer) (erase-buffer)
(goto-char (point-min)) (goto-char (point-min))
(save-excursion (save-excursion

View file

@ -218,6 +218,7 @@ For a mouse binding, return nil."
(define-key map "-" 'notmuch-search-remove-tag) (define-key map "-" 'notmuch-search-remove-tag)
(define-key map "+" 'notmuch-search-add-tag) (define-key map "+" 'notmuch-search-add-tag)
(define-key map (kbd "RET") 'notmuch-search-show-thread) (define-key map (kbd "RET") 'notmuch-search-show-thread)
(define-key map (kbd "M-RET") 'notmuch-search-show-thread-crypto-switch)
map) map)
"Keymap for \"notmuch search\" buffers.") "Keymap for \"notmuch search\" buffers.")
(fset 'notmuch-search-mode-map notmuch-search-mode-map) (fset 'notmuch-search-mode-map notmuch-search-mode-map)
@ -417,7 +418,11 @@ Complete list of currently available key bindings:
"Return a list of authors for the current region" "Return a list of authors for the current region"
(notmuch-search-properties-in-region 'notmuch-search-subject beg end)) (notmuch-search-properties-in-region 'notmuch-search-subject beg end))
(defun notmuch-search-show-thread () (defun notmuch-search-show-thread-crypto-switch ()
(interactive)
(notmuch-search-show-thread t))
(defun notmuch-search-show-thread (&optional crypto-switch)
"Display the currently selected thread." "Display the currently selected thread."
(interactive) (interactive)
(let ((thread-id (notmuch-search-find-thread-id)) (let ((thread-id (notmuch-search-find-thread-id))
@ -433,7 +438,8 @@ Complete list of currently available key bindings:
(concat "*" (concat "*"
(truncate-string-to-width subject 32 nil nil t) (truncate-string-to-width subject 32 nil nil t)
"*") "*")
32 nil nil t))) 32 nil nil t))
crypto-switch)
(error "End of search results")))) (error "End of search results"))))
(defun notmuch-search-reply-to-thread (&optional prompt-for-sender) (defun notmuch-search-reply-to-thread (&optional prompt-for-sender)