1 ;; mc-toplev.el, entry point functions for Mailcrypt
2 ;; Copyright (C) 1995 Jin Choi <jsc@mit.edu>
3 ;; Patrick LoPresti <patl@lcs.mit.edu>
6 ;; This file is intended to be used with GNU Emacs.
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING. If not, write to
20 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
22 ;;{{{ Load some required packages
29 (condition-case nil (require 'rmail) (error nil))
30 (autoload 'rmail-abort-edit "rmailedit")
31 (autoload 'rmail-cease-edit "rmailedit")
32 ;; Is this a good idea?
33 (defvar rmail-buffer nil)
36 (condition-case nil (require 'vm) (error nil))
39 (condition-case nil (require 'gnus) (error nil))
40 (autoload 'gnus-summary-select-article "gnus-sum")
41 (autoload 'gnus-summary-edit-article "gnus-sum")
42 (autoload 'gnus-summary-edit-article-postpone "gnus-sum")
43 (autoload 'gnus-summary-edit-article-done "gnus-sum")
46 (condition-case nil (require 'mh-e) (error nil))
47 (autoload 'mh-in-show-buffer "mh-utils")
48 (autoload 'mh-invalidate-show-buffer "mh-utils")
49 (autoload 'mh-msg-filename "mh-utils")
50 (autoload 'mh-show "mh-utils")
53 (condition-case nil (require 'mew) (error nil))
54 (autoload 'mew-summary-display "mew-summary")
55 (autoload 'mew-buffer-message "mew")
60 (condition-case nil (require 'mailalias) (error nil)))
62 (autoload 'mc-scheme-pgp "mc-pgp" nil t)
63 (autoload 'mc-scheme-pgp50 "mc-pgp5" nil t)
64 (autoload 'mc-scheme-gpg "mc-gpg" nil t)
70 (defun mc-cleanup-recipient-headers (str)
71 ;; Takes a comma separated string of recipients to encrypt for and,
72 ;; assuming they were possibly extracted from the headers of a reply,
73 ;; returns a list of the address components.
74 (mapcar 'mc-strip-address
75 (rfc822-addresses str)))
77 (defun mc-find-headers-end ()
79 (goto-char (point-min))
81 (concat "^" (regexp-quote mail-header-separator) "\n"))
82 (if (looking-at "^::\n")
83 (re-search-forward "^\n" nil t))
84 (if (looking-at "^##\n")
85 (re-search-forward "^\n" nil t))
88 (defun mc-encrypt (arg)
89 "*Encrypt the current buffer.
91 Exact behavior depends on current major mode.
93 With \\[universal-argument], prompt for User ID to sign as.
95 With \\[universal-argument] \\[universal-argument], prompt for encryption scheme to use."
97 (mc-encrypt-region arg nil nil))
99 (defun mc-encrypt-region (arg start end)
100 "*Encrypt the current region."
102 (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist)))
103 (func (or (cdr-safe (assq 'encrypt mode-alist))
104 'mc-encrypt-generic))
107 (setq from (read-string "User ID: ")
112 (completing-read "Encryption Scheme: " mc-schemes)
114 (funcall func nil scheme start end from sign)))
116 (defun mc-encrypt-generic (&optional recipients scheme start end from sign)
117 "*Generic function to encrypt a region of data."
119 (or start (setq start (point-min-marker)))
120 (or (markerp start) (setq start (copy-marker start)))
121 (or end (setq end (point-max-marker)))
122 (or (markerp end) (setq end (copy-marker end)))
123 (run-hooks 'mc-pre-encryption-hook)
124 (cond ((stringp recipients)
126 (mc-split "\\([ \t\n]*,[ \t\n]*\\)+" recipients)))
129 (mc-cleanup-recipient-headers (read-string "Recipients: "))))
130 (t (error "mc-encrypt-generic: recipients not string or nil")))
131 (or scheme (setq scheme mc-default-scheme))
132 (if (funcall (cdr (assoc 'encryption-func (funcall scheme)))
133 recipients start end from sign)
135 (run-hooks 'mc-post-encryption-hook)
138 (defun mc-encrypt-message (&optional recipients scheme start end from sign)
139 "*Encrypt a message for RECIPIENTS using the given encryption SCHEME.
140 RECIPIENTS is a comma separated string. If SCHEME is nil, use the value
141 of `mc-default-scheme'. Returns t on success, nil otherwise."
143 (let ((headers-end (mc-find-headers-end))
146 (setq default-recipients
148 (goto-char (point-min))
150 (concat "^" (regexp-quote mail-header-separator) "$"))
151 (narrow-to-region (point-min) (point))
152 (and (featurep 'mailalias)
153 (not (featurep 'mail-abbrevs))
155 (expand-mail-aliases (point-min) (point-max)))
158 (mc-get-fields "to\\|cc\\|bcc")))))
162 (goto-char (point-min))
164 (concat "^" (regexp-quote mail-header-separator) "\n"))
165 (narrow-to-region (point) headers-end)
166 (setq from (mail-fetch-field "From"))))
170 (if mc-use-default-recipients
172 (read-from-minibuffer "Recipients: " default-recipients))))
174 (or start (setq start headers-end))
175 (or end (setq end (point-max-marker)))
177 (mc-encrypt-generic recipients scheme start end from sign))))
184 "*Decrypt a message in the current buffer.
186 Exact behavior depends on current major mode."
188 (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist)))
189 (func (or (cdr-safe (assq 'decrypt mode-alist))
190 'mc-decrypt-message)))
193 (defun mc-decrypt-message ()
194 "Decrypt whatever message is in the current buffer.
195 Returns a pair (SUCCEEDED . VERIFIED) where SUCCEEDED is t if the encryption
196 succeeded and VERIFIED is t if it had a valid signature."
198 (let ((schemes mc-schemes)
200 (scheme mc-default-scheme))
202 ; Attempt to find a message signed according to the default
204 (if mc-default-scheme
207 (mc-message-delimiter-positions
208 (cdr (assoc 'msg-begin-line (funcall mc-default-scheme)))
209 (cdr (assoc 'msg-end-line (funcall mc-default-scheme))))))
211 ; We can't find a message signed in the default scheme.
212 ; Step through all the schemes we know, trying to identify
213 ; the applicable one by examining headers.
214 (while (and (null limits)
216 (setq scheme (cdr (car schemes)))
219 (mc-message-delimiter-positions
220 (cdr (assoc 'msg-begin-line (funcall scheme)))
221 (cdr (assoc 'msg-end-line (funcall scheme)))))))
222 (setq schemes (cdr schemes)))
225 (error "Found no encrypted message in this buffer.")
226 (run-hooks 'mc-pre-decryption-hook)
227 (let ((resultval (funcall (cdr (assoc 'decryption-func
229 (car limits) (cdr limits))))
230 (goto-char (point-min))
231 (if (car resultval) ; decryption succeeded
232 (run-hooks 'mc-post-decryption-hook))
237 "*Sign a message in the current buffer.
239 Exact behavior depends on current major mode.
241 With one prefix arg, prompts for private key to use, with two prefix args,
242 also prompts for encryption scheme to use. With negative prefix arg,
243 inhibits clearsigning (pgp)."
245 (mc-sign-region arg nil nil))
247 (defun mc-sign-region (arg start end)
248 "*Sign the current region."
250 (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist)))
251 (func (or (cdr-safe (assq 'sign mode-alist))
257 (completing-read "Encryption Scheme: " mc-schemes)
260 (setq from (read-string "User ID: ")))
262 (funcall func from scheme start end (< arg 0))))
264 (defun mc-sign-generic (withkey scheme start end unclearsig)
265 (or scheme (setq scheme mc-default-scheme))
266 (or start (setq start (point-min-marker)))
267 (or (markerp start) (setq start (copy-marker start)))
268 (or end (setq end (point-max-marker)))
269 (or (markerp end) (setq end (copy-marker end)))
270 (run-hooks 'mc-pre-signature-hook)
271 (if (funcall (cdr (assoc 'signing-func (funcall scheme)))
272 start end withkey unclearsig)
274 (run-hooks 'mc-post-signature-hook)
277 (defun mc-sign-message (&optional withkey scheme start end unclearsig)
278 "Clear sign the message."
280 (let ((headers-end (mc-find-headers-end)))
283 (goto-char (point-min))
285 (concat "^" (regexp-quote mail-header-separator) "\n"))
287 (narrow-to-region (point) headers-end)
288 (setq withkey (mail-fetch-field "From")))))
289 (or start (setq start headers-end))
290 (or end (setq end (point-max-marker)))
291 (mc-sign-generic withkey scheme start end unclearsig))))
294 ;;{{{ Signature verification
297 "*Verify a message in the current buffer.
299 Exact behavior depends on current major mode."
301 (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist)))
302 (func (or (cdr-safe (assq 'verify mode-alist))
303 'mc-verify-signature)))
306 (defun mc-verify-signature ()
307 "*Verify the signature of the signed message in the current buffer.
308 Show the result as a message in the minibuffer. Returns t if the signature
311 (let ((schemes mc-schemes)
313 (scheme mc-default-scheme))
315 ; Attempt to find a message signed according to the default
317 (if mc-default-scheme
320 (mc-message-delimiter-positions
321 (cdr (assoc 'signed-begin-line (funcall mc-default-scheme)))
322 (cdr (assoc 'signed-end-line (funcall mc-default-scheme))))))
324 ; We can't find a message signed in the default scheme.
325 ; Step through all the schemes we know, trying to identify
326 ; the applicable one by examining headers.
327 (while (and (null limits)
329 (setq scheme (cdr (car schemes)))
333 (mc-message-delimiter-positions
334 (cdr (assoc 'signed-begin-line (funcall scheme)))
335 (cdr (assoc 'signed-end-line (funcall scheme)))))))
336 (setq schemes (cdr schemes)))
339 (error "Found no signed message in this buffer.")
340 (funcall (cdr (assoc 'verification-func (funcall scheme)))
341 (car limits) (cdr limits))))))
347 ;;{{{ mc-insert-public-key
349 (defun mc-insert-public-key (&optional userid scheme)
350 "*Insert your public key at point.
351 With one prefix arg, prompts for user id to use. With two prefix
352 args, prompts for encryption scheme."
355 (if (not (and (listp current-prefix-arg)
356 (numberp (car current-prefix-arg))))
358 (if (>= (car current-prefix-arg) 16)
360 (cons (cdr (assoc (completing-read "Encryption Scheme: "
364 (if (>= (car current-prefix-arg) 4)
365 (setq arglist (cons (read-string "User ID: ") arglist))))
368 ; (if (< (point) (mc-find-headers-end))
369 ; (error "Can't insert key inside message header"))
370 (or scheme (setq scheme mc-default-scheme))
371 (or userid (setq userid (cdr (assoc 'user-id (funcall scheme)))))
373 ;; (goto-char (point-max))
376 (funcall (cdr (assoc 'key-insertion-func (funcall scheme))) userid))
382 "*Add all public keys in the buffer to your keyring.
384 Exact behavior depends on current major mode."
386 (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist)))
387 (func (or (cdr-safe (assq 'snarf mode-alist))
391 (defun mc-snarf-keys ()
392 "*Add all public keys in the buffer to your keyring."
394 (let ((schemes mc-schemes)
398 (scheme mc-default-scheme))
403 ; Attempt to find a message signed according to the default
405 (if mc-default-scheme
408 (mc-message-delimiter-positions
409 (cdr (assoc 'key-begin-line (funcall mc-default-scheme)))
410 (cdr (assoc 'key-end-line (funcall mc-default-scheme)))
412 ; We can't find a message signed in the default scheme.
413 ; Step through all the schemes we know, trying to identify
414 ; the applicable one by examining headers.
415 (while (and (null limits)
417 (setq scheme (cdr (car schemes)))
421 (mc-message-delimiter-positions
422 (cdr (assoc 'key-begin-line (funcall scheme)))
423 (cdr (assoc 'key-end-line (funcall scheme)))
425 (setq schemes (cdr schemes)))
428 (setq start (cdr limits))
429 (setq found (+ found (funcall (cdr (assoc 'snarf-func
431 (car limits) (cdr limits)))))))
432 (message (format "%d new key%s found" found
433 (if (eq 1 found) "" "s"))))))
436 ;;{{{ Mode specific functions
439 (defun mc-rmail-summary-verify-signature ()
440 "*Verify the signature in the current message."
442 (if (not (eq major-mode 'rmail-summary-mode))
444 "mc-rmail-summary-verify-signature called in inappropriate buffer"))
446 (set-buffer rmail-buffer)
449 (defun mc-rmail-summary-decrypt-message ()
450 "*Decrypt the contents of this message"
452 (if (not (eq major-mode 'rmail-summary-mode))
454 "mc-rmail-summary-decrypt-message called in inappropriate buffer"))
456 (set-buffer rmail-buffer)
459 (defun mc-rmail-summary-snarf-keys ()
460 "*Adds keys from current message to public key ring"
462 (if (not (eq major-mode 'rmail-summary-mode))
464 "mc-rmail-summary-snarf-keys called in inappropriate buffer"))
466 (set-buffer rmail-buffer)
469 (defun mc-rmail-verify-signature ()
470 "*Verify the signature in the current message."
472 (if (not (equal mode-name "RMAIL"))
473 (error "mc-rmail-verify-signature called in a non-RMAIL buffer"))
474 ;; Hack to load rmailkwd before verifying sig
475 (rmail-add-label "verified")
476 (rmail-kill-label "verified")
477 (if (mc-verify-signature)
478 (rmail-add-label "verified")))
480 (defun mc-rmail-decrypt-message ()
481 "*Decrypt the contents of this message"
483 (let (decryption-result)
484 (if (not (equal mode-name "RMAIL"))
485 (error "mc-rmail-decrypt-message called in a non-RMAIL buffer"))
488 (rmail-edit-current-message)
489 (setq decryption-result (mc-decrypt-message))
490 (cond ((not (car decryption-result))
492 ((and (not (eq mc-always-replace 'never))
493 (or mc-always-replace
495 "Replace encrypted message with decrypted? ")))
497 (rmail-kill-label "edited")
498 (rmail-add-label "decrypted")
499 (if (cdr decryption-result)
500 (rmail-add-label "verified")))
502 (let ((tmp (generate-new-buffer "*Mailcrypt Viewing*")))
503 (copy-to-buffer tmp (point-min) (point-max))
505 (switch-to-buffer tmp t)
506 (goto-char (point-min))
507 (insert "From Mailcrypt-" mc-version " "
508 (current-time-string) "\n")
511 (use-local-map (copy-keymap (current-local-map)))
512 (local-set-key "q" 'mc-rmail-view-quit)
513 (set-buffer-modified-p nil)
514 (rmail-add-label "decrypted")
515 (if (cdr decryption-result)
516 (rmail-add-label "verified"))))))
517 (if (eq major-mode 'rmail-edit-mode)
518 (rmail-abort-edit)))))
520 (defun mc-rmail-view-quit ()
522 (let ((buf (current-buffer)))
523 (set-buffer-modified-p nil)
529 (defun mc-vm-verify-signature ()
530 "*Verify the signature in the current VM message"
533 (vm-follow-summary-cursor))
534 (vm-select-folder-buffer)
535 (vm-check-for-killed-summary)
536 (vm-error-if-folder-empty)
539 (mc-verify-signature)))
541 (defun mc-vm-decrypt-message ()
542 "*Decrypt the contents of the current VM message"
544 (let ((vm-frame-per-edit nil)
547 (vm-follow-summary-cursor))
548 ; (vm-select-folder-buffer) ;; TNX Eric C. Newton for commenting out.
549 (vm-check-for-killed-summary)
550 (vm-error-if-folder-read-only)
551 (vm-error-if-folder-empty)
553 ;; store away a valid "From " line for possible later use.
554 (setq from-line (vm-leading-message-separator))
556 (cond ((not (condition-case condition-data
557 (car (mc-decrypt-message))
559 (vm-edit-message-abort)
560 (error (message "Decryption failed: %s"
561 (car (cdr condition-data)))))))
562 (vm-edit-message-abort)
563 (error "Decryption failed."))
564 ((and (not (eq mc-always-replace 'never))
565 (or mc-always-replace
566 (y-or-n-p "Replace encrypted message with decrypted? ")))
567 (let ((this-command 'vm-edit-message-end))
568 (vm-edit-message-end)))
570 (let ((tmp (generate-new-buffer "*Mailcrypt Viewing*")))
571 (copy-to-buffer tmp (point-min) (point-max))
572 (vm-edit-message-abort)
573 (switch-to-buffer tmp t)
574 (goto-char (point-min))
576 (set-buffer-modified-p nil)
579 (defun mc-vm-snarf-keys ()
580 "*Snarf public key from the contents of the current VM message"
583 (vm-follow-summary-cursor))
584 (vm-select-folder-buffer)
585 (vm-check-for-killed-summary)
586 (vm-error-if-folder-empty)
594 (defun mc-gnus-verify-signature ()
596 (gnus-summary-select-article t)
598 (set-buffer gnus-original-article-buffer)
599 (save-restriction (widen) (mc-verify-signature))))
601 (defun mc-gnus-snarf-keys ()
603 (gnus-summary-select-article t)
604 (gnus-eval-in-buffer-window gnus-original-article-buffer
605 (save-restriction (widen) (mc-snarf-keys))))
607 (defun mc-gnus-decrypt-message ()
609 (gnus-summary-select-article t)
610 ;; Gnus 5 has the string "Gnus" instead of "GNUS" in gnus-version.
611 (if (not (let ((case-fold-search nil))
612 (string-match "Gnus" gnus-version)))
613 (gnus-eval-in-buffer-window
615 (save-restriction (widen) (mc-decrypt-message)))
616 ;; Gnus 5 allows editing of articles. (Actually, it makes a great
618 (gnus-eval-in-buffer-window gnus-article-buffer
619 (gnus-summary-edit-article t)
622 (cond ((not (car (mc-decrypt-message)))
623 (gnus-summary-edit-article-postpone))
624 ((and (not (gnus-group-read-only-p))
625 (not (eq mc-always-replace 'never))
626 (or mc-always-replace
628 "Replace encrypted message on disk? ")))
629 (gnus-summary-edit-article-done))
631 (gnus-summary-edit-article-postpone)))))))
635 (defvar mc-mh-backup-msg 3
636 "If 0, never back up MH messages. If 3, always back up messages.")
638 (defun mc-mh-decrypt-message ()
639 "Decrypt the contents of the current MH message in the show buffer."
641 (let* ((msg (mh-get-msg-num t))
642 (msg-filename (mh-msg-filename msg))
643 (show-buffer (get-buffer mh-show-buffer))
644 decrypt-okay decrypt-on-disk)
647 (and (not (eq mc-always-replace 'never))
648 (or mc-always-replace
649 (y-or-n-p "Replace encrypted message on disk? "))))
653 (set-buffer (create-file-buffer msg-filename))
654 (insert-file-contents msg-filename t)
655 (if (setq decrypt-okay (car (mc-decrypt-message)))
656 (save-buffer mc-mh-backup-msg)
657 (message "Decryption failed.")
658 (set-buffer-modified-p nil))
662 (equal msg-filename (buffer-file-name show-buffer)))
664 (save-window-excursion
665 (mh-invalidate-show-buffer)))))
669 (set-buffer mh-show-buffer)
670 (let ((read-only buffer-read-only))
673 (setq buffer-read-only nil)
674 (if (setq decrypt-okay (car (mc-decrypt-message)))
676 (goto-char (point-min))
677 (set-buffer-modified-p nil))
678 (message "Decryption failed.")))
679 (setq buffer-read-only read-only)
681 (if (not decrypt-okay)
683 (mh-invalidate-show-buffer)
686 (defun mc-mh-verify-signature ()
687 "*Verify the signature in the current MH message."
690 (mh-in-show-buffer (mh-show-buffer)
691 (mc-verify-signature)))
694 (defun mc-mh-snarf-keys ()
697 (mh-in-show-buffer (mh-show-buffer)
703 ;; decrypt, verify, snarf, encrypt, sign
704 ;; in summary mode, decrypt/verify/snarf must switch to correct buffer first
705 ;; in correct buffer, generic functions are probably sufficient.
706 ;; mew doesn't have any labels to be added (though I really wish it did)
707 ;; could have a "replace encrypted with decrypted" hook for decrypt
711 ;; (add-hook 'mew-message-mode-hook 'mc-install-read-mode)
713 ;; (add-hook 'mew-summary-mode-hook 'mc-install-read-mode)
715 ;; (add-hook 'mew-draft-mode-hook 'mc-install-write-mode)
717 (defun mc-mew-summary-decrypt-message()
718 "*Decrypt the current message"
720 (if (not (eq major-mode 'mew-summary-mode))
722 "mc-mew-summary-decrypt-message called in inappropriate buffer"))
724 (mew-summary-display t)
725 (set-buffer (mew-buffer-message))
729 (defun mc-mew-summary-verify-signature()
730 "*Verify the signature in the current message."
732 (if (not (eq major-mode 'mew-summary-mode))
734 "mc-mew-summary-verify-signature called in inappropriate buffer"))
736 (mew-summary-display t)
737 (set-buffer (mew-buffer-message))
741 (defun mc-mew-summary-snarf-keys()
742 "*Add keys from the current message to the public keyring."
744 (if (not (eq major-mode 'mew-summary-mode))
746 "mc-mew-summary-snarf-keys called in inappropriate buffer"))
748 (mew-summary-display t)
749 (set-buffer (mew-buffer-message))
753 (defun mc-mew-decrypt-message ()
754 "*Decrypt the contents of this message."
755 ;; This is a hack to deal with the fact that mew-message buffers are
756 ;; generally read-only. For now, there is no option to replace the
757 ;; encrypted message in-place; it simply disappears when you move to a
758 ;; different message.
760 (let ((read-only buffer-read-only))
763 (setq buffer-read-only nil)
766 (setq buffer-read-only read-only)