+
+ 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"))
+
+ local domain = text ~= "" and text or session.from_host;
+ module:log("info", "Accepting SASL EXTERNAL identity from %s", domain);
+ module:fire_event("s2s-authenticated", { session = session, host = domain });
+ 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, session);
+ end
+ local mechanism = stanza.attr.mechanism;