Synch with Gnus.
[elisp/gnus.git-] / lisp / gpg-ring.el
1 ;;; gpg-ring.el --- Major mode for editing GnuPG key rings.
2
3 ;; Copyright (C) 2000 RUS-CERT, University Of Stuttgart
4
5 ;; Author: Florian Weimer <Florian.Weimer@RUS.Uni-Stuttgart.DE>
6 ;; Maintainer: Florian Weimer <Florian.Weimer@RUS.Uni-Stuttgart.DE>
7 ;; Keywords: crypto
8 ;; Created: 2000-04-28
9
10 ;; This file is NOT (yet?) part of GNU Emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27
28 \f
29 ;;;; Code:
30
31 (require 'gpg)
32 (eval-when-compile 
33   (require 'cl))
34
35 ;;;; Customization:
36
37 ;;; Customization: Groups:
38
39 (defgroup gpg-ring nil
40   "GNU Privacy Guard user interface."
41   :tag "GnuPG user interface"
42   :group 'gpg)
43
44 ;;; Customization: Variables:
45
46 (defface gpg-ring-key-invalid-face 
47   '((((class color))
48      (:foreground "yellow" :background "red"))
49     (t (:bold t :italic t :underline t)))
50   "Face for strings indicating key invalidity."
51   :group 'gpg-ring)
52
53 (defface gpg-ring-uncertain-validity-face
54   '((((class color)) (:foreground "red"))
55     (t (:bold t)))
56   "Face for strings indicating uncertain validity."
57   :group 'gpg-ring)
58
59 (defface gpg-ring-full-validity-face
60   '((((class color)) (:foreground "ForestGreen" :bold t))
61     (t (:bold t)))
62   "Face for strings indicating key invalidity."
63   :group 'gpg-ring)
64
65 (defvar gpg-ring-mode-hook nil
66   "Normal hook run when entering GnuPG ring mode.")
67
68 ;;; Constants
69
70 (defconst gpg-ring-algo-alist
71   '((rsa . "RSA")
72     (rsa-encrypt-only . "RSA-E")
73     (rsa-sign-only . "RSA-S")
74     (elgamal-encrypt-only . "ELG-E")
75     (dsa . "DSA")
76     (elgamal . "ELG-E"))
77   "Alist mapping algorithm IDs to algorithm abbreviations.")
78     
79 (defconst gpg-ring-trust-alist
80   '((not-known       "???" gpg-ring-uncertain-validity-face)
81     (disabled        "DIS" gpg-ring-key-invalid-face)
82     (revoked         "REV" gpg-ring-key-invalid-face)
83     (expired         "EXP" gpg-ring-key-invalid-face)
84     (trust-undefined "QES" gpg-ring-uncertain-validity-face)
85     (trust-none      "NON" gpg-ring-uncertain-validity-face)
86     (trust-marginal  "MAR")
87     (trust-full      "FUL" gpg-ring-full-validity-face)
88     (trust-ultimate  "ULT" gpg-ring-full-validity-face))
89   "Alist mapping trust IDs to trust abbrevs and faces.")
90
91 (defvar gpg-ring-mode-map
92   (let ((map (make-keymap)))
93     (suppress-keymap map t)
94     map)
95   "Keymap for `gpg-ring-mode'.")
96
97 (define-key gpg-ring-mode-map "0" 'delete-window)
98 (define-key gpg-ring-mode-map "1" 'delete-other-windows)
99 (define-key gpg-ring-mode-map "M" 'gpg-ring-mark-process-all)
100 (define-key gpg-ring-mode-map "U" 'gpg-ring-unmark-all)
101 (define-key gpg-ring-mode-map "a" 'gpg-ring-toggle-show-unusable)
102 (define-key gpg-ring-mode-map "d" 'gpg-ring-mark-delete)
103 (define-key gpg-ring-mode-map "f" 'gpg-ring-update-key)
104 (define-key gpg-ring-mode-map "g" 'gpg-ring-update)
105 (define-key gpg-ring-mode-map "i" 'gpg-ring-show-key)
106 (define-key gpg-ring-mode-map "l" 'gpg-ring-toggle-show-all-ids)
107 (define-key gpg-ring-mode-map "m" 'gpg-ring-mark-process)
108 (define-key gpg-ring-mode-map "n" 'gpg-ring-next-record)
109 (define-key gpg-ring-mode-map "p" 'gpg-ring-previous-record)
110 (define-key gpg-ring-mode-map "q" 'gpg-ring-quit)
111 (define-key gpg-ring-mode-map "u" 'gpg-ring-unmark)
112 (define-key gpg-ring-mode-map "x" 'gpg-ring-extract-keys)
113 (define-key gpg-ring-mode-map "X" 'gpg-ring-extract-keys-to-kill)
114
115 (define-key gpg-ring-mode-map "\C-c\C-c" 'gpg-ring-action)
116
117 ;;; Internal functions:
118
119 (defvar gpg-ring-key-list
120   nil
121   "List of keys in the key list buffer.")
122 (make-variable-buffer-local 'gpg-ring-key-list)
123
124 (defvar gpg-ring-update-funcs
125   nil
126   "List of functions called to obtain the key list.")
127 (make-variable-buffer-local 'gpg-ring-update-funcs)
128
129 (defvar gpg-ring-show-unusable
130   nil
131   "If t, show expired, revoked and disabled keys, too.")
132 (make-variable-buffer-local 'gpg-ring-show-unusable)
133
134 (defvar gpg-ring-show-all-ids
135   nil
136   "If t, show all user IDs.  If nil, show only the primary user ID.")
137 (make-variable-buffer-local 'gpg-ring-show-all-ids)
138
139 (defvar gpg-ring-marks-alist
140   nil
141   "Alist of (UNIQUE-ID MARK KEY).
142 UNIQUE-ID is a unique key ID from GnuPG.  MARK is either `?D'
143 (marked for deletion), or `?*' (marked for processing).")
144 (make-variable-buffer-local 'gpg-ring-marks-alist)
145
146 (defvar gpg-ring-action
147   nil
148   "Function to call when `gpg-ring-action' is invoked.
149 A list of the keys which are marked for processing is passed as argument.")
150 (make-variable-buffer-local 'gpg-ring-action)
151
152 (defun gpg-ring-mode ()
153   "Mode for editing GnuPG key rings.
154 \\{gpg-ring-mode-map}
155 Turning on gpg-ring-mode runs `gpg-ring-mode-hook'."
156   (interactive)
157   (kill-all-local-variables)
158   (buffer-disable-undo)
159   (setq truncate-lines t)
160   (setq buffer-read-only t)
161   (use-local-map gpg-ring-mode-map)
162   (setq mode-name "Key Ring")
163   (setq major-mode 'gpg-ring-mode)
164   (run-hooks 'gpg-ring-mode-hook))
165
166
167 (defmacro gpg-ring-record-start (&optional pos)
168   "Return buffer position of start of record containing POS."
169   `(get-text-property (or ,pos (point)) 'gpg-record-start))
170                                          
171 (defun gpg-ring-current-key (&optional pos)
172   "Return GnuPG key at POS, or at point if ommitted."
173   (or (get-text-property (or pos (point)) 'gpg-key)
174       (error "No record on current line")))
175
176 (defun gpg-ring-goto-record (pos)
177   "Go to record starting at POS.
178 Position point after the marks at the beginning of a record."
179   (goto-char pos)
180   (forward-char 2))
181
182 (defun gpg-ring-next-record ()
183   "Advances point to the start of the next record."
184   (interactive)
185   (let ((start (next-single-property-change 
186                 (point) 'gpg-record-start nil (point-max))))
187     ;; Don't advance to the last line of the buffer.
188     (when (/= start (point-max))
189         (gpg-ring-goto-record start))))
190
191 (defun gpg-ring-previous-record ()
192   "Advances point to the start of the previous record."
193   (interactive)
194   ;; The last line of the buffer doesn't contain a record.
195   (let ((start (gpg-ring-record-start)))
196     (if start
197         (gpg-ring-goto-record (previous-single-property-change 
198                                     start 'gpg-record-start nil (point-min)))
199       (gpg-ring-goto-record
200        (gpg-ring-record-start (1- (point-max)))))))
201       
202 (defun gpg-ring-set-mark (&optional pos mark)
203   "Set MARK on record at POS, or at point if POS is omitted.
204 If MARK is omitted, clear it."
205   (save-excursion
206     (let* ((start (gpg-ring-record-start pos))
207            (key (gpg-ring-current-key start))
208            (id (gpg-key-unique-id key))
209            (entry (assoc id gpg-ring-marks-alist))
210            buffer-read-only)
211       (goto-char start)
212       ;; Replace the mark character.
213       (subst-char-in-region (point) (1+ (point)) (char-after) 
214                             (or mark ? ))
215       ;; Store the mark in alist.
216       (if entry
217           (setcdr entry (if mark (list mark key)))
218         (when mark
219           (push (list id mark key) gpg-ring-marks-alist))))))
220
221 (defun gpg-ring-marked-keys (&optional only-marked mark)
222   "Return list of key specs which have MARK.
223 If no marks are present and ONLY-MARKED is not nil, return singleton
224 list with key of the current record.  If MARK is omitted, `?*' is
225 used."
226   (let ((the-marker (or mark ?*))
227         (marks gpg-ring-marks-alist)
228         key-list)
229     (while marks
230       (let ((mark (pop marks)))
231         ;; If this entry has got the right mark ...
232         (when (equal (nth 1 mark) the-marker)
233           ;; ... rember the key spec.
234           (push (nth 2 mark) key-list))))
235     (or key-list (if (not only-marked) (list (gpg-ring-current-key))))))
236
237 (defun gpg-ring-mark-process ()
238   "Mark record at point for processing."
239   (interactive)
240   (gpg-ring-set-mark nil ?*)
241   (gpg-ring-next-record))
242
243 (defun gpg-ring-mark-delete ()
244   "Mark record at point for processing."
245   (interactive)
246   (gpg-ring-set-mark nil ?D)
247   (gpg-ring-next-record))
248
249 (defun gpg-ring-unmark ()
250   "Mark record at point for processing."
251   (interactive)
252   (gpg-ring-set-mark)
253   (gpg-ring-next-record))
254
255 (defun gpg-ring-mark-process-all ()
256   "Put process mark on all records."
257   (interactive)
258   (setq gpg-ring-marks-alist 
259         (mapcar (lambda (key)
260                   (list (gpg-key-unique-id key) ?* key))
261                 gpg-ring-key-list))
262   (gpg-ring-regenerate))
263
264 (defun gpg-ring-unmark-all ()
265   "Remove all record marks."
266   (interactive)
267   (setq gpg-ring-marks-alist nil)
268   (gpg-ring-regenerate))
269
270 (defun gpg-ring-toggle-show-unusable ()
271   "Toggle value if `gpg-ring-show-unusable'."
272   (interactive)
273   (setq gpg-ring-show-unusable (not gpg-ring-show-unusable))
274   (gpg-ring-regenerate))
275   
276 (defun gpg-ring-toggle-show-all-ids ()
277   "Toggle value of `gpg-ring-show-all-ids'."
278   (interactive)
279   (setq gpg-ring-show-all-ids (not gpg-ring-show-all-ids))
280   (gpg-ring-regenerate))
281
282 (defvar gpg-ring-output-buffer-name "*GnuPG Output*"
283   "Name buffer to which output from GnuPG is sent.")
284
285 (defmacro gpg-ring-with-output-buffer (&rest body)
286   "Erase GnuPG output buffer, evaluate BODY in it, and display it."
287   `(with-current-buffer (get-buffer-create gpg-ring-output-buffer-name)
288      (erase-buffer)
289      (setq truncate-lines t)
290      ,@body
291      (goto-char (point-min))
292      (display-buffer gpg-ring-output-buffer-name)))
293
294 (defun gpg-ring-quit ()
295   "Bury key list buffer and kill GnuPG output buffer."
296   (interactive)
297   (let ((output (get-buffer gpg-ring-output-buffer-name)))
298     (when output
299       (kill-buffer output)))
300   (when (eq 'gpg-ring-mode major-mode)
301     (bury-buffer)))
302
303 (defun gpg-ring-show-key ()
304   "Show information for current key."
305   (interactive)
306   (let ((keys (gpg-ring-marked-keys)))
307     (gpg-ring-with-output-buffer
308      (gpg-key-insert-information (gpg-key-unique-id-list keys)))))
309
310 (defun gpg-ring-extract-keys ()
311   "Export currently selected public keys in ASCII armor."
312   (interactive)
313   (let ((keys (gpg-ring-marked-keys)))
314     (gpg-ring-with-output-buffer
315      (gpg-key-insert-public-key (gpg-key-unique-id-list keys)))))
316
317 (defun gpg-ring-extract-keys-to-kill ()
318   "Export currently selected public keys in ASCII armor to kill ring."
319   (interactive)
320   (let ((keys (gpg-ring-marked-keys)))
321     (with-temp-buffer
322       (gpg-key-insert-public-key (gpg-key-unique-id-list keys))
323       (copy-region-as-kill (point-min) (point-max)))))
324
325 (defun gpg-ring-update-key ()
326   "Fetch key information from key server."
327   (interactive)
328   (let ((keys (gpg-ring-marked-keys)))
329     (gpg-ring-with-output-buffer
330      (gpg-key-retrieve (gpg-key-unique-id-list keys)))))
331
332 (defun gpg-ring-insert-key-stat (key)
333   (let* ((validity (gpg-key-validity key))
334          (validity-entry (assq validity gpg-ring-trust-alist))
335          (trust (gpg-key-trust key))
336          (trust-entry (assq trust gpg-ring-trust-alist)))
337     ;; Insert abbrev for key status.
338     (let ((start (point)))
339       (insert (nth 1 validity-entry))
340       ;; Change face if necessary.
341       (when (nth 2 validity-entry)
342         (add-text-properties start (point) 
343                              (list 'face (nth 2 validity-entry)))))
344     ;; Trust, key ID, length, algorithm, creation date.
345     (insert (format "/%s %-8s/%4d/%-5s created %s"
346                     (nth 1 trust-entry)
347                     (gpg-short-key-id key)
348                     (gpg-key-length key) 
349                     (cdr (assq (gpg-key-algorithm key) gpg-ring-algo-alist))
350                     (gpg-key-creation-date key)))
351     ;; Expire date.
352     (when (gpg-key-expire-date key)
353       (insert ", ")
354       (let ((start (point))
355             (expired (eq 'expired validity))
356             (notice (concat )))
357         (insert (if expired "EXPIRED" "expires")
358                 " " (gpg-key-expire-date key))
359         (when expired
360           (add-text-properties start (point) 
361                                '(face gpg-ring-key-invalid-face)))))))
362
363 (defun gpg-ring-insert-key (key &optional mark)
364   "Inserts description for KEY into current buffer before point."
365   (let ((start (point)))
366     (insert (if mark mark " ")
367             " " (gpg-key-primary-user-id key) "\n"
368             "    ")
369     (gpg-ring-insert-key-stat key)
370     (insert "\n")
371     (when gpg-ring-show-all-ids
372       (let ((uids (gpg-key-user-ids key)))
373         (while uids
374           (insert "     ID " (pop uids) "\n"))))
375     (add-text-properties start (point)
376                          (list 'gpg-record-start start
377                                'gpg-key key))))
378
379 (defun gpg-ring-regenerate ()
380   "Regenerate the key list buffer from stored data."
381   (interactive)
382   (let* ((key-list gpg-ring-key-list)
383          ;; Record position of point.
384          (old-record (if (eobp)         ; No record on last line.
385                          nil 
386                        (gpg-key-unique-id (gpg-ring-current-key))))
387          (old-pos (if old-record (- (point) (gpg-ring-record-start))))
388          found new-pos new-pos-offset buffer-read-only new-marks)
389     ;; Replace buffer contents with new data.
390     (erase-buffer)
391     (while key-list
392       (let* ((key (pop key-list))
393              (id (gpg-key-unique-id key))
394              (mark (assoc id gpg-ring-marks-alist)))
395         (when (or gpg-ring-show-unusable
396                   (not (memq (gpg-key-validity key) 
397                              '(disabled revoked expired))))
398           ;; Check if point was in this record.
399           (when (and old-record 
400                      (string-equal old-record id))
401             (setq new-pos (point))
402             (setq new-pos-offset (+ new-pos old-pos)))
403           ;; Check if this record was marked.
404           (if (nth 1 mark)
405               (progn
406                 (push mark new-marks)
407                 (gpg-ring-insert-key key (nth 1 mark)))
408             (gpg-ring-insert-key key)))))
409     ;; Replace mark alist with the new one (which does not contain
410     ;; marks for records which vanished during this update).
411     (setq gpg-ring-marks-alist new-marks)
412     ;; Restore point.
413     (if (not old-record)
414         ;; We were at the end of the buffer before.
415         (goto-char (point-max))
416       (if new-pos
417           (if (and (< new-pos-offset (point-max))
418                    (equal old-record (gpg-key-unique-id 
419                                       (gpg-ring-current-key new-pos-offset))))
420               ;; Record is there, with offset.
421               (goto-char new-pos-offset)
422             ;; Record is there, but not offset.
423             (goto-char new-pos))
424         ;; Record is not there.
425         (goto-char (point-min))))))
426
427 (defun gpg-ring-update ()
428   "Update the key list buffer with new data."
429   (interactive)
430   (let ((funcs gpg-ring-update-funcs)
431         old)
432     ;; Merge the sorted lists obtained by calling elements of
433     ;; `gpg-ring-update-funcs'.
434     (while funcs 
435       (let ((additional (funcall (pop funcs)))
436             new)
437         (while (and additional old)
438           (if (gpg-key-lessp (car additional) (car old))
439               (push (pop additional) new)
440             (if (gpg-key-lessp (car old) (car additional))
441                 (push (pop old) new)
442               ;; Keys are perhaps equal.  Always Add old key.
443               (push (pop old) new)
444               ;; If new key is equal, drop it, otherwise add it as well.
445               (if (string-equal (gpg-key-unique-id (car old))
446                                 (gpg-key-unique-id (car additional)))
447                   (pop additional)
448                 (push (pop additional) new)))))
449         ;; Store new list as old one for next round.
450         (setq old (nconc (nreverse new) old additional))))
451     ;; Store the list in the buffer.
452     (setq gpg-ring-key-list old))
453   (gpg-ring-regenerate))
454
455 (defun gpg-ring-action ()
456   "Perform the action associated with this buffer."
457   (interactive)
458   (if gpg-ring-action
459       (funcall gpg-ring-action (gpg-ring-marked-keys))
460     (error "No action for this buffer specified")))
461      
462 ;;;###autoload
463 (defun gpg-ring-keys (&optional key-list-funcs action)
464   (interactive)
465   (let ((buffer (get-buffer-create "*GnuPG Key List*")))
466     (with-current-buffer buffer
467       (gpg-ring-mode)
468       (setq gpg-ring-action action)
469       (setq gpg-ring-update-funcs key-list-funcs key-list-funcs)
470       (gpg-ring-update)
471       (goto-char (point-min)))
472     (switch-to-buffer buffer)))
473
474 ;;;###autoload
475 (defun gpg-ring-public (key-spec)
476   "List public keys matching keys KEY-SPEC."
477   (interactive "sList public keys containing: ")
478   (gpg-ring-keys  `((lambda () (gpg-key-list-keys ,key-spec)))))
479
480 (provide 'gpg-ring)
481
482 ;;; gpg-ring.el ends here