aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTed Zlatanov2012-07-29 22:07:41 +0000
committerKatsumi Yamaoka2012-07-29 22:07:41 +0000
commitd7fcec5d7d7b5c457dd6b44572f672c81691e171 (patch)
tree40bd1eb45cb5d268505a5f09ec420c6d57bc1128
parentc91562a619ba72b93196791a519e6481ff633fc1 (diff)
downloademacs-d7fcec5d7d7b5c457dd6b44572f672c81691e171.tar.gz
emacs-d7fcec5d7d7b5c457dd6b44572f672c81691e171.zip
lisp/gnus/auth-source.el: Support Mac OS X Keychains
-rw-r--r--lisp/gnus/ChangeLog11
-rw-r--r--lisp/gnus/auth-source.el264
2 files changed, 274 insertions, 1 deletions
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog
index 0476fc043c2..9426b7889c8 100644
--- a/lisp/gnus/ChangeLog
+++ b/lisp/gnus/ChangeLog
@@ -1,3 +1,14 @@
12012-07-29 Teodor Zlatanov <tzz@lifelogs.com>
2
3 * auth-source.el (auth-sources, auth-source-backend-parse)
4 (auth-source-macos-keychain-search)
5 (auth-source-macos-keychain-search-items)
6 (auth-source-macos-keychain-result-append)
7 (auth-source-macos-keychain-create): Support Mac OS X Keychains in
8 auth-source.el through the /usr/bin/security utility.
9 (auth-sources): Fix syntax error.
10 (auth-source-macos-keychain-result-append): Fix variable name.
11
12012-07-27 Julien Danjou <jd@dex.adm.naquadah.org> 122012-07-27 Julien Danjou <jd@dex.adm.naquadah.org>
2 13
3 * message.el (fboundp): Add a defalias on `mail-dont-reply-to' for 14 * message.el (fboundp): Add a defalias on `mail-dont-reply-to' for
diff --git a/lisp/gnus/auth-source.el b/lisp/gnus/auth-source.el
index 47359500dc4..87f09632250 100644
--- a/lisp/gnus/auth-source.el
+++ b/lisp/gnus/auth-source.el
@@ -254,6 +254,13 @@ can get pretty complex."
254 (const :tag "Default Secrets API Collection" 'default) 254 (const :tag "Default Secrets API Collection" 'default)
255 (const :tag "Login Secrets API Collection" "secrets:Login") 255 (const :tag "Login Secrets API Collection" "secrets:Login")
256 (const :tag "Temp Secrets API Collection" "secrets:session") 256 (const :tag "Temp Secrets API Collection" "secrets:session")
257
258 (const :tag "Default internet Mac OS Keychain"
259 'macos-keychain-internet)
260
261 (const :tag "Default generic Mac OS Keychain"
262 'macos-keychain-generic)
263
257 (list :tag "Source definition" 264 (list :tag "Source definition"
258 (const :format "" :value :source) 265 (const :format "" :value :source)
259 (choice :tag "Authentication backend choice" 266 (choice :tag "Authentication backend choice"
@@ -266,7 +273,21 @@ can get pretty complex."
266 (const :tag "Default" 'default) 273 (const :tag "Default" 'default)
267 (const :tag "Login" "Login") 274 (const :tag "Login" "Login")
268 (const 275 (const
269 :tag "Temporary" "session")))) 276 :tag "Temporary" "session")))
277 (list
278 :tag "Mac OS internet Keychain"
279 (const :format ""
280 :value :macos-keychain-internet)
281 (choice :tag "Collection to use"
282 (string :tag "internet Keychain path")
283 (const :tag "default" 'default))))
284 (list
285 :tag "Mac OS generic Keychain"
286 (const :format ""
287 :value :macos-keychain-generic)
288 (choice :tag "Collection to use"
289 (string :tag "generic Keychain path")
290 (const :tag "default" 'default))))
270 (repeat :tag "Extra Parameters" :inline t 291 (repeat :tag "Extra Parameters" :inline t
271 (choice :tag "Extra parameter" 292 (choice :tag "Extra parameter"
272 (list 293 (list
@@ -377,6 +398,10 @@ with \"[a/b/c] \" if CHOICES is '\(?a ?b ?c\)."
377;; (auth-source-backend-parse "myfile.gpg") 398;; (auth-source-backend-parse "myfile.gpg")
378;; (auth-source-backend-parse 'default) 399;; (auth-source-backend-parse 'default)
379;; (auth-source-backend-parse "secrets:Login") 400;; (auth-source-backend-parse "secrets:Login")
401;; (auth-source-backend-parse 'macos-keychain-internet)
402;; (auth-source-backend-parse 'macos-keychain-generic)
403;; (auth-source-backend-parse "macos-keychain-internet:/path/here.keychain")
404;; (auth-source-backend-parse "macos-keychain-generic:/path/here.keychain")
380 405
381(defun auth-source-backend-parse (entry) 406(defun auth-source-backend-parse (entry)
382 "Creates an auth-source-backend from an ENTRY in `auth-sources'." 407 "Creates an auth-source-backend from an ENTRY in `auth-sources'."
@@ -391,6 +416,28 @@ with \"[a/b/c] \" if CHOICES is '\(?a ?b ?c\)."
391 ;; matching any user, host, and protocol 416 ;; matching any user, host, and protocol
392 ((and (stringp entry) (string-match "^secrets:\\(.+\\)" entry)) 417 ((and (stringp entry) (string-match "^secrets:\\(.+\\)" entry))
393 (auth-source-backend-parse `(:source (:secrets ,(match-string 1 entry))))) 418 (auth-source-backend-parse `(:source (:secrets ,(match-string 1 entry)))))
419
420 ;; take 'macos-keychain-internet and recurse to get it as a Mac OS
421 ;; Keychain collection matching any user, host, and protocol
422 ((eq entry 'macos-keychain-internet)
423 (auth-source-backend-parse '(:source (:macos-keychain-internet default))))
424 ;; take 'macos-keychain-generic and recurse to get it as a Mac OS
425 ;; Keychain collection matching any user, host, and protocol
426 ((eq entry 'macos-keychain-generic)
427 (auth-source-backend-parse '(:source (:macos-keychain-generic default))))
428 ;; take macos-keychain-internet:XYZ and recurse to get it as MacOS
429 ;; Keychain "XYZ" matching any user, host, and protocol
430 ((and (stringp entry) (string-match "^macos-keychain-internet:\\(.+\\)"
431 entry))
432 (auth-source-backend-parse `(:source (:macos-keychain-internet
433 ,(match-string 1 entry)))))
434 ;; take macos-keychain-generic:XYZ and recurse to get it as MacOS
435 ;; Keychain "XYZ" matching any user, host, and protocol
436 ((and (stringp entry) (string-match "^macos-keychain-generic:\\(.+\\)"
437 entry))
438 (auth-source-backend-parse `(:source (:macos-keychain-generic
439 ,(match-string 1 entry)))))
440
394 ;; take just a file name and recurse to get it as a netrc file 441 ;; take just a file name and recurse to get it as a netrc file
395 ;; matching any user, host, and protocol 442 ;; matching any user, host, and protocol
396 ((stringp entry) 443 ((stringp entry)
@@ -413,6 +460,33 @@ with \"[a/b/c] \" if CHOICES is '\(?a ?b ?c\)."
413 :search-function 'auth-source-netrc-search 460 :search-function 'auth-source-netrc-search
414 :create-function 'auth-source-netrc-create))) 461 :create-function 'auth-source-netrc-create)))
415 462
463 ;; the MacOS Keychain
464 ((and
465 (not (null (plist-get entry :source))) ; the source must not be nil
466 (listp (plist-get entry :source)) ; and it must be a list
467 (or
468 (plist-get (plist-get entry :source) :macos-keychain-generic)
469 (plist-get (plist-get entry :source) :macos-keychain-internet)))
470
471 (let* ((source-spec (plist-get entry :source))
472 (keychain-generic (plist-get source-spec :macos-keychain-generic))
473 (keychain-type (if keychain-generic
474 'macos-keychain-generic
475 'macos-keychain-internet))
476 (source (plist-get source-spec (if keychain-generic
477 :macos-keychain-generic
478 :macos-keychain-internet))))
479
480 (when (symbolp source)
481 (setq source (symbol-name source)))
482
483 (auth-source-backend
484 (format "Mac OS Keychain (%s)" source)
485 :source source
486 :type keychain-type
487 :search-function 'auth-source-macos-keychain-search
488 :create-function 'auth-source-macos-keychain-create)))
489
416 ;; the Secrets API. We require the package, in order to have a 490 ;; the Secrets API. We require the package, in order to have a
417 ;; defined value for `secrets-enabled'. 491 ;; defined value for `secrets-enabled'.
418 ((and 492 ((and
@@ -694,6 +768,7 @@ must call it to obtain the actual value."
694 (let* ((bmatches (apply 768 (let* ((bmatches (apply
695 (slot-value backend 'search-function) 769 (slot-value backend 'search-function)
696 :backend backend 770 :backend backend
771 :type (slot-value backend :type)
697 ;; note we're overriding whatever the spec 772 ;; note we're overriding whatever the spec
698 ;; has for :require, :create, and :delete 773 ;; has for :require, :create, and :delete
699 :require require 774 :require require
@@ -1515,6 +1590,193 @@ authentication tokens:
1515 ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec) 1590 ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec)
1516 (debug spec)) 1591 (debug spec))
1517 1592
1593;;; Backend specific parsing: Mac OS Keychain (using /usr/bin/security) backend
1594
1595;; (let ((auth-sources '(macos-keychain-internet))) (auth-source-search :max 1 :create t))
1596;; (let ((auth-sources '(macos-keychain-internet))) (auth-source-search :max 1 :delete t))
1597;; (let ((auth-sources '(macos-keychain-internet))) (auth-source-search :max 1))
1598;; (let ((auth-sources '(macos-keychain-internet))) (auth-source-search))
1599
1600;; (let ((auth-sources '(macos-keychain-generic))) (auth-source-search :max 1 :create t))
1601;; (let ((auth-sources '(macos-keychain-generic))) (auth-source-search :max 1 :delete t))
1602;; (let ((auth-sources '(macos-keychain-generic))) (auth-source-search :max 1))
1603;; (let ((auth-sources '(macos-keychain-generic))) (auth-source-search))
1604
1605;; (let ((auth-sources '("macos-keychain-internet:/Users/tzz/Library/Keychains/login.keychain"))) (auth-source-search :max 1))
1606;; (let ((auth-sources '("macos-keychain-generic:Login"))) (auth-source-search :max 1 :host "git.gnus.org"))
1607
1608(defun* auth-source-macos-keychain-search (&rest
1609 spec
1610 &key backend create delete label
1611 type max host user port
1612 &allow-other-keys)
1613 "Search the MacOS Keychain; spec is like `auth-source'.
1614
1615All search keys must match exactly. If you need substring
1616matching, do a wider search and narrow it down yourself.
1617
1618You'll get back all the properties of the token as a plist.
1619
1620The :type key is either 'macos-keychain-internet or
1621'macos-keychain-generic.
1622
1623For the internet keychain type, the :label key searches the
1624item's labels (\"-l LABEL\" passed to \"/usr/bin/security\").
1625Similarly, :host maps to \"-s HOST\", :user maps to \"-a USER\",
1626and :port maps to \"-P PORT\" or \"-r PROT\"
1627(note PROT has to be a 4-character string).
1628
1629For the generic keychain type, the :label key searches the item's
1630labels (\"-l LABEL\" passed to \"/usr/bin/security\").
1631Similarly, :host maps to \"-c HOST\" (the \"creator\" keychain
1632field), :user maps to \"-a USER\", and :port maps to \"-s PORT\".
1633
1634Here's an example that looks for the first item in the default
1635generic MacOS Keychain:
1636
1637 \(let ((auth-sources '(macos-keychain-generic)))
1638 (auth-source-search :max 1)
1639
1640Here's another that looks for the first item in the internet
1641MacOS Keychain collection whose label is 'gnus':
1642
1643 \(let ((auth-sources '(macos-keychain-internet)))
1644 (auth-source-search :max 1 :label \"gnus\")
1645
1646And this one looks for the first item in the internet keychain
1647entries for git.gnus.org:
1648
1649 \(let ((auth-sources '(macos-keychain-internet\")))
1650 (auth-source-search :max 1 :host \"git.gnus.org\"))
1651"
1652 ;; TODO
1653 (assert (not create) nil
1654 "The MacOS Keychain auth-source backend doesn't support creation yet")
1655 ;; TODO
1656 ;; (macos-keychain-delete-item coll elt)
1657 (assert (not delete) nil
1658 "The MacOS Keychain auth-source backend doesn't support deletion yet")
1659
1660 (let* ((coll (oref backend source))
1661 (max (or max 5000)) ; sanity check: default to stop at 5K
1662 (ignored-keys '(:create :delete :max :backend :label))
1663 (search-keys (loop for i below (length spec) by 2
1664 unless (memq (nth i spec) ignored-keys)
1665 collect (nth i spec)))
1666 ;; build a search spec without the ignored keys
1667 ;; if a search key is nil or t (match anything), we skip it
1668 (search-spec (apply 'append (mapcar
1669 (lambda (k)
1670 (if (or (null (plist-get spec k))
1671 (eq t (plist-get spec k)))
1672 nil
1673 (list k (plist-get spec k))))
1674 search-keys)))
1675 ;; needed keys (always including host, login, port, and secret)
1676 (returned-keys (mm-delete-duplicates (append
1677 '(:host :login :port :secret)
1678 search-keys)))
1679 (items (apply 'auth-source-macos-keychain-search-items
1680 coll
1681 type
1682 max
1683 search-spec))
1684
1685 ;; ensure each item has each key in `returned-keys'
1686 (items (mapcar (lambda (plist)
1687 (append
1688 (apply 'append
1689 (mapcar (lambda (req)
1690 (if (plist-get plist req)
1691 nil
1692 (list req nil)))
1693 returned-keys))
1694 plist))
1695 items)))
1696 items))
1697
1698(defun* auth-source-macos-keychain-search-items (coll type max
1699 &rest spec
1700 &key label type
1701 host user port
1702 &allow-other-keys)
1703
1704 (let* ((keychain-generic (eq type 'macos-keychain-generic))
1705 (args `(,(if keychain-generic
1706 "find-generic-password"
1707 "find-internet-password")
1708 "-g"))
1709 (ret (list :type type)))
1710 (when label
1711 (setq args (append args (list "-l" label))))
1712 (when host
1713 (setq args (append args (list (if keychain-generic "-c" "-s") host))))
1714 (when user
1715 (setq args (append args (list "-a" user))))
1716
1717 (when port
1718 (if keychain-generic
1719 (setq args (append args (list "-s" port)))
1720 (setq args (append args (list
1721 (if (string-match "[0-9]+" port) "-P" "-r")
1722 port)))))
1723
1724 (unless (equal coll "default")
1725 (setq args (append args (list coll))))
1726
1727 (with-temp-buffer
1728 (apply 'call-process "/usr/bin/security" nil t nil args)
1729 (goto-char (point-min))
1730 (while (not (eobp))
1731 (cond
1732 ((looking-at "^password: \"\\(.+\\)\"$")
1733 (auth-source-macos-keychain-result-append
1734 ret
1735 keychain-generic
1736 "secret"
1737 (lexical-let ((v (match-string 1)))
1738 (lambda () v))))
1739 ;; TODO: check if this is really the label
1740 ;; match 0x00000007 <blob>="AppleID"
1741 ((looking-at "^[ ]+0x00000007 <blob>=\"\\(.+\\)\"")
1742 (auth-source-macos-keychain-result-append
1743 ret
1744 keychain-generic
1745 "label"
1746 (match-string 1)))
1747 ;; match "crtr"<uint32>="aapl"
1748 ;; match "svce"<blob>="AppleID"
1749 ((looking-at "^[ ]+\"\\([a-z]+\\)\"[^=]+=\"\\(.+\\)\"")
1750 (auth-source-macos-keychain-result-append
1751 ret
1752 keychain-generic
1753 (match-string 1)
1754 (match-string 2))))
1755 (forward-line)))
1756 ;; return `ret' iff it has the :secret key
1757 (and (plist-get ret :secret) (list ret))))
1758
1759(defun auth-source-macos-keychain-result-append (result generic k v)
1760 (push v ret)
1761 (setq k (cond
1762 ((equal k "acct") "user")
1763 ;; for generic keychains, creator is host, service is port
1764 ((and generic (equal k "crtr")) "host")
1765 ((and generic (equal k "svce")) "port")
1766 ;; for internet keychains, protocol is port, server is host
1767 ((and (not generic) (equal k "ptcl")) "port")
1768 ((and (not generic) (equal k "srvr")) "host")
1769 (t k)))
1770
1771 (push (intern (format ":%s" k)) ret))
1772
1773(defun* auth-source-macos-keychain-create (&rest
1774 spec
1775 &key backend type max host user port
1776 &allow-other-keys)
1777 ;; TODO
1778 (debug spec))
1779
1518;;; Backend specific parsing: PLSTORE backend 1780;;; Backend specific parsing: PLSTORE backend
1519 1781
1520(defun* auth-source-plstore-search (&rest 1782(defun* auth-source-plstore-search (&rest