1 ;;; gpg-ring.el --- Major mode for editing GnuPG key rings.
3 ;; Copyright (C) 2000 RUS-CERT, University Of Stuttgart
5 ;; Author: Florian Weimer <Florian.Weimer@RUS.Uni-Stuttgart.DE>
6 ;; Maintainer: Florian Weimer <Florian.Weimer@RUS.Uni-Stuttgart.DE>
10 ;; This file is NOT (yet?) part of GNU Emacs.
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)
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.
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.
32 (eval-when-compile (require 'cl))
36 ;;; Customization: Groups:
38 (defgroup gpg-ring nil
39 "GNU Privacy Guard user interface."
40 :tag "GnuPG user interface"
43 ;;; Customization: Variables:
45 (defface gpg-ring-key-invalid-face
47 (:foreground "yellow" :background "red"))
48 (t (:bold t :italic t :underline t)))
49 "Face for strings indicating key invalidity."
52 (defface gpg-ring-uncertain-validity-face
53 '((((class color)) (:foreground "red"))
55 "Face for strings indicating uncertain validity."
58 (defface gpg-ring-full-validity-face
59 '((((class color)) (:foreground "ForestGreen" :bold t))
61 "Face for strings indicating key invalidity."
64 (defvar gpg-ring-mode-hook nil
65 "Normal hook run when entering GnuPG ring mode.")
69 (defconst gpg-ring-algo-alist
71 (rsa-encrypt-only . "RSA-E")
72 (rsa-sign-only . "RSA-S")
73 (elgamal-encrypt-only . "ELG-E")
76 "Alist mapping algorithm IDs to algorithm abbreviations.")
78 (defconst gpg-ring-trust-alist
79 '((not-known "???" gpg-ring-uncertain-validity-face)
80 (disabled "DIS" gpg-ring-key-invalid-face)
81 (revoked "REV" gpg-ring-key-invalid-face)
82 (expired "EXP" gpg-ring-key-invalid-face)
83 (trust-undefined "QES" gpg-ring-uncertain-validity-face)
84 (trust-none "NON" gpg-ring-uncertain-validity-face)
85 (trust-marginal "MAR")
86 (trust-full "FUL" gpg-ring-full-validity-face)
87 (trust-ultimate "ULT" gpg-ring-full-validity-face))
88 "Alist mapping trust IDs to trust abbrevs and faces.")
90 (defvar gpg-ring-mode-map
91 (let ((map (make-keymap)))
92 (suppress-keymap map t)
94 "Keymap for `gpg-ring-mode'.")
96 (define-key gpg-ring-mode-map "0" 'delete-window)
97 (define-key gpg-ring-mode-map "1" 'delete-other-windows)
98 (define-key gpg-ring-mode-map "M" 'gpg-ring-mark-process-all)
99 (define-key gpg-ring-mode-map "U" 'gpg-ring-unmark-all)
100 (define-key gpg-ring-mode-map "a" 'gpg-ring-toggle-show-unusable)
101 (define-key gpg-ring-mode-map "d" 'gpg-ring-mark-delete)
102 (define-key gpg-ring-mode-map "f" 'gpg-ring-update-key)
103 (define-key gpg-ring-mode-map "g" 'gpg-ring-update)
104 (define-key gpg-ring-mode-map "i" 'gpg-ring-show-key)
105 (define-key gpg-ring-mode-map "l" 'gpg-ring-toggle-show-all-ids)
106 (define-key gpg-ring-mode-map "m" 'gpg-ring-mark-process)
107 (define-key gpg-ring-mode-map "n" 'gpg-ring-next-record)
108 (define-key gpg-ring-mode-map "p" 'gpg-ring-previous-record)
109 (define-key gpg-ring-mode-map "q" 'gpg-ring-quit)
110 (define-key gpg-ring-mode-map "u" 'gpg-ring-unmark)
111 (define-key gpg-ring-mode-map "x" 'gpg-ring-extract-keys)
112 (define-key gpg-ring-mode-map "X" 'gpg-ring-extract-keys-to-kill)
114 (define-key gpg-ring-mode-map "\C-c\C-c" 'gpg-ring-action)
116 ;;; Internal functions:
118 (defvar gpg-ring-key-list
120 "List of keys in the key list buffer.")
121 (make-variable-buffer-local 'gpg-ring-key-list)
123 (defvar gpg-ring-update-funcs
125 "List of functions called to obtain the key list.")
126 (make-variable-buffer-local 'gpg-ring-update-funcs)
128 (defvar gpg-ring-show-unusable
130 "If t, show expired, revoked and disabled keys, too.")
131 (make-variable-buffer-local 'gpg-ring-show-unusable)
133 (defvar gpg-ring-show-all-ids
135 "If t, show all user IDs. If nil, show only the primary user ID.")
136 (make-variable-buffer-local 'gpg-ring-show-all-ids)
138 (defvar gpg-ring-marks-alist
140 "Alist of (UNIQUE-ID MARK KEY).
141 UNIQUE-ID is a unique key ID from GnuPG. MARK is either `?D'
142 (marked for deletion), or `?*' (marked for processing).")
143 (make-variable-buffer-local 'gpg-ring-marks-alist)
145 (defvar gpg-ring-action
147 "Function to call when `gpg-ring-action' is invoked.
148 A list of the keys which are marked for processing is passed as argument.")
149 (make-variable-buffer-local 'gpg-ring-action)
151 (defun gpg-ring-mode ()
152 "Mode for editing GnuPG key rings.
153 \\{gpg-ring-mode-map}
154 Turning on gpg-ring-mode runs `gpg-ring-mode-hook'."
156 (kill-all-local-variables)
157 (buffer-disable-undo)
158 (setq truncate-lines t)
159 (setq buffer-read-only t)
160 (use-local-map gpg-ring-mode-map)
161 (setq mode-name "Key Ring")
162 (setq major-mode 'gpg-ring-mode)
163 (run-hooks 'gpg-ring-mode-hook))
166 (defmacro gpg-ring-record-start (&optional pos)
167 "Return buffer position of start of record containing POS."
168 `(get-text-property (or ,pos (point)) 'gpg-record-start))
170 (defun gpg-ring-current-key (&optional pos)
171 "Return GnuPG key at POS, or at point if ommitted."
172 (or (get-text-property (or pos (point)) 'gpg-key)
173 (error "No record on current line")))
175 (defun gpg-ring-goto-record (pos)
176 "Go to record starting at POS.
177 Position point after the marks at the beginning of a record."
181 (defun gpg-ring-next-record ()
182 "Advances point to the start of the next record."
184 (let ((start (next-single-property-change
185 (point) 'gpg-record-start nil (point-max))))
186 ;; Don't advance to the last line of the buffer.
187 (when (/= start (point-max))
188 (gpg-ring-goto-record start))))
190 (defun gpg-ring-previous-record ()
191 "Advances point to the start of the previous record."
193 ;; The last line of the buffer doesn't contain a record.
194 (let ((start (gpg-ring-record-start)))
196 (gpg-ring-goto-record (previous-single-property-change
197 start 'gpg-record-start nil (point-min)))
198 (gpg-ring-goto-record
199 (gpg-ring-record-start (1- (point-max)))))))
201 (defun gpg-ring-set-mark (&optional pos mark)
202 "Set MARK on record at POS, or at point if POS is omitted.
203 If MARK is omitted, clear it."
205 (let* ((start (gpg-ring-record-start pos))
206 (key (gpg-ring-current-key start))
207 (id (gpg-key-unique-id key))
208 (entry (assoc id gpg-ring-marks-alist))
211 ;; Replace the mark character.
212 (subst-char-in-region (point) (1+ (point)) (char-after)
214 ;; Store the mark in alist.
216 (setcdr entry (if mark (list mark key)))
218 (push (list id mark key) gpg-ring-marks-alist))))))
220 (defun gpg-ring-marked-keys (&optional only-marked mark)
221 "Return list of key specs which have MARK.
222 If no marks are present and ONLY-MARKED is not nil, return singleton
223 list with key of the current record. If MARK is omitted, `?*' is
225 (let ((the-marker (or mark ?*))
226 (marks gpg-ring-marks-alist)
229 (let ((mark (pop marks)))
230 ;; If this entry has got the right mark ...
231 (when (equal (nth 1 mark) the-marker)
232 ;; ... rember the key spec.
233 (push (nth 2 mark) key-list))))
234 (or key-list (if (not only-marked) (list (gpg-ring-current-key))))))
236 (defun gpg-ring-mark-process ()
237 "Mark record at point for processing."
239 (gpg-ring-set-mark nil ?*)
240 (gpg-ring-next-record))
242 (defun gpg-ring-mark-delete ()
243 "Mark record at point for processing."
245 (gpg-ring-set-mark nil ?D)
246 (gpg-ring-next-record))
248 (defun gpg-ring-unmark ()
249 "Mark record at point for processing."
252 (gpg-ring-next-record))
254 (defun gpg-ring-mark-process-all ()
255 "Put process mark on all records."
257 (setq gpg-ring-marks-alist
258 (mapcar (lambda (key)
259 (list (gpg-key-unique-id key) ?* key))
261 (gpg-ring-regenerate))
263 (defun gpg-ring-unmark-all ()
264 "Remove all record marks."
266 (setq gpg-ring-marks-alist nil)
267 (gpg-ring-regenerate))
269 (defun gpg-ring-toggle-show-unusable ()
270 "Toggle value if `gpg-ring-show-unusable'."
272 (setq gpg-ring-show-unusable (not gpg-ring-show-unusable))
273 (gpg-ring-regenerate))
275 (defun gpg-ring-toggle-show-all-ids ()
276 "Toggle value of `gpg-ring-show-all-ids'."
278 (setq gpg-ring-show-all-ids (not gpg-ring-show-all-ids))
279 (gpg-ring-regenerate))
281 (defvar gpg-ring-output-buffer-name "*GnuPG Output*"
282 "Name buffer to which output from GnuPG is sent.")
284 (defmacro gpg-ring-with-output-buffer (&rest body)
285 "Erase GnuPG output buffer, evaluate BODY in it, and display it."
286 `(with-current-buffer (get-buffer-create gpg-ring-output-buffer-name)
288 (setq truncate-lines t)
290 (goto-char (point-min))
291 (display-buffer gpg-ring-output-buffer-name)))
293 (defun gpg-ring-quit ()
294 "Bury key list buffer and kill GnuPG output buffer."
296 (let ((output (get-buffer gpg-ring-output-buffer-name)))
298 (kill-buffer output)))
299 (when (eq 'gpg-ring-mode major-mode)
302 (defun gpg-ring-show-key ()
303 "Show information for current key."
305 (let ((keys (gpg-ring-marked-keys)))
306 (gpg-ring-with-output-buffer
307 (gpg-key-insert-information (gpg-key-unique-id-list keys)))))
309 (defun gpg-ring-extract-keys ()
310 "Export currently selected public keys in ASCII armor."
312 (let ((keys (gpg-ring-marked-keys)))
313 (gpg-ring-with-output-buffer
314 (gpg-key-insert-public-key (gpg-key-unique-id-list keys)))))
316 (defun gpg-ring-extract-keys-to-kill ()
317 "Export currently selected public keys in ASCII armor to kill ring."
319 (let ((keys (gpg-ring-marked-keys)))
321 (gpg-key-insert-public-key (gpg-key-unique-id-list keys))
322 (copy-region-as-kill (point-min) (point-max)))))
324 (defun gpg-ring-update-key ()
325 "Fetch key information from key server."
327 (let ((keys (gpg-ring-marked-keys)))
328 (gpg-ring-with-output-buffer
329 (gpg-key-retrieve (gpg-key-unique-id-list keys)))))
331 (defun gpg-ring-insert-key-stat (key)
332 (let* ((validity (gpg-key-validity key))
333 (validity-entry (assq validity gpg-ring-trust-alist))
334 (trust (gpg-key-trust key))
335 (trust-entry (assq trust gpg-ring-trust-alist)))
336 ;; Insert abbrev for key status.
337 (let ((start (point)))
338 (insert (nth 1 validity-entry))
339 ;; Change face if necessary.
340 (when (nth 2 validity-entry)
341 (add-text-properties start (point)
342 (list 'face (nth 2 validity-entry)))))
343 ;; Trust, key ID, length, algorithm, creation date.
344 (insert (format "/%s %-8s/%4d/%-5s created %s"
346 (gpg-short-key-id key)
348 (cdr (assq (gpg-key-algorithm key) gpg-ring-algo-alist))
349 (gpg-key-creation-date key)))
351 (when (gpg-key-expire-date key)
353 (let ((start (point))
354 (expired (eq 'expired validity))
356 (insert (if expired "EXPIRED" "expires")
357 " " (gpg-key-expire-date key))
359 (add-text-properties start (point)
360 '(face gpg-ring-key-invalid-face)))))))
362 (defun gpg-ring-insert-key (key &optional mark)
363 "Inserts description for KEY into current buffer before point."
364 (let ((start (point)))
365 (insert (if mark mark " ")
366 " " (gpg-key-primary-user-id key) "\n"
368 (gpg-ring-insert-key-stat key)
370 (when gpg-ring-show-all-ids
371 (let ((uids (gpg-key-user-ids key)))
373 (insert " ID " (pop uids) "\n"))))
374 (add-text-properties start (point)
375 (list 'gpg-record-start start
378 (defun gpg-ring-regenerate ()
379 "Regenerate the key list buffer from stored data."
381 (let* ((key-list gpg-ring-key-list)
382 ;; Record position of point.
383 (old-record (if (eobp) ; No record on last line.
385 (gpg-key-unique-id (gpg-ring-current-key))))
386 (old-pos (if old-record (- (point) (gpg-ring-record-start))))
387 found new-pos new-pos-offset buffer-read-only new-marks)
388 ;; Replace buffer contents with new data.
391 (let* ((key (pop key-list))
392 (id (gpg-key-unique-id key))
393 (mark (assoc id gpg-ring-marks-alist)))
394 (when (or gpg-ring-show-unusable
395 (not (memq (gpg-key-validity key)
396 '(disabled revoked expired))))
397 ;; Check if point was in this record.
398 (when (and old-record
399 (string-equal old-record id))
400 (setq new-pos (point))
401 (setq new-pos-offset (+ new-pos old-pos)))
402 ;; Check if this record was marked.
405 (push mark new-marks)
406 (gpg-ring-insert-key key (nth 1 mark)))
407 (gpg-ring-insert-key key)))))
408 ;; Replace mark alist with the new one (which does not contain
409 ;; marks for records which vanished during this update).
410 (setq gpg-ring-marks-alist new-marks)
413 ;; We were at the end of the buffer before.
414 (goto-char (point-max))
416 (if (and (< new-pos-offset (point-max))
417 (equal old-record (gpg-key-unique-id
418 (gpg-ring-current-key new-pos-offset))))
419 ;; Record is there, with offset.
420 (goto-char new-pos-offset)
421 ;; Record is there, but not offset.
423 ;; Record is not there.
424 (goto-char (point-min))))))
426 (defun gpg-ring-update ()
427 "Update the key list buffer with new data."
429 (let ((funcs gpg-ring-update-funcs)
431 ;; Merge the sorted lists obtained by calling elements of
432 ;; `gpg-ring-update-funcs'.
434 (let ((additional (funcall (pop funcs)))
436 (while (and additional old)
437 (if (gpg-key-lessp (car additional) (car old))
438 (push (pop additional) new)
439 (if (gpg-key-lessp (car old) (car additional))
441 ;; Keys are perhaps equal. Always Add old key.
443 ;; If new key is equal, drop it, otherwise add it as well.
444 (if (string-equal (gpg-key-unique-id (car old))
445 (gpg-key-unique-id (car additional)))
447 (push (pop additional) new)))))
448 ;; Store new list as old one for next round.
449 (setq old (nconc (nreverse new) old additional))))
450 ;; Store the list in the buffer.
451 (setq gpg-ring-key-list old))
452 (gpg-ring-regenerate))
454 (defun gpg-ring-action ()
455 "Perform the action associated with this buffer."
458 (funcall gpg-ring-action (gpg-ring-marked-keys))
459 (error "No action for this buffer specified")))
462 (defun gpg-ring-keys (&optional key-list-funcs action)
464 (let ((buffer (get-buffer-create "*GnuPG Key List*")))
465 (with-current-buffer buffer
467 (setq gpg-ring-action action)
468 (setq gpg-ring-update-funcs key-list-funcs key-list-funcs)
470 (goto-char (point-min)))
471 (switch-to-buffer buffer)))
474 (defun gpg-ring-public (key-spec)
475 "List public keys matching keys KEY-SPEC."
476 (interactive "sList public keys containing: ")
477 (gpg-ring-keys `((lambda () (gpg-key-list-keys ,key-spec)))))
481 ;;; gpg-ring.el ends here