Added README and epg-file.el.
authorueno <ueno>
Wed, 12 Apr 2006 02:45:25 +0000 (02:45 +0000)
committerueno <ueno>
Wed, 12 Apr 2006 02:45:25 +0000 (02:45 +0000)
README [new file with mode: 0644]
epg-file.el [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..187db0d
--- /dev/null
+++ b/README
@@ -0,0 +1,30 @@
+* What's this?
+
+EasyPG is yet another GnuPG interface for Emacs.  It consists of a
+transparent file encryption application and easy-to-use elisp library
+to interact with GnuPG.
+
+* Quick start
+
+(0) Put (require 'epg-file) in your ~/.emacs.el
+
+(1) Restart emacs
+
+(2) C-x C-f ~/test.gpg
+
+* Advantages over other competitors
+
+** EasyPG avoides potential security flaws of Emacs.
+
+*** `call-process-region' writes data in region to a temporary file.
+PGG and gpg.el use `call-process-region' to communicate with a
+subprocess "gpg".  So, your passphrases may leak to the filesystem.
+
+*** There is no way to clear strings safely.
+To prevent passphrases from been stealing from cores, `read-passwd'
+function clears passphrase strings by `(fillarray string 0)'.
+However, it is not enough.  In GC sweep phase, Emacs does compaction
+on small strings.  If GC happens before `fillarray', passphrase
+strings may be copied elsewhere in the memory.
+
+PGG and gpg.el enables passphrase caching by default.
diff --git a/epg-file.el b/epg-file.el
new file mode 100644 (file)
index 0000000..19cda1c
--- /dev/null
@@ -0,0 +1,225 @@
+(require 'epg)
+
+(defcustom epg-file-name-regexp "\\.gpg\\'"
+  "Regexp that matches filenames that are assumed to be encrypted
+with GnuPG."
+  :type 'regexp
+  :group 'epg-file)
+
+(defun epg-file-handler (operation &rest args)
+  (let ((epg-file-operation (get operation 'epg-file)))
+    (if epg-file-operation
+        (apply epg-file-operation args)
+      (epg-file-invoke-default-handler operation args))))
+
+(defun epg-file-invoke-default-handler (operation args)
+  (let ((inhibit-file-name-handlers
+         (cons 'epg-file-handler
+               (and (eq inhibit-file-name-operation operation)
+                    inhibit-file-name-handlers)))
+        (inhibit-file-name-operation operation))
+    (apply operation args)))
+
+(defun epg-file-write-region (start end filename &optional append visit
+                                   lockname mustbenew)
+  (let* ((visit-file (if (stringp visit)
+                         (expand-file-name visit)
+                       (expand-file-name filename)))
+         ;; XXX: Obtain the value returned by choose_write_coding_system
+         (coding-system (condition-case nil
+                            (epg-file-invoke-default-handler
+                            #'write-region
+                            (list start end "/"))
+                          (file-error last-coding-system-used)))
+         ;; start and end are normally buffer positions
+         ;; specifying the part of the buffer to write.
+         ;; If start is nil, that means to use the entire buffer contents.
+         ;; If start is a string, then output that string to the file
+         ;; instead of any buffer contents; end is ignored.
+         (string (encode-coding-string (cond
+                                        ((stringp start)
+                                         start)
+                                        ((null start)
+                                         (buffer-string))
+                                        (t
+                                         (buffer-substring start end)))
+                                       coding-system)))
+    (with-temp-buffer
+      (set-buffer-multibyte nil)
+      ;; Optional fourth argument append if non-nil means
+      ;;   append to existing file contents (if any).  If it is an integer,
+      ;;   seek to that offset in the file before writing.
+      (if (and append (file-exists-p filename))
+          ;; Enable passphrase cache on this temp buffer
+          (let ((coding-system-for-read 'binary))
+            ;; set visit to t so that passphrase is cached
+            (insert-file-contents filename t)
+            (setq buffer-file-name nil)))
+      ;; Insert data to encrypt
+      (goto-char (if (integerp append) (1+ append) (point-max)))
+      (delete-region (point) (min (+ (point) (length string)) (point-max)))
+      (insert string)
+
+      (let ((coding-system-for-write 'binary)
+            (coding-system-for-read 'binary)
+           (context (epg-make-context))
+           cipher)
+       (when (setq cipher (epg-encrypt-string context (buffer-string) nil))
+         (if (memq system-type '(ms-dos windows-nt))
+             (setq buffer-file-type t))
+         (epg-file-invoke-default-handler
+          #'write-region
+          (list cipher nil filename nil 'not-visit lockname mustbenew)))))
+    ;; Optional fifth argument visit, if t or a string, means
+    ;;   set the last-save-file-modtime of buffer to this file's modtime
+    ;;   and mark buffer not modified.
+    ;; If visit is a string, it is a second file name;
+    ;;   the output goes to filename, but the buffer is marked as visiting visit.
+    ;;   visit is also the file name to lock and unlock for clash detection.
+    ;; If visit is neither t nor nil nor a string,
+    ;;   that means do not display the "Wrote file" message.
+    (when (or (eq visit t) (stringp visit))
+      (setq buffer-file-name filename)
+      (set-visited-file-modtime))
+    (when (stringp visit)
+      (setq buffer-file-name visit))
+    (when (or (eq visit t) (eq visit nil) (stringp visit))
+      (message "Wrote %s" visit-file))
+    (setq last-coding-system-used coding-system)
+    nil))
+
+(defun epg-file-insert-file-contents (filename &optional visit beg end replace)
+  (barf-if-buffer-read-only)
+
+  ;; If visit is non-nil, beg and end must be nil.
+  (if (and visit (or beg end))
+      (error "Attempt to visit less than an entire file"))
+
+  (let ((expanded-filename (expand-file-name filename))
+       (length 0))
+    (if (file-exists-p expanded-filename)
+       (let* ((local-copy (epg-file-invoke-default-handler
+                           'file-local-copy
+                           (list expanded-filename)))
+              (local-file (or local-copy expanded-filename))
+              (coding-system-for-read 'binary)
+              (context (epg-make-context))
+              string)
+         (unwind-protect
+             (progn
+               (setq string (epg-decrypt-file context local-file)
+                     length (length string))
+               (if replace
+                   (goto-char (point-min)))
+               (save-excursion
+                 (let ((buffer-file-name (if visit nil buffer-file-name)))
+                   (save-restriction
+                     (narrow-to-region (point) (point))
+                     (insert string)
+                     (epg-file-decode-coding-inserted-region
+                      (point-min) (point-max)
+                      expanded-filename
+                      visit beg end replace))
+                   (if replace
+                       (delete-region (point) (point-max))))))
+           (when (and local-copy (file-exists-p local-copy))
+             (delete-file local-copy)))))
+    ;; If second argument visit is non-nil, the buffer's visited filename
+    ;; and last save file modtime are set, and it is marked unmodified.
+    (when visit
+      (unlock-buffer)
+      (setq buffer-file-name expanded-filename)
+      (set-visited-file-modtime))
+
+    ;; If visiting and the file does not exist, visiting is completed
+    ;; before the error is signaled.
+    (if (and visit (not (file-exists-p expanded-filename)))
+       (signal 'file-error (list "Opening input file" filename)))
+
+    ;; Returns list of absolute file name and number of characters inserted.
+    (list expanded-filename length)))
+
+(if (fboundp 'decode-coding-inserted-region)
+    (defalias 'epg-file-decode-coding-inserted-region 'decode-coding-inserted-region)
+  (defun epg-file-decode-coding-inserted-region (from to filename
+                                                 &optional visit beg end replace)
+    "Decode the region between FROM and TO as if it is read from file FILENAME.
+The idea is that the text between FROM and TO was just inserted somehow.
+Optional arguments VISIT, BEG, END, and REPLACE are the same as those
+of the function `insert-file-contents'.
+Part of the job of this function is setting `buffer-undo-list' appropriately."
+  (save-excursion
+    (save-restriction
+      (let ((coding coding-system-for-read)
+            undo-list-saved)
+        (if visit
+            ;; Temporarily turn off undo recording, if we're decoding the
+            ;; text of a visited file.
+            (setq buffer-undo-list t)
+          ;; Otherwise, if we can recognize the undo elt for the insertion,
+          ;; remove it and get ready to replace it later.
+          ;; In the mean time, turn off undo recording.
+          (let ((last (car-safe buffer-undo-list)))
+            (if (and (consp last) (eql (car last) from) (eql (cdr last) to))
+                (setq undo-list-saved (cdr buffer-undo-list)
+                      buffer-undo-list t))))
+        (narrow-to-region from to)
+        (goto-char (point-min))
+        (or coding
+            (setq coding (funcall set-auto-coding-function
+                                  filename (- (point-max) (point-min)))))
+        (or coding
+            (setq coding (car (find-operation-coding-system
+                               'insert-file-contents
+                               filename visit beg end replace))))
+        (if (coding-system-p coding)
+            (or enable-multibyte-characters
+                (setq coding
+                      (coding-system-change-text-conversion coding 'raw-text)))
+          (setq coding nil))
+        (if coding
+            (decode-coding-region (point-min) (point-max) coding)
+          (setq last-coding-system-used coding))
+        ;; If we're decoding the text of a visited file,
+        ;; the undo list should start out empty.
+        (if visit
+            (setq buffer-undo-list nil)
+          ;; If we decided to replace the undo entry for the insertion,
+          ;; do so now.
+          (if undo-list-saved
+              (setq buffer-undo-list
+                    (cons (cons from (point-max)) undo-list-saved)))))))))
+      
+(put 'epg-file-handler 'safe-magic t)
+(let (epg-file-operations)
+  (mapc
+   (lambda (operation)
+     (let ((epg-file-operation (intern (format "epg-file-%s" operation))))
+       (when (fboundp epg-file-operation)
+         (push operation epg-file-operations)
+         (put operation 'epg-file epg-file-operation))))
+   '(access-file add-name-to-file byte-compiler-base-file-name copy-file
+     delete-directory delete-file  diff-latest-backup-file directory-file-name
+     directory-files directory-files-and-attributes dired-call-process
+     dired-compress-file dired-uncache expand-file-name
+     file-accessible-directory-p file-attributes file-directory-p
+     file-executable-p file-exists-p file-local-copy file-remote-p file-modes
+     file-name-all-completions file-name-as-directory file-name-completion
+     file-name-directory file-name-nondirectory file-name-sans-versions
+     file-newer-than-file-p file-ownership-preserved-p file-readable-p
+     file-regular-p file-symlink-p file-truename file-writable-p
+     find-backup-file-name find-file-noselect get-file-buffer
+     insert-directory insert-file-contents load make-directory
+     make-directory-internal make-symbolic-link rename-file set-file-modes
+     set-visited-file-modtime shell-command substitute-in-file-name
+     unhandled-file-name-directory vc-registered verify-visited-file-modtime
+     write-region))
+  (put 'epg-file-handler 'operations epg-file-operations))
+
+(unless (assoc epg-file-name-regexp file-name-handler-alist)
+  (push (cons epg-file-name-regexp 'epg-file-handler)
+        file-name-handler-alist))
+
+(unless (assoc epg-file-name-regexp auto-mode-alist)
+  (push (list epg-file-name-regexp nil 'strip-suffix)
+        auto-mode-alist))