2006-11-20 Andreas Seltenreich <uwi7@rz.uni-karlsruhe.de>
[elisp/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 (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 (defcustom epa-file-cache-passphrase-for-symmetric-encryption nil
38   "If t, cache passphrase for symmetric encryption."
39   :type 'boolean
40   :group 'epa-file)
41
42 (defvar epa-file-encrypt-to nil
43   "*Recipient(s) used for encrypting files.
44 May either be a string or a list of strings.")
45
46 ;;;###autoload
47 (put 'epa-file-encrypt-to 'safe-local-variable
48      (lambda (val)
49        (or (stringp val)
50            (and (listp val)
51                 (catch 'safe
52                   (mapc (lambda (elt)
53                           (unless (stringp elt)
54                             (throw 'safe nil)))
55                         val)
56                   t)))))
57
58 (defvar epa-file-handler
59   (cons epa-file-name-regexp 'epa-file-handler))
60
61 (defvar epa-file-passphrase-alist nil)
62
63 (defun epa-file-passphrase-callback-function (context key-id file)
64   (if (and epa-file-cache-passphrase-for-symmetric-encryption
65            (eq key-id 'SYM))
66       (let ((entry (assoc file epa-file-passphrase-alist))
67             passphrase)
68         (or (copy-sequence (cdr entry))
69             (progn
70               (unless entry
71                 (setq entry (list file)
72                       epa-file-passphrase-alist (cons entry
73                                                  epa-file-passphrase-alist)))
74               (setq passphrase (epa-passphrase-callback-function context
75                                                                  key-id nil))
76               (setcdr entry (copy-sequence passphrase))
77               passphrase)))
78     (epa-passphrase-callback-function context key-id nil)))
79
80 (defun epa-file-handler (operation &rest args)
81   (save-match-data
82     (let ((op (get operation 'epa-file)))
83       (if op
84           (apply op args)
85         (epa-file-run-real-handler operation args)))))
86
87 (defun epa-file-run-real-handler (operation args)
88   (let ((inhibit-file-name-handlers
89          (cons 'epa-file-handler
90                (and (eq inhibit-file-name-operation operation)
91                     inhibit-file-name-handlers)))
92         (inhibit-file-name-operation operation))
93     (apply operation args)))
94
95 (defun epa-file-decode-and-insert (string file visit beg end replace)
96   (if (fboundp 'decode-coding-inserted-region)
97       (save-restriction
98         (narrow-to-region (point) (point))
99         (let ((multibyte enable-multibyte-characters))
100           (set-buffer-multibyte nil)
101           (insert string)
102           (set-buffer-multibyte multibyte)
103           (decode-coding-inserted-region
104            (point-min) (point-max)
105            (substring file 0 (string-match epa-file-name-regexp file))
106            visit beg end replace)))
107     (insert (decode-coding-string string (or coding-system-for-read
108                                              'undecided)))))
109
110 (defvar last-coding-system-used)
111 (defun epa-file-insert-file-contents (file &optional visit beg end replace)
112   (barf-if-buffer-read-only)
113   (if (and visit (or beg end))
114       (error "Attempt to visit less than an entire file"))
115   (setq file (expand-file-name file))
116   (let ((local-copy (epa-file-run-real-handler #'file-local-copy (list file)))
117         (context (epg-make-context))
118         string length entry)
119     (if visit
120         (setq buffer-file-name file))
121     (epg-context-set-passphrase-callback
122      context
123      (cons #'epa-file-passphrase-callback-function
124            file))
125     (epg-context-set-progress-callback context
126                                        #'epa-progress-callback-function)
127     (unwind-protect
128         (progn
129           (if replace
130               (goto-char (point-min)))
131           (condition-case error
132               (setq string (epg-decrypt-file context file nil))
133             (error
134              (if (setq entry (assoc file epa-file-passphrase-alist))
135                  (setcdr entry nil))
136              (signal 'file-error
137                      (cons "Opening input file" (cdr error)))))
138           (if (or beg end)
139               (setq string (substring string (or beg 0) end)))
140           (save-excursion
141             (save-restriction
142               (narrow-to-region (point) (point))
143               (epa-file-decode-and-insert string file visit beg end replace)
144               (setq length (- (point-max) (point-min))))
145             (if replace
146                 (delete-region (point) (point-max)))))
147       (if (and local-copy
148                (file-exists-p local-copy))
149           (delete-file local-copy)))
150     (list file length)))
151 (put 'insert-file-contents 'epa-file 'epa-file-insert-file-contents)
152
153 (defun epa-file-write-region (start end file &optional append visit lockname
154                                     mustbenew)
155   (if append
156       (error "Can't append to the file."))
157   (setq file (expand-file-name file))
158   (let* ((coding-system (or coding-system-for-write
159                             (if (boundp 'last-coding-system-used)
160                                 (condition-case nil
161                                     (write-region (point-min) (point-max) "/")
162                                   (error last-coding-system-used))
163                               buffer-file-coding-system)))
164          (context (epg-make-context))
165          (coding-system-for-write 'binary)
166          string entry)
167     (epg-context-set-passphrase-callback
168      context
169      (cons #'epa-file-passphrase-callback-function
170            file))
171     (epg-context-set-progress-callback context
172                                        #'epa-progress-callback-function)
173     (condition-case error
174         (setq string
175               (epg-encrypt-string
176                context
177                (if (stringp start)
178                    (encode-coding-string start coding-system)
179                  (encode-coding-string (buffer-substring start end)
180                                        coding-system))
181                (unless (assoc file epa-file-passphrase-alist)
182                  (epa-select-keys
183                   context
184                   "Select recipents for encryption.
185 If no one is selected, symmetric encryption will be performed.  "
186                   (cond
187                    ((listp epa-file-encrypt-to) epa-file-encrypt-to)
188                    ((stringp epa-file-encrypt-to) (list epa-file-encrypt-to)))))))
189       (error
190        (if (setq entry (assoc file epa-file-passphrase-alist))
191            (setcdr entry nil))
192        (signal 'file-error (cons "Opening output file" (cdr error)))))
193     (epa-file-run-real-handler
194      #'write-region
195      (list string nil file append visit lockname mustbenew))
196     (if (boundp 'last-coding-system-used)
197         (setq last-coding-system-used coding-system))
198     (if (eq visit t)
199         (progn
200           (setq buffer-file-name file)
201           (set-visited-file-modtime))
202       (if (stringp visit)
203           (progn
204             (set-visited-file-modtime)
205             (setq buffer-file-name visit))))
206     (if (or (eq visit t)
207             (eq visit nil)
208             (stringp visit))
209         (message "Wrote %s" buffer-file-name))))
210 (put 'write-region 'epa-file 'epa-file-write-region)
211
212 ;;;###autoload
213 (defun epa-file-enable ()
214   (interactive)
215   (if (memq epa-file-handler file-name-handler-alist)
216       (message "`epa-file' already enabled")
217     (setq file-name-handler-alist
218           (cons epa-file-handler file-name-handler-alist))
219     (message "`epa-file' enabled")))
220
221 ;;;###autoload
222 (defun epa-file-disable ()
223   (interactive)
224   (if (memq epa-file-handler file-name-handler-alist)
225       (progn
226         (setq file-name-handler-alist
227               (delq epa-file-handler file-name-handler-alist))
228         (message "`epa-file' disabled"))
229     (message "`epa-file' already disabled")))
230
231 (provide 'epa-file)
232
233 ;;; epa-file.el ends here