comparison mod_sasl2/mod_sasl2.lua @ 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
children 9d57aa79c5d9
comparison
equal deleted inserted replaced
3904:d14fc974efbc 3905:5ae2e865eea0
1 -- Prosody IM
2 -- Copyright (C) 2019 Kim Alvefur
3 --
4 -- This project is MIT/X11 licensed. Please see the
5 -- COPYING file in the source package for more information.
6 --
7 -- XEP-0388: Extensible SASL Profile
8 --
9
10 local st = require "util.stanza";
11 local errors = require "util.error";
12 local base64 = require "util.encodings".base64;
13 local jid_join = require "util.jid".join;
14
15 local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
16 local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
17
18 local xmlns_sasl2 = "urn:xmpp:sasl:1";
19
20 local allow_unencrypted_plain_auth = module:get_option_boolean("allow_unencrypted_plain_auth", false)
21 local insecure_mechanisms = module:get_option_set("insecure_sasl_mechanisms", allow_unencrypted_plain_auth and {} or {"PLAIN", "LOGIN"});
22 local disabled_mechanisms = module:get_option_set("disable_sasl_mechanisms", { "DIGEST-MD5" });
23
24 local host = module.host;
25
26 local function tls_unique(self)
27 return self.userdata["tls-unique"]:getpeerfinished();
28 end
29
30 module:hook("stream-features", function(event)
31 local origin, features = event.origin, event.features;
32 local log = origin.log or module._log;
33
34 if origin.type ~= "c2s_unauthed" then
35 log("debug", "Already authenticated");
36 return
37 end
38
39 local sasl_handler = usermanager_get_sasl_handler(host, origin)
40 origin.sasl_handler = sasl_handler;
41
42 if sasl_handler.add_cb_handler then
43 local socket = origin.conn:socket();
44 if socket.getpeerfinished then
45 sasl_handler:add_cb_handler("tls-unique", tls_unique);
46 end
47 sasl_handler["userdata"] = {
48 ["tls-unique"] = socket;
49 };
50 end
51
52 local mechanisms = st.stanza("mechanisms", { xmlns = xmlns_sasl2 });
53
54 local available_mechanisms = sasl_handler:mechanisms()
55 for mechanism in pairs(available_mechanisms) do
56 if disabled_mechanisms:contains(mechanism) then
57 log("debug", "Not offering disabled mechanism %s", mechanism);
58 elseif not origin.secure and insecure_mechanisms:contains(mechanism) then
59 log("debug", "Not offering mechanism %s on insecure connection", mechanism);
60 else
61 log("debug", "Offering mechanism %s", mechanism);
62 mechanisms:text_tag("mechanism", mechanism);
63 end
64 end
65
66 features:add_direct_child(mechanisms);
67 end, 1);
68
69 local function handle_status(session, status, ret, err_msg)
70 local err = nil;
71 if status == "error" then
72 ret, err = nil, ret;
73 if not errors.is_err(err) then
74 err = errors.new({ condition = err, text = err_msg }, { session = session });
75 end
76 end
77
78 module:fire_event("sasl2/"..session.base_type.."/"..status, {
79 session = session,
80 message = ret;
81 error = err;
82 });
83 end
84
85 module:hook("sasl2/c2s/failure", function (event)
86 local session = event.session
87 session.send(st.stanza("failure", { xmlns = xmlns_sasl2 })
88 :tag(event.error.condition));
89 return true;
90 end);
91
92 module:hook("sasl2/c2s/challenge", function (event)
93 local session = event.session;
94 session.send(st.stanza("challenge", { xmlns = xmlns_sasl2 })
95 :text_tag(event.message));
96 end);
97
98 module:hook("sasl2/c2s/success", function (event)
99 local session = event.session
100 local ok, err = sm_make_authenticated(session, session.sasl_handler.username);
101 if not ok then
102 handle_status(session, "failure", err);
103 return true;
104 end
105 event.success = st.stanza("success", { xmlns = xmlns_sasl2 });
106 end, 1000);
107
108 module:hook("sasl2/c2s/success", function (event)
109 local session = event.session
110 event.success:text_tag("authorization-identifier", jid_join(session.username, session.host, session.resource));
111 session.send(event.success);
112 local features = st.stanza("stream:features");
113 module:fire_event("stream-features", { origin = session, features = features });
114 session.send(features);
115 end, -1000);
116
117 local function process_cdata(session, cdata)
118 if cdata then
119 cdata = base64.decode(cdata);
120 if not cdata then
121 return handle_status(session, "failure");
122 end
123 end
124 return handle_status(session, session.sasl_handler:process(cdata));
125 end
126
127 module:hook_tag(xmlns_sasl2, "authenticate", function (session, auth)
128 local sasl_handler = session.sasl_handler;
129 if not sasl_handler then
130 sasl_handler = usermanager_get_sasl_handler(host, session);
131 session.sasl_handler = sasl_handler;
132 end
133 local mechanism = assert(auth.attr.mechanism);
134 if not sasl_handler:select(mechanism) then
135 return handle_status(session, "failure");
136 end
137 local initial = auth:get_child_text("initial-response");
138 return process_cdata(session, initial);
139 end);
140
141 module:hook_tag(xmlns_sasl2, "response", function (session, response)
142 local sasl_handler = session.sasl_handler;
143 if not sasl_handler or not sasl_handler.selected then
144 return handle_status(session, "failure");
145 end
146 return process_cdata(session, response:get_text());
147 end);