Merge remote branch 'origin/no-gnus'
[gnus] / lisp / auth-source.el
index 39bf32f..ee608b3 100644 (file)
@@ -1,6 +1,6 @@
 ;;; auth-source.el --- authentication sources for Gnus and Emacs
 
-;; Copyright (C) 2008-2011 Free Software Foundation, Inc.
+;; Copyright (C) 2008-2012 Free Software Foundation, Inc.
 
 ;; Author: Ted Zlatanov <tzz@lifelogs.com>
 ;; Keywords: news
@@ -42,7 +42,6 @@
 (require 'password-cache)
 (require 'mm-util)
 (require 'gnus-util)
-(require 'assoc)
 
 (eval-when-compile (require 'cl))
 (eval-and-compile
@@ -94,6 +93,7 @@
   "How many seconds passwords are cached, or nil to disable
 expiring.  Overrides `password-cache-expiry' through a
 let-binding."
+  :version "24.1"
   :group 'auth-source
   :type '(choice (const :tag "Never" nil)
                  (const :tag "All Day" 86400)
@@ -101,9 +101,9 @@ let-binding."
                  (const :tag "30 Minutes" 1800)
                  (integer :tag "Seconds")))
 
-;;; The slots below correspond with the `auth-source-search' spec,
-;;; so a backend with :host set, for instance, would match only
-;;; searches for that host.  Normally they are nil.
+;; The slots below correspond with the `auth-source-search' spec,
+;; so a backend with :host set, for instance, would match only
+;; searches for that host.  Normally they are nil.
 (defclass auth-source-backend ()
   ((type :initarg :type
          :initform 'netrc
@@ -158,8 +158,8 @@ let-binding."
                        (repeat :tag "Names"
                                (string :tag "Name")))))
 
-;;; generate all the protocols in a format Customize can use
-;;; TODO: generate on the fly from auth-source-protocols
+;; Generate all the protocols in a format Customize can use.
+;; TODO: generate on the fly from auth-source-protocols
 (defconst auth-source-protocols-customize
   (mapcar (lambda (a)
             (let ((p (car-safe a)))
@@ -348,7 +348,7 @@ If the value is not a list, symmetric encryption will be used."
    msg))
 
 
-;;; (auth-source-read-char-choice "enter choice? " '(?a ?b ?q))
+;; (auth-source-read-char-choice "enter choice? " '(?a ?b ?q))
 (defun auth-source-read-char-choice (prompt choices)
   "Read one of CHOICES by `read-char-choice', or `read-char'.
 `dropdown-list' support is disabled because it doesn't work reliably.
@@ -720,10 +720,10 @@ must call it to obtain the actual value."
             (setq matches (append matches bmatches))))))
     matches))
 
-;;; (auth-source-search :max 1)
-;;; (funcall (plist-get (nth 0 (auth-source-search :max 1)) :secret))
-;;; (auth-source-search :host "nonesuch" :type 'netrc :K 1)
-;;; (auth-source-search :host "nonesuch" :type 'secrets)
+;; (auth-source-search :max 1)
+;; (funcall (plist-get (nth 0 (auth-source-search :max 1)) :secret))
+;; (auth-source-search :host "nonesuch" :type 'netrc :K 1)
+;; (auth-source-search :host "nonesuch" :type 'secrets)
 
 (defun* auth-source-delete (&rest spec
                                   &key delete
@@ -759,39 +759,42 @@ Returns the deleted entries."
         do (password-cache-remove (symbol-name sym)))
   (setq auth-source-netrc-cache nil))
 
+(defun auth-source-format-cache-entry (spec)
+  "Format SPEC entry to put it in the password cache."
+  (concat auth-source-magic (format "%S" spec)))
+
 (defun auth-source-remember (spec found)
   "Remember FOUND search results for SPEC."
   (let ((password-cache-expiry auth-source-cache-expiry))
     (password-cache-add
-     (concat auth-source-magic (format "%S" spec)) found)))
+     (auth-source-format-cache-entry spec) found)))
 
 (defun auth-source-recall (spec)
   "Recall FOUND search results for SPEC."
-  (password-read-from-cache
-   (concat auth-source-magic (format "%S" spec))))
+  (password-read-from-cache (auth-source-format-cache-entry spec)))
 
 (defun auth-source-remembered-p (spec)
   "Check if SPEC is remembered."
   (password-in-cache-p
-   (concat auth-source-magic (format "%S" spec))))
+   (auth-source-format-cache-entry spec)))
 
 (defun auth-source-forget (spec)
   "Forget any cached data matching SPEC exactly.
 
 This is the same SPEC you passed to `auth-source-search'.
 Returns t or nil for forgotten or not found."
-  (password-cache-remove (concat auth-source-magic (format "%S" spec))))
+  (password-cache-remove (auth-source-format-cache-entry spec)))
 
-;;; (loop for sym being the symbols of password-data when (string-match (concat "^" auth-source-magic) (symbol-name sym)) collect (symbol-name sym))
+;; (loop for sym being the symbols of password-data when (string-match (concat "^" auth-source-magic) (symbol-name sym)) collect (symbol-name sym))
 
-;;; (auth-source-remember '(:host "wedd") '(4 5 6))
-;;; (auth-source-remembered-p '(:host "wedd"))
-;;; (auth-source-remember '(:host "xedd") '(1 2 3))
-;;; (auth-source-remembered-p '(:host "xedd"))
-;;; (auth-source-remembered-p '(:host "zedd"))
-;;; (auth-source-recall '(:host "xedd"))
-;;; (auth-source-recall '(:host t))
-;;; (auth-source-forget+ :host t)
+;; (auth-source-remember '(:host "wedd") '(4 5 6))
+;; (auth-source-remembered-p '(:host "wedd"))
+;; (auth-source-remember '(:host "xedd") '(1 2 3))
+;; (auth-source-remembered-p '(:host "xedd"))
+;; (auth-source-remembered-p '(:host "zedd"))
+;; (auth-source-recall '(:host "xedd"))
+;; (auth-source-recall '(:host t))
+;; (auth-source-forget+ :host t)
 
 (defun* auth-source-forget+ (&rest spec &allow-other-keys)
   "Forget any cached data matching SPEC.  Returns forgotten count.
@@ -825,8 +828,8 @@ while \(:host t) would find all host entries."
               (return 'no)))
           'no))))
 
-;;; (auth-source-pick-first-password :host "z.lifelogs.com")
-;;; (auth-source-pick-first-password :port "imap")
+;; (auth-source-pick-first-password :host "z.lifelogs.com")
+;; (auth-source-pick-first-password :port "imap")
 (defun auth-source-pick-first-password (&rest spec)
   "Pick the first secret found from applying SPEC to `auth-source-search'."
   (let* ((result (nth 0 (apply 'auth-source-search (plist-put spec :max 1))))
@@ -859,7 +862,22 @@ while \(:host t) would find all host entries."
 
 ;;; Backend specific parsing: netrc/authinfo backend
 
-;;; (auth-source-netrc-parse "~/.authinfo.gpg")
+(defun auth-source--aput-1 (alist key val)
+  (let ((seen ())
+        (rest alist))
+    (while (and (consp rest) (not (equal key (caar rest))))
+      (push (pop rest) seen))
+    (cons (cons key val)
+          (if (null rest) alist
+            (nconc (nreverse seen)
+                   (if (equal key (caar rest)) (cdr rest) rest))))))
+(defmacro auth-source--aput (var key val)
+  `(setq ,var (auth-source--aput-1 ,var ,key ,val)))
+
+(defun auth-source--aget (alist key)
+  (cdr (assoc key alist)))
+
+;; (auth-source-netrc-parse "~/.authinfo.gpg")
 (defun* auth-source-netrc-parse (&rest
                                  spec
                                  &key file max host user port delete require
@@ -894,10 +912,11 @@ Note that the MAX parameter is used so we can exit the parse early."
             ;; cache all netrc files (used to be just .gpg files)
             ;; Store the contents of the file heavily encrypted in memory.
             ;; (note for the irony-impaired: they are just obfuscated)
-            (aput 'auth-source-netrc-cache file
-                  (list :mtime (nth 5 (file-attributes file))
-                        :secret (lexical-let ((v (mapcar '1+ (buffer-string))))
-                                  (lambda () (apply 'string (mapcar '1- v)))))))
+            (auth-source--aput
+             auth-source-netrc-cache file
+             (list :mtime (nth 5 (file-attributes file))
+                   :secret (lexical-let ((v (mapcar '1+ (buffer-string))))
+                             (lambda () (apply 'string (mapcar '1- v)))))))
           (goto-char (point-min))
           ;; Go through the file, line by line.
           (while (and (not (eobp))
@@ -943,21 +962,21 @@ Note that the MAX parameter is used so we can exit the parse early."
                        (auth-source-search-collection
                         host
                         (or
-                         (aget alist "machine")
-                         (aget alist "host")
+                         (auth-source--aget alist "machine")
+                         (auth-source--aget alist "host")
                          t))
                        (auth-source-search-collection
                         user
                         (or
-                         (aget alist "login")
-                         (aget alist "account")
-                         (aget alist "user")
+                         (auth-source--aget alist "login")
+                         (auth-source--aget alist "account")
+                         (auth-source--aget alist "user")
                          t))
                        (auth-source-search-collection
                         port
                         (or
-                         (aget alist "port")
-                         (aget alist "protocol")
+                         (auth-source--aget alist "port")
+                         (auth-source--aget alist "protocol")
                          t))
                        (or
                         ;; the required list of keys is nil, or
@@ -1092,8 +1111,8 @@ FILE is the file from which we obtained this token."
               ret))
           alist))
 
-;;; (setq secret (plist-get (nth 0 (auth-source-search :host t :type 'netrc :K 1 :max 1)) :secret))
-;;; (funcall secret)
+;; (setq secret (plist-get (nth 0 (auth-source-search :host t :type 'netrc :K 1 :max 1)) :secret))
+;; (funcall secret)
 
 (defun* auth-source-netrc-search (&rest
                                   spec
@@ -1139,8 +1158,8 @@ See `auth-source-search' for details on SPEC."
       (nth 0 v)
     v))
 
-;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t)
-;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t :create-extra-keys '((A "default A") (B)))
+;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t)
+;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t :create-extra-keys '((A "default A") (B)))
 
 (defun* auth-source-netrc-create (&rest spec
                                         &key backend
@@ -1172,7 +1191,7 @@ See `auth-source-search' for details on SPEC."
                           ;; just the value otherwise
                           (t (symbol-value br)))))
           (when br-choice
-            (aput 'valist br br-choice)))))
+            (auth-source--aput valist br br-choice)))))
 
     ;; for extra required elements, see if the spec includes a value for them
     (dolist (er create-extra)
@@ -1181,17 +1200,18 @@ See `auth-source-search' for details on SPEC."
                         collect (nth i spec))))
         (dolist (k keys)
           (when (equal (symbol-name k) name)
-            (aput 'valist er (plist-get spec k))))))
+            (auth-source--aput valist er (plist-get spec k))))))
 
     ;; for each required element
     (dolist (r required)
-      (let* ((data (aget valist r))
+      (let* ((data (auth-source--aget valist r))
              ;; take the first element if the data is a list
              (data (or (auth-source-netrc-element-or-first data)
                        (plist-get current-data
                                   (intern (format ":%s" r) obarray))))
              ;; this is the default to be offered
-             (given-default (aget auth-source-creation-defaults r))
+             (given-default (auth-source--aget
+                             auth-source-creation-defaults r))
              ;; the default supplementals are simple:
              ;; for the user, try `given-default' and then (user-login-name);
              ;; otherwise take `given-default'
@@ -1203,22 +1223,22 @@ See `auth-source-search' for details on SPEC."
                                   (cons 'user
                                         (or
                                          (auth-source-netrc-element-or-first
-                                          (aget valist 'user))
+                                          (auth-source--aget valist 'user))
                                          (plist-get artificial :user)
                                          "[any user]"))
                                   (cons 'host
                                         (or
                                          (auth-source-netrc-element-or-first
-                                          (aget valist 'host))
+                                          (auth-source--aget valist 'host))
                                          (plist-get artificial :host)
                                          "[any host]"))
                                   (cons 'port
                                         (or
                                          (auth-source-netrc-element-or-first
-                                          (aget valist 'port))
+                                          (auth-source--aget valist 'port))
                                          (plist-get artificial :port)
                                          "[any port]"))))
-             (prompt (or (aget auth-source-creation-prompts r)
+             (prompt (or (auth-source--aget auth-source-creation-prompts r)
                          (case r
                            (secret "%p password for %u@%h: ")
                            (user "%p user name for %h: ")
@@ -1227,54 +1247,51 @@ See `auth-source-search' for details on SPEC."
                          (format "Enter %s (%%u@%%h:%%p): " r)))
              (prompt (auth-source-format-prompt
                       prompt
-                      `((?u ,(aget printable-defaults 'user))
-                        (?h ,(aget printable-defaults 'host))
-                        (?p ,(aget printable-defaults 'port))))))
+                      `((?u ,(auth-source--aget printable-defaults 'user))
+                        (?h ,(auth-source--aget printable-defaults 'host))
+                        (?p ,(auth-source--aget printable-defaults 'port))))))
 
         ;; Store the data, prompting for the password if needed.
-        (setq data
-              (cond
-               ((and (null data) (eq r 'secret))
-                ;; Special case prompt for passwords.
-                ;; TODO: make the default (setq auth-source-netrc-use-gpg-tokens `((,(if (boundp 'epa-file-auto-mode-alist-entry) (car (symbol-value 'epa-file-auto-mode-alist-entry)) "\\.gpg\\'") nil) (t gpg)))
-                ;; TODO: or maybe leave as (setq auth-source-netrc-use-gpg-tokens 'never)
-                (let* ((ep (format "Use GPG password tokens in %s?" file))
-                       (gpg-encrypt
-                        (cond
-                         ((eq auth-source-netrc-use-gpg-tokens 'never)
-                          'never)
-                         ((listp auth-source-netrc-use-gpg-tokens)
-                          (let ((check (copy-sequence
-                                        auth-source-netrc-use-gpg-tokens))
-                                item ret)
-                            (while check
-                              (setq item (pop check))
-                              (when (or (eq (car item) t)
-                                        (string-match (car item) file))
-                                (setq ret (cdr item))
-                                (setq check nil)))))
-                         (t 'never)))
-                       (plain (read-passwd prompt)))
-                  ;; ask if we don't know what to do (in which case
-                  ;; auth-source-netrc-use-gpg-tokens must be a list)
-                  (unless gpg-encrypt
-                    (setq gpg-encrypt (if (y-or-n-p ep) 'gpg 'never))
-                    ;; TODO: save the defcustom now? or ask?
-                    (setq auth-source-netrc-use-gpg-tokens
-                          (cons `(,file ,gpg-encrypt)
-                                auth-source-netrc-use-gpg-tokens)))
-                  (if (eq gpg-encrypt 'gpg)
-                      (auth-source-epa-make-gpg-token plain file)
-                    plain)))
-               ((null data)
-                (when default
-                  (setq prompt
-                        (if (string-match ": *\\'" prompt)
-                            (concat (substring prompt 0 (match-beginning 0))
-                                    " (default " default "): ")
-                          (concat prompt "(default " default ") "))))
-                (read-string prompt nil nil default))
-               (t (or data default))))
+        (setq data (or data
+                       (if (eq r 'secret)
+                           ;; Special case prompt for passwords.
+                           ;; TODO: make the default (setq auth-source-netrc-use-gpg-tokens `((,(if (boundp 'epa-file-auto-mode-alist-entry) (car (symbol-value 'epa-file-auto-mode-alist-entry)) "\\.gpg\\'") nil) (t gpg)))
+                           ;; TODO: or maybe leave as (setq auth-source-netrc-use-gpg-tokens 'never)
+                           (let* ((ep (format "Use GPG password tokens in %s?" file))
+                                  (gpg-encrypt
+                                   (cond
+                                    ((eq auth-source-netrc-use-gpg-tokens 'never)
+                                     'never)
+                                    ((listp auth-source-netrc-use-gpg-tokens)
+                                     (let ((check (copy-sequence
+                                                   auth-source-netrc-use-gpg-tokens))
+                                           item ret)
+                                       (while check
+                                         (setq item (pop check))
+                                         (when (or (eq (car item) t)
+                                                   (string-match (car item) file))
+                                           (setq ret (cdr item))
+                                           (setq check nil)))))
+                                    (t 'never)))
+                                  (plain (or (eval default) (read-passwd prompt))))
+                             ;; ask if we don't know what to do (in which case
+                             ;; auth-source-netrc-use-gpg-tokens must be a list)
+                             (unless gpg-encrypt
+                               (setq gpg-encrypt (if (y-or-n-p ep) 'gpg 'never))
+                               ;; TODO: save the defcustom now? or ask?
+                               (setq auth-source-netrc-use-gpg-tokens
+                                     (cons `(,file ,gpg-encrypt)
+                                           auth-source-netrc-use-gpg-tokens)))
+                             (if (eq gpg-encrypt 'gpg)
+                                 (auth-source-epa-make-gpg-token plain file)
+                               plain))
+                         (if (stringp default)
+                             (read-string (if (string-match ": *\\'" prompt)
+                                              (concat (substring prompt 0 (match-beginning 0))
+                                                      " (default " default "): ")
+                                            (concat prompt "(default " default ") "))
+                                          nil nil default)
+                           (eval default)))))
 
         (when data
           (setq artificial (plist-put artificial
@@ -1302,7 +1319,7 @@ See `auth-source-search' for details on SPEC."
                                      (secret "password")
                                      (port   "port") ; redundant but clearer
                                      (t (symbol-name r)))
-                                   (if (string-match "[\" ]" data)
+                                   (if (string-match "[\"# ]" data)
                                        (format "%S" data)
                                      data)))))
             (setq add (concat add (funcall printer)))))))
@@ -1393,16 +1410,16 @@ Respects `auth-source-save-behavior'.  Uses
                file)
               (message "Saved new authentication information to %s" file)
               nil))))
-      (aput 'auth-source-netrc-cache key "ran"))))
+      (auth-source--aput auth-source-netrc-cache key "ran"))))
 
 ;;; Backend specific parsing: Secrets API backend
 
-;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :create t))
-;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :delete t))
-;;; (let ((auth-sources '(default))) (auth-source-search :max 1))
-;;; (let ((auth-sources '(default))) (auth-source-search))
-;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1))
-;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1 :signon_realm "https://git.gnus.org/Git"))
+;; (let ((auth-sources '(default))) (auth-source-search :max 1 :create t))
+;; (let ((auth-sources '(default))) (auth-source-search :max 1 :delete t))
+;; (let ((auth-sources '(default))) (auth-source-search :max 1))
+;; (let ((auth-sources '(default))) (auth-source-search))
+;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1))
+;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1 :signon_realm "https://git.gnus.org/Git"))
 
 (defun* auth-source-secrets-search (&rest
                                     spec
@@ -1618,7 +1635,7 @@ authentication tokens:
                           ;; just the value otherwise
                           (t (symbol-value br)))))
           (when br-choice
-            (aput 'valist br br-choice)))))
+            (auth-source--aput valist br br-choice)))))
 
     ;; for extra required elements, see if the spec includes a value for them
     (dolist (er create-extra)
@@ -1627,17 +1644,18 @@ authentication tokens:
                         collect (nth i spec))))
         (dolist (k keys)
           (when (equal (symbol-name k) name)
-            (aput 'valist er (plist-get spec k))))))
+            (auth-source--aput valist er (plist-get spec k))))))
 
     ;; for each required element
     (dolist (r required)
-      (let* ((data (aget valist r))
+      (let* ((data (auth-source--aget valist r))
              ;; take the first element if the data is a list
              (data (or (auth-source-netrc-element-or-first data)
                        (plist-get current-data
                                   (intern (format ":%s" r) obarray))))
              ;; this is the default to be offered
-             (given-default (aget auth-source-creation-defaults r))
+             (given-default (auth-source--aget
+                             auth-source-creation-defaults r))
              ;; the default supplementals are simple:
              ;; for the user, try `given-default' and then (user-login-name);
              ;; otherwise take `given-default'
@@ -1649,22 +1667,22 @@ authentication tokens:
                                   (cons 'user
                                         (or
                                          (auth-source-netrc-element-or-first
-                                          (aget valist 'user))
+                                          (auth-source--aget valist 'user))
                                          (plist-get artificial :user)
                                          "[any user]"))
                                   (cons 'host
                                         (or
                                          (auth-source-netrc-element-or-first
-                                          (aget valist 'host))
+                                          (auth-source--aget valist 'host))
                                          (plist-get artificial :host)
                                          "[any host]"))
                                   (cons 'port
                                         (or
                                          (auth-source-netrc-element-or-first
-                                          (aget valist 'port))
+                                          (auth-source--aget valist 'port))
                                          (plist-get artificial :port)
                                          "[any port]"))))
-             (prompt (or (aget auth-source-creation-prompts r)
+             (prompt (or (auth-source--aget auth-source-creation-prompts r)
                          (case r
                            (secret "%p password for %u@%h: ")
                            (user "%p user name for %h: ")
@@ -1673,25 +1691,22 @@ authentication tokens:
                          (format "Enter %s (%%u@%%h:%%p): " r)))
              (prompt (auth-source-format-prompt
                       prompt
-                      `((?u ,(aget printable-defaults 'user))
-                        (?h ,(aget printable-defaults 'host))
-                        (?p ,(aget printable-defaults 'port))))))
+                      `((?u ,(auth-source--aget printable-defaults 'user))
+                        (?h ,(auth-source--aget printable-defaults 'host))
+                        (?p ,(auth-source--aget printable-defaults 'port))))))
 
         ;; Store the data, prompting for the password if needed.
-        (setq data
-              (cond
-               ((and (null data) (eq r 'secret))
-                ;; Special case prompt for passwords.
-                (read-passwd prompt))
-               ((null data)
-                (when default
-                  (setq prompt
-                        (if (string-match ": *\\'" prompt)
-                            (concat (substring prompt 0 (match-beginning 0))
-                                    " (default " default "): ")
-                          (concat prompt "(default " default ") "))))
-                (read-string prompt nil nil default))
-               (t (or data default))))
+        (setq data (or data
+                       (if (eq r 'secret)
+                           (or (eval default) (read-passwd prompt))
+                         (if (stringp default)
+                             (read-string
+                              (if (string-match ": *\\'" prompt)
+                                  (concat (substring prompt 0 (match-beginning 0))
+                                          " (default " default "): ")
+                                (concat prompt "(default " default ") "))
+                              nil nil default)
+                           (eval default)))))
 
         (when data
           (if (member r base-secret)
@@ -1714,7 +1729,7 @@ authentication tokens:
 
 ;;; older API
 
-;;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" t "tzz")
+;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" t "tzz")
 
 ;; deprecate the old interface
 (make-obsolete 'auth-source-user-or-password
@@ -1795,6 +1810,26 @@ MODE can be \"login\" or \"password\"."
 
     found))
 
+(defun auth-source-user-and-password (host &optional user)
+  (let* ((auth-info (car
+                     (if user
+                         (auth-source-search
+                          :host host
+                          :user "yourusername"
+                          :max 1
+                          :require '(:user :secret)
+                          :create nil)
+                       (auth-source-search
+                        :host host
+                        :max 1
+                        :require '(:user :secret)
+                        :create nil))))
+         (user (plist-get auth-info :user))
+         (password (plist-get auth-info :secret)))
+    (when (functionp password)
+      (setq password (funcall password)))
+    (list user password auth-info)))
+
 (provide 'auth-source)
 
 ;;; auth-source.el ends here