Fix typo.
[gnus] / lisp / auth-source.el
index e866f6b..e5a2e31 100644 (file)
@@ -158,7 +158,7 @@ let-binding."
   :version "23.2" ;; No Gnus
   :type `boolean)
 
-(defcustom auth-source-debug t
+(defcustom auth-source-debug nil
   "Whether auth-source should log debug messages.
 
 If the value is nil, debug messages are not logged.
@@ -463,8 +463,8 @@ which says:
  search to find only entries that have P set to 'pppp'.\"
 
 When multiple values are specified in the search parameter, the
-first one is used for creation.  So :host (X Y Z) would create a
-token for host X, for instance.
+user is prompted for which one.  So :host (X Y Z) would ask the
+user to choose between X, Y, and Z.
 
 This creation can fail if the search was not specific enough to
 create a new token (it's up to the backend to decide that).  You
@@ -510,7 +510,7 @@ must call it to obtain the actual value."
                      unless (memq (nth i spec) ignored-keys)
                      collect (nth i spec)))
          (found (auth-source-recall spec))
-         filtered-backends accessor-key found-here goal)
+         filtered-backends accessor-key found-here goal matches backend)
 
     (if (and found auth-source-do-cache)
         (auth-source-do-debug
@@ -539,38 +539,60 @@ must call it to obtain the actual value."
 
       ;; (debug spec "filtered" filtered-backends)
       (setq goal max)
-      (dolist (backend filtered-backends)
-        (setq found-here (apply
-                          (slot-value backend 'search-function)
-                          :backend backend
-                          :create create
-                          :delete delete
-                          spec))
-
-        ;; if max is 0, as soon as we find something, return it
-        (when (and (zerop max) (> 0 (length found-here)))
-          (return t))
-
-        ;; decrement the goal by the number of new results
-        (decf goal (length found-here))
-        ;; and append the new results to the full list
-        (setq found (append found found-here))
-
-        (auth-source-do-debug
-         "auth-source-search: found %d results (max %d/%d) in %S matching %S"
-         (length found-here) max goal backend spec)
-
-        ;; return full list if the goal is 0 or negative
-        (when (zerop (max 0 goal))
-          (return found))
-
-        ;; change the :max parameter in the spec to the goal
-        (setq spec (plist-put spec :max goal)))
-
-      (when (and found auth-source-do-cache)
-        (auth-source-remember spec found)))
-
-      found))
+      ;; First go through all the backends without :create, so we can
+      ;; query them all.
+      (let ((uspec (copy-sequence spec)))
+       (plist-put uspec :create nil)
+       (dolist (backend filtered-backends)
+         (let ((match (apply
+                       (slot-value backend 'search-function)
+                       :backend backend
+                       uspec)))
+           (when match
+             (push (list backend match) matches)))))
+      ;; If we didn't find anything, then we allow the backend(s) to
+      ;; create the entries.
+      (when (and create
+                (not matches))
+       (dolist (backend filtered-backends)
+         (unless matches
+           (let ((match (apply
+                         (slot-value backend 'search-function)
+                         :backend backend
+                         :create create
+                         :delete delete
+                         spec)))
+             (when match
+               (push (list backend match) matches))))))
+
+      (setq backend (caar matches)
+           found-here (cadar matches))
+
+      (block nil
+       ;; if max is 0, as soon as we find something, return it
+       (when (and (zerop max) (> 0 (length found-here)))
+         (return t))
+
+       ;; decrement the goal by the number of new results
+       (decf goal (length found-here))
+       ;; and append the new results to the full list
+       (setq found (append found found-here))
+
+       (auth-source-do-debug
+        "auth-source-search: found %d results (max %d/%d) in %S matching %S"
+        (length found-here) max goal backend spec)
+
+       ;; return full list if the goal is 0 or negative
+       (when (zerop (max 0 goal))
+         (return found))
+
+       ;; change the :max parameter in the spec to the goal
+       (setq spec (plist-put spec :max goal))
+
+       (when (and found auth-source-do-cache)
+         (auth-source-remember spec found))))
+
+    found))
 
 ;;; (auth-source-search :max 1)
 ;;; (funcall (plist-get (nth 0 (auth-source-search :max 1)) :secret))
@@ -668,6 +690,17 @@ while \(:host t) would find all host entries."
 
 ;;; Backend specific parsing: netrc/authinfo backend
 
+(defun auth-source-ensure-strings (values)
+  (unless (listp values)
+    (setq values (list values)))
+  (mapcar (lambda (value)
+           (if (numberp value)
+               (format "%s" value)
+             value))
+         values))
+
+(defvar auth-source-netrc-cache nil)
+
 ;;; (auth-source-netrc-parse "~/.authinfo.gpg")
 (defun* auth-source-netrc-parse (&rest
                                  spec
@@ -679,6 +712,7 @@ Note that the MAX parameter is used so we can exit the parse early."
       ;; We got already parsed contents; just return it.
       file
     (when (file-exists-p file)
+      (setq port (auth-source-ensure-strings port))
       (with-temp-buffer
         (let ((tokens '("machine" "host" "default" "login" "user"
                         "password" "account" "macdef" "force"
@@ -686,7 +720,19 @@ Note that the MAX parameter is used so we can exit the parse early."
               (max (or max 5000))       ; sanity check: default to stop at 5K
               (modified 0)
               alist elem result pair)
-          (insert-file-contents file)
+         (if (and auth-source-netrc-cache
+                  (equal (car auth-source-netrc-cache)
+                         (nth 5 (file-attributes file))))
+             (insert (base64-decode-string
+                      (rot13-string (cdr auth-source-netrc-cache))))
+           (insert-file-contents file)
+           (when (string-match "\\.gpg\\'" file)
+             ;; Store the contents of the file heavily encrypted in memory.
+             (setq auth-source-netrc-cache
+                   (cons (nth 5 (file-attributes file))
+                         (rot13-string
+                          (base64-encode-string
+                           (buffer-string)))))))
           (goto-char (point-min))
           ;; Go through the file, line by line.
           (while (and (not (eobp))
@@ -861,6 +907,7 @@ See `auth-source-search' for details on SPEC."
          (required (append base-required create-extra))
          (file (oref backend source))
          (add "")
