Mercurial > hg > xemacs-beta
view lisp/mailcrypt/mc-pgp.el @ 138:6608ceec7cf8 r20-2b3
Import from CVS: tag r20-2b3
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:31:46 +0200 |
parents | 360340f9fd5f |
children |
line wrap: on
line source
;; mc-pgp.el, PGP support for Mailcrypt ;; Copyright (C) 1995 Jin Choi <jin@atype.com> ;; Patrick LoPresti <patl@lcs.mit.edu> ;;{{{ Licensing ;; This file is intended to be used with GNU Emacs. ;; This program 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 2, or (at your option) ;; any later version. ;; This program 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 GNU Emacs; see the file COPYING. If not, write to ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. ;;}}} (require 'mailcrypt) (defvar mc-pgp-user-id (user-login-name) "*PGP ID of your default identity.") (defvar mc-pgp-always-sign nil "*If t, always sign encrypted PGP messages, or never sign if 'never.") (defvar mc-pgp-path "pgp" "*The PGP executable.") (defvar mc-pgp-display-snarf-output nil "*If t, pop up the PGP output window when snarfing keys.") (defvar mc-pgp-alternate-keyring nil "*Public keyring to use instead of default.") (defvar mc-pgp-comment (format "Processed by Mailcrypt %s, an Emacs/PGP interface" mc-version) "*Comment field to appear in ASCII armor output. If nil, let PGP use its default.") (defconst mc-pgp-msg-begin-line "-----BEGIN PGP MESSAGE-----" "Text for start of PGP message delimiter.") (defconst mc-pgp-msg-end-line "-----END PGP MESSAGE-----\n?" "Text for end of PGP message delimiter.") (defconst mc-pgp-signed-begin-line "-----BEGIN PGP SIGNED MESSAGE-----" "Text for start of PGP signed messages.") (defconst mc-pgp-signed-end-line "-----END PGP SIGNATURE-----" "Text for end of PGP signed messages.") (defconst mc-pgp-key-begin-line "^-----BEGIN PGP PUBLIC KEY BLOCK-----\r?$" "Text for start of PGP public key.") (defconst mc-pgp-key-end-line "^-----END PGP PUBLIC KEY BLOCK-----\r?$" "Text for end of PGP public key.") (defconst mc-pgp-error-re "^\\(ERROR:\\|WARNING:\\).*" "Regular expression matching an error from PGP") (defconst mc-pgp-sigok-re "^.*Good signature.*" "Regular expression matching a PGP signature validation message") (defconst mc-pgp-newkey-re "^[ \t]*\\(No\\|[0-9]+\\) +new [ku].*" "Regular expression matching a PGP key snarf message") (defconst mc-pgp-nokey-re "Cannot find the public key matching userid '\\(.+\\)'$" "Regular expression matching a PGP missing-key message") (defconst mc-pgp-key-expected-re "Key matching expected Key ID \\(\\S +\\) not found") (defvar mc-pgp-keydir nil "Directory in which keyrings are stored.") (defun mc-get-pgp-keydir () (if (null mc-pgp-keydir) (let ((buffer (generate-new-buffer " *mailcrypt temp*")) (obuf (current-buffer))) (unwind-protect (progn (call-process mc-pgp-path nil buffer nil "+verbose=1" "+language=en" "-kv" "XXXXXXXXXX") (set-buffer buffer) (goto-char (point-min)) (re-search-forward "^Key ring:\\s *'\\(.*\\)'") (setq mc-pgp-keydir (file-name-directory (buffer-substring-no-properties (match-beginning 1) (match-end 1))))) (set-buffer obuf) (kill-buffer buffer)))) mc-pgp-keydir) (defvar mc-pgp-key-cache nil "Association list mapping PGP IDs to canonical \"keys\". A \"key\" is a pair (USER-ID . KEY-ID) which identifies the canonical IDs of the PGP ID.") (defun mc-pgp-lookup-key (str) ;; Look up the string STR in the user's secret key ring. Return a ;; pair of strings (USER-ID . KEY-ID) which uniquely identifies the ;; matching key, or nil if no key matches. (if (equal str "***** CONVENTIONAL *****") nil (let ((keyring (concat (mc-get-pgp-keydir) "secring")) (result (cdr-safe (assoc str mc-pgp-key-cache))) (key-regexp "^\\(pub\\|sec\\)\\s +[^/]+/\\(\\S *\\)\\s +\\S +\\s +\\(.*\\)$") (obuf (current-buffer)) buffer) (if (null result) (unwind-protect (progn (setq buffer (generate-new-buffer " *mailcrypt temp")) (call-process mc-pgp-path nil buffer nil "+language=en" "-kv" str keyring) (set-buffer buffer) (goto-char (point-min)) (if (re-search-forward key-regexp nil t) (progn (setq result (cons (buffer-substring-no-properties (match-beginning 3) (match-end 3)) (concat "0x" (buffer-substring-no-properties (match-beginning 2) (match-end 2))))) (setq mc-pgp-key-cache (cons (cons str result) mc-pgp-key-cache))))) (if buffer (kill-buffer buffer)) (set-buffer obuf))) (if (null result) (error "No PGP secret key for %s" str)) result))) (defun mc-pgp-generic-parser (result) (let (start) (goto-char (point-min)) (cond ((not (eq result 0)) (prog1 nil (if (mc-message "^\aError: +Bad pass phrase\\.$" (current-buffer)) (mc-deactivate-passwd t) (mc-message mc-pgp-error-re (current-buffer) (format "PGP exited with status %d" result))))) ((re-search-forward mc-pgp-nokey-re nil t) nil) (t (and (goto-char (point-min)) (re-search-forward "-----BEGIN PGP.*-----$" nil t) (setq start (match-beginning 0)) (goto-char (point-max)) (re-search-backward "^-----END PGP.*-----\n" nil t) (cons start (match-end 0))))))) (defun mc-pgp-encrypt-region (recipients start end &optional id sign) (let ((process-environment process-environment) (buffer (get-buffer-create mc-buffer-name)) ;; Crock. Rewrite someday. (mc-pgp-always-sign mc-pgp-always-sign) (obuf (current-buffer)) action msg args key passwd result pgp-id) (setq args (list "+encrypttoself=off +verbose=1" "+batchmode" "+language=en" "-fat")) (setq action (if recipients "Encrypting" "Armoring")) (setq msg (format "%s..." action)) ; May get overridden below (if recipients (setq args (cons "-e" args))) (if mc-pgp-comment (setq args (cons (format "+comment=%s" mc-pgp-comment) args))) (if mc-pgp-alternate-keyring (setq args (append args (list (format "+pubring=%s" mc-pgp-alternate-keyring))))) (if (and (not (eq mc-pgp-always-sign 'never)) (or mc-pgp-always-sign sign (y-or-n-p "Sign the message? "))) (progn (setq mc-pgp-always-sign t) (setq key (mc-pgp-lookup-key (or id mc-pgp-user-id))) (setq passwd (mc-activate-passwd (cdr key) (format "PGP passphrase for %s (%s): " (car key) (cdr key)))) (setq args (nconc args (list "-s" "-u" (cdr key)))) (setenv "PGPPASSFD" "0") (setq msg (format "%s+signing as %s ..." action (car key)))) (setq mc-pgp-always-sign 'never)) (or key (setq key (mc-pgp-lookup-key mc-pgp-user-id))) (if (and recipients mc-encrypt-for-me) (setq recipients (cons (cdr key) recipients))) (setq args (append args recipients)) (message "%s" msg) (setq result (mc-process-region start end passwd mc-pgp-path args 'mc-pgp-generic-parser buffer)) (save-excursion (set-buffer buffer) (goto-char (point-min)) (if (re-search-forward mc-pgp-nokey-re nil t) (progn (if result (error "This should never happen.")) (setq pgp-id (buffer-substring-no-properties (match-beginning 1) (match-end 1))) (if (and (not (eq mc-pgp-always-fetch 'never)) (or mc-pgp-always-fetch (y-or-n-p (format "Key for '%s' not found; try to fetch? " pgp-id)))) (progn (mc-pgp-fetch-key (cons pgp-id nil)) (set-buffer obuf) (mc-pgp-encrypt-region recipients start end id)) (mc-message mc-pgp-nokey-re buffer) nil)) (if (not result) nil (message "%s Done." msg) t))))) (defun mc-pgp-decrypt-parser (result) (goto-char (point-min)) (cond ((eq result 0) ;; Valid signature (re-search-forward "^Signature made.*\n") (if (looking-at "\a\nWARNING: Because this public key.*\n.*\n.*\n") (goto-char (match-end 0))) (cons (point) (point-max))) ((eq result 1) (re-search-forward "\\(\\(^File is conven.*\\)?Just a moment\\.+\\)\\|\\(^\\.\\)") (if (eq (match-beginning 2) (match-end 2)) (if (looking-at "\nFile has signature.*\\(\n\a.*\n\\)*\nWARNING:.*\n") (goto-char (match-end 0))) (if (looking-at "Pass phrase appears good\\. \\.") (goto-char (match-end 0)))) (cons (point) (point-max))) (t nil))) (defun mc-pgp-decrypt-region (start end &optional id) ;; returns a pair (SUCCEEDED . VERIFIED) where SUCCEEDED is t if ;; the decryption succeeded and verified is t if there was a valid signature (let ((process-environment process-environment) (buffer (get-buffer-create mc-buffer-name)) args key new-key passwd result pgp-id) (undo-boundary) (setq key (mc-pgp-lookup-key (or id mc-pgp-user-id))) (setq passwd (if key (mc-activate-passwd (cdr key) (and id (format "PGP passphrase for %s (%s): " (car key) (cdr key)))) (mc-activate-passwd id "PGP passphrase for conventional decryption: "))) (if passwd (setenv "PGPPASSFD" "0")) (setq args '("+verbose=1" "+batchmode" "+language=en" "-f")) (if mc-pgp-alternate-keyring (setq args (append args (list (format "+pubring=%s" mc-pgp-alternate-keyring))))) (message "Decrypting...") (setq result (mc-process-region start end passwd mc-pgp-path args 'mc-pgp-decrypt-parser buffer)) (cond (result (message "Decrypting... Done.") ;; If verification failed due to missing key, offer to fetch it. (save-excursion (set-buffer buffer) (goto-char (point-min)) (if (re-search-forward mc-pgp-key-expected-re nil t) (setq pgp-id (concat "0x" (buffer-substring-no-properties (match-beginning 1) (match-end 1)))))) (if (and pgp-id (not (eq mc-pgp-always-fetch 'never)) (or mc-pgp-always-fetch (y-or-n-p (format "Key %s not found; attempt to fetch? " pgp-id))) (mc-pgp-fetch-key (cons nil pgp-id))) (progn (undo-start) (undo-more 1) (mc-pgp-decrypt-region start end id)) (mc-message mc-pgp-key-expected-re buffer) (cons t (eq result 0)))) ;; Decryption failed; maybe we need to use a different user-id ((save-excursion (and (set-buffer buffer) (goto-char (point-min)) (re-search-forward "^Key for user ID:.*\n.*Key ID \\([0-9A-F]+\\)" nil t) (setq new-key (mc-pgp-lookup-key (concat "0x" (buffer-substring-no-properties (match-beginning 1) (match-end 1))))) (not (and id (equal key new-key))))) (mc-pgp-decrypt-region start end (cdr new-key))) ;; Or maybe it is conventionally encrypted ((save-excursion (and (set-buffer buffer) (goto-char (point-min)) (re-search-forward "^File is conventionally encrypted" nil t))) (if (null key) (mc-deactivate-passwd t)) (mc-pgp-decrypt-region start end "***** CONVENTIONAL *****")) (t (mc-display-buffer buffer) (if (mc-message "^\aError: +Bad pass phrase\\.$" buffer) (mc-deactivate-passwd t) (mc-message mc-pgp-error-re buffer "Error decrypting buffer")) (cons nil nil))))) (defun mc-pgp-sign-region (start end &optional id unclear) (let ((process-environment process-environment) (buffer (get-buffer-create mc-buffer-name)) passwd args key) (setq key (mc-pgp-lookup-key (or id mc-pgp-user-id))) (setq passwd (mc-activate-passwd (cdr key) (format "PGP passphrase for %s (%s): " (car key) (cdr key)))) (setenv "PGPPASSFD" "0") (setq args (list "-fast" "+verbose=1" "+language=en" (format "+clearsig=%s" (if unclear "off" "on")) "+batchmode" "-u" (cdr key))) (if mc-pgp-comment (setq args (cons (format "+comment=%s" mc-pgp-comment) args))) (message "Signing as %s ..." (car key)) (if (mc-process-region start end passwd mc-pgp-path args 'mc-pgp-generic-parser buffer) (progn (message "Signing as %s ... Done." (car key)) t) nil))) (defun mc-pgp-verify-parser (result) (cond ((eq result 0) (mc-message mc-pgp-sigok-re (current-buffer) "Good signature") t) ((eq result 1) (mc-message mc-pgp-error-re (current-buffer) "Bad signature") nil) (t (mc-message mc-pgp-error-re (current-buffer) (format "PGP exited with status %d" result)) nil))) (defun mc-pgp-verify-region (start end &optional no-fetch) (let ((buffer (get-buffer-create mc-buffer-name)) (obuf (current-buffer)) args pgp-id) (setq args '("+verbose=1" "+batchmode" "+language=en" "-f")) (if mc-pgp-alternate-keyring (setq args (append args (list (format "+pubring=%s" mc-pgp-alternate-keyring))))) (message "Verifying...") (if (mc-process-region start end nil mc-pgp-path args 'mc-pgp-verify-parser buffer) t (save-excursion (set-buffer buffer) (goto-char (point-min)) (if (and (not no-fetch) (re-search-forward mc-pgp-key-expected-re nil t) (setq pgp-id (concat "0x" (buffer-substring-no-properties (match-beginning 1) (match-end 1)))) (not (eq mc-pgp-always-fetch 'never)) (or mc-pgp-always-fetch (y-or-n-p (format "Key %s not found; attempt to fetch? " pgp-id))) (mc-pgp-fetch-key (cons nil pgp-id)) (set-buffer obuf)) (mc-pgp-verify-region start end t) (mc-message mc-pgp-error-re buffer) nil))))) (defun mc-pgp-insert-public-key (&optional id) (let ((buffer (get-buffer-create mc-buffer-name)) args) (setq id (or id mc-pgp-user-id)) (setq args (list "+verbose=1" "+batchmode" "+language=en" "-kxaf" id)) (if mc-pgp-comment (setq args (cons (format "+comment=%s" mc-pgp-comment) args))) (if mc-pgp-alternate-keyring (setq args (append args (list (format "+pubring=%s" mc-pgp-alternate-keyring))))) (if (mc-process-region (point) (point) nil mc-pgp-path args 'mc-pgp-generic-parser buffer) (progn (mc-message "Key for user ID: .*" buffer) t)))) (defun mc-pgp-snarf-parser (result) (eq result 0)) (defun mc-pgp-snarf-keys (start end) ;; Returns number of keys found. (let ((buffer (get-buffer-create mc-buffer-name)) tmpstr args) (setq args '("+verbose=1" "+batchmode" "+language=en" "-kaf")) (if mc-pgp-alternate-keyring (setq args (append args (list (format "+pubring=%s" mc-pgp-alternate-keyring))))) (message "Snarfing...") (if (mc-process-region start end nil mc-pgp-path args 'mc-pgp-snarf-parser buffer) (save-excursion (set-buffer buffer) (goto-char (point-min)) (if (re-search-forward mc-pgp-newkey-re nil t) (progn (if mc-pgp-display-snarf-output (mc-display-buffer buffer)) (setq tmpstr (buffer-substring-no-properties (match-beginning 1) (match-end 1))) (if (equal tmpstr "No") 0 (car (read-from-string tmpstr)))))) (mc-display-buffer buffer) (mc-message mc-pgp-error-re buffer "Error snarfing PGP keys") 0))) ;;;###autoload (defun mc-scheme-pgp () (list (cons 'encryption-func 'mc-pgp-encrypt-region) (cons 'decryption-func 'mc-pgp-decrypt-region) (cons 'signing-func 'mc-pgp-sign-region) (cons 'verification-func 'mc-pgp-verify-region) (cons 'key-insertion-func 'mc-pgp-insert-public-key) (cons 'snarf-func 'mc-pgp-snarf-keys) (cons 'msg-begin-line mc-pgp-msg-begin-line) (cons 'msg-end-line mc-pgp-msg-end-line) (cons 'signed-begin-line mc-pgp-signed-begin-line) (cons 'signed-end-line mc-pgp-signed-end-line) (cons 'key-begin-line mc-pgp-key-begin-line) (cons 'key-end-line mc-pgp-key-end-line) (cons 'user-id mc-pgp-user-id))) ;;{{{ Key fetching (defvar mc-pgp-always-fetch nil "*If t, always attempt to fetch missing keys, or never fetch if 'never.") (defvar mc-pgp-keyserver-url-template "/htbin/pks-extract-key.pl?op=get&search=%s" "The URL to pass to the keyserver.") (defvar mc-pgp-keyserver-address "pgp.ai.mit.edu" "Host name of keyserver.") (defvar mc-pgp-keyserver-port 80 "Port on which the keyserver's HTTP daemon lives.") (defvar mc-pgp-fetch-timeout 20 "*Timeout, in seconds, for any particular key fetch operation.") (defvar mc-pgp-fetch-keyring-list nil "*List of strings which are filenames of public keyrings to search when fetching keys.") (defsubst mc-pgp-buffer-get-key (buf) "Return the first key block in BUF as a string, or nil if none found." (save-excursion (let (start) (set-buffer buf) (goto-char (point-min)) (and (re-search-forward mc-pgp-key-begin-line nil t) (setq start (match-beginning 0)) (re-search-forward mc-pgp-key-end-line nil t) (buffer-substring-no-properties start (match-end 0)))))) (defun mc-pgp-fetch-from-keyrings (id) (let ((keyring-list mc-pgp-fetch-keyring-list) buf proc key) (unwind-protect (progn (message "Fetching %s from keyrings..." (or (cdr id) (car id))) (while (and (not key) keyring-list) (setq buf (generate-new-buffer " *mailcrypt temp*")) (setq proc (start-process "*PGP*" buf mc-pgp-path "-kxaf" "+verbose=0" "+batchmode" (format "+pubring=%s" (car keyring-list)) (or (cdr id) (car id)))) ;; Because PGPPASSFD might be set (process-send-string proc "\r\n") (while (eq 'run (process-status proc)) (accept-process-output proc 5)) (setq key (mc-pgp-buffer-get-key buf)) (setq keyring-list (cdr keyring-list))) key) (if buf (kill-buffer buf)) (if (and proc (eq 'run (process-status proc))) (interrupt-process proc))))) (defun mc-pgp-fetch-from-http (id) (let (buf connection) (unwind-protect (progn (message "Fetching %s via HTTP to %s..." (or (cdr id) (car id)) mc-pgp-keyserver-address) (setq buf (generate-new-buffer " *mailcrypt temp*")) (setq connection (open-network-stream "*key fetch*" buf mc-pgp-keyserver-address mc-pgp-keyserver-port)) (process-send-string connection (concat "GET " (format mc-pgp-keyserver-url-template (or (cdr id) (car id))) "\r\n")) (while (and (eq 'open (process-status connection)) (accept-process-output connection mc-pgp-fetch-timeout))) (mc-pgp-buffer-get-key buf)) (if buf (kill-buffer buf)) (if connection (delete-process connection))))) (defun mc-pgp-fetch-from-finger (id) (let (buf connection user host) (unwind-protect (and (car id) (string-match "^\\(.+\\)@\\([^@]+\\)$" (car id)) (progn (message "Trying finger %s..." (car id)) (setq user (substring (car id) (match-beginning 1) (match-end 1))) (setq host (substring (car id) (match-beginning 2) (match-end 2))) (setq buf (generate-new-buffer " *mailcrypt temp*")) (condition-case nil (progn (setq connection (open-network-stream "*key fetch*" buf host 79)) (process-send-string connection (concat "/W " user "\r\n")) (while (and (eq 'open (process-status connection)) (accept-process-output connection mc-pgp-fetch-timeout))) (mc-pgp-buffer-get-key buf)) (error nil)))) (if buf (kill-buffer buf)) (if connection (delete-process connection))))) (defvar mc-pgp-fetch-methods '(mc-pgp-fetch-from-keyrings mc-pgp-fetch-from-finger mc-pgp-fetch-from-http) "List of methods to try when attempting to fetch a key. Each element is a function to call with an ID as argument. See the documentation for the function mc-pgp-fetch-key for a description of the ID.") ;;;###autoload (defun mc-pgp-fetch-key (&optional id) "Attempt to fetch a key for addition to PGP keyring. Interactively, prompt for string matching key to fetch. Non-interactively, ID must be a pair. The CAR must be a bare Email address and the CDR a keyID (with \"0x\" prefix). Either, but not both, may be nil. Return t if we think we were successful; nil otherwise. Note that nil is not necessarily an error, since we may have merely fired off an Email request for the key." (interactive) (let ((methods mc-pgp-fetch-methods) (process-connection-type nil) key proc buf args) (if (null id) (setq id (cons (read-string "Fetch key for: ") nil))) (while (and (not key) methods) (setq key (funcall (car methods) id)) (setq methods (cdr methods))) (if (not (stringp key)) (progn (message "Key not found.") nil) ;; Maybe I'll do this right someday. (unwind-protect (save-window-excursion (setq buf (generate-new-buffer " *PGP Key Info*")) (pop-to-buffer buf) (if (< (window-height) (/ (frame-height) 2)) (enlarge-window (- (/ (frame-height) 2) (window-height)))) (setq args '("-f" "+verbose=0" "+batchmode")) (if mc-pgp-alternate-keyring (setq args (append args (list (format "+pubring=%s" mc-pgp-alternate-keyring))))) (setq proc (apply 'start-process "*PGP*" buf mc-pgp-path args)) ;; Because PGPPASSFD might be set (process-send-string proc "\r\n") (process-send-string proc key) (process-send-string proc "\r\n") (process-send-eof proc) (set-buffer buf) (while (eq 'run (process-status proc)) (accept-process-output proc 5) (goto-char (point-min))) (if (y-or-n-p "Add this key to keyring? ") (progn (setq args (append args '("-ka"))) (setq proc (apply 'start-process "*PGP*" buf mc-pgp-path args)) ;; Because PGPPASSFD might be set (process-send-string proc "\r\n") (process-send-string proc key) (process-send-string proc "\r\n") (process-send-eof proc) (while (eq 'run (process-status proc)) (accept-process-output proc 5)) t))) (if buf (kill-buffer buf)))))) ;;}}}