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