+         (show "")
          ;; `valist' is an alist
          valist
          ;; `artificial' will be returned if no creation is needed
@@ -868,12 +915,16 @@ See `auth-source-search' for details on SPEC."
 
     ;; only for base required elements (defined as function parameters):
     ;; fill in the valist with whatever data we may have from the search
-    ;; we take the first value if it's a list, the whole value otherwise
+    ;; we complete the first value if it's a list and use the value otherwise
     (dolist (br base-required)
       (when (symbol-value br)
-        (aput 'valist br (if (listp (symbol-value br))
-                             (nth 0 (symbol-value br))
-                           (symbol-value br)))))
+        (let ((br-choice (cond
+                          ;; all-accepting choice (predicate is t)
+                          ((eq t (symbol-value br)) nil)
+                          ;; just the value otherwise
+                          (t (symbol-value br)))))
+          (when br-choice
+            (aput 'valist br br-choice)))))
 
     ;; for extra required elements, see if the spec includes a value for them
     (dolist (er create-extra)
@@ -904,6 +955,8 @@ See `auth-source-search' for details on SPEC."
              (user-value (aget valist 'user))
              (host-value (aget valist 'host))
              (port-value (aget valist 'port))
+             ;; note this handles lists by just printing them
+             ;; later we allow the user to use completing-read to pick
              (info-so-far (concat (if user-value
                                       (format "%s@" user-value)
                                     "[USER?]")
@@ -931,6 +984,16 @@ See `auth-source-search' for details on SPEC."
                        (format "Enter %s for %s%s: "
                                r info-so-far default-string)
                        nil nil default))
+                     ((listp data)
+                      (completing-read
+                       (format "Enter %s for %s (TAB to see the choices): "
+                               r info-so-far)
+                       data
+                       nil              ; no predicate
+                       t                ; require a match
+                       ;; note the default is nil, but if the user
+                       ;; hits RET we'll get "", which is handled OK later
+                       nil))
                      (t data))))
 
         (when data
@@ -944,20 +1007,25 @@ See `auth-source-search' for details on SPEC."
         ;; when r is not an empty string...
         (when (and (stringp data)
                    (< 0 (length data)))
-          ;; append the key (the symbol name of r) and the value in r
-          (setq add (concat add
-                            (format "%s%s %S"
-                                    ;; prepend a space
-                                    (if (zerop (length add)) "" " ")
-                                    ;; remap auth-source tokens to netrc
-                                    (case r
+          (let ((printer (lambda (hide)
+                           ;; append the key (the symbol name of r)
+                           ;; and the value in r
+                           (format "%s%s %S"
+                                   ;; prepend a space
+                                   (if (zerop (length add)) "" " ")
+                                   ;; remap auth-source tokens to netrc
+                                   (case r
                                      ('user "login")
                                      ('host "machine")
                                      ('secret "password")
                                      ('port "port") ; redundant but clearer
                                      (t (symbol-name r)))
-                                    ;; the value will be printed in %S format
-                                    data))))))
+                                   ;; the value will be printed in %S format
+                                   (if (and hide (eq r 'secret))
+                                       "HIDDEN_SECRET"
+                                     data)))))
+            (setq add (concat add (funcall printer nil)))
+            (setq show (concat show (funcall printer t)))))))
 
     (with-temp-buffer
       (when (file-exists-p file)
@@ -974,7 +1042,7 @@ See `auth-source-search' for details on SPEC."
       (goto-char (point-max))
 
       ;; ask AFTER we've successfully opened the file
-      (if (y-or-n-p (format "Add to file %s: line [%s]" file add))
+      (if (y-or-n-p (format "Add to file %s: line [%s]" file show))
           (progn
             (unless (bolp)
               (insert "\n"))