Fixed passphrase caching.
[elisp/epg.git] / epa-file.el
1 ;;; epa-file.el --- the EasyPG Assistant hooks for transparent file encryption
2 ;; Copyright (C) 2006 Daiki Ueno
3
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
5 ;; Keywords: PGP, GnuPG
6
7 ;; This file is part of EasyPG.
8
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
23
24 ;;; Code:
25
26 (require 'epa)
27
28 (defgroup epa-file nil
29   "The EasyPG Assistant hooks for transparent file encryption"
30   :group 'epa)
31
32 (defcustom epa-file-name-regexp "\\.gpg\\'"
33   "Regexp which matches filenames to be encrypted with GnuPG."
34   :type 'regexp
35   :group 'epa-file)
36   
37 (defvar epa-file-passphrase-alist nil)
38
39 (defun epa-file-passphrase-callback-function (key-id file)
40   (if (eq key-id 'SYM)
41       (let ((entry (assoc file epa-file-passphrase-alist))
42             passphrase)
43         (or (copy-sequence (cdr entry))
44             (progn
45               (unless entry
46                 (setq entry (list file)
47                       epa-file-passphrase-alist (cons entry
48                                                  epa-file-passphrase-alist)))
49               (setq passphrase (epg-passphrase-callback-function key-id nil))
50               (setcdr entry (copy-sequence passphrase))
51               passphrase)))
52     (epg-passphrase-callback-function key-id nil)))
53
54 (defun epa-file-handler (operation &rest args)
55   (save-match-data
56     (let ((op (get operation 'epa-file)))
57       (if op
58           (apply op args)
59         (epa-file-run-real-handler operation args)))))
60
61 (defun epa-file-run-real-handler (operation args)
62   (let ((inhibit-file-name-handlers
63          (cons 'epa-file-handler
64                (and (eq inhibit-file-name-operation operation)
65                     inhibit-file-name-handlers)))
66         (inhibit-file-name-operation operation))
67     (apply operation args)))
68
69 (defvar last-coding-system-used)
70 (defun epa-file-insert-file-contents (file &optional visit beg end replace)
71   (barf-if-buffer-read-only)
72   (if (or beg end)
73       (error "Can't read the file partially."))
74   (setq file (expand-file-name file))
75   (let ((local-copy (epa-file-run-real-handler #'file-local-copy (list file)))
76         (context (epg-make-context))
77         point length entry)
78     (if visit
79         (setq buffer-file-name file))
80     (epg-context-set-passphrase-callback
81      context
82      (cons #'epa-file-passphrase-callback-function
83            file))
84     (unwind-protect
85         (progn
86           (if replace
87               (goto-char (point-min)))
88           (setq point (point))
89           (condition-case error
90               (insert (epg-decrypt-file context file nil))
91             (error (signal 'file-error
92                            (cons "Opening input file"
93                                  (nthcdr 2 error)))))
94           (setq length (- (point) point))
95           (if replace
96               (delete-region (point) (point-max)))
97           (decode-coding-region point (point) 'undecided)
98           (if (boundp 'last-coding-system-used)
99               (set-buffer-file-coding-system last-coding-system-used)
100             (set-buffer-file-coding-system default-buffer-file-coding-system))
101           (goto-char point))
102       (if (and local-copy
103                (file-exists-p local-copy))
104           (delete-file local-copy)))
105     (list file length)))
106 (put 'insert-file-contents 'epa-file 'epa-file-insert-file-contents)
107
108 (defun epa-file-write-region (start end file &optional append visit lockname
109                                     mustbenew)
110   (if append
111       (error "Can't append to the file."))
112   (setq file (expand-file-name file))
113   (let* ((coding-system (if (boundp 'last-coding-system-used)
114                             (condition-case nil
115                                 (write-region (point-min) (point-max) "/")
116                               (error last-coding-system-used))
117                           buffer-file-coding-system))
118          (context (epg-make-context))
119          (coding-system-for-write 'binary))
120     (epg-context-set-passphrase-callback
121      context
122      (cons #'epa-file-passphrase-callback-function
123            file))
124     (epa-file-run-real-handler
125      #'write-region
126      (list (epg-encrypt-string
127             context
128             (encode-coding-string (buffer-string) coding-system)
129             (mapcar (lambda (key)
130                       (epg-sub-key-id (car (epg-key-sub-key-list key))))
131                     (unless (assoc file epa-file-passphrase-alist)
132                       (epa-select-keys
133                        "Select recipents for encryption.
134 If no one is selected, symmetric encryption will be performed.  "))))
135            nil file append visit lockname mustbenew))
136     (if (boundp 'last-coding-system-used)
137         (setq last-coding-system-used coding-system))
138     (if (eq visit t)
139         (progn
140           (setq buffer-file-name file)
141           (set-visited-file-modtime))
142       (if (stringp visit)
143           (progn
144             (set-visited-file-modtime)
145             (setq buffer-file-name visit))))
146     (if (or (eq visit t)
147             (eq visit nil)
148             (stringp visit))
149         (message "Wrote %s" buffer-file-name))))
150 (put 'write-region 'epa-file 'epa-file-write-region)
151
152 (provide 'epa-file)
153
154 ;;; epa-file.el ends here