diff options
| author | Daiki Ueno | 2011-06-30 16:27:25 +0900 |
|---|---|---|
| committer | Daiki Ueno | 2011-06-30 16:27:25 +0900 |
| commit | 8977de272444fe109c0266591e2107c5563802bf (patch) | |
| tree | 82b56839fa124e12d97abe77e59d06797b279e99 | |
| parent | d0b36cbeb198377ef831a75b75645e76b471e7c1 (diff) | |
| download | emacs-8977de272444fe109c0266591e2107c5563802bf.tar.gz emacs-8977de272444fe109c0266591e2107c5563802bf.zip | |
Add new auth-source backend 'plstore.
* auth-source.el (auth-source-backend): New member "arg".
(auth-source-backend-parse): Handle new backend 'plstore.
* plstore.el: New file.
| -rw-r--r-- | lisp/gnus/ChangeLog | 6 | ||||
| -rw-r--r-- | lisp/gnus/auth-source.el | 230 | ||||
| -rw-r--r-- | lisp/gnus/plstore.el | 319 |
3 files changed, 549 insertions, 6 deletions
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog index 5f173b2aeed..4f31130b2c5 100644 --- a/lisp/gnus/ChangeLog +++ b/lisp/gnus/ChangeLog | |||
| @@ -1,3 +1,9 @@ | |||
| 1 | 2011-06-30 Daiki Ueno <ueno@unixuser.org> | ||
| 2 | |||
| 3 | * auth-source.el (auth-source-backend): New member "arg". | ||
| 4 | (auth-source-backend-parse): Handle new backend 'plstore. | ||
| 5 | * plstore.el: New file. | ||
| 6 | |||
| 1 | 2011-06-30 Glenn Morris <rgm@gnu.org> | 7 | 2011-06-30 Glenn Morris <rgm@gnu.org> |
| 2 | 8 | ||
| 3 | * gnus-fun.el (gnus-convert-image-to-x-face-command): Doc fix. | 9 | * gnus-fun.el (gnus-convert-image-to-x-face-command): Doc fix. |
diff --git a/lisp/gnus/auth-source.el b/lisp/gnus/auth-source.el index 72f0cb7ae58..4de1f1abf8b 100644 --- a/lisp/gnus/auth-source.el +++ b/lisp/gnus/auth-source.el | |||
| @@ -56,6 +56,11 @@ | |||
| 56 | 56 | ||
| 57 | (autoload 'rfc2104-hash "rfc2104") | 57 | (autoload 'rfc2104-hash "rfc2104") |
| 58 | 58 | ||
| 59 | (autoload 'plstore-open "plstore") | ||
| 60 | (autoload 'plstore-find "plstore") | ||
| 61 | (autoload 'plstore-put "plstore") | ||
| 62 | (autoload 'plstore-save "plstore") | ||
| 63 | |||
| 59 | (defvar secrets-enabled) | 64 | (defvar secrets-enabled) |
| 60 | 65 | ||
| 61 | (defgroup auth-source nil | 66 | (defgroup auth-source nil |
| @@ -100,6 +105,9 @@ let-binding." | |||
| 100 | :type t | 105 | :type t |
| 101 | :custom string | 106 | :custom string |
| 102 | :documentation "The backend protocol.") | 107 | :documentation "The backend protocol.") |
| 108 | (arg :initarg :arg | ||
| 109 | :initform nil | ||
| 110 | :documentation "The backend arg.") | ||
| 103 | (create-function :initarg :create-function | 111 | (create-function :initarg :create-function |
| 104 | :initform ignore | 112 | :initform ignore |
| 105 | :type function | 113 | :type function |
| @@ -375,12 +383,20 @@ with \"[a/b/c] \" if CHOICES is '\(?a ?b ?c\)." | |||
| 375 | 383 | ||
| 376 | ;; a file name with parameters | 384 | ;; a file name with parameters |
| 377 | ((stringp (plist-get entry :source)) | 385 | ((stringp (plist-get entry :source)) |
| 378 | (auth-source-backend | 386 | (if (equal (file-name-extension (plist-get entry :source)) "plist") |
| 379 | (plist-get entry :source) | 387 | (auth-source-backend |
| 380 | :source (plist-get entry :source) | 388 | (plist-get entry :source) |
| 381 | :type 'netrc | 389 | :source (plist-get entry :source) |
| 382 | :search-function 'auth-source-netrc-search | 390 | :type 'plstore |
| 383 | :create-function 'auth-source-netrc-create)) | 391 | :search-function 'auth-source-plstore-search |
| 392 | :create-function 'auth-source-plstore-create | ||
| 393 | :arg (plstore-open (plist-get entry :source))) | ||
| 394 | (auth-source-backend | ||
| 395 | (plist-get entry :source) | ||
| 396 | :source (plist-get entry :source) | ||
| 397 | :type 'netrc | ||
| 398 | :search-function 'auth-source-netrc-search | ||
| 399 | :create-function 'auth-source-netrc-create))) | ||
| 384 | 400 | ||
| 385 | ;; the Secrets API. We require the package, in order to have a | 401 | ;; the Secrets API. We require the package, in order to have a |
| 386 | ;; defined value for `secrets-enabled'. | 402 | ;; defined value for `secrets-enabled'. |
| @@ -1503,6 +1519,208 @@ authentication tokens: | |||
| 1503 | ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec) | 1519 | ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec) |
| 1504 | (debug spec)) | 1520 | (debug spec)) |
| 1505 | 1521 | ||
| 1522 | ;;; Backend specific parsing: PLSTORE backend | ||
| 1523 | |||
| 1524 | (defun* auth-source-plstore-search (&rest | ||
| 1525 | spec | ||
| 1526 | &key backend create delete label | ||
| 1527 | type max host user port | ||
| 1528 | &allow-other-keys) | ||
| 1529 | "Search the PLSTORE; spec is like `auth-source'." | ||
| 1530 | |||
| 1531 | ;; TODO | ||
| 1532 | (assert (not delete) nil | ||
| 1533 | "The PLSTORE auth-source backend doesn't support deletion yet") | ||
| 1534 | |||
| 1535 | (let* ((store (oref backend arg)) | ||
| 1536 | (max (or max 5000)) ; sanity check: default to stop at 5K | ||
| 1537 | (ignored-keys '(:create :delete :max :backend :require)) | ||
| 1538 | (search-keys (loop for i below (length spec) by 2 | ||
| 1539 | unless (memq (nth i spec) ignored-keys) | ||
| 1540 | collect (nth i spec))) | ||
| 1541 | ;; build a search spec without the ignored keys | ||
| 1542 | ;; if a search key is nil or t (match anything), we skip it | ||
| 1543 | (search-spec (apply 'append (mapcar | ||
| 1544 | (lambda (k) | ||
| 1545 | (let ((v (plist-get spec k))) | ||
| 1546 | (if (or (null v) | ||
| 1547 | (eq t v)) | ||
| 1548 | nil | ||
| 1549 | (if (stringp v) | ||
| 1550 | (setq v (list v))) | ||
| 1551 | (list k v)))) | ||
| 1552 | search-keys))) | ||
| 1553 | ;; needed keys (always including host, login, port, and secret) | ||
| 1554 | (returned-keys (mm-delete-duplicates (append | ||
| 1555 | '(:host :login :port :secret) | ||
| 1556 | search-keys))) | ||
| 1557 | (items (plstore-find store search-spec)) | ||
| 1558 | (items (butlast items (- (length items) max))) | ||
| 1559 | ;; convert the item to a full plist | ||
| 1560 | (items (mapcar (lambda (item) | ||
| 1561 | (let* ((plist (copy-tree (cdr item))) | ||
| 1562 | (secret (plist-member plist :secret))) | ||
| 1563 | (if secret | ||
| 1564 | (setcar | ||
| 1565 | (cdr secret) | ||
| 1566 | (lexical-let ((v (car (cdr secret)))) | ||
| 1567 | (lambda () v)))) | ||
| 1568 | plist)) | ||
| 1569 | items)) | ||
| 1570 | ;; ensure each item has each key in `returned-keys' | ||
| 1571 | (items (mapcar (lambda (plist) | ||
| 1572 | (append | ||
| 1573 | (apply 'append | ||
| 1574 | (mapcar (lambda (req) | ||
| 1575 | (if (plist-get plist req) | ||
| 1576 | nil | ||
| 1577 | (list req nil))) | ||
| 1578 | returned-keys)) | ||
| 1579 | plist)) | ||
| 1580 | items))) | ||
| 1581 | ;; if we need to create an entry AND none were found to match | ||
| 1582 | (when (and create | ||
| 1583 | (not items)) | ||
| 1584 | |||
| 1585 | ;; create based on the spec and record the value | ||
| 1586 | (setq items (or | ||
| 1587 | ;; if the user did not want to create the entry | ||
| 1588 | ;; in the file, it will be returned | ||
| 1589 | (apply (slot-value backend 'create-function) spec) | ||
| 1590 | ;; if not, we do the search again without :create | ||
| 1591 | ;; to get the updated data. | ||
| 1592 | |||
| 1593 | ;; the result will be returned, even if the search fails | ||
| 1594 | (apply 'auth-source-plstore-search | ||
| 1595 | (plist-put spec :create nil))))) | ||
| 1596 | items)) | ||
| 1597 | |||
| 1598 | (defun* auth-source-plstore-create (&rest spec | ||
| 1599 | &key backend | ||
| 1600 | secret host user port create | ||
| 1601 | &allow-other-keys) | ||
| 1602 | (let* ((base-required '(host user port secret)) | ||
| 1603 | (base-secret '(secret)) | ||
| 1604 | ;; we know (because of an assertion in auth-source-search) that the | ||
| 1605 | ;; :create parameter is either t or a list (which includes nil) | ||
| 1606 | (create-extra (if (eq t create) nil create)) | ||
| 1607 | (current-data (car (auth-source-search :max 1 | ||
| 1608 | :host host | ||
| 1609 | :port port))) | ||
| 1610 | (required (append base-required create-extra)) | ||
| 1611 | (file (oref backend source)) | ||
| 1612 | (add "") | ||
| 1613 | ;; `valist' is an alist | ||
| 1614 | valist | ||
| 1615 | ;; `artificial' will be returned if no creation is needed | ||
| 1616 | artificial | ||
| 1617 | secret-artificial) | ||
| 1618 | |||
| 1619 | ;; only for base required elements (defined as function parameters): | ||
| 1620 | ;; fill in the valist with whatever data we may have from the search | ||
| 1621 | ;; we complete the first value if it's a list and use the value otherwise | ||
| 1622 | (dolist (br base-required) | ||
| 1623 | (when (symbol-value br) | ||
| 1624 | (let ((br-choice (cond | ||
| 1625 | ;; all-accepting choice (predicate is t) | ||
| 1626 | ((eq t (symbol-value br)) nil) | ||
| 1627 | ;; just the value otherwise | ||
| 1628 | (t (symbol-value br))))) | ||
| 1629 | (when br-choice | ||
| 1630 | (aput 'valist br br-choice))))) | ||
| 1631 | |||
| 1632 | ;; for extra required elements, see if the spec includes a value for them | ||
| 1633 | (dolist (er create-extra) | ||
| 1634 | (let ((name (concat ":" (symbol-name er))) | ||
| 1635 | (keys (loop for i below (length spec) by 2 | ||
| 1636 | collect (nth i spec)))) | ||
| 1637 | (dolist (k keys) | ||
| 1638 | (when (equal (symbol-name k) name) | ||
| 1639 | (aput 'valist er (plist-get spec k)))))) | ||
| 1640 | |||
| 1641 | ;; for each required element | ||
| 1642 | (dolist (r required) | ||
| 1643 | (let* ((data (aget valist r)) | ||
| 1644 | ;; take the first element if the data is a list | ||
| 1645 | (data (or (auth-source-netrc-element-or-first data) | ||
| 1646 | (plist-get current-data | ||
| 1647 | (intern (format ":%s" r) obarray)))) | ||
| 1648 | ;; this is the default to be offered | ||
| 1649 | (given-default (aget auth-source-creation-defaults r)) | ||
| 1650 | ;; the default supplementals are simple: | ||
| 1651 | ;; for the user, try `given-default' and then (user-login-name); | ||
| 1652 | ;; otherwise take `given-default' | ||
| 1653 | (default (cond | ||
| 1654 | ((and (not given-default) (eq r 'user)) | ||
| 1655 | (user-login-name)) | ||
| 1656 | (t given-default))) | ||
| 1657 | (printable-defaults (list | ||
| 1658 | (cons 'user | ||
| 1659 | (or | ||
| 1660 | (auth-source-netrc-element-or-first | ||
| 1661 | (aget valist 'user)) | ||
| 1662 | (plist-get artificial :user) | ||
| 1663 | "[any user]")) | ||
| 1664 | (cons 'host | ||
| 1665 | (or | ||
| 1666 | (auth-source-netrc-element-or-first | ||
| 1667 | (aget valist 'host)) | ||
| 1668 | (plist-get artificial :host) | ||
| 1669 | "[any host]")) | ||
| 1670 | (cons 'port | ||
| 1671 | (or | ||
| 1672 | (auth-source-netrc-element-or-first | ||
| 1673 | (aget valist 'port)) | ||
| 1674 | (plist-get artificial :port) | ||
| 1675 | "[any port]")))) | ||
| 1676 | (prompt (or (aget auth-source-creation-prompts r) | ||
| 1677 | (case r | ||
| 1678 | (secret "%p password for %u@%h: ") | ||
| 1679 | (user "%p user name for %h: ") | ||
| 1680 | (host "%p host name for user %u: ") | ||
| 1681 | (port "%p port for %u@%h: ")) | ||
| 1682 | (format "Enter %s (%%u@%%h:%%p): " r))) | ||
| 1683 | (prompt (auth-source-format-prompt | ||
| 1684 | prompt | ||
| 1685 | `((?u ,(aget printable-defaults 'user)) | ||
| 1686 | (?h ,(aget printable-defaults 'host)) | ||
| 1687 | (?p ,(aget printable-defaults 'port)))))) | ||
| 1688 | |||
| 1689 | ;; Store the data, prompting for the password if needed. | ||
| 1690 | (setq data | ||
| 1691 | (cond | ||
| 1692 | ((and (null data) (eq r 'secret)) | ||
| 1693 | ;; Special case prompt for passwords. | ||
| 1694 | (read-passwd prompt)) | ||
| 1695 | ((null data) | ||
| 1696 | (when default | ||
| 1697 | (setq prompt | ||
| 1698 | (if (string-match ": *\\'" prompt) | ||
| 1699 | (concat (substring prompt 0 (match-beginning 0)) | ||
| 1700 | " (default " default "): ") | ||
| 1701 | (concat prompt "(default " default ") ")))) | ||
| 1702 | (read-string prompt nil nil default)) | ||
| 1703 | (t (or data default)))) | ||
| 1704 | |||
| 1705 | (when data | ||
| 1706 | (if (member r base-secret) | ||
| 1707 | (setq secret-artificial | ||
| 1708 | (plist-put secret-artificial | ||
| 1709 | (intern (concat ":" (symbol-name r))) | ||
| 1710 | data)) | ||
| 1711 | (setq artificial (plist-put artificial | ||
| 1712 | (intern (concat ":" (symbol-name r))) | ||
| 1713 | data)))))) | ||
| 1714 | (plstore-put (oref backend arg) | ||
| 1715 | (sha1 (format "%s@%s:%s" | ||
| 1716 | (plist-get artificial :user) | ||
| 1717 | (plist-get artificial :host) | ||
| 1718 | (plist-get artificial :port))) | ||
| 1719 | artificial secret-artificial) | ||
| 1720 | (if (y-or-n-p (format "Save auth info to file %s? " | ||
| 1721 | (plstore-get-file (oref backend arg)))) | ||
| 1722 | (plstore-save (oref backend arg))))) | ||
| 1723 | |||
| 1506 | ;;; older API | 1724 | ;;; older API |
| 1507 | 1725 | ||
| 1508 | ;;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" t "tzz") | 1726 | ;;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" t "tzz") |
diff --git a/lisp/gnus/plstore.el b/lisp/gnus/plstore.el new file mode 100644 index 00000000000..3aa3b84fcbc --- /dev/null +++ b/lisp/gnus/plstore.el | |||
| @@ -0,0 +1,319 @@ | |||
| 1 | ;;; plstore.el --- searchable, partially encrypted, persistent plist store -*- lexical-binding: t -*- | ||
| 2 | ;; Copyright (C) 2011 Free Software Foundation, Inc. | ||
| 3 | |||
| 4 | ;; Author: Daiki Ueno <ueno@unixuser.org> | ||
| 5 | ;; Keywords: PGP, GnuPG | ||
| 6 | |||
| 7 | ;; This file is part of GNU Emacs. | ||
| 8 | |||
| 9 | ;; GNU Emacs 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 3 of the License, or | ||
| 12 | ;; (at your option) any later version. | ||
| 13 | |||
| 14 | ;; GNU Emacs 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. If not, see <http://www.gnu.org/licenses/>. | ||
| 21 | |||
| 22 | ;;; Commentary | ||
| 23 | |||
| 24 | ;; Creating: | ||
| 25 | ;; | ||
| 26 | ;; (setq store (plstore-open (expand-file-name "~/.emacs.d/auth.plist"))) | ||
| 27 | ;; (plstore-put store "foo" '(:host "foo.example.org" :port 80) nil) | ||
| 28 | ;; (plstore-save store) | ||
| 29 | ;; ;; :user property is secret | ||
| 30 | ;; (plstore-put store "bar" '(:host "bar.example.org") '(:user "test")) | ||
| 31 | ;; (plstore-put store "baz" '(:host "baz.example.org") '(:user "test")) | ||
| 32 | ;; (plstore-save store) ;<= will ask passphrase via GPG | ||
| 33 | ;; (plstore-close store) | ||
| 34 | ;; | ||
| 35 | ;; Searching: | ||
| 36 | ;; | ||
| 37 | ;; (setq store (plstore-open (expand-file-name "~/.emacs.d/auth.plist"))) | ||
| 38 | ;; (plstore-find store '(:host ("foo.example.org"))) | ||
| 39 | ;; (plstore-find store '(:host ("bar.example.org"))) ;<= will ask passphrase via GPG | ||
| 40 | ;; (plstore-close store) | ||
| 41 | ;; | ||
| 42 | |||
| 43 | ;;; Code: | ||
| 44 | |||
| 45 | (require 'epg) | ||
| 46 | |||
| 47 | (defvar plstore-cache-passphrase-for-symmetric-encryption nil) | ||
| 48 | (defvar plstore-passphrase-alist nil) | ||
| 49 | |||
| 50 | (defun plstore-passphrase-callback-function (_context _key-id plstore) | ||
| 51 | (if plstore-cache-passphrase-for-symmetric-encryption | ||
| 52 | (let* ((file (file-truename (plstore--get-buffer plstore))) | ||
| 53 | (entry (assoc file plstore-passphrase-alist)) | ||
| 54 | passphrase) | ||
| 55 | (or (copy-sequence (cdr entry)) | ||
| 56 | (progn | ||
| 57 | (unless entry | ||
| 58 | (setq entry (list file) | ||
| 59 | plstore-passphrase-alist | ||
| 60 | (cons entry | ||
| 61 | plstore-passphrase-alist))) | ||
| 62 | (setq passphrase | ||
| 63 | (read-passwd (format "Passphrase for PLSTORE %s: " | ||
| 64 | (plstore--get-buffer plstore)))) | ||
| 65 | (setcdr entry (copy-sequence passphrase)) | ||
| 66 | passphrase))) | ||
| 67 | (read-passwd (format "Passphrase for PLSTORE %s: " | ||
| 68 | (plstore--get-buffer plstore))))) | ||
| 69 | |||
| 70 | (defun plstore-progress-callback-function (_context _what _char current total | ||
| 71 | handback) | ||
| 72 | (if (= current total) | ||
| 73 | (message "%s...done" handback) | ||
| 74 | (message "%s...%d%%" handback | ||
| 75 | (if (> total 0) (floor (* (/ current (float total)) 100)) 0)))) | ||
| 76 | |||
| 77 | (defun plstore--get-buffer (this) | ||
| 78 | (aref this 0)) | ||
| 79 | |||
| 80 | (defun plstore--get-alist (this) | ||
| 81 | (aref this 1)) | ||
| 82 | |||
| 83 | (defun plstore--get-encrypted-data (this) | ||
| 84 | (aref this 2)) | ||
| 85 | |||
| 86 | (defun plstore--get-secret-alist (this) | ||
| 87 | (aref this 3)) | ||
| 88 | |||
| 89 | (defun plstore--get-merged-alist (this) | ||
| 90 | (aref this 4)) | ||
| 91 | |||
| 92 | (defun plstore--set-file (this file) | ||
| 93 | (aset this 0 file)) | ||
| 94 | |||
| 95 | (defun plstore--set-alist (this plist) | ||
| 96 | (aset this 1 plist)) | ||
| 97 | |||
| 98 | (defun plstore--set-encrypted-data (this encrypted-data) | ||
| 99 | (aset this 2 encrypted-data)) | ||
| 100 | |||
| 101 | (defun plstore--set-secret-alist (this secret-alist) | ||
| 102 | (aset this 3 secret-alist)) | ||
| 103 | |||
| 104 | (defun plstore--set-merged-alist (this merged-alist) | ||
| 105 | (aset this 4 merged-alist)) | ||
| 106 | |||
| 107 | (defun plstore-get-file (this) | ||
| 108 | (buffer-file-name (plstore--get-buffer this))) | ||
| 109 | |||
| 110 | ;;;###autoload | ||
| 111 | (defun plstore-open (file) | ||
| 112 | "Create a plstore instance associated with FILE." | ||
| 113 | (let ((store (vector | ||
| 114 | (find-file-noselect file) | ||
| 115 | nil ;plist (plist) | ||
| 116 | nil ;encrypted data (string) | ||
| 117 | nil ;secret plist (plist) | ||
| 118 | nil ;merged plist (plist) | ||
| 119 | ))) | ||
| 120 | (with-current-buffer (plstore--get-buffer store) | ||
| 121 | (goto-char (point-min)) | ||
| 122 | (when (looking-at ";;; public entries\n") | ||
| 123 | (forward-line) | ||
| 124 | (plstore--set-alist store (read (point-marker))) | ||
| 125 | (forward-sexp) | ||
| 126 | (forward-char) | ||
| 127 | (when (looking-at ";;; secret entries\n") | ||
| 128 | (forward-line) | ||
| 129 | (plstore--set-encrypted-data store (read (point-marker)))) | ||
| 130 | (plstore--merge-secret store))) | ||
| 131 | store)) | ||
| 132 | |||
| 133 | (defun plstore-close (plstore) | ||
| 134 | "Destroy a plstore instance PLSTORE." | ||
| 135 | (kill-buffer (plstore--get-buffer plstore))) | ||
| 136 | |||
| 137 | (defun plstore--merge-secret (plstore) | ||
| 138 | (let ((alist (plstore--get-secret-alist plstore)) | ||
| 139 | modified-alist | ||
| 140 | modified-plist | ||
| 141 | modified-entry | ||
| 142 | entry | ||
| 143 | plist | ||
| 144 | placeholder) | ||
| 145 | (plstore--set-merged-alist | ||
| 146 | plstore | ||
| 147 | (copy-tree (plstore--get-alist plstore))) | ||
| 148 | (setq modified-alist (plstore--get-merged-alist plstore)) | ||
| 149 | (while alist | ||
| 150 | (setq entry (car alist) | ||
| 151 | alist (cdr alist) | ||
| 152 | plist (cdr entry) | ||
| 153 | modified-entry (assoc (car entry) modified-alist) | ||
| 154 | modified-plist (cdr modified-entry)) | ||
| 155 | (while plist | ||
| 156 | (setq placeholder | ||
| 157 | (plist-member | ||
| 158 | modified-plist | ||
| 159 | (intern (concat ":secret-" | ||
| 160 | (substring (symbol-name (car plist)) 1))))) | ||
| 161 | (if placeholder | ||
| 162 | (setcar placeholder (car plist))) | ||
| 163 | (setq modified-plist | ||
| 164 | (plist-put modified-plist (car plist) (car (cdr plist)))) | ||
| 165 | (setq plist (nthcdr 2 plist))) | ||
| 166 | (setcdr modified-entry modified-plist)))) | ||
| 167 | |||
| 168 | (defun plstore--decrypt (plstore) | ||
| 169 | (if (plstore--get-encrypted-data plstore) | ||
| 170 | (let ((context (epg-make-context 'OpenPGP)) | ||
| 171 | plain) | ||
| 172 | (epg-context-set-passphrase-callback | ||
| 173 | context | ||
| 174 | (cons #'plstore-passphrase-callback-function | ||
| 175 | plstore)) | ||
| 176 | (epg-context-set-progress-callback | ||
| 177 | context | ||
| 178 | (cons #'plstore-progress-callback-function | ||
| 179 | (format "Decrypting %s" (plstore-get-file plstore)))) | ||
| 180 | (setq plain | ||
| 181 | (epg-decrypt-string context | ||
| 182 | (plstore--get-encrypted-data plstore))) | ||
| 183 | (plstore--set-secret-alist plstore (car (read-from-string plain))) | ||
| 184 | (plstore--merge-secret plstore) | ||
| 185 | (plstore--set-encrypted-data plstore nil)))) | ||
| 186 | |||
| 187 | (defun plstore--match (entry keys skip-if-secret-found) | ||
| 188 | (let ((result t) key-name key-value prop-value secret-name) | ||
| 189 | (while keys | ||
| 190 | (setq key-name (car keys) | ||
| 191 | key-value (car (cdr keys)) | ||
| 192 | prop-value (plist-get (cdr entry) key-name)) | ||
| 193 | (unless (member prop-value key-value) | ||
| 194 | (if skip-if-secret-found | ||
| 195 | (progn | ||
| 196 | (setq secret-name | ||
| 197 | (intern (concat ":secret-" | ||
| 198 | (substring (symbol-name key-name) 1)))) | ||
| 199 | (if (plist-member (cdr entry) secret-name) | ||
| 200 | (setq result 'secret) | ||
| 201 | (setq result nil | ||
| 202 | keys nil))) | ||
| 203 | (setq result nil | ||
| 204 | keys nil))) | ||
| 205 | (setq keys (nthcdr 2 keys))) | ||
| 206 | result)) | ||
| 207 | |||
| 208 | (defun plstore-find (plstore keys) | ||
| 209 | "Perform search on PLSTORE with KEYS. | ||
| 210 | KEYS is a plist." | ||
| 211 | (let (entries alist entry match decrypt plist) | ||
| 212 | ;; First, go through the merged plist alist and collect entries | ||
| 213 | ;; matched with keys. | ||
| 214 | (setq alist (plstore--get-merged-alist plstore)) | ||
| 215 | (while alist | ||
| 216 | (setq entry (car alist) | ||
| 217 | alist (cdr alist) | ||
| 218 | match (plstore--match entry keys t)) | ||
| 219 | (if (eq match 'secret) | ||
| 220 | (setq decrypt t) | ||
| 221 | (when match | ||
| 222 | (setq plist (cdr entry)) | ||
| 223 | (while plist | ||
| 224 | (if (string-match "\\`:secret-" (symbol-name (car plist))) | ||
| 225 | (setq decrypt t | ||
| 226 | plist nil)) | ||
| 227 | (setq plist (nthcdr 2 plist))) | ||
| 228 | (setq entries (cons entry entries))))) | ||
| 229 | ;; Second, decrypt the encrypted plist and try again. | ||
| 230 | (when decrypt | ||
| 231 | (setq entries nil) | ||
| 232 | (plstore--decrypt plstore) | ||
| 233 | (setq alist (plstore--get-merged-alist plstore)) | ||
| 234 | (while alist | ||
| 235 | (setq entry (car alist) | ||
| 236 | alist (cdr alist) | ||
| 237 | match (plstore--match entry keys nil)) | ||
| 238 | (if match | ||
| 239 | (setq entries (cons entry entries))))) | ||
| 240 | (nreverse entries))) | ||
| 241 | |||
| 242 | (defun plstore-get (plstore name) | ||
| 243 | "Get an entry with NAME in PLSTORE." | ||
| 244 | (let ((entry (assoc name (plstore--get-merged-alist plstore))) | ||
| 245 | plist) | ||
| 246 | (setq plist (cdr entry)) | ||
| 247 | (while plist | ||
| 248 | (if (string-match "\\`:secret-" (symbol-name (car plist))) | ||
| 249 | (progn | ||
| 250 | (plstore--decrypt plstore) | ||
| 251 | (setq entry (assoc name (plstore--get-merged-alist plstore)) | ||
| 252 | plist nil)) | ||
| 253 | (setq plist (nthcdr 2 plist)))) | ||
| 254 | entry)) | ||
| 255 | |||
| 256 | (defun plstore-put (plstore name keys secret-keys) | ||
| 257 | "Put an entry with NAME in PLSTORE. | ||
| 258 | KEYS is a plist containing non-secret data. | ||
| 259 | SECRET-KEYS is a plist containing secret data." | ||
| 260 | (let (entry | ||
| 261 | plist | ||
| 262 | secret-plist | ||
| 263 | symbol) | ||
| 264 | (if secret-keys | ||
| 265 | (plstore--decrypt plstore)) | ||
| 266 | (while secret-keys | ||
| 267 | (setq symbol | ||
| 268 | (intern (concat ":secret-" | ||
| 269 | (substring (symbol-name (car secret-keys)) 1)))) | ||
| 270 | (setq plist (plist-put plist symbol t) | ||
| 271 | secret-plist (plist-put secret-plist | ||
| 272 | (car secret-keys) (car (cdr secret-keys))) | ||
| 273 | secret-keys (nthcdr 2 secret-keys))) | ||
| 274 | (while keys | ||
| 275 | (setq symbol | ||
| 276 | (intern (concat ":secret-" | ||
| 277 | (substring (symbol-name (car keys)) 1)))) | ||
| 278 | (setq plist (plist-put plist (car keys) (car (cdr keys))) | ||
| 279 | keys (nthcdr 2 keys))) | ||
| 280 | (setq entry (assoc name (plstore--get-alist plstore))) | ||
| 281 | (if entry | ||
| 282 | (setcdr entry plist) | ||
| 283 | (plstore--set-alist | ||
| 284 | plstore | ||
| 285 | (cons (cons name plist) (plstore--get-alist plstore)))) | ||
| 286 | (when secret-plist | ||
| 287 | (setq entry (assoc name (plstore--get-secret-alist plstore))) | ||
| 288 | (if entry | ||
| 289 | (setcdr entry secret-plist) | ||
| 290 | (plstore--set-secret-alist | ||
| 291 | plstore | ||
| 292 | (cons (cons name secret-plist) (plstore--get-secret-alist plstore))))) | ||
| 293 | (plstore--merge-secret plstore))) | ||
| 294 | |||
| 295 | (defvar pp-escape-newlines) | ||
| 296 | (defun plstore-save (plstore) | ||
| 297 | "Save the contents of PLSTORE associated with a FILE." | ||
| 298 | (with-current-buffer (plstore--get-buffer plstore) | ||
| 299 | (erase-buffer) | ||
| 300 | (insert ";;; public entries\n" (pp-to-string (plstore--get-alist plstore))) | ||
| 301 | (if (plstore--get-secret-alist plstore) | ||
| 302 | (let ((context (epg-make-context 'OpenPGP)) | ||
| 303 | (pp-escape-newlines nil) | ||
| 304 | cipher) | ||
| 305 | (epg-context-set-armor context t) | ||
| 306 | (epg-context-set-passphrase-callback | ||
| 307 | context | ||
| 308 | (cons #'plstore-passphrase-callback-function | ||
| 309 | plstore)) | ||
| 310 | (setq cipher (epg-encrypt-string context | ||
| 311 | (pp-to-string | ||
| 312 | (plstore--get-secret-alist plstore)) | ||
| 313 | nil)) | ||
| 314 | (insert ";;; secret entries\n" (pp-to-string cipher)))) | ||
| 315 | (save-buffer))) | ||
| 316 | |||
| 317 | (provide 'plstore) | ||
| 318 | |||
| 319 | ;;; plstore.el ends here | ||