;;;; -*- mode:emacs-lisp;coding:utf-8 -*- ;;;;****************************************************************************** ;;;;FILE: pjb-pgp.el ;;;;LANGUAGE: emacs lisp ;;;;SYSTEM: UNIX ;;;;USER-INTERFACE: emacs ;;;;DESCRIPTION ;;;; This module defines emacs lisp functions to integrate pgp with emacs. ;;;; It can be configured by a set of constants and a variable. ;;;; variable pgp-signer --> Specifies the key used to sign. ;;;; variable pgp-command --> Specifies the OpenPGP program to use. ;;;; The public (interactive) functions are: ;;;; pgp-encrypt-conventional-region (key) ;;;; pgp-encrypt-conventional-buffer (key) ;;;; pgp-decrypt-conventional-region (key) ;;;; pgp-decrypt-conventional-buffer (key) ;;;; pgp-sign-region (pass-phrase) ;;;; pgp-sign-buffer (pass-phrase) ;;;; pgp-check-sign-region () ;;;; pgp-check-sign-buffer () ;;;; pgp-sign-encrypt-region (pass-phrase recipients) ;;;; pgp-sign-encrypt-buffer (pass-phrase recipients) ;;;; pgp-just-encrypt-region (recipients) ;;;; pgp-just-encrypt-buffer (recipients) ;;;; pgp-decrypt-region (pass-phrase) ;;;; pgp-decrypt-buffer (pass-phrase) ;;;;USAGE ;;;; See the documentation strings of the items defined here. ;;;;AUTHORS ;;;; Pascal J. Bourguignon ;;;;MODIFICATIONS ;;;; 2001-12-20 Added support for gpg. Made it the default OpenPGP used. ;;;; 2001-03-19 Made it work on read-only buffers, ;;;; creating a temporary buffer for output. ;;;; 2001-02-23 Creation. ;;;;BUGS ;;;; * TODO: What if new output buffer has the same name as an existing one? ;;;; * view-lossage will show the passwords. (recent-keys) ;;;; * buffer-flush-undo should probably used to prevent undoing an encryption. ;;;; * Still missing a pgp-wipe. ;;;;LEGAL ;;;; LGPL ;;;; ;;;; Copyright Pascal J. Bourguignon 2001 - 2011 ;;;; ;;;; This library is free software; you can redistribute it and/or ;;;; modify it under the terms of the GNU Lesser General Public ;;;; License as published by the Free Software Foundation; either ;;;; version 2 of the License, or (at your option) any later version. ;;;; ;;;; This library 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 ;;;; Lesser General Public License for more details. ;;;; ;;;; You should have received a copy of the GNU Lesser General Public ;;;; License along with this library; if not, write to the Free Software ;;;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ;;;; ;;;;****************************************************************************** (provide 'pgp) (provide 'pjb-pgp) (require 'pjb-strings) (defvar pgp-signer nil "When nil, the PGP Default Signing Key is used. Otherwise it must be a string containing the KeyID or the UserID to be used to sign." ) (defvar pgp-command 'pgp-pgp-command "The name of the function to build pgp commands. Choose either 'pgp-pgp-command or 'pgp-gpg-command, or implement your own.") ;; (setq pgp-command 'pgp-pgp-command) ;; (setq pgp-command 'pgp-gpg-command) (defvar pgp-err-buffer-name "*PGP*" "The name of the OpenPGP result buffer.") (defvar pgp$*out-buffer* nil "The last output buffer.") (defun pgp-pgp-signer (signer) "PRIVATE" (if signer (format " -u %s" (shell-quote-argument signer)) "")) (defun pgp-pgp-recipients (recipients) "PRIVATE" (unsplit-string (mapcar 'shell-quote-argument (split-string recipients)) " ")) (defun pgp-pgp-command (selector &rest args) "Build the pgp command." (cond ((eq selector 'list-keys) "pgp +batchmode -kv" ) ((eq selector 'add-keys) "pgp +batchmode -f" ) ((eq selector 'encrypt-conventional) (format "PGPPASS=%s pgp +batchmode -fcat" (shell-quote-argument (nth 0 args)));; passphrase ) ((eq selector 'decrypt-conventional) (format "PGPPASS=%s pgp +batchmode -f" (shell-quote-argument (nth 0 args)));; passphrase ) ((eq selector 'sign) (format "PGPPASS=%s pgp +batchmode -fsat %s" (shell-quote-argument (nth 0 args));; passphrase (pgp-pgp-signer (nth 1 args)));; signer ) ((eq selector 'check-sign) "pgp +batchmode -f >/dev/null " ;; stdout gets the signed data. ) ((eq selector 'sign-encrypt) (format "PGPPASS=%s pgp +batchmode -fseat %s %s" (shell-quote-argument (nth 0 args));; passphrase (pgp-pgp-signer (nth 1 args));; signer (pgp-pgp-recipients (nth 2 args)));; recipients ) ((eq selector 'encrypt) (format "pgp +batchmode -feat %s" (pgp-pgp-recipients (nth 0 args)));; recipients ) ((eq selector 'decrypt) (format "PGPPASS=%s pgp +batchmode -f" (shell-quote-argument (nth 0 args)));; passphrase ) (t (error "Unknown selector '%s'." selector)))) (defun pgp-gpg-signer (signer) "PRIVATE" (if signer (format " --local-user %s" (shell-quote-argument signer)) "")) (defun pgp-gpg-recipients (recipients) "PRIVATE" (apply 'concat (mapcar (lambda (elem) (concat " --recipient " (shell-quote-argument elem))) (split-string recipients)))) (defun pgp-gpg-command (selector &rest args) "Build the gpg command." (let* ((pipe-name (format "/tmp/pipe-%d-%s" (emacs-pid) (gensym "gpg-"))) (path (format "PATH=%S" (mapconcat (function identity) exec-path path-separator))) (prefix (format ;; TODO: Here the password is put in args! (if (eq system-type 'darwin) "trap 'rm -f %s' 0 ; mkfifo -m 600 %s ; echo %%s >> %s & " "trap 'rm -f %s' 0 ; mknod %s p ; echo %%s >> %s & ") pipe-name pipe-name pipe-name)) (gpg "gpg --no-utf8-strings --batch --no-tty --textmode")) (cond ((eq selector 'list-keys) (concat path " " "gpg --no-utf8-strings --batch --no-tty --list-keys")) ((eq selector 'add-keys) (concat path " " "gpg --no-utf8-strings --batch --no-tty --import")) ((eq selector 'encrypt-conventional) (format (concat prefix "%s %s --armor --symmetric " " --passphrase-fd 6 6< %s") (shell-quote-argument (nth 0 args));; passphrase path gpg pipe-name)) ((eq selector 'decrypt-conventional) (format (concat prefix "%s %s --decrypt " " --passphrase-fd 6 6< %s") (shell-quote-argument (nth 0 args));; passphrase path gpg pipe-name)) ((eq selector 'sign) (format (concat prefix "%s %s %s --clearsign " " --passphrase-fd 6 6< %s") (shell-quote-argument (nth 0 args));; passphrase path gpg (pgp-gpg-signer (nth 1 args));; signer pipe-name)) ((eq selector 'check-sign) (format "%s %s --verify" path gpg)) ((eq selector 'sign-encrypt) (format (concat prefix "%s %s --armor %s %s " " --sign --encrypt " " --passphrase-fd 6 6< %s") (shell-quote-argument (nth 0 args));; passphrase path gpg (pgp-gpg-signer (nth 1 args));; signer (pgp-gpg-recipients (nth 2 args)) ;; recipients pipe-name)) ((eq selector 'encrypt) (format "%s %s --armor %s --encrypt " path gpg (pgp-gpg-recipients (nth 0 args)))) ;; recipients ((eq selector 'decrypt) (format (concat prefix "%s %s --decrypt " " --passphrase-fd 6 6< %s") (shell-quote-argument (nth 0 args));; passphrase path gpg pipe-name)) (t (error "Unknown selector '%s'." selector))))) (defun pgp-prepare-err-buffer () "Prepare the buffer where pgp stderr will be put." (save-excursion (switch-to-buffer (get-buffer-create pgp-err-buffer-name)) (erase-buffer))) (defun pgp-bury-err-buffer-if-empty () "PRIVATE" (save-excursion (set-buffer pgp-err-buffer-name) (when (= 0 (buffer-size)) (bury-buffer) (switch-to-buffer pgp$*out-buffer*)))) (defun pgp-prepare-out-buffer (action) "Prepare the buffer where pgp stdout will be put. If the current buffer is read-only, the we make a new temporary buffer, otherwise we keep the current buffer." (setq pgp$*out-buffer* (if buffer-read-only (get-buffer-create (format "*%s %s*" (buffer-name (current-buffer)) action)) (current-buffer))) pgp$*out-buffer*) ;;(let ( (coding-system-for-read utf-8-unix) ;; (coding-system-for-write iso-latin-1-unix) ) (defun pgp-remove-key-from-history (key) "Remove the given KEY from the minibuffer-history." (setq minibuffer-history (delete key minibuffer-history))) (defun pgp-encrypt-conventional-range (key begin end) "Encrypt conventionaly the range BEGIN, END with the KEY." (pgp-remove-key-from-history key) (pgp-prepare-err-buffer) (let ((out-buffer (pgp-prepare-out-buffer "Encrypted"))) (message "Encrypting...") (shell-command-on-region begin end (funcall pgp-command 'encrypt-conventional key) out-buffer (eq out-buffer (current-buffer)) pgp-err-buffer-name)) (pgp-bury-err-buffer-if-empty)) (defun pgp-decrypt-conventional-range (key begin end) "Decrypt conventionaly the range BEGIN, END with the KEY." (pgp-remove-key-from-history key) (pgp-prepare-err-buffer) (let ((out-buffer (pgp-prepare-out-buffer "Decrypted"))) (message "Decrypting...") (shell-command-on-region begin end (funcall pgp-command 'decrypt-conventional key) out-buffer (eq out-buffer (current-buffer)) pgp-err-buffer-name)) (pgp-bury-err-buffer-if-empty)) (defun pgp-sign-range (pass-phrase begin end) "Sign the range BEGIN, END, with the signing key indicated by pgp-signer, unlocked with the pass-phrase." (pgp-remove-key-from-history pass-phrase) (pgp-prepare-err-buffer) (let ((out-buffer (pgp-prepare-out-buffer "Signed"))) (message "Signing...") (shell-command-on-region begin end (funcall pgp-command 'sign pass-phrase pgp-signer) out-buffer (eq out-buffer (current-buffer)) pgp-err-buffer-name)) (pgp-bury-err-buffer-if-empty)) (defun pgp-check-sign-range (begin end) "Check the signature of the range BEGIN, END." (pgp-prepare-err-buffer) (message "Checking signature...") (shell-command-on-region begin end (funcall pgp-command 'check-sign) pgp-err-buffer-name nil pgp-err-buffer-name) (pgp-bury-err-buffer-if-empty)) (defun pgp-encrypt-pk-range (pass-phrase recipients begin end) "When pass-phrase is not nil, sign with the signing key indicated by pgp-signer, unlocked with the PASS-PHRASE, and encrypt, otherwise only encrypt the range BEGIN to END, for the RECIPIENTS." (pgp-remove-key-from-history pass-phrase) (pgp-prepare-err-buffer) (let ((out-buffer (pgp-prepare-out-buffer "PK Encrypted"))) (message "Encrypting...") (if pass-phrase (shell-command-on-region begin end (funcall pgp-command 'sign-encrypt pass-phrase pgp-signer recipients) out-buffer (eq out-buffer (current-buffer)) pgp-err-buffer-name) (shell-command-on-region begin end (funcall pgp-command 'encrypt recipients) out-buffer (eq out-buffer (current-buffer)) pgp-err-buffer-name)) (switch-to-buffer out-buffer)) (pgp-bury-err-buffer-if-empty)) (defun pgp-decrypt-pk-range (pass-phrase begin end) "Decrypt the range BEGIN, END, for the keyid unlocked by PASS-PHRASE." (pgp-remove-key-from-history pass-phrase) (pgp-prepare-err-buffer) (let ((out-buffer (pgp-prepare-out-buffer "PK Decrypted"))) (message "Decrypting...") (shell-command-on-region begin end (funcall pgp-command 'decrypt pass-phrase) out-buffer (eq out-buffer (current-buffer)) pgp-err-buffer-name) (switch-to-buffer out-buffer)) (pgp-bury-err-buffer-if-empty)) (defun pgp-encrypt-conventional-region (key) "Encrypt conventionaly the region with the KEY." (interactive "sKey: ") (pgp-encrypt-conventional-range key (region-beginning) (region-end))) (defun pgp-encrypt-conventional-buffer (key) "Encrypt conventionaly the buffer with the KEY." (interactive "sKey: ") (pgp-encrypt-conventional-range key (point-min) (point-max))) (defun pgp-decrypt-conventional-region (key) "Decrypt conventionaly the region with the KEY." (interactive "sKey: ") (auto-save-mode -1);; Decrypted buffers must not be auto-saved! (pgp-decrypt-conventional-range key (region-beginning) (region-end))) (defun pgp-decrypt-conventional-buffer (key) "Decrypt conventionaly the buffer with the KEY." (interactive "sKey: ") (auto-save-mode -1);; Decrypted buffers must not be auto-saved! (pgp-decrypt-conventional-range key (point-min) (point-max))) (defun pgp-sign-region (pass-phrase) "Sign the region with the pgp-signer key, unlocked by the PASS-PHRASE." (interactive "sPass phrase: ") (pgp-sign-range pass-phrase (region-beginning) (region-end))) (defun pgp-sign-buffer (pass-phrase) "Sign the buffer with the pgp-signer key, unlocked by the PASS-PHRASE." (interactive "sPass phrase: ") (pgp-sign-range pass-phrase (point-min) (point-max))) (defun pgp-check-sign-region () "Check the signature of the region." (interactive) (pgp-check-sign-range (region-beginning) (region-end))) (defun pgp-check-sign-buffer () "Check the signature of the buffer." (interactive) (pgp-check-sign-range (point-min) (point-max))) (defun pgp-convert-to-utf-8 (start end) "PRIVATE BUG: The handling of output buffer (whether read-only or not) should be done before calling this function! " (interactive "r") (shell-command-on-region start end "iconv -f ISO8859-1 -t UTF-8" t t)) (defun pgp-convert-from-utf-8 (start end) "PRIVATE " (interactive "r") (shell-command-on-region start end "iconv -f UTF-8 -t ISO8859-1" t t)) (defun pgp-sign-encrypt-region (pass-phrase recipients &optional conv-utf-8) "Sign and encrypt the region, with pgp-signer key, unlocked by the PASS-PHRASE, for the given RECIPIENTS." (interactive "sPass phrase: sRecipients: P") (when conv-utf-8 (pgp-convert-to-utf-8 (region-beginning) (region-end)) ) (pgp-encrypt-pk-range pass-phrase recipients (region-beginning) (region-end))) (defun pgp-sign-encrypt-buffer (pass-phrase recipients &optional conv-utf-8) "Sign and encrypt the buffer, with pgp-signer key, unlocked by the PASS-PHRASE, for the given RECIPIENTS." (interactive "sPass phrase: sRecipients: P") (when conv-utf-8 (pgp-convert-to-utf-8 (point-min) (point-max)) ) (pgp-encrypt-pk-range pass-phrase recipients (point-min) (point-max))) (defun pgp-just-encrypt-region (recipients &optional conv-utf-8) "Encrypt the region, without signing, for the given RECIPIENTS." (interactive "sRecipients: P") (when conv-utf-8 (pgp-convert-to-utf-8 (region-beginning) (region-end)) ) (pgp-encrypt-pk-range nil recipients (region-beginning) (region-end))) (defun pgp-just-encrypt-buffer (recipients &optional conv-utf-8) "Encrypt the buffer, without signing, for the given RECIPIENTS." (interactive "sRecipients: P") (when conv-utf-8 (pgp-convert-to-utf-8 (point-min) (point-max)) ) (pgp-encrypt-pk-range nil recipients (point-min) (point-max))) (defun pgp-decrypt-region (pass-phrase &optional conv-utf-8) "Decrypt the region, for the keyid unlocked by the PASS-PHRASE." (interactive "sPass phrase: P") (auto-save-mode -1);; Decrypted buffers must not be auto-saved! (pgp-decrypt-pk-range pass-phrase (region-beginning) (region-end)) (when conv-utf-8 (pgp-convert-from-utf-8 (point-min) (point-max)) )) (defun pgp-decrypt-buffer (pass-phrase &optional conv-utf-8) "Decrypt the buffer, for the keyid unlocked by the PASS-PHRASE." (interactive "sPass phrase: P") (auto-save-mode -1);; Decrypted buffers must not be auto-saved! (pgp-decrypt-pk-range pass-phrase (point-min) (point-max)) (when conv-utf-8 (pgp-convert-from-utf-8 (point-min) (point-max)))) (defun gunzip () "Uncompress the current buffer." (interactive) (let ((out-buffer (pgp-prepare-out-buffer "Gunziped"))) (message "Gunziping...") (shell-command-on-region (point-min) (point-max) "gunzip" out-buffer (eq out-buffer (current-buffer)) nil) (set-buffer out-buffer) (view-mode 1) (delete-other-windows (get-buffer-window out-buffer)))) (defun invoke-ding-dictionary () "Retrieve the definition of the selected word with the Ding dictionary." (interactive) (save-excursion (shell-command "( ding -R -x > /dev/null < /dev/null 2>&1 & )" nil))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Some experimental stuff: (require 'mailheader) (defun get-emails-in-public-key-ring () "RETURN: a list of emails registered in your OpenPGP public key ring." (split-string (shell-command-to-string (concat (funcall pgp-command 'list-keys) "| sed -n -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/' " " -e 's/.*\(<.*@.*>\).*/\1/p' " "|sort -u")))) (defun mail-get-recipients () "RETURN: a list of emails set as recipients in current buffer mail be it as To:, CC: or BCC:." (save-excursion (goto-char 0) (remove-duplicates (mapcar (lambda (email) ;; let's clean-up the address. (cond ((string-match ".*\\(<.*@.*>\\).*" email 0) (substring email (match-beginning 1) (match-end 1))) ((string-match "@" email 0) (format "<%s>" email)) (t email))) (apply 'append (mapcar (lambda (ass) ;; lets select the to, cc, and bcc headers (if (member (car ass) '(to cc bcc)) (split-string (cdr ass) ","))) (mail-header-extract))))))) (require 'w3 nil t) (require 'url) (require 'pjb-emacs) (defun pgp-lookup-keys (critere) "DO: Search for CRITERE with http://wwwkeys.eu.pgp.net:11371/pks/lookup? and if a key is returned, add it to the public key ring. NOTE: We don't use gpg --recv-keys because this feature does not exist with pgp and because it only accept key ids, not emails..." (let ((result (url-retrieve-as-string (format "http://wwwkeys.eu.pgp.net:11371/pks/lookup?op=get&search=%s" (url-hexify-string critere))))) (if (string-match "No matching keys" result) nil ;; let's add the key to our public key ring. (save-excursion (pgp-prepare-err-buffer) (let ( (in-buffer (get-buffer-create (concat " Adding key for " critere))) ) (message (buffer-name in-buffer)) (set-buffer in-buffer) (insert result) (shell-command-on-region (point-min) (point-max) (funcall pgp-command 'add-keys) nil t pgp-err-buffer-name) ;; nothing interesting in stdout... (set-buffer in-buffer) (set-buffer-modified-p nil) (kill-buffer in-buffer) ;; let's report stderr... (set-buffer pgp-err-buffer-name) (message (buffer-string)) (erase-buffer)) (pgp-bury-err-buffer-if-empty) t)))) ;; (defun mail-test () ;; (interactive) ;; (let ( (pkr-emails (get-emails-in-public-key-ring)) ;; (recipients (mail-get-recipients)) ;; (cryppients nil) ;; there is a pubkey for them. ;; (clearients nil) ;; no pubkey found for them. ;; ) ;; (while recipients ;; (let ((recipient (car recipients))) ;; (if (member recipient pkr-emails) ;; (setq cryppients (cons recipient cryppients)) ;; ;; else lookup in wwwkeys.eu.pgp.net ;; (if (and (string-match "<.*@.*>" recipient) ;; (pgp-lookup-keys recipient)) ;; (setq cryppients (cons recipient cryppients)) ;; (setq clearients (cons recipient clearients))))) ;; (setq recipients (cdr recipients)));;while ;; (message "\ncryppients=%S\nclearients=%S\n" cryppients clearients) ;; (when (null clearients) ;; ;; ))) ;;;; THE END ;;;;