Software / code / prosody-modules
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 |
| child | 287: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); |