1 ;; jabber-sasl.el - SASL authentication -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu
5 ;; This file is a part of jabber.el.
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2 of the License, or
10 ;; (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program; if not, write to the Free Software
19 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 ;;; This file uses sasl.el from FLIM or Gnus. If it can't be found,
24 ;;; jabber-core.el won't use the SASL functions.
30 ;;; Alternatives to FLIM would be the command line utility of GNU SASL,
31 ;;; or anything the Gnus people decide to use.
33 ;;; See XMPP-CORE and XMPP-IM for details about the protocol.
37 (defun jabber-sasl-start-auth (jc stream-features)
38 "Start the SASL authentication mechanism.
39 JC is The Jabber Connection.
40 STREAM-FEATURES the XML parsed \"stream features\" answer (it is used
41 with `jabber-xml-get-chidlren')."
42 ;; Find a suitable common mechanism.
43 (let* ((mechanism-elements (car (jabber-xml-get-children stream-features 'mechanisms)))
46 (car (jabber-xml-node-children tag)))
47 (jabber-xml-get-children mechanism-elements 'mechanism)))
49 (if (and (member "ANONYMOUS" mechanisms)
50 (or jabber-silent-mode (yes-or-no-p "Use anonymous authentication? ")))
51 (sasl-find-mechanism '("ANONYMOUS"))
52 (sasl-find-mechanism mechanisms))))
54 ;; No suitable mechanism?
56 ;; Maybe we can use legacy authentication
57 (let ((iq-auth (cl-find "http://jabber.org/features/iq-auth"
58 (jabber-xml-get-children stream-features 'auth)
59 :key #'jabber-xml-get-xmlns
61 ;; Or maybe we have to use STARTTLS, but can't
62 (starttls (cl-find "urn:ietf:params:xml:ns:xmpp-tls"
63 (jabber-xml-get-children stream-features 'starttls)
64 :key #'jabber-xml-get-xmlns
68 (fsm-send jc :use-legacy-auth-instead))
70 (message "STARTTLS encryption required, but disabled/non-functional at our end")
71 (fsm-send jc :authentication-failure))
73 (message "Authentication failure: no suitable SASL mechanism found")
74 (fsm-send jc :authentication-failure))))
76 ;; Watch for plaintext logins over unencrypted connections
77 (if (and (not (plist-get (fsm-get-state-data jc) :encrypted))
78 (member (sasl-mechanism-name mechanism)
80 (not (yes-or-no-p "Jabber server only allows cleartext password transmission! Continue? ")))
81 (fsm-send jc :authentication-failure)
83 ;; Start authentication.
85 (client (sasl-make-client mechanism
86 (plist-get (fsm-get-state-data jc) :username)
88 (plist-get (fsm-get-state-data jc) :server)))
89 (sasl-read-passphrase (jabber-sasl-read-passphrase-closure
91 (lambda (p) (setq passphrase (copy-sequence p)) p)))
92 (step (sasl-next-step client nil)))
95 `(auth ((xmlns . "urn:ietf:params:xml:ns:xmpp-sasl")
96 (mechanism . ,(sasl-mechanism-name mechanism)))
97 ,(when (sasl-step-data step)
98 (base64-encode-string (sasl-step-data step) t))))
99 (list client step passphrase))))))
101 (defun jabber-sasl-read-passphrase-closure (jc remember)
102 "Return a lambda function suitable for `sasl-read-passphrase' for JC.
103 Call REMEMBER with the password. REMEMBER is expected to return it as well."
104 (let ((password (plist-get (fsm-get-state-data jc) :password))
105 (bare-jid (jabber-connection-bare-jid jc))
108 (lambda (_prompt) (funcall remember (copy-sequence password)))
109 (lambda (_prompt) (funcall remember (jabber-read-password bare-jid))))))
111 (defun jabber-sasl-process-input (jc xml-data sasl-data)
112 "SASL protocol input processing.
114 JC is the Jabber connection.
115 XML-DATA is the parsed tree data from the stream (stanzas)
116 obtained from `xml-parse-region'."
117 (let* ((client (cl-first sasl-data))
118 (step (cl-second sasl-data))
119 (passphrase (cl-third sasl-data))
120 (sasl-read-passphrase (jabber-sasl-read-passphrase-closure
122 (lambda (p) (setq passphrase (copy-sequence p)) p))))
124 ((eq (car xml-data) 'challenge)
125 (sasl-step-set-data step (base64-decode-string (car (jabber-xml-node-children xml-data))))
126 (setq step (sasl-next-step client step))
129 `(response ((xmlns . "urn:ietf:params:xml:ns:xmpp-sasl"))
130 ,(when (sasl-step-data step)
131 (base64-encode-string (sasl-step-data step) t)))))
133 ((eq (car xml-data) 'failure)
134 (message "%s: authentication failure: %s"
135 (jabber-connection-bare-jid jc)
136 (jabber-xml-node-name (car (jabber-xml-node-children xml-data))))
137 (fsm-send jc :authentication-failure))
139 ((eq (car xml-data) 'success)
140 ;; The server might, depending on the mechanism, send
141 ;; "additional data" (see RFC 4422) with the <success/> element.
142 ;; Since some SASL mechanisms perform mutual authentication, we
143 ;; need to pass this data to sasl.el - we're not necessarily
144 ;; done just because the server says we're done.
145 (let* ((data (car (jabber-xml-node-children xml-data)))
147 (base64-decode-string data)
149 (sasl-step-set-data step decoded)
152 ;; Check that sasl-next-step doesn't signal an error.
153 ;; TODO: once sasl.el allows it, check that all steps have
155 (sasl-next-step client step)
156 (message "Authentication succeeded for %s" (jabber-connection-bare-jid jc))
157 (fsm-send jc (cons :authentication-success passphrase)))
159 (message "%s: authentication failure: %s"
160 (jabber-connection-bare-jid jc)
161 (error-message-string e))
162 (fsm-send jc :authentication-failure))))))
163 (list client step passphrase)))
165 (provide 'jabber-sasl)
166 ;;; arch-tag: 2a4a234d-34d3-49dd-950d-518c899c0fd0