Signal 'file-error if cryptographic operations fail.
[elisp/epg.git] / epa-file.el
1 ;;; epa-file.el --- the EasyPG Assistant hooks for 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 (defvar epa-file-passphrase-alist nil)
38
39 (defun epa-file-passphrase-callback-function (key-id file)
40   (if (eq key-id 'SYM)
41       (let ((entry (assoc file epa-file-passphrase-alist))
42             passphrase)
43         (or (copy-sequence (cdr entry))
44             (progn
45               (unless entry
46                 (setq entry (list file)
47                       epa-file-passphrase-alist (cons entry
48                                                  epa-file-passphrase-alist)))
49               (setq passphrase (epg-passphrase-callback-function key-id nil))
50               (setcdr entry (copy-sequence passphrase))
51               passphrase)))
52     (epg-passphrase-callback-function key-id nil)))
53
54 (defun epa-file-handler (operation &rest args)
55   (save-match-data
56     (let ((op (get operation 'epa-file)))
57       (if op
58           (apply op args)
59         (epa-file-run-real-handler operation args)))))
60
61 (defun epa-file-run-real-handler (operation args)
62   (let ((inhibit-file-name-handlers
63          (cons 'epa-file-handler
64                (and (eq inhibit-file-name-operation operation)
65                     inhibit-file-name-handlers)))
66         (inhibit-file-name-operation operation))
67     (apply operation args)))
68
69 (defvar last-coding-system-used)
70 (defun epa-file-insert-file-contents (file &optional visit beg end replace)
71   (barf-if-buffer-read-only)
72   (if (or beg end)
73       (error "Can't read the file partially."))
74   (setq file (expand-file-name file))
75   (let ((local-copy (epa-file-run-real-handler #'file-local-copy (list file)))
76         (context (epg-make-context))
77         string length entry)
78     (if visit
79         (setq buffer-file-name file))
80     (epg-context-set-passphrase-callback
81      context
82      (cons #'epa-file-passphrase-callback-function
83            file))
84     (unwind-protect
85         (progn
86           (if replace
87               (goto-char (point-min)))
88           (condition-case error
89               (setq string (decode-coding-string
90                             (epg-decrypt-file context file nil)
91                             'undecided))
92             (error
93              (if (setq entry (assoc file epa-file-passphrase-alist))
94                  (setcdr entry nil))
95              (signal 'file-error
96                      (cons "Opening input file" (nthcdr 2 error)))))
97           (if (boundp 'last-coding-system-used)
98               (set-buffer-file-coding-system last-coding-system-used)
99             (set-buffer-file-coding-system default-buffer-file-coding-system))
100           (insert string)
101           (setq length (length string))
102           (if replace
103               (delete-region (point) (point-max))))
104       (if (and local-copy
105                (file-exists-p local-copy))
106           (delete-file local-copy)))
107     (list file length)))
108 (put 'insert-file-contents 'epa-file 'epa-file-insert-file-contents)
109
110 (defun epa-file-write-region (start end file &optional append visit lockname
111                                     mustbenew)
112   (if append
113       (error "Can't append to the file."))
114   (setq file (expand-file-name file))
115   (let* ((coding-system (if (boundp 'last-coding-system-used)
116                             (condition-case nil
117                                 (write-region (point-min) (point-max) "/")
118                               (error last-coding-system-used))
119                           buffer-file-coding-system))
120          (context (epg-make-context))
121          (coding-system-for-write 'binary)
122          string entry)
123     (epg-context-set-passphrase-callback
124      context
125      (cons #'epa-file-passphrase-callback-function
126            file))
127     (condition-case error
128         (setq string
129               (epg-encrypt-string
130                context
131                (encode-coding-string (buffer-string) coding-system)
132                (mapcar (lambda (key)
133                          (epg-sub-key-id (car (epg-key-sub-key-list key))))
134                        (unless (assoc file epa-file-passphrase-alist)
135                          (epa-select-keys
136                     "Select recipents for encryption.
137 If no one is selected, symmetric encryption will be performed.  ")))))
138       (error
139        (if (setq entry (assoc file epa-file-passphrase-alist))
140            (setcdr entry nil))
141        (signal 'file-error (cons "Opening output file" (nthcdr 2 error)))))
142     (epa-file-run-real-handler
143      #'write-region
144      (list string nil file append visit lockname mustbenew))
145     (if (boundp 'last-coding-system-used)
146         (setq last-coding-system-used coding-system))
147     (if (eq visit t)
148         (progn
149           (setq buffer-file-name file)
150           (set-visited-file-modtime))
151       (if (stringp visit)
152           (progn
153             (set-visited-file-modtime)
154             (setq buffer-file-name visit))))
155     (if (or (eq visit t)
156             (eq visit nil)
157             (stringp visit))
158         (message "Wrote %s" buffer-file-name))))
159 (put 'write-region 'epa-file 'epa-file-write-region)
160
161 (provide 'epa-file)
162
163 ;;; epa-file.el ends here