Merge from mainline.
[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 string length entry)
136 (if visit
137 (setq buffer-file-name file))
138 (epg-context-set-passphrase-callback
139 context
140 (cons #'epa-file-passphrase-callback-function
141 local-file))
142 (epg-context-set-progress-callback
143 context
144 (cons #'epa-progress-callback-function
145 (format "Decrypting %s" file)))
146 (unwind-protect
147 (progn
148 (if replace
149 (goto-char (point-min)))
150 (condition-case error
151 (setq string (epg-decrypt-file context local-file nil))
152 (error
153 (if (setq entry (assoc file epa-file-passphrase-alist))
154 (setcdr entry nil))
155 ;; Hack to prevent find-file from opening empty buffer
156 ;; when decryption failed (bug#6568). See the place
157 ;; where `find-file-not-found-functions' are called in
158 ;; `find-file-noselect-1'.
159 (when (file-exists-p local-file)
160 (make-local-variable 'epa-file-error)
161 (setq 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 (make-local-variable 'epa-file-encrypt-to)
168 (setq epa-file-encrypt-to
169 (mapcar #'car (epg-context-result-for context 'encrypted-to)))
170 (if (or beg end)
171 (setq string (substring string (or beg 0) end)))
172 (save-excursion
173 ;; If visiting, bind off buffer-file-name so that
174 ;; file-locking will not ask whether we should
175 ;; really edit the buffer.
176 (let ((buffer-file-name
177 (if visit nil buffer-file-name)))
178 (save-restriction
179 (narrow-to-region (point) (point))
180 (epa-file-decode-and-insert string file visit beg end replace)
181 (setq length (- (point-max) (point-min))))
182 (if replace
183 (delete-region (point) (point-max))))
184 (if visit
185 (set-visited-file-modtime))))
186 (if (and local-copy
187 (file-exists-p local-copy))
188 (delete-file local-copy)))
189 (list file length)))
190 (put 'insert-file-contents 'epa-file 'epa-file-insert-file-contents)
191
192 (defun epa-file-write-region (start end file &optional append visit lockname
193 mustbenew)
194 (if append
195 (error "Can't append to the file"))
196 (setq file (expand-file-name file))
197 (let* ((coding-system (or coding-system-for-write
198 (if (fboundp 'select-safe-coding-system)
199 ;; This is needed since Emacs 22 has
200 ;; no-conversion setting for *.gpg in
201 ;; `auto-coding-alist'.
202 (let ((buffer-file-name
203 (file-name-sans-extension file)))
204 (select-safe-coding-system
205 (point-min) (point-max)))
206 buffer-file-coding-system)))
207 (context (epg-make-context))
208 (coding-system-for-write 'binary)
209 string entry
210 (recipients
211 (cond
212 ((listp epa-file-encrypt-to) epa-file-encrypt-to)
213 ((stringp epa-file-encrypt-to) (list epa-file-encrypt-to)))))
214 (epg-context-set-passphrase-callback
215 context
216 (cons #'epa-file-passphrase-callback-function
217 file))
218 (epg-context-set-progress-callback
219 context
220 (cons #'epa-progress-callback-function
221 (format "Encrypting %s" file)))
222 (epg-context-set-armor context epa-armor)
223 (condition-case error
224 (setq string
225 (epg-encrypt-string
226 context
227 (if (stringp start)
228 (epa-file--encode-coding-string start coding-system)
229 (unless start
230 (setq start (point-min)
231 end (point-max)))
232 (epa-file--encode-coding-string (buffer-substring start end)
233 coding-system))
234 (if (or (eq epa-file-select-keys t)
235 (and (null epa-file-select-keys)
236 (not (local-variable-p 'epa-file-encrypt-to
237 (current-buffer)))))
238 (epa-select-keys
239 context
240 "Select recipients for encryption.
241 If no one is selected, symmetric encryption will be performed. "
242 recipients)
243 (if epa-file-encrypt-to
244 (epg-list-keys context recipients)))))
245 (error
246 (if (setq entry (assoc file epa-file-passphrase-alist))
247 (setcdr entry nil))
248 (signal 'file-error (cons "Opening output file" (cdr error)))))
249 (epa-file-run-real-handler
250 #'write-region
251 (list string nil file append visit lockname mustbenew))
252 (if (boundp 'last-coding-system-used)
253 (setq last-coding-system-used coding-system))
254 (if (eq visit t)
255 (progn
256 (setq buffer-file-name file)
257 (set-visited-file-modtime))
258 (if (stringp visit)
259 (progn
260 (set-visited-file-modtime)
261 (setq buffer-file-name visit))))
262 (if (or (eq visit t)
263 (eq visit nil)
264 (stringp visit))
265 (message "Wrote %s" buffer-file-name))))
266 (put 'write-region 'epa-file 'epa-file-write-region)
267
268 (defun epa-file-select-keys ()
269 "Select recipients for encryption."
270 (interactive)
271 (make-local-variable 'epa-file-encrypt-to)
272 (setq 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