Comparison

plugins/mod_saslauth.lua @ 3651:337391d34b70

s2s: SASL EXTERNAL
author Paul Aurich <paul@darkrain42.org>
date Sun, 21 Nov 2010 21:10:43 -0800
parent 3553:1f0af8572f15
child 3733:26571a99f6e6
comparison
equal deleted inserted replaced
3650:2b80450bd7ae 3651:337391d34b70
9 9
10 10
11 local st = require "util.stanza"; 11 local st = require "util.stanza";
12 local sm_bind_resource = require "core.sessionmanager".bind_resource; 12 local sm_bind_resource = require "core.sessionmanager".bind_resource;
13 local sm_make_authenticated = require "core.sessionmanager".make_authenticated; 13 local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
14 local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
14 local base64 = require "util.encodings".base64; 15 local base64 = require "util.encodings".base64;
16
17 local cert_verify_identity = require "util.certverification".verify_identity;
15 18
16 local nodeprep = require "util.encodings".stringprep.nodeprep; 19 local nodeprep = require "util.encodings".stringprep.nodeprep;
17 local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; 20 local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
18 local t_concat, t_insert = table.concat, table.insert; 21 local t_concat, t_insert = table.concat, table.insert;
19 local tostring = tostring; 22 local tostring = tostring;
89 log("debug", "sasl reply: %s", tostring(s)); 92 log("debug", "sasl reply: %s", tostring(s));
90 session.send(s); 93 session.send(s);
91 return true; 94 return true;
92 end 95 end
93 96
97 module:hook_stanza(xmlns_sasl, "success", function (session, stanza)
98 if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
99 module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host);
100 session.external_auth = "succeeded"
101 session:reset_stream();
102
103 local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
104 ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
105 session.sends2s("<?xml version='1.0'?>");
106 session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
107
108 s2s_make_authenticated(session, session.to_host);
109 return true;
110 end)
111
112 module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
113 if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
114
115 module:log("info", "SASL EXTERNAL with %s failed", session.to_host)
116 -- TODO: Log the failure reason
117 session.external_auth = "failed"
118 end, 500)
119
120 module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
121 -- TODO: Dialback wasn't loaded. Do something useful.
122 end, 90)
123
124 module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
125 if session.type ~= "s2sout_unauthed" or not session.secure then return; end
126
127 local mechanisms = stanza:get_child("mechanisms", xmlns_sasl)
128 if mechanisms then
129 for mech in mechanisms:childtags() do
130 if mech[1] == "EXTERNAL" then
131 module:log("debug", "Initiating SASL EXTERNAL with %s", session.to_host);
132 local reply = st.stanza("auth", {xmlns = xmlns_sasl, mechanism = "EXTERNAL"});
133 reply:text(base64.encode(session.from_host))
134 session.sends2s(reply)
135 session.external_auth = "attempting"
136 return true
137 end
138 end
139 end
140 end, 150);
141
142 local function s2s_external_auth(session, stanza)
143 local mechanism = stanza.attr.mechanism;
144
145 if not session.secure then
146 if mechanism == "EXTERNAL" then
147 session.sends2s(build_reply("failure", "encryption-required"))
148 else
149 session.sends2s(build_reply("failure", "invalid-mechanism"))
150 end
151 return true;
152 end
153
154 if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then
155 session.sends2s(build_reply("failure", "invalid-mechanism"))
156 return true;
157 end
158
159 local text = stanza[1]
160 if not text then
161 session.sends2s(build_reply("failure", "malformed-request"))
162 return true
163 end
164
165 -- Either the value is "=" and we've already verified the external
166 -- cert identity, or the value is a string and either matches the
167 -- from_host (
168
169 text = base64.decode(text)
170 if not text then
171 session.sends2s(build_reply("failure", "incorrect-encoding"))
172 return true;
173 end
174
175 if session.cert_identity_status == "valid" then
176 if text ~= "" and text ~= session.from_host then
177 session.sends2s(build_reply("failure", "invalid-authzid"))
178 return true
179 end
180 else
181 if text == "" then
182 session.sends2s(build_reply("failure", "invalid-authzid"))
183 return true
184 end
185
186 local cert = session.conn:socket():getpeercertificate()
187 if (cert_verify_identity(text, "xmpp-server", cert)) then
188 session.cert_identity_status = "valid"
189 else
190 session.cert_identity_status = "invalid"
191 session.sends2s(build_reply("failure", "invalid-authzid"))
192 return true
193 end
194 end
195
196 session.external_auth = "succeeded"
197
198 if not session.from_host then
199 session.from_host = text;
200 end
201 session.sends2s(build_reply("success"))
202 module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host);
203 s2s_make_authenticated(session, text or session.from_host)
204 session:reset_stream();
205 return true
206 end
207
94 module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event) 208 module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
95 local session, stanza = event.origin, event.stanza; 209 local session, stanza = event.origin, event.stanza;
210 if session.type == "s2sin_unauthed" then
211 return s2s_external_auth(session, stanza)
212 end
213
96 if session.type ~= "c2s_unauthed" then return; end 214 if session.type ~= "c2s_unauthed" then return; end
97 215
98 if session.sasl_handler and session.sasl_handler.selected then 216 if session.sasl_handler and session.sasl_handler.selected then
99 session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one 217 session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one
100 end 218 end
163 end 281 end
164 features:up(); 282 features:up();
165 else 283 else
166 features:tag("bind", bind_attr):tag("required"):up():up(); 284 features:tag("bind", bind_attr):tag("required"):up():up();
167 features:tag("session", xmpp_session_attr):tag("optional"):up():up(); 285 features:tag("session", xmpp_session_attr):tag("optional"):up():up();
286 end
287 end);
288
289 module:hook("s2s-stream-features", function(event)
290 local origin, features = event.origin, event.features;
291 if origin.secure and origin.type == "s2sin_unauthed" then
292 -- Offer EXTERNAL if chain is valid and either we didn't validate
293 -- the identity or it passed.
294 if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable
295 module:log("debug", "Offering SASL EXTERNAL")
296 features:tag("mechanisms", { xmlns = xmlns_sasl })
297 :tag("mechanism"):text("EXTERNAL")
298 :up():up();
299 end
168 end 300 end
169 end); 301 end);
170 302
171 module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event) 303 module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event)
172 local origin, stanza = event.origin, event.stanza; 304 local origin, stanza = event.origin, event.stanza;