auth-source.el 91 KB

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