auth-source.el 88 KB

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