1 ;;; vm-pgg.el --- PGP/MIME support for VM by pgg.el
3 ;; Copyright (C) 2006 Robert Widhopf-Fenk
5 ;; Author: Robert Widhopf-Fenk, Jens Gustedt
6 ;; Status: Tested with XEmacs 21.4.19 & VM 7.19
7 ;; Keywords: VM helpers
8 ;; X-URL: http://www.robf.de/Hacking/elisp
11 ;; This code is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 1, or (at your option)
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program; if not, write to the Free Software
23 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 ;; This is a replacement for mailcrypt adding PGP/MIME support to VM.
29 ;; It requires PGG which is a standard package for XEmacs and is a part
30 ;; of Gnus for GNU Emacs. On Debian "apt-get install gnus" should do the
33 ;; It is still in BETA state thus you must explicitly load it by
35 ;; (and (locate-library "vm-pgg") (require 'vm-pgg))
37 ;; If you set `vm-auto-displayed-mime-content-types' and/or
38 ;; `vm-mime-internal-content-types' make sure that they contain
39 ;; "application/pgp-keys" or set them before loading vm-pgg.
40 ;; Otherwise public keys are not detected automatically .
42 ;; To customize vm-pgg use: M-x customize-group RET vm-pgg RET
44 ;; Displaying of messages in the PGP(/MIME) format will automatically trigger:
45 ;; * decrypted of encrypted MIME parts
46 ;; * verification of signed MIME parts
47 ;; * snarfing of public keys
49 ;; The status of the current message will also be displayed in the modeline.
51 ;; To create messages according to PGP/MIME you should use:
52 ;; * M-x vm-pgg-encrypt for encrypting
53 ;; * M-x vm-pgg-sign for signing
54 ;; * C-u M-x vm-pgg-encrypt for encrypting + signing
56 ;; All these commands are also available in the menu PGP/MIME which is
57 ;; activated by the minor mode `vm-pgg-compose-mode'. There are also
58 ;; commands for the old style clear text format as MC had them.
60 ;; If you get annoyed by answering password prompts you might want to set the
61 ;; variable `pgg-cache-passphrase' to t and `pgg-passphrase-cache-expiry' to a
62 ;; higher value or nil!
67 ;; Code partially stems from the sources:
68 ;; * mml2015.el (Gnus)
69 ;; * mc-toplev.el (Mailcrypt)
72 ;; * http://www.faqs.org/rfcs/rfc2015.html
73 ;; * http://www.faqs.org/rfcs/rfc2440.html
74 ;; * http://www.faqs.org/rfcs/rfc3156.html
79 ;; * add annotation see to signed/encrypted regions. XEmacs has annotations
80 ;; and GNU Emacs? Maybe I simply use overlays at the line start without eys
82 ;; * allow attaching of other keys from key-ring
87 ;; handle missing pgg.el gracefully
89 (if (and (boundp 'byte-compile-current-file) byte-compile-current-file)
92 (error (message "WARNING: Cannot load pgg.el, related functions may not work!")))
109 (defvar vm-mode-line-format)
110 (defvar vm-message-pointer)
111 (defvar vm-presentation-buffer)
112 (defvar vm-summary-buffer)
113 ;; avoid bytecompile warnings
114 (defvar vm-pgg-cleartext-state nil "For interfunction communication.")
122 "PGP and PGP/MIME support for VM by PGG."
125 (defface vm-pgg-bad-signature
126 '((((type tty) (class color))
127 (:foreground "red" :bold t))
130 (((background light))
131 (:foreground "red" :bold t))
133 (:foreground "red" :bold t)))
134 "The face used to highlight bad signature messages."
138 (defface vm-pgg-good-signature
139 '((((type tty) (class color))
140 (:foreground "green" :bold t))
143 (((background light))
144 (:foreground "green4"))
146 (:foreground "green")))
147 "The face used to highlight good signature messages."
151 (defface vm-pgg-unknown-signature-type
152 '((((type tty) (class color))
156 "The face used to highlight unknown signature types."
160 (defface vm-pgg-error
161 '((((type tty) (class color))
162 (:foreground "red" :bold t))
165 (((background light))
166 (:foreground "red" :bold t))
168 (:foreground "red" :bold t)))
169 "The face used to highlight error messages."
173 (defface vm-pgg-bad-signature-modeline
174 '((((type tty) (class color))
175 (:inherit modeline :foreground "red" :bold t))
177 (:inherit modeline :bold t))
178 (((background light))
179 (:inherit modeline :foreground "red" :bold t))
181 (:inherit modeline :foreground "red" :bold t)))
182 "The face used to highlight bad signature messages."
186 (defface vm-pgg-good-signature-modeline
187 '((((type tty) (class color))
188 (:inherit modeline :foreground "green" :bold t))
190 (:inherit modeline :bold t))
191 (((background light))
192 (:inherit modeline :foreground "green4"))
194 (:inherit modeline :foreground "green")))
195 "The face used to highlight good signature messages."
199 (defface vm-pgg-unknown-signature-type-modeline
200 '((((type tty) (class color))
201 (:inherit modeline :bold t))
203 (:inherit modeline :bold t)))
204 "The face used to highlight unknown signature types."
208 (defface vm-pgg-error-modeline
209 '((((type tty) (class color))
210 (:inherit modeline :foreground "red" :bold t))
212 (:inherit modeline :bold t))
213 (((background light))
214 (:inherit modeline :foreground "red"))
216 (:inherit modeline :foreground "red")))
217 "The face used to highlight error messages."
221 ;; hack to work around the missing support for :inherit in XEmacs
222 (when (featurep 'xemacs)
223 (let ((faces '(vm-pgg-bad-signature-modeline
224 vm-pgg-good-signature-modeline
225 vm-pgg-unknown-signature-type-modeline
226 vm-pgg-error-modeline))
227 (faces-list (face-list))
231 (set-face-parent f 'modeline)
232 (face-display-set f (custom-face-get-spec f) nil '(custom))
233 (setq faces (cdr faces)))))
235 (defcustom vm-pgg-fetch-missing-keys t
236 "*If t, PGP will try to fetch missing keys from `pgg-default-keyserver-address'."
240 (defcustom vm-pgg-auto-snarf t
241 "*If t, snarfing of keys will happen automatically."
245 (defcustom vm-pgg-auto-decrypt t
246 "*If t, decrypting will happen automatically."
250 (defcustom vm-pgg-get-author-headers '("From:" "Sender:")
251 "*The list of headers to get the author of a mail that is to be send.
252 If nil, `pgg-default-user-id' is used as a fallback."
254 :type '(repeat string))
256 (defcustom vm-pgg-sign-text-transfer-encoding 'quoted-printable
257 "*The encoding used for signed MIME parts of type text.
258 See `vm-pgg-sign' for details."
260 :type '(choice (const quoted-printable) (const base64)))
262 (defvar vm-pgg-compose-mode-map
263 (let ((map (make-sparse-keymap)))
264 (define-key map "\C-c#s" 'vm-pgg-sign)
265 (define-key map "\C-c#e" 'vm-pgg-encrypt)
266 (define-key map "\C-c#E" 'vm-pgg-sign-and-encrypt)
267 (define-key map "\C-c#a" 'vm-pgg-ask-hook)
268 (define-key map "\C-c#k" 'vm-pgg-attach-public-key)
271 (defvar vm-pgg-compose-mode-menu nil
272 "The composition menu of vm-pgg.")
275 vm-pgg-compose-mode-menu (if (featurep 'xemacs) nil (list vm-pgg-compose-mode-map))
276 "PGP/MIME compose mode menu."
278 ["Sign" vm-pgg-sign t]
279 ["Encrypt" vm-pgg-encrypt t]
280 ["Sign+Encrypt" vm-pgg-sign-and-encrypt t]
281 ["Ask For An Action" vm-pgg-ask-hook t]
283 ["Attach Public Key" vm-pgg-attach-public-key t]
284 ["Insert Public Key" pgg-insert-key t]))
286 (defvar vm-pgg-compose-mode nil
287 "None-nil means PGP/MIME composition mode key bindings and menu are available.")
289 (make-variable-buffer-local 'vm-pgg-compose-mode)
291 (defun vm-pgg-compose-mode (&optional arg)
292 "\nMinor mode for interfacing with cryptographic functions.
294 Switch mode on/off according to ARG.
296 \\<vm-pgg-compose-mode-map>"
298 (setq vm-pgg-compose-mode
299 (if (null arg) (not vm-pgg-compose-mode)
300 (> (prefix-numeric-value arg) 0)))
301 (if vm-pgg-compose-mode
302 (easy-menu-add vm-pgg-compose-mode-menu)
303 (easy-menu-remove vm-pgg-compose-mode-menu)))
305 (defvar vm-pgg-compose-mode-string " vm-pgg"
306 "*String to put in mode line when function `vm-pgg-compose-mode' is active.")
308 (defcustom vm-pgg-ask-function 'vm-pgg-prompt-for-action
309 "*The function to use in `vm-pgg-ask-hook'."
314 :doc "Disable `vm-pgg-ask-hook'"
318 :doc "Ask whether to sign the message before sending"
322 :doc "Ask whether to encryt the message before sending"
325 :tag "encrypt and sign"
326 :doc "Ask whether to encrypt and sign the message before sending"
329 :tag "ask for the action"
330 :doc "Will prompt for an action by calling `vm-pgg-prompt-for-action'"
331 vm-pgg-prompt-for-action)
333 :tag "your own function"
334 :doc "It should returning one of the other const values.")))
337 (if (not (assq 'vm-pgg-compose-mode minor-mode-map-alist))
338 (setq minor-mode-map-alist
339 (cons (cons 'vm-pgg-compose-mode vm-pgg-compose-mode-map)
340 minor-mode-map-alist)))
342 (if (not (assq 'vm-pgg-compose-mode minor-mode-alist))
343 (setq minor-mode-alist
344 (cons '(vm-pgg-compose-mode vm-pgg-compose-mode-string) minor-mode-alist)))
346 (defun vm-pgg-compose-mode-activate ()
347 "Activate function `vm-pgg-compose-mode'."
348 (vm-pgg-compose-mode 1))
350 (add-hook 'vm-mail-mode-hook 'vm-pgg-compose-mode-activate t)
352 (defun vm-pgg-get-emails (headers)
353 "Return email addresses found in the given HEADERS."
354 (let (content recipients)
356 (setq content (vm-mail-mode-get-header-contents (car headers)))
358 (setq recipients (append (rfc822-addresses content) recipients)))
359 (setq headers (cdr headers)))
362 (defvar vm-pgg-get-recipients-headers '("To:" "CC:" "BCC:")
363 "The list of headers to get recipients from.")
365 (defun vm-pgg-get-recipients ()
366 "Return a list of recipients."
367 (vm-pgg-get-emails vm-pgg-get-recipients-headers))
369 (defun vm-pgg-get-author ()
370 "Return the author of the message."
371 (car (vm-pgg-get-emails vm-pgg-get-author-headers)))
373 (defun vm-pgp-goto-body-start ()
374 "Goto the start of the body and return point."
375 (goto-char (point-min))
376 (search-forward (concat "\n" mail-header-separator "\n"))
377 (goto-char (match-end 0))
380 (defun vm-pgp-prepare-composition ()
381 "Prepare the composition for encrypting or signing."
383 (unless (vm-mail-mode-get-header-contents "MIME-Version:")
384 (vm-mime-encode-composition))
385 ;; ensure newline at the end
386 (goto-char (point-max))
387 (skip-chars-backward " \t\r\n\f")
388 (delete-region (point) (point-max))
391 (vm-pgp-goto-body-start)
393 (make-local-variable 'pgg-default-user-id)
394 (setq pgg-default-user-id
396 (and vm-pgg-get-author-headers (vm-pgg-get-author))
397 pgg-default-user-id)))
400 (defun vm-pgg-cleartext-encrypt (sign)
401 "*Encrypt the composition as cleartext and with a prefix also SIGN it."
404 (vm-pgp-prepare-composition)
405 (let ((start (point)) (end (point-max)))
406 (unless (pgg-encrypt-region start end (vm-pgg-get-recipients) sign)
407 (pop-to-buffer pgg-errors-buffer)
408 (error "Encrypt error"))
409 (delete-region start end)
410 (insert-buffer-substring pgg-output-buffer))))
412 (defun vm-pgg-make-presentation-copy ()
413 "Make a presentation copy also for cleartext PGP messages."
414 (let* ((m (car vm-message-pointer))
415 (layout (vm-mm-layout m)))
416 ;; make a presentation copy
417 (vm-make-presentation-copy m)
418 (vm-save-buffer-excursion
419 (vm-replace-buffer-in-windows (current-buffer)
420 vm-presentation-buffer))
421 (set-buffer vm-presentation-buffer)
424 (goto-char (point-min))
426 (let ((buffer-read-only nil))
427 (delete-region (point-min) (point))
428 (vm-reorder-message-headers nil vm-visible-headers
429 vm-invisible-header-regexp)
430 (vm-decode-mime-message-headers m)
431 (when (vectorp layout)
432 ;; skip headers otherwise they get removed
433 (goto-char (point-min))
434 (search-forward "\n\n")
435 (vm-decode-mime-layout layout)
436 (delete-region (point) (point-max)))
437 (vm-energize-urls-in-message-region)
438 (vm-highlight-headers-maybe)
439 (vm-energize-headers-and-xfaces))))
441 (defvar vm-pgg-state nil
442 "State of the currently viewed message.")
444 (make-variable-buffer-local 'vm-pgg-state)
446 (defvar vm-pgg-state-message nil
447 "The message for `vm-pgg-state'.")
449 (make-variable-buffer-local 'vm-pgg-state-message)
451 (defvar vm-pgg-mode-line-items
452 (let ((items '((error " ERROR" vm-pgg-error-modeline)
453 (unknown " unknown" vm-pgg-unknown-signature-type-modeline)
454 (verified " verified" vm-pgg-good-signature-modeline)))
457 (while (and (featurep 'xemacs) items)
462 x (vm-make-extent 0 (length s) s))
463 (vm-set-extent-property x 'face f)
464 (setq items (cdr items))
465 (setq mode-line-items (append mode-line-items (list (list i x s)))))
467 "An alist mapping states to modeline strings.")
469 (if (not (member 'vm-pgg-state vm-mode-line-format))
470 (setq vm-mode-line-format (append '("" vm-pgg-state) vm-mode-line-format)))
472 (defun vm-pgg-state-set (&rest states)
473 "Set the message state displayed in the modeline acording to STATES.
474 If STATES is nil, clear it."
475 ;; clear state for a new message
477 (vm-select-folder-buffer-if-possible)
478 (when (not (equal (car vm-message-pointer) vm-pgg-state-message))
479 (setq vm-pgg-state-message (car vm-message-pointer))
480 (setq vm-pgg-state nil)
481 (when vm-presentation-buffer
483 (set-buffer vm-presentation-buffer)
484 (setq vm-pgg-state nil)))
485 (when vm-summary-buffer
487 (set-buffer vm-summary-buffer)
488 (setq vm-pgg-state nil))))
490 (if (and states (not vm-pgg-state))
491 (setq vm-pgg-state '("PGP:")))
496 vm-pgg-state (append vm-pgg-state
497 (list (or (cdr (assoc s vm-pgg-mode-line-items))
499 states (cdr states))))
501 (setq states vm-pgg-state)
502 (when vm-presentation-buffer
504 (set-buffer vm-presentation-buffer)
505 (setq vm-pgg-state states)))
506 (when vm-summary-buffer
508 (set-buffer vm-summary-buffer)
509 (setq vm-pgg-state states)))))
511 (defvar vm-pgg-cleartext-begin-regexp
512 "^-----BEGIN PGP \\(\\(SIGNED \\)?MESSAGE\\|PUBLIC KEY BLOCK\\)-----$"
513 "Regexp used to match PGP armor.")
515 (defvar vm-pgg-cleartext-end-regexp
516 "^-----END PGP %s-----$"
517 "Regexp used to match PGP armor.")
519 (defcustom vm-pgg-cleartext-search-limit 4096
520 "Number of bytes to peek into the message for a PGP clear text armor."
524 (defun vm-pgg-cleartext-automode-button (label action)
525 "Cleartext thing by a button with text LABEL and associate ACTION with it.
526 When the button is pressed ACTION is called."
528 (unless (eq major-mode 'vm-presentation-mode)
529 (vm-pgg-make-presentation-copy))
530 (goto-char (match-beginning 0))
531 (let ((buffer-read-only nil)
534 (if (re-search-forward (format vm-pgg-cleartext-end-regexp
537 (delete-region start (match-end 0)))
539 (setq o (make-overlay start (point)))
540 (overlay-put o 'vm-pgg t)
541 (overlay-put o 'face vm-mime-button-face)
542 (overlay-put o 'vm-button t)
543 (overlay-put o 'mouse-face 'highlight)
544 (let ((keymap (make-sparse-keymap)))
545 (define-key keymap [mouse-2] action)
546 (define-key keymap "\r" action)
547 (overlay-put o 'local-map keymap)))))
549 (defvar vm-pgg-cleartext-decoded nil
550 "State of the cleartext message.")
551 (make-variable-buffer-local 'vm-pgg-cleartext-decoded)
553 (defun vm-pgg-set-cleartext-decoded ()
555 (vm-select-folder-buffer)
556 (setq vm-pgg-cleartext-decoded (car vm-message-pointer))))
558 (defun vm-pgg-cleartext-automode ()
559 "Check for PGP ASCII armor and triggers automatic verification/decryption."
561 (vm-select-folder-buffer-if-possible)
562 (if (equal vm-pgg-cleartext-decoded (car vm-message-pointer))
563 (setq vm-pgg-cleartext-decoded nil)
564 (setq vm-pgg-cleartext-decoded nil)
565 (if vm-presentation-buffer
566 (set-buffer vm-presentation-buffer))
567 (goto-char (point-min))
568 (when (and (vm-mime-plain-message-p (car vm-message-pointer))
569 (re-search-forward vm-pgg-cleartext-begin-regexp
570 (+ (point) vm-pgg-cleartext-search-limit)
572 (cond ((string= (match-string 1) "SIGNED MESSAGE")
573 (vm-pgg-set-cleartext-decoded)
574 (vm-pgg-cleartext-verify))
575 ((string= (match-string 1) "MESSAGE")
576 (vm-pgg-set-cleartext-decoded)
577 (if vm-pgg-auto-decrypt
578 (vm-pgg-cleartext-decrypt)
579 (vm-pgg-cleartext-automode-button
580 "Decrypt PGP message\n"
583 (let ((vm-pgg-auto-decrypt t))
584 (vm-pgg-cleartext-decrypt))))))
585 ((string= (match-string 1) "PUBLIC KEY BLOCK")
586 (vm-pgg-set-cleartext-decoded)
587 (if vm-pgg-auto-snarf
589 (vm-pgg-cleartext-automode-button
593 (let ((vm-pgg-auto-snarf t))
594 (vm-pgg-snarf-keys))))))
596 (error "This should never happen!")))))))
598 (defadvice vm-preview-current-message (after vm-pgg-cleartext-automode activate)
599 "Decode or check signature on clear text messages."
601 (when (and vm-pgg-cleartext-decoded
602 (not (equal vm-pgg-cleartext-decoded (car vm-message-pointer))))
603 (setq vm-pgg-cleartext-decoded nil))
604 (when (and (not (eq vm-system-state 'previewing))
605 (not vm-mime-decoded))
606 (vm-pgg-cleartext-automode)))
608 (defadvice vm-scroll-forward (around vm-pgg-cleartext-automode activate)
609 "Decode or check signature on clear text messages."
610 (let ((vm-system-state-was
612 (vm-select-folder-buffer-if-possible)
616 (when (and (eq vm-system-state-was 'previewing)
617 (not vm-mime-decoded))
618 (vm-pgg-cleartext-automode))))
621 (defun vm-pgg-cleartext-sign ()
625 (vm-pgp-prepare-composition)
626 (let ((start (point)) (end (point-max)))
627 (unless (pgg-sign-region start end t)
628 (pop-to-buffer pgg-errors-buffer)
629 (error "Signing error"))
630 (delete-region start end)
631 (insert-buffer-substring pgg-output-buffer))))
633 (defun vm-pgg-cleartext-cleanup (status)
634 "Removed ASCII armor and insert PGG output depending on STATUS."
636 (setq start (and (re-search-forward "^-----BEGIN PGP SIGNED MESSAGE-----$")
638 end (and (search-forward "\n\n")
640 (delete-region start end)
641 (setq start (and (re-search-forward "^-----BEGIN PGP SIGNATURE-----$")
643 end (and (re-search-forward "^-----END PGP SIGNATURE-----$")
645 (delete-region start end)
646 ;; add output from PGP
648 (let ((start (point)) end)
649 (if (eq status 'error)
650 (insert-buffer-substring pgg-errors-buffer)
651 (insert-buffer-substring pgg-output-buffer)
652 (vm-pgg-crlf-cleanup start (point)))
654 (put-text-property start end 'face
655 (if (eq status 'error)
656 'vm-pgg-bad-signature
657 'vm-pgg-good-signature)))))
659 (defadvice vm-mime-transfer-decode-region (around vm-pgg-cleartext-automode activate)
660 "Decode or check signature on clear text messages parts."
661 (let ((vm-pgg-part-start (point)))
663 ;; BUGME should we use marks here?
664 (when (and (vm-mime-text-type-layout-p (ad-get-arg 0))
665 (< vm-pgg-part-start (point)))
668 (narrow-to-region vm-pgg-part-start (point))
669 (vm-pgg-cleartext-automode)
671 ; (set-window-start (selected-window) 0)
675 (defadvice vm-mime-display-internal-text/plain (around vm-pgg-cleartext-automode activate)
676 "Decode or check signature on clear text messages parts.
677 We use the advice here in order to avoid overwriting VMs internal text display
678 function. Faces will get lost if a charset conversion happens thus we do the
679 cleanup here after verification and decoding took place."
680 (let ((vm-pgg-cleartext-state nil)
684 (when vm-pgg-cleartext-state
687 (narrow-to-region start end)
688 (goto-char (point-min))
689 (vm-pgg-cleartext-cleanup vm-pgg-cleartext-state)
693 (defun vm-pgg-cleartext-verify ()
694 "*Verify the signature in the current message."
696 (message "Verifying PGP cleartext message...")
697 (when (interactive-p)
698 (vm-follow-summary-cursor)
699 (vm-select-folder-buffer-if-possible)
700 (vm-check-for-killed-summary)
701 (vm-error-if-folder-empty))
703 ;; make a presentation copy
704 (unless (eq major-mode 'vm-presentation-mode)
705 (vm-pgg-make-presentation-copy))
709 (goto-char (point-min))
710 (let ((buffer-read-only nil)
711 (status (pgg-verify-region (point) (point-max) nil
712 vm-pgg-fetch-missing-keys)))
714 (vm-pgg-state-set 'signed)
715 (setq status (if (not status) 'error 'verified))
716 (vm-pgg-state-set status)
717 (if (boundp 'vm-pgg-cleartext-state)
718 (setq vm-pgg-cleartext-state status)
719 (vm-pgg-cleartext-cleanup status)))))
722 (defun vm-pgg-cleartext-decrypt ()
723 "*Decrypt the contents of the current message."
726 (vm-follow-summary-cursor))
727 (vm-select-folder-buffer-if-possible)
728 (vm-check-for-killed-summary)
729 (vm-error-if-folder-read-only)
730 (vm-error-if-folder-empty)
732 ;; make a presentation copy
733 (unless (eq major-mode 'vm-presentation-mode)
734 (vm-pgg-make-presentation-copy))
735 (goto-char (point-min))
738 (let (state start end)
739 (setq start (and (re-search-forward "^-----BEGIN PGP MESSAGE-----$")
741 end (and (re-search-forward "^-----END PGP MESSAGE-----$")
743 state (condition-case nil
744 (pgg-decrypt-region start end)
747 (vm-pgg-state-set 'encrypted)
750 ;; insert the error message
751 (let ((buffer-read-only nil))
752 (vm-pgg-state-set 'error)
754 (insert-buffer-substring pgg-errors-buffer)
755 (put-text-property start (point) 'face 'vm-pgg-error))
756 ;; replace it with decrypted message
757 (let ((buffer-read-only nil))
758 (delete-region start end)
759 (insert-buffer-substring pgg-output-buffer))
760 ;; if it signed then also verify it
762 (if (looking-at "^-----BEGIN PGP \\(SIGNED \\)?MESSAGE-----$")
763 (vm-pgg-cleartext-verify)))))
765 (defun vm-pgg-crlf-cleanup (start end)
766 "Convert CRLF to LF in region from START to END."
769 (while (search-forward "\r\n" end t)
770 (replace-match "\n" t t))))
772 (defun vm-pgg-make-crlf (start end)
773 "Convert CRLF to LF in region from START to END."
776 (while (search-backward "\n" start t)
777 (replace-match "\r\n" t t)
780 (defvar vm-pgg-mime-decoded nil
781 "Saves decoded state for later use, i.e. decoding to buttons.")
782 (make-variable-buffer-local 'vm-pgg-mime-decoded)
784 (defun vm-pgg-get-mime-decoded ()
785 "Return `vm-pgg-mime-decoded'."
787 (vm-select-folder-buffer)
788 vm-pgg-mime-decoded))
790 (defvar vm-pgg-recursion nil
791 "Detect recursive calles.")
793 (defadvice vm-decode-mime-message (around vm-pgg-clear-state activate)
794 "Clear the modeline state before decoding."
795 (vm-select-folder-buffer)
796 (when (not vm-pgg-recursion)
797 (setq vm-pgg-mime-decoded vm-mime-decoded))
798 (setq vm-pgg-state-message nil)
799 (setq vm-pgg-state nil)
800 (if (vm-mime-plain-message-p (car vm-message-pointer))
801 (if vm-pgg-cleartext-decoded
802 (vm-preview-current-message))
803 (let ((vm-pgg-recursion t))
806 (defun vm-pgg-mime-decrypt (button)
807 "Replace the BUTTON with the output from `pgg-snarf-keys'."
808 (let ((vm-pgg-auto-decrypt t)
809 (layout (copy-sequence (vm-extent-property button 'vm-mime-layout))))
810 (vm-set-extent-property button 'vm-mime-disposable t)
811 (vm-set-extent-property button 'vm-mime-layout layout)
812 (goto-char (vm-extent-start-position button))
813 (let ((buffer-read-only nil))
814 (vm-decode-mime-layout button t))))
817 (defun vm-mime-display-internal-multipart/encrypted (layout)
818 "Display multipart/encrypted LAYOUT."
819 (vm-pgg-state-set 'encrypted)
820 (let* ((part-list (vm-mm-layout-parts layout))
821 (header (car part-list))
822 (message (car (cdr part-list)))
824 (cond ((eq (vm-pgg-get-mime-decoded) 'decoded)
825 ;; after decode the state of vm-mime-decoded is 'buttons
827 ((not (and (= (length part-list) 2)
828 (vm-mime-types-match (car (vm-mm-layout-type header))
829 "application/pgp-encrypted")
830 ;; TODO: check version and protocol here?
831 (vm-mime-types-match (car (vm-mm-layout-type message))
832 "application/octet-stream")))
833 (insert "Unknown multipart/encrypted format."))
834 ((not vm-pgg-auto-decrypt)
836 (let ((buffer-read-only nil))
837 (vm-mime-insert-button
838 (vm-mime-sprintf (vm-mime-find-format-for-layout layout) layout)
842 ;; decode the message now
844 (set-buffer (vm-buffer-of (vm-mm-layout-message message)))
847 (setq status (pgg-decrypt-region (vm-mm-layout-body-start message)
848 (vm-mm-layout-body-end message)))))
850 (let ((start (point)))
851 (vm-pgg-state-set 'error)
852 (insert-buffer-substring pgg-errors-buffer)
853 (put-text-property start (point) 'face 'vm-pgg-error))
855 (set-buffer pgg-output-buffer)
856 (vm-pgg-crlf-cleanup (point-min) (point-max))
857 (setq message (vm-mime-parse-entity-safe nil nil nil t)))
859 (vm-decode-mime-layout message)
860 (insert-buffer-substring pgg-output-buffer))
861 (setq status (save-excursion
862 (set-buffer pgg-errors-buffer)
863 (goto-char (point-min))
864 ;; TODO: care for BADSIG
865 (when (re-search-forward "GOODSIG [^\n\r]+" (point-max) t)
866 (vm-pgg-state-set 'signed 'verified)
867 (buffer-substring (match-beginning 0) (match-end 0)))))
869 (let ((start (point)))
870 (insert "\n" status "\n")
871 (put-text-property start (point) 'face 'vm-pgg-good-signature))))
875 (defun vm-mime-display-internal-multipart/signed (layout)
876 "Display multipart/signed LAYOUT."
877 (vm-pgg-state-set 'signed)
878 (let* ((part-list (vm-mm-layout-parts layout))
879 (message (car part-list))
880 (signature (car (cdr part-list)))
881 status signature-file start end)
882 (cond ((eq (vm-pgg-get-mime-decoded) 'decoded)
883 ;; after decode the state of vm-mime-decoded is 'buttons
885 ((not (and (= (length part-list) 2)
887 ;; TODO: check version and protocol here?
888 (vm-mime-types-match (car (vm-mm-layout-type signature))
889 "application/pgp-signature")))
890 ;; insert the message
891 (vm-decode-mime-layout message)
893 (vm-pgg-state-set 'unknown)
897 "******* unknown signature type %s *******\n"
898 (car (and signature (vm-mm-layout-type signature)))))
901 (vm-decode-mime-layout signature))
902 (put-text-property start end 'face 'vm-pgg-unknown-signature-type))
905 ;; insert the message
906 (vm-decode-mime-layout message)
907 ;; write signature to a temp file
909 (vm-mime-insert-mime-body signature)
911 (write-region start end
912 (setq signature-file (pgg-make-temp-file "vm-pgg-signature")))
913 (delete-region start end)
915 (vm-insert-region-from-buffer (marker-buffer (vm-mm-layout-header-start
917 (vm-mm-layout-header-start message)
918 (vm-mm-layout-body-end message))
919 (setq end (point-marker))
920 (vm-pgg-make-crlf start end)
921 (setq status (pgg-verify-region start end signature-file
922 vm-pgg-fetch-missing-keys))
923 (delete-file signature-file)
924 (delete-region start end)
925 ;; now insert the content
927 (let ((start (point)) end)
930 (vm-pgg-state-set 'error)
931 (insert-buffer-substring pgg-errors-buffer))
932 (vm-pgg-state-set 'verified)
933 (insert-buffer-substring
934 (if vm-fsfemacs-p pgg-errors-buffer pgg-output-buffer))
935 (vm-pgg-crlf-cleanup start (point)))
937 (put-text-property start end 'face
938 (if status 'vm-pgg-good-signature
939 'vm-pgg-bad-signature)))
942 ;; we must add these in order to force VM to call our handler
944 ;; (if (listp vm-auto-displayed-mime-content-types)
945 ;; (add-to-list 'vm-auto-displayed-mime-content-types "application/pgp-keys"))
946 (if (listp vm-mime-internal-content-types)
947 (add-to-list 'vm-mime-internal-content-types "application/pgp-keys"))
948 (add-to-list 'vm-mime-button-format-alist
949 '("application/pgp-keys" . "Snarf %d"))
950 (add-to-list 'vm-mime-button-format-alist
951 '("multipart/encrypted" . "Decrypt PGP/MIME message")))
953 (defun vm-pgg-mime-snarf-keys (button)
954 "Replace the BUTTON with the output from `pgg-snarf-keys'."
955 (let ((vm-pgg-auto-snarf t)
956 (layout (copy-sequence (vm-extent-property button 'vm-mime-layout))))
957 (vm-set-extent-property button 'vm-mime-disposable t)
958 (vm-set-extent-property button 'vm-mime-layout layout)
959 (goto-char (vm-extent-start-position button))
960 (let ((buffer-read-only nil))
961 (vm-decode-mime-layout button t))))
964 (defun vm-mime-display-internal-application/pgp-keys (layout)
965 "Snarf keys in LAYOUT and display result of snarfing."
966 (vm-pgg-state-set 'public-key)
968 (if vm-pgg-auto-snarf
969 (let ((start (point)) end status)
970 (vm-mime-insert-mime-body layout)
971 (setq end (point-marker))
972 (vm-mime-transfer-decode-region layout start end)
974 (setq status (pgg-snarf-keys-region start end)))
975 (delete-region start end)
976 ;; now insert the result of snafing
978 (insert-buffer-substring pgg-output-buffer)
979 (insert-buffer-substring pgg-errors-buffer)))
980 (let ((buffer-read-only nil))
981 (vm-mime-insert-button
982 (vm-mime-sprintf (vm-mime-find-format-for-layout layout) layout)
983 'vm-pgg-mime-snarf-keys
988 (defun vm-pgg-snarf-keys ()
989 "*Snarf keys from the current message."
992 (vm-follow-summary-cursor))
993 (vm-select-folder-buffer)
994 (vm-check-for-killed-summary)
995 (vm-error-if-folder-empty)
997 ;; ensure we are in the right buffer
998 (if vm-presentation-buffer
999 (set-buffer vm-presentation-buffer))
1001 (goto-char (point-min))
1002 (search-forward "\n\n")
1003 (goto-char (match-end 0))
1005 (unless (pgg-snarf-keys)
1006 (error "Snarfing failed"))
1008 (set-buffer (if vm-fsfemacs-p pgg-errors-buffer pgg-output-buffer))
1009 (message (buffer-substring (point-min) (point-max))))))
1012 (defun vm-pgg-attach-public-key ()
1013 "Attach your public key to a composition."
1015 (let* ((pgg-default-user-id
1017 (and vm-pgg-get-author-headers (vm-pgg-get-author))
1018 pgg-default-user-id))
1019 (description (concat "public key of " pgg-default-user-id))
1020 (buffer (get-buffer-create (concat " *" description "*")))
1025 (setq start (point))
1027 (if (= start (point))
1028 (error "%s has no public key!" pgg-default-user-id)))
1030 (goto-char (point-max))
1032 (setq start (point))
1033 (vm-mime-attach-object buffer
1034 "application/pgp-keys"
1035 (list (concat "name=\"" pgg-default-user-id ".asc\""))
1038 ;; a crude hack to set the disposition
1039 (let ((disposition (list "attachment"
1040 (concat "filename=\"" pgg-default-user-id ".asc\"")))
1042 (if (featurep 'xemacs)
1043 (set-extent-property (extent-at start nil 'vm-mime-disposition)
1044 'vm-mime-disposition disposition)
1045 (put-text-property start end 'vm-mime-disposition disposition))))))
1047 (defun vm-pgg-make-multipart-boundary (word)
1048 "Create a mime part boundery starting with WORD and return it.
1050 We cannot use `vm-mime-make-multipart-boundary' as it uses the current time as
1051 seed and thus creates the same boundery when called twice in a short period."
1052 (if word (setq word (concat word "+")))
1053 (let ((boundary (concat word (make-string 15 ?a)))
1056 (while (< i (length boundary))
1057 (aset boundary i (aref vm-mime-base64-alphabet
1058 (% (vm-abs (lsh (random) -8))
1059 (length vm-mime-base64-alphabet))))
1063 (defun vm-pgg-save-work (function &rest args)
1064 "Call FUNCTION with ARGS without messing up the composition in case of an error."
1065 (let ((composition-buffer (current-buffer))
1066 (undo-list-backup buffer-undo-list)
1067 (work-buffer (get-buffer-create " *VM-PGG-WORK*")))
1069 (set-buffer work-buffer)
1070 (buffer-disable-undo)
1072 (insert-buffer-substring composition-buffer)
1073 (setq major-mode 'mail-mode)
1074 (apply function args))
1076 (insert-buffer-substring work-buffer)
1077 (kill-buffer work-buffer)))
1080 (defun vm-pgg-sign ()
1081 "Sign the composition with PGP/MIME.
1083 If the composition is not encoded so far, it is encoded before signing.
1084 Signing of already encoded messages is discouraged.
1086 RFC 2015 and its successor 3156 forbid the use of 8bit encoding for signed
1087 messages, but require to use quoted-printable or base64 instead. Also lines
1088 starting with \"From \" cause trouble and should be quoted.
1090 Thus signing of encoded messages may cause an error. To avoid this you must
1091 set `vm-mime-8bit-text-transfer-encoding' to something different than 8bit and
1092 `vm-mime-composition-armor-from-lines' to t.
1094 The transfer encoding done by `vm-pgg-sign' can be controlled by the variable
1095 `vm-pgg-sign-text-transfer-encoding'."
1098 (when (vm-mail-mode-get-header-contents "MIME-Version:")
1099 ;; do a simple sanity check ... too simple as we should walk the MIME part
1100 ;; hierarchy and only check the MIME headers ...
1101 (goto-char (point-min))
1102 (when (re-search-forward "Content-Transfer-Encoding:\\s-*8bit" nil t)
1103 (describe-function 'vm-pgg-sign)
1104 (error "Signing is broken for 8bit encoding!"))
1105 (goto-char (point-min))
1106 (when (re-search-forward "^From\\s-+" nil t)
1107 (describe-function 'vm-pgg-sign)
1108 (error "Signing is broken for lines starting with \"From \"!")))
1110 (vm-pgg-save-work 'vm-pgg-sign-internal))
1112 (defun vm-pgg-sign-internal ()
1114 ;; prepare composition
1115 (let ((vm-mime-8bit-text-transfer-encoding
1116 vm-pgg-sign-text-transfer-encoding)
1117 (vm-mime-composition-armor-from-lines t))
1118 (vm-pgp-prepare-composition))
1120 (let ((content-type (vm-mail-mode-get-header-contents "Content-Type:"))
1121 (encoding (vm-mail-mode-get-header-contents "Content-Transfer-Encoding:"))
1122 (boundary (vm-pgg-make-multipart-boundary "pgp+signed"))
1123 (pgg-text-mode t) ;; For GNU Emacs PGG
1128 (setq body-start (vm-marker (vm-pgp-goto-body-start)))
1129 (insert "Content-Type: " (or content-type "text/plain") "\n")
1130 (insert "Content-Transfer-Encoding: " (or encoding "7bit") "\n")
1131 (if (not (looking-at "\n"))
1133 ;; now create the signature
1135 ;; BUGME do we need the CRLF conversion?
1136 ; (vm-pgg-make-crlf (point) (point-max))
1137 (unless (pgg-sign-region body-start (point-max) nil)
1138 (pop-to-buffer pgg-errors-buffer)
1139 (error "Signing error"))
1140 (and (setq entry (assq 2 (pgg-parse-armor
1141 (with-current-buffer pgg-output-buffer
1143 (setq entry (assq 'hash-algorithm (cdr entry)))
1145 (setq micalg (downcase (format "%s" (cdr entry)))))))
1146 ;; insert mime part bounderies
1147 (goto-char body-start)
1148 (insert "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)\n")
1149 (insert "--" boundary "\n")
1150 (goto-char (point-max))
1151 (insert "\n--" boundary "\n")
1152 ;; insert the signature
1153 (insert "Content-Type: application/pgp-signature\n\n")
1154 (goto-char (point-max))
1155 (insert-buffer-substring pgg-output-buffer)
1156 (insert "\n--" boundary "--\n")
1158 (vm-mail-mode-remove-header "MIME-Version:")
1159 (vm-mail-mode-remove-header "Content-Type:")
1160 (vm-mail-mode-remove-header "Content-Transfer-Encoding:")
1161 (mail-position-on-field "MIME-Version")
1163 (mail-position-on-field "Content-Type")
1164 (insert "multipart/signed; boundary=\"" boundary "\";\n"
1165 "\tmicalg=pgg-" micalg "; protocol=\"application/pgp-signature\"")))
1168 (defun vm-pgg-encrypt (&optional sign)
1169 "Encrypt the composition as PGP/MIME. With a prefix arg SIGN also sign it."
1171 (vm-pgg-save-work 'vm-pgg-encrypt-internal sign))
1173 (defun vm-pgg-encrypt-internal (sign)
1174 "Do the encrypting, if SIGN is t also sign it."
1175 (unless (vm-mail-mode-get-header-contents "MIME-Version:")
1176 (vm-mime-encode-composition))
1177 (let ((content-type (vm-mail-mode-get-header-contents "Content-Type:"))
1178 (encoding (vm-mail-mode-get-header-contents "Content-Transfer-Encoding:"))
1179 (boundary (vm-pgg-make-multipart-boundary "pgp+encrypted"))
1180 (pgg-text-mode t) ;; For GNU Emacs PGG
1183 (setq body-start (vm-marker (vm-pgp-goto-body-start)))
1184 (insert "Content-Type: " (or content-type "text/plain") "\n")
1185 (insert "Content-Transfer-Encoding: " (or encoding "7bit") "\n")
1187 (goto-char (point-max))
1189 (vm-pgg-cleartext-encrypt sign)
1190 (goto-char body-start)
1191 (insert "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)\n")
1192 (insert "--" boundary "\n")
1193 (insert "Content-Type: application/pgp-encrypted\n\n")
1194 (insert "Version: 1\n\n")
1195 (insert "--" boundary "\n")
1196 (insert "Content-Type: application/octet-stream\n\n")
1197 (goto-char (point-max))
1198 (insert "\n--" boundary "--\n")
1200 (vm-mail-mode-remove-header "MIME-Version:")
1201 (vm-mail-mode-remove-header "Content-Type:")
1202 (vm-mail-mode-remove-header "Content-Transfer-Encoding:")
1203 (mail-position-on-field "MIME-Version")
1205 (mail-position-on-field "Content-Type")
1206 (insert "multipart/encrypted; boundary=\"" boundary "\";\n"
1207 "\tprotocol=\"application/pgp-encrypted\"")))
1209 (defun vm-pgg-sign-and-encrypt ()
1210 "*Sign and encrypt the composition as PGP/MIME."
1214 (defvar vm-pgg-prompt-last-action nil
1215 "The action last taken in `vm-pgg-prompt-for-action'.")
1217 (defvar vm-pgg-prompt-action-alist
1219 (?e encrypt "encrypt")
1220 (?E sign-and-encrypt "both")
1223 "Alist of (KEY ACTION LABEL) elements.")
1225 (defun vm-pgg-prompt-for-action ()
1226 "Prompt for an action and return it. See also `vm-pgg-prompt-action-alist'."
1228 (let (prompt event action)
1229 (setq prompt (mapconcat (lambda (a)
1230 (format "%s (%c)" (nth 2 a) (car a)))
1231 vm-pgg-prompt-action-alist ", ")
1232 action (mapcar (lambda (a)
1234 vm-pgg-prompt-last-action)
1235 (downcase (nth 2 a))))
1236 vm-pgg-prompt-action-alist)
1237 prompt (format "%s (default %s)?"
1239 (car (delete nil action)))
1242 (setq event (read-key-sequence prompt))
1243 (if (featurep 'xemacs)
1244 (setq event (event-to-character (aref event 0)))
1245 (setq event (if (stringp event) (aref event 0))))
1247 (setq action vm-pgg-prompt-last-action)
1248 (setq action (assoc event vm-pgg-prompt-action-alist))
1250 (setq action (nth 1 action))
1252 (when (eq action 'quit)
1253 (error "Sending aborted!"))
1255 (message "Action is %s." action)
1256 (message "No action selected."))
1257 (setq vm-pgg-prompt-last-action action)
1261 (defun vm-pgg-ask-hook ()
1262 "Hook to automatically ask for signing or encrypting outgoing messages with PGP/MIME.
1264 Put this function into `vm-mail-send-hook' to be asked each time you
1265 send a message whether or not you want to sign or encrypt the
1266 message. See `vm-pgg-ask-function' to determine which function is
1269 This hook should probably be the last of your hooks if you have several
1270 other functions there. Signing crucially relies on the fact that the
1271 message is not altered afterwards. To put it into `vm-mail-send-hook'
1274 (add-hook 'vm-mail-send-hook 'vm-pgg-ask-hook t)
1276 into your VM init file."
1279 ;; ensure we are the last hook
1280 (when (and (member 'vm-pgg-ask-hook vm-mail-send-hook)
1281 (cdr (member 'vm-pgg-ask-hook vm-mail-send-hook)))
1282 (describe-function 'vm-pgg-ask-hook)
1283 (error "`vm-pgg-ask-function' must be the last hook in `vm-mail-send-hook'!"))
1285 (let ((handler vm-pgg-ask-function)
1288 (setq action (if (fboundp handler)
1290 (if (y-or-n-p (format "%s the composition? " handler))
1293 (funcall (intern (format "vm-pgg-%s" action)))))))
1297 ;;; vm-pgg.el ends here