diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/mod_bosh.lua | 4 | ||||
-rw-r--r-- | plugins/mod_compression.lua | 206 | ||||
-rw-r--r-- | plugins/mod_console.lua | 12 | ||||
-rw-r--r-- | plugins/mod_proxy65.lua | 255 | ||||
-rw-r--r-- | plugins/mod_saslauth.lua | 89 | ||||
-rw-r--r-- | plugins/mod_tls.lua | 10 | ||||
-rw-r--r-- | plugins/muc/muc.lib.lua | 143 |
7 files changed, 554 insertions, 165 deletions
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index 3e41ef7b..5de79eff 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -152,7 +152,7 @@ function stream_callbacks.streamopened(request, attr) local r, send_buffer = session.requests, session.send_buffer; local response = { headers = default_headers } function session.send(s) - log("debug", "Sending BOSH data: %s", tostring(s)); + --log("debug", "Sending BOSH data: %s", tostring(s)); local oldest_request = r[1]; while oldest_request and oldest_request.destroyed do t_remove(r, 1); @@ -160,7 +160,7 @@ function stream_callbacks.streamopened(request, attr) oldest_request = r[1]; end if oldest_request then - log("debug", "We have an open request, so using that to send with"); + log("debug", "We have an open request, so sending on that"); response.body = t_concat{"<body xmlns='http://jabber.org/protocol/httpbind' sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>", tostring(s), "</body>" }; oldest_request:send(response); --log("debug", "Sent"); diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua index f1cae737..8fdf9dcc 100644 --- a/plugins/mod_compression.lua +++ b/plugins/mod_compression.lua @@ -8,16 +8,16 @@ local st = require "util.stanza"; local zlib = require "zlib"; local pcall = pcall; - local xmlns_compression_feature = "http://jabber.org/features/compress" local xmlns_compression_protocol = "http://jabber.org/protocol/compress" +local xmlns_stream = "http://etherx.jabber.org/streams"; local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up(); local compression_level = module:get_option("compression_level"); - -- if not defined assume admin wants best compression if compression_level == nil then compression_level = 9 end; + compression_level = tonumber(compression_level); if not compression_level or compression_level < 1 or compression_level > 9 then module:log("warn", "Invalid compression level in config: %s", tostring(compression_level)); @@ -34,89 +34,179 @@ module:add_event_hook("stream-features", end ); --- TODO Support compression on S2S level too. -module:add_handler({"c2s_unauthed", "c2s"}, "compress", xmlns_compression_protocol, +module:hook("s2s-stream-features", + function (data) + local session, features = data.session, data.features; + -- FIXME only advertise compression support when TLS layer has no compression enabled + if not session.compressed then + features:add_child(compression_stream_feature); + end + end +); + +-- Hook to activate compression if remote server supports it. +module:hook_stanza(xmlns_stream, "features", + function (session, stanza) + if not session.compressed then + -- does remote server support compression? + local comp_st = stanza:child_with_name("compression"); + if comp_st then + -- do we support the mechanism + for a in comp_st:children() do + local algorithm = a[1] + if algorithm == "zlib" then + session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib")) + session.log("info", "Enabled compression using zlib.") + return true; + end + end + session.log("debug", "Remote server supports no compression algorithm we support.") + end + end + end +, 250); + + +-- returns either nil or a fully functional ready to use inflate stream +local function get_deflate_stream(session) + local status, deflate_stream = pcall(zlib.deflate, compression_level); + if status == false then + local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"); + (session.sends2s or session.send)(error_st); + session.log("error", "Failed to create zlib.deflate filter."); + module:log("error", deflate_stream); + return + end + return deflate_stream +end + +-- returns either nil or a fully functional ready to use inflate stream +local function get_inflate_stream(session) + local status, inflate_stream = pcall(zlib.inflate); + if status == false then + local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"); + (session.sends2s or session.send)(error_st); + session.log("error", "Failed to create zlib.deflate filter."); + module:log("error", inflate_stream); + return + end + return inflate_stream +end + +-- setup compression for a stream +local function setup_compression(session, deflate_stream) + local old_send = (session.sends2s or session.send); + + local new_send = function(t) + --TODO: Better code injection in the sending process + session.log(t) + local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync'); + if status == false then + session:close({ + condition = "undefined-condition"; + text = compressed; + extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); + }); + module:log("warn", compressed); + return; + end + session.conn:write(compressed); + end; + + if session.sends2s then session.sends2s = new_send + elseif session.send then session.send = new_send end +end + +-- setup decompression for a stream +local function setup_decompression(session, inflate_stream) + local old_data = session.data + session.data = function(conn, data) + local status, decompressed, eof = pcall(inflate_stream, data); + if status == false then + session:close({ + condition = "undefined-condition"; + text = decompressed; + extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); + }); + module:log("warn", decompressed); + return; + end + old_data(conn, decompressed); + end; +end + +module:add_handler({"s2sout_unauthed", "s2sout"}, "compressed", xmlns_compression_protocol, + function(session ,stanza) + session.log("debug", "Activating compression...") + -- create deflate and inflate streams + local deflate_stream = get_deflate_stream(session); + if not deflate_stream then return end + + local inflate_stream = get_inflate_stream(session); + if not inflate_stream then return end + + -- setup compression for session.w + setup_compression(session, deflate_stream); + + -- setup decompression for session.data + setup_decompression(session, inflate_stream); + local session_reset_stream = session.reset_stream; + session.reset_stream = function(session) + session_reset_stream(session); + setup_decompression(session, inflate_stream); + return true; + end; + session:reset_stream(); + local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams", + ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host}; + session.sends2s("<?xml version='1.0'?>"); + session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag()); + session.compressed = true; + end +); + +module:add_handler({"c2s_unauthed", "c2s", "s2sin_unauthed", "s2sin"}, "compress", xmlns_compression_protocol, function(session, stanza) -- fail if we are already compressed if session.compressed then local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method"); - session.send(error_st); - session:log("warn", "Tried to establish another compression layer."); + (session.sends2s or session.send)(error_st); + session.log("warn", "Tried to establish another compression layer."); end -- checking if the compression method is supported local method = stanza:child_with_name("method")[1]; if method == "zlib" then session.log("info", method.." compression selected."); - session.send(st.stanza("compressed", {xmlns=xmlns_compression_protocol})); - session:reset_stream(); -- create deflate and inflate streams - local status, deflate_stream = pcall(zlib.deflate, compression_level); - if status == false then - local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"); - session.send(error_st); - session:log("error", "Failed to create zlib.deflate filter."); - module:log("error", deflate_stream); - return - end + local deflate_stream = get_deflate_stream(session); + if not deflate_stream then return end - local status, inflate_stream = pcall(zlib.inflate); - if status == false then - local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"); - session.send(error_st); - session:log("error", "Failed to create zlib.deflate filter."); - module:log("error", inflate_stream); - return - end + local inflate_stream = get_inflate_stream(session); + if not inflate_stream then return end - -- setup compression for session.w - local old_send = session.send; + (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol})); + session:reset_stream(); - session.send = function(t) - local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync'); - if status == false then - session:close({ - condition = "undefined-condition"; - text = compressed; - extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); - }); - module:log("warn", compressed); - return; - end - old_send(compressed); - end; + -- setup compression for session.w + setup_compression(session, deflate_stream); -- setup decompression for session.data - local function setup_decompression(session) - local old_data = session.data - session.data = function(conn, data) - local status, decompressed, eof = pcall(inflate_stream, data); - if status == false then - session:close({ - condition = "undefined-condition"; - text = decompressed; - extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); - }); - module:log("warn", decompressed); - return; - end - old_data(conn, decompressed); - end; - end - setup_decompression(session); + setup_decompression(session, inflate_stream); local session_reset_stream = session.reset_stream; session.reset_stream = function(session) session_reset_stream(session); - setup_decompression(session); + setup_decompression(session, inflate_stream); return true; end; session.compressed = true; else session.log("info", method.." compression selected. But we don't support it."); local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method"); - session.send(error_st); + (session.sends2s or session.send)(error_st); end end ); + diff --git a/plugins/mod_console.lua b/plugins/mod_console.lua index 82045232..5e6b6846 100644 --- a/plugins/mod_console.lua +++ b/plugins/mod_console.lua @@ -33,11 +33,11 @@ end console = {}; function console:new_session(conn) - local w = function(s) conn.write(s:gsub("\n", "\r\n")); end; + local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; send = function (t) w(tostring(t)); end; print = function (t) w("| "..tostring(t).."\n"); end; - disconnect = function () conn.close(); end; + disconnect = function () conn:close(); end; }; session.env = setmetatable({}, default_env_mt); @@ -53,7 +53,7 @@ end local sessions = {}; -function console_listener.listener(conn, data) +function console_listener.onincoming(conn, data) local session = sessions[conn]; if not session then @@ -126,7 +126,7 @@ function console_listener.listener(conn, data) session.send(string.char(0)); end -function console_listener.disconnect(conn, err) +function console_listener.ondisconnect(conn, err) local session = sessions[conn]; if session then session.disconnect(); @@ -478,7 +478,7 @@ function def_env.s2s:show(match_jid) for remotehost, session in pairs(host_session.s2sout) do if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then count_out = count_out + 1; - print(" "..host.." -> "..remotehost..(session.secure and " (encrypted)" or "")); + print(" "..host.." -> "..remotehost..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or "")); if session.sendq then print(" There are "..#session.sendq.." queued outgoing stanzas for this connection"); end @@ -515,7 +515,7 @@ function def_env.s2s:show(match_jid) -- Pft! is what I say to list comprehensions or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then count_in = count_in + 1; - print(" "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or "")); + print(" "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or "")); if session.type == "s2sin_unauthed" then print(" Connection not yet authenticated"); end diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua new file mode 100644 index 00000000..b2f901af --- /dev/null +++ b/plugins/mod_proxy65.lua @@ -0,0 +1,255 @@ +-- Copyright (C) 2009 Thilo Cestonaro +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- +--[[ +* to restart the proxy in the console: e.g. +module:unload("proxy65"); +> server.removeserver(<proxy65_port>); +module:load("proxy65", <proxy65_jid>); +]]-- + +if module:get_host_type() ~= "component" then + error("proxy65 should be loaded as a component, please see http://prosody.im/doc/components", 0); +end + +local jid_split, jid_join = require "util.jid".split, require "util.jid".join; +local st = require "util.stanza"; +local componentmanager = require "core.componentmanager"; +local config_get = require "core.configmanager".get; +local connlisteners = require "net.connlisteners"; +local sha1 = require "util.hashes".sha1; + +local host, name = module:get_host(), "SOCKS5 Bytestreams Service"; +local sessions, transfers, component, replies_cache = {}, {}, nil, {}; + +local proxy_port = config_get(host, "core", "proxy65_port") or 5000; +local proxy_interface = config_get(host, "core", "proxy65_interface") or "*"; +local proxy_address = config_get(host, "core", "proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host; +local proxy_acl = config_get(host, "core", "proxy65_acl"); + +local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" }; + +function connlistener.onincoming(conn, data) + local session = sessions[conn] or {}; + + if session.setup == nil and data ~= nil and data:sub(1):byte() == 0x05 and data:len() > 2 then + local nmethods = data:sub(2):byte(); + local methods = data:sub(3); + local supported = false; + for i=1, nmethods, 1 do + if(methods:sub(i):byte() == 0x00) then -- 0x00 == method: NO AUTH + supported = true; + break; + end + end + if(supported) then + module:log("debug", "new session found ... ") + session.setup = true; + sessions[conn] = session; + conn:write(string.char(5, 0)); + end + return; + end + if session.setup then + if session.sha ~= nil and transfers[session.sha] ~= nil then + local sha = session.sha; + if transfers[sha].activated == true and transfers[sha].initiator == conn and transfers[sha].target ~= nil then + transfers[sha].target:write(data); + return; + end + end + if data ~= nil and data:len() == 0x2F and -- 40 == length of SHA1 HASH, and 7 other bytes => 47 => 0x2F + data:sub(1):byte() == 0x05 and -- SOCKS5 has 5 in first byte + data:sub(2):byte() == 0x01 and -- CMD must be 1 + data:sub(3):byte() == 0x00 and -- RSV must be 0 + data:sub(4):byte() == 0x03 and -- ATYP must be 3 + data:sub(5):byte() == 40 and -- SHA1 HASH length must be 40 (0x28) + data:sub(-2):byte() == 0x00 and -- PORT must be 0, size 2 byte + data:sub(-1):byte() == 0x00 + then + local sha = data:sub(6, 45); -- second param is not count! it's the ending index (included!) + if transfers[sha] == nil then + transfers[sha] = {}; + transfers[sha].activated = false; + transfers[sha].target = conn; + session.sha = sha; + module:log("debug", "target connected ... "); + elseif transfers[sha].target ~= nil then + transfers[sha].initiator = conn; + session.sha = sha; + module:log("debug", "initiator connected ... "); + end + conn:write(string.char(5, 0, 0, 3, sha:len()) .. sha .. string.char(0, 0)); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) + else + module:log("warn", "Neither data transfer nor initial connect of a participator of a transfer.") + conn.close(); + end + else + if data ~= nil then + module:log("warn", "unknown connection with no authentication data -> closing it"); + conn.close(); + end + end +end + +function connlistener.ondisconnect(conn, err) + local session = sessions[conn]; + if session then + if session.sha and transfers[session.sha] then + local initiator, target = transfers[session.sha].initiator, transfers[session.sha].target; + if initiator == conn and target ~= nil then + target.close(); + elseif target == conn and initiator ~= nil then + initiator.close(); + end + transfers[session.sha] = nil; + end + -- Clean up any session-related stuff here + sessions[conn] = nil; + end +end + +local function get_disco_info(stanza) + local reply = replies_cache.disco_info; + if reply == nil then + reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#info") + :tag("identity", {category='proxy', type='bytestreams', name=name}):up() + :tag("feature", {var="http://jabber.org/protocol/bytestreams"}); + replies_cache.disco_info = reply; + end + + reply.attr.id = stanza.attr.id; + reply.attr.to = stanza.attr.from; + return reply; +end + +local function get_disco_items(stanza) + local reply = replies_cache.disco_items; + if reply == nil then + reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#items"); + replies_cache.disco_items = reply; + end + + reply.attr.id = stanza.attr.id; + reply.attr.to = stanza.attr.from; + return reply; +end + +local function get_stream_host(origin, stanza) + local reply = replies_cache.stream_host; + local err_reply = replies_cache.stream_host_err; + local sid = stanza.tags[1].attr.sid; + local allow = false; + local jid_node, jid_host, jid_resource = jid_split(stanza.attr.from); + + if stanza.attr.from == nil then + jid_node = origin.username; + jid_host = origin.host; + jid_resource = origin.resource; + end + + if proxy_acl and #proxy_acl > 0 then + if host ~= nil then -- at least a domain is needed. + for _, acl in ipairs(proxy_acl) do + local acl_node, acl_host, acl_resource = jid_split(acl); + if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and + ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and + ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then + allow = true; + end + end + end + else + allow = true; + end + if allow == true then + if reply == nil then + reply = st.iq({type="result", from=host}) + :query("http://jabber.org/protocol/bytestreams") + :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}); + replies_cache.stream_host = reply; + end + else + module:log("warn", "Denying use of proxy for %s", tostring(jid_join(jid_node, jid_host, jid_resource))); + if err_reply == nil then + err_reply = st.iq({type="error", from=host}) + :query("http://jabber.org/protocol/bytestreams") + :tag("error", {code='403', type='auth'}) + :tag("forbidden", {xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'}); + replies_cache.stream_host_err = err_reply; + end + reply = err_reply; + end + reply.attr.id = stanza.attr.id; + reply.attr.to = stanza.attr.from; + reply.tags[1].attr.sid = sid; + return reply; +end + +module.unload = function() + componentmanager.deregister_component(host); + connlisteners.deregister(module.host .. ':proxy65'); +end + +local function set_activation(stanza) + local from, to, sid, reply = nil; + from = stanza.attr.from; + if stanza.tags[1] ~= nil and tostring(stanza.tags[1].name) == "query" then + if stanza.tags[1].attr ~= nil then + sid = stanza.tags[1].attr.sid; + end + if stanza.tags[1].tags[1] ~= nil and tostring(stanza.tags[1].tags[1].name) == "activate" then + to = stanza.tags[1].tags[1][1]; + end + end + if from ~= nil and to ~= nil and sid ~= nil then + reply = st.iq({type="result", from=host, to=from}); + reply.attr.id = stanza.attr.id; + end + return reply, from, to, sid; +end + +function handle_to_domain(origin, stanza) + local to_node, to_host, to_resource = jid_split(stanza.attr.to); + if to_node == nil then + local type = stanza.attr.type; + if type == "error" or type == "result" then return; end + if stanza.name == "iq" and type == "get" then + local xmlns = stanza.tags[1].attr.xmlns + if xmlns == "http://jabber.org/protocol/disco#info" then + origin.send(get_disco_info(stanza)); + return true; + elseif xmlns == "http://jabber.org/protocol/disco#items" then + origin.send(get_disco_items(stanza)); + return true; + elseif xmlns == "http://jabber.org/protocol/bytestreams" then + origin.send(get_stream_host(origin, stanza)); + return true; + end + elseif stanza.name == "iq" and type == "set" then + local reply, from, to, sid = set_activation(stanza); + if reply ~= nil and from ~= nil and to ~= nil and sid ~= nil then + local sha = sha1(sid .. from .. to, true); + if transfers[sha] == nil then + module:log("error", "transfers[sha]: nil"); + elseif(transfers[sha] ~= nil and transfers[sha].initiator ~= nil and transfers[sha].target ~= nil) then + origin.send(reply); + transfers[sha].activated = true; + end + else + module:log("error", "activation failed: sid: %s, initiator: %s, target: %s", tostring(sid), tostring(from), tostring(to)); + end + end + end + return; +end + +if not connlisteners.register(module.host .. ':proxy65', connlistener) then + error("mod_proxy65: Could not establish a connection listener. Check your configuration please."); + error(" one possible cause for this would be that two proxy65 components share the same port."); +end + +connlisteners.start(module.host .. ':proxy65'); +component = componentmanager.register_component(host, handle_to_domain); diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index c0f57cd7..001f14e2 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2009 Matthew Wild -- Copyright (C) 2008-2009 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -35,6 +35,27 @@ local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas'; local new_sasl = require "util.sasl".new; +default_authentication_profile = { + plain = function(username, realm) + local prepped_username = nodeprep(username); + if not prepped_username then + log("debug", "NODEprep failed on username: %s", username); + return "", nil; + end + local password = usermanager_get_password(prepped_username, realm); + if not password then + return "", nil; + end + return password, true; + end +}; + +anonymous_authentication_profile = { + anonymous = function(username, realm) + return true; -- for normal usage you should always return true here + end +} + local function build_reply(status, ret, err_msg) local reply = st.stanza(status, {xmlns = xmlns_sasl}); if status == "challenge" then @@ -54,50 +75,18 @@ end local function handle_status(session, status) if status == "failure" then - session.sasl_handler = nil; + session.sasl_handler = session.sasl_handler:clean_clone(); elseif status == "success" then local username = nodeprep(session.sasl_handler.username); - session.sasl_handler = nil; if not username then -- TODO move this to sessionmanager module:log("warn", "SASL succeeded but we didn't get a username!"); session.sasl_handler = nil; session:reset_stream(); return; - end - sm_make_authenticated(session, username); - session:reset_stream(); - end -end - -local function credentials_callback(mechanism, ...) - if mechanism == "PLAIN" then - local username, hostname, password = ...; - username = nodeprep(username); - if not username then - return false; - end - local response = usermanager_validate_credentials(hostname, username, password, mechanism); - if response == nil then - return false; - else - return response; - end - elseif mechanism == "DIGEST-MD5" then - local function func(x) return x; end - local node, domain, realm, decoder = ...; - local prepped_node = nodeprep(node); - if not prepped_node then - return func, nil; - end - local password = usermanager_get_password(prepped_node, domain); - if password then - if decoder then - node, realm, password = decoder(node), decoder(realm), decoder(password); - end - return func, md5(node..":"..realm..":"..password); - else - return func, nil; end + sm_make_authenticated(session, session.sasl_handler.username); + session.sasl_handler = nil; + session:reset_stream(); end end @@ -111,8 +100,8 @@ local function sasl_handler(session, stanza) elseif stanza.attr.mechanism == "ANONYMOUS" then return session.send(build_reply("failure", "mechanism-too-weak")); end - session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, credentials_callback); - if not session.sasl_handler then + local valid_mechanism = session.sasl_handler:select(stanza.attr.mechanism); + if not valid_mechanism then return session.send(build_reply("failure", "invalid-mechanism")); end elseif not session.sasl_handler then @@ -128,7 +117,7 @@ local function sasl_handler(session, stanza) return; end end - local status, ret, err_msg = session.sasl_handler:feed(text); + local status, ret, err_msg = session.sasl_handler:process(text); handle_status(session, status); local s = build_reply(status, ret, err_msg); log("debug", "sasl reply: %s", tostring(s)); @@ -148,16 +137,18 @@ module:add_event_hook("stream-features", if secure_auth_only and not session.secure then return; end - features:tag("mechanisms", mechanisms_attr); - -- TODO: Provide PLAIN only if TLS is active, this is a SHOULD from the introduction of RFC 4616. This behavior could be overridden via configuration but will issuing a warning or so. - if config.get(session.host or "*", "core", "anonymous_login") then - features:tag("mechanism"):text("ANONYMOUS"):up(); - else - local mechanisms = usermanager_get_supported_methods(session.host or "*"); - for k, v in pairs(mechanisms) do - features:tag("mechanism"):text(k):up(); - end + if module:get_option("anonymous_login") then + session.sasl_handler = new_sasl(session.host, anonymous_authentication_profile); + else + session.sasl_handler = new_sasl(session.host, default_authentication_profile); + if not (module:get_option("allow_unencrypted_plain_auth")) and not session.secure then + session.sasl_handler:forbidden({"PLAIN"}); end + end + features:tag("mechanisms", mechanisms_attr); + for k, v in pairs(session.sasl_handler:mechanisms()) do + features:tag("mechanism"):text(v):up(); + end features:up(); else features:tag("bind", bind_attr):tag("required"):up():up(); diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua index 8a450803..706b42c9 100644 --- a/plugins/mod_tls.lua +++ b/plugins/mod_tls.lua @@ -20,9 +20,9 @@ module:add_handler("c2s_unauthed", "starttls", xmlns_starttls, session.send(st.stanza("proceed", { xmlns = xmlns_starttls })); session:reset_stream(); if session.host and hosts[session.host].ssl_ctx_in then - session.conn.set_sslctx(hosts[session.host].ssl_ctx_in); + session.conn:set_sslctx(hosts[session.host].ssl_ctx_in); end - session.conn.starttls(); + session.conn:starttls(); session.log("info", "TLS negotiation started..."); session.secure = false; else @@ -37,9 +37,9 @@ module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls, session.sends2s(st.stanza("proceed", { xmlns = xmlns_starttls })); session:reset_stream(); if session.to_host and hosts[session.to_host].ssl_ctx_in then - session.conn.set_sslctx(hosts[session.to_host].ssl_ctx_in); + session.conn:set_sslctx(hosts[session.to_host].ssl_ctx_in); end - session.conn.starttls(); + session.conn:starttls(); session.log("info", "TLS negotiation started for incoming s2s..."); session.secure = false; else @@ -91,7 +91,7 @@ module:hook_stanza(xmlns_starttls, "proceed", module:log("debug", "Proceeding with TLS on s2sout..."); local format, to_host, from_host = string.format, session.to_host, session.from_host; session:reset_stream(); - session.conn.starttls(true); + session.conn:starttls(true); session.secure = false; return true; end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 3a185e17..002498af 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -128,19 +128,21 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) end end function room_mt:broadcast_message(stanza, historic) + local to = stanza.attr.to; for occupant, o_data in pairs(self._occupants) do for jid in pairs(o_data.sessions) do stanza.attr.to = jid; self:_route_stanza(stanza); end end + stanza.attr.to = to; if historic then -- add to history local history = self._data['history']; if not history then history = {}; self._data['history'] = history; end - -- stanza = st.clone(stanza); + stanza = st.clone(stanza); stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - t_insert(history, st.clone(st.preserialize(stanza))); + t_insert(history, st.preserialize(stanza)); while #history > history_length do t_remove(history, 1) end end end @@ -387,51 +389,70 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc end end -function room_mt:handle_form(origin, stanza) - if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end - if stanza.attr.type == "get" then - local title = "Configuration for "..self.jid; - origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") - :tag("x", {xmlns='jabber:x:data', type='form'}) - :tag("title"):text(title):up() - :tag("instructions"):text(title):up() - :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up() - :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'}) - :tag("value"):text(self._data.persistent and "1" or "0"):up() - :up() - :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'}) - :tag("value"):text(self._data.hidden and "0" or "1"):up() - :up() - ); - elseif stanza.attr.type == "set" then - local query = stanza.tags[1]; - local form; - for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end - if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end - if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end - if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end - local fields = {}; - for _, field in pairs(form.tags) do - if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then - fields[field.attr.var] = field.tags[1][1] or ""; - end +function room_mt:send_form(origin, stanza) + local title = "Configuration for "..self.jid; + origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") + :tag("x", {xmlns='jabber:x:data', type='form'}) + :tag("title"):text(title):up() + :tag("instructions"):text(title):up() + :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up() + :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'}) + :tag("value"):text(self._data.persistent and "1" or "0"):up() + :up() + :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'}) + :tag("value"):text(self._data.hidden and "0" or "1"):up() + :up() + ); +end + +function room_mt:process_form(origin, stanza) + local query = stanza.tags[1]; + local form; + for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end + if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end + if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end + if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + local fields = {}; + for _, field in pairs(form.tags) do + if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then + fields[field.attr.var] = field.tags[1][1] or ""; end - if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + end + if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end - local persistent = fields['muc#roomconfig_persistentroom']; - if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true; - else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end - self._data.persistent = persistent; - module:log("debug", "persistent=%s", tostring(persistent)); + local persistent = fields['muc#roomconfig_persistentroom']; + if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true; + else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + self._data.persistent = persistent; + module:log("debug", "persistent=%s", tostring(persistent)); - local public = fields['muc#roomconfig_publicroom']; - if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true; - else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end - self._data.hidden = not public and true or nil; + local public = fields['muc#roomconfig_publicroom']; + if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true; + else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + self._data.hidden = not public and true or nil; - if self.save then self:save(true); end - origin.send(st.reply(stanza)); + if self.save then self:save(true); end + origin.send(st.reply(stanza)); +end + +function room_mt:destroy(newjid, reason, password) + local pr = st.presence({type = "unavailable"}) + :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("item", { affiliation='none', role='none' }):up() + :tag("destroy", {jid=newjid}) + if reason then pr:tag("reason"):text(reason):up(); end + if password then pr:tag("password"):text(password):up(); end + for nick, occupant in pairs(self._occupants) do + pr.attr.from = nick; + for jid in pairs(occupant.sessions) do + pr.attr.to = jid; + self:_route_stanza(pr); + self._jid_nick[jid] = nil; + end + self._occupants[nick] = nil; end + self._data.persistent = nil; + if self.save then self:save(true); end end function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc @@ -509,7 +530,30 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha origin.send(st.error_reply(stanza, "cancel", "bad-request")); end elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - self:handle_form(origin, stanza); + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + elseif stanza.attr.type == "get" then + self:send_form(origin, stanza); + elseif stanza.attr.type == "set" then + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "auth", "bad-request")); + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; + end + end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + else + self:process_form(origin, stanza); + end + end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end @@ -517,17 +561,26 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha local from, to = stanza.attr.from, stanza.attr.to; local room = jid_bare(to); local current_nick = self._jid_nick[from]; - if not current_nick then -- not in room + local occupant = self._occupants[current_nick]; + if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + elseif occupant.role == "visitor" then + origin.send(st.error_reply(stanza, "cancel", "forbidden")); else local from = stanza.attr.from; stanza.attr.from = current_nick; local subject = getText(stanza, {"subject"}); if subject then - self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza + if occupant.role == "moderator" then + self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza + else + stanza.attr.from = from; + origin.send(st.error_reply(stanza, "cancel", "forbidden")); + end else self:broadcast_message(stanza, true); end + stanza.attr.from = from; end elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then local current_nick = self._jid_nick[stanza.attr.from]; |