comparison mod_saslauth_muc/mod_saslauth_muc.lua @ 284:3b96bba9f7e5

mod_saslauth_muc: Initial commit. Implements SASL auth for MUC rooms <http://xmpp.org/extensions/inbox/remote-auth.html>.
author Waqas Hussain <waqas20@gmail.com>
date Thu, 02 Dec 2010 17:22:34 +0500
parents
children 6144fe6161f1
comparison
equal deleted inserted replaced
283:10c3f6c6a04c 284:3b96bba9f7e5
1 --
2 -- mod_saslauth_muc
3 -- This module implements http://xmpp.org/extensions/inbox/remote-auth.html for Prosody's MUC component
4 --
5 -- In your config:
6 -- Component "conference.example.com" "muc"
7 -- modules_enabled = { "saslauth_muc" };
8 --
9 --
10
11 local timeout = 60; -- SASL timeout in seconds
12
13 -- various imports
14 local new_sasl = require "util.sasl".new;
15 local st = require "util.stanza";
16 local timer = require "util.timer";
17
18 local jid_bare = require "util.jid".bare;
19 local jid_prep = require "util.jid".prep;
20 local base64 = require "util.encodings".base64;
21
22 local hosts = hosts;
23 local module = module;
24 local pairs, next = pairs, next;
25 local os_time = os.time;
26
27 -- SASL sessions management
28 local _rooms = {}; -- SASL data
29 local function get_handler_for(room, jid) return _rooms[room] and _rooms[room][jid]; end
30 local function remove_handler_for(room, jid) if _rooms[room] then _rooms[room][jid] = nil; end end
31 local function create_handler_for(room_jid, jid)
32 _rooms[room_jid] = _rooms[room_jid] or {};
33 _rooms[room_jid][jid] = new_sasl(module.host, { plain = function(username, realm)
34 local muc = hosts[module.host].modules.muc;
35 local room = muc and muc.rooms[room_jid];
36 local password = room and room:get_password();
37 local ret = password and true or false;
38 return password, true;
39 end });
40 _rooms[room_jid][jid].timeout = os_time() + timeout;
41 return _rooms[room_jid][jid];
42 end
43
44 -- Timer to clear SASL sessions
45 timer.add_task(timeout, function()
46 local now = os_time();
47 for room, handlers in pairs(_rooms) do
48 for jid, handler in pairs(handlers) do
49 if handler.timeout <= now then handlers[jid] = nil; end
50 end
51 if next(handlers) == nil then _rooms[room] = nil; end
52 end
53 return timeout;
54 end);
55
56 -- Stanza handlers
57 module:hook("presence/full", function(event)
58 local origin, stanza = event.origin, event.stanza;
59
60 if not stanza.attr.type then -- available presence
61 local room_jid = jid_bare(stanza.attr.to);
62 local room = hosts[module.host].modules.muc.rooms[room_jid];
63
64 if room and not room:get_role(stanza.attr.from) then -- this is a room join
65 if room:get_password() then -- room has a password
66 local x = stanza:get_child("x", "http://jabber.org/protocol/muc");
67 local password = x and x:get_child("password");
68 if not password then -- no password sent
69 local sasl_handler = get_handler_for(jid_bare(stanza.attr.to), stanza.attr.from);
70 if x and sasl_handler and sasl_handler.authorized then -- if already passed SASL
71 x:reset():tag("password", { xmlns = "http://jabber.org/protocol/muc" }):text(room:get_password());
72 else
73 origin.send(st.error_reply(stanza, "auth", "not-authorized")
74 :tag("sasl-required", { xmlns = "urn:xmpp:errors" }));
75 return true;
76 end
77 end
78 end
79 end
80 end
81 end, 10);
82
83 module:hook("iq-get/bare/urn:ietf:params:xml:ns:xmpp-sasl:mechanisms", function(event)
84 local origin, stanza = event.origin, event.stanza;
85
86 local reply = st.reply(stanza):tag("mechanisms", { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' });
87 for mechanism in pairs(create_handler_for(stanza.attr.to, true):mechanisms()) do
88 reply:tag("mechanism"):text(mechanism):up();
89 end
90 origin.send(reply:up());
91 return true;
92 end);
93
94 local function build_reply(stanza, status, ret, err_msg)
95 local reply = st.stanza(status, {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"});
96 if status == "challenge" then
97 reply:text(base64.encode(ret or ""));
98 elseif status == "failure" then
99 reply:tag(ret):up();
100 if err_msg then reply:tag("text"):text(err_msg); end
101 elseif status == "success" then
102 reply:text(base64.encode(ret or ""));
103 else
104 module:log("error", "Unknown sasl status: %s", status);
105 end
106 return st.reply(stanza):add_child(reply);
107 end
108 local function handle_status(stanza, status)
109 if status == "failure" then
110 remove_handler_for(stanza.attr.to, stanza.attr.from);
111 elseif status == "success" then
112 get_handler_for(stanza.attr.to, stanza.attr.from).authorized = true;
113 end
114 end
115 local function sasl_process_cdata(session, stanza)
116 local text = stanza.tags[1][1];
117 if text then
118 text = base64.decode(text);
119 if not text then
120 remove_handler_for(stanza.attr.to, stanza.attr.from);
121 session.send(build_reply(stanza, "failure", "incorrect-encoding"));
122 return true;
123 end
124 end
125 local status, ret, err_msg = get_handler_for(stanza.attr.to, stanza.attr.from):process(text);
126 handle_status(stanza, status);
127 local s = build_reply(stanza, status, ret, err_msg);
128 session.send(s);
129 return true;
130 end
131
132 module:hook("iq-set/bare/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
133 local session, stanza = event.origin, event.stanza;
134
135 if not create_handler_for(stanza.attr.to, stanza.attr.from):select(stanza.tags[1].attr.mechanism) then
136 remove_handler_for(stanza.attr.to, stanza.attr.from);
137 session.send(build_reply(stanza, "failure", "invalid-mechanism"));
138 return true;
139 end
140 return sasl_process_cdata(session, stanza);
141 end);
142 module:hook("iq-set/bare/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event)
143 local session, stanza = event.origin, event.stanza;
144 if not get_handler_for(stanza.attr.to, stanza.attr.from) then
145 session.send(build_reply(stanza, "failure", "not-authorized", "Out of order SASL element"));
146 return true;
147 end
148 return sasl_process_cdata(session, event.stanza);
149 end);
150 module:hook("iq-set/bare/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event)
151 local session, stanza = event.origin, event.stanza;
152 remove_handler_for(stanza.attr.to, stanza.attr.from);
153 session.send(build_reply(stanza, "failure", "aborted"));
154 return true;
155 end);