X-Git-Url: https://cgit.sxemacs.org/?a=blobdiff_plain;f=lisp%2Fauth-source.el;h=3ddbb5679d970c154d0c307dd9a37c0134b939c8;hb=c0ab8e31abf8f9bbe438a18e3a217199dfae02cc;hp=135c6797b76f63b7b6682bd7b18b8944d25d27d0;hpb=7eba7601cf458aa3e7bb0f49c57464dcee5958e4;p=gnus diff --git a/lisp/auth-source.el b/lisp/auth-source.el index 135c6797b..3ddbb5679 100644 --- a/lisp/auth-source.el +++ b/lisp/auth-source.el @@ -1,6 +1,6 @@ ;;; auth-source.el --- authentication sources for Gnus and Emacs -;; Copyright (C) 2008-2011 Free Software Foundation, Inc. +;; Copyright (C) 2008-2012 Free Software Foundation, Inc. ;; Author: Ted Zlatanov ;; Keywords: news @@ -49,11 +49,11 @@ (or (ignore-errors (require 'eieio)) ;; gnus-fallback-lib/ from gnus/lisp/gnus-fallback-lib (ignore-errors - (let ((load-path (cons (expand-file-name - "gnus-fallback-lib/eieio" - (file-name-directory (locate-library "gnus"))) - load-path))) - (require 'eieio))) + (let ((load-path (cons (expand-file-name + "gnus-fallback-lib/eieio" + (file-name-directory (locate-library "gnus"))) + load-path))) + (require 'eieio))) (error "eieio not found in `load-path' or gnus-fallback-lib/ directory."))) @@ -70,18 +70,18 @@ (autoload 'plstore-open "plstore") (autoload 'plstore-find "plstore") (autoload 'plstore-put "plstore") +(autoload 'plstore-delete "plstore") (autoload 'plstore-save "plstore") (autoload 'plstore-get-file "plstore") -(autoload 'epa-passphrase-callback-function "epa") - -(autoload 'epg-context-operation "epg") (autoload 'epg-make-context "epg") (autoload 'epg-context-set-passphrase-callback "epg") (autoload 'epg-decrypt-string "epg") (autoload 'epg-context-set-armor "epg") (autoload 'epg-encrypt-string "epg") +(autoload 'help-mode "help-mode" nil t) + (defvar secrets-enabled) (defgroup auth-source nil @@ -94,6 +94,7 @@ "How many seconds passwords are cached, or nil to disable expiring. Overrides `password-cache-expiry' through a let-binding." + :version "24.1" :group 'auth-source :type '(choice (const :tag "Never" nil) (const :tag "All Day" 86400) @@ -101,6 +102,9 @@ let-binding." (const :tag "30 Minutes" 1800) (integer :tag "Seconds"))) +;;; The slots below correspond with the `auth-source-search' spec, +;;; so a backend with :host set, for instance, would match only +;;; searches for that host. Normally they are nil. (defclass auth-source-backend () ((type :initarg :type :initform 'netrc @@ -126,7 +130,7 @@ let-binding." :type t :custom string :documentation "The backend protocol.") - (data :initarg :arg + (data :initarg :data :initform nil :documentation "Internal backend data.") (create-function :initarg :create-function @@ -294,9 +298,9 @@ can get pretty complex." (const :format "" :value :user) (choice :tag "Personality/Username" - (const :tag "Any" t) - (string - :tag "Name"))))))))) + (const :tag "Any" t) + (string + :tag "Name"))))))))) (defcustom auth-source-gpg-encrypt-to t "List of recipient keys that `authinfo.gpg' encrypted to. @@ -337,8 +341,8 @@ If the value is not a list, symmetric encryption will be used." (defun auth-source-do-warn (&rest msg) (apply - ;; set logger to either the function in auth-source-debug or 'message - ;; note that it will be 'message if auth-source-debug is nil + ;; set logger to either the function in auth-source-debug or 'message + ;; note that it will be 'message if auth-source-debug is nil (if (functionp auth-source-debug) auth-source-debug 'message) @@ -406,19 +410,19 @@ with \"[a/b/c] \" if CHOICES is '\(?a ?b ?c\)." ;; a file name with parameters ((stringp (plist-get entry :source)) (if (equal (file-name-extension (plist-get entry :source)) "plist") - (auth-source-backend - (plist-get entry :source) - :source (plist-get entry :source) - :type 'plstore - :search-function 'auth-source-plstore-search - :create-function 'auth-source-plstore-create - :data (plstore-open (plist-get entry :source))) + (auth-source-backend + (plist-get entry :source) + :source (plist-get entry :source) + :type 'plstore + :search-function 'auth-source-plstore-search + :create-function 'auth-source-plstore-create + :data (plstore-open (plist-get entry :source))) (auth-source-backend - (plist-get entry :source) - :source (plist-get entry :source) - :type 'netrc - :search-function 'auth-source-netrc-search - :create-function 'auth-source-netrc-create))) + (plist-get entry :source) + :source (plist-get entry :source) + :type 'netrc + :search-function 'auth-source-netrc-search + :create-function 'auth-source-netrc-create))) ;; the Secrets API. We require the package, in order to have a ;; defined value for `secrets-enabled'. @@ -692,7 +696,7 @@ must call it to obtain the actual value." (when auth-source-do-cache (auth-source-remember spec found))) - found)) + found)) (defun auth-source-search-backends (backends spec max create delete require) (let (matches) @@ -756,28 +760,31 @@ Returns the deleted entries." do (password-cache-remove (symbol-name sym))) (setq auth-source-netrc-cache nil)) +(defun auth-source-format-cache-entry (spec) + "Format SPEC entry to put it in the password cache." + (concat auth-source-magic (format "%S" spec))) + (defun auth-source-remember (spec found) "Remember FOUND search results for SPEC." (let ((password-cache-expiry auth-source-cache-expiry)) (password-cache-add - (concat auth-source-magic (format "%S" spec)) found))) + (auth-source-format-cache-entry spec) found))) (defun auth-source-recall (spec) "Recall FOUND search results for SPEC." - (password-read-from-cache - (concat auth-source-magic (format "%S" spec)))) + (password-read-from-cache (auth-source-format-cache-entry spec))) (defun auth-source-remembered-p (spec) "Check if SPEC is remembered." (password-in-cache-p - (concat auth-source-magic (format "%S" spec)))) + (auth-source-format-cache-entry spec))) (defun auth-source-forget (spec) "Forget any cached data matching SPEC exactly. This is the same SPEC you passed to `auth-source-search'. Returns t or nil for forgotten or not found." - (password-cache-remove (concat auth-source-magic (format "%S" spec)))) + (password-cache-remove (auth-source-format-cache-entry spec))) ;;; (loop for sym being the symbols of password-data when (string-match (concat "^" auth-source-magic) (symbol-name sym)) collect (symbol-name sym)) @@ -814,7 +821,7 @@ while \(:host t) would find all host entries." (defun auth-source-specmatchp (spec stored) (let ((keys (loop for i below (length spec) by 2 - collect (nth i spec)))) + collect (nth i spec)))) (not (eq (dolist (key keys) (unless (auth-source-search-collection (plist-get stored key) @@ -849,10 +856,10 @@ while \(:host t) would find all host entries." (unless (listp values) (setq values (list values))) (mapcar (lambda (value) - (if (numberp value) - (format "%s" value) - value)) - values)) + (if (numberp value) + (format "%s" value) + value)) + values)) ;;; Backend specific parsing: netrc/authinfo backend @@ -893,11 +900,8 @@ Note that the MAX parameter is used so we can exit the parse early." ;; (note for the irony-impaired: they are just obfuscated) (aput 'auth-source-netrc-cache file (list :mtime (nth 5 (file-attributes file)) - :secret (lexical-let ((v (rot13-string - (base64-encode-string - (buffer-string))))) - (lambda () (base64-decode-string - (rot13-string v))))))) + :secret (lexical-let ((v (mapcar '1+ (buffer-string)))) + (lambda () (apply 'string (mapcar '1- v))))))) (goto-char (point-min)) ;; Go through the file, line by line. (while (and (not (eobp)) @@ -964,7 +968,7 @@ Note that the MAX parameter is used so we can exit the parse early." (null require) ;; every element of require is in the normalized list (let ((normalized (nth 0 (auth-source-netrc-normalize - (list alist) file)))) + (list alist) file)))) (loop for req in require always (plist-get normalized req))))) (decf max) @@ -1002,44 +1006,25 @@ Note that the MAX parameter is used so we can exit the parse early." (defvar auth-source-passphrase-alist nil) -(defun auth-source-passphrase-callback-function (context key-id handback - &optional sym-detail) - "Exactly like `epa-passphrase-callback-function' but takes an -extra SYM-DETAIL parameter which will be printed at the end of -the symmetric passphrase prompt, and assumes symmetric -encryption." - (read-passwd - (format "Passphrase for symmetric encryption%s%s: " - ;; Add the file name to the prompt, if any. - (if (stringp handback) - (format " for %s" handback) - "") - (if (stringp sym-detail) - sym-detail - "")) - (eq (epg-context-operation context) 'encrypt))) - (defun auth-source-token-passphrase-callback-function (context key-id file) - (if (eq key-id 'SYM) - (let* ((file (file-truename file)) - (entry (assoc file auth-source-passphrase-alist)) - passphrase) - ;; return the saved passphrase, calling a function if needed - (or (copy-sequence (if (functionp (cdr entry)) - (funcall (cdr entry)) - (cdr entry))) - (progn - (unless entry - (setq entry (list file)) - (push entry auth-source-passphrase-alist)) - (setq passphrase (auth-source-passphrase-callback-function context - key-id - file - " tokens")) - (setcdr entry (lexical-let ((p (copy-sequence passphrase))) - (lambda () p))) - passphrase))) - (epa-passphrase-callback-function context key-id file))) + (let* ((file (file-truename file)) + (entry (assoc file auth-source-passphrase-alist)) + passphrase) + ;; return the saved passphrase, calling a function if needed + (or (copy-sequence (if (functionp (cdr entry)) + (funcall (cdr entry)) + (cdr entry))) + (progn + (unless entry + (setq entry (list file)) + (push entry auth-source-passphrase-alist)) + (setq passphrase + (read-passwd + (format "Passphrase for %s tokens: " file) + t)) + (setcdr entry (lexical-let ((p (copy-sequence passphrase))) + (lambda () p))) + passphrase)))) ;; (auth-source-epa-extract-gpg-token "gpg:LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tClZlcnNpb246IEdudVBHIHYxLjQuMTEgKEdOVS9MaW51eCkKCmpBMEVBd01DT25qMjB1ak9rZnRneVI3K21iNm9aZWhuLzRad3cySkdlbnVaKzRpeEswWDY5di9icDI1U1dsQT0KPS9yc2wKLS0tLS1FTkQgUEdQIE1FU1NBR0UtLS0tLQo=" "~/.netrc") (defun auth-source-epa-extract-gpg-token (secret file) @@ -1105,11 +1090,11 @@ FILE is the file from which we obtained this token." (when token-decoder (setq lexv (funcall token-decoder lexv))) lexv)))) - (setq ret (plist-put ret - (intern (concat ":" k)) - v)))) - ret)) - alist)) + (setq ret (plist-put ret + (intern (concat ":" k)) + v)))) + ret)) + alist)) ;;; (setq secret (plist-get (nth 0 (auth-source-search :host t :type 'netrc :K 1 :max 1)) :secret)) ;;; (funcall secret) @@ -1119,7 +1104,7 @@ FILE is the file from which we obtained this token." &key backend require create delete type max host user port &allow-other-keys) -"Given a property list SPEC, return search matches from the :backend. + "Given a property list SPEC, return search matches from the :backend. See `auth-source-search' for details on SPEC." ;; just in case, check that the type is correct (null or same as the backend) (assert (or (null type) (eq type (oref backend type))) @@ -1169,9 +1154,9 @@ See `auth-source-search' for details on SPEC." ;; we know (because of an assertion in auth-source-search) that the ;; :create parameter is either t or a list (which includes nil) (create-extra (if (eq t create) nil create)) - (current-data (car (auth-source-search :max 1 - :host host - :port port))) + (current-data (car (auth-source-search :max 1 + :host host + :port port))) (required (append base-required create-extra)) (file (oref backend source)) (add "") @@ -1207,8 +1192,8 @@ See `auth-source-search' for details on SPEC." (let* ((data (aget valist r)) ;; take the first element if the data is a list (data (or (auth-source-netrc-element-or-first data) - (plist-get current-data - (intern (format ":%s" r) obarray)))) + (plist-get current-data + (intern (format ":%s" r) obarray)))) ;; this is the default to be offered (given-default (aget auth-source-creation-defaults r)) ;; the default supplementals are simple: @@ -1251,49 +1236,46 @@ See `auth-source-search' for details on SPEC." (?p ,(aget printable-defaults 'port)))))) ;; Store the data, prompting for the password if needed. - (setq data - (cond - ((and (null data) (eq r 'secret)) - ;; Special case prompt for passwords. -;; 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))) -;; TODO: or maybe leave as (setq auth-source-netrc-use-gpg-tokens 'never) - (let* ((ep (format "Use GPG password tokens in %s?" file)) - (gpg-encrypt - (cond - ((eq auth-source-netrc-use-gpg-tokens 'never) - 'never) - ((listp auth-source-netrc-use-gpg-tokens) - (let ((check (copy-sequence - auth-source-netrc-use-gpg-tokens)) - item ret) - (while check - (setq item (pop check)) - (when (or (eq (car item) t) - (string-match (car item) file)) - (setq ret (cdr item)) - (setq check nil))))) - (t 'never))) - (plain (read-passwd prompt))) - ;; ask if we don't know what to do (in which case - ;; auth-source-netrc-use-gpg-tokens must be a list) - (unless gpg-encrypt - (setq gpg-encrypt (if (y-or-n-p ep) 'gpg 'never)) - ;; TODO: save the defcustom now? or ask? - (setq auth-source-netrc-use-gpg-tokens - (cons `(,file ,gpg-encrypt) - auth-source-netrc-use-gpg-tokens))) - (if (eq gpg-encrypt 'gpg) - (auth-source-epa-make-gpg-token plain file) - plain))) - ((null data) - (when default - (setq prompt - (if (string-match ": *\\'" prompt) - (concat (substring prompt 0 (match-beginning 0)) - " (default " default "): ") - (concat prompt "(default " default ") ")))) - (read-string prompt nil nil default)) - (t (or data default)))) + (setq data (or data + (if (eq r 'secret) + ;; Special case prompt for passwords. + ;; 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))) + ;; TODO: or maybe leave as (setq auth-source-netrc-use-gpg-tokens 'never) + (let* ((ep (format "Use GPG password tokens in %s?" file)) + (gpg-encrypt + (cond + ((eq auth-source-netrc-use-gpg-tokens 'never) + 'never) + ((listp auth-source-netrc-use-gpg-tokens) + (let ((check (copy-sequence + auth-source-netrc-use-gpg-tokens)) + item ret) + (while check + (setq item (pop check)) + (when (or (eq (car item) t) + (string-match (car item) file)) + (setq ret (cdr item)) + (setq check nil))))) + (t 'never))) + (plain (or (eval default) (read-passwd prompt)))) + ;; ask if we don't know what to do (in which case + ;; auth-source-netrc-use-gpg-tokens must be a list) + (unless gpg-encrypt + (setq gpg-encrypt (if (y-or-n-p ep) 'gpg 'never)) + ;; TODO: save the defcustom now? or ask? + (setq auth-source-netrc-use-gpg-tokens + (cons `(,file ,gpg-encrypt) + auth-source-netrc-use-gpg-tokens))) + (if (eq gpg-encrypt 'gpg) + (auth-source-epa-make-gpg-token plain file) + plain)) + (if (stringp default) + (read-string (if (string-match ": *\\'" prompt) + (concat (substring prompt 0 (match-beginning 0)) + " (default " default "): ") + (concat prompt "(default " default ") ")) + nil nil default) + (eval default))))) (when data (setq artificial (plist-put artificial @@ -1321,9 +1303,9 @@ See `auth-source-search' for details on SPEC." (secret "password") (port "port") ; redundant but clearer (t (symbol-name r))) - (if (string-match "[\" ]" data) - (format "%S" data) - data))))) + (if (string-match "[\" ]" data) + (format "%S" data) + data))))) (setq add (concat add (funcall printer))))))) (plist-put @@ -1386,9 +1368,9 @@ Respects `auth-source-save-behavior'. Uses (?n (setq add "" done t)) (?N - (setq add "" - done t) - (customize-save-variable 'auth-source-save-behavior nil)) + (setq add "" + done t) + (customize-save-variable 'auth-source-save-behavior nil)) (?e (setq add (read-string "Line to add: " add))) (t nil))) @@ -1405,6 +1387,8 @@ Respects `auth-source-save-behavior'. Uses (insert "\n")) (insert add "\n") (write-region (point-min) (point-max) file nil 'silent) + ;; Make the .authinfo file non-world-readable. + (set-file-modes file #o600) (auth-source-do-debug "auth-source-netrc-create: wrote 1 new line to %s" file) @@ -1479,11 +1463,11 @@ authentication tokens: (eq t (plist-get spec k))) nil (list k (plist-get spec k)))) - search-keys))) + search-keys))) ;; needed keys (always including host, login, port, and secret) (returned-keys (mm-delete-duplicates (append - '(:host :login :port :secret) - search-keys))) + '(:host :login :port :secret) + search-keys))) (items (loop for item in (apply 'secrets-search-items coll search-spec) unless (and (stringp label) (not (string-match label item))) @@ -1533,11 +1517,6 @@ authentication tokens: type max host user port &allow-other-keys) "Search the PLSTORE; spec is like `auth-source'." - - ;; TODO - (assert (not delete) nil - "The PLSTORE auth-source backend doesn't support deletion yet") - (let* ((store (oref backend data)) (max (or max 5000)) ; sanity check: default to stop at 5K (ignored-keys '(:create :delete :max :backend :require)) @@ -1548,30 +1527,31 @@ authentication tokens: ;; if a search key is nil or t (match anything), we skip it (search-spec (apply 'append (mapcar (lambda (k) - (let ((v (plist-get spec k))) - (if (or (null v) - (eq t v)) - nil - (if (stringp v) - (setq v (list v))) - (list k v)))) - search-keys))) + (let ((v (plist-get spec k))) + (if (or (null v) + (eq t v)) + nil + (if (stringp v) + (setq v (list v))) + (list k v)))) + search-keys))) ;; needed keys (always including host, login, port, and secret) (returned-keys (mm-delete-duplicates (append - '(:host :login :port :secret) - search-keys))) + '(:host :login :port :secret) + search-keys))) (items (plstore-find store search-spec)) + (item-names (mapcar #'car items)) (items (butlast items (- (length items) max))) ;; convert the item to a full plist (items (mapcar (lambda (item) - (let* ((plist (copy-tree (cdr item))) - (secret (plist-member plist :secret))) - (if secret - (setcar - (cdr secret) - (lexical-let ((v (car (cdr secret)))) - (lambda () v)))) - plist)) + (let* ((plist (copy-tree (cdr item))) + (secret (plist-member plist :secret))) + (if secret + (setcar + (cdr secret) + (lexical-let ((v (car (cdr secret)))) + (lambda () v)))) + plist)) items)) ;; ensure each item has each key in `returned-keys' (items (mapcar (lambda (plist) @@ -1584,35 +1564,41 @@ authentication tokens: returned-keys)) plist)) items))) - ;; if we need to create an entry AND none were found to match - (when (and create - (not items)) + (cond + ;; if we need to create an entry AND none were found to match + ((and create + (not items)) ;; create based on the spec and record the value (setq items (or - ;; if the user did not want to create the entry - ;; in the file, it will be returned - (apply (slot-value backend 'create-function) spec) - ;; if not, we do the search again without :create - ;; to get the updated data. - - ;; the result will be returned, even if the search fails - (apply 'auth-source-plstore-search - (plist-put spec :create nil))))) + ;; if the user did not want to create the entry + ;; in the file, it will be returned + (apply (slot-value backend 'create-function) spec) + ;; if not, we do the search again without :create + ;; to get the updated data. + + ;; the result will be returned, even if the search fails + (apply 'auth-source-plstore-search + (plist-put spec :create nil))))) + ((and delete + item-names) + (dolist (item-name item-names) + (plstore-delete store item-name)) + (plstore-save store))) items)) (defun* auth-source-plstore-create (&rest spec - &key backend - secret host user port create - &allow-other-keys) + &key backend + secret host user port create + &allow-other-keys) (let* ((base-required '(host user port secret)) - (base-secret '(secret)) + (base-secret '(secret)) ;; we know (because of an assertion in auth-source-search) that the ;; :create parameter is either t or a list (which includes nil) (create-extra (if (eq t create) nil create)) - (current-data (car (auth-source-search :max 1 - :host host - :port port))) + (current-data (car (auth-source-search :max 1 + :host host + :port port))) (required (append base-required create-extra)) (file (oref backend source)) (add "") @@ -1620,7 +1606,7 @@ authentication tokens: valist ;; `artificial' will be returned if no creation is needed artificial - secret-artificial) + secret-artificial) ;; only for base required elements (defined as function parameters): ;; fill in the valist with whatever data we may have from the search @@ -1649,8 +1635,8 @@ authentication tokens: (let* ((data (aget valist r)) ;; take the first element if the data is a list (data (or (auth-source-netrc-element-or-first data) - (plist-get current-data - (intern (format ":%s" r) obarray)))) + (plist-get current-data + (intern (format ":%s" r) obarray)))) ;; this is the default to be offered (given-default (aget auth-source-creation-defaults r)) ;; the default supplementals are simple: @@ -1693,39 +1679,35 @@ authentication tokens: (?p ,(aget printable-defaults 'port)))))) ;; Store the data, prompting for the password if needed. - (setq data - (cond - ((and (null data) (eq r 'secret)) - ;; Special case prompt for passwords. - (read-passwd prompt)) - ((null data) - (when default - (setq prompt - (if (string-match ": *\\'" prompt) - (concat (substring prompt 0 (match-beginning 0)) - " (default " default "): ") - (concat prompt "(default " default ") ")))) - (read-string prompt nil nil default)) - (t (or data default)))) + (setq data (or data + (if (eq r 'secret) + (or (eval default) (read-passwd prompt)) + (if (stringp default) + (read-string (if (string-match ": *\\'" prompt) + (concat (substring prompt 0 (match-beginning 0)) + " (default " default "): ") + (concat prompt "(default " default ") ")) + nil nil default) + (eval default))))) (when data - (if (member r base-secret) - (setq secret-artificial - (plist-put secret-artificial - (intern (concat ":" (symbol-name r))) - data)) - (setq artificial (plist-put artificial - (intern (concat ":" (symbol-name r))) - data)))))) + (if (member r base-secret) + (setq secret-artificial + (plist-put secret-artificial + (intern (concat ":" (symbol-name r))) + data)) + (setq artificial (plist-put artificial + (intern (concat ":" (symbol-name r))) + data)))))) (plstore-put (oref backend data) - (sha1 (format "%s@%s:%s" - (plist-get artificial :user) - (plist-get artificial :host) - (plist-get artificial :port))) - artificial secret-artificial) + (sha1 (format "%s@%s:%s" + (plist-get artificial :user) + (plist-get artificial :host) + (plist-get artificial :port))) + artificial secret-artificial) (if (y-or-n-p (format "Save auth info to file %s? " - (plstore-get-file (oref backend data)))) - (plstore-save (oref backend data))))) + (plstore-get-file (oref backend data)))) + (plstore-save (oref backend data))))) ;;; older API @@ -1801,14 +1783,34 @@ MODE can be \"login\" or \"password\"." (cond ((equal "password" m) (push (if (plist-get choice :secret) - (funcall (plist-get choice :secret)) - nil) found)) + (funcall (plist-get choice :secret)) + nil) found)) ((equal "login" m) (push (plist-get choice :user) found))))) (setq found (nreverse found)) (setq found (if listy found (car-safe found))))) - found)) + found)) + +(defun auth-source-user-and-password (host &optional user) + (let* ((auth-info (car + (if user + (auth-source-search + :host host + :user "yourusername" + :max 1 + :require '(:user :secret) + :create nil) + (auth-source-search + :host host + :max 1 + :require '(:user :secret) + :create nil)))) + (user (plist-get auth-info :user)) + (password (plist-get auth-info :secret))) + (when (functionp password) + (setq password (funcall password))) + (list user password auth-info))) (provide 'auth-source)