From: Matthew Wild Date: Thu, 2 Jun 2011 14:28:12 +0000 (+0100) Subject: Merge 0.7->0.8 X-Git-Url: https://git.enpas.org/?a=commitdiff_plain;h=c269396a37a8bef1b71982f2cdd336324eb72d07;p=prosody.git Merge 0.7->0.8 --- c269396a37a8bef1b71982f2cdd336324eb72d07 diff --cc certs/openssl.cnf index db1640b9,db1640b9..44fc0424 --- a/certs/openssl.cnf +++ b/certs/openssl.cnf @@@ -43,10 -43,10 +43,10 @@@ subjectAltName = @subject_alternative # See http://tools.ietf.org/html/draft-ietf-xmpp-3920bis#section-13.7.1.2 for more info. DNS.0 = example.com --otherName.0 = xmppAddr;FORMAT:UTF8,UTF8:example.com ++otherName.0 = xmppAddr;UTF8:example.com otherName.1 = SRVName;IA5STRING:_xmpp-client.example.com otherName.2 = SRVName;IA5STRING:_xmpp-server.example.com DNS.1 = conference.example.com --otherName.3 = xmppAddr;FORMAT:UTF8,UTF8:conference.example.com ++otherName.3 = xmppAddr;UTF8:conference.example.com otherName.4 = SRVName;IA5STRING:_xmpp-server.conference.example.com diff --cc core/certmanager.lua index 0dc0bfd4,3dd06585..7f1ca42e --- a/core/certmanager.lua +++ b/core/certmanager.lua @@@ -19,60 -9,51 +19,58 @@@ local config_path = prosody.paths.confi module "certmanager" --- These are the defaults if not overridden in the config -local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; }; -local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; }; - -local default_ssl_ctx_mt = { __index = default_ssl_ctx }; -local default_ssl_ctx_in_mt = { __index = default_ssl_ctx_in }; - -- Global SSL options if not overridden per-host local default_ssl_config = configmanager.get("*", "core", "ssl"); +local default_capath = "/etc/ssl/certs"; - local default_verify = (ssl and ssl.x509 and { "peer", "client_once", "continue", "ignore_purpose" }) or "none"; - local default_options = { "no_sslv2" }; -function create_context(host, mode, config) - local ssl_config = config and config.core.ssl or default_ssl_config; - if ssl and ssl_config then - local ctx, err = ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt)); - if not ctx then - err = err or "invalid ssl config" - local file = err:match("^error loading (.-) %("); - if file then - if file == "private key" then - file = ssl_config.key or "your private key"; - elseif file == "certificate" then - file = ssl_config.certificate or "your certificate file"; - end - local reason = err:match("%((.+)%)$") or "some reason"; - if reason == "Permission denied" then - reason = "Check that the permissions allow Prosody to read this file."; - elseif reason == "No such file or directory" then - reason = "Check that the path is correct, and the file exists."; - elseif reason == "system lib" then - reason = "Previous error (see logs), or other system error."; - elseif reason == "(null)" or not reason then - reason = "Check that the file exists and the permissions are correct"; - else - reason = "Reason: "..tostring(reason):lower(); - end - log("error", "SSL/TLS: Failed to load %s: %s", file, reason); +function create_context(host, mode, user_ssl_config) + user_ssl_config = user_ssl_config or default_ssl_config; + + if not ssl then return nil, "LuaSec (required for encryption) was not found"; end + if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end + + local ssl_config = { + mode = mode; + protocol = user_ssl_config.protocol or "sslv23"; + key = resolve_path(config_path, user_ssl_config.key); + password = user_ssl_config.password; + certificate = resolve_path(config_path, user_ssl_config.certificate); + capath = resolve_path(config_path, user_ssl_config.capath or default_capath); + cafile = resolve_path(config_path, user_ssl_config.cafile); - verify = user_ssl_config.verify or default_verify; - options = user_ssl_config.options or default_options; ++ verify = user_ssl_config.verify or "none"; ++ options = user_ssl_config.options or "no_sslv2"; + ciphers = user_ssl_config.ciphers; + depth = user_ssl_config.depth; + }; + + local ctx, err = ssl_newcontext(ssl_config); + if not ctx then + err = err or "invalid ssl config" + local file = err:match("^error loading (.-) %("); + if file then + if file == "private key" then + file = ssl_config.key or "your private key"; + elseif file == "certificate" then + file = ssl_config.certificate or "your certificate file"; + end + local reason = err:match("%((.+)%)$") or "some reason"; + if reason == "Permission denied" then + reason = "Check that the permissions allow Prosody to read this file."; + elseif reason == "No such file or directory" then + reason = "Check that the path is correct, and the file exists."; + elseif reason == "system lib" then + reason = "Previous error (see logs), or other system error."; + elseif reason == "(null)" or not reason then + reason = "Check that the file exists and the permissions are correct"; else - log("error", "SSL/TLS: Error initialising for host %s: %s", host, err ); + reason = "Reason: "..tostring(reason):lower(); end - end - return ctx, err; - elseif not ssl then - return nil, "LuaSec (required for encryption) was not found"; + log("error", "SSL/TLS: Failed to load %s: %s", file, reason); + else + log("error", "SSL/TLS: Error initialising for host %s: %s", host, err ); + end end - return nil, "No SSL/TLS configuration present for "..host; + return ctx, err; end function reload_ssl_config() diff --cc core/s2smanager.lua index f49fae9d,0c29da14..fd9a72d0 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@@ -27,9 -28,8 +27,8 @@@ local modulemanager = require "core.mod local st = require "stanza"; local stanza = st.stanza; local nameprep = require "util.encodings".stringprep.nameprep; - local cert_verify_identity = require "util.x509".verify_identity; -local fire_event = require "core.eventmanager".fire_event; +local fire_event = prosody.events.fire_event; local uuid_gen = require "util.uuid".generate; local logger_init = require "util.logger".init; @@@ -441,26 -375,11 +409,23 @@@ function streamopened(session, attr session.streamid = uuid_gen(); (session.log or log)("debug", "incoming s2s received "); - if session.to_host and not hosts[session.to_host] then - -- Attempting to connect to a host we don't serve - session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host }); - return; + if session.to_host then + if not hosts[session.to_host] then + -- Attempting to connect to a host we don't serve + session:close({ + condition = "host-unknown"; + text = "This host does not serve "..session.to_host + }); + return; + elseif hosts[session.to_host].disallow_s2s then + -- Attempting to connect to a host that disallows s2s + session:close({ + condition = "policy-violation"; + text = "Server-to-server communication is not allowed to this host"; + }); + return; + end end - - if session.secure and not session.cert_chain_status then check_cert_status(session); end - send(""); send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag()); @@@ -625,7 -529,7 +588,7 @@@ en function destroy_session(session, reason) if session.destroyed then return; end - (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or "")); - (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)); ++ (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)); if session.direction == "outgoing" then hosts[session.from_host].s2sout[session.to_host] = nil; diff --cc net/dns.lua index 3f1cb4f6,c0de97fd..c905f56c --- a/net/dns.lua +++ b/net/dns.lua @@@ -482,12 -434,15 +474,12 @@@ function resolver:SRV(rr) -- - - - rr.srv.target = self:name(); end - -function SRV_tostring(rr) -- - - - - - - - - - - - - - - - - - SRV_tostring - local s = rr.srv; - return string.format( '%5d %5d %5d %s', s.priority, s.weight, s.port, s.target ); +function resolver:PTR(rr) + rr.ptr = self:name(); end - function resolver:TXT(rr) -- - - - - - - - - - - - - - - - - - - - - - TXT - rr.txt = self:sub (self:byte()); + rr.txt = self:sub (rr.rdlength); end diff --cc plugins/mod_admin_telnet.lua index da40f57e,da40f57e..712e9eb7 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@@ -19,7 -19,7 +19,6 @@@ local console_listener = { default_por require "util.iterators"; local jid_bare = require "util.jid".bare; local set, array = require "util.set", require "util.array"; --local cert_verify_identity = require "util.x509".verify_identity; local commands = {}; local def_env = {}; @@@ -499,7 -499,7 +498,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.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" 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 @@@ -536,7 -536,7 +535,7 @@@ -- 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.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" 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 @@@ -562,109 -562,109 +561,6 @@@ return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections"; end --local function print_subject(print, subject) -- for _, entry in ipairs(subject) do -- print( -- (" %s: %q"):format( -- entry.name or entry.oid, -- entry.value:gsub("[\r\n%z%c]", " ") -- ) -- ); -- end --end -- --function def_env.s2s:showcert(domain) -- local ser = require "util.serialization".serialize; -- local print = self.session.print; -- local domain_sessions = set.new(array.collect(keys(incoming_s2s))) -- /function(session) return session.from_host == domain; end; -- for local_host in values(prosody.hosts) do -- local s2sout = local_host.s2sout; -- if s2sout and s2sout[domain] then -- domain_sessions:add(s2sout[domain]); -- end -- end -- local cert_set = {}; -- for session in domain_sessions do -- local conn = session.conn; -- conn = conn and conn:socket(); -- if not conn.getpeercertificate then -- if conn.dohandshake then -- error("This version of LuaSec does not support certificate viewing"); -- end -- else -- local cert = conn:getpeercertificate(); -- if cert then -- local digest = cert:digest("sha1"); -- if not cert_set[digest] then -- local chain_valid, chain_err = conn:getpeerchainvalid(); -- cert_set[digest] = { -- { -- from = session.from_host, -- to = session.to_host, -- direction = session.direction -- }; -- chain_valid = chain_valid; -- chain_err = chain_err; -- cert = cert; -- }; -- else -- table.insert(cert_set[digest], { -- from = session.from_host, -- to = session.to_host, -- direction = session.direction -- }); -- end -- end -- end -- end -- local domain_certs = array.collect(values(cert_set)); -- -- Phew. We now have a array of unique certificates presented by domain. -- local print = self.session.print; -- local n_certs = #domain_certs; -- -- if n_certs == 0 then -- return "No certificates found for "..domain; -- end -- -- local function _capitalize_and_colon(byte) -- return string.upper(byte)..":"; -- end -- local function pretty_fingerprint(hash) -- return hash:gsub("..", _capitalize_and_colon):sub(1, -2); -- end -- -- for cert_info in values(domain_certs) do -- local cert = cert_info.cert; -- print("---") -- print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1"))); -- print(""); -- local n_streams = #cert_info; -- print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":"); -- for _, stream in ipairs(cert_info) do -- if stream.direction == "incoming" then -- print(" "..stream.to.." <- "..stream.from); -- else -- print(" "..stream.from.." -> "..stream.to); -- end -- end -- print(""); -- local chain_valid, err = cert_info.chain_valid, cert_info.chain_err; -- local valid_identity = cert_verify_identity(domain, "xmpp-server", cert); -- print("Trusted certificate: "..(chain_valid and "Yes" or ("No ("..err..")"))); -- print("Issuer: "); -- print_subject(print, cert:issuer()); -- print(""); -- print("Valid for "..domain..": "..(valid_identity and "Yes" or "No")); -- print("Subject:"); -- print_subject(print, cert:subject()); -- end -- print("---"); -- return ("Showing "..n_certs.." certificate" -- ..(n_certs==1 and "" or "s") -- .." presented by "..domain.."."); --end -- function def_env.s2s:close(from, to) local print, count = self.session.print, 0; diff --cc plugins/mod_bosh.lua index c2c7eae9,66a79785..a747f3cb --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@@ -160,56 -132,25 +160,51 @@@ function handle_request(method, body, r request.reply_before = os_time() + session.bosh_wait; waiting_requests[request] = true; end - if inactive_sessions[session] then - -- Session was marked as inactive, since we have - -- a request open now, unmark it - inactive_sessions[session] = nil; - end end - if session.bosh_terminate then - session.log("debug", "Closing session with %d requests open", #session.requests); - session:close(); - return nil; - else - return true; -- Inform httpserver we shall reply later - end + return true; -- Inform httpserver we shall reply later end end local function bosh_reset_stream(session) session.notopen = true; end +local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" }; + local function bosh_close_stream(session, reason) (session.log or log)("info", "BOSH client disconnected"); - session_close_reply.attr.condition = reason; + + local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", + ["xmlns:streams"] = xmlns_streams }); + + + if reason then + close_reply.attr.condition = "remote-stream-error"; + if type(reason) == "string" then -- assume stream error + close_reply:tag("stream:error") + :tag(reason, {xmlns = xmlns_xmpp_streams}); + elseif type(reason) == "table" then + if reason.condition then + close_reply:tag("stream:error") + :tag(reason.condition, stream_xmlns_attr):up(); + if reason.text then + close_reply:tag("text", stream_xmlns_attr):text(reason.text):up(); + end + if reason.extra then + close_reply:add_child(reason.extra); + end + elseif reason.name then -- a stanza + close_reply = reason; + end + end + log("info", "Disconnecting client, is: %s", tostring(close_reply)); + end + + local session_close_response = { headers = default_headers, body = tostring(close_reply) }; + ++ --FIXME: Quite sure we shouldn't reply to all requests with the error for _, held_request in ipairs(session.requests) do - held_request:send(session_close_reply); + held_request:send(session_close_response); held_request:destroy(); end sessions[session.sid] = nil; diff --cc plugins/mod_dialback.lua index 91291e24,91291e24..8c80dce6 --- a/plugins/mod_dialback.lua +++ b/plugins/mod_dialback.lua @@@ -131,22 -131,22 +131,12 @@@ module:hook("stanza/jabber:server:dialb end end); --module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza) -- if origin.external_auth == "failed" then -- module:log("debug", "SASL EXTERNAL failed, falling back to dialback"); -- s2s_initiate_dialback(origin); -- return true; -- end --end, 100); -- module:hook_stanza(xmlns_stream, "features", function (origin, stanza) -- if not origin.external_auth or origin.external_auth == "failed" then -- s2s_initiate_dialback(origin); -- return true; -- end ++ s2s_initiate_dialback(origin); ++ return true; end, 100); -- Offer dialback to incoming hosts module:hook("s2s-stream-features", function (data) -- data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up(); ++ data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up(); end); diff --cc plugins/mod_register.lua index 8a818d02,2818e336..25c09dfa --- a/plugins/mod_register.lua +++ b/plugins/mod_register.lua @@@ -13,86 -13,72 +13,73 @@@ local datamanager = require "util.datam local usermanager_user_exists = require "core.usermanager".user_exists; local usermanager_create_user = require "core.usermanager".create_user; local usermanager_set_password = require "core.usermanager".set_password; -local datamanager_store = require "util.datamanager".store; +local usermanager_delete_user = require "core.usermanager".delete_user; local os_time = os.time; local nodeprep = require "util.encodings".stringprep.nodeprep; +local jid_bare = require "util.jid".bare; + +local compat = module:get_option_boolean("registration_compat", true); - local allow_registration = module:get_option_boolean("allow_registration", false); module:add_feature("jabber:iq:register"); - local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up(); - module:hook("stream-features", function(event) - local session, features = event.origin, event.features; - - -- Advertise registration to unauthorized clients only. - if not(allow_registration) or session.type ~= "c2s_unauthed" then - return - end - - features:add_child(register_stream_feature); - end); - -module:add_iq_handler("c2s", "jabber:iq:register", function (session, stanza) - if stanza.tags[1].name == "query" then - local query = stanza.tags[1]; - if stanza.attr.type == "get" then - local reply = st.reply(stanza); - reply:tag("query", {xmlns = "jabber:iq:register"}) - :tag("registered"):up() - :tag("username"):text(session.username):up() - :tag("password"):up(); - session.send(reply); - elseif stanza.attr.type == "set" then - if query.tags[1] and query.tags[1].name == "remove" then - -- TODO delete user auth data, send iq response, kick all user resources with a , delete all user data - local username, host = session.username, session.host; - --session.send(st.error_reply(stanza, "cancel", "not-allowed")); - --return; - --usermanager_set_password(username, host, nil); -- Disable account - -- FIXME the disabling currently allows a different user to recreate the account - -- we should add an in-memory account block mode when we have threading - session.send(st.reply(stanza)); - local roster = session.roster; - for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources - session:close({condition = "not-authorized", text = "Account deleted"}); - end - -- TODO datamanager should be able to delete all user data itself - datamanager.store(username, host, "vcard", nil); - datamanager.store(username, host, "private", nil); - datamanager.list_store(username, host, "offline", nil); - local bare = username.."@"..host; - for jid, item in pairs(roster) do - if jid and jid ~= "pending" then - if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then - core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid})); - end - if item.subscription == "both" or item.subscription == "to" or item.ask then - core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid})); - end +local function handle_registration_stanza(event) + local session, stanza = event.origin, event.stanza; + + local query = stanza.tags[1]; + if stanza.attr.type == "get" then + local reply = st.reply(stanza); + reply:tag("query", {xmlns = "jabber:iq:register"}) + :tag("registered"):up() + :tag("username"):text(session.username):up() + :tag("password"):up(); + session.send(reply); + else -- stanza.attr.type == "set" + if query.tags[1] and query.tags[1].name == "remove" then + -- TODO delete user auth data, send iq response, kick all user resources with a , delete all user data + local username, host = session.username, session.host; + + local ok, err = usermanager_delete_user(username, host); + + if not ok then + module:log("debug", "Removing user account %s@%s failed: %s", username, host, err); + session.send(st.error_reply(stanza, "cancel", "service-unavailable", err)); + return true; + end + + session.send(st.reply(stanza)); + local roster = session.roster; + for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources + session:close({condition = "not-authorized", text = "Account deleted"}); + end + -- TODO datamanager should be able to delete all user data itself + datamanager.store(username, host, "vcard", nil); + datamanager.store(username, host, "private", nil); + datamanager.list_store(username, host, "offline", nil); + local bare = username.."@"..host; + for jid, item in pairs(roster) do + if jid and jid ~= "pending" then + if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then + core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid})); + end + if item.subscription == "both" or item.subscription == "to" or item.ask then + core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid})); end end - datamanager.store(username, host, "roster", nil); - datamanager.store(username, host, "privacy", nil); - datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end - module:log("info", "User removed their account: %s@%s", username, host); - module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session }); - else - local username = query:child_with_name("username"); - local password = query:child_with_name("password"); - if username and password then - -- FIXME shouldn't use table.concat - username = nodeprep(table.concat(username)); - password = table.concat(password); - if username == session.username then - if usermanager_set_password(username, session.host, password) then - session.send(st.reply(stanza)); - else - -- TODO unable to write file, file may be locked, etc, what's the correct error? - session.send(st.error_reply(stanza, "wait", "internal-server-error")); - end + end + datamanager.store(username, host, "roster", nil); + datamanager.store(username, host, "privacy", nil); + module:log("info", "User removed their account: %s@%s", username, host); + module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session }); + else + local username = nodeprep(query:get_child("username"):get_text()); + local password = query:get_child("password"):get_text(); + if username and password then + if username == session.username then + if usermanager_set_password(username, password, session.host) then + session.send(st.reply(stanza)); else - session.send(st.error_reply(stanza, "modify", "bad-request")); + -- TODO unable to write file, file may be locked, etc, what's the correct error? + session.send(st.error_reply(stanza, "wait", "internal-server-error")); end else session.send(st.error_reply(stanza, "modify", "bad-request")); @@@ -124,12 -99,10 +111,12 @@@ local blacklisted_ips = module:get_opti for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end -module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, stanza) - if module:get_option("allow_registration") == false then +module:hook("stanza/iq/jabber:iq:register:query", function(event) + local session, stanza = event.origin, event.stanza; + - if not(allow_registration) or session.type ~= "c2s_unauthed" then ++ if module:get_option("allow_registration") == false or session.type ~= "c2s_unauthed" then session.send(st.error_reply(stanza, "cancel", "service-unavailable")); - elseif stanza.tags[1].name == "query" then + else local query = stanza.tags[1]; if stanza.attr.type == "get" then local reply = st.reply(stanza); diff --cc plugins/mod_saslauth.lua index 1c0d0673,d407e5da..4906d01f --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@@ -11,17 -11,28 +11,14 @@@ local st = require "util.stanza"; local sm_bind_resource = require "core.sessionmanager".bind_resource; local sm_make_authenticated = require "core.sessionmanager".make_authenticated; - local s2s_make_authenticated = require "core.s2smanager".make_authenticated; local base64 = require "util.encodings".base64; - local cert_verify_identity = require "util.x509".verify_identity; - local nodeprep = require "util.encodings".stringprep.nodeprep; -local datamanager_load = require "util.datamanager".load; -local usermanager_validate_credentials = require "core.usermanager".validate_credentials; -local usermanager_get_supported_methods = require "core.usermanager".get_supported_methods; -local usermanager_user_exists = require "core.usermanager".user_exists; -local usermanager_get_password = require "core.usermanager".get_password; -local t_concat, t_insert = table.concat, table.insert; +local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; local tostring = tostring; -local jid_split = require "util.jid".split; -local md5 = require "util.hashes".md5; -local config = require "core.configmanager"; local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption"); -local sasl_backend = module:get_option("sasl_backend") or "builtin"; - --- Cyrus config options -local require_provisioning = module:get_option("cyrus_require_provisioning") or false; -local cyrus_service_realm = module:get_option("cyrus_service_realm"); -local cyrus_service_name = module:get_option("cyrus_service_name"); -local cyrus_application_name = module:get_option("cyrus_application_name"); +local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth") local log = module._log; @@@ -81,160 -164,11 +78,45 @@@ local function sasl_process_cdata(sessi local s = build_reply(status, ret, err_msg); log("debug", "sasl reply: %s", tostring(s)); session.send(s); + return true; end - module:hook_stanza(xmlns_sasl, "success", function (session, stanza) - if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end - module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host); - session.external_auth = "succeeded" - 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(""); - session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag()); - - s2s_make_authenticated(session, session.to_host); - return true; - end) - - module:hook_stanza(xmlns_sasl, "failure", function (session, stanza) - if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end - - module:log("info", "SASL EXTERNAL with %s failed", session.to_host) - -- TODO: Log the failure reason - session.external_auth = "failed" - end, 500) - - module:hook_stanza(xmlns_sasl, "failure", function (session, stanza) - -- TODO: Dialback wasn't loaded. Do something useful. - end, 90) - - module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) - if session.type ~= "s2sout_unauthed" or not session.secure then return; end - - local mechanisms = stanza:get_child("mechanisms", xmlns_sasl) - if mechanisms then - for mech in mechanisms:childtags() do - if mech[1] == "EXTERNAL" then - module:log("debug", "Initiating SASL EXTERNAL with %s", session.to_host); - local reply = st.stanza("auth", {xmlns = xmlns_sasl, mechanism = "EXTERNAL"}); - reply:text(base64.encode(session.from_host)) - session.sends2s(reply) - session.external_auth = "attempting" - return true - end - end - end - end, 150); - - local function s2s_external_auth(session, stanza) - local mechanism = stanza.attr.mechanism; - - if not session.secure then - if mechanism == "EXTERNAL" then - session.sends2s(build_reply("failure", "encryption-required")) - else - session.sends2s(build_reply("failure", "invalid-mechanism")) - end - return true; - end - - if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then - session.sends2s(build_reply("failure", "invalid-mechanism")) - return true; - end - - local text = stanza[1] - if not text then - session.sends2s(build_reply("failure", "malformed-request")) - return true - end - - -- Either the value is "=" and we've already verified the external - -- cert identity, or the value is a string and either matches the - -- from_host ( - - text = base64.decode(text) - if not text then - session.sends2s(build_reply("failure", "incorrect-encoding")) - return true; - end - - if session.cert_identity_status == "valid" then - if text ~= "" and text ~= session.from_host then - session.sends2s(build_reply("failure", "invalid-authzid")) - return true - end - else - if text == "" then - session.sends2s(build_reply("failure", "invalid-authzid")) - return true - end - - local cert = session.conn:socket():getpeercertificate() - if (cert_verify_identity(text, "xmpp-server", cert)) then - session.cert_identity_status = "valid" - else - session.cert_identity_status = "invalid" - session.sends2s(build_reply("failure", "invalid-authzid")) - return true - end - end - - session.external_auth = "succeeded" - - if not session.from_host then - session.from_host = text; - end - session.sends2s(build_reply("success")) - module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host); - s2s_make_authenticated(session, text or session.from_host) - session:reset_stream(); - return true - end - -module:add_handler("c2s_unauthed", "auth", xmlns_sasl, sasl_handler); -module:add_handler("c2s_unauthed", "abort", xmlns_sasl, sasl_handler); -module:add_handler("c2s_unauthed", "response", xmlns_sasl, sasl_handler); +module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event) + local session, stanza = event.origin, event.stanza; - if session.type == "s2sin_unauthed" then - return s2s_external_auth(session, stanza) - end - + if session.type ~= "c2s_unauthed" then return; end + + if session.sasl_handler and session.sasl_handler.selected then + session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one + end + if not session.sasl_handler then + session.sasl_handler = usermanager_get_sasl_handler(module.host); + end + local mechanism = stanza.attr.mechanism; + if not session.secure and (secure_auth_only or (mechanism == "PLAIN" and not allow_unencrypted_plain_auth)) then + session.send(build_reply("failure", "encryption-required")); + return true; + end + local valid_mechanism = session.sasl_handler:select(mechanism); + if not valid_mechanism then + session.send(build_reply("failure", "invalid-mechanism")); + return true; + end + return sasl_process_cdata(session, stanza); +end); +module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event) + local session = event.origin; + if not(session.sasl_handler and session.sasl_handler.selected) then + session.send(build_reply("failure", "not-authorized", "Out of order SASL element")); + return true; + end + return sasl_process_cdata(session, event.stanza); +end); +module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event) + local session = event.origin; + session.sasl_handler = nil; + session.send(build_reply("failure", "aborted")); + return true; +end); local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' }; local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' }; @@@ -259,22 -199,8 +141,8 @@@ module:hook("stream-features", function end end); - module:hook("s2s-stream-features", function(event) - local origin, features = event.origin, event.features; - if origin.secure and origin.type == "s2sin_unauthed" then - -- Offer EXTERNAL if chain is valid and either we didn't validate - -- the identity or it passed. - if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable - module:log("debug", "Offering SASL EXTERNAL") - features:tag("mechanisms", { xmlns = xmlns_sasl }) - :tag("mechanism"):text("EXTERNAL") - :up():up(); - end - end - end); - -module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", function(session, stanza) - log("debug", "Client requesting a resource bind"); +module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event) + local origin, stanza = event.origin, event.stanza; local resource; if stanza.attr.type == "set" then local bind = stanza.tags[1]; diff --cc plugins/muc/muc.lib.lua index ec85d185,18c80325..647bf915 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@@ -213,22 -166,9 +213,20 @@@ function room_mt:send_history(to, stanz end function room_mt:get_disco_info(stanza) - local count = 0; for _ in pairs(self._occupants) do count = count + 1; end return st.reply(stanza):query("http://jabber.org/protocol/disco#info") - :tag("identity", {category="conference", type="text"}):up() - :tag("feature", {var="http://jabber.org/protocol/muc"}); + :tag("identity", {category="conference", type="text", name=self:get_name()}):up() + :tag("feature", {var="http://jabber.org/protocol/muc"}):up() + :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up() + :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up() + :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up() + :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up() + :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up() + :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() + :add_child(dataform.new({ + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, - { name = "muc#roominfo_description", label = "Description"}, - { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) } ++ { name = "muc#roominfo_description", label = "Description"} + }):form({["muc#roominfo_description"] = self:get_description()}, 'result')) + ; end function room_mt:get_disco_items(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); diff --cc util-src/encodings.c index 9b6c6cf4,9b6c6cf4..2a4653fb --- a/util-src/encodings.c +++ b/util-src/encodings.c @@@ -117,8 -117,8 +117,55 @@@ static const luaL_Reg Reg_base64[] }; /***************** STRINGPREP *****************/ --#ifdef USE_STRINGPREP_ICU ++#ifndef USE_STRINGPREP_ICU ++/****************** libidn ********************/ ++ ++#include ++ ++static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) ++{ ++ size_t len; ++ const char *s; ++ char string[1024]; ++ int ret; ++ if(!lua_isstring(L, 1)) { ++ lua_pushnil(L); ++ return 1; ++ } ++ s = lua_tolstring(L, 1, &len); ++ if (len >= 1024) { ++ lua_pushnil(L); ++ return 1; /* TODO return error message */ ++ } ++ strcpy(string, s); ++ ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile); ++ if (ret == STRINGPREP_OK) { ++ lua_pushstring(L, string); ++ return 1; ++ } else { ++ lua_pushnil(L); ++ return 1; /* TODO return error message */ ++ } ++} ++ ++#define MAKE_PREP_FUNC(myFunc, prep) \ ++static int myFunc(lua_State *L) { return stringprep_prep(L, prep); } ++ ++MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep) /** stringprep.nameprep(s) */ ++MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep) /** stringprep.nodeprep(s) */ ++MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep) /** stringprep.resourceprep(s) */ ++MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep) /** stringprep.saslprep(s) */ ++ ++static const luaL_Reg Reg_stringprep[] = ++{ ++ { "nameprep", Lstringprep_nameprep }, ++ { "nodeprep", Lstringprep_nodeprep }, ++ { "resourceprep", Lstringprep_resourceprep }, ++ { "saslprep", Lstringprep_saslprep }, ++ { NULL, NULL } ++}; ++#else #include #include #include @@@ -145,17 -145,17 +192,13 @@@ static int icu_stringprep_prep(lua_Stat return 1; } u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err); -- if (U_FAILURE(err)) { -- luah_pushnil(L); -- return 1; -- } prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, 0, NULL, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, prepped, prepped_len, &err); -- if (U_SUCCESS(err) && output_len < 1024) ++ if(output_len < 1024) lua_pushlstring(L, output, output_len); else lua_pushnil(L); @@@ -196,58 -196,58 +239,49 @@@ static const luaL_Reg Reg_stringprep[] { "saslprep", Lstringprep_saslprep }, { NULL, NULL } }; --#else /* USE_STRINGPREP_ICU */ ++#endif ++/***************** IDNA *****************/ ++#ifndef USE_STRINGPREP_ICU /****************** libidn ********************/ --#include ++#include ++#include --static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) ++static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */ { size_t len; -- const char *s; -- char string[1024]; -- int ret; -- if(!lua_isstring(L, 1)) { -- lua_pushnil(L); ++ const char *s = luaL_checklstring(L, 1, &len); ++ char* output = NULL; ++ int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES); ++ if (ret == IDNA_SUCCESS) { ++ lua_pushstring(L, output); ++ idn_free(output); return 1; -- } -- s = lua_tolstring(L, 1, &len); -- if (len >= 1024) { ++ } else { lua_pushnil(L); ++ idn_free(output); return 1; /* TODO return error message */ } -- strcpy(string, s); -- ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile); -- if (ret == STRINGPREP_OK) { -- lua_pushstring(L, string); ++} ++ ++static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */ ++{ ++ size_t len; ++ const char *s = luaL_checklstring(L, 1, &len); ++ char* output = NULL; ++ int ret = idna_to_unicode_8z8z(s, &output, 0); ++ if (ret == IDNA_SUCCESS) { ++ lua_pushstring(L, output); ++ idn_free(output); return 1; } else { lua_pushnil(L); ++ idn_free(output); return 1; /* TODO return error message */ } } -- --#define MAKE_PREP_FUNC(myFunc, prep) \ --static int myFunc(lua_State *L) { return stringprep_prep(L, prep); } -- --MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep) /** stringprep.nameprep(s) */ --MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep) /** stringprep.nodeprep(s) */ --MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep) /** stringprep.resourceprep(s) */ --MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep) /** stringprep.saslprep(s) */ -- --static const luaL_Reg Reg_stringprep[] = --{ -- { "nameprep", Lstringprep_nameprep }, -- { "nodeprep", Lstringprep_nodeprep }, -- { "resourceprep", Lstringprep_resourceprep }, -- { "saslprep", Lstringprep_saslprep }, -- { NULL, NULL } --}; --#endif -- --/***************** IDNA *****************/ --#ifdef USE_STRINGPREP_ICU ++#else #include #include /* IDNA2003 or IDNA2008 ? ? ? */ @@@ -262,18 -262,18 +296,13 @@@ static int Lidna_to_ascii(lua_State *L char output[1024]; u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); -- if (U_FAILURE(err)) { -- lua_pushnil(L); -- return 1; -- } -- dest_len = uidna_IDNToASCII(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); -- if (U_SUCCESS(err) && output_len < 1024) ++ if(output_len < 1024) lua_pushlstring(L, output, output_len); else lua_pushnil(L); @@@ -286,70 -286,70 +315,25 @@@ static int Lidna_to_unicode(lua_State * size_t len; int32_t ulen, dest_len, output_len; const char *s = luaL_checklstring(L, 1, &len); -- UChar ustr[1024]; ++ UChar* ustr; UErrorCode err = U_ZERO_ERROR; UChar dest[1024]; char output[1024]; u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); -- if (U_FAILURE(err)) { -- lua_pushnil(L); -- return 1; -- } -- dest_len = uidna_IDNToUnicode(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); -- if (U_SUCCESS(err) && output_len < 1024) ++ if(output_len < 1024) lua_pushlstring(L, output, output_len); else lua_pushnil(L); return 1; } } -- --#else /* USE_STRINGPREP_ICU */ --/****************** libidn ********************/ -- --#include --#include -- --static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */ --{ -- size_t len; -- const char *s = luaL_checklstring(L, 1, &len); -- char* output = NULL; -- int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES); -- if (ret == IDNA_SUCCESS) { -- lua_pushstring(L, output); -- idn_free(output); -- return 1; -- } else { -- lua_pushnil(L); -- idn_free(output); -- return 1; /* TODO return error message */ -- } --} -- --static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */ --{ -- size_t len; -- const char *s = luaL_checklstring(L, 1, &len); -- char* output = NULL; -- int ret = idna_to_unicode_8z8z(s, &output, 0); -- if (ret == IDNA_SUCCESS) { -- lua_pushstring(L, output); -- idn_free(output); -- return 1; -- } else { -- lua_pushnil(L); -- idn_free(output); -- return 1; /* TODO return error message */ -- } --} #endif static const luaL_Reg Reg_idna[] = diff --cc util/xmppstream.lua index cf485dac,cf485dac..69e7690d --- a/util/xmppstream.lua +++ b/util/xmppstream.lua @@@ -9,13 -9,13 +9,10 @@@ local lxp = require "lxp"; local st = require "util.stanza"; --local stanza_mt = st.stanza_mt; local tostring = tostring; local t_insert = table.insert; local t_concat = table.concat; --local t_remove = table.remove; --local setmetatable = setmetatable; local default_log = require "util.logger".init("xmppstream"); @@@ -66,13 -66,13 +63,12 @@@ function new_sax_handlers(session, stre local stream_default_ns = stream_callbacks.default_ns; -- local stack = {}; local chardata, stanza = {}; local non_streamns_depth = 0; function xml_handlers:StartElement(tagname, attr) if stanza and #chardata > 0 then -- We have some character data in the buffer -- t_insert(stanza, t_concat(chardata)); ++ stanza:text(t_concat(chardata)); chardata = {}; end local curr_ns,name = tagname:match(ns_pattern); @@@ -116,13 -116,13 +112,9 @@@ cb_error(session, "invalid-top-level-element"); end -- stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); ++ stanza = st.stanza(name, attr); else -- we are inside a stanza, so add a tag -- t_insert(stack, stanza); -- local oldstanza = stanza; -- stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); -- t_insert(oldstanza, stanza); -- t_insert(oldstanza.tags, stanza); ++ stanza:tag(name, attr); end end function xml_handlers:CharacterData(data) @@@ -137,11 -137,11 +129,12 @@@ if stanza then if #chardata > 0 then -- We have some character data in the buffer -- t_insert(stanza, t_concat(chardata)); ++ stanza:text(t_concat(chardata)); chardata = {}; end -- Complete stanza -- if #stack == 0 then ++ local last_add = stanza.last_add; ++ if not last_add or #last_add == 0 then if tagname ~= stream_error_tag then cb_handlestanza(session, stanza); else @@@ -149,7 -149,7 +142,7 @@@ end stanza = nil; else -- stanza = t_remove(stack); ++ stanza:up(); end else if tagname == stream_tag then @@@ -164,12 -164,12 +157,14 @@@ cb_error(session, "parse-error", "unexpected-element-close", name); end stanza, chardata = nil, {}; -- stack = {}; end end -- -- local function restricted_handler() ++ ++ local function restricted_handler(parser) cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1."); ++ if not parser:stop() then ++ error("Failed to abort parsing"); ++ end end if lxp_supports_doctype then @@@ -180,7 -180,7 +175,6 @@@ local function reset() stanza, chardata = nil, {}; -- stack = {}; end local function set_session(stream, new_session)