aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaiki Ueno2011-06-30 16:27:25 +0900
committerDaiki Ueno2011-06-30 16:27:25 +0900
commit8977de272444fe109c0266591e2107c5563802bf (patch)
tree82b56839fa124e12d97abe77e59d06797b279e99
parentd0b36cbeb198377ef831a75b75645e76b471e7c1 (diff)
downloademacs-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/ChangeLog6
-rw-r--r--lisp/gnus/auth-source.el230
-rw-r--r--lisp/gnus/plstore.el319
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 @@
12011-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
12011-06-30 Glenn Morris <rgm@gnu.org> 72011-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.
210KEYS 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.
258KEYS is a plist containing non-secret data.
259SECRET-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