- local success, err = sm_bind_resource(session, resource);
- if not success then
- local reply = st.reply(stanza);
- reply.attr.type = "error";
- if err == "conflict" then
- reply:tag("error", { type = "modify" })
- :tag("conflict", { xmlns = xmlns_stanzas });
- elseif err == "constraint" then
- reply:tag("error", { type = "cancel" })
- :tag("resource-constraint", { xmlns = xmlns_stanzas });
- elseif err == "auth" then
- reply:tag("error", { type = "cancel" })
- :tag("not-allowed", { xmlns = xmlns_stanzas });
- end
- send(session, reply);
- else
- local reply = st.reply(stanza);
- reply:tag("bind", { xmlns = xmlns_bind})
- :tag("jid"):text(session.full_jid);
- send(session, reply);
+ end
+ end
+end, 150);
+
+local function s2s_external_auth(session, stanza)
+ local mechanism = stanza.attr.mechanism;
+
+ if not session.secure then
+ if mechanism == "EXTERNAL" then
+ session.sends2s(build_reply("failure", "encryption-required"))
+ else
+ session.sends2s(build_reply("failure", "invalid-mechanism"))
+ end
+ return true;
+ end
+
+ if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then
+ session.sends2s(build_reply("failure", "invalid-mechanism"))
+ return true;
+ end
+
+ local text = stanza[1]
+ if not text then
+ session.sends2s(build_reply("failure", "malformed-request"))
+ return true
+ end
+
+ -- Either the value is "=" and we've already verified the external
+ -- cert identity, or the value is a string and either matches the
+ -- from_host (
+
+ text = base64.decode(text)
+ if not text then
+ session.sends2s(build_reply("failure", "incorrect-encoding"))
+ return true;
+ end
+
+ if session.cert_identity_status == "valid" then
+ if text ~= "" and text ~= session.from_host then
+ session.sends2s(build_reply("failure", "invalid-authzid"))
+ return true
+ end
+ else
+ if text == "" then
+ session.sends2s(build_reply("failure", "invalid-authzid"))
+ return true
+ end
+
+ local cert = session.conn:socket():getpeercertificate()
+ if (cert_verify_identity(text, "xmpp-server", cert)) then
+ session.cert_identity_status = "valid"
+ else
+ session.cert_identity_status = "invalid"
+ session.sends2s(build_reply("failure", "invalid-authzid"))
+ return true
+ end
+ end
+
+ session.external_auth = "succeeded"
+
+ if not session.from_host then
+ session.from_host = text;
+ end
+ session.sends2s(build_reply("success"))
+ module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host);
+ s2s_make_authenticated(session, text or session.from_host)
+ session:reset_stream();
+ return true
+end
+
+module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
+ local session, stanza = event.origin, event.stanza;
+ if session.type == "s2sin_unauthed" then
+ return s2s_external_auth(session, stanza)
+ end
+
+ if session.type ~= "c2s_unauthed" then return; end
+
+ if session.sasl_handler and session.sasl_handler.selected then
+ session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one
+ end
+ if not session.sasl_handler then
+ session.sasl_handler = usermanager_get_sasl_handler(module.host);
+ end
+ local mechanism = stanza.attr.mechanism;
+ if not session.secure and (secure_auth_only or (mechanism == "PLAIN" and not allow_unencrypted_plain_auth)) then
+ session.send(build_reply("failure", "encryption-required"));
+ return true;
+ end
+ local valid_mechanism = session.sasl_handler:select(mechanism);
+ if not valid_mechanism then
+ session.send(build_reply("failure", "invalid-mechanism"));
+ return true;
+ end
+ return sasl_process_cdata(session, stanza);
+end);
+module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event)
+ local session = event.origin;
+ if not(session.sasl_handler and session.sasl_handler.selected) then
+ session.send(build_reply("failure", "not-authorized", "Out of order SASL element"));
+ return true;
+ end
+ return sasl_process_cdata(session, event.stanza);
+end);
+module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event)
+ local session = event.origin;
+ session.sasl_handler = nil;
+ session.send(build_reply("failure", "aborted"));
+ return true;
+end);
+
+local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
+local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' };
+local xmpp_session_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-session' };
+module:hook("stream-features", function(event)
+ local origin, features = event.origin, event.features;
+ if not origin.username then
+ if secure_auth_only and not origin.secure then
+ return;
+ end
+ origin.sasl_handler = usermanager_get_sasl_handler(module.host);
+ features:tag("mechanisms", mechanisms_attr);
+ for mechanism in pairs(origin.sasl_handler:mechanisms()) do
+ if mechanism ~= "PLAIN" or origin.secure or allow_unencrypted_plain_auth then
+ features:tag("mechanism"):text(mechanism):up();