Mercurial > prosody-hg
comparison plugins/mod_saslauth.lua @ 6428:3ee09bfe16e1
Merge 0.10->trunk
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Tue, 23 Sep 2014 23:22:13 +0200 |
| parents | 7653bbd5247e |
| children | edc63dc72566 |
comparison
equal
deleted
inserted
replaced
| 6423:1c78f10f05d0 | 6428:3ee09bfe16e1 |
|---|---|
| 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 base64 = require "util.encodings".base64; | 14 local base64 = require "util.encodings".base64; |
| 15 | 15 |
| 16 local cert_verify_identity = require "util.x509".verify_identity; | |
| 17 | |
| 18 local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; | 16 local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; |
| 19 local tostring = tostring; | 17 local tostring = tostring; |
| 20 | 18 |
| 21 local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption"); | 19 local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption"); |
| 22 local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth") | 20 local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth") |
| 26 local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl'; | 24 local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl'; |
| 27 local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind'; | 25 local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind'; |
| 28 | 26 |
| 29 local function build_reply(status, ret, err_msg) | 27 local function build_reply(status, ret, err_msg) |
| 30 local reply = st.stanza(status, {xmlns = xmlns_sasl}); | 28 local reply = st.stanza(status, {xmlns = xmlns_sasl}); |
| 31 if status == "challenge" then | 29 if status == "failure" then |
| 32 --log("debug", "CHALLENGE: %s", ret or ""); | |
| 33 reply:text(base64.encode(ret or "")); | |
| 34 elseif status == "failure" then | |
| 35 reply:tag(ret):up(); | 30 reply:tag(ret):up(); |
| 36 if err_msg then reply:tag("text"):text(err_msg); end | 31 if err_msg then reply:tag("text"):text(err_msg); end |
| 37 elseif status == "success" then | 32 elseif status == "challenge" or status == "success" then |
| 38 --log("debug", "SUCCESS: %s", ret or ""); | 33 if ret == "" then |
| 39 reply:text(base64.encode(ret or "")); | 34 reply:text("=") |
| 35 elseif ret then | |
| 36 reply:text(base64.encode(ret)); | |
| 37 end | |
| 40 else | 38 else |
| 41 module:log("error", "Unknown sasl status: %s", status); | 39 module:log("error", "Unknown sasl status: %s", status); |
| 42 end | 40 end |
| 43 return reply; | 41 return reply; |
| 44 end | 42 end |
| 97 if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end | 95 if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end |
| 98 | 96 |
| 99 module:log("info", "SASL EXTERNAL with %s failed", session.to_host) | 97 module:log("info", "SASL EXTERNAL with %s failed", session.to_host) |
| 100 -- TODO: Log the failure reason | 98 -- TODO: Log the failure reason |
| 101 session.external_auth = "failed" | 99 session.external_auth = "failed" |
| 100 session:close(); | |
| 101 return true; | |
| 102 end, 500) | 102 end, 500) |
| 103 | |
| 104 module:hook_stanza(xmlns_sasl, "failure", function (session, stanza) | |
| 105 -- TODO: Dialback wasn't loaded. Do something useful. | |
| 106 end, 90) | |
| 107 | 103 |
| 108 module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) | 104 module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) |
| 109 if session.type ~= "s2sout_unauthed" or not session.secure then return; end | 105 if session.type ~= "s2sout_unauthed" or not session.secure then return; end |
| 110 | 106 |
| 111 local mechanisms = stanza:get_child("mechanisms", xmlns_sasl) | 107 local mechanisms = stanza:get_child("mechanisms", xmlns_sasl) |
| 122 end | 118 end |
| 123 end | 119 end |
| 124 end, 150); | 120 end, 150); |
| 125 | 121 |
| 126 local function s2s_external_auth(session, stanza) | 122 local function s2s_external_auth(session, stanza) |
| 123 if session.external_auth ~= "offered" then return end -- Unexpected request | |
| 124 | |
| 127 local mechanism = stanza.attr.mechanism; | 125 local mechanism = stanza.attr.mechanism; |
| 128 | 126 |
| 127 if mechanism ~= "EXTERNAL" then | |
| 128 session.sends2s(build_reply("failure", "invalid-mechanism")); | |
| 129 return true; | |
| 130 end | |
| 131 | |
| 129 if not session.secure then | 132 if not session.secure then |
| 130 if mechanism == "EXTERNAL" then | 133 session.sends2s(build_reply("failure", "encryption-required")); |
| 131 session.sends2s(build_reply("failure", "encryption-required")) | 134 return true; |
| 132 else | 135 end |
| 133 session.sends2s(build_reply("failure", "invalid-mechanism")) | 136 |
| 134 end | 137 local text = stanza[1]; |
| 135 return true; | |
| 136 end | |
| 137 | |
| 138 if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then | |
| 139 session.sends2s(build_reply("failure", "invalid-mechanism")) | |
| 140 return true; | |
| 141 end | |
| 142 | |
| 143 local text = stanza[1] | |
| 144 if not text then | 138 if not text then |
| 145 session.sends2s(build_reply("failure", "malformed-request")) | 139 session.sends2s(build_reply("failure", "malformed-request")); |
| 146 return true | 140 return true; |
| 147 end | 141 end |
| 148 | 142 |
| 149 -- Either the value is "=" and we've already verified the external | 143 text = base64.decode(text); |
| 150 -- cert identity, or the value is a string and either matches the | |
| 151 -- from_host ( | |
| 152 | |
| 153 text = base64.decode(text) | |
| 154 if not text then | 144 if not text then |
| 155 session.sends2s(build_reply("failure", "incorrect-encoding")) | 145 session.sends2s(build_reply("failure", "incorrect-encoding")); |
| 156 return true; | 146 return true; |
| 157 end | 147 end |
| 158 | 148 |
| 159 if session.cert_identity_status == "valid" then | 149 -- The text value is either "" or equals session.from_host |
| 160 if text ~= "" and text ~= session.from_host then | 150 if not ( text == "" or text == session.from_host ) then |
| 161 session.sends2s(build_reply("failure", "invalid-authzid")) | 151 session.sends2s(build_reply("failure", "invalid-authzid")); |
| 162 return true | 152 return true; |
| 163 end | 153 end |
| 164 else | 154 |
| 165 if text == "" then | 155 -- We've already verified the external cert identity before offering EXTERNAL |
| 166 session.sends2s(build_reply("failure", "invalid-authzid")) | 156 if session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid" then |
| 167 return true | 157 session.sends2s(build_reply("failure", "not-authorized")); |
| 168 end | 158 session:close(); |
| 169 | 159 return true; |
| 170 local cert = session.conn:socket():getpeercertificate() | 160 end |
| 171 if (cert_verify_identity(text, "xmpp-server", cert)) then | 161 |
| 172 session.cert_identity_status = "valid" | 162 -- Success! |
| 173 else | 163 session.external_auth = "succeeded"; |
| 174 session.cert_identity_status = "invalid" | 164 session.sends2s(build_reply("success")); |
| 175 session.sends2s(build_reply("failure", "invalid-authzid")) | 165 module:log("info", "Accepting SASL EXTERNAL identity from %s", session.from_host); |
| 176 return true | 166 module:fire_event("s2s-authenticated", { session = session, host = session.from_host }); |
| 177 end | |
| 178 end | |
| 179 | |
| 180 session.external_auth = "succeeded" | |
| 181 | |
| 182 if not session.from_host then | |
| 183 session.from_host = text; | |
| 184 end | |
| 185 session.sends2s(build_reply("success")) | |
| 186 | |
| 187 local domain = text ~= "" and text or session.from_host; | |
| 188 module:log("info", "Accepting SASL EXTERNAL identity from %s", domain); | |
| 189 module:fire_event("s2s-authenticated", { session = session, host = domain }); | |
| 190 session:reset_stream(); | 167 session:reset_stream(); |
| 191 return true | 168 return true; |
| 192 end | 169 end |
| 193 | 170 |
| 194 module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event) | 171 module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event) |
| 195 local session, stanza = event.origin, event.stanza; | 172 local session, stanza = event.origin, event.stanza; |
| 196 if session.type == "s2sin_unauthed" then | 173 if session.type == "s2sin_unauthed" then |
| 266 end); | 243 end); |
| 267 | 244 |
| 268 module:hook("s2s-stream-features", function(event) | 245 module:hook("s2s-stream-features", function(event) |
| 269 local origin, features = event.origin, event.features; | 246 local origin, features = event.origin, event.features; |
| 270 if origin.secure and origin.type == "s2sin_unauthed" then | 247 if origin.secure and origin.type == "s2sin_unauthed" then |
| 271 -- Offer EXTERNAL if chain is valid and either we didn't validate | 248 -- Offer EXTERNAL only if both chain and identity is valid. |
| 272 -- the identity or it passed. | 249 if origin.cert_chain_status == "valid" and origin.cert_identity_status == "valid" then |
| 273 if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable | 250 module:log("debug", "Offering SASL EXTERNAL"); |
| 274 module:log("debug", "Offering SASL EXTERNAL") | 251 origin.external_auth = "offered" |
| 275 features:tag("mechanisms", { xmlns = xmlns_sasl }) | 252 features:tag("mechanisms", { xmlns = xmlns_sasl }) |
| 276 :tag("mechanism"):text("EXTERNAL") | 253 :tag("mechanism"):text("EXTERNAL") |
| 277 :up():up(); | 254 :up():up(); |
| 278 end | 255 end |
| 279 end | 256 end |
