File: plstore.el.html
Plist based data store providing search and partial encryption.
By default, this library uses symmetric encryption, which means
that you have to enter the password protecting your store more
often than you probably expect to. To use public key encryption
with this library, create a GnuPG key and customize user option
plstore-encrypt-to to use it. You can then configure the GnuPG
agent to adjust caching and expiration of the passphrase for your
store.
You can read more on these topics in the EasyPG Assistant user's manual (Info node (epa)), of which much information also applies to this library. Most notably:
- Info node (epa)GnuPG version compatibility
- Info node (epa)GnuPG Pinentry
- Info node (epa)Caching Passphrases
Use only keyword symbols (starting with a colon) as property names
in any plist stored with this library. While this library does not
actively enforce the use of keyword symbols, it silently assumes
that the first character of all property names can be discarded
without sacrificing uniqueness of names (FIXME). Likewise, this
library does not enforce that the plists provided as input are
actually valid and can behave in undefined ways if they are not
(FIXME).
Creating:
;; Open a new store associated with ~/.emacs.d/auth.plist.
(setq store (plstore-open (expand-file-name "~/.emacs.d/auth.plist")))
;; Both :host and :port are public property.
(plstore-put store "foo" '(:host "foo.example.org" :port 80) nil)
;; No encryption will be needed.
(plstore-save store)
;; :user is marked as secret.
(plstore-put store "bar" '(:host "bar.example.org") '(:user "test"))
;; :password is marked as secret.
(plstore-put store "baz" '(:host "baz.example.org") '(:password "test"))
;; Those secret properties are encrypted together.
(plstore-save store)
;; Kill the buffer visiting ~/.emacs.d/auth.plist.
(plstore-close store)
Avoid marking one property both as public *and* secret, as the behavior of this library with respect to such duplicate properties is not defined (FIXME).
Searching:
(setq store (plstore-open (expand-file-name "~/.emacs.d/auth.plist")))
;; As the entry "foo" associated with "foo.example.org" has no
;; secret properties, no need for decryption.
(plstore-find store '(:host ("foo.example.org")))
;; As the entry "bar" associated with "bar.example.org" has a
;; secret property :user, Emacs tries to decrypt the secret (and
;; thus you will need to input passphrase).
(plstore-find store '(:host ("bar.example.org")))
;; While the entry "baz" associated with "baz.example.org" has also
;; a secret property :password, it is encrypted together with
;; :user of "bar", so no need to decrypt the secret.
(plstore-find store '(:host ("baz.example.org")))
(plstore-close store)
Editing:
This file also provides plstore-mode, a major mode for editing
the plstore format file. Visit a non-existing file and put the
following line:
(("foo" :host "foo.example.org" :secret-user "user"))
where the prefixing :secret- means the property (without
:secret- prefix) is marked as secret. Thus, when you save the
buffer, the :secret-user property is encrypted as :user. Do
not use a property consisting solely of the prefix, as the behavior
of this library with respect to such properties is not defined
(FIXME).
You can toggle the view between encrypted form and the decrypted form with C-c C-c.
If you have opened a plstore with plstore-open you should not
edit its underlying buffer in plstore-mode or in any other way at
the same time, since your manual changes will be overwritten when
plstore-save is called on that plstore.
Internals:
This is information on the internal data structure and functions of this library. None of it should be necessary to actually use it. For easier reading, we usually do not distinguish in this internal documentation between a Lisp object and its printed representation.
A plstore corresponds to an alist mapping strings to property lists. Internally, that alist is organized as two alists, one mapping to the non-secret properties and placeholders for the secret properties (called "template alist" with identifier ALIST) and one mapping to the secret properties ("secret alist", SECRET-ALIST). The secret alist is read from and written to file as pgp-encrypted printed representation of the alist ("encrypted data", ENCRYPTED-DATA).
During the lifetime of a plstore, a third type of alist may pop up, which maps to the merged non-secret properties and plain-text secret properties ("merged alist", MERGED-ALIST).
After executing the "foo", "bar", "baz" example from above the alists described above look like the following:
Template Alist:
(("foo" :host "foo.example.org" :port 80)
("bar" :secret-user t :host "bar.example.org")
("baz" :secret-password t :host "baz.example.org"))
Secret Alist:
(("bar" :user "test")
("baz" :password "test"))
Merged Alist:
(("foo" :host "foo.example.org" :port 80)
("bar" :user "test" :host "bar.example.org")
("baz" :password "test" :host "baz.example.org"))
Finally, a plstore requires a buffer ("plstore buffer", BUFFER) for
conversion between its Lisp objects and its file representation.
It is important to note that this buffer is *not* continuously
synchronized as the plstore changes. During the lifetime of a
plstore, its buffer is read from in function plstore-open and
(destructively) written to in plstore-save, but not touched
otherwise. We call the file visited by the plstore buffer the
associated file of the plstore.
With the identifiers defined above a plstore is a vector with the following elements and accessor functions:
[
BUFFER ; plstore--get/set-buffer
ALIST ; plstore--get/set-alist
ENCRYPTED-DATA ; plstore--get/set-encrypted-data
SECRET-ALIST ; plstore--get/set-secret-alist
MERGED-ALIST ; plstore--get/set-merged-alist
]
When a plstore is created through plstore-open, its ALIST and
ENCRYPTED-DATA are initialized from the contents of BUFFER without
any decryption taking place, and MERGED-ALIST is initialized as a
copy of ALIST. (Which means that at that stage the merged alist
still contains the secret property placeholders!)
During on-demand decryption of a plstore through function
plstore--decrypt, SECRET-ALIST is populated from ENCRYPTED-DATA,
which is in turn replaced by value nil. (Which further serves as
an indicator that the plstore has been decrypted already.) In
addition, MERGED-ALIST is recomputed by function
plstore--merge-secret to replace the secret property placeholders
by their plain-text secret property equivalents.
The file representation of a plstore consists of two Lisp forms plus markers to introduce them:
;;; public entries
ALIST
;;; secret entries
ENCRYPTED-DATA
Both of these are optional, but the first section must be present if the second one is. If both sections are missing, the plstore is empty. If the second section is missing, it contains only non-secret data. If present, the printed representation of the encrypted data includes the delimiting double quotes.
The plstore API (plstore-open, plstore-put, etc.) and the
plstore mode implemented by plstore-mode are orthogonal to each
other and should not be mixed up. In particular, encoding and
decoding a plstore mode buffer with plstore-mode-toggle-display
is not related in any way to the state of the plstore buffer.
Defined variables (7)
plstore-encoded | Non-nil if the current buffer shows the decoded alist. |
plstore-encrypt-to | Recipient(s) used for encrypting secret entries. |
plstore-mode-abbrev-table | Abbrev table for ‘plstore-mode’. |
plstore-mode-hook | Hook run after entering PLSTORE mode. |
plstore-mode-map | Keymap for ‘plstore-mode’. |
plstore-mode-syntax-table | Syntax table for ‘plstore-mode’. |
plstore-select-keys | Control whether or not to pop up the key selection dialog. |