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