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