changeset 3905:5ae2e865eea0

mod_sasl2: Experimental implementation of XEP-0388
author Kim Alvefur <zash@zash.se>
date Sat, 28 Sep 2019 00:16:13 +0200
parents d14fc974efbc
children dbebc9226597
files mod_sasl2/mod_sasl2.lua
diffstat 1 files changed, 147 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_sasl2/mod_sasl2.lua	Sat Sep 28 00:16:13 2019 +0200
@@ -0,0 +1,147 @@
+-- Prosody IM
+-- Copyright (C) 2019 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- XEP-0388: Extensible SASL Profile
+--
+
+local st = require "util.stanza";
+local errors = require "util.error";
+local base64 = require "util.encodings".base64;
+local jid_join = require "util.jid".join;
+
+local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
+local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
+
+local xmlns_sasl2 = "urn:xmpp:sasl:1";
+
+local allow_unencrypted_plain_auth = module:get_option_boolean("allow_unencrypted_plain_auth", false)
+local insecure_mechanisms = module:get_option_set("insecure_sasl_mechanisms", allow_unencrypted_plain_auth and {} or {"PLAIN", "LOGIN"});
+local disabled_mechanisms = module:get_option_set("disable_sasl_mechanisms", { "DIGEST-MD5" });
+
+local host = module.host;
+
+local function tls_unique(self)
+	return self.userdata["tls-unique"]:getpeerfinished();
+end
+
+module:hook("stream-features", function(event)
+	local origin, features = event.origin, event.features;
+	local log = origin.log or module._log;
+
+	if origin.type ~= "c2s_unauthed" then
+		log("debug", "Already authenticated");
+		return
+	end
+
+	local sasl_handler = usermanager_get_sasl_handler(host, origin)
+	origin.sasl_handler = sasl_handler;
+
+	if sasl_handler.add_cb_handler then
+		local socket = origin.conn:socket();
+		if socket.getpeerfinished then
+			sasl_handler:add_cb_handler("tls-unique", tls_unique);
+		end
+		sasl_handler["userdata"] = {
+			["tls-unique"] = socket;
+		};
+	end
+
+	local mechanisms = st.stanza("mechanisms", { xmlns = xmlns_sasl2 });
+
+	local available_mechanisms = sasl_handler:mechanisms()
+	for mechanism in pairs(available_mechanisms) do
+		if disabled_mechanisms:contains(mechanism) then
+			log("debug", "Not offering disabled mechanism %s", mechanism);
+		elseif not origin.secure and insecure_mechanisms:contains(mechanism) then
+			log("debug", "Not offering mechanism %s on insecure connection", mechanism);
+		else
+			log("debug", "Offering mechanism %s", mechanism);
+			mechanisms:text_tag("mechanism", mechanism);
+		end
+	end
+
+	features:add_direct_child(mechanisms);
+end, 1);
+
+local function handle_status(session, status, ret, err_msg)
+	local err = nil;
+	if status == "error" then
+		ret, err = nil, ret;
+		if not errors.is_err(err) then
+			err = errors.new({ condition = err, text = err_msg }, { session = session });
+		end
+	end
+
+	module:fire_event("sasl2/"..session.base_type.."/"..status, {
+			session = session,
+			message = ret;
+			error = err;
+		});
+end
+
+module:hook("sasl2/c2s/failure", function (event)
+	local session = event.session
+	session.send(st.stanza("failure", { xmlns = xmlns_sasl2 })
+		:tag(event.error.condition));
+	return true;
+end);
+
+module:hook("sasl2/c2s/challenge", function (event)
+	local session = event.session;
+	session.send(st.stanza("challenge", { xmlns = xmlns_sasl2 })
+		:text_tag(event.message));
+end);
+
+module:hook("sasl2/c2s/success", function (event)
+	local session = event.session
+	local ok, err = sm_make_authenticated(session, session.sasl_handler.username);
+	if not ok then
+		handle_status(session, "failure", err);
+		return true;
+	end
+	event.success = st.stanza("success", { xmlns = xmlns_sasl2 });
+end, 1000);
+
+module:hook("sasl2/c2s/success", function (event)
+	local session = event.session
+	event.success:text_tag("authorization-identifier", jid_join(session.username, session.host, session.resource));
+	session.send(event.success);
+	local features = st.stanza("stream:features");
+	module:fire_event("stream-features", { origin = session, features = features });
+	session.send(features);
+end, -1000);
+
+local function process_cdata(session, cdata)
+	if cdata then
+		cdata = base64.decode(cdata);
+		if not cdata then
+			return handle_status(session, "failure");
+		end
+	end
+	return handle_status(session, session.sasl_handler:process(cdata));
+end
+
+module:hook_tag(xmlns_sasl2, "authenticate", function (session, auth)
+	local sasl_handler = session.sasl_handler;
+	if not sasl_handler then
+		sasl_handler = usermanager_get_sasl_handler(host, session);
+		session.sasl_handler = sasl_handler;
+	end
+	local mechanism = assert(auth.attr.mechanism);
+	if not sasl_handler:select(mechanism) then
+		return handle_status(session, "failure");
+	end
+	local initial = auth:get_child_text("initial-response");
+	return process_cdata(session, initial);
+end);
+
+module:hook_tag(xmlns_sasl2, "response", function (session, response)
+	local sasl_handler = session.sasl_handler;
+	if not sasl_handler or not sasl_handler.selected then
+		return handle_status(session, "failure");
+	end
+	return process_cdata(session, response:get_text());
+end);