Apply patch by Katsumi Yamaoka <yamaoka@jpl.org>
[gnus] / lisp / mml2015.el
index 275a486..e2e9977 100644 (file)
@@ -1,6 +1,6 @@
 ;;; mml2015.el --- MIME Security with Pretty Good Privacy (PGP)
 
-;; Copyright (C) 2000-2013 Free Software Foundation, Inc.
+;; Copyright (C) 2000-2016 Free Software Foundation, Inc.
 
 ;; Author: Shenghuo Zhu <zsh@cs.rochester.edu>
 ;; Keywords: PGP MIME MML
@@ -28,9 +28,6 @@
 ;;; Code:
 
 (eval-and-compile
-  ;; For Emacs <22.2 and XEmacs.
-  (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))
-
   (if (locate-library "password-cache")
       (require 'password-cache)
     (require 'password)))
                   (config &optional minimum-version))
 (declare-function epg-configuration "ext:epg-config" ())
 
+;; Maybe this should be in eg mml-sec.el (and have a different name).
+;; Then mml1991 would not need to require mml2015, and mml1991-use
+;; could be removed.
 (defvar mml2015-use (or
-                    (condition-case nil
-                        (progn
-                          (require 'epg-config)
-                          (epg-check-configuration (epg-configuration))
-                          'epg)
-                      (error))
+                    (progn
+                      (ignore-errors (require 'epg-config))
+                      (and (fboundp 'epg-check-configuration)
+                          'epg))
                     (progn
                       (let ((abs-file (locate-library "pgg")))
                         ;; Don't load PGG if it is marked as obsolete
@@ -113,6 +111,9 @@ Valid packages include `epg', `pgg' and `mailcrypt'.")
   "If t, cache passphrase."
   :group 'mime-security
   :type 'boolean)
+(make-obsolete-variable 'mml2015-cache-passphrase
+                       'mml-secure-cache-passphrase
+                       "25.1")
 
 (defcustom mml2015-passphrase-cache-expiry mml-secure-passphrase-cache-expiry
   "How many seconds the passphrase is cached.
@@ -120,6 +121,9 @@ Whether the passphrase is cached at all is controlled by
 `mml2015-cache-passphrase'."
   :group 'mime-security
   :type 'integer)
+(make-obsolete-variable 'mml2015-passphrase-cache-expiry
+                       'mml-secure-passphrase-cache-expiry
+                       "25.1")
 
 (defcustom mml2015-signers nil
   "A list of your own key ID(s) which will be used to sign a message.
@@ -143,6 +147,18 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
   :group 'mime-security
   :type 'boolean)
 
+(defcustom mml2015-maximum-key-image-dimension 64
+  "The maximum dimension (width or height) of any key images."
+  :version "24.4"
+  :group 'mime-security
+  :type 'integer)
+
+(defcustom mml2015-display-key-image t
+  "If t, try to display key images."
+  :version "24.5"
+  :group 'mime-security
+  :type 'boolean)
+
 ;; Extract plaintext from cleartext signature.  IMO, this kind of task
 ;; should be done by GnuPG rather than Elisp, but older PGP backends
 ;; (such as Mailcrypt, and PGG) discard the output from GnuPG.
@@ -757,68 +773,14 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
 (autoload 'epg-sub-key-fingerprint "epg")
 (autoload 'epg-signature-key-id "epg")
 (autoload 'epg-signature-to-string "epg")
+(autoload 'epg-key-user-id-list "epg")
+(autoload 'epg-user-id-string "epg")
+(autoload 'epg-user-id-validity "epg")
 (autoload 'epg-configuration "epg-config")
 (autoload 'epg-expand-group "epg-config")
 (autoload 'epa-select-keys "epa")
 
-(defvar mml2015-epg-secret-key-id-list nil)
-
-(defun mml2015-epg-passphrase-callback (context key-id ignore)
-  (if (eq key-id 'SYM)
-      (epg-passphrase-callback-function context key-id nil)
-    (let* ((password-cache-key-id
-           (if (eq key-id 'PIN)
-               "PIN"
-              key-id))
-          entry
-          (passphrase
-           (password-read
-            (if (eq key-id 'PIN)
-                "Passphrase for PIN: "
-              (if (setq entry (assoc key-id epg-user-id-alist))
-                  (format "Passphrase for %s %s: " key-id (cdr entry))
-                (format "Passphrase for %s: " key-id)))
-            password-cache-key-id)))
-      (when passphrase
-       (let ((password-cache-expiry mml2015-passphrase-cache-expiry))
-         (password-cache-add password-cache-key-id passphrase))
-       (setq mml2015-epg-secret-key-id-list
-             (cons password-cache-key-id mml2015-epg-secret-key-id-list))
-       (copy-sequence passphrase)))))
-
-(defun mml2015-epg-find-usable-key (keys usage)
-  (catch 'found
-    (while keys
-      (let ((pointer (epg-key-sub-key-list (car keys))))
-       ;; The primary key will be marked as disabled, when the entire
-       ;; key is disabled (see 12 Field, Format of colon listings, in
-       ;; gnupg/doc/DETAILS)
-       (unless (memq 'disabled (epg-sub-key-capability (car pointer)))
-         (while pointer
-           (if (and (memq usage (epg-sub-key-capability (car pointer)))
-                    (not (memq (epg-sub-key-validity (car pointer))
-                               '(revoked expired))))
-               (throw 'found (car keys)))
-           (setq pointer (cdr pointer)))))
-      (setq keys (cdr keys)))))
-
-;; XXX: since gpg --list-secret-keys does not return validity of each
-;; key, `mml2015-epg-find-usable-key' defined above is not enough for
-;; secret keys.  The function `mml2015-epg-find-usable-secret-key'
-;; below looks at appropriate public keys to check usability.
-(defun mml2015-epg-find-usable-secret-key (context name usage)
-  (let ((secret-keys (epg-list-keys context name t))
-       secret-key)
-    (while (and (not secret-key) secret-keys)
-      (if (mml2015-epg-find-usable-key
-          (epg-list-keys context (epg-sub-key-fingerprint
-                                  (car (epg-key-sub-key-list
-                                        (car secret-keys)))))
-          usage)
-         (setq secret-key (car secret-keys)
-               secret-keys nil)
-       (setq secret-keys (cdr secret-keys))))
-    secret-key))
+(autoload 'gnus-create-image "gnus-ems")
 
 (defun mml2015-epg-key-image (key-id)
   "Return the image of a key, if any"
@@ -828,23 +790,35 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
            (coding-system-for-read 'binary)
            (data (shell-command-to-string
                   (format "%s --list-options no-show-photos --attribute-fd 3 --list-keys %s 3>&1 >/dev/null 2>&1"
-                          epg-gpg-program key-id))))
+                          (shell-quote-argument epg-gpg-program) key-id))))
       (when (> (length data) 0)
         (insert (substring data 16))
-        (create-image (buffer-string) nil t)))))
+       (condition-case nil
+           (gnus-create-image (buffer-string) nil t)
+         (error))))))
+
+(autoload 'gnus-rescale-image "gnus-util")
 
 (defun mml2015-epg-key-image-to-string (key-id)
   "Return a string with the image of a key, if any"
-  (let* ((result "")
-         (key-image (mml2015-epg-key-image key-id)))
-    (when key-image
-      (setq result "  ")
-      (put-text-property 1 2 'display key-image result))
-    result))
+  (let ((key-image (mml2015-epg-key-image key-id)))
+    (if (not key-image)
+       ""
+      (condition-case error
+         (let ((result "  "))
+           (put-text-property
+            1 2 'display
+            (gnus-rescale-image key-image
+                                (cons mml2015-maximum-key-image-dimension
+                                      mml2015-maximum-key-image-dimension))
+            result)
+           result)
+       (error "")))))
 
 (defun mml2015-epg-signature-to-string (signature)
   (concat (epg-signature-to-string signature)
-         (mml2015-epg-key-image-to-string (epg-signature-key-id signature))))
+          (when mml2015-display-key-image
+            (mml2015-epg-key-image-to-string (epg-signature-key-id signature)))))
 
 (defun mml2015-epg-verify-result-to-string (verify-result)
   (mapconcat #'mml2015-epg-signature-to-string verify-result "\n"))
@@ -860,18 +834,15 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
         mm-security-handle 'gnus-info "Corrupted")
        (throw 'error handle))
       (setq context (epg-make-context))
-      (if mml2015-cache-passphrase
+      (if (or mml2015-cache-passphrase mml-secure-cache-passphrase)
          (epg-context-set-passphrase-callback
           context
-          #'mml2015-epg-passphrase-callback))
+          (cons 'mml-secure-passphrase-callback 'OpenPGP)))
       (condition-case error
          (setq plain (epg-decrypt-string context (mm-get-part child))
-               mml2015-epg-secret-key-id-list nil)
+               mml-secure-secret-key-id-list nil)
        (error
-        (while mml2015-epg-secret-key-id-list
-          (password-cache-remove (car mml2015-epg-secret-key-id-list))
-          (setq mml2015-epg-secret-key-id-list
-                (cdr mml2015-epg-secret-key-id-list)))
+        (mml-secure-clear-secret-key-id-list)
         (mm-set-handle-multipart-parameter
          mm-security-handle 'gnus-info "Failed")
         (if (eq (car error) 'quit)
@@ -907,18 +878,15 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
   (let ((inhibit-redisplay t)
        (context (epg-make-context))
        plain)
-    (if mml2015-cache-passphrase
+    (if (or mml2015-cache-passphrase mml-secure-cache-passphrase)
        (epg-context-set-passphrase-callback
         context
-        #'mml2015-epg-passphrase-callback))
+        (cons 'mml-secure-passphrase-callback 'OpenPGP)))
     (condition-case error
        (setq plain (epg-decrypt-string context (buffer-string))
-             mml2015-epg-secret-key-id-list nil)
+             mml-secure-secret-key-id-list nil)
       (error
-       (while mml2015-epg-secret-key-id-list
-        (password-cache-remove (car mml2015-epg-secret-key-id-list))
-        (setq mml2015-epg-secret-key-id-list
-              (cdr mml2015-epg-secret-key-id-list)))
+       (mml-secure-clear-secret-key-id-list)
        (mm-set-handle-multipart-parameter
        mm-security-handle 'gnus-info "Failed")
        (if (eq (car error) 'quit)
@@ -1004,173 +972,37 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
       (mml2015-extract-cleartext-signature))))
 
 (defun mml2015-epg-sign (cont)
-  (let* ((inhibit-redisplay t)
-        (context (epg-make-context))
-        (boundary (mml-compute-boundary cont))
-        (sender (message-options-get 'message-sender))
-        (signer-names (or mml2015-signers
-                          (if (and mml2015-sign-with-sender sender)
-                              (list (concat "<" sender ">")))))
-        signer-key
-        (signers
-         (or (message-options-get 'mml2015-epg-signers)
-             (message-options-set
-              'mml2015-epg-signers
-              (if (eq mm-sign-option 'guided)
-                  (epa-select-keys context "\
-Select keys for signing.
-If no one is selected, default secret key is used.  "
-                                   signer-names
-                                   t)
-                (if (or sender mml2015-signers)
-                    (delq nil
-                          (mapcar
-                           (lambda (signer)
-                             (setq signer-key
-                                   (mml2015-epg-find-usable-secret-key
-                                    context signer 'sign))
-                             (unless (or signer-key
-                                         (y-or-n-p
-                                          (format
-                                           "No secret key for %s; skip it? "
-                                           signer)))
-                               (error "No secret key for %s" signer))
-                             signer-key)
-                           signer-names)))))))
-        signature micalg)
-    (epg-context-set-armor context t)
-    (epg-context-set-textmode context t)
-    (epg-context-set-signers context signers)
-    (if mml2015-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml2015-epg-passphrase-callback))
-    (condition-case error
-       (setq signature (epg-sign-string context (buffer-string) t)
-             mml2015-epg-secret-key-id-list nil)
-      (error
-       (while mml2015-epg-secret-key-id-list
-        (password-cache-remove (car mml2015-epg-secret-key-id-list))
-        (setq mml2015-epg-secret-key-id-list
-              (cdr mml2015-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
-    (if (epg-context-result-for context 'sign)
-       (setq micalg (epg-new-signature-digest-algorithm
-                     (car (epg-context-result-for context 'sign)))))
-    (goto-char (point-min))
-    (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
-                   boundary))
-    (if micalg
-       (insert (format "\tmicalg=pgp-%s; "
-                       (downcase
-                        (cdr (assq micalg
-                                   epg-digest-algorithm-alist))))))
-    (insert "protocol=\"application/pgp-signature\"\n")
-    (insert (format "\n--%s\n" boundary))
-    (goto-char (point-max))
-    (insert (format "\n--%s\n" boundary))
-    (insert "Content-Type: application/pgp-signature\n\n")
-    (insert signature)
+  (let ((inhibit-redisplay t)
+       (boundary (mml-compute-boundary cont)))
+    ;; Signed data must end with a newline (RFC 3156, 5).
     (goto-char (point-max))
-    (insert (format "--%s--\n" boundary))
-    (goto-char (point-max))))
+    (unless (bolp)
+      (insert "\n"))
+    (let* ((pair (mml-secure-epg-sign 'OpenPGP t))
+          (signature (car pair))
+          (micalg (cdr pair)))
+      (goto-char (point-min))
+      (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
+                     boundary))
+      (if micalg
+         (insert (format "\tmicalg=pgp-%s; "
+                         (downcase
+                          (cdr (assq micalg
+                                     epg-digest-algorithm-alist))))))
+      (insert "protocol=\"application/pgp-signature\"\n")
+      (insert (format "\n--%s\n" boundary))
+      (goto-char (point-max))
+      (insert (format "\n--%s\n" boundary))
+      (insert "Content-Type: application/pgp-signature; name=\"signature.asc\"\n\n")
+      (insert signature)
+      (goto-char (point-max))
+      (insert (format "--%s--\n" boundary))
+      (goto-char (point-max)))))
 
 (defun mml2015-epg-encrypt (cont &optional sign)
   (let* ((inhibit-redisplay t)
-        (context (epg-make-context))
         (boundary (mml-compute-boundary cont))
-        (config (epg-configuration))
-        (recipients (message-options-get 'mml2015-epg-recipients))
-        cipher
-        (sender (message-options-get 'message-sender))
-        (signer-names (or mml2015-signers
-                          (if (and mml2015-sign-with-sender sender)
-                              (list (concat "<" sender ">")))))
-        signers
-        recipient-key signer-key)
-    (unless recipients
-      (setq recipients
-           (apply #'nconc
-                  (mapcar
-                   (lambda (recipient)
-                     (or (epg-expand-group config recipient)
-                         (list (concat "<" recipient ">"))))
-                   (split-string
-                    (or (message-options-get 'message-recipients)
-                        (message-options-set 'message-recipients
-                                             (read-string "Recipients: ")))
-                    "[ \f\t\n\r\v,]+"))))
-      (when mml2015-encrypt-to-self
-       (unless signer-names
-         (error "Neither message sender nor mml2015-signers are set"))
-       (setq recipients (nconc recipients signer-names)))
-      (if (eq mm-encrypt-option 'guided)
-         (setq recipients
-               (epa-select-keys context "\
-Select recipients for encryption.
-If no one is selected, symmetric encryption will be performed.  "
-                                recipients))
-       (setq recipients
-             (delq nil
-                   (mapcar
-                    (lambda (recipient)
-                      (setq recipient-key (mml2015-epg-find-usable-key
-                                           (epg-list-keys context recipient)
-                                           'encrypt))
-                      (unless (or recipient-key
-                                  (y-or-n-p
-                                   (format "No public key for %s; skip it? "
-                                           recipient)))
-                        (error "No public key for %s" recipient))
-                      recipient-key)
-                    recipients)))
-       (unless recipients
-         (error "No recipient specified")))
-      (message-options-set 'mml2015-epg-recipients recipients))
-    (when sign
-      (setq signers
-           (or (message-options-get 'mml2015-epg-signers)
-               (message-options-set
-                'mml2015-epg-signers
-                (if (eq mm-sign-option 'guided)
-                    (epa-select-keys context "\
-Select keys for signing.
-If no one is selected, default secret key is used.  "
-                                     signer-names
-                                     t)
-                  (if (or sender mml2015-signers)
-                      (delq nil
-                            (mapcar
-                             (lambda (signer)
-                               (setq signer-key
-                                     (mml2015-epg-find-usable-secret-key
-                                      context signer 'sign))
-                               (unless (or signer-key
-                                           (y-or-n-p
-                                            (format
-                                             "No secret key for %s; skip it? "
-                                             signer)))
-                                 (error "No secret key for %s" signer))
-                               signer-key)
-                             signer-names)))))))
-      (epg-context-set-signers context signers))
-    (epg-context-set-armor context t)
-    (epg-context-set-textmode context t)
-    (if mml2015-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml2015-epg-passphrase-callback))
-    (condition-case error
-       (setq cipher
-             (epg-encrypt-string context (buffer-string) recipients sign
-                                 mml2015-always-trust)
-             mml2015-epg-secret-key-id-list nil)
-      (error
-       (while mml2015-epg-secret-key-id-list
-        (password-cache-remove (car mml2015-epg-secret-key-id-list))
-        (setq mml2015-epg-secret-key-id-list
-              (cdr mml2015-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
+        (cipher (mml-secure-epg-encrypt 'OpenPGP cont sign)))
     (delete-region (point-min) (point-max))
     (goto-char (point-min))
     (insert (format "Content-Type: multipart/encrypted; boundary=\"%s\";\n"