Merge changes from emacs-23 branch
[bpt/emacs.git] / lisp / gnus / auth-source.el
CommitLineData
8f7abae3
MB
1;;; auth-source.el --- authentication sources for Gnus and Emacs
2
73b0cd50 3;; Copyright (C) 2008-2011 Free Software Foundation, Inc.
8f7abae3
MB
4
5;; Author: Ted Zlatanov <tzz@lifelogs.com>
6;; Keywords: news
7
8;; This file is part of GNU Emacs.
9
5e809f55 10;; GNU Emacs is free software: you can redistribute it and/or modify
8f7abae3 11;; it under the terms of the GNU General Public License as published by
5e809f55
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
8f7abae3
MB
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
5e809f55 17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8f7abae3
MB
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
5e809f55 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
8f7abae3
MB
22
23;;; Commentary:
24
25;; This is the auth-source.el package. It lets users tell Gnus how to
26;; authenticate in a single place. Simplicity is the goal. Instead
27;; of providing 5000 options, we'll stick to simple, easy to
28;; understand options.
d55fe5bb 29
554a69b8 30;; See the auth.info Info documentation for details.
4079589f 31
cbabe91f
TZ
32;; TODO:
33
34;; - never decode the backend file unless it's necessary
35;; - a more generic way to match backends and search backend contents
36;; - absorb netrc.el and simplify it
37;; - protect passwords better
38;; - allow creating and changing netrc lines (not files) e.g. change a password
39
8f7abae3
MB
40;;; Code:
41
e952b711 42(require 'gnus-util)
1821a7b4 43(require 'netrc)
e952b711 44
8f7abae3 45(eval-when-compile (require 'cl))
0e4966fb
MA
46(autoload 'secrets-create-item "secrets")
47(autoload 'secrets-delete-item "secrets")
ec7995fa
KY
48(autoload 'secrets-get-alias "secrets")
49(autoload 'secrets-get-attribute "secrets")
fb178e4c 50(autoload 'secrets-get-secret "secrets")
0e4966fb
MA
51(autoload 'secrets-list-collections "secrets")
52(autoload 'secrets-search-items "secrets")
8f7abae3
MB
53
54(defgroup auth-source nil
55 "Authentication sources."
9b3ebcb6 56 :version "23.1" ;; No Gnus
8f7abae3
MB
57 :group 'gnus)
58
9b3ebcb6 59(defcustom auth-source-protocols '((imap "imap" "imaps" "143" "993")
cbabe91f
TZ
60 (pop3 "pop3" "pop" "pop3s" "110" "995")
61 (ssh "ssh" "22")
62 (sftp "sftp" "115")
63 (smtp "smtp" "25"))
9b3ebcb6
MB
64 "List of authentication protocols and their names"
65
66 :group 'auth-source
ec7995fa 67 :version "23.2" ;; No Gnus
9b3ebcb6 68 :type '(repeat :tag "Authentication Protocols"
cbabe91f
TZ
69 (cons :tag "Protocol Entry"
70 (symbol :tag "Protocol")
71 (repeat :tag "Names"
72 (string :tag "Name")))))
9b3ebcb6
MB
73
74;;; generate all the protocols in a format Customize can use
fb178e4c 75;;; TODO: generate on the fly from auth-source-protocols
9b3ebcb6
MB
76(defconst auth-source-protocols-customize
77 (mapcar (lambda (a)
cbabe91f
TZ
78 (let ((p (car-safe a)))
79 (list 'const
80 :tag (upcase (symbol-name p))
81 p)))
82 auth-source-protocols))
9b3ebcb6 83
ed778fad
MB
84(defvar auth-source-cache (make-hash-table :test 'equal)
85 "Cache for auth-source data")
86
87(defcustom auth-source-do-cache t
88 "Whether auth-source should cache information."
89 :group 'auth-source
ec7995fa 90 :version "23.2" ;; No Gnus
ed778fad
MB
91 :type `boolean)
92
554a69b8
KY
93(defcustom auth-source-debug nil
94 "Whether auth-source should log debug messages.
95Also see `auth-source-hide-passwords'.
96
97If the value is nil, debug messages are not logged.
98If the value is t, debug messages are logged with `message'.
99 In that case, your authentication data will be in the
100 clear (except for passwords, which are always stripped out).
101If the value is a function, debug messages are logged by calling
102 that function using the same arguments as `message'."
103 :group 'auth-source
ec7995fa 104 :version "23.2" ;; No Gnus
cbabe91f
TZ
105 :type `(choice
106 :tag "auth-source debugging mode"
107 (const :tag "Log using `message' to the *Messages* buffer" t)
108 (function :tag "Function that takes arguments like `message'")
109 (const :tag "Don't log anything" nil)))
554a69b8
KY
110
111(defcustom auth-source-hide-passwords t
112 "Whether auth-source should hide passwords in log messages.
113Only relevant if `auth-source-debug' is not nil."
114 :group 'auth-source
ec7995fa 115 :version "23.2" ;; No Gnus
554a69b8
KY
116 :type `boolean)
117
20a673b2 118(defcustom auth-sources '((:source "~/.authinfo.gpg")
cbabe91f 119 (:source "~/.authinfo"))
8f7abae3
MB
120 "List of authentication sources.
121
fb178e4c
KY
122The default will get login and password information from a .gpg
123file, which you should set up with the EPA/EPG packages to be
124encrypted. See the auth.info manual for details.
125
ec7995fa
KY
126Each entry is the authentication type with optional properties.
127
128It's best to customize this with `M-x customize-variable' because the choices
129can get pretty complex."
8f7abae3 130 :group 'auth-source
ec7995fa 131 :version "23.2" ;; No Gnus
9b3ebcb6 132 :type `(repeat :tag "Authentication Sources"
cbabe91f
TZ
133 (list :tag "Source definition"
134 (const :format "" :value :source)
135 (choice :tag "Authentication backend choice"
136 (string :tag "Authentication Source (file)")
137 (list :tag "secrets.el (Secret Service API/KWallet/GNOME Keyring)"
ec7995fa
KY
138 (const :format "" :value :secrets)
139 (choice :tag "Collection to use"
140 (string :tag "Collection name")
141 (const :tag "Default" 'default)
fb178e4c
KY
142 (const :tag "Login" "login")
143 (const :tag "Temporary" "session"))))
cbabe91f
TZ
144 (repeat :tag "Extra Parameters" :inline t
145 (choice :tag "Extra parameter"
146 (list :tag "Host (omit to match as a fallback)"
147 (const :format "" :value :host)
148 (choice :tag "Host (machine) choice"
149 (const :tag "Any" t)
150 (regexp :tag "Host (machine) regular expression")))
151 (list :tag "Protocol (omit to match as a fallback)"
152 (const :format "" :value :protocol)
153 (choice :tag "Protocol"
154 (const :tag "Any" t)
155 ,@auth-source-protocols-customize))
156 (list :tag "User (omit to match as a fallback)" :inline t
157 (const :format "" :value :user)
158 (choice :tag "Personality or username"
159 (const :tag "Any" t)
160 (string :tag "Specific user name"))))))))
8f7abae3 161
549c9aed
G
162(defcustom auth-source-gpg-encrypt-to t
163 "List of recipient keys that `authinfo.gpg' encrypted to.
164If the value is not a list, symmetric encryption will be used."
165 :group 'auth-source
166 :version "23.2" ;; No Gnus
167 :type '(choice (const :tag "Symmetric encryption" t)
168 (repeat :tag "Recipient public keys"
169 (string :tag "Recipient public key"))))
170
8f7abae3 171;; temp for debugging
9b3ebcb6
MB
172;; (unintern 'auth-source-protocols)
173;; (unintern 'auth-sources)
174;; (customize-variable 'auth-sources)
175;; (setq auth-sources nil)
176;; (format "%S" auth-sources)
177;; (customize-variable 'auth-source-protocols)
178;; (setq auth-source-protocols nil)
179;; (format "%S" auth-source-protocols)
fb178e4c 180;; (auth-source-pick nil :host "a" :port 'imap)
9b3ebcb6
MB
181;; (auth-source-user-or-password "login" "imap.myhost.com" 'imap)
182;; (auth-source-user-or-password "password" "imap.myhost.com" 'imap)
183;; (auth-source-user-or-password-imap "login" "imap.myhost.com")
184;; (auth-source-user-or-password-imap "password" "imap.myhost.com")
185;; (auth-source-protocol-defaults 'imap)
186
554a69b8
KY
187;; (let ((auth-source-debug 'debug)) (auth-source-debug "hello"))
188;; (let ((auth-source-debug t)) (auth-source-debug "hello"))
189;; (let ((auth-source-debug nil)) (auth-source-debug "hello"))
190(defun auth-source-do-debug (&rest msg)
191 ;; set logger to either the function in auth-source-debug or 'message
192 ;; note that it will be 'message if auth-source-debug is nil, so
193 ;; we also check the value
194 (when auth-source-debug
195 (let ((logger (if (functionp auth-source-debug)
cbabe91f
TZ
196 auth-source-debug
197 'message)))
554a69b8
KY
198 (apply logger msg))))
199
fb178e4c
KY
200;; (auth-source-pick nil :host "any" :protocol 'imap :user "joe")
201;; (auth-source-pick t :host "any" :protocol 'imap :user "joe")
0e4966fb 202;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe")
cbabe91f
TZ
203;; (:source (:secrets "session") :host t :protocol t :user "joe")
204;; (:source (:secrets "login") :host t :protocol t)
205;; (:source "~/.authinfo.gpg" :host t :protocol t)))
fb178e4c 206
0e4966fb 207;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe")
cbabe91f
TZ
208;; (:source (:secrets "session") :host t :protocol t :user "joe")
209;; (:source (:secrets "login") :host t :protocol t)
210;; ))
fb178e4c
KY
211
212;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :protocol t)))
213
0e4966fb
MA
214(defun auth-get-source (entry)
215 "Return the source string of ENTRY, which is one entry in `auth-sources'.
216If it is a Secret Service API, return the collection name, otherwise
217the file name."
218 (let ((source (plist-get entry :source)))
219 (if (stringp source)
cbabe91f 220 source
0e4966fb
MA
221 ;; Secret Service API.
222 (setq source (plist-get source :secrets))
223 (when (eq source 'default)
cbabe91f 224 (setq source (or (secrets-get-alias "default") "login")))
0e4966fb
MA
225 (or source "session"))))
226
fb178e4c
KY
227(defun auth-source-pick (&rest spec)
228 "Parse `auth-sources' for matches of the SPEC plist.
229
230Common keys are :host, :protocol, and :user. A value of t in
231SPEC means to always succeed in the match. A string value is
0e4966fb
MA
232matched as a regex."
233 (let ((keys (loop for i below (length spec) by 2 collect (nth i spec)))
cbabe91f 234 choices)
0e4966fb
MA
235 (dolist (choice (copy-tree auth-sources) choices)
236 (let ((source (plist-get choice :source))
cbabe91f
TZ
237 (match t))
238 (when
239 (and
240 ;; Check existence of source.
241 (if (consp source)
242 ;; Secret Service API.
243 (member (auth-get-source choice) (secrets-list-collections))
244 ;; authinfo file.
245 (file-exists-p source))
246
247 ;; Check keywords.
248 (dolist (k keys match)
249 (let* ((v (plist-get spec k))
250 (choicev (if (plist-member choice k)
251 (plist-get choice k) t)))
252 (setq match
253 (and match
254 (or
255 ;; source always matches spec key
256 (eq t choicev)
257 ;; source key gives regex to match against spec
258 (and (stringp choicev) (string-match choicev v))
259 ;; source key gives symbol to match against spec
260 (and (symbolp choicev) (eq choicev v))))))))
261
262 (add-to-list 'choices choice 'append))))))
0e4966fb
MA
263
264(defun auth-source-retrieve (mode entry &rest spec)
265 "Retrieve MODE credentials according to SPEC from ENTRY."
266 (catch 'no-password
267 (let ((host (plist-get spec :host))
cbabe91f
TZ
268 (user (plist-get spec :user))
269 (prot (plist-get spec :protocol))
270 (source (plist-get entry :source))
271 result)
0e4966fb
MA
272 (cond
273 ;; Secret Service API.
274 ((consp source)
cbabe91f
TZ
275 (let ((coll (auth-get-source entry))
276 item)
277 ;; Loop over candidates with a matching host attribute.
278 (dolist (elt (secrets-search-items coll :host host) item)
279 (when (and (or (not user)
280 (string-equal
281 user (secrets-get-attribute coll elt :user)))
282 (or (not prot)
283 (string-equal
284 prot (secrets-get-attribute coll elt :protocol))))
285 (setq item elt)
286 (return elt)))
287 ;; Compose result.
288 (when item
289 (setq result
290 (mapcar (lambda (m)
291 (if (string-equal "password" m)
292 (or (secrets-get-secret coll item)
293 ;; When we do not find a password,
294 ;; we return nil anyway.
295 (throw 'no-password nil))
296 (or (secrets-get-attribute coll item :user)
297 user)))
298 (if (consp mode) mode (list mode)))))
299 (if (consp mode) result (car result))))
0e4966fb
MA
300 ;; Anything else is netrc.
301 (t
cbabe91f
TZ
302 (let ((search (list source (list host) (list (format "%s" prot))
303 (auth-source-protocol-defaults prot))))
304 (setq result
305 (mapcar (lambda (m)
306 (if (string-equal "password" m)
307 (or (apply
308 'netrc-machine-user-or-password m search)
309 ;; When we do not find a password, we
310 ;; return nil anyway.
311 (throw 'no-password nil))
312 (or (apply
313 'netrc-machine-user-or-password m search)
314 user)))
315 (if (consp mode) mode (list mode)))))
316 (if (consp mode) result (car result)))))))
0e4966fb
MA
317
318(defun auth-source-create (mode entry &rest spec)
319 "Create interactively credentials according to SPEC in ENTRY.
320Return structure as specified by MODE."
321 (let* ((host (plist-get spec :host))
cbabe91f
TZ
322 (user (plist-get spec :user))
323 (prot (plist-get spec :protocol))
324 (source (plist-get entry :source))
325 (name (concat (if user (format "%s@" user))
326 host
327 (if prot (format ":%s" prot))))
328 result)
0e4966fb 329 (setq result
cbabe91f
TZ
330 (mapcar
331 (lambda (m)
332 (cons
333 m
334 (cond
335 ((equal "password" m)
336 (let ((passwd (read-passwd
337 (format "Password for %s on %s: " prot host))))
338 (cond
339 ;; Secret Service API.
340 ((consp source)
341 (apply
342 'secrets-create-item
343 (auth-get-source entry) name passwd spec))
344 (t)) ;; netrc not implemented yes.
345 passwd))
346 ((equal "login" m)
347 (or user
dab0271f
G
348 (read-string
349 (format "User name for %s on %s (default %s): " prot host
350 (user-login-name))
351 nil nil (user-login-name))))
cbabe91f
TZ
352 (t
353 "unknownuser"))))
354 (if (consp mode) mode (list mode))))
1821a7b4
LMI
355 ;; Allow the source to save the data.
356 (cond
357 ((consp source)
358 ;; Secret Service API -- not implemented.
359 )
360 (t
361 ;; netrc interface.
362 (when (y-or-n-p (format "Do you want to save this password in %s? "
cbabe91f 363 source))
549c9aed
G
364 ;; the code below is almost same as `netrc-store-data' except
365 ;; the `epa-file-encrypt-to' hack (see bug#7487).
366 (with-temp-buffer
367 (when (file-exists-p source)
368 (insert-file-contents source))
369 (when auth-source-gpg-encrypt-to
370 ;; making `epa-file-encrypt-to' local to this buffer lets
371 ;; epa-file skip the key selection query (see the
372 ;; `local-variable-p' check in `epa-file-write-region').
bb7f5cbc 373 (unless (local-variable-p 'epa-file-encrypt-to (current-buffer))
549c9aed
G
374 (make-local-variable 'epa-file-encrypt-to))
375 (if (listp auth-source-gpg-encrypt-to)
376 (setq epa-file-encrypt-to auth-source-gpg-encrypt-to)))
377 (goto-char (point-max))
378 (unless (bolp)
379 (insert "\n"))
380 (insert (format "machine %s login %s password %s port %s\n"
381 host
382 (or user (cdr (assoc "login" result)))
383 (cdr (assoc "password" result))
384 prot))
385 (write-region (point-min) (point-max) source nil 'silent)))))
1821a7b4 386 (if (consp mode)
cbabe91f 387 (mapcar #'cdr result)
1821a7b4 388 (cdar result))))
0e4966fb
MA
389
390(defun auth-source-delete (entry &rest spec)
391 "Delete credentials according to SPEC in ENTRY."
392 (let ((host (plist-get spec :host))
cbabe91f
TZ
393 (user (plist-get spec :user))
394 (prot (plist-get spec :protocol))
395 (source (plist-get entry :source)))
0e4966fb
MA
396 (cond
397 ;; Secret Service API.
398 ((consp source)
399 (let ((coll (auth-get-source entry)))
cbabe91f
TZ
400 ;; Loop over candidates with a matching host attribute.
401 (dolist (elt (secrets-search-items coll :host host))
402 (when (and (or (not user)
403 (string-equal
404 user (secrets-get-attribute coll elt :user)))
405 (or (not prot)
406 (string-equal
407 prot (secrets-get-attribute coll elt :protocol))))
408 (secrets-delete-item coll elt)))))
0e4966fb
MA
409 (t)))) ;; netrc not implemented yes.
410
411(defun auth-source-forget-user-or-password
412 (mode host protocol &optional username)
413 "Remove cached authentication token."
ed778fad 414 (interactive "slogin/password: \nsHost: \nsProtocol: \n") ;for testing
0e4966fb
MA
415 (remhash
416 (if username
417 (format "%s %s:%s %s" mode host protocol username)
418 (format "%s %s:%s" mode host protocol))
419 auth-source-cache))
ed778fad 420
3b36c17e
MB
421(defun auth-source-forget-all-cached ()
422 "Forget all cached auth-source authentication tokens."
423 (interactive)
424 (setq auth-source-cache (make-hash-table :test 'equal)))
425
fb178e4c
KY
426;; (progn
427;; (auth-source-forget-all-cached)
428;; (list
429;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other")
430;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "tzz")
431;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "joe")))
432
0e4966fb
MA
433(defun auth-source-user-or-password
434 (mode host protocol &optional username create-missing delete-existing)
3b36c17e 435 "Find MODE (string or list of strings) matching HOST and PROTOCOL.
fb178e4c
KY
436
437USERNAME is optional and will be used as \"login\" in a search
438across the Secret Service API (see secrets.el) if the resulting
439items don't have a username. This means that if you search for
440username \"joe\" and it matches an item but the item doesn't have
441a :user attribute, the username \"joe\" will be returned.
442
0e4966fb
MA
443A non nil DELETE-EXISTING means deleting any matching password
444entry in the respective sources. This is useful only when
445CREATE-MISSING is non nil as well; the intended use case is to
446remove wrong password entries.
447
448If no matching entry is found, and CREATE-MISSING is non nil,
449the password will be retrieved interactively, and it will be
450stored in the password database which matches best (see
451`auth-sources').
452
453MODE can be \"login\" or \"password\"."
554a69b8 454 (auth-source-do-debug
fb178e4c
KY
455 "auth-source-user-or-password: get %s for %s (%s) + user=%s"
456 mode host protocol username)
3b36c17e 457 (let* ((listy (listp mode))
cbabe91f
TZ
458 (mode (if listy mode (list mode)))
459 (cname (if username
460 (format "%s %s:%s %s" mode host protocol username)
461 (format "%s %s:%s" mode host protocol)))
462 (search (list :host host :protocol protocol))
463 (search (if username (append search (list :user username)) search))
464 (found (if (not delete-existing)
465 (gethash cname auth-source-cache)
466 (remhash cname auth-source-cache)
467 nil)))
ed778fad 468 (if found
cbabe91f
TZ
469 (progn
470 (auth-source-do-debug
471 "auth-source-user-or-password: cached %s=%s for %s (%s) + %s"
472 mode
473 ;; don't show the password
474 (if (and (member "password" mode) auth-source-hide-passwords)
475 "SECRET"
476 found)
477 host protocol username)
478 found) ; return the found data
fb178e4c 479 ;; else, if not found
0e4966fb 480 (let ((choices (apply 'auth-source-pick search)))
cbabe91f
TZ
481 (dolist (choice choices)
482 (if delete-existing
483 (apply 'auth-source-delete choice search)
484 (setq found (apply 'auth-source-retrieve mode choice search)))
485 (and found (return found)))
486
487 ;; We haven't found something, so we will create it interactively.
488 (when (and (not found) create-missing)
489 (setq found (apply 'auth-source-create
490 mode (if choices
491 (car choices)
492 (car auth-sources))
493 search)))
494
495 ;; Cache the result.
496 (when found
497 (auth-source-do-debug
498 "auth-source-user-or-password: found %s=%s for %s (%s) + %s"
499 mode
500 ;; don't show the password
501 (if (and (member "password" mode) auth-source-hide-passwords)
502 "SECRET" found)
503 host protocol username)
504 (setq found (if listy found (car-safe found)))
505 (when auth-source-do-cache
506 (puthash cname found auth-source-cache)))
507
508 found))))
0e4966fb 509
9b3ebcb6
MB
510(defun auth-source-protocol-defaults (protocol)
511 "Return a list of default ports and names for PROTOCOL."
512 (cdr-safe (assoc protocol auth-source-protocols)))
513
58a67d68
MB
514(defun auth-source-user-or-password-imap (mode host)
515 (auth-source-user-or-password mode host 'imap))
9b3ebcb6 516
58a67d68
MB
517(defun auth-source-user-or-password-pop3 (mode host)
518 (auth-source-user-or-password mode host 'pop3))
9b3ebcb6 519
58a67d68
MB
520(defun auth-source-user-or-password-ssh (mode host)
521 (auth-source-user-or-password mode host 'ssh))
9b3ebcb6 522
58a67d68
MB
523(defun auth-source-user-or-password-sftp (mode host)
524 (auth-source-user-or-password mode host 'sftp))
9b3ebcb6 525
58a67d68
MB
526(defun auth-source-user-or-password-smtp (mode host)
527 (auth-source-user-or-password mode host 'smtp))
8f7abae3
MB
528
529(provide 'auth-source)
530
8f7abae3 531;;; auth-source.el ends here