OSDN Git Service

(epg-make-context): New slot "operation-data".
[epg/epg.git] / epa-file.el
1 ;;; epa-file.el --- the EasyPG Assistant, 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 (defun epa-file--file-name-regexp-set (variable value)
33   (set-default variable value)
34   (if (fboundp 'epa-file-name-regexp-update)
35       (epa-file-name-regexp-update)))
36
37 (defcustom epa-file-name-regexp "\\.gpg\\(~\\|\\.~[0-9]+~\\)?\\'"
38   "Regexp which matches filenames to be encrypted with GnuPG.
39
40 If you set this outside Custom while epa-file is already enabled, you
41 have to call `epa-file-name-regexp-update' after setting it to
42 properly update file-name-handler-alist.  Setting this through Custom
43 does that automatically."
44   :type 'regexp
45   :group 'epa-file
46   :set 'epa-file--file-name-regexp-set)
47
48 (defcustom epa-file-cache-passphrase-for-symmetric-encryption nil
49   "If non-nil, cache passphrase for symmetric encryption."
50   :type 'boolean
51   :group 'epa-file)
52
53 (defcustom epa-file-inhibit-auto-save t
54   "If non-nil, disable auto-saving when opening an encrypted file."
55   :type 'boolean
56   :group 'epa-file)
57
58 (defcustom epa-file-select-keys nil
59   "If non-nil, always asks user to select recipients."
60   :type 'boolean
61   :group 'epa-file)
62
63 (defvar epa-file-encrypt-to nil
64   "*Recipient(s) used for encrypting files.
65 May either be a string or a list of strings.")
66
67 ;;;###autoload
68 (put 'epa-file-encrypt-to 'safe-local-variable
69      (lambda (val)
70        (or (stringp val)
71            (and (listp val)
72                 (catch 'safe
73                   (mapc (lambda (elt)
74                           (unless (stringp elt)
75                             (throw 'safe nil)))
76                         val)
77                   t)))))
78
79 ;;;###autoload
80 (put 'epa-file-encrypt-to 'permanent-local t)
81
82 (defvar epa-file-handler
83   (cons epa-file-name-regexp 'epa-file-handler))
84
85 (defvar epa-file-auto-mode-alist-entry
86   (list epa-file-name-regexp nil 'epa-file))
87
88 (defvar epa-file-passphrase-alist nil)
89
90 (eval-and-compile
91   (if (fboundp 'encode-coding-string)
92       (defalias 'epa-file--encode-coding-string 'encode-coding-string)
93     (defalias 'epa-file--encode-coding-string 'identity)))
94
95 (eval-and-compile
96   (if (fboundp 'decode-coding-string)
97       (defalias 'epa-file--decode-coding-string 'decode-coding-string)
98     (defalias 'epa-file--decode-coding-string 'identity)))
99
100 (defun epa-file-name-regexp-update ()
101   (interactive)
102   (unless (equal (car epa-file-handler) epa-file-name-regexp)
103     (setcar epa-file-handler epa-file-name-regexp)))
104
105 (defun epa-file-passphrase-callback-function (context key-id file)
106   (if (and epa-file-cache-passphrase-for-symmetric-encryption
107            (eq key-id 'SYM))
108       (let ((entry (assoc file epa-file-passphrase-alist))
109             passphrase)
110         (or (copy-sequence (cdr entry))
111             (progn
112               (unless entry
113                 (setq entry (list file)
114                       epa-file-passphrase-alist (cons entry
115                                                  epa-file-passphrase-alist)))
116               (setq passphrase (epa-passphrase-callback-function context
117                                                                  key-id nil))
118               (setcdr entry (copy-sequence passphrase))
119               passphrase)))
120     (epa-passphrase-callback-function context key-id nil)))
121
122 (defun epa-file-handler (operation &rest args)
123   (save-match-data
124     (let ((op (get operation 'epa-file)))
125       (if op
126           (apply op args)
127         (epa-file-run-real-handler operation args)))))
128
129 (defun epa-file-run-real-handler (operation args)
130   (let ((inhibit-file-name-handlers
131          (cons 'epa-file-handler
132                (and (eq inhibit-file-name-operation operation)
133                     inhibit-file-name-handlers)))
134         (inhibit-file-name-operation operation))
135     (apply operation args)))
136
137 (defun epa-file-decode-and-insert (string file visit beg end replace)
138   (if (fboundp 'decode-coding-inserted-region)
139       (save-restriction
140         (narrow-to-region (point) (point))
141         (let ((multibyte enable-multibyte-characters))
142           (set-buffer-multibyte nil)
143           (insert string)
144           (set-buffer-multibyte multibyte)
145           (decode-coding-inserted-region
146            (point-min) (point-max)
147            (substring file 0 (string-match epa-file-name-regexp file))
148            visit beg end replace)))
149     (insert (epa-file--decode-coding-string string (or coding-system-for-read
150                                                        'undecided)))))
151
152 (defvar last-coding-system-used)
153 (defun epa-file-insert-file-contents (file &optional visit beg end replace)
154   (barf-if-buffer-read-only)
155   (if (and visit (or beg end))
156       (error "Attempt to visit less than an entire file"))
157   (setq file (expand-file-name file))
158   (let* ((local-copy
159           (condition-case inl
160               (epa-file-run-real-handler #'file-local-copy (list file))
161             (error)))
162          (local-file (or local-copy file))
163          (context (epg-make-context))
164          string length entry)
165     (if visit
166         (setq buffer-file-name file))
167     (epg-context-set-passphrase-callback
168      context
169      (cons #'epa-file-passphrase-callback-function
170            local-file))
171     (epg-context-set-progress-callback context
172                                        #'epa-progress-callback-function)
173     (unwind-protect
174         (progn
175           (if replace
176               (goto-char (point-min)))
177           (condition-case error
178               (setq string (epg-decrypt-file context local-file nil))
179             (error
180              (if (setq entry (assoc file epa-file-passphrase-alist))
181                  (setcdr entry nil))
182              (signal 'file-error
183                      (cons "Opening input file" (cdr error)))))
184           (make-local-variable 'epa-file-encrypt-to)
185           (setq epa-file-encrypt-to
186                 (mapcar #'car (epg-context-result-for context 'encrypted-to)))
187           (if (or beg end)
188               (setq string (substring string (or beg 0) end)))
189           (save-excursion
190             (save-restriction
191               (narrow-to-region (point) (point))
192               (epa-file-decode-and-insert string file visit beg end replace)
193               (setq length (- (point-max) (point-min))))
194             (if replace
195                 (delete-region (point) (point-max)))))
196       (if (and local-copy
197                (file-exists-p local-copy))
198           (delete-file local-copy)))
199     (list file length)))
200 (put 'insert-file-contents 'epa-file 'epa-file-insert-file-contents)
201
202 (defun epa-file-write-region (start end file &optional append visit lockname
203                                     mustbenew)
204   (if append
205       (error "Can't append to the file."))
206   (setq file (expand-file-name file))
207   (let* ((coding-system (or coding-system-for-write
208                             (if (fboundp 'select-safe-coding-system)
209                                 ;; This is needed since Emacs 22 has
210                                 ;; no-conversion setting for *.gpg in
211                                 ;; `auto-coding-alist'.
212                                 (let ((buffer-file-name
213                                        (file-name-sans-extension file)))
214                                   (select-safe-coding-system
215                                    (point-min) (point-max)))
216                               buffer-file-coding-system)))
217          (context (epg-make-context))
218          (coding-system-for-write 'binary)
219          string entry
220          (recipients
221           (cond
222            ((listp epa-file-encrypt-to) epa-file-encrypt-to)
223            ((stringp epa-file-encrypt-to) (list epa-file-encrypt-to)))))
224     (epg-context-set-passphrase-callback
225      context
226      (cons #'epa-file-passphrase-callback-function
227            file))
228     (epg-context-set-progress-callback context
229                                        #'epa-progress-callback-function)
230     (epg-context-set-armor context epa-armor)
231     (condition-case error
232         (setq string
233               (epg-encrypt-string
234                context
235                (if (stringp start)
236                    (epa-file--encode-coding-string start coding-system)
237                  (epa-file--encode-coding-string (buffer-substring start end)
238                                                  coding-system))
239                (if (or epa-file-select-keys
240                        (not (local-variable-p 'epa-file-encrypt-to
241                                               (current-buffer))))
242                    (epa-select-keys
243                     context
244                     "Select recipents for encryption.
245 If no one is selected, symmetric encryption will be performed.  "
246                     recipients)
247                  (if epa-file-encrypt-to
248                      (epg-list-keys context recipients)))))
249       (error
250        (if (setq entry (assoc file epa-file-passphrase-alist))
251            (setcdr entry nil))
252        (signal 'file-error (cons "Opening output file" (cdr error)))))
253     (epa-file-run-real-handler
254      #'write-region
255      (list string nil file append visit lockname mustbenew))
256     (if (boundp 'last-coding-system-used)
257         (setq last-coding-system-used coding-system))
258     (if (eq visit t)
259         (progn
260           (setq buffer-file-name file)
261           (set-visited-file-modtime))
262       (if (stringp visit)
263           (progn
264             (set-visited-file-modtime)
265             (setq buffer-file-name visit))))
266     (if (or (eq visit t)
267             (eq visit nil)
268             (stringp visit))
269         (message "Wrote %s" buffer-file-name))))
270 (put 'write-region 'epa-file 'epa-file-write-region)
271
272 (defun epa-file-find-file-hook ()
273   (if (and buffer-file-name
274            (string-match epa-file-name-regexp buffer-file-name)
275            epa-file-inhibit-auto-save)
276       (auto-save-mode 0))
277   (set-buffer-modified-p nil))
278
279 (defun epa-file-select-keys ()
280   "Select recipients for encryption."
281   (interactive)
282   (make-local-variable 'epa-file-encrypt-to)
283   (setq epa-file-encrypt-to
284         (epa-select-keys
285          (epg-make-context)
286          "Select recipents for encryption.
287 If no one is selected, symmetric encryption will be performed.  ")))
288
289 ;;;###autoload
290 (defun epa-file-enable ()
291   (interactive)
292   (if (memq epa-file-handler file-name-handler-alist)
293       (message "`epa-file' already enabled")
294     (setq file-name-handler-alist
295           (cons epa-file-handler file-name-handler-alist))
296     (add-hook 'find-file-hooks 'epa-file-find-file-hook)
297     (setq auto-mode-alist (cons epa-file-auto-mode-alist-entry auto-mode-alist))
298     (message "`epa-file' enabled")))
299
300 ;;;###autoload
301 (defun epa-file-disable ()
302   (interactive)
303   (if (memq epa-file-handler file-name-handler-alist)
304       (progn
305         (setq file-name-handler-alist
306               (delq epa-file-handler file-name-handler-alist))
307         (remove-hook 'find-file-hooks 'epa-file-find-file-hook)
308         (setq auto-mode-alist (delq epa-file-auto-mode-alist-entry
309                                     auto-mode-alist))
310         (message "`epa-file' disabled"))
311     (message "`epa-file' already disabled")))
312
313 (provide 'epa-file)
314
315 ;;; epa-file.el ends here