(dired-show-file-type): Doc fixup (bug#8818).
[bpt/emacs.git] / lisp / gnus / auth-source.el
CommitLineData
8f7abae3
MB
1;;; auth-source.el --- authentication sources for Gnus and Emacs
2
73b0cd50 3;; Copyright (C) 2008-2011 Free Software Foundation, Inc.
8f7abae3
MB
4
5;; Author: Ted Zlatanov <tzz@lifelogs.com>
6;; Keywords: news
7
8;; This file is part of GNU Emacs.
9
5e809f55 10;; GNU Emacs is free software: you can redistribute it and/or modify
8f7abae3 11;; it under the terms of the GNU General Public License as published by
5e809f55
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
8f7abae3
MB
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
5e809f55 17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8f7abae3
MB
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
5e809f55 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
8f7abae3
MB
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.
d55fe5bb 29
554a69b8 30;; See the auth.info Info documentation for details.
4079589f 31
cbabe91f
TZ
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
8f7abae3
MB
40;;; Code:
41
b8e0f0cd 42(require 'password-cache)
d638ac9e 43(require 'mm-util)
e952b711 44(require 'gnus-util)
b8e0f0cd 45(require 'assoc)
936d08bb 46
8f7abae3 47(eval-when-compile (require 'cl))
f3b54b0e 48(require 'eieio)
b8e0f0cd 49
0e4966fb
MA
50(autoload 'secrets-create-item "secrets")
51(autoload 'secrets-delete-item "secrets")
ec7995fa 52(autoload 'secrets-get-alias "secrets")
b8e0f0cd 53(autoload 'secrets-get-attributes "secrets")
fb178e4c 54(autoload 'secrets-get-secret "secrets")
0e4966fb
MA
55(autoload 'secrets-list-collections "secrets")
56(autoload 'secrets-search-items "secrets")
8f7abae3 57
4248cca2
TZ
58(autoload 'rfc2104-hash "rfc2104")
59
8977de27
DU
60(autoload 'plstore-open "plstore")
61(autoload 'plstore-find "plstore")
62(autoload 'plstore-put "plstore")
f3078a00 63(autoload 'plstore-delete "plstore")
8977de27 64(autoload 'plstore-save "plstore")
c302199d 65(autoload 'plstore-get-file "plstore")
8977de27 66
b09c3fe0
G
67(autoload 'epa-passphrase-callback-function "epa")
68
69(autoload 'epg-context-operation "epg")
70(autoload 'epg-make-context "epg")
71(autoload 'epg-context-set-passphrase-callback "epg")
72(autoload 'epg-decrypt-string "epg")
73(autoload 'epg-context-set-armor "epg")
74(autoload 'epg-encrypt-string "epg")
75
b8e0f0cd
G
76(defvar secrets-enabled)
77
8f7abae3
MB
78(defgroup auth-source nil
79 "Authentication sources."
9b3ebcb6 80 :version "23.1" ;; No Gnus
8f7abae3
MB
81 :group 'gnus)
82
584c9d3f
G
83;;;###autoload
84(defcustom auth-source-cache-expiry 7200
85 "How many seconds passwords are cached, or nil to disable
86expiring. Overrides `password-cache-expiry' through a
87let-binding."
88 :group 'auth-source
89 :type '(choice (const :tag "Never" nil)
90 (const :tag "All Day" 86400)
91 (const :tag "2 Hours" 7200)
92 (const :tag "30 Minutes" 1800)
93 (integer :tag "Seconds")))
94
b8e0f0cd
G
95(defclass auth-source-backend ()
96 ((type :initarg :type
97 :initform 'netrc
98 :type symbol
99 :custom symbol
100 :documentation "The backend type.")
101 (source :initarg :source
102 :type string
103 :custom string
104 :documentation "The backend source.")
105 (host :initarg :host
106 :initform t
107 :type t
108 :custom string
109 :documentation "The backend host.")
110 (user :initarg :user
111 :initform t
112 :type t
113 :custom string
114 :documentation "The backend user.")
35123c04
TZ
115 (port :initarg :port
116 :initform t
117 :type t
118 :custom string
119 :documentation "The backend protocol.")
65afde5c 120 (data :initarg :data
b09c3fe0
G
121 :initform nil
122 :documentation "Internal backend data.")
b8e0f0cd
G
123 (create-function :initarg :create-function
124 :initform ignore
125 :type function
126 :custom function
127 :documentation "The create function.")
128 (search-function :initarg :search-function
129 :initform ignore
130 :type function
131 :custom function
132 :documentation "The search function.")))
133
9b3ebcb6 134(defcustom auth-source-protocols '((imap "imap" "imaps" "143" "993")
cbabe91f
TZ
135 (pop3 "pop3" "pop" "pop3s" "110" "995")
136 (ssh "ssh" "22")
137 (sftp "sftp" "115")
138 (smtp "smtp" "25"))
9b3ebcb6
MB
139 "List of authentication protocols and their names"
140
141 :group 'auth-source
ec7995fa 142 :version "23.2" ;; No Gnus
9b3ebcb6 143 :type '(repeat :tag "Authentication Protocols"
cbabe91f
TZ
144 (cons :tag "Protocol Entry"
145 (symbol :tag "Protocol")
146 (repeat :tag "Names"
147 (string :tag "Name")))))
9b3ebcb6
MB
148
149;;; generate all the protocols in a format Customize can use
fb178e4c 150;;; TODO: generate on the fly from auth-source-protocols
9b3ebcb6
MB
151(defconst auth-source-protocols-customize
152 (mapcar (lambda (a)
cbabe91f
TZ
153 (let ((p (car-safe a)))
154 (list 'const
155 :tag (upcase (symbol-name p))
156 p)))
157 auth-source-protocols))
9b3ebcb6 158
b8e0f0cd
G
159(defvar auth-source-creation-defaults nil
160 "Defaults for creating token values. Usually let-bound.")
161
003522ce
G
162(defvar auth-source-creation-prompts nil
163 "Default prompts for token values. Usually let-bound.")
164
b8e0f0cd
G
165(make-obsolete 'auth-source-hide-passwords nil "Emacs 24.1")
166
003522ce
G
167(defcustom auth-source-save-behavior 'ask
168 "If set, auth-source will respect it for save behavior."
169 :group 'auth-source
170 :version "23.2" ;; No Gnus
171 :type `(choice
172 :tag "auth-source new token save behavior"
173 (const :tag "Always save" t)
174 (const :tag "Never save" nil)
175 (const :tag "Ask" ask)))
176
61e6a0ac
TZ
177;; 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\\'") never) (t gpg)))
178;; TODO: or maybe leave as (setq auth-source-netrc-use-gpg-tokens 'never)
179
180(defcustom auth-source-netrc-use-gpg-tokens 'never
181 "Set this to tell auth-source when to create GPG password
b09c3fe0
G
182tokens in netrc files. It's either an alist or `never'.
183Note that if EPA/EPG is not available, this should NOT be used."
2b8c5660
TZ
184 :group 'auth-source
185 :version "23.2" ;; No Gnus
186 :type `(choice
61e6a0ac
TZ
187 (const :tag "Always use GPG password tokens" (t gpg))
188 (const :tag "Never use GPG password tokens" never)
189 (repeat :tag "Use a lookup list"
190 (list
191 (choice :tag "Matcher"
192 (const :tag "Match anything" t)
193 (const :tag "The EPA encrypted file extensions"
194 ,(if (boundp 'epa-file-auto-mode-alist-entry)
195 (car (symbol-value
196 'epa-file-auto-mode-alist-entry))
197 "\\.gpg\\'"))
198 (regexp :tag "Regular expression"))
199 (choice :tag "What to do"
200 (const :tag "Save GPG-encrypted password tokens" gpg)
201 (const :tag "Don't encrypt tokens" never))))))
2b8c5660 202
b8e0f0cd 203(defvar auth-source-magic "auth-source-magic ")
ed778fad
MB
204
205(defcustom auth-source-do-cache t
b8e0f0cd 206 "Whether auth-source should cache information with `password-cache'."
ed778fad 207 :group 'auth-source
ec7995fa 208 :version "23.2" ;; No Gnus
ed778fad
MB
209 :type `boolean)
210
a202ff49 211(defcustom auth-source-debug nil
554a69b8 212 "Whether auth-source should log debug messages.
554a69b8
KY
213
214If the value is nil, debug messages are not logged.
ca6ddb88
TZ
215
216If the value is t, debug messages are logged with `message'. In
217that case, your authentication data will be in the clear (except
218for passwords).
219
554a69b8
KY
220If the value is a function, debug messages are logged by calling
221 that function using the same arguments as `message'."
222 :group 'auth-source
ec7995fa 223 :version "23.2" ;; No Gnus
cbabe91f
TZ
224 :type `(choice
225 :tag "auth-source debugging mode"
226 (const :tag "Log using `message' to the *Messages* buffer" t)
4a3988d5
G
227 (const :tag "Log all trivia with `message' to the *Messages* buffer"
228 trivia)
cbabe91f
TZ
229 (function :tag "Function that takes arguments like `message'")
230 (const :tag "Don't log anything" nil)))
554a69b8 231
6a6e4d93 232(defcustom auth-sources '("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")
8f7abae3
MB
233 "List of authentication sources.
234
b8e0f0cd
G
235The default will get login and password information from
236\"~/.authinfo.gpg\", which you should set up with the EPA/EPG
237packages to be encrypted. If that file doesn't exist, it will
4a3988d5
G
238try the unencrypted version \"~/.authinfo\" and the famous
239\"~/.netrc\" file.
b8e0f0cd
G
240
241See the auth.info manual for details.
fb178e4c 242
ec7995fa
KY
243Each entry is the authentication type with optional properties.
244
245It's best to customize this with `M-x customize-variable' because the choices
246can get pretty complex."
8f7abae3 247 :group 'auth-source
b8e0f0cd 248 :version "24.1" ;; No Gnus
9b3ebcb6 249 :type `(repeat :tag "Authentication Sources"
b8e0f0cd
G
250 (choice
251 (string :tag "Just a file")
252 (const :tag "Default Secrets API Collection" 'default)
5415d076 253 (const :tag "Login Secrets API Collection" "secrets:Login")
b8e0f0cd
G
254 (const :tag "Temp Secrets API Collection" "secrets:session")
255 (list :tag "Source definition"
256 (const :format "" :value :source)
257 (choice :tag "Authentication backend choice"
258 (string :tag "Authentication Source (file)")
259 (list
260 :tag "Secret Service API/KWallet/GNOME Keyring"
261 (const :format "" :value :secrets)
262 (choice :tag "Collection to use"
263 (string :tag "Collection name")
264 (const :tag "Default" 'default)
5415d076 265 (const :tag "Login" "Login")
b8e0f0cd
G
266 (const
267 :tag "Temporary" "session"))))
268 (repeat :tag "Extra Parameters" :inline t
269 (choice :tag "Extra parameter"
270 (list
271 :tag "Host"
272 (const :format "" :value :host)
273 (choice :tag "Host (machine) choice"
274 (const :tag "Any" t)
275 (regexp
276 :tag "Regular expression")))
277 (list
278 :tag "Protocol"
35123c04 279 (const :format "" :value :port)
b8e0f0cd
G
280 (choice
281 :tag "Protocol"
282 (const :tag "Any" t)
283 ,@auth-source-protocols-customize))
284 (list :tag "User" :inline t
285 (const :format "" :value :user)
61e6a0ac
TZ
286 (choice
287 :tag "Personality/Username"
b8e0f0cd 288 (const :tag "Any" t)
61e6a0ac
TZ
289 (string
290 :tag "Name")))))))))
8f7abae3 291
549c9aed
G
292(defcustom auth-source-gpg-encrypt-to t
293 "List of recipient keys that `authinfo.gpg' encrypted to.
294If the value is not a list, symmetric encryption will be used."
295 :group 'auth-source
b8e0f0cd 296 :version "24.1" ;; No Gnus
549c9aed 297 :type '(choice (const :tag "Symmetric encryption" t)
b8e0f0cd
G
298 (repeat :tag "Recipient public keys"
299 (string :tag "Recipient public key"))))
549c9aed 300
8f7abae3 301;; temp for debugging
9b3ebcb6
MB
302;; (unintern 'auth-source-protocols)
303;; (unintern 'auth-sources)
304;; (customize-variable 'auth-sources)
305;; (setq auth-sources nil)
306;; (format "%S" auth-sources)
307;; (customize-variable 'auth-source-protocols)
308;; (setq auth-source-protocols nil)
309;; (format "%S" auth-source-protocols)
fb178e4c 310;; (auth-source-pick nil :host "a" :port 'imap)
9b3ebcb6
MB
311;; (auth-source-user-or-password "login" "imap.myhost.com" 'imap)
312;; (auth-source-user-or-password "password" "imap.myhost.com" 'imap)
313;; (auth-source-user-or-password-imap "login" "imap.myhost.com")
314;; (auth-source-user-or-password-imap "password" "imap.myhost.com")
315;; (auth-source-protocol-defaults 'imap)
316
ca6ddb88
TZ
317;; (let ((auth-source-debug 'debug)) (auth-source-do-debug "hello"))
318;; (let ((auth-source-debug t)) (auth-source-do-debug "hello"))
319;; (let ((auth-source-debug nil)) (auth-source-do-debug "hello"))
554a69b8 320(defun auth-source-do-debug (&rest msg)
554a69b8 321 (when auth-source-debug
ca6ddb88
TZ
322 (apply 'auth-source-do-warn msg)))
323
4a3988d5
G
324(defun auth-source-do-trivia (&rest msg)
325 (when (or (eq auth-source-debug 'trivia)
326 (functionp auth-source-debug))
327 (apply 'auth-source-do-warn msg)))
328
ca6ddb88
TZ
329(defun auth-source-do-warn (&rest msg)
330 (apply
331 ;; set logger to either the function in auth-source-debug or 'message
332 ;; note that it will be 'message if auth-source-debug is nil
333 (if (functionp auth-source-debug)
334 auth-source-debug
335 'message)
336 msg))
337
554a69b8 338
733afdf4
TZ
339;;; (auth-source-read-char-choice "enter choice? " '(?a ?b ?q))
340(defun auth-source-read-char-choice (prompt choices)
341 "Read one of CHOICES by `read-char-choice', or `read-char'.
342`dropdown-list' support is disabled because it doesn't work reliably.
343Only one of CHOICES will be returned. The PROMPT is augmented
344with \"[a/b/c] \" if CHOICES is '\(?a ?b ?c\)."
345 (when choices
346 (let* ((prompt-choices
347 (apply 'concat (loop for c in choices
348 collect (format "%c/" c))))
349 (prompt-choices (concat "[" (substring prompt-choices 0 -1) "] "))
350 (full-prompt (concat prompt prompt-choices))
351 k)
352
353 (while (not (memq k choices))
354 (setq k (cond
733afdf4
TZ
355 ((fboundp 'read-char-choice)
356 (read-char-choice full-prompt choices))
357 (t (message "%s" full-prompt)
358 (setq k (read-char))))))
359 k)))
360
35123c04
TZ
361;; (auth-source-pick nil :host "any" :port 'imap :user "joe")
362;; (auth-source-pick t :host "any" :port 'imap :user "joe")
363;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe")
364;; (:source (:secrets "session") :host t :port t :user "joe")
365;; (:source (:secrets "Login") :host t :port t)
366;; (:source "~/.authinfo.gpg" :host t :port t)))
fb178e4c 367
35123c04
TZ
368;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe")
369;; (:source (:secrets "session") :host t :port t :user "joe")
370;; (:source (:secrets "Login") :host t :port t)
cbabe91f 371;; ))
fb178e4c 372
35123c04 373;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :port t)))
fb178e4c 374
b8e0f0cd
G
375;; (auth-source-backend-parse "myfile.gpg")
376;; (auth-source-backend-parse 'default)
5415d076 377;; (auth-source-backend-parse "secrets:Login")
b8e0f0cd
G
378
379(defun auth-source-backend-parse (entry)
380 "Creates an auth-source-backend from an ENTRY in `auth-sources'."
381 (auth-source-backend-parse-parameters
382 entry
383 (cond
384 ;; take 'default and recurse to get it as a Secrets API default collection
385 ;; matching any user, host, and protocol
386 ((eq entry 'default)
387 (auth-source-backend-parse '(:source (:secrets default))))
388 ;; take secrets:XYZ and recurse to get it as Secrets API collection "XYZ"
389 ;; matching any user, host, and protocol
390 ((and (stringp entry) (string-match "^secrets:\\(.+\\)" entry))
391 (auth-source-backend-parse `(:source (:secrets ,(match-string 1 entry)))))
392 ;; take just a file name and recurse to get it as a netrc file
393 ;; matching any user, host, and protocol
394 ((stringp entry)
395 (auth-source-backend-parse `(:source ,entry)))
396
397 ;; a file name with parameters
398 ((stringp (plist-get entry :source))
8977de27
DU
399 (if (equal (file-name-extension (plist-get entry :source)) "plist")
400 (auth-source-backend
401 (plist-get entry :source)
402 :source (plist-get entry :source)
403 :type 'plstore
404 :search-function 'auth-source-plstore-search
405 :create-function 'auth-source-plstore-create
b09c3fe0 406 :data (plstore-open (plist-get entry :source)))
8977de27
DU
407 (auth-source-backend
408 (plist-get entry :source)
409 :source (plist-get entry :source)
410 :type 'netrc
411 :search-function 'auth-source-netrc-search
412 :create-function 'auth-source-netrc-create)))
b8e0f0cd
G
413
414 ;; the Secrets API. We require the package, in order to have a
415 ;; defined value for `secrets-enabled'.
416 ((and
417 (not (null (plist-get entry :source))) ; the source must not be nil
418 (listp (plist-get entry :source)) ; and it must be a list
419 (require 'secrets nil t) ; and we must load the Secrets API
420 secrets-enabled) ; and that API must be enabled
421
422 ;; the source is either the :secrets key in ENTRY or
423 ;; if that's missing or nil, it's "session"
424 (let ((source (or (plist-get (plist-get entry :source) :secrets)
425 "session")))
426
427 ;; if the source is a symbol, we look for the alias named so,
5415d076 428 ;; and if that alias is missing, we use "Login"
b8e0f0cd
G
429 (when (symbolp source)
430 (setq source (or (secrets-get-alias (symbol-name source))
5415d076 431 "Login")))
b8e0f0cd 432
ca6ddb88
TZ
433 (if (featurep 'secrets)
434 (auth-source-backend
435 (format "Secrets API (%s)" source)
436 :source source
437 :type 'secrets
438 :search-function 'auth-source-secrets-search
439 :create-function 'auth-source-secrets-create)
440 (auth-source-do-warn
441 "auth-source-backend-parse: no Secrets API, ignoring spec: %S" entry)
442 (auth-source-backend
443 (format "Ignored Secrets API (%s)" source)
444 :source ""
445 :type 'ignore))))
b8e0f0cd
G
446
447 ;; none of them
448 (t
ca6ddb88 449 (auth-source-do-warn
b8e0f0cd
G
450 "auth-source-backend-parse: invalid backend spec: %S" entry)
451 (auth-source-backend
452 "Empty"
453 :source ""
454 :type 'ignore)))))
455
456(defun auth-source-backend-parse-parameters (entry backend)
457 "Fills in the extra auth-source-backend parameters of ENTRY.
35123c04
TZ
458Using the plist ENTRY, get the :host, :port, and :user search
459parameters."
e45de620
TZ
460 (let ((entry (if (stringp entry)
461 nil
462 entry))
463 val)
b8e0f0cd
G
464 (when (setq val (plist-get entry :host))
465 (oset backend host val))
466 (when (setq val (plist-get entry :user))
467 (oset backend user val))
35123c04
TZ
468 (when (setq val (plist-get entry :port))
469 (oset backend port val)))
b8e0f0cd
G
470 backend)
471
472;; (mapcar 'auth-source-backend-parse auth-sources)
473
474(defun* auth-source-search (&rest spec
35123c04 475 &key type max host user port secret
733afdf4 476 require create delete
b8e0f0cd
G
477 &allow-other-keys)
478 "Search or modify authentication backends according to SPEC.
479
480This function parses `auth-sources' for matches of the SPEC
481plist. It can optionally create or update an authentication
482token if requested. A token is just a standard Emacs property
483list with a :secret property that can be a function; all the
484other properties will always hold scalar values.
485
486Typically the :secret property, if present, contains a password.
487
35123c04 488Common search keys are :max, :host, :port, and :user. In
b8e0f0cd
G
489addition, :create specifies how tokens will be or created.
490Finally, :type can specify which backend types you want to check.
491
492A string value is always matched literally. A symbol is matched
493as its string value, literally. All the SPEC values can be
494single values (symbol or string) or lists thereof (in which case
495any of the search terms matches).
496
497:create t means to create a token if possible.
498
499A new token will be created if no matching tokens were found.
500The new token will have only the keys the backend requires. For
501the netrc backend, for instance, that's the user, host, and
35123c04 502port keys.
b8e0f0cd
G
503
504Here's an example:
505
506\(let ((auth-source-creation-defaults '((user . \"defaultUser\")
507 (A . \"default A\"))))
508 (auth-source-search :host \"mine\" :type 'netrc :max 1
509 :P \"pppp\" :Q \"qqqq\"
510 :create t))
511
512which says:
513
514\"Search for any entry matching host 'mine' in backends of type
515 'netrc', maximum one result.
516
517 Create a new entry if you found none. The netrc backend will
35123c04 518 automatically require host, user, and port. The host will be
b8e0f0cd 519 'mine'. We prompt for the user with default 'defaultUser' and
35123c04 520 for the port without a default. We will not prompt for A, Q,
b8e0f0cd 521 or P. The resulting token will only have keys user, host, and
35123c04 522 port.\"
b8e0f0cd
G
523
524:create '(A B C) also means to create a token if possible.
525
526The behavior is like :create t but if the list contains any
527parameter, that parameter will be required in the resulting
528token. The value for that parameter will be obtained from the
529search parameters or from user input. If any queries are needed,
530the alist `auth-source-creation-defaults' will be checked for the
003522ce
G
531default value. If the user, host, or port are missing, the alist
532`auth-source-creation-prompts' will be used to look up the
533prompts IN THAT ORDER (so the 'user prompt will be queried first,
534then 'host, then 'port, and finally 'secret). Each prompt string
535can use %u, %h, and %p to show the user, host, and port.
b8e0f0cd
G
536
537Here's an example:
538
539\(let ((auth-source-creation-defaults '((user . \"defaultUser\")
003522ce
G
540 (A . \"default A\")))
541 (auth-source-creation-prompts
542 '((password . \"Enter IMAP password for %h:%p: \"))))
b8e0f0cd
G
543 (auth-source-search :host '(\"nonesuch\" \"twosuch\") :type 'netrc :max 1
544 :P \"pppp\" :Q \"qqqq\"
545 :create '(A B Q)))
546
547which says:
548
549\"Search for any entry matching host 'nonesuch'
550 or 'twosuch' in backends of type 'netrc', maximum one result.
551
552 Create a new entry if you found none. The netrc backend will
35123c04 553 automatically require host, user, and port. The host will be
003522ce
G
554 'nonesuch' and Q will be 'qqqq'. We prompt for the password
555 with the shown prompt. We will not prompt for Q. The resulting
556 token will have keys user, host, port, A, B, and Q. It will not
557 have P with any value, even though P is used in the search to
558 find only entries that have P set to 'pppp'.\"
b8e0f0cd
G
559
560When multiple values are specified in the search parameter, the
7ba93e94
G
561user is prompted for which one. So :host (X Y Z) would ask the
562user to choose between X, Y, and Z.
b8e0f0cd
G
563
564This creation can fail if the search was not specific enough to
565create a new token (it's up to the backend to decide that). You
566should `catch' the backend-specific error as usual. Some
567backends (netrc, at least) will prompt the user rather than throw
568an error.
569
733afdf4
TZ
570:require (A B C) means that only results that contain those
571tokens will be returned. Thus for instance requiring :secret
572will ensure that any results will actually have a :secret
573property.
574
b8e0f0cd
G
575:delete t means to delete any found entries. nil by default.
576Use `auth-source-delete' in ELisp code instead of calling
577`auth-source-search' directly with this parameter.
578
579:type (X Y Z) will check only those backend types. 'netrc and
580'secrets are the only ones supported right now.
581
582:max N means to try to return at most N items (defaults to 1).
583When 0 the function will return just t or nil to indicate if any
584matches were found. More than N items may be returned, depending
585on the search and the backend.
586
587:host (X Y Z) means to match only hosts X, Y, or Z according to
588the match rules above. Defaults to t.
589
590:user (X Y Z) means to match only users X, Y, or Z according to
591the match rules above. Defaults to t.
592
35123c04 593:port (P Q R) means to match only protocols P, Q, or R.
b8e0f0cd
G
594Defaults to t.
595
596:K (V1 V2 V3) for any other key K will match values V1, V2, or
597V3 (note the match rules above).
598
599The return value is a list with at most :max tokens. Each token
35123c04 600is a plist with keys :backend :host :port :user, plus any other
b8e0f0cd
G
601keys provided by the backend (notably :secret). But note the
602exception for :max 0, which see above.
603
733afdf4
TZ
604The token can hold a :save-function key. If you call that, the
605user will be prompted to save the data to the backend. You can't
606request that this should happen right after creation, because
607`auth-source-search' has no way of knowing if the token is
608actually useful. So the caller must arrange to call this function.
609
b8e0f0cd
G
610The token's :secret key can hold a function. In that case you
611must call it to obtain the actual value."
612 (let* ((backends (mapcar 'auth-source-backend-parse auth-sources))
613 (max (or max 1))
733afdf4 614 (ignored-keys '(:require :create :delete :max))
b8e0f0cd
G
615 (keys (loop for i below (length spec) by 2
616 unless (memq (nth i spec) ignored-keys)
617 collect (nth i spec)))
61e9662e
TZ
618 (cached (auth-source-remembered-p spec))
619 ;; note that we may have cached results but found is still nil
620 ;; (there were no results from the search)
b8e0f0cd 621 (found (auth-source-recall spec))
4a3988d5 622 filtered-backends accessor-key backend)
b8e0f0cd 623
61e9662e 624 (if (and cached auth-source-do-cache)
b8e0f0cd
G
625 (auth-source-do-debug
626 "auth-source-search: found %d CACHED results matching %S"
627 (length found) spec)
628
629 (assert
630 (or (eq t create) (listp create)) t
4a3988d5 631 "Invalid auth-source :create parameter (must be t or a list): %s %s")
b8e0f0cd 632
733afdf4
TZ
633 (assert
634 (listp require) t
635 "Invalid auth-source :require parameter (must be a list): %s")
636
6ce6c742 637 (setq filtered-backends (copy-sequence backends))
b8e0f0cd
G
638 (dolist (backend backends)
639 (dolist (key keys)
640 ;; ignore invalid slots
641 (condition-case signal
642 (unless (eval `(auth-source-search-collection
643 (plist-get spec key)
644 (oref backend ,key)))
645 (setq filtered-backends (delq backend filtered-backends))
646 (return))
647 (invalid-slot-name))))
648
4a3988d5 649 (auth-source-do-trivia
b8e0f0cd
G
650 "auth-source-search: found %d backends matching %S"
651 (length filtered-backends) spec)
652
653 ;; (debug spec "filtered" filtered-backends)
1d2c4a49
LI
654 ;; First go through all the backends without :create, so we can
655 ;; query them all.
4a3988d5
G
656 (setq found (auth-source-search-backends filtered-backends
657 spec
658 ;; to exit early
659 max
733afdf4
TZ
660 ;; create is always nil here
661 nil delete
662 require))
4a3988d5
G
663
664 (auth-source-do-debug
665 "auth-source-search: found %d results (max %d) matching %S"
666 (length found) max spec)
667
1d2c4a49
LI
668 ;; If we didn't find anything, then we allow the backend(s) to
669 ;; create the entries.
38046520 670 (when (and create
4a3988d5
G
671 (not found))
672 (setq found (auth-source-search-backends filtered-backends
673 spec
674 ;; to exit early
675 max
733afdf4
TZ
676 create delete
677 require))
678 (auth-source-do-debug
4a3988d5
G
679 "auth-source-search: CREATED %d results (max %d) matching %S"
680 (length found) max spec))
681
61e9662e
TZ
682 ;; note we remember the lack of result too, if it's applicable
683 (when auth-source-do-cache
4a3988d5
G
684 (auth-source-remember spec found)))
685
686 found))
687
733afdf4 688(defun auth-source-search-backends (backends spec max create delete require)
4a3988d5
G
689 (let (matches)
690 (dolist (backend backends)
691 (when (> max (length matches)) ; when we need more matches...
733afdf4
TZ
692 (let* ((bmatches (apply
693 (slot-value backend 'search-function)
694 :backend backend
695 ;; note we're overriding whatever the spec
696 ;; has for :require, :create, and :delete
697 :require require
698 :create create
699 :delete delete
700 spec)))
4a3988d5
G
701 (when bmatches
702 (auth-source-do-trivia
703 "auth-source-search-backend: got %d (max %d) in %s:%s matching %S"
704 (length bmatches) max
705 (slot-value backend :type)
706 (slot-value backend :source)
707 spec)
708 (setq matches (append matches bmatches))))))
709 matches))
b8e0f0cd
G
710
711;;; (auth-source-search :max 1)
712;;; (funcall (plist-get (nth 0 (auth-source-search :max 1)) :secret))
713;;; (auth-source-search :host "nonesuch" :type 'netrc :K 1)
714;;; (auth-source-search :host "nonesuch" :type 'secrets)
715
716(defun* auth-source-delete (&rest spec
717 &key delete
718 &allow-other-keys)
719 "Delete entries from the authentication backends according to SPEC.
720Calls `auth-source-search' with the :delete property in SPEC set to t.
721The backend may not actually delete the entries.
722
723Returns the deleted entries."
724 (auth-source-search (plist-put spec :delete t)))
725
726(defun auth-source-search-collection (collection value)
727 "Returns t is VALUE is t or COLLECTION is t or contains VALUE."
728 (when (and (atom collection) (not (eq t collection)))
729 (setq collection (list collection)))
730
731 ;; (debug :collection collection :value value)
732 (or (eq collection t)
733 (eq value t)
734 (equal collection value)
735 (member value collection)))
ed778fad 736
74e8193b
KY
737(defvar auth-source-netrc-cache nil)
738
3b36c17e 739(defun auth-source-forget-all-cached ()
b8e0f0cd 740 "Forget all cached auth-source data."
3b36c17e 741 (interactive)
b8e0f0cd
G
742 (loop for sym being the symbols of password-data
743 ;; when the symbol name starts with auth-source-magic
744 when (string-match (concat "^" auth-source-magic)
745 (symbol-name sym))
746 ;; remove that key
ddb7ffee
LMI
747 do (password-cache-remove (symbol-name sym)))
748 (setq auth-source-netrc-cache nil))
b8e0f0cd
G
749
750(defun auth-source-remember (spec found)
751 "Remember FOUND search results for SPEC."
584c9d3f
G
752 (let ((password-cache-expiry auth-source-cache-expiry))
753 (password-cache-add
754 (concat auth-source-magic (format "%S" spec)) found)))
b8e0f0cd
G
755
756(defun auth-source-recall (spec)
757 "Recall FOUND search results for SPEC."
758 (password-read-from-cache
759 (concat auth-source-magic (format "%S" spec))))
760
61e9662e
TZ
761(defun auth-source-remembered-p (spec)
762 "Check if SPEC is remembered."
763 (password-in-cache-p
764 (concat auth-source-magic (format "%S" spec))))
765
b8e0f0cd
G
766(defun auth-source-forget (spec)
767 "Forget any cached data matching SPEC exactly.
768
769This is the same SPEC you passed to `auth-source-search'.
770Returns t or nil for forgotten or not found."
771 (password-cache-remove (concat auth-source-magic (format "%S" spec))))
772
773;;; (loop for sym being the symbols of password-data when (string-match (concat "^" auth-source-magic) (symbol-name sym)) collect (symbol-name sym))
774
775;;; (auth-source-remember '(:host "wedd") '(4 5 6))
61e9662e 776;;; (auth-source-remembered-p '(:host "wedd"))
b8e0f0cd 777;;; (auth-source-remember '(:host "xedd") '(1 2 3))
61e9662e
TZ
778;;; (auth-source-remembered-p '(:host "xedd"))
779;;; (auth-source-remembered-p '(:host "zedd"))
b8e0f0cd
G
780;;; (auth-source-recall '(:host "xedd"))
781;;; (auth-source-recall '(:host t))
782;;; (auth-source-forget+ :host t)
783
784(defun* auth-source-forget+ (&rest spec &allow-other-keys)
785 "Forget any cached data matching SPEC. Returns forgotten count.
786
787This is not a full `auth-source-search' spec but works similarly.
788For instance, \(:host \"myhost\" \"yourhost\") would find all the
789cached data that was found with a search for those two hosts,
790while \(:host t) would find all host entries."
791 (let ((count 0)
792 sname)
793 (loop for sym being the symbols of password-data
794 ;; when the symbol name matches with auth-source-magic
795 when (and (setq sname (symbol-name sym))
796 (string-match (concat "^" auth-source-magic "\\(.+\\)")
797 sname)
798 ;; and the spec matches what was stored in the cache
799 (auth-source-specmatchp spec (read (match-string 1 sname))))
800 ;; remove that key
801 do (progn
802 (password-cache-remove sname)
803 (incf count)))
804 count))
805
806(defun auth-source-specmatchp (spec stored)
807 (let ((keys (loop for i below (length spec) by 2
808 collect (nth i spec))))
809 (not (eq
810 (dolist (key keys)
811 (unless (auth-source-search-collection (plist-get stored key)
812 (plist-get spec key))
813 (return 'no)))
814 'no))))
815
f3b54b0e
TZ
816;;; (auth-source-pick-first-password :host "z.lifelogs.com")
817;;; (auth-source-pick-first-password :port "imap")
818(defun auth-source-pick-first-password (&rest spec)
819 "Pick the first secret found from applying SPEC to `auth-source-search'."
820 (let* ((result (nth 0 (apply 'auth-source-search (plist-put spec :max 1))))
821 (secret (plist-get result :secret)))
822
823 (if (functionp secret)
824 (funcall secret)
825 secret)))
826
827;; (auth-source-format-prompt "test %u %h %p" '((?u "user") (?h "host")))
828(defun auth-source-format-prompt (prompt alist)
829 "Format PROMPT using %x (for any character x) specifiers in ALIST."
830 (dolist (cell alist)
831 (let ((c (nth 0 cell))
832 (v (nth 1 cell)))
833 (when (and c v)
4248cca2
TZ
834 (setq prompt (replace-regexp-in-string (format "%%%c" c)
835 (format "%s" v)
836 prompt)))))
f3b54b0e 837 prompt)
b8e0f0cd 838
8e22bee0
G
839(defun auth-source-ensure-strings (values)
840 (unless (listp values)
841 (setq values (list values)))
842 (mapcar (lambda (value)
843 (if (numberp value)
844 (format "%s" value)
845 value))
846 values))
847
f3b54b0e
TZ
848;;; Backend specific parsing: netrc/authinfo backend
849
b8e0f0cd
G
850;;; (auth-source-netrc-parse "~/.authinfo.gpg")
851(defun* auth-source-netrc-parse (&rest
852 spec
733afdf4 853 &key file max host user port delete require
b8e0f0cd
G
854 &allow-other-keys)
855 "Parse FILE and return a list of all entries in the file.
856Note that the MAX parameter is used so we can exit the parse early."
857 (if (listp file)
858 ;; We got already parsed contents; just return it.
859 file
860 (when (file-exists-p file)
8e22bee0 861 (setq port (auth-source-ensure-strings port))
b8e0f0cd 862 (with-temp-buffer
4a3988d5
G
863 (let* ((tokens '("machine" "host" "default" "login" "user"
864 "password" "account" "macdef" "force"
865 "port" "protocol"))
866 (max (or max 5000)) ; sanity check: default to stop at 5K
867 (modified 0)
868 (cached (cdr-safe (assoc file auth-source-netrc-cache)))
869 (cached-mtime (plist-get cached :mtime))
870 (cached-secrets (plist-get cached :secret))
871 alist elem result pair)
872
873 (if (and (functionp cached-secrets)
874 (equal cached-mtime
875 (nth 5 (file-attributes file))))
876 (progn
877 (auth-source-do-trivia
878 "auth-source-netrc-parse: using CACHED file data for %s"
879 file)
880 (insert (funcall cached-secrets)))
881 (insert-file-contents file)
882 ;; cache all netrc files (used to be just .gpg files)
883 ;; Store the contents of the file heavily encrypted in memory.
884 ;; (note for the irony-impaired: they are just obfuscated)
885 (aput 'auth-source-netrc-cache file
886 (list :mtime (nth 5 (file-attributes file))
887 :secret (lexical-let ((v (rot13-string
888 (base64-encode-string
889 (buffer-string)))))
890 (lambda () (base64-decode-string
891 (rot13-string v)))))))
b8e0f0cd
G
892 (goto-char (point-min))
893 ;; Go through the file, line by line.
894 (while (and (not (eobp))
895 (> max 0))
896
897 (narrow-to-region (point) (point-at-eol))
898 ;; For each line, get the tokens and values.
899 (while (not (eobp))
900 (skip-chars-forward "\t ")
901 ;; Skip lines that begin with a "#".
902 (if (eq (char-after) ?#)
903 (goto-char (point-max))
904 (unless (eobp)
905 (setq elem
906 (if (= (following-char) ?\")
907 (read (current-buffer))
908 (buffer-substring
909 (point) (progn (skip-chars-forward "^\t ")
910 (point)))))
911 (cond
912 ((equal elem "macdef")
913 ;; We skip past the macro definition.
914 (widen)
915 (while (and (zerop (forward-line 1))
916 (looking-at "$")))
917 (narrow-to-region (point) (point)))
918 ((member elem tokens)
919 ;; Tokens that don't have a following value are ignored,
920 ;; except "default".
921 (when (and pair (or (cdr pair)
922 (equal (car pair) "default")))
923 (push pair alist))
924 (setq pair (list elem)))
925 (t
926 ;; Values that haven't got a preceding token are ignored.
927 (when pair
928 (setcdr pair elem)
929 (push pair alist)
930 (setq pair nil)))))))
931
932 (when (and alist
933 (> max 0)
934 (auth-source-search-collection
935 host
936 (or
937 (aget alist "machine")
35123c04
TZ
938 (aget alist "host")
939 t))
b8e0f0cd
G
940 (auth-source-search-collection
941 user
942 (or
943 (aget alist "login")
944 (aget alist "account")
35123c04
TZ
945 (aget alist "user")
946 t))
b8e0f0cd 947 (auth-source-search-collection
35123c04 948 port
b8e0f0cd
G
949 (or
950 (aget alist "port")
35123c04 951 (aget alist "protocol")
733afdf4
TZ
952 t))
953 (or
954 ;; the required list of keys is nil, or
955 (null require)
956 ;; every element of require is in the normalized list
957 (let ((normalized (nth 0 (auth-source-netrc-normalize
2b8c5660 958 (list alist) file))))
733afdf4
TZ
959 (loop for req in require
960 always (plist-get normalized req)))))
b8e0f0cd
G
961 (decf max)
962 (push (nreverse alist) result)
963 ;; to delete a line, we just comment it out
964 (when delete
965 (goto-char (point-min))
966 (insert "#")
967 (incf modified)))
968 (setq alist nil
969 pair nil)
970 (widen)
971 (forward-line 1))
972
973 (when (< 0 modified)
974 (when auth-source-gpg-encrypt-to
975 ;; (see bug#7487) making `epa-file-encrypt-to' local to
976 ;; this buffer lets epa-file skip the key selection query
977 ;; (see the `local-variable-p' check in
978 ;; `epa-file-write-region').
979 (unless (local-variable-p 'epa-file-encrypt-to (current-buffer))
980 (make-local-variable 'epa-file-encrypt-to))
981 (if (listp auth-source-gpg-encrypt-to)
982 (setq epa-file-encrypt-to auth-source-gpg-encrypt-to)))
983
984 ;; ask AFTER we've successfully opened the file
733afdf4 985 (when (y-or-n-p (format "Save file %s? (%d deletions)"
b8e0f0cd
G
986 file modified))
987 (write-region (point-min) (point-max) file nil 'silent)
988 (auth-source-do-debug
989 "auth-source-netrc-parse: modified %d lines in %s"
990 modified file)))
991
992 (nreverse result))))))
993
936d08bb
G
994(defvar auth-source-passphrase-alist nil)
995
996(defun auth-source-passphrase-callback-function (context key-id handback
997 &optional sym-detail)
998 "Exactly like `epa-passphrase-callback-function' but takes an
999extra SYM-DETAIL parameter which will be printed at the end of
1000the symmetric passphrase prompt, and assumes symmetric
1001encryption."
1002 (read-passwd
1003 (format "Passphrase for symmetric encryption%s%s: "
1004 ;; Add the file name to the prompt, if any.
1005 (if (stringp handback)
1006 (format " for %s" handback)
1007 "")
1008 (if (stringp sym-detail)
1009 sym-detail
1010 ""))
1011 (eq (epg-context-operation context) 'encrypt)))
1012
1013(defun auth-source-token-passphrase-callback-function (context key-id file)
1014 (if (eq key-id 'SYM)
1015 (let* ((file (file-truename file))
1016 (entry (assoc file auth-source-passphrase-alist))
1017 passphrase)
1018 ;; return the saved passphrase, calling a function if needed
1019 (or (copy-sequence (if (functionp (cdr entry))
1020 (funcall (cdr entry))
1021 (cdr entry)))
1022 (progn
1023 (unless entry
1024 (setq entry (list file))
1025 (push entry auth-source-passphrase-alist))
1026 (setq passphrase (auth-source-passphrase-callback-function context
1027 key-id
1028 file
1029 " tokens"))
1030 (setcdr entry (lexical-let ((p (copy-sequence passphrase)))
1031 (lambda () p)))
1032 passphrase)))
1033 (epa-passphrase-callback-function context key-id file)))
1034
1035;; (auth-source-epa-extract-gpg-token "gpg:LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tClZlcnNpb246IEdudVBHIHYxLjQuMTEgKEdOVS9MaW51eCkKCmpBMEVBd01DT25qMjB1ak9rZnRneVI3K21iNm9aZWhuLzRad3cySkdlbnVaKzRpeEswWDY5di9icDI1U1dsQT0KPS9yc2wKLS0tLS1FTkQgUEdQIE1FU1NBR0UtLS0tLQo=" "~/.netrc")
1036(defun auth-source-epa-extract-gpg-token (secret file)
1037 "Pass either the decoded SECRET or the gpg:BASE64DATA version.
1038FILE is the file from which we obtained this token."
1039 (when (string-match "^gpg:\\(.+\\)" secret)
1040 (setq secret (base64-decode-string (match-string 1 secret))))
1041 (let ((context (epg-make-context 'OpenPGP))
1042 plain)
1043 (epg-context-set-passphrase-callback
1044 context
1045 (cons #'auth-source-token-passphrase-callback-function
1046 file))
1047 (epg-decrypt-string context secret)))
1048
1049;; (insert (auth-source-epa-make-gpg-token "mysecret" "~/.netrc"))
2b8c5660 1050(defun auth-source-epa-make-gpg-token (secret file)
936d08bb
G
1051 (let ((context (epg-make-context 'OpenPGP))
1052 (pp-escape-newlines nil)
1053 cipher)
1054 (epg-context-set-armor context t)
1055 (epg-context-set-passphrase-callback
1056 context
1057 (cons #'auth-source-token-passphrase-callback-function
1058 file))
1059 (setq cipher (epg-encrypt-string context secret nil))
1060 (with-temp-buffer
1061 (insert cipher)
1062 (base64-encode-region (point-min) (point-max) t)
1063 (concat "gpg:" (buffer-substring-no-properties
1064 (point-min)
1065 (point-max))))))
2b8c5660
TZ
1066
1067(defun auth-source-netrc-normalize (alist filename)
b8e0f0cd
G
1068 (mapcar (lambda (entry)
1069 (let (ret item)
1070 (while (setq item (pop entry))
1071 (let ((k (car item))
1072 (v (cdr item)))
1073
1074 ;; apply key aliases
1075 (setq k (cond ((member k '("machine")) "host")
1076 ((member k '("login" "account")) "user")
1077 ((member k '("protocol")) "port")
1078 ((member k '("password")) "secret")
1079 (t k)))
1080
1081 ;; send back the secret in a function (lexical binding)
1082 (when (equal k "secret")
936d08bb
G
1083 (setq v (lexical-let ((lexv v)
1084 (token-decoder nil))
1085 (when (string-match "^gpg:" lexv)
1086 ;; it's a GPG token: create a token decoder
1087 ;; which unsets itself once
1088 (setq token-decoder
1089 (lambda (val)
1090 (prog1
1091 (auth-source-epa-extract-gpg-token
1092 val
1093 filename)
1094 (setq token-decoder nil)))))
1095 (lambda ()
1096 (when token-decoder
1097 (setq lexv (funcall token-decoder lexv)))
1098 lexv))))
2b8c5660
TZ
1099 (setq ret (plist-put ret
1100 (intern (concat ":" k))
1101 v))))
1102 ret))
1103 alist))
b8e0f0cd
G
1104
1105;;; (setq secret (plist-get (nth 0 (auth-source-search :host t :type 'netrc :K 1 :max 1)) :secret))
1106;;; (funcall secret)
1107
1108(defun* auth-source-netrc-search (&rest
1109 spec
733afdf4 1110 &key backend require create delete
35123c04 1111 type max host user port
b8e0f0cd
G
1112 &allow-other-keys)
1113"Given a property list SPEC, return search matches from the :backend.
1114See `auth-source-search' for details on SPEC."
1115 ;; just in case, check that the type is correct (null or same as the backend)
1116 (assert (or (null type) (eq type (oref backend type)))
d5e9a4e9 1117 t "Invalid netrc search: %s %s")
b8e0f0cd
G
1118
1119 (let ((results (auth-source-netrc-normalize
1120 (auth-source-netrc-parse
1121 :max max
733afdf4 1122 :require require
b8e0f0cd
G
1123 :delete delete
1124 :file (oref backend source)
1125 :host (or host t)
1126 :user (or user t)
2b8c5660
TZ
1127 :port (or port t))
1128 (oref backend source))))
b8e0f0cd
G
1129
1130 ;; if we need to create an entry AND none were found to match
1131 (when (and create
4a3988d5 1132 (not results))
b8e0f0cd 1133
584c9d3f
G
1134 ;; create based on the spec and record the value
1135 (setq results (or
1136 ;; if the user did not want to create the entry
1137 ;; in the file, it will be returned
1138 (apply (slot-value backend 'create-function) spec)
1139 ;; if not, we do the search again without :create
1140 ;; to get the updated data.
1141
1142 ;; the result will be returned, even if the search fails
1143 (apply 'auth-source-netrc-search
1144 (plist-put spec :create nil)))))
b8e0f0cd
G
1145 results))
1146
fa41748c
G
1147(defun auth-source-netrc-element-or-first (v)
1148 (if (listp v)
1149 (nth 0 v)
1150 v))
1151
b8e0f0cd
G
1152;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t)
1153;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t :create-extra-keys '((A "default A") (B)))
1154
1155(defun* auth-source-netrc-create (&rest spec
1156 &key backend
35123c04 1157 secret host user port create
b8e0f0cd 1158 &allow-other-keys)
35123c04 1159 (let* ((base-required '(host user port secret))
b8e0f0cd
G
1160 ;; we know (because of an assertion in auth-source-search) that the
1161 ;; :create parameter is either t or a list (which includes nil)
1162 (create-extra (if (eq t create) nil create))
ddb7ffee
LMI
1163 (current-data (car (auth-source-search :max 1
1164 :host host
1165 :port port)))
b8e0f0cd
G
1166 (required (append base-required create-extra))
1167 (file (oref backend source))
1168 (add "")
1169 ;; `valist' is an alist
584c9d3f
G
1170 valist
1171 ;; `artificial' will be returned if no creation is needed
1172 artificial)
b8e0f0cd
G
1173
1174 ;; only for base required elements (defined as function parameters):
1175 ;; fill in the valist with whatever data we may have from the search
7ba93e94 1176 ;; we complete the first value if it's a list and use the value otherwise
b8e0f0cd
G
1177 (dolist (br base-required)
1178 (when (symbol-value br)
7ba93e94
G
1179 (let ((br-choice (cond
1180 ;; all-accepting choice (predicate is t)
1181 ((eq t (symbol-value br)) nil)
1182 ;; just the value otherwise
1183 (t (symbol-value br)))))
1184 (when br-choice
1185 (aput 'valist br br-choice)))))
b8e0f0cd
G
1186
1187 ;; for extra required elements, see if the spec includes a value for them
1188 (dolist (er create-extra)
1189 (let ((name (concat ":" (symbol-name er)))
1190 (keys (loop for i below (length spec) by 2
1191 collect (nth i spec))))
1192 (dolist (k keys)
1193 (when (equal (symbol-name k) name)
1194 (aput 'valist er (plist-get spec k))))))
1195
1196 ;; for each required element
1197 (dolist (r required)
1198 (let* ((data (aget valist r))
4a3988d5 1199 ;; take the first element if the data is a list
ddb7ffee
LMI
1200 (data (or (auth-source-netrc-element-or-first data)
1201 (plist-get current-data
1202 (intern (format ":%s" r) obarray))))
4a3988d5 1203 ;; this is the default to be offered
b8e0f0cd 1204 (given-default (aget auth-source-creation-defaults r))
733afdf4
TZ
1205 ;; the default supplementals are simple:
1206 ;; for the user, try `given-default' and then (user-login-name);
1207 ;; otherwise take `given-default'
b8e0f0cd 1208 (default (cond
733afdf4
TZ
1209 ((and (not given-default) (eq r 'user))
1210 (user-login-name))
003522ce
G
1211 (t given-default)))
1212 (printable-defaults (list
1213 (cons 'user
1214 (or
1215 (auth-source-netrc-element-or-first
1216 (aget valist 'user))
1217 (plist-get artificial :user)
1218 "[any user]"))
1219 (cons 'host
1220 (or
1221 (auth-source-netrc-element-or-first
1222 (aget valist 'host))
1223 (plist-get artificial :host)
1224 "[any host]"))
1225 (cons 'port
1226 (or
1227 (auth-source-netrc-element-or-first
1228 (aget valist 'port))
1229 (plist-get artificial :port)
1230 "[any port]"))))
1231 (prompt (or (aget auth-source-creation-prompts r)
1232 (case r
733afdf4
TZ
1233 (secret "%p password for %u@%h: ")
1234 (user "%p user name for %h: ")
1235 (host "%p host name for user %u: ")
1236 (port "%p port for %u@%h: "))
003522ce
G
1237 (format "Enter %s (%%u@%%h:%%p): " r)))
1238 (prompt (auth-source-format-prompt
1239 prompt
1240 `((?u ,(aget printable-defaults 'user))
1241 (?h ,(aget printable-defaults 'host))
1242 (?p ,(aget printable-defaults 'port))))))
4a3988d5 1243
aa2ebce9 1244 ;; Store the data, prompting for the password if needed.
4a3988d5
G
1245 (setq data
1246 (cond
1247 ((and (null data) (eq r 'secret))
aa2ebce9 1248 ;; Special case prompt for passwords.
61e6a0ac
TZ
1249;; 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)))
1250;; TODO: or maybe leave as (setq auth-source-netrc-use-gpg-tokens 'never)
1251 (let* ((ep (format "Use GPG password tokens in %s?" file))
2b8c5660 1252 (gpg-encrypt
61e6a0ac
TZ
1253 (cond
1254 ((eq auth-source-netrc-use-gpg-tokens 'never)
1255 'never)
1256 ((listp auth-source-netrc-use-gpg-tokens)
1257 (let ((check (copy-sequence
1258 auth-source-netrc-use-gpg-tokens))
1259 item ret)
1260 (while check
1261 (setq item (pop check))
1262 (when (or (eq (car item) t)
1263 (string-match (car item) file))
1264 (setq ret (cdr item))
1265 (setq check nil)))))
1266 (t 'never)))
2b8c5660 1267 (plain (read-passwd prompt)))
61e6a0ac
TZ
1268 ;; ask if we don't know what to do (in which case
1269 ;; auth-source-netrc-use-gpg-tokens must be a list)
1270 (unless gpg-encrypt
1271 (setq gpg-encrypt (if (y-or-n-p ep) 'gpg 'never))
1272 ;; TODO: save the defcustom now? or ask?
1273 (setq auth-source-netrc-use-gpg-tokens
1274 (cons `(,file ,gpg-encrypt)
1275 auth-source-netrc-use-gpg-tokens)))
1276 (if (eq gpg-encrypt 'gpg)
2b8c5660
TZ
1277 (auth-source-epa-make-gpg-token plain file)
1278 plain)))
003522ce 1279 ((null data)
67613d31 1280 (when default
aa2ebce9
SM
1281 (setq prompt
1282 (if (string-match ": *\\'" prompt)
1283 (concat (substring prompt 0 (match-beginning 0))
1284 " (default " default "): ")
1285 (concat prompt "(default " default ") "))))
4248cca2 1286 (read-string prompt nil nil default))
fa41748c 1287 (t (or data default))))
b8e0f0cd 1288
584c9d3f
G
1289 (when data
1290 (setq artificial (plist-put artificial
1291 (intern (concat ":" (symbol-name r)))
1292 (if (eq r 'secret)
1293 (lexical-let ((data data))
1294 (lambda () data))
1295 data))))
1296
aa2ebce9 1297 ;; When r is not an empty string...
b8e0f0cd
G
1298 (when (and (stringp data)
1299 (< 0 (length data)))
4a3988d5
G
1300 ;; this function is not strictly necessary but I think it
1301 ;; makes the code clearer -tzz
1302 (let ((printer (lambda ()
7ba93e94
G
1303 ;; append the key (the symbol name of r)
1304 ;; and the value in r
6a6e4d93 1305 (format "%s%s %s"
7ba93e94
G
1306 ;; prepend a space
1307 (if (zerop (length add)) "" " ")
1308 ;; remap auth-source tokens to netrc
1309 (case r
0adf5618
SM
1310 (user "login")
1311 (host "machine")
1312 (secret "password")
1313 (port "port") ; redundant but clearer
b8e0f0cd 1314 (t (symbol-name r)))
6a6e4d93
LMI
1315 (if (string-match "[\" ]" data)
1316 (format "%S" data)
1317 data)))))
4a3988d5 1318 (setq add (concat add (funcall printer)))))))
b8e0f0cd 1319
733afdf4
TZ
1320 (plist-put
1321 artificial
1322 :save-function
1323 (lexical-let ((file file)
1324 (add add))
1325 (lambda () (auth-source-netrc-saver file add))))
1326
1327 (list artificial)))
1328
4248cca2 1329;;(funcall (plist-get (nth 0 (auth-source-search :host '("nonesuch2") :user "tzz" :port "imap" :create t :max 1)) :save-function))
733afdf4
TZ
1330(defun auth-source-netrc-saver (file add)
1331 "Save a line ADD in FILE, prompting along the way.
4248cca2
TZ
1332Respects `auth-source-save-behavior'. Uses
1333`auth-source-netrc-cache' to avoid prompting more than once."
1334 (let* ((key (format "%s %s" file (rfc2104-hash 'md5 64 16 file add)))
1335 (cached (assoc key auth-source-netrc-cache)))
1336
1337 (if cached
1338 (auth-source-do-trivia
1339 "auth-source-netrc-saver: found previous run for key %s, returning"
1340 key)
1341 (with-temp-buffer
1342 (when (file-exists-p file)
1343 (insert-file-contents file))
1344 (when auth-source-gpg-encrypt-to
1345 ;; (see bug#7487) making `epa-file-encrypt-to' local to
1346 ;; this buffer lets epa-file skip the key selection query
1347 ;; (see the `local-variable-p' check in
1348 ;; `epa-file-write-region').
1349 (unless (local-variable-p 'epa-file-encrypt-to (current-buffer))
1350 (make-local-variable 'epa-file-encrypt-to))
1351 (if (listp auth-source-gpg-encrypt-to)
1352 (setq epa-file-encrypt-to auth-source-gpg-encrypt-to)))
1353 ;; we want the new data to be found first, so insert at beginning
1354 (goto-char (point-min))
1355
aa2ebce9 1356 ;; Ask AFTER we've successfully opened the file.
4248cca2
TZ
1357 (let ((prompt (format "Save auth info to file %s? " file))
1358 (done (not (eq auth-source-save-behavior 'ask)))
1359 (bufname "*auth-source Help*")
1360 k)
1361 (while (not done)
1362 (setq k (auth-source-read-char-choice prompt '(?y ?n ?N ?e ??)))
1363 (case k
1364 (?y (setq done t))
1365 (?? (save-excursion
1366 (with-output-to-temp-buffer bufname
1367 (princ
1368 (concat "(y)es, save\n"
1369 "(n)o but use the info\n"
1370 "(N)o and don't ask to save again\n"
1371 "(e)dit the line\n"
1372 "(?) for help as you can see.\n"))
aa2ebce9
SM
1373 ;; Why? Doesn't with-output-to-temp-buffer already do
1374 ;; the exact same thing anyway? --Stef
4248cca2
TZ
1375 (set-buffer standard-output)
1376 (help-mode))))
1377 (?n (setq add ""
1378 done t))
3451795c
LMI
1379 (?N
1380 (setq add ""
1381 done t)
1382 (customize-save-variable 'auth-source-save-behavior nil))
4248cca2
TZ
1383 (?e (setq add (read-string "Line to add: " add)))
1384 (t nil)))
1385
1386 (when (get-buffer-window bufname)
1387 (delete-window (get-buffer-window bufname)))
1388
aa2ebce9 1389 ;; Make sure the info is not saved.
4248cca2
TZ
1390 (when (null auth-source-save-behavior)
1391 (setq add ""))
1392
1393 (when (< 0 (length add))
1394 (progn
1395 (unless (bolp)
1396 (insert "\n"))
1397 (insert add "\n")
1398 (write-region (point-min) (point-max) file nil 'silent)
1399 (auth-source-do-debug
1400 "auth-source-netrc-create: wrote 1 new line to %s"
1401 file)
1402 (message "Saved new authentication information to %s" file)
1403 nil))))
1404 (aput 'auth-source-netrc-cache key "ran"))))
b8e0f0cd
G
1405
1406;;; Backend specific parsing: Secrets API backend
1407
1408;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :create t))
1409;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :delete t))
1410;;; (let ((auth-sources '(default))) (auth-source-search :max 1))
1411;;; (let ((auth-sources '(default))) (auth-source-search))
5415d076
G
1412;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1))
1413;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1 :signon_realm "https://git.gnus.org/Git"))
b8e0f0cd
G
1414
1415(defun* auth-source-secrets-search (&rest
1416 spec
1417 &key backend create delete label
35123c04 1418 type max host user port
b8e0f0cd
G
1419 &allow-other-keys)
1420 "Search the Secrets API; spec is like `auth-source'.
1421
1422The :label key specifies the item's label. It is the only key
1423that can specify a substring. Any :label value besides a string
1424will allow any label.
1425
1426All other search keys must match exactly. If you need substring
1427matching, do a wider search and narrow it down yourself.
1428
1429You'll get back all the properties of the token as a plist.
1430
5415d076 1431Here's an example that looks for the first item in the 'Login'
b8e0f0cd
G
1432Secrets collection:
1433
5415d076 1434 \(let ((auth-sources '(\"secrets:Login\")))
b8e0f0cd
G
1435 (auth-source-search :max 1)
1436
5415d076 1437Here's another that looks for the first item in the 'Login'
b8e0f0cd
G
1438Secrets collection whose label contains 'gnus':
1439
5415d076 1440 \(let ((auth-sources '(\"secrets:Login\")))
b8e0f0cd
G
1441 (auth-source-search :max 1 :label \"gnus\")
1442
5415d076 1443And this one looks for the first item in the 'Login' Secrets
b8e0f0cd 1444collection that's a Google Chrome entry for the git.gnus.org site
5415d076 1445authentication tokens:
b8e0f0cd 1446
5415d076 1447 \(let ((auth-sources '(\"secrets:Login\")))
b8e0f0cd
G
1448 (auth-source-search :max 1 :signon_realm \"https://git.gnus.org/Git\"))
1449"
1450
1451 ;; TODO
1452 (assert (not create) nil
1453 "The Secrets API auth-source backend doesn't support creation yet")
1454 ;; TODO
1455 ;; (secrets-delete-item coll elt)
1456 (assert (not delete) nil
1457 "The Secrets API auth-source backend doesn't support deletion yet")
1458
1459 (let* ((coll (oref backend source))
1460 (max (or max 5000)) ; sanity check: default to stop at 5K
1461 (ignored-keys '(:create :delete :max :backend :label))
1462 (search-keys (loop for i below (length spec) by 2
1463 unless (memq (nth i spec) ignored-keys)
1464 collect (nth i spec)))
1465 ;; build a search spec without the ignored keys
1466 ;; if a search key is nil or t (match anything), we skip it
5415d076
G
1467 (search-spec (apply 'append (mapcar
1468 (lambda (k)
1469 (if (or (null (plist-get spec k))
1470 (eq t (plist-get spec k)))
1471 nil
1472 (list k (plist-get spec k))))
1473 search-keys)))
35123c04 1474 ;; needed keys (always including host, login, port, and secret)
d638ac9e 1475 (returned-keys (mm-delete-duplicates (append
35123c04 1476 '(:host :login :port :secret)
d638ac9e 1477 search-keys)))
b8e0f0cd
G
1478 (items (loop for item in (apply 'secrets-search-items coll search-spec)
1479 unless (and (stringp label)
1480 (not (string-match label item)))
1481 collect item))
1482 ;; TODO: respect max in `secrets-search-items', not after the fact
5415d076 1483 (items (butlast items (- (length items) max)))
b8e0f0cd
G
1484 ;; convert the item name to a full plist
1485 (items (mapcar (lambda (item)
1486 (append
1487 ;; make an entry for the secret (password) element
1488 (list
1489 :secret
1490 (lexical-let ((v (secrets-get-secret coll item)))
1491 (lambda () v)))
1492 ;; rewrite the entry from ((k1 v1) (k2 v2)) to plist
5415d076
G
1493 (apply 'append
1494 (mapcar (lambda (entry)
1495 (list (car entry) (cdr entry)))
1496 (secrets-get-attributes coll item)))))
b8e0f0cd
G
1497 items))
1498 ;; ensure each item has each key in `returned-keys'
1499 (items (mapcar (lambda (plist)
1500 (append
5415d076
G
1501 (apply 'append
1502 (mapcar (lambda (req)
1503 (if (plist-get plist req)
1504 nil
1505 (list req nil)))
1506 returned-keys))
b8e0f0cd
G
1507 plist))
1508 items)))
1509 items))
1510
1511(defun* auth-source-secrets-create (&rest
1512 spec
35123c04 1513 &key backend type max host user port
b8e0f0cd
G
1514 &allow-other-keys)
1515 ;; TODO
1516 ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec)
1517 (debug spec))
1518
8977de27
DU
1519;;; Backend specific parsing: PLSTORE backend
1520
1521(defun* auth-source-plstore-search (&rest
1522 spec
1523 &key backend create delete label
1524 type max host user port
1525 &allow-other-keys)
1526 "Search the PLSTORE; spec is like `auth-source'."
b09c3fe0 1527 (let* ((store (oref backend data))
8977de27
DU
1528 (max (or max 5000)) ; sanity check: default to stop at 5K
1529 (ignored-keys '(:create :delete :max :backend :require))
1530 (search-keys (loop for i below (length spec) by 2
1531 unless (memq (nth i spec) ignored-keys)
1532 collect (nth i spec)))
1533 ;; build a search spec without the ignored keys
1534 ;; if a search key is nil or t (match anything), we skip it
1535 (search-spec (apply 'append (mapcar
1536 (lambda (k)
1537 (let ((v (plist-get spec k)))
1538 (if (or (null v)
1539 (eq t v))
1540 nil
1541 (if (stringp v)
1542 (setq v (list v)))
1543 (list k v))))
1544 search-keys)))
1545 ;; needed keys (always including host, login, port, and secret)
1546 (returned-keys (mm-delete-duplicates (append
1547 '(:host :login :port :secret)
1548 search-keys)))
1549 (items (plstore-find store search-spec))
f3078a00 1550 (item-names (mapcar #'car items))
8977de27
DU
1551 (items (butlast items (- (length items) max)))
1552 ;; convert the item to a full plist
1553 (items (mapcar (lambda (item)
1554 (let* ((plist (copy-tree (cdr item)))
1555 (secret (plist-member plist :secret)))
1556 (if secret
1557 (setcar
1558 (cdr secret)
1559 (lexical-let ((v (car (cdr secret))))
1560 (lambda () v))))
1561 plist))
1562 items))
1563 ;; ensure each item has each key in `returned-keys'
1564 (items (mapcar (lambda (plist)
1565 (append
1566 (apply 'append
1567 (mapcar (lambda (req)
1568 (if (plist-get plist req)
1569 nil
1570 (list req nil)))
1571 returned-keys))
1572 plist))
1573 items)))
f3078a00
DU
1574 (cond
1575 ;; if we need to create an entry AND none were found to match
1576 ((and create
1577 (not items))
8977de27
DU
1578
1579 ;; create based on the spec and record the value
1580 (setq items (or
1581 ;; if the user did not want to create the entry
1582 ;; in the file, it will be returned
1583 (apply (slot-value backend 'create-function) spec)
1584 ;; if not, we do the search again without :create
1585 ;; to get the updated data.
1586
1587 ;; the result will be returned, even if the search fails
1588 (apply 'auth-source-plstore-search
1589 (plist-put spec :create nil)))))
f3078a00
DU
1590 ((and delete
1591 item-names)
1592 (dolist (item-name item-names)
1593 (plstore-delete store item-name))
1594 (plstore-save store)))
8977de27
DU
1595 items))
1596
1597(defun* auth-source-plstore-create (&rest spec
1598 &key backend
1599 secret host user port create
1600 &allow-other-keys)
1601 (let* ((base-required '(host user port secret))
1602 (base-secret '(secret))
1603 ;; we know (because of an assertion in auth-source-search) that the
1604 ;; :create parameter is either t or a list (which includes nil)
1605 (create-extra (if (eq t create) nil create))
1606 (current-data (car (auth-source-search :max 1
1607 :host host
1608 :port port)))
1609 (required (append base-required create-extra))
1610 (file (oref backend source))
1611 (add "")
1612 ;; `valist' is an alist
1613 valist
1614 ;; `artificial' will be returned if no creation is needed
1615 artificial
1616 secret-artificial)
1617
1618 ;; only for base required elements (defined as function parameters):
1619 ;; fill in the valist with whatever data we may have from the search
1620 ;; we complete the first value if it's a list and use the value otherwise
1621 (dolist (br base-required)
1622 (when (symbol-value br)
1623 (let ((br-choice (cond
1624 ;; all-accepting choice (predicate is t)
1625 ((eq t (symbol-value br)) nil)
1626 ;; just the value otherwise
1627 (t (symbol-value br)))))
1628 (when br-choice
1629 (aput 'valist br br-choice)))))
1630
1631 ;; for extra required elements, see if the spec includes a value for them
1632 (dolist (er create-extra)
1633 (let ((name (concat ":" (symbol-name er)))
1634 (keys (loop for i below (length spec) by 2
1635 collect (nth i spec))))
1636 (dolist (k keys)
1637 (when (equal (symbol-name k) name)
1638 (aput 'valist er (plist-get spec k))))))
1639
1640 ;; for each required element
1641 (dolist (r required)
1642 (let* ((data (aget valist r))
1643 ;; take the first element if the data is a list
1644 (data (or (auth-source-netrc-element-or-first data)
1645 (plist-get current-data
1646 (intern (format ":%s" r) obarray))))
1647 ;; this is the default to be offered
1648 (given-default (aget auth-source-creation-defaults r))
1649 ;; the default supplementals are simple:
1650 ;; for the user, try `given-default' and then (user-login-name);
1651 ;; otherwise take `given-default'
1652 (default (cond
1653 ((and (not given-default) (eq r 'user))
1654 (user-login-name))
1655 (t given-default)))
1656 (printable-defaults (list
1657 (cons 'user
1658 (or
1659 (auth-source-netrc-element-or-first
1660 (aget valist 'user))
1661 (plist-get artificial :user)
1662 "[any user]"))
1663 (cons 'host
1664 (or
1665 (auth-source-netrc-element-or-first
1666 (aget valist 'host))
1667 (plist-get artificial :host)
1668 "[any host]"))
1669 (cons 'port
1670 (or
1671 (auth-source-netrc-element-or-first
1672 (aget valist 'port))
1673 (plist-get artificial :port)
1674 "[any port]"))))
1675 (prompt (or (aget auth-source-creation-prompts r)
1676 (case r
1677 (secret "%p password for %u@%h: ")
1678 (user "%p user name for %h: ")
1679 (host "%p host name for user %u: ")
1680 (port "%p port for %u@%h: "))
1681 (format "Enter %s (%%u@%%h:%%p): " r)))
1682 (prompt (auth-source-format-prompt
1683 prompt
1684 `((?u ,(aget printable-defaults 'user))
1685 (?h ,(aget printable-defaults 'host))
1686 (?p ,(aget printable-defaults 'port))))))
1687
1688 ;; Store the data, prompting for the password if needed.
1689 (setq data
1690 (cond
1691 ((and (null data) (eq r 'secret))
1692 ;; Special case prompt for passwords.
1693 (read-passwd prompt))
1694 ((null data)
1695 (when default
1696 (setq prompt
1697 (if (string-match ": *\\'" prompt)
1698 (concat (substring prompt 0 (match-beginning 0))
1699 " (default " default "): ")
1700 (concat prompt "(default " default ") "))))
1701 (read-string prompt nil nil default))
1702 (t (or data default))))
1703
1704 (when data
1705 (if (member r base-secret)
1706 (setq secret-artificial
1707 (plist-put secret-artificial
1708 (intern (concat ":" (symbol-name r)))
1709 data))
1710 (setq artificial (plist-put artificial
1711 (intern (concat ":" (symbol-name r)))
1712 data))))))
b09c3fe0 1713 (plstore-put (oref backend data)
8977de27
DU
1714 (sha1 (format "%s@%s:%s"
1715 (plist-get artificial :user)
1716 (plist-get artificial :host)
1717 (plist-get artificial :port)))
1718 artificial secret-artificial)
1719 (if (y-or-n-p (format "Save auth info to file %s? "
b09c3fe0
G
1720 (plstore-get-file (oref backend data))))
1721 (plstore-save (oref backend data)))))
8977de27 1722
b8e0f0cd
G
1723;;; older API
1724
1725;;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" t "tzz")
1726
1727;; deprecate the old interface
1728(make-obsolete 'auth-source-user-or-password
1729 'auth-source-search "Emacs 24.1")
1730(make-obsolete 'auth-source-forget-user-or-password
1731 'auth-source-forget "Emacs 24.1")
fb178e4c 1732
0e4966fb 1733(defun auth-source-user-or-password
35123c04
TZ
1734 (mode host port &optional username create-missing delete-existing)
1735 "Find MODE (string or list of strings) matching HOST and PORT.
fb178e4c 1736
b8e0f0cd
G
1737DEPRECATED in favor of `auth-source-search'!
1738
fb178e4c
KY
1739USERNAME is optional and will be used as \"login\" in a search
1740across the Secret Service API (see secrets.el) if the resulting
1741items don't have a username. This means that if you search for
1742username \"joe\" and it matches an item but the item doesn't have
1743a :user attribute, the username \"joe\" will be returned.
1744
0e4966fb
MA
1745A non nil DELETE-EXISTING means deleting any matching password
1746entry in the respective sources. This is useful only when
1747CREATE-MISSING is non nil as well; the intended use case is to
1748remove wrong password entries.
1749
1750If no matching entry is found, and CREATE-MISSING is non nil,
1751the password will be retrieved interactively, and it will be
1752stored in the password database which matches best (see
1753`auth-sources').
1754
1755MODE can be \"login\" or \"password\"."
554a69b8 1756 (auth-source-do-debug
b8e0f0cd 1757 "auth-source-user-or-password: DEPRECATED get %s for %s (%s) + user=%s"
35123c04 1758 mode host port username)
b8e0f0cd 1759
3b36c17e 1760 (let* ((listy (listp mode))
cbabe91f
TZ
1761 (mode (if listy mode (list mode)))
1762 (cname (if username
35123c04
TZ
1763 (format "%s %s:%s %s" mode host port username)
1764 (format "%s %s:%s" mode host port)))
1765 (search (list :host host :port port))
cbabe91f 1766 (search (if username (append search (list :user username)) search))
b8e0f0cd
G
1767 (search (if create-missing
1768 (append search (list :create t))
1769 search))
1770 (search (if delete-existing
1771 (append search (list :delete t))
1772 search))
1773 ;; (found (if (not delete-existing)
1774 ;; (gethash cname auth-source-cache)
1775 ;; (remhash cname auth-source-cache)
1776 ;; nil)))
1777 (found nil))
ed778fad 1778 (if found
cbabe91f
TZ
1779 (progn
1780 (auth-source-do-debug
b8e0f0cd 1781 "auth-source-user-or-password: DEPRECATED cached %s=%s for %s (%s) + %s"
cbabe91f
TZ
1782 mode
1783 ;; don't show the password
b8e0f0cd 1784 (if (and (member "password" mode) t)
cbabe91f
TZ
1785 "SECRET"
1786 found)
35123c04 1787 host port username)
cbabe91f 1788 found) ; return the found data
b8e0f0cd
G
1789 ;; else, if not found, search with a max of 1
1790 (let ((choice (nth 0 (apply 'auth-source-search
1791 (append '(:max 1) search)))))
1792 (when choice
1793 (dolist (m mode)
1794 (cond
1795 ((equal "password" m)
1796 (push (if (plist-get choice :secret)
1797 (funcall (plist-get choice :secret))
1798 nil) found))
1799 ((equal "login" m)
1800 (push (plist-get choice :user) found)))))
1801 (setq found (nreverse found))
1802 (setq found (if listy found (car-safe found)))))
9b3ebcb6 1803
b8e0f0cd 1804 found))
8f7abae3
MB
1805
1806(provide 'auth-source)
1807
8f7abae3 1808;;; auth-source.el ends here