Cache empty auth-source-search result sets.
[gnus] / lisp / auth-source.el
1 ;;; auth-source.el --- authentication sources for Gnus and Emacs
2
3 ;; Copyright (C) 2008-2011 Free Software Foundation, Inc.
4
5 ;; Author: Ted Zlatanov <tzz@lifelogs.com>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;; This is the auth-source.el package.  It lets users tell Gnus how to
26 ;; authenticate in a single place.  Simplicity is the goal.  Instead
27 ;; of providing 5000 options, we'll stick to simple, easy to
28 ;; understand options.
29
30 ;; See the auth.info Info documentation for details.
31
32 ;; TODO:
33
34 ;; - never decode the backend file unless it's necessary
35 ;; - a more generic way to match backends and search backend contents
36 ;; - absorb netrc.el and simplify it
37 ;; - protect passwords better
38 ;; - allow creating and changing netrc lines (not files) e.g. change a password
39
40 ;;; Code:
41
42 (require 'password-cache)
43 (require 'mm-util)
44 (require 'gnus-util)
45 (require 'assoc)
46 (eval-when-compile (require 'cl))
47 (eval-and-compile
48   (or (ignore-errors (require 'eieio))
49       ;; gnus-fallback-lib/ from gnus/lisp/gnus-fallback-lib
50       (ignore-errors
51         (let ((load-path (cons (expand-file-name
52                                 "gnus-fallback-lib/eieio"
53                                 (file-name-directory (locate-library "gnus")))
54                                load-path)))
55           (require 'eieio)))
56       (error
57        "eieio not found in `load-path' or gnus-fallback-lib/ directory.")))
58
59 (autoload 'secrets-create-item "secrets")
60 (autoload 'secrets-delete-item "secrets")
61 (autoload 'secrets-get-alias "secrets")
62 (autoload 'secrets-get-attributes "secrets")
63 (autoload 'secrets-get-secret "secrets")
64 (autoload 'secrets-list-collections "secrets")
65 (autoload 'secrets-search-items "secrets")
66
67 (defvar secrets-enabled)
68
69 (defgroup auth-source nil
70   "Authentication sources."
71   :version "23.1" ;; No Gnus
72   :group 'gnus)
73
74 ;;;###autoload
75 (defcustom auth-source-cache-expiry 7200
76   "How many seconds passwords are cached, or nil to disable
77 expiring.  Overrides `password-cache-expiry' through a
78 let-binding."
79   :group 'auth-source
80   :type '(choice (const :tag "Never" nil)
81                  (const :tag "All Day" 86400)
82                  (const :tag "2 Hours" 7200)
83                  (const :tag "30 Minutes" 1800)
84                  (integer :tag "Seconds")))
85
86 (defclass auth-source-backend ()
87   ((type :initarg :type
88          :initform 'netrc
89          :type symbol
90          :custom symbol
91          :documentation "The backend type.")
92    (source :initarg :source
93            :type string
94            :custom string
95            :documentation "The backend source.")
96    (host :initarg :host
97          :initform t
98          :type t
99          :custom string
100          :documentation "The backend host.")
101    (user :initarg :user
102          :initform t
103          :type t
104          :custom string
105          :documentation "The backend user.")
106    (port :initarg :port
107          :initform t
108          :type t
109          :custom string
110          :documentation "The backend protocol.")
111    (create-function :initarg :create-function
112                     :initform ignore
113                     :type function
114                     :custom function
115                     :documentation "The create function.")
116    (search-function :initarg :search-function
117                     :initform ignore
118                     :type function
119                     :custom function
120                     :documentation "The search function.")))
121
122 (defcustom auth-source-protocols '((imap "imap" "imaps" "143" "993")
123                                    (pop3 "pop3" "pop" "pop3s" "110" "995")
124                                    (ssh  "ssh" "22")
125                                    (sftp "sftp" "115")
126                                    (smtp "smtp" "25"))
127   "List of authentication protocols and their names"
128
129   :group 'auth-source
130   :version "23.2" ;; No Gnus
131   :type '(repeat :tag "Authentication Protocols"
132                  (cons :tag "Protocol Entry"
133                        (symbol :tag "Protocol")
134                        (repeat :tag "Names"
135                                (string :tag "Name")))))
136
137 ;;; generate all the protocols in a format Customize can use
138 ;;; TODO: generate on the fly from auth-source-protocols
139 (defconst auth-source-protocols-customize
140   (mapcar (lambda (a)
141             (let ((p (car-safe a)))
142               (list 'const
143                     :tag (upcase (symbol-name p))
144                     p)))
145           auth-source-protocols))
146
147 (defvar auth-source-creation-defaults nil
148   "Defaults for creating token values.  Usually let-bound.")
149
150 (defvar auth-source-creation-prompts nil
151   "Default prompts for token values.  Usually let-bound.")
152
153 (make-obsolete 'auth-source-hide-passwords nil "Emacs 24.1")
154
155 (defcustom auth-source-save-behavior 'ask
156   "If set, auth-source will respect it for save behavior."
157   :group 'auth-source
158   :version "23.2" ;; No Gnus
159   :type `(choice
160           :tag "auth-source new token save behavior"
161           (const :tag "Always save" t)
162           (const :tag "Never save" nil)
163           (const :tag "Ask" ask)))
164
165 (defvar auth-source-magic "auth-source-magic ")
166
167 (defcustom auth-source-do-cache t
168   "Whether auth-source should cache information with `password-cache'."
169   :group 'auth-source
170   :version "23.2" ;; No Gnus
171   :type `boolean)
172
173 (defcustom auth-source-debug nil
174   "Whether auth-source should log debug messages.
175
176 If the value is nil, debug messages are not logged.
177
178 If the value is t, debug messages are logged with `message'.  In
179 that case, your authentication data will be in the clear (except
180 for passwords).
181
182 If the value is a function, debug messages are logged by calling
183  that function using the same arguments as `message'."
184   :group 'auth-source
185   :version "23.2" ;; No Gnus
186   :type `(choice
187           :tag "auth-source debugging mode"
188           (const :tag "Log using `message' to the *Messages* buffer" t)
189           (const :tag "Log all trivia with `message' to the *Messages* buffer"
190                  trivia)
191           (function :tag "Function that takes arguments like `message'")
192           (const :tag "Don't log anything" nil)))
193
194 (defcustom auth-sources '("~/.authinfo.gpg" "~/.authinfo" "~/.netrc")
195   "List of authentication sources.
196
197 The default will get login and password information from
198 \"~/.authinfo.gpg\", which you should set up with the EPA/EPG
199 packages to be encrypted.  If that file doesn't exist, it will
200 try the unencrypted version \"~/.authinfo\" and the famous
201 \"~/.netrc\" file.
202
203 See the auth.info manual for details.
204
205 Each entry is the authentication type with optional properties.
206
207 It's best to customize this with `M-x customize-variable' because the choices
208 can get pretty complex."
209   :group 'auth-source
210   :version "24.1" ;; No Gnus
211   :type `(repeat :tag "Authentication Sources"
212                  (choice
213                   (string :tag "Just a file")
214                   (const :tag "Default Secrets API Collection" 'default)
215                   (const :tag "Login Secrets API Collection" "secrets:Login")
216                   (const :tag "Temp Secrets API Collection" "secrets:session")
217                   (list :tag "Source definition"
218                         (const :format "" :value :source)
219                         (choice :tag "Authentication backend choice"
220                                 (string :tag "Authentication Source (file)")
221                                 (list
222                                  :tag "Secret Service API/KWallet/GNOME Keyring"
223                                  (const :format "" :value :secrets)
224                                  (choice :tag "Collection to use"
225                                          (string :tag "Collection name")
226                                          (const :tag "Default" 'default)
227                                          (const :tag "Login" "Login")
228                                          (const
229                                           :tag "Temporary" "session"))))
230                         (repeat :tag "Extra Parameters" :inline t
231                                 (choice :tag "Extra parameter"
232                                         (list
233                                          :tag "Host"
234                                          (const :format "" :value :host)
235                                          (choice :tag "Host (machine) choice"
236                                                  (const :tag "Any" t)
237                                                  (regexp
238                                                   :tag "Regular expression")))
239                                         (list
240                                          :tag "Protocol"
241                                          (const :format "" :value :port)
242                                          (choice
243                                           :tag "Protocol"
244                                           (const :tag "Any" t)
245                                           ,@auth-source-protocols-customize))
246                                         (list :tag "User" :inline t
247                                               (const :format "" :value :user)
248                                               (choice :tag "Personality/Username"
249                                                       (const :tag "Any" t)
250                                                       (string :tag "Name")))))))))
251
252 (defcustom auth-source-gpg-encrypt-to t
253   "List of recipient keys that `authinfo.gpg' encrypted to.
254 If the value is not a list, symmetric encryption will be used."
255   :group 'auth-source
256   :version "24.1" ;; No Gnus
257   :type '(choice (const :tag "Symmetric encryption" t)
258                  (repeat :tag "Recipient public keys"
259                          (string :tag "Recipient public key"))))
260
261 ;; temp for debugging
262 ;; (unintern 'auth-source-protocols)
263 ;; (unintern 'auth-sources)
264 ;; (customize-variable 'auth-sources)
265 ;; (setq auth-sources nil)
266 ;; (format "%S" auth-sources)
267 ;; (customize-variable 'auth-source-protocols)
268 ;; (setq auth-source-protocols nil)
269 ;; (format "%S" auth-source-protocols)
270 ;; (auth-source-pick nil :host "a" :port 'imap)
271 ;; (auth-source-user-or-password "login" "imap.myhost.com" 'imap)
272 ;; (auth-source-user-or-password "password" "imap.myhost.com" 'imap)
273 ;; (auth-source-user-or-password-imap "login" "imap.myhost.com")
274 ;; (auth-source-user-or-password-imap "password" "imap.myhost.com")
275 ;; (auth-source-protocol-defaults 'imap)
276
277 ;; (let ((auth-source-debug 'debug)) (auth-source-do-debug "hello"))
278 ;; (let ((auth-source-debug t)) (auth-source-do-debug "hello"))
279 ;; (let ((auth-source-debug nil)) (auth-source-do-debug "hello"))
280 (defun auth-source-do-debug (&rest msg)
281   (when auth-source-debug
282     (apply 'auth-source-do-warn msg)))
283
284 (defun auth-source-do-trivia (&rest msg)
285   (when (or (eq auth-source-debug 'trivia)
286             (functionp auth-source-debug))
287     (apply 'auth-source-do-warn msg)))
288
289 (defun auth-source-do-warn (&rest msg)
290   (apply
291     ;; set logger to either the function in auth-source-debug or 'message
292     ;; note that it will be 'message if auth-source-debug is nil
293    (if (functionp auth-source-debug)
294        auth-source-debug
295      'message)
296    msg))
297
298
299 ;; (auth-source-pick nil :host "any" :port 'imap :user "joe")
300 ;; (auth-source-pick t :host "any" :port 'imap :user "joe")
301 ;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe")
302 ;;                   (:source (:secrets "session") :host t :port t :user "joe")
303 ;;                   (:source (:secrets "Login") :host t :port t)
304 ;;                   (:source "~/.authinfo.gpg" :host t :port t)))
305
306 ;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe")
307 ;;                   (:source (:secrets "session") :host t :port t :user "joe")
308 ;;                   (:source (:secrets "Login") :host t :port t)
309 ;;                   ))
310
311 ;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :port t)))
312
313 ;; (auth-source-backend-parse "myfile.gpg")
314 ;; (auth-source-backend-parse 'default)
315 ;; (auth-source-backend-parse "secrets:Login")
316
317 (defun auth-source-backend-parse (entry)
318   "Creates an auth-source-backend from an ENTRY in `auth-sources'."
319   (auth-source-backend-parse-parameters
320    entry
321    (cond
322     ;; take 'default and recurse to get it as a Secrets API default collection
323     ;; matching any user, host, and protocol
324     ((eq entry 'default)
325      (auth-source-backend-parse '(:source (:secrets default))))
326     ;; take secrets:XYZ and recurse to get it as Secrets API collection "XYZ"
327     ;; matching any user, host, and protocol
328     ((and (stringp entry) (string-match "^secrets:\\(.+\\)" entry))
329      (auth-source-backend-parse `(:source (:secrets ,(match-string 1 entry)))))
330     ;; take just a file name and recurse to get it as a netrc file
331     ;; matching any user, host, and protocol
332     ((stringp entry)
333      (auth-source-backend-parse `(:source ,entry)))
334
335     ;; a file name with parameters
336     ((stringp (plist-get entry :source))
337      (auth-source-backend
338       (plist-get entry :source)
339       :source (plist-get entry :source)
340       :type 'netrc
341       :search-function 'auth-source-netrc-search
342       :create-function 'auth-source-netrc-create))
343
344     ;; the Secrets API.  We require the package, in order to have a
345     ;; defined value for `secrets-enabled'.
346     ((and
347       (not (null (plist-get entry :source))) ; the source must not be nil
348       (listp (plist-get entry :source))      ; and it must be a list
349       (require 'secrets nil t)               ; and we must load the Secrets API
350       secrets-enabled)                       ; and that API must be enabled
351
352      ;; the source is either the :secrets key in ENTRY or
353      ;; if that's missing or nil, it's "session"
354      (let ((source (or (plist-get (plist-get entry :source) :secrets)
355                        "session")))
356
357        ;; if the source is a symbol, we look for the alias named so,
358        ;; and if that alias is missing, we use "Login"
359        (when (symbolp source)
360          (setq source (or (secrets-get-alias (symbol-name source))
361                           "Login")))
362
363        (if (featurep 'secrets)
364            (auth-source-backend
365             (format "Secrets API (%s)" source)
366             :source source
367             :type 'secrets
368             :search-function 'auth-source-secrets-search
369             :create-function 'auth-source-secrets-create)
370          (auth-source-do-warn
371           "auth-source-backend-parse: no Secrets API, ignoring spec: %S" entry)
372          (auth-source-backend
373           (format "Ignored Secrets API (%s)" source)
374           :source ""
375           :type 'ignore))))
376
377     ;; none of them
378     (t
379      (auth-source-do-warn
380       "auth-source-backend-parse: invalid backend spec: %S" entry)
381      (auth-source-backend
382       "Empty"
383       :source ""
384       :type 'ignore)))))
385
386 (defun auth-source-backend-parse-parameters (entry backend)
387   "Fills in the extra auth-source-backend parameters of ENTRY.
388 Using the plist ENTRY, get the :host, :port, and :user search
389 parameters."
390   (let ((entry (if (stringp entry)
391                    nil
392                  entry))
393         val)
394     (when (setq val (plist-get entry :host))
395       (oset backend host val))
396     (when (setq val (plist-get entry :user))
397       (oset backend user val))
398     (when (setq val (plist-get entry :port))
399       (oset backend port val)))
400   backend)
401
402 ;; (mapcar 'auth-source-backend-parse auth-sources)
403
404 (defun* auth-source-search (&rest spec
405                                   &key type max host user port secret
406                                   create delete
407                                   &allow-other-keys)
408   "Search or modify authentication backends according to SPEC.
409
410 This function parses `auth-sources' for matches of the SPEC
411 plist.  It can optionally create or update an authentication
412 token if requested.  A token is just a standard Emacs property
413 list with a :secret property that can be a function; all the
414 other properties will always hold scalar values.
415
416 Typically the :secret property, if present, contains a password.
417
418 Common search keys are :max, :host, :port, and :user.  In
419 addition, :create specifies how tokens will be or created.
420 Finally, :type can specify which backend types you want to check.
421
422 A string value is always matched literally.  A symbol is matched
423 as its string value, literally.  All the SPEC values can be
424 single values (symbol or string) or lists thereof (in which case
425 any of the search terms matches).
426
427 :create t means to create a token if possible.
428
429 A new token will be created if no matching tokens were found.
430 The new token will have only the keys the backend requires.  For
431 the netrc backend, for instance, that's the user, host, and
432 port keys.
433
434 Here's an example:
435
436 \(let ((auth-source-creation-defaults '((user . \"defaultUser\")
437                                         (A    . \"default A\"))))
438   (auth-source-search :host \"mine\" :type 'netrc :max 1
439                       :P \"pppp\" :Q \"qqqq\"
440                       :create t))
441
442 which says:
443
444 \"Search for any entry matching host 'mine' in backends of type
445  'netrc', maximum one result.
446
447  Create a new entry if you found none.  The netrc backend will
448  automatically require host, user, and port.  The host will be
449  'mine'.  We prompt for the user with default 'defaultUser' and
450  for the port without a default.  We will not prompt for A, Q,
451  or P.  The resulting token will only have keys user, host, and
452  port.\"
453
454 :create '(A B C) also means to create a token if possible.
455
456 The behavior is like :create t but if the list contains any
457 parameter, that parameter will be required in the resulting
458 token.  The value for that parameter will be obtained from the
459 search parameters or from user input.  If any queries are needed,
460 the alist `auth-source-creation-defaults' will be checked for the
461 default value.  If the user, host, or port are missing, the alist
462 `auth-source-creation-prompts' will be used to look up the
463 prompts IN THAT ORDER (so the 'user prompt will be queried first,
464 then 'host, then 'port, and finally 'secret).  Each prompt string
465 can use %u, %h, and %p to show the user, host, and port.
466
467 Here's an example:
468
469 \(let ((auth-source-creation-defaults '((user . \"defaultUser\")
470                                         (A    . \"default A\")))
471        (auth-source-creation-prompts
472         '((password . \"Enter IMAP password for %h:%p: \"))))
473   (auth-source-search :host '(\"nonesuch\" \"twosuch\") :type 'netrc :max 1
474                       :P \"pppp\" :Q \"qqqq\"
475                       :create '(A B Q)))
476
477 which says:
478
479 \"Search for any entry matching host 'nonesuch'
480  or 'twosuch' in backends of type 'netrc', maximum one result.
481
482  Create a new entry if you found none.  The netrc backend will
483  automatically require host, user, and port.  The host will be
484  'nonesuch' and Q will be 'qqqq'.  We prompt for the password
485  with the shown prompt.  We will not prompt for Q.  The resulting
486  token will have keys user, host, port, A, B, and Q.  It will not
487  have P with any value, even though P is used in the search to
488  find only entries that have P set to 'pppp'.\"
489
490 When multiple values are specified in the search parameter, the
491 user is prompted for which one.  So :host (X Y Z) would ask the
492 user to choose between X, Y, and Z.
493
494 This creation can fail if the search was not specific enough to
495 create a new token (it's up to the backend to decide that).  You
496 should `catch' the backend-specific error as usual.  Some
497 backends (netrc, at least) will prompt the user rather than throw
498 an error.
499
500 :delete t means to delete any found entries.  nil by default.
501 Use `auth-source-delete' in ELisp code instead of calling
502 `auth-source-search' directly with this parameter.
503
504 :type (X Y Z) will check only those backend types.  'netrc and
505 'secrets are the only ones supported right now.
506
507 :max N means to try to return at most N items (defaults to 1).
508 When 0 the function will return just t or nil to indicate if any
509 matches were found.  More than N items may be returned, depending
510 on the search and the backend.
511
512 :host (X Y Z) means to match only hosts X, Y, or Z according to
513 the match rules above.  Defaults to t.
514
515 :user (X Y Z) means to match only users X, Y, or Z according to
516 the match rules above.  Defaults to t.
517
518 :port (P Q R) means to match only protocols P, Q, or R.
519 Defaults to t.
520
521 :K (V1 V2 V3) for any other key K will match values V1, V2, or
522 V3 (note the match rules above).
523
524 The return value is a list with at most :max tokens.  Each token
525 is a plist with keys :backend :host :port :user, plus any other
526 keys provided by the backend (notably :secret).  But note the
527 exception for :max 0, which see above.
528
529 The token's :secret key can hold a function.  In that case you
530 must call it to obtain the actual value."
531   (let* ((backends (mapcar 'auth-source-backend-parse auth-sources))
532          (max (or max 1))
533          (ignored-keys '(:create :delete :max))
534          (keys (loop for i below (length spec) by 2
535                      unless (memq (nth i spec) ignored-keys)
536                      collect (nth i spec)))
537          (cached (auth-source-remembered-p spec))
538          ;; note that we may have cached results but found is still nil
539          ;; (there were no results from the search)
540          (found (auth-source-recall spec))
541          filtered-backends accessor-key backend)
542
543     (if (and cached auth-source-do-cache)
544         (auth-source-do-debug
545          "auth-source-search: found %d CACHED results matching %S"
546          (length found) spec)
547
548       (assert
549        (or (eq t create) (listp create)) t
550        "Invalid auth-source :create parameter (must be t or a list): %s %s")
551
552       (setq filtered-backends (copy-sequence backends))
553       (dolist (backend backends)
554         (dolist (key keys)
555           ;; ignore invalid slots
556           (condition-case signal
557               (unless (eval `(auth-source-search-collection
558                               (plist-get spec key)
559                               (oref backend ,key)))
560                 (setq filtered-backends (delq backend filtered-backends))
561                 (return))
562             (invalid-slot-name))))
563
564       (auth-source-do-trivia
565        "auth-source-search: found %d backends matching %S"
566        (length filtered-backends) spec)
567
568       ;; (debug spec "filtered" filtered-backends)
569       ;; First go through all the backends without :create, so we can
570       ;; query them all.
571       (setq found (auth-source-search-backends filtered-backends
572                                                spec
573                                                ;; to exit early
574                                                max
575                                                ;; create and delete
576                                                nil delete))
577
578       (auth-source-do-debug
579        "auth-source-search: found %d results (max %d) matching %S"
580        (length found) max spec)
581
582       ;; If we didn't find anything, then we allow the backend(s) to
583       ;; create the entries.
584       (when (and create
585                  (not found))
586         (setq found (auth-source-search-backends filtered-backends
587                                                  spec
588                                                  ;; to exit early
589                                                  max
590                                                  ;; create and delete
591                                                  create delete))
592         (auth-source-do-warn
593          "auth-source-search: CREATED %d results (max %d) matching %S"
594          (length found) max spec))
595
596       ;; note we remember the lack of result too, if it's applicable
597       (when auth-source-do-cache
598         (auth-source-remember spec found)))
599
600       found))
601
602 (defun auth-source-search-backends (backends spec max create delete)
603   (let (matches)
604     (dolist (backend backends)
605       (when (> max (length matches))   ; when we need more matches...
606         (let ((bmatches (apply
607                          (slot-value backend 'search-function)
608                          :backend backend
609                          ;; note we're overriding whatever the spec
610                          ;; has for :create and :delete
611                          :create create
612                          :delete delete
613                          spec)))
614           (when bmatches
615             (auth-source-do-trivia
616              "auth-source-search-backend: got %d (max %d) in %s:%s matching %S"
617              (length bmatches) max
618              (slot-value backend :type)
619              (slot-value backend :source)
620              spec)
621             (setq matches (append matches bmatches))))))
622     matches))
623
624 ;;; (auth-source-search :max 1)
625 ;;; (funcall (plist-get (nth 0 (auth-source-search :max 1)) :secret))
626 ;;; (auth-source-search :host "nonesuch" :type 'netrc :K 1)
627 ;;; (auth-source-search :host "nonesuch" :type 'secrets)
628
629 (defun* auth-source-delete (&rest spec
630                                   &key delete
631                                   &allow-other-keys)
632   "Delete entries from the authentication backends according to SPEC.
633 Calls `auth-source-search' with the :delete property in SPEC set to t.
634 The backend may not actually delete the entries.
635
636 Returns the deleted entries."
637   (auth-source-search (plist-put spec :delete t)))
638
639 (defun auth-source-search-collection (collection value)
640   "Returns t is VALUE is t or COLLECTION is t or contains VALUE."
641   (when (and (atom collection) (not (eq t collection)))
642     (setq collection (list collection)))
643
644   ;; (debug :collection collection :value value)
645   (or (eq collection t)
646       (eq value t)
647       (equal collection value)
648       (member value collection)))
649
650 (defun auth-source-forget-all-cached ()
651   "Forget all cached auth-source data."
652   (interactive)
653   (loop for sym being the symbols of password-data
654         ;; when the symbol name starts with auth-source-magic
655         when (string-match (concat "^" auth-source-magic)
656                            (symbol-name sym))
657         ;; remove that key
658         do (password-cache-remove (symbol-name sym))))
659
660 (defun auth-source-remember (spec found)
661   "Remember FOUND search results for SPEC."
662   (let ((password-cache-expiry auth-source-cache-expiry))
663     (password-cache-add
664      (concat auth-source-magic (format "%S" spec)) found)))
665
666 (defun auth-source-recall (spec)
667   "Recall FOUND search results for SPEC."
668   (password-read-from-cache
669    (concat auth-source-magic (format "%S" spec))))
670
671 (defun auth-source-remembered-p (spec)
672   "Check if SPEC is remembered."
673   (password-in-cache-p
674    (concat auth-source-magic (format "%S" spec))))
675
676 (defun auth-source-forget (spec)
677   "Forget any cached data matching SPEC exactly.
678
679 This is the same SPEC you passed to `auth-source-search'.
680 Returns t or nil for forgotten or not found."
681   (password-cache-remove (concat auth-source-magic (format "%S" spec))))
682
683 ;;; (loop for sym being the symbols of password-data when (string-match (concat "^" auth-source-magic) (symbol-name sym)) collect (symbol-name sym))
684
685 ;;; (auth-source-remember '(:host "wedd") '(4 5 6))
686 ;;; (auth-source-remembered-p '(:host "wedd"))
687 ;;; (auth-source-remember '(:host "xedd") '(1 2 3))
688 ;;; (auth-source-remembered-p '(:host "xedd"))
689 ;;; (auth-source-remembered-p '(:host "zedd"))
690 ;;; (auth-source-recall '(:host "xedd"))
691 ;;; (auth-source-recall '(:host t))
692 ;;; (auth-source-forget+ :host t)
693
694 (defun* auth-source-forget+ (&rest spec &allow-other-keys)
695   "Forget any cached data matching SPEC.  Returns forgotten count.
696
697 This is not a full `auth-source-search' spec but works similarly.
698 For instance, \(:host \"myhost\" \"yourhost\") would find all the
699 cached data that was found with a search for those two hosts,
700 while \(:host t) would find all host entries."
701   (let ((count 0)
702         sname)
703     (loop for sym being the symbols of password-data
704           ;; when the symbol name matches with auth-source-magic
705           when (and (setq sname (symbol-name sym))
706                     (string-match (concat "^" auth-source-magic "\\(.+\\)")
707                                   sname)
708                     ;; and the spec matches what was stored in the cache
709                     (auth-source-specmatchp spec (read (match-string 1 sname))))
710           ;; remove that key
711           do (progn
712                (password-cache-remove sname)
713                (incf count)))
714     count))
715
716 (defun auth-source-specmatchp (spec stored)
717   (let ((keys (loop for i below (length spec) by 2
718                    collect (nth i spec))))
719     (not (eq
720           (dolist (key keys)
721             (unless (auth-source-search-collection (plist-get stored key)
722                                                    (plist-get spec key))
723               (return 'no)))
724           'no))))
725
726 ;;; Backend specific parsing: netrc/authinfo backend
727
728 (defun auth-source-ensure-strings (values)
729   (unless (listp values)
730     (setq values (list values)))
731   (mapcar (lambda (value)
732             (if (numberp value)
733                 (format "%s" value)
734               value))
735           values))
736
737 (defvar auth-source-netrc-cache nil)
738
739 ;;; (auth-source-netrc-parse "~/.authinfo.gpg")
740 (defun* auth-source-netrc-parse (&rest
741                                  spec
742                                  &key file max host user port delete
743                                  &allow-other-keys)
744   "Parse FILE and return a list of all entries in the file.
745 Note that the MAX parameter is used so we can exit the parse early."
746   (if (listp file)
747       ;; We got already parsed contents; just return it.
748       file
749     (when (file-exists-p file)
750       (setq port (auth-source-ensure-strings port))
751       (with-temp-buffer
752         (let* ((tokens '("machine" "host" "default" "login" "user"
753                          "password" "account" "macdef" "force"
754                          "port" "protocol"))
755                (max (or max 5000))       ; sanity check: default to stop at 5K
756                (modified 0)
757                (cached (cdr-safe (assoc file auth-source-netrc-cache)))
758                (cached-mtime (plist-get cached :mtime))
759                (cached-secrets (plist-get cached :secret))
760                alist elem result pair)
761
762           (if (and (functionp cached-secrets)
763                    (equal cached-mtime
764                           (nth 5 (file-attributes file))))
765               (progn
766                 (auth-source-do-trivia
767                  "auth-source-netrc-parse: using CACHED file data for %s"
768                  file)
769                 (insert (funcall cached-secrets)))
770             (insert-file-contents file)
771             ;; cache all netrc files (used to be just .gpg files)
772             ;; Store the contents of the file heavily encrypted in memory.
773             ;; (note for the irony-impaired: they are just obfuscated)
774             (aput 'auth-source-netrc-cache file
775                   (list :mtime (nth 5 (file-attributes file))
776                         :secret (lexical-let ((v (rot13-string
777                                                   (base64-encode-string
778                                                    (buffer-string)))))
779                                   (lambda () (base64-decode-string
780                                          (rot13-string v)))))))
781           (goto-char (point-min))
782           ;; Go through the file, line by line.
783           (while (and (not (eobp))
784                       (> max 0))
785
786             (narrow-to-region (point) (point-at-eol))
787             ;; For each line, get the tokens and values.
788             (while (not (eobp))
789               (skip-chars-forward "\t ")
790               ;; Skip lines that begin with a "#".
791               (if (eq (char-after) ?#)
792                   (goto-char (point-max))
793                 (unless (eobp)
794                   (setq elem
795                         (if (= (following-char) ?\")
796                             (read (current-buffer))
797                           (buffer-substring
798                            (point) (progn (skip-chars-forward "^\t ")
799                                           (point)))))
800                   (cond
801                    ((equal elem "macdef")
802                     ;; We skip past the macro definition.
803                     (widen)
804                     (while (and (zerop (forward-line 1))
805                                 (looking-at "$")))
806                     (narrow-to-region (point) (point)))
807                    ((member elem tokens)
808                     ;; Tokens that don't have a following value are ignored,
809                     ;; except "default".
810                     (when (and pair (or (cdr pair)
811                                         (equal (car pair) "default")))
812                       (push pair alist))
813                     (setq pair (list elem)))
814                    (t
815                     ;; Values that haven't got a preceding token are ignored.
816                     (when pair
817                       (setcdr pair elem)
818                       (push pair alist)
819                       (setq pair nil)))))))
820
821             (when (and alist
822                        (> max 0)
823                        (auth-source-search-collection
824                         host
825                         (or
826                          (aget alist "machine")
827                          (aget alist "host")
828                          t))
829                        (auth-source-search-collection
830                         user
831                         (or
832                          (aget alist "login")
833                          (aget alist "account")
834                          (aget alist "user")
835                          t))
836                        (auth-source-search-collection
837                         port
838                         (or
839                          (aget alist "port")
840                          (aget alist "protocol")
841                          t)))
842               (decf max)
843               (push (nreverse alist) result)
844               ;; to delete a line, we just comment it out
845               (when delete
846                 (goto-char (point-min))
847                 (insert "#")
848                 (incf modified)))
849             (setq alist nil
850                   pair nil)
851             (widen)
852             (forward-line 1))
853
854           (when (< 0 modified)
855             (when auth-source-gpg-encrypt-to
856               ;; (see bug#7487) making `epa-file-encrypt-to' local to
857               ;; this buffer lets epa-file skip the key selection query
858               ;; (see the `local-variable-p' check in
859               ;; `epa-file-write-region').
860               (unless (local-variable-p 'epa-file-encrypt-to (current-buffer))
861                 (make-local-variable 'epa-file-encrypt-to))
862               (if (listp auth-source-gpg-encrypt-to)
863                   (setq epa-file-encrypt-to auth-source-gpg-encrypt-to)))
864
865             ;; ask AFTER we've successfully opened the file
866             (when (y-or-n-p (format "Save file %s? (%d modifications)"
867                                     file modified))
868               (write-region (point-min) (point-max) file nil 'silent)
869               (auth-source-do-debug
870                "auth-source-netrc-parse: modified %d lines in %s"
871                modified file)))
872
873           (nreverse result))))))
874
875 (defun auth-source-netrc-normalize (alist)
876   (mapcar (lambda (entry)
877             (let (ret item)
878               (while (setq item (pop entry))
879                 (let ((k (car item))
880                       (v (cdr item)))
881
882                   ;; apply key aliases
883                   (setq k (cond ((member k '("machine")) "host")
884                                 ((member k '("login" "account")) "user")
885                                 ((member k '("protocol")) "port")
886                                 ((member k '("password")) "secret")
887                                 (t k)))
888
889                   ;; send back the secret in a function (lexical binding)
890                   (when (equal k "secret")
891                     (setq v (lexical-let ((v v))
892                               (lambda () v))))
893
894                   (setq ret (plist-put ret
895                                        (intern (concat ":" k))
896                                        v))
897                   ))
898               ret))
899           alist))
900
901 ;;; (setq secret (plist-get (nth 0 (auth-source-search :host t :type 'netrc :K 1 :max 1)) :secret))
902 ;;; (funcall secret)
903
904 (defun* auth-source-netrc-search (&rest
905                                   spec
906                                   &key backend create delete
907                                   type max host user port
908                                   &allow-other-keys)
909 "Given a property list SPEC, return search matches from the :backend.
910 See `auth-source-search' for details on SPEC."
911   ;; just in case, check that the type is correct (null or same as the backend)
912   (assert (or (null type) (eq type (oref backend type)))
913           t "Invalid netrc search: %s %s")
914
915   (let ((results (auth-source-netrc-normalize
916                   (auth-source-netrc-parse
917                    :max max
918                    :delete delete
919                    :file (oref backend source)
920                    :host (or host t)
921                    :user (or user t)
922                    :port (or port t)))))
923
924     ;; if we need to create an entry AND none were found to match
925     (when (and create
926                (not results))
927
928       ;; create based on the spec and record the value
929       (setq results (or
930                      ;; if the user did not want to create the entry
931                      ;; in the file, it will be returned
932                      (apply (slot-value backend 'create-function) spec)
933                      ;; if not, we do the search again without :create
934                      ;; to get the updated data.
935
936                      ;; the result will be returned, even if the search fails
937                      (apply 'auth-source-netrc-search
938                             (plist-put spec :create nil)))))
939     results))
940
941 (defun auth-source-netrc-element-or-first (v)
942   (if (listp v)
943       (nth 0 v)
944     v))
945
946 ;; (auth-source-format-prompt "test %u %h %p" '((?u "user") (?h "host")))
947
948 (defun auth-source-format-prompt (prompt alist)
949   "Format PROMPT using %x (for any character x) specifiers in ALIST."
950   (dolist (cell alist)
951     (let ((c (nth 0 cell))
952           (v (nth 1 cell)))
953       (when (and c v)
954         (setq prompt (replace-regexp-in-string (format "%%%c" c) v prompt)))))
955   prompt)
956
957 ;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t)
958 ;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t :create-extra-keys '((A "default A") (B)))
959
960 (defun* auth-source-netrc-create (&rest spec
961                                         &key backend
962                                         secret host user port create
963                                         &allow-other-keys)
964   (let* ((base-required '(host user port secret))
965          ;; we know (because of an assertion in auth-source-search) that the
966          ;; :create parameter is either t or a list (which includes nil)
967          (create-extra (if (eq t create) nil create))
968          (required (append base-required create-extra))
969          (file (oref backend source))
970          (add "")
971          ;; `valist' is an alist
972          valist
973          ;; `artificial' will be returned if no creation is needed
974          artificial)
975
976     ;; only for base required elements (defined as function parameters):
977     ;; fill in the valist with whatever data we may have from the search
978     ;; we complete the first value if it's a list and use the value otherwise
979     (dolist (br base-required)
980       (when (symbol-value br)
981         (let ((br-choice (cond
982                           ;; all-accepting choice (predicate is t)
983                           ((eq t (symbol-value br)) nil)
984                           ;; just the value otherwise
985                           (t (symbol-value br)))))
986           (when br-choice
987             (aput 'valist br br-choice)))))
988
989     ;; for extra required elements, see if the spec includes a value for them
990     (dolist (er create-extra)
991       (let ((name (concat ":" (symbol-name er)))
992             (keys (loop for i below (length spec) by 2
993                         collect (nth i spec))))
994         (dolist (k keys)
995           (when (equal (symbol-name k) name)
996             (aput 'valist er (plist-get spec k))))))
997
998     ;; for each required element
999     (dolist (r required)
1000       (let* ((data (aget valist r))
1001              ;; take the first element if the data is a list
1002              (data (auth-source-netrc-element-or-first data))
1003              ;; this is the default to be offered
1004              (given-default (aget auth-source-creation-defaults r))
1005              ;; the default supplementals are simple: for the user,
1006              ;; try (user-login-name), otherwise take given-default
1007              (default (cond
1008                        ;; don't default the user name
1009                        ;; ((and (not given-default) (eq r 'user))
1010                        ;;  (user-login-name))
1011                        (t given-default)))
1012              (printable-defaults (list
1013                                   (cons 'user
1014                                         (or
1015                                          (auth-source-netrc-element-or-first
1016                                           (aget valist 'user))
1017                                          (plist-get artificial :user)
1018                                          "[any user]"))
1019                                   (cons 'host
1020                                         (or
1021                                          (auth-source-netrc-element-or-first
1022                                           (aget valist 'host))
1023                                          (plist-get artificial :host)
1024                                          "[any host]"))
1025                                   (cons 'port
1026                                         (or
1027                                          (auth-source-netrc-element-or-first
1028                                           (aget valist 'port))
1029                                          (plist-get artificial :port)
1030                                          "[any port]"))))
1031              (prompt (or (aget auth-source-creation-prompts r)
1032                          (case r
1033                            ('secret "%p password for user %u, host %h: ")
1034                            ('user "%p user name: ")
1035                            ('host "%p host name for user %u: ")
1036                            ('port "%p port for user %u and host %h: "))
1037                          (format "Enter %s (%%u@%%h:%%p): " r)))
1038              (prompt (auth-source-format-prompt
1039                       prompt
1040                       `((?u ,(aget printable-defaults 'user))
1041                         (?h ,(aget printable-defaults 'host))
1042                         (?p ,(aget printable-defaults 'port))))))
1043
1044         ;; store the data, prompting for the password if needed
1045         (setq data
1046               (cond
1047                ((and (null data) (eq r 'secret))
1048                 ;; special case prompt for passwords
1049                 (read-passwd prompt))
1050                ((null data)
1051                 (read-string prompt default))
1052                (t (or data default))))
1053
1054         (when data
1055           (setq artificial (plist-put artificial
1056                                       (intern (concat ":" (symbol-name r)))
1057                                       (if (eq r 'secret)
1058                                           (lexical-let ((data data))
1059                                             (lambda () data))
1060                                         data))))
1061
1062         ;; when r is not an empty string...
1063         (when (and (stringp data)
1064                    (< 0 (length data)))
1065           ;; this function is not strictly necessary but I think it
1066           ;; makes the code clearer -tzz
1067           (let ((printer (lambda ()
1068                            ;; append the key (the symbol name of r)
1069                            ;; and the value in r
1070                            (format "%s%s %S"
1071                                    ;; prepend a space
1072                                    (if (zerop (length add)) "" " ")
1073                                    ;; remap auth-source tokens to netrc
1074                                    (case r
1075                                      ('user   "login")
1076                                      ('host   "machine")
1077                                      ('secret "password")
1078                                      ('port   "port") ; redundant but clearer
1079                                      (t (symbol-name r)))
1080                                    ;; the value will be printed in %S format
1081                                    data))))
1082             (setq add (concat add (funcall printer)))))))
1083
1084     (with-temp-buffer
1085       (when (file-exists-p file)
1086         (insert-file-contents file))
1087       (when auth-source-gpg-encrypt-to
1088         ;; (see bug#7487) making `epa-file-encrypt-to' local to
1089         ;; this buffer lets epa-file skip the key selection query
1090         ;; (see the `local-variable-p' check in
1091         ;; `epa-file-write-region').
1092         (unless (local-variable-p 'epa-file-encrypt-to (current-buffer))
1093           (make-local-variable 'epa-file-encrypt-to))
1094         (if (listp auth-source-gpg-encrypt-to)
1095             (setq epa-file-encrypt-to auth-source-gpg-encrypt-to)))
1096       (goto-char (point-max))
1097
1098       ;; ask AFTER we've successfully opened the file
1099       (let ((prompt (format "Save auth info to file %s? %s: "
1100                             file
1101                             "y/n/N/e/?"))
1102             (done (not (eq auth-source-save-behavior 'ask)))
1103             (bufname "*auth-source Help*")
1104             k)
1105         (while (not done)
1106           (message "%s" prompt)
1107           (setq k (read-char))
1108           (case k
1109             (?y (setq done t))
1110             (?? (save-excursion
1111                   (with-output-to-temp-buffer bufname
1112                     (princ
1113                      (concat "(y)es, save\n"
1114                              "(n)o but use the info\n"
1115                              "(N)o and don't ask to save again\n"
1116                              "(e)dit the line\n"
1117                              "(?) for help as you can see.\n"))
1118                   (set-buffer standard-output)
1119                   (help-mode))))
1120             (?n (setq add ""
1121                       done t))
1122             (?N (setq add ""
1123                       done t
1124                       auth-source-save-behavior nil))
1125             (?e (setq add (read-string "Line to add: " add)))
1126             (t nil)))
1127
1128         (when (get-buffer-window bufname)
1129           (delete-window (get-buffer-window bufname)))
1130
1131         ;; make sure the info is not saved
1132         (when (null auth-source-save-behavior)
1133           (setq add ""))
1134
1135         (when (< 0 (length add))
1136           (progn
1137             (unless (bolp)
1138               (insert "\n"))
1139             (insert add "\n")
1140             (write-region (point-min) (point-max) file nil 'silent)
1141             (auth-source-do-warn
1142              "auth-source-netrc-create: wrote 1 new line to %s"
1143              file)
1144             nil))
1145
1146         (when (eq done t)
1147           (list artificial))))))
1148
1149 ;;; Backend specific parsing: Secrets API backend
1150
1151 ;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :create t))
1152 ;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :delete t))
1153 ;;; (let ((auth-sources '(default))) (auth-source-search :max 1))
1154 ;;; (let ((auth-sources '(default))) (auth-source-search))
1155 ;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1))
1156 ;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1 :signon_realm "https://git.gnus.org/Git"))
1157
1158 (defun* auth-source-secrets-search (&rest
1159                                     spec
1160                                     &key backend create delete label
1161                                     type max host user port
1162                                     &allow-other-keys)
1163   "Search the Secrets API; spec is like `auth-source'.
1164
1165 The :label key specifies the item's label.  It is the only key
1166 that can specify a substring.  Any :label value besides a string
1167 will allow any label.
1168
1169 All other search keys must match exactly.  If you need substring
1170 matching, do a wider search and narrow it down yourself.
1171
1172 You'll get back all the properties of the token as a plist.
1173
1174 Here's an example that looks for the first item in the 'Login'
1175 Secrets collection:
1176
1177  \(let ((auth-sources '(\"secrets:Login\")))
1178     (auth-source-search :max 1)
1179
1180 Here's another that looks for the first item in the 'Login'
1181 Secrets collection whose label contains 'gnus':
1182
1183  \(let ((auth-sources '(\"secrets:Login\")))
1184     (auth-source-search :max 1 :label \"gnus\")
1185
1186 And this one looks for the first item in the 'Login' Secrets
1187 collection that's a Google Chrome entry for the git.gnus.org site
1188 authentication tokens:
1189
1190  \(let ((auth-sources '(\"secrets:Login\")))
1191     (auth-source-search :max 1 :signon_realm \"https://git.gnus.org/Git\"))
1192 "
1193
1194   ;; TODO
1195   (assert (not create) nil
1196           "The Secrets API auth-source backend doesn't support creation yet")
1197   ;; TODO
1198   ;; (secrets-delete-item coll elt)
1199   (assert (not delete) nil
1200           "The Secrets API auth-source backend doesn't support deletion yet")
1201
1202   (let* ((coll (oref backend source))
1203          (max (or max 5000))     ; sanity check: default to stop at 5K
1204          (ignored-keys '(:create :delete :max :backend :label))
1205          (search-keys (loop for i below (length spec) by 2
1206                             unless (memq (nth i spec) ignored-keys)
1207                             collect (nth i spec)))
1208          ;; build a search spec without the ignored keys
1209          ;; if a search key is nil or t (match anything), we skip it
1210          (search-spec (apply 'append (mapcar
1211                                       (lambda (k)
1212                                         (if (or (null (plist-get spec k))
1213                                                 (eq t (plist-get spec k)))
1214                                             nil
1215                                           (list k (plist-get spec k))))
1216                               search-keys)))
1217          ;; needed keys (always including host, login, port, and secret)
1218          (returned-keys (mm-delete-duplicates (append
1219                                                '(:host :login :port :secret)
1220                                                search-keys)))
1221          (items (loop for item in (apply 'secrets-search-items coll search-spec)
1222                       unless (and (stringp label)
1223                                   (not (string-match label item)))
1224                       collect item))
1225          ;; TODO: respect max in `secrets-search-items', not after the fact
1226          (items (butlast items (- (length items) max)))
1227          ;; convert the item name to a full plist
1228          (items (mapcar (lambda (item)
1229                           (append
1230                            ;; make an entry for the secret (password) element
1231                            (list
1232                             :secret
1233                             (lexical-let ((v (secrets-get-secret coll item)))
1234                               (lambda () v)))
1235                            ;; rewrite the entry from ((k1 v1) (k2 v2)) to plist
1236                            (apply 'append
1237                                   (mapcar (lambda (entry)
1238                                             (list (car entry) (cdr entry)))
1239                                           (secrets-get-attributes coll item)))))
1240                         items))
1241          ;; ensure each item has each key in `returned-keys'
1242          (items (mapcar (lambda (plist)
1243                           (append
1244                            (apply 'append
1245                                   (mapcar (lambda (req)
1246                                             (if (plist-get plist req)
1247                                                 nil
1248                                               (list req nil)))
1249                                           returned-keys))
1250                            plist))
1251                         items)))
1252     items))
1253
1254 (defun* auth-source-secrets-create (&rest
1255                                     spec
1256                                     &key backend type max host user port
1257                                     &allow-other-keys)
1258   ;; TODO
1259   ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec)
1260   (debug spec))
1261
1262 ;;; older API
1263
1264 ;;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" t "tzz")
1265
1266 ;; deprecate the old interface
1267 (make-obsolete 'auth-source-user-or-password
1268                'auth-source-search "Emacs 24.1")
1269 (make-obsolete 'auth-source-forget-user-or-password
1270                'auth-source-forget "Emacs 24.1")
1271
1272 (defun auth-source-user-or-password
1273   (mode host port &optional username create-missing delete-existing)
1274   "Find MODE (string or list of strings) matching HOST and PORT.
1275
1276 DEPRECATED in favor of `auth-source-search'!
1277
1278 USERNAME is optional and will be used as \"login\" in a search
1279 across the Secret Service API (see secrets.el) if the resulting
1280 items don't have a username.  This means that if you search for
1281 username \"joe\" and it matches an item but the item doesn't have
1282 a :user attribute, the username \"joe\" will be returned.
1283
1284 A non nil DELETE-EXISTING means deleting any matching password
1285 entry in the respective sources.  This is useful only when
1286 CREATE-MISSING is non nil as well; the intended use case is to
1287 remove wrong password entries.
1288
1289 If no matching entry is found, and CREATE-MISSING is non nil,
1290 the password will be retrieved interactively, and it will be
1291 stored in the password database which matches best (see
1292 `auth-sources').
1293
1294 MODE can be \"login\" or \"password\"."
1295   (auth-source-do-debug
1296    "auth-source-user-or-password: DEPRECATED get %s for %s (%s) + user=%s"
1297    mode host port username)
1298
1299   (let* ((listy (listp mode))
1300          (mode (if listy mode (list mode)))
1301          (cname (if username
1302                     (format "%s %s:%s %s" mode host port username)
1303                   (format "%s %s:%s" mode host port)))
1304          (search (list :host host :port port))
1305          (search (if username (append search (list :user username)) search))
1306          (search (if create-missing
1307                      (append search (list :create t))
1308                    search))
1309          (search (if delete-existing
1310                      (append search (list :delete t))
1311                    search))
1312          ;; (found (if (not delete-existing)
1313          ;;            (gethash cname auth-source-cache)
1314          ;;          (remhash cname auth-source-cache)
1315          ;;          nil)))
1316          (found nil))
1317     (if found
1318         (progn
1319           (auth-source-do-debug
1320            "auth-source-user-or-password: DEPRECATED cached %s=%s for %s (%s) + %s"
1321            mode
1322            ;; don't show the password
1323            (if (and (member "password" mode) t)
1324                "SECRET"
1325              found)
1326            host port username)
1327           found)                        ; return the found data
1328       ;; else, if not found, search with a max of 1
1329       (let ((choice (nth 0 (apply 'auth-source-search
1330                                   (append '(:max 1) search)))))
1331         (when choice
1332           (dolist (m mode)
1333             (cond
1334              ((equal "password" m)
1335               (push (if (plist-get choice :secret)
1336                       (funcall (plist-get choice :secret))
1337                     nil) found))
1338              ((equal "login" m)
1339               (push (plist-get choice :user) found)))))
1340         (setq found (nreverse found))
1341         (setq found (if listy found (car-safe found)))))
1342
1343         found))
1344
1345 (provide 'auth-source)
1346
1347 ;;; auth-source.el ends here