diff options
-rw-r--r-- | core/rostermanager.lua | 28 | ||||
-rw-r--r-- | core/s2smanager.lua | 32 | ||||
-rw-r--r-- | core/sessionmanager.lua | 31 | ||||
-rw-r--r-- | core/usermanager.lua | 9 | ||||
-rw-r--r-- | net/dns.lua | 3 | ||||
-rw-r--r-- | net/multiplex_listener.lua | 4 | ||||
-rw-r--r-- | net/server_event.lua | 2 | ||||
-rw-r--r-- | net/xmppclient_listener.lua | 75 | ||||
-rw-r--r-- | net/xmppserver_listener.lua | 86 | ||||
-rw-r--r-- | plugins/mod_bosh.lua | 14 | ||||
-rw-r--r-- | plugins/mod_compression.lua | 62 | ||||
-rw-r--r-- | plugins/mod_groups.lua | 10 | ||||
-rw-r--r-- | plugins/mod_iq.lua | 11 | ||||
-rw-r--r-- | plugins/mod_pep.lua | 44 | ||||
-rw-r--r-- | plugins/mod_presence.lua | 60 | ||||
-rw-r--r-- | plugins/mod_privacy.lua | 60 | ||||
-rw-r--r-- | plugins/mod_private.lua | 6 | ||||
-rw-r--r-- | plugins/mod_saslauth.lua | 32 | ||||
-rw-r--r-- | util/datamanager.lua | 18 | ||||
-rw-r--r-- | util/filters.lua | 68 | ||||
-rw-r--r-- | util/sasl/digest-md5.lua | 4 | ||||
-rw-r--r-- | util/sasl/plain.lua | 13 | ||||
-rw-r--r-- | util/sasl/scram.lua | 84 | ||||
-rw-r--r-- | util/sasl_cyrus.lua | 4 |
24 files changed, 472 insertions, 288 deletions
diff --git a/core/rostermanager.lua b/core/rostermanager.lua index e2a92696..59ba6579 100644 --- a/core/rostermanager.lua +++ b/core/rostermanager.lua @@ -93,15 +93,18 @@ function load_roster(username, host) else -- Attempt to load roster for non-loaded user log("debug", "load_roster: loading for offline user: "..username.."@"..host); end - roster = datamanager.load(username, host, "roster") or {}; + local data, err = datamanager.load(username, host, "roster"); + roster = data or {}; if user then user.roster = roster; end - if not roster[false] then roster[false] = { }; end + if not roster[false] then roster[false] = { broken = err or nil }; end if roster[jid] then roster[jid] = nil; log("warn", "roster for "..jid.." has a self-contact"); end - hosts[host].events.fire_event("roster-load", username, host, roster); - return roster; + if not err then + hosts[host].events.fire_event("roster-load", username, host, roster); + end + return roster, err; end function save_roster(username, host, roster) @@ -122,6 +125,7 @@ function save_roster(username, host, roster) if metadata.version ~= true then metadata.version = (metadata.version or 0) + 1; end + if roster[false].broken then return nil, "Not saving broken roster" end return datamanager.store(username, host, "roster", roster); end log("warn", "save_roster: user had no roster to save"); @@ -186,10 +190,22 @@ function process_inbound_unsubscribe(username, host, jid) end end +local function _get_online_roster_subscription(jidA, jidB) + local user = bare_sessions[jidA]; + local item = user and (user.roster[jidB] or { subscription = "none" }); + return item and item.subscription; +end function is_contact_subscribed(username, host, jid) - local roster = load_roster(username, host); + do + local selfjid = username.."@"..host; + local subscription = _get_online_roster_subscription(selfjid, jid); + if subscription then return (subscription == "both" or subscription == "from"); end + local subscription = _get_online_roster_subscription(jid, selfjid); + if subscription then return (subscription == "both" or subscription == "to"); end + end + local roster, err = load_roster(username, host); local item = roster[jid]; - return item and (item.subscription == "from" or item.subscription == "both"); + return item and (item.subscription == "from" or item.subscription == "both"), err; end function is_contact_pending_in(username, host, jid) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index ca87670a..ced367a3 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -23,6 +23,7 @@ local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, local idna_to_ascii = require "util.encodings".idna.to_ascii; local connlisteners_get = require "net.connlisteners".get; +local initialize_filters = require "util.filters".initialize; local wrapclient = require "net.server".wrapclient; local modulemanager = require "core.modulemanager"; local st = require "stanza"; @@ -137,7 +138,19 @@ function new_incoming(conn) open_sessions = open_sessions + 1; local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$")); session.log = log; - session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); w(conn, tostring(t)); end + local filter = initialize_filters(session); + session.sends2s = function (t) + if t.name then + t = filter("stanzas/out", t); + end + if t then + t = filter("bytes/out", tostring(t)); + if t then + log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); + return w(conn, t); + end + end + end incoming_s2s[session] = true; add_task(connect_timeout, function () if session.conn ~= conn or @@ -166,6 +179,8 @@ function new_outgoing(from_host, to_host, connect) host_session.log = log; end + initialize_filters(host_session); + if connect ~= false then -- Kick the connection attempting machine into life attempt_connection(host_session); @@ -241,7 +256,6 @@ function attempt_connection(host_session, err) end end); - log("debug", "DNS lookup for %s sent, waiting for response before we can connect", to_host); return true; -- Attempt in progress elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV host_session.srv_choice = host_session.srv_choice + 1; @@ -332,8 +346,20 @@ function make_connect(host_session, connect_host, connect_port) -- otherwise it will assume it is a new incoming connection cl.register_outgoing(conn, host_session); + local filter = initialize_filters(host_session); local w, log = conn.write, host_session.log; - host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, tostring(t)); end + host_session.sends2s = function (t) + if t.name then + t = filter("stanzas/out", t); + end + if t then + t = filter("bytes/out", tostring(t)); + if t then + log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); + return w(conn, tostring(t)); + end + end + end host_session:open_stream(from_host, to_host); diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 6e771a84..ac07a793 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -26,6 +26,7 @@ local config_get = require "core.configmanager".get; local nameprep = require "util.encodings".stringprep.nameprep; local resourceprep = require "util.encodings".stringprep.resourceprep; +local initialize_filters = require "util.filters".initialize; local fire_event = require "core.eventmanager".fire_event; local add_task = require "util.timer".add_task; local gettime = require "socket".gettime; @@ -49,8 +50,20 @@ function new_session(conn) end open_sessions = open_sessions + 1; log("debug", "open sessions now: ".. open_sessions); + + local filter = initialize_filters(session); local w = conn.write; - session.send = function (t) w(conn, tostring(t)); end + session.send = function (t) + if t.name then + t = filter("stanzas/out", t); + end + if t then + t = filter("bytes/out", tostring(t)); + if t then + return w(conn, t); + end + end + end session.ip = conn:ip(); local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$"); session.log = logger.init(conn_name); @@ -136,7 +149,7 @@ function bind_resource(session, resource) local sessions = hosts[session.host].sessions[session.username].sessions; local limit = config_get(session.host, "core", "max_resources") or 10; if #sessions >= limit then - return nil, "cancel", "conflict", "Resource limit reached; only "..limit.." resources allowed"; + return nil, "cancel", "resource-constraint", "Resource limit reached; only "..limit.." resources allowed"; end if sessions[resource] then -- Resource conflict @@ -174,7 +187,19 @@ function bind_resource(session, resource) hosts[session.host].sessions[session.username].sessions[resource] = session; full_sessions[session.full_jid] = session; - session.roster = rm_load_roster(session.username, session.host); + local err; + session.roster, err = rm_load_roster(session.username, session.host); + if err then + full_sessions[session.full_jid] = nil; + hosts[session.host].sessions[session.username].sessions[resource] = nil; + session.full_jid = nil; + session.resource = nil; + if next(bare_sessions[session.username..'@'..session.host].sessions) == nil then + bare_sessions[session.username..'@'..session.host] = nil; + hosts[session.host].sessions[session.username] = nil; + end + return nil, "cancel", "internal-server-error", "Error loading roster"; + end hosts[session.host].events.fire_event("resource-bind", {session=session}); diff --git a/core/usermanager.lua b/core/usermanager.lua index a97e2ad7..e4654698 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -16,6 +16,8 @@ local jid_bare = require "util.jid".bare; local config = require "core.configmanager"; local hosts = hosts; +local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false; + local prosody = _G.prosody; module "usermanager" @@ -71,12 +73,13 @@ function new_default_provider(host) end function provider.user_exists(username) - if is_cyrus(host) then return true; end - return datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials + if not(require_provisioning) and is_cyrus(host) then return true; end + local account, err = datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials + return (account or err) ~= nil; -- FIXME also check for empty credentials end function provider.create_user(username, password) - if is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end + if not(require_provisioning) and is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end return datamanager.store(username, host, "accounts", {password = password}); end diff --git a/net/dns.lua b/net/dns.lua index 020a0ae8..c0de97fd 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -853,9 +853,8 @@ function resolver:lookup(qname, qtype, qclass) -- - - - - - - - - - lookup self:query (qname, qtype, qclass) while self:pulse() do local recvt = {} - local i, s for i, s in ipairs(self.socket) do - recvt[i] = s.socket() + recvt[i] = s end socket.select(recvt, nil, 4) end diff --git a/net/multiplex_listener.lua b/net/multiplex_listener.lua index bf193ad8..b515ccce 100644 --- a/net/multiplex_listener.lua +++ b/net/multiplex_listener.lua @@ -19,6 +19,8 @@ function server.onincoming(conn, data) if buf:match("^[a-zA-Z]") then local listener = httpserver_listener; conn:setlistener(listener); + local onconnect = listener.onconnect; + if onconnect then onconnect(conn) end listener.onincoming(conn, buf); elseif buf:match(">") then local listener; @@ -31,6 +33,8 @@ function server.onincoming(conn, data) listener = xmppclient_listener; end conn:setlistener(listener); + local onconnect = listener.onconnect; + if onconnect then onconnect(conn) end listener.onincoming(conn, buf); elseif #buf > 1024 then conn:close(); diff --git a/net/server_event.lua b/net/server_event.lua index 43e70a0f..b286e8c2 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -750,7 +750,7 @@ do function wrapclient( client, ip, port, listeners, pattern, sslctx, startssl ) local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx ) interface:_start_session() - return interface + return interface, client --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface end diff --git a/net/xmppclient_listener.lua b/net/xmppclient_listener.lua index f3a372ae..623a98c8 100644 --- a/net/xmppclient_listener.lua +++ b/net/xmppclient_listener.lua @@ -111,45 +111,56 @@ end -- End of session methods -- -function xmppclient.onincoming(conn, data) - local session = sessions[conn]; - if not session then - session = sm_new_session(conn); - sessions[conn] = session; - - session.log("info", "Client connected"); - - -- Client is using legacy SSL (otherwise mod_tls sets this flag) - if conn:ssl() then - session.secure = true; - end - - if opt_keepalives ~= nil then - conn:setoption("keepalive", opt_keepalives); - end - - session.close = session_close; - - local stream = new_xmpp_stream(session, stream_callbacks); - session.stream = stream; - +function xmppclient.onconnect(conn) + local session = sm_new_session(conn); + sessions[conn] = session; + + session.log("info", "Client connected"); + + -- Client is using legacy SSL (otherwise mod_tls sets this flag) + if conn:ssl() then + session.secure = true; + end + + if opt_keepalives ~= nil then + conn:setoption("keepalive", opt_keepalives); + end + + session.close = session_close; + + local stream = new_xmpp_stream(session, stream_callbacks); + session.stream = stream; + + session.notopen = true; + + function session.reset_stream() session.notopen = true; - - function session.reset_stream() - session.notopen = true; - session.stream:reset(); - end - - function session.data(data) + session.stream:reset(); + end + + local filter = session.filter; + function session.data(data) + data = filter("bytes/in", data); + if data then local ok, err = stream:feed(data); if ok then return; end log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); session:close("xml-not-well-formed"); end - - session.dispatch_stanza = stream_callbacks.handlestanza; end - if data then + + local handlestanza = stream_callbacks.handlestanza; + function session.dispatch_stanza(session, stanza) + stanza = filter("stanzas/in", stanza); + if stanza then + return handlestanza(session, stanza); + end + end +end + +function xmppclient.onincoming(conn, data) + local session = sessions[conn]; + if session then session.data(data); end end diff --git a/net/xmppserver_listener.lua b/net/xmppserver_listener.lua index d1272edb..8700e6f2 100644 --- a/net/xmppserver_listener.lua +++ b/net/xmppserver_listener.lua @@ -11,7 +11,7 @@ local logger = require "logger"; local log = logger.init("xmppserver_listener"); local lxp = require "lxp" -local init_xmlhandlers = require "core.xmlhandlers" +local new_xmpp_stream = require "util.xmppstream".new; local s2s_new_incoming = require "core.s2smanager".new_incoming; local s2s_streamopened = require "core.s2smanager".streamopened; local s2s_streamclosed = require "core.s2smanager".streamclosed; @@ -72,24 +72,6 @@ local xmppserver = { default_port = 5269, default_mode = "*a" }; -- These are session methods -- -local function session_reset_stream(session) - -- Reset stream - local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1"); - session.parser = parser; - - session.notopen = true; - - function session.data(conn, data) - local ok, err = parser:parse(data); - if ok then return; end - (session.log or log)("warn", "Received invalid XML: %s", data); - (session.log or log)("warn", "Problem was: %s", err); - session:close("xml-not-well-formed"); - end - - return true; -end - local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; local function session_close(session, reason, remote_reason) @@ -132,29 +114,58 @@ end -- End of session methods -- -function xmppserver.onincoming(conn, data) - local session = sessions[conn]; - if not session then - session = s2s_new_incoming(conn); - sessions[conn] = session; +local function initialize_session(session) + local stream = new_xmpp_stream(session, stream_callbacks); + session.stream = stream; + + session.notopen = true; + + function session.reset_stream() + session.notopen = true; + session.stream:reset(); + end + + local filter = session.filter; + function session.data(data) + data = filter("bytes/in", data); + if data then + local ok, err = stream:feed(data); + if ok then return; end + (session.log or log)("warn", "Received invalid XML: %s", data); + (session.log or log)("warn", "Problem was: %s", err); + session:close("xml-not-well-formed"); + end + end - -- Logging functions -- + session.close = session_close; + local handlestanza = stream_callbacks.handlestanza; + function session.dispatch_stanza(session, stanza) + stanza = filters("stanzas/in", stanza); + if stanza then + return handlestanza(session, stanza); + end + end +end - +function xmppserver.onconnect(conn) + if not sessions[conn] then -- May be an existing outgoing session + local session = s2s_new_incoming(conn); + sessions[conn] = session; + + -- Logging functions -- local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$"); session.log = logger.init(conn_name); session.log("info", "Incoming s2s connection"); - session.reset_stream = session_reset_stream; - session.close = session_close; - - session_reset_stream(session); -- Initialise, ready for use - - session.dispatch_stanza = stream_callbacks.handlestanza; + initialize_session(session); end - if data then - session.data(conn, data); +end + +function xmppserver.onincoming(conn, data) + local session = sessions[conn]; + if session then + session.data(data); end end @@ -190,12 +201,7 @@ function xmppserver.register_outgoing(conn, session) session.direction = "outgoing"; sessions[conn] = session; - session.reset_stream = session_reset_stream; - session.close = session_close; - session_reset_stream(session); -- Initialise, ready for use - - --local function handleerr(err) print("Traceback:", err, debug.traceback()); end - --session.stanza_dispatch = function (stanza) return select(2, xpcall(function () return core_process_stanza(session, stanza); end, handleerr)); end + initialize_session(session); end connlisteners_register("xmppserver", xmppserver); diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index b11de6c6..66a79785 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -31,6 +31,8 @@ local BOSH_DEFAULT_POLLING = tonumber(module:get_option("bosh_max_polling")) or local BOSH_DEFAULT_REQUESTS = tonumber(module:get_option("bosh_max_requests")) or 2; local BOSH_DEFAULT_MAXPAUSE = tonumber(module:get_option("bosh_max_pause")) or 300; +local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); + local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" }; local session_close_reply = { headers = default_headers, body = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" }), attr = {} }; @@ -174,10 +176,14 @@ function stream_callbacks.streamopened(request, attr) -- New session sid = new_uuid(); - local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to, bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, - bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY, - requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream, - dispatch_stanza = core_process_stanza, log = logger.init("bosh"..sid), secure = request.secure }; + local session = { + type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to, + bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, + bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY, + requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, + close = bosh_close_stream, dispatch_stanza = core_process_stanza, + log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure + }; sessions[sid] = session; log("info", "New BOSH session, assigned it sid '%s'", sid); diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua index c2e84f2b..0e1aab8c 100644 --- a/plugins/mod_compression.lua +++ b/plugins/mod_compression.lua @@ -14,6 +14,7 @@ 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 add_filter = require "util.filters".add_filter; local compression_level = module:get_option("compression_level"); -- if not defined assume admin wants best compression @@ -94,44 +95,37 @@ 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", "%s", tostring(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 + add_filter(session, "bytes/out", function(t) + 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", "%s", tostring(compressed)); + return; + end + return compressed; + 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", "%s", tostring(decompressed)); - return; - end - old_data(conn, decompressed); - end; + add_filter(session, "bytes/in", function(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", "%s", tostring(decompressed)); + return; + end + return decompressed; + end); end module:add_handler({"s2sout_unauthed", "s2sout"}, "compressed", xmlns_compression_protocol, diff --git a/plugins/mod_groups.lua b/plugins/mod_groups.lua index d4604b1e..5f821cbc 100644 --- a/plugins/mod_groups.lua +++ b/plugins/mod_groups.lua @@ -18,7 +18,7 @@ local jid_bare, jid_prep = jid.bare, jid.prep; local module_host = module:get_host(); function inject_roster_contacts(username, host, roster) - module:log("warn", "Injecting group members to roster"); + --module:log("debug", "Injecting group members to roster"); local bare_jid = username.."@"..host; if not members[bare_jid] and not members[false] then return; end -- Not a member of any groups @@ -41,7 +41,7 @@ function inject_roster_contacts(username, host, roster) -- Find groups this JID is a member of if members[bare_jid] then for _, group_name in ipairs(members[bare_jid]) do - module:log("debug", "Importing group %s", group_name); + --module:log("debug", "Importing group %s", group_name); import_jids_to_roster(group_name); end end @@ -49,7 +49,7 @@ function inject_roster_contacts(username, host, roster) -- Import public groups if members[false] then for _, group_name in ipairs(members[false]) do - module:log("debug", "Importing group %s", group_name); + --module:log("debug", "Importing group %s", group_name); import_jids_to_roster(group_name); end end @@ -67,7 +67,9 @@ function remove_virtual_contacts(username, host, datastore, data) new_roster[jid] = contact; end end - new_roster[false].version = nil; -- Version is void + if new_roster[false] then + new_roster[false].version = nil; -- Version is void + end return username, host, datastore, new_roster; end diff --git a/plugins/mod_iq.lua b/plugins/mod_iq.lua index b3001fe5..e90af781 100644 --- a/plugins/mod_iq.lua +++ b/plugins/mod_iq.lua @@ -9,7 +9,6 @@ local st = require "util.stanza"; local jid_split = require "util.jid".split; -local user_exists = require "core.usermanager".user_exists; local full_sessions = full_sessions; local bare_sessions = bare_sessions; @@ -34,16 +33,6 @@ module:hook("iq/bare", function(data) -- IQ to bare JID recieved local origin, stanza = data.origin, data.stanza; - local to = stanza.attr.to; - if to and not bare_sessions[to] then -- quick check for account existance - local node, host = jid_split(to); - if not user_exists(node, host) then -- full check for account existance - if stanza.attr.type == "get" or stanza.attr.type == "set" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - end - return true; - end - end -- TODO fire post processing events if stanza.attr.type == "get" or stanza.attr.type == "set" then return module:fire_event("iq/bare/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name, data); diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua index aa46d2d3..31546dff 100644 --- a/plugins/mod_pep.lua +++ b/plugins/mod_pep.lua @@ -16,7 +16,6 @@ local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed local pairs, ipairs = pairs, ipairs; local next = next; local type = type; -local load_roster = require "core.rostermanager".load_roster; local sha1 = require "util.hashes".sha1; local base64 = require "util.encodings".base64.encode; @@ -40,8 +39,8 @@ module:add_feature("http://jabber.org/protocol/pubsub#publish"); local function subscription_presence(user_bare, recipient) local recipient_bare = jid_bare(recipient); if (recipient_bare == user_bare) then return true end - local item = load_roster(jid_split(user_bare))[recipient_bare]; - return item and (item.subscription == 'from' or item.subscription == 'both'); + local username, host = jid_split(user_bare); + return is_contact_subscribed(username, host, recipient_bare); end local function publish(session, node, id, item) @@ -118,27 +117,32 @@ module:hook("presence/bare", function(event) -- inbound presence to bare JID recieved local origin, stanza = event.origin, event.stanza; local user = stanza.attr.to or (origin.username..'@'..origin.host); + local t = stanza.attr.type; - if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then - local recipient = stanza.attr.from; - local current = recipients[user] and recipients[user][recipient]; - local hash = get_caps_hash_from_presence(stanza, current); - if current == hash then return; end - if not hash then - if recipients[user] then recipients[user][recipient] = nil; end - else - recipients[user] = recipients[user] or {}; - if hash_map[hash] then - recipients[user][recipient] = hash_map[hash]; - publish_all(user, recipient, origin); + if not t then -- available presence + if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then + local recipient = stanza.attr.from; + local current = recipients[user] and recipients[user][recipient]; + local hash = get_caps_hash_from_presence(stanza, current); + if current == hash then return; end + if not hash then + if recipients[user] then recipients[user][recipient] = nil; end else - recipients[user][recipient] = hash; - origin.send( - st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"}) - :query("http://jabber.org/protocol/disco#info") - ); + recipients[user] = recipients[user] or {}; + if hash_map[hash] then + recipients[user][recipient] = hash_map[hash]; + publish_all(user, recipient, origin); + else + recipients[user][recipient] = hash; + origin.send( + st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"}) + :query("http://jabber.org/protocol/disco#info") + ); + end end end + elseif t == "unavailable" then + if recipients[user] then recipients[user][stanza.attr.from] = nil; end end end, 10); diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index 5ad3bfdf..9071ae4c 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -38,42 +38,23 @@ function core_route_stanza(origin, stanza) _core_route_stanza(origin, stanza); end -local select_top_resources; -local bare_message_delivery_policy = module:get_option("bare_message_delivery_policy") or "priority"; -if bare_message_delivery_policy == "broadcast" then - function select_top_resources(user) - local recipients = {}; - for _, session in pairs(user.sessions) do -- find resources with non-negative priority +local function select_top_resources(user) + local priority = 0; + local recipients = {}; + for _, session in pairs(user.sessions) do -- find resource with greatest priority + if session.presence then + -- TODO check active privacy list for session local p = session.priority; - if p and p >= 0 then + if p > priority then + priority = p; + recipients = {session}; + elseif p == priority then t_insert(recipients, session); end end - return recipients; - end -else - if bare_message_delivery_policy ~= "priority" then - module:log("warn", "Invalid value for config option bare_message_delivery_policy"); - end - function select_top_resources(user) - local priority = 0; - local recipients = {}; - for _, session in pairs(user.sessions) do -- find resource with greatest priority - if session.presence then - -- TODO check active privacy list for session - local p = session.priority; - if p > priority then - priority = p; - recipients = {session}; - elseif p == priority then - t_insert(recipients, session); - end - end - end - return recipients; end + return recipients; end - local function recalc_resource_map(user) if user then user.top_resources = select_top_resources(user); @@ -81,7 +62,17 @@ local function recalc_resource_map(user) end end +local ignore_presence_priority = module:get_option("ignore_presence_priority"); + function handle_normal_presence(origin, stanza, core_route_stanza) + if ignore_presence_priority then + local priority = stanza:child_with_name("priority"); + if priority and priority[1] ~= "0" then + for i=#priority.tags,1,-1 do priority.tags[i] = nil; end + for i=#priority,1,-1 do priority[i] = nil; end + priority[1] = "0"; + end + end if full_sessions[origin.full_jid] then -- if user is still connected origin.send(stanza); -- reflect their presence back to them end @@ -236,16 +227,13 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b stanza.attr.from, stanza.attr.to = from_bare, to_bare; log("debug", "inbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare); - if not node then - log("debug", "dropping presence sent to host or invalid address '%s'", tostring(to_bare)); - end - if stanza.attr.type == "probe" then - if rostermanager.is_contact_subscribed(node, host, from_bare) then + local result, err = rostermanager.is_contact_subscribed(node, host, from_bare); + if result then if 0 == send_presence_of_available_resources(node, host, st_from, origin, core_route_stanza) then core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity end - else + elseif not err then core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"})); end elseif stanza.attr.type == "subscribe" then diff --git a/plugins/mod_privacy.lua b/plugins/mod_privacy.lua index 77b4dd12..ca5d51fa 100644 --- a/plugins/mod_privacy.lua +++ b/plugins/mod_privacy.lua @@ -13,7 +13,7 @@ local datamanager = require "util.datamanager"; local bare_sessions, full_sessions = bare_sessions, full_sessions; local util_Jid = require "util.jid"; local jid_bare = util_Jid.bare; -local jid_split = util_Jid.split; +local jid_split, jid_join = util_Jid.split, util_Jid.join; local load_roster = require "core.rostermanager".load_roster; local to_number = tonumber; @@ -160,26 +160,7 @@ function createOrReplaceList (privacy_lists, origin, stanza, name, entries, rost end end - if tmp.type == "group" then - local found = false; - local roster = load_roster(origin.username, origin.host); - for jid,item in pairs(roster) do - if item.groups ~= nil then - for group in pairs(item.groups) do - if group == tmp.value then - found = true; - break; - end - end - if found == true then - break; - end - end - end - if found == false then - return {"cancel", "item-not-found", "Specifed roster group not existing."}; - end - elseif tmp.type == "subscription" then + if tmp.type == "subscription" then if tmp.value ~= "both" and tmp.value ~= "to" and tmp.value ~= "from" and @@ -326,7 +307,7 @@ function checkIfNeedToBeBlocked(e, session) local is_to_user = bare_jid == jid_bare(to); local is_from_user = bare_jid == jid_bare(from); - module:log("debug", "stanza: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from)); + --module:log("debug", "stanza: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from)); if privacy_lists.lists == nil or not (session.activePrivacyList or privacy_lists.default) @@ -334,7 +315,7 @@ function checkIfNeedToBeBlocked(e, session) return; -- Nothing to block, default is Allow all end if is_from_user and is_to_user then - module:log("debug", "Not blocking communications between user's resources"); + --module:log("debug", "Not blocking communications between user's resources"); return; -- from one of a user's resource to another => HANDS OFF! end @@ -344,8 +325,8 @@ function checkIfNeedToBeBlocked(e, session) listname = privacy_lists.default; -- no active list selected, use default list end local list = privacy_lists.lists[listname]; - if not list then - module:log("debug", "given privacy list not found. name: %s", listname); + if not list then -- should never happen + module:log("warn", "given privacy list not found. name: %s for user %s", listname, bare_jid); return; end for _,item in ipairs(list.items) do @@ -364,10 +345,10 @@ function checkIfNeedToBeBlocked(e, session) local evilJid = {}; apply = false; if is_to_user then - module:log("debug", "evil jid is (from): %s", from); + --module:log("debug", "evil jid is (from): %s", from); evilJid.node, evilJid.host, evilJid.resource = jid_split(from); else - module:log("debug", "evil jid is (to): %s", to); + --module:log("debug", "evil jid is (to): %s", to); evilJid.node, evilJid.host, evilJid.resource = jid_split(to); end if item.type == "jid" and @@ -379,17 +360,22 @@ function checkIfNeedToBeBlocked(e, session) block = (item.action == "deny"); elseif item.type == "group" then local roster = load_roster(session.username, session.host); - local groups = roster[evilJid.node .. "@" .. evilJid.host].groups; - for group in pairs(groups) do - if group == item.value then - apply = true; - block = (item.action == "deny"); - break; + local roster_entry = roster[jid_join(evilJid.node, evilJid.host)]; + if roster_entry then + local groups = roster_entry.groups; + for group in pairs(groups) do + if group == item.value then + apply = true; + block = (item.action == "deny"); + break; + end end end - elseif item.type == "subscription" and evilJid.node ~= nil and evilJid.host ~= nil then -- we need a valid bare evil jid + elseif item.type == "subscription" then -- we need a valid bare evil jid local roster = load_roster(session.username, session.host); - if roster[evilJid.node .. "@" .. evilJid.host].subscription == item.value then + local roster_entry = roster[jid_join(evilJid.node, evilJid.host)]; + if (not(roster_entry) and item.value == "none") + or (roster_entry and roster_entry.subscription == item.value) then apply = true; block = (item.action == "deny"); end @@ -408,7 +394,7 @@ function checkIfNeedToBeBlocked(e, session) end return true; -- stanza blocked ! else - module:log("debug", "stanza explicitly allowed!") + --module:log("debug", "stanza explicitly allowed!") return; end end @@ -439,7 +425,7 @@ function preCheckIncoming(e) if session ~= nil then return checkIfNeedToBeBlocked(e, session); else - module:log("debug", "preCheckIncoming: Couldn't get session for jid: %s@%s/%s", tostring(node), tostring(host), tostring(resource)); + --module:log("debug", "preCheckIncoming: Couldn't get session for jid: %s@%s/%s", tostring(node), tostring(host), tostring(resource)); end end end diff --git a/plugins/mod_private.lua b/plugins/mod_private.lua index 859bf45a..abf1ec03 100644 --- a/plugins/mod_private.lua +++ b/plugins/mod_private.lua @@ -26,7 +26,11 @@ module:add_iq_handler("c2s", "jabber:iq:private", if #query.tags == 1 then local tag = query.tags[1]; local key = tag.name..":"..tag.attr.xmlns; - local data = datamanager.load(node, host, "private"); + local data, err = datamanager.load(node, host, "private"); + if err then + session.send(st.error_reply(stanza, "wait", "internal-server-error")); + return true; + end if stanza.attr.type == "get" then if data and data[key] then session.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"}):add_child(st.deserialize(data[key]))); diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index c0360553..9f940c37 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -28,6 +28,12 @@ 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 log = module._log; local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl'; @@ -45,7 +51,11 @@ elseif sasl_backend == "cyrus" then if ok then local cyrus_new = cyrus.new; new_sasl = function(realm) - return cyrus_new(realm, module:get_option("cyrus_service_name") or "xmpp"); + return cyrus_new( + cyrus_service_realm or realm, + cyrus_service_name or "xmpp", + cyrus_application_name or "prosody" + ); end else module:log("error", "Failed to load Cyrus SASL because: %s", cyrus); @@ -94,7 +104,7 @@ local function build_reply(status, ret, err_msg) return reply; end -local function handle_status(session, status) +local function handle_status(session, status, ret, err_msg) if status == "failure" then session.sasl_handler = session.sasl_handler:clean_clone(); elseif status == "success" then @@ -103,12 +113,20 @@ local function handle_status(session, status) module:log("warn", "SASL succeeded but we didn't get a username!"); session.sasl_handler = nil; session:reset_stream(); - return; + return status, ret, err_msg; + end + + if not(require_provisioning) or usermanager_user_exists(username, session.host) then + sm_make_authenticated(session, session.sasl_handler.username); + session.sasl_handler = nil; + session:reset_stream(); + else + module:log("warn", "SASL succeeded but we don't have an account provisioned for %s", username); + session.sasl_handler = session.sasl_handler:clean_clone(); + return "failure", "not-authorized", "User authenticated successfully, but not provisioned for XMPP"; end - sm_make_authenticated(session, session.sasl_handler.username); - session.sasl_handler = nil; - session:reset_stream(); end + return status, ret, err_msg; end local function sasl_handler(session, stanza) @@ -142,7 +160,7 @@ local function sasl_handler(session, stanza) end end local status, ret, err_msg = session.sasl_handler:process(text); - handle_status(session, status); + status, ret, err_msg = handle_status(session, status, ret, err_msg); local s = build_reply(status, ret, err_msg); log("debug", "sasl reply: %s", tostring(s)); session.send(s); diff --git a/util/datamanager.lua b/util/datamanager.lua index 01c7aab2..57cd2594 100644 --- a/util/datamanager.lua +++ b/util/datamanager.lua @@ -21,12 +21,13 @@ local next = next; local t_insert = table.insert; local append = require "util.serialization".append; local path_separator = "/"; if os.getenv("WINDIR") then path_separator = "\\" end +local lfs = require "lfs"; local raw_mkdir; if prosody.platform == "posix" then raw_mkdir = require "util.pposix".mkdir; -- Doesn't trample on umask else - raw_mkdir = require "lfs".mkdir; + raw_mkdir = lfs.mkdir; end module "datamanager" @@ -111,14 +112,21 @@ end function load(username, host, datastore) local data, ret = loadfile(getpath(username, host, datastore)); if not data then - log("debug", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil")); - return nil; + local mode = lfs.attributes(getpath(username, host, datastore), "mode"); + if not mode then + log("debug", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil")); + return nil; + else -- file exists, but can't be read + -- TODO more detailed error checking and logging? + log("error", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil")); + return nil, "Error reading storage"; + end end setfenv(data, {}); local success, ret = pcall(data); if not success then log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil")); - return nil; + return nil, "Error reading storage"; end return ret; end @@ -137,7 +145,7 @@ function store(username, host, datastore, data) local f, msg = io_open(getpath(username, host, datastore, nil, true), "w+"); if not f then log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil")); - return; + return nil, "Error saving to storage"; end f:write("return "); append(f, data); diff --git a/util/filters.lua b/util/filters.lua new file mode 100644 index 00000000..0ac4cb56 --- /dev/null +++ b/util/filters.lua @@ -0,0 +1,68 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local t_insert, t_remove = table.insert, table.remove; + +module "filters" + +function initialize(session) + if not session.filters then + local filters = {}; + session.filters = filters; + + function session.filter(type, data) + local filter_list = filters[type]; + if filter_list then + for i = 1, #filter_list do + data = filter_list[i](data); + if data == nil then break; end + end + end + return data; + end + end + return session.filter; +end + +function add_filter(session, type, callback, priority) + if not session.filters then + initialize(session); + end + + local filter_list = session.filters[type]; + if not filter_list then + filter_list = {}; + session.filters[type] = filter_list; + end + + priority = priority or 0; + + local i = 0; + repeat + i = i + 1; + until not filter_list[i] or filter_list[filter_list[i]] >= priority; + + t_insert(filter_list, i, callback); + filter_list[callback] = priority; +end + +function remove_filter(session, type, callback) + if not session.filters then return; end + local filter_list = session.filters[type]; + if filter_list and filter_list[callback] then + for i=1, #filter_list do + if filter_list[i] == callback then + t_remove(filter_list, i); + filter_list[callback] = nil; + return true; + end + end + end +end + +return _M;
\ No newline at end of file diff --git a/util/sasl/digest-md5.lua b/util/sasl/digest-md5.lua index 8986ca45..2837148e 100644 --- a/util/sasl/digest-md5.lua +++ b/util/sasl/digest-md5.lua @@ -32,13 +32,13 @@ module "digest-md5" --[[ Supported Authentication Backends -digest-md5: +digest_md5: function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken -- implementations it's not return digesthash, state; end -digest-md5-test: +digest_md5_test: function(username, domain, realm, encoding, digesthash) return true or false, state; end diff --git a/util/sasl/plain.lua b/util/sasl/plain.lua index 2abbc53a..39821182 100644 --- a/util/sasl/plain.lua +++ b/util/sasl/plain.lua @@ -28,15 +28,10 @@ plain: return password, state; end -plain-test: +plain_test: function(username, realm, password) return true or false, state; end - -plain-hashed: - function(username, realm) - return hashed_password, hash_function, state; - end ]] local function plain(self, message) @@ -66,10 +61,6 @@ local function plain(self, message) if correct_password == password then correct = true; else correct = false; end elseif self.profile.plain_test then correct, state = self.profile.plain_test(authentication, self.realm, password); - elseif self.profile.plain_hashed then - local hashed_password, hash_f; - hashed_password, hash_f, state = self.profile.plain_hashed(authentication, self.realm); - if hashed_password == hash_f(password) then correct = true; else correct = false; end end self.username = authentication @@ -85,7 +76,7 @@ local function plain(self, message) end function init(registerMechanism) - registerMechanism("PLAIN", {"plain", "plain_test", "plain_hashed"}, plain); + registerMechanism("PLAIN", {"plain", "plain_test"}, plain); end return _M; diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index ed7d7bc3..52656a89 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -1,15 +1,15 @@ -- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- --- All rights reserved. +-- All rights reserved. -- --- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- --- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. --- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. --- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local s_match = string.match; local type = type @@ -32,7 +32,8 @@ module "scram" --[[ Supported Authentication Backends -scram-{MECH}: +scram_{MECH}: + -- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_' function(username, realm) return salted_password, iteration_count, salt, state; end @@ -92,20 +93,46 @@ local function validate_username(username) return username; end +local function hashprep( hashname ) + local hash = hashname:lower() + hash = hash:gsub("-", "_") + return hash +end + +function saltedPasswordSHA1(password, salt, iteration_count) + local salted_password + if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then + return false, "inappropriate argument types" + end + if iteration_count < 4096 then + log("warning", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.") + end + + return true, Hi(hmac_sha1, password, salt, iteration_count); +end + local function scram_gen(hash_name, H_f, HMAC_f) local function scram_hash(self, message) if not self.state then self["state"] = {} end + if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end if not self.state.name then -- we are processing client_first_message local client_first_message = message; + + -- TODO: fail if authzid is provided, since we don't support them yet self.state["client_first_message"] = client_first_message; - self.state["name"] = client_first_message:match("n=(.+),r=") - self.state["clientnonce"] = client_first_message:match("r=([^,]+)") - - if not self.state.name or not self.state.clientnonce then + self.state["gs2_cbind_flag"], self.state["authzid"], self.state["name"], self.state["clientnonce"] + = client_first_message:match("^(%a),(.*),n=(.*),r=([^,]*).*"); + + -- we don't do any channel binding yet + if self.state.gs2_cbind_flag ~= "n" and self.state.gs2_cbind_flag ~= "y" then return "failure", "malformed-request"; end + + if not self.state.name or not self.state.clientnonce then + return "failure", "malformed-request", "Channel binding isn't support at this time."; + end self.state.name = validate_username(self.state.name); if not self.state.name then @@ -126,11 +153,18 @@ local function scram_gen(hash_name, H_f, HMAC_f) log("debug", "Password violates SASLprep."); return "failure", "not-authorized", "Invalid password." end + self.state.salt = generate_uuid(); self.state.iteration_count = default_i; - self.state.salted_password = Hi(HMAC_f, password, self.state.salt, default_i); - elseif self.profile["scram_"..hash_name] then - local salted_password, iteration_count, salt, state = self.profile["scram-"..hash_name](self.state.name, self.realm); + + local succ = false; + succ, self.state.salted_password = saltedPasswordSHA1(password, self.state.salt, default_i, self.state.iteration_count); + if not succ then + log("error", "Generating salted password failed. Reason: %s", self.state.salted_password); + return "failure", "temporary-auth-failure"; + end + elseif self.profile["scram_"..hashprep(hash_name)] then + local salted_password, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self.state.name, self.realm); if state == nil then return "failure", "not-authorized" elseif state == false then return "failure", "account-disabled" end @@ -143,17 +177,19 @@ local function scram_gen(hash_name, H_f, HMAC_f) self.state["server_first_message"] = server_first_message; return "challenge", server_first_message else - if type(message) ~= "string" then return "failure", "malformed-request" end -- we are processing client_final_message local client_final_message = message; - - self.state["proof"] = client_final_message:match("p=(.+)"); - self.state["nonce"] = client_final_message:match("r=(.+),p="); - self.state["channelbinding"] = client_final_message:match("c=(.+),r="); + + self.state["channelbinding"], self.state["nonce"], self.state["proof"] = client_final_message:match("^c=(.*),r=(.*),.*p=(.*)"); + if not self.state.proof or not self.state.nonce or not self.state.channelbinding then return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; end - + + if self.state.nonce ~= self.state.clientnonce..self.state.servernonce then + return "failure", "malformed-request", "Wrong nonce in client-final-message."; + end + local SaltedPassword = self.state.salted_password; local ClientKey = HMAC_f(SaltedPassword, "Client Key") local ServerKey = HMAC_f(SaltedPassword, "Server Key") @@ -162,7 +198,7 @@ local function scram_gen(hash_name, H_f, HMAC_f) local ClientSignature = HMAC_f(StoredKey, AuthMessage) local ClientProof = binaryXOR(ClientKey, ClientSignature) local ServerSignature = HMAC_f(ServerKey, AuthMessage) - + if base64.encode(ClientProof) == self.state.proof then local server_final_message = "v="..base64.encode(ServerSignature); self["username"] = self.state.name; @@ -177,10 +213,10 @@ end function init(registerMechanism) local function registerSCRAMMechanism(hash_name, hash, hmac_hash) - registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hash_name:lower())}, scram_gen(hash_name:lower(), hash, hmac_hash)); + registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash)); end - + registerSCRAMMechanism("SHA-1", sha1, hmac_sha1); end -return _M;
\ No newline at end of file +return _M; diff --git a/util/sasl_cyrus.lua b/util/sasl_cyrus.lua index b5b0e08d..b5f505eb 100644 --- a/util/sasl_cyrus.lua +++ b/util/sasl_cyrus.lua @@ -45,10 +45,10 @@ local function init(service_name) end -- create a new SASL object which can be used to authenticate clients -function new(realm, service_name) +function new(realm, service_name, app_name) local sasl_i = {}; - init(service_name); + init(app_name or service_name); sasl_i.realm = realm; sasl_i.service_name = service_name; |