Merge from trunk.
[bpt/emacs.git] / lisp / epa-file.el
1 ;;; epa-file.el --- the EasyPG Assistant, transparent file encryption -*- lexical-binding: t -*-
2 ;; Copyright (C) 2006-2013 Free Software Foundation, Inc.
3
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
5 ;; Keywords: PGP, GnuPG
6 ;; Package: epa
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Code:
24
25 (require 'epa)
26 (require 'epa-hook)
27
28 (defcustom epa-file-cache-passphrase-for-symmetric-encryption nil
29 "If non-nil, cache passphrase for symmetric encryption.
30
31 For security reasons, this option is turned off by default and
32 not recommended to use. Instead, consider using gpg-agent which
33 does the same job in a safer way. See Info node `(epa) Caching
34 Passphrases' for more information.
35
36 Note that this option has no effect if you use GnuPG 2.0."
37 :type 'boolean
38 :group 'epa-file)
39
40 (defcustom epa-file-select-keys nil
41 "Control whether or not to pop up the key selection dialog.
42
43 If t, always asks user to select recipients.
44 If nil, query user only when `epa-file-encrypt-to' is not set.
45 If neither t nor nil, doesn't ask user. In this case, symmetric
46 encryption is used."
47 :type '(choice (const :tag "Ask always" t)
48 (const :tag "Ask when recipients are not set" nil)
49 (const :tag "Don't ask" silent))
50 :group 'epa-file)
51
52 (defvar epa-file-passphrase-alist nil)
53
54 (eval-and-compile
55 (if (fboundp 'encode-coding-string)
56 (defalias 'epa-file--encode-coding-string 'encode-coding-string)
57 (defalias 'epa-file--encode-coding-string 'identity)))
58
59 (eval-and-compile
60 (if (fboundp 'decode-coding-string)
61 (defalias 'epa-file--decode-coding-string 'decode-coding-string)
62 (defalias 'epa-file--decode-coding-string 'identity)))
63
64 (defun epa-file-passphrase-callback-function (context key-id file)
65 (if (and epa-file-cache-passphrase-for-symmetric-encryption
66 (eq key-id 'SYM))
67 (progn
68 (setq file (file-truename file))
69 (let ((entry (assoc file epa-file-passphrase-alist))
70 passphrase)
71 (or (copy-sequence (cdr entry))
72 (progn
73 (unless entry
74 (setq entry (list file)
75 epa-file-passphrase-alist
76 (cons entry
77 epa-file-passphrase-alist)))
78 (setq passphrase (epa-passphrase-callback-function context
79 key-id
80 file))
81 (setcdr entry (copy-sequence passphrase))
82 passphrase))))
83 (epa-passphrase-callback-function context key-id file)))
84
85 ;;;###autoload
86 (defun epa-file-handler (operation &rest args)
87 (save-match-data
88 (let ((op (get operation 'epa-file)))
89 (if op
90 (apply op args)
91 (epa-file-run-real-handler operation args)))))
92
93 (defun epa-file-run-real-handler (operation args)
94 (let ((inhibit-file-name-handlers
95 (cons 'epa-file-handler
96 (and (eq inhibit-file-name-operation operation)
97 inhibit-file-name-handlers)))
98 (inhibit-file-name-operation operation))
99 (apply operation args)))
100
101 (defun epa-file-decode-and-insert (string file visit beg end replace)
102 (if (fboundp 'decode-coding-inserted-region)
103 (save-restriction
104 (narrow-to-region (point) (point))
105 (insert (if enable-multibyte-characters
106 (string-to-multibyte string)
107 string))
108 (decode-coding-inserted-region
109 (point-min) (point-max)
110 (substring file 0 (string-match epa-file-name-regexp file))
111 visit beg end replace))
112 (insert (epa-file--decode-coding-string string (or coding-system-for-read
113 'undecided)))))
114
115 (defvar epa-file-error nil)
116 (defun epa-file--find-file-not-found-function ()
117 (let ((error epa-file-error))
118 (save-window-excursion
119 (kill-buffer))
120 (signal 'file-error
121 (cons "Opening input file" (cdr error)))))
122
123 (defvar last-coding-system-used)
124 (defun epa-file-insert-file-contents (file &optional visit beg end replace)
125 (barf-if-buffer-read-only)
126 (if (and visit (or beg end))
127 (error "Attempt to visit less than an entire file"))
128 (setq file (expand-file-name file))
129 (let* ((local-copy
130 (condition-case nil
131 (epa-file-run-real-handler #'file-local-copy (list file))
132 (error)))
133 (local-file (or local-copy file))
134 (context (epg-make-context))
135 (buf (current-buffer))
136 string length entry)
137 (if visit
138 (setq buffer-file-name file))
139 (epg-context-set-passphrase-callback
140 context
141 (cons #'epa-file-passphrase-callback-function
142 local-file))
143 (epg-context-set-progress-callback
144 context
145 (cons #'epa-progress-callback-function
146 (format "Decrypting %s" file)))
147 (unwind-protect
148 (progn
149 (if replace
150 (goto-char (point-min)))
151 (condition-case error
152 (setq string (epg-decrypt-file context local-file nil))
153 (error
154 (if (setq entry (assoc file epa-file-passphrase-alist))
155 (setcdr entry nil))
156 ;; Hack to prevent find-file from opening empty buffer
157 ;; when decryption failed (bug#6568). See the place
158 ;; where `find-file-not-found-functions' are called in
159 ;; `find-file-noselect-1'.
160 (when (file-exists-p local-file)
161 (setq-local epa-file-error error)
162 (add-hook 'find-file-not-found-functions
163 'epa-file--find-file-not-found-function
164 nil t))
165 (signal 'file-error
166 (cons "Opening input file" (cdr error)))))
167 (set-buffer buf) ;In case timer/filter changed/killed it (bug#16029)!
168 (setq-local epa-file-encrypt-to
169 (mapcar #'car (epg-context-result-for
170 context 'encrypted-to)))
171 (if (or beg end)
172 (setq string (substring string (or beg 0) end)))
173 (save-excursion
174 ;; If visiting, bind off buffer-file-name so that
175 ;; file-locking will not ask whether we should
176 ;; really edit the buffer.
177 (let ((buffer-file-name
178 (if visit nil buffer-file-name)))
179 (save-restriction
180 (narrow-to-region (point) (point))
181 (epa-file-decode-and-insert string file visit beg end replace)
182 (setq length (- (point-max) (point-min))))
183 (if replace
184 (delete-region (point) (point-max))))
185 (if visit
186 (set-visited-file-modtime))))
187 (if (and local-copy
188 (file-exists-p local-copy))
189 (delete-file local-copy)))
190 (list file length)))
191 (put 'insert-file-contents 'epa-file 'epa-file-insert-file-contents)
192
193 (defun epa-file-write-region (start end file &optional append visit lockname
194 mustbenew)
195 (if append
196 (error "Can't append to the file"))
197 (setq file (expand-file-name file))
198 (let* ((coding-system (or coding-system-for-write
199 (if (fboundp 'select-safe-coding-system)
200 ;; This is needed since Emacs 22 has
201 ;; no-conversion setting for *.gpg in
202 ;; `auto-coding-alist'.
203 (let ((buffer-file-name
204 (file-name-sans-extension file)))
205 (select-safe-coding-system
206 (point-min) (point-max)))
207 buffer-file-coding-system)))
208 (context (epg-make-context))
209 (coding-system-for-write 'binary)
210 string entry
211 (recipients
212 (cond
213 ((listp epa-file-encrypt-to) epa-file-encrypt-to)
214 ((stringp epa-file-encrypt-to) (list epa-file-encrypt-to)))))
215 (epg-context-set-passphrase-callback
216 context
217 (cons #'epa-file-passphrase-callback-function
218 file))
219 (epg-context-set-progress-callback
220 context
221 (cons #'epa-progress-callback-function
222 (format "Encrypting %s" file)))
223 (epg-context-set-armor context epa-armor)
224 (condition-case error
225 (setq string
226 (epg-encrypt-string
227 context
228 (if (stringp start)
229 (epa-file--encode-coding-string start coding-system)
230 (unless start
231 (setq start (point-min)
232 end (point-max)))
233 (epa-file--encode-coding-string (buffer-substring start end)
234 coding-system))
235 (if (or (eq epa-file-select-keys t)
236 (and (null epa-file-select-keys)
237 (not (local-variable-p 'epa-file-encrypt-to
238 (current-buffer)))))
239 (epa-select-keys
240 context
241 "Select recipients for encryption.
242 If no one is selected, symmetric encryption will be performed. "
243 recipients)
244 (if epa-file-encrypt-to
245 (epg-list-keys context recipients)))))
246 (error
247 (if (setq entry (assoc file epa-file-passphrase-alist))
248 (setcdr entry nil))
249 (signal 'file-error (cons "Opening output file" (cdr error)))))
250 (epa-file-run-real-handler
251 #'write-region
252 (list string nil file append visit lockname mustbenew))
253 (if (boundp 'last-coding-system-used)
254 (setq last-coding-system-used coding-system))
255 (if (eq visit t)
256 (progn
257 (setq buffer-file-name file)
258 (set-visited-file-modtime))
259 (if (stringp visit)
260 (progn
261 (set-visited-file-modtime)
262 (setq buffer-file-name visit))))
263 (if (or (eq visit t)
264 (eq visit nil)
265 (stringp visit))
266 (message "Wrote %s" buffer-file-name))))
267 (put 'write-region 'epa-file 'epa-file-write-region)
268
269 (defun epa-file-select-keys ()
270 "Select recipients for encryption."
271 (interactive)
272 (setq-local epa-file-encrypt-to
273 (mapcar
274 (lambda (key)
275 (epg-sub-key-id (car (epg-key-sub-key-list key))))
276 (epa-select-keys
277 (epg-make-context)
278 "Select recipients for encryption.
279 If no one is selected, symmetric encryption will be performed. "))))
280
281 ;;;###autoload
282 (defun epa-file-enable ()
283 (interactive)
284 (if (memq epa-file-handler file-name-handler-alist)
285 (message "`epa-file' already enabled")
286 (setq file-name-handler-alist
287 (cons epa-file-handler file-name-handler-alist))
288 (add-hook 'find-file-hook 'epa-file-find-file-hook)
289 (setq auto-mode-alist (cons epa-file-auto-mode-alist-entry auto-mode-alist))
290 (message "`epa-file' enabled")))
291
292 ;;;###autoload
293 (defun epa-file-disable ()
294 (interactive)
295 (if (memq epa-file-handler file-name-handler-alist)
296 (progn
297 (setq file-name-handler-alist
298 (delq epa-file-handler file-name-handler-alist))
299 (remove-hook 'find-file-hook 'epa-file-find-file-hook)
300 (setq auto-mode-alist (delq epa-file-auto-mode-alist-entry
301 auto-mode-alist))
302 (message "`epa-file' disabled"))
303 (message "`epa-file' already disabled")))
304
305 (provide 'epa-file)
306
307 ;;; epa-file.el ends here