# HG changeset patch # User Matthew Wild # Date 1277839391 -3600 # Node ID fec74b84df61288850188e02fa2c9546c39c9ecc # Parent b651f8290e2727da35e21ce54df24c0ee88c3c0a# Parent 79ae027a3c09d23d31e758713e61edde124b0e51 Merge backout diff -r 79ae027a3c09 -r fec74b84df61 .hgtags --- a/.hgtags Tue Jun 29 20:23:00 2010 +0100 +++ b/.hgtags Tue Jun 29 20:23:11 2010 +0100 @@ -37,3 +37,6 @@ 1a99a3bf3ce6dbdfb362b7fd101d761fb3cc10af 0.6.0 81b4e738e4d321b78274132f63a9aec7007e64eb 0.6.1 0395f2f34bd55a01ec7276884fb9a4e0051b0e7a 0.6.2 +ea80531e5cbc567c3e211d38749c45e8b66d33b7 0.7.0 +ea80531e5cbc567c3e211d38749c45e8b66d33b7 0.7.0 +5ea90ee96022b9c106e7e79b4a1d8b2ee99d45dc 0.7.0 diff -r 79ae027a3c09 -r fec74b84df61 configure --- a/configure Tue Jun 29 20:23:00 2010 +0100 +++ b/configure Tue Jun 29 20:23:11 2010 +0100 @@ -61,7 +61,7 @@ while [ "$1" ] do - value="`echo $1 | sed 's/.*=\(.*\)/\1/'`" + value="`echo $1 | sed 's/[^=]*=\(.*\)/\1/'`" if echo "$value" | grep -q "~" then echo diff -r 79ae027a3c09 -r fec74b84df61 core/loggingmanager.lua diff -r 79ae027a3c09 -r fec74b84df61 core/rostermanager.lua --- a/core/rostermanager.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/core/rostermanager.lua Tue Jun 29 20:23:11 2010 +0100 @@ -93,15 +93,18 @@ 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 @@ 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"); @@ -187,9 +191,9 @@ end function is_contact_subscribed(username, host, jid) - local roster = load_roster(username, host); + 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 -r 79ae027a3c09 -r fec74b84df61 core/s2smanager.lua --- a/core/s2smanager.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/core/s2smanager.lua Tue Jun 29 20:23:11 2010 +0100 @@ -241,7 +241,6 @@ 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; diff -r 79ae027a3c09 -r fec74b84df61 core/sessionmanager.lua --- a/core/sessionmanager.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/core/sessionmanager.lua Tue Jun 29 20:23:11 2010 +0100 @@ -25,6 +25,7 @@ local config_get = require "core.configmanager".get; local nameprep = require "util.encodings".stringprep.nameprep; local resourceprep = require "util.encodings".stringprep.resourceprep; +local nodeprep = require "util.encodings".stringprep.nodeprep; local fire_event = require "core.eventmanager".fire_event; local add_task = require "util.timer".add_task; @@ -109,6 +110,8 @@ end function make_authenticated(session, username) + username = nodeprep(username); + if not username or #username == 0 then return nil, "Invalid username"; end session.username = username; if session.type == "c2s_unauthed" then session.type = "c2s"; @@ -136,7 +139,7 @@ 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 +177,19 @@ 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 -r 79ae027a3c09 -r fec74b84df61 core/usermanager.lua --- a/core/usermanager.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/core/usermanager.lua Tue Jun 29 20:23:11 2010 +0100 @@ -16,6 +16,8 @@ local config = require "core.configmanager"; local hosts = hosts; +local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false; + module "usermanager" local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end @@ -66,12 +68,13 @@ end function user_exists(username, host) - 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"); + return (account or err) ~= nil; -- FIXME also check for empty credentials end function create_user(username, password, host) - 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 -r 79ae027a3c09 -r fec74b84df61 net/dns.lua --- a/net/dns.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/net/dns.lua Tue Jun 29 20:23:11 2010 +0100 @@ -851,7 +851,13 @@ function resolver:lookup(qname, qtype, qclass) -- - - - - - - - - - lookup self:query (qname, qtype, qclass) - while self:pulse() do socket.select(self.socket, nil, 4); end + while self:pulse() do + local recvt = {} + for i, s in ipairs(self.socket) do + recvt[i] = s + end + socket.select(recvt, nil, 4) + end --print(self.cache); return self:peek(qname, qtype, qclass); end diff -r 79ae027a3c09 -r fec74b84df61 net/httpserver_listener.lua --- a/net/httpserver_listener.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/net/httpserver_listener.lua Tue Jun 29 20:23:11 2010 +0100 @@ -29,7 +29,7 @@ end end - if data then + if data and data ~= "" then request_reader(request, data); end end diff -r 79ae027a3c09 -r fec74b84df61 net/server_event.lua --- a/net/server_event.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/net/server_event.lua Tue Jun 29 20:23:11 2010 +0100 @@ -20,8 +20,8 @@ local cfg = { MAX_CONNECTIONS = 100000, -- max per server connections (use "ulimit -n" on *nix) - MAX_HANDSHAKE_ATTEMPS = 1000, -- attempts to finish ssl handshake - HANDSHAKE_TIMEOUT = 60, -- timout in seconds per handshake attempt + MAX_HANDSHAKE_ATTEMPTS= 1000, -- attempts to finish ssl handshake + HANDSHAKE_TIMEOUT = 60, -- timeout in seconds per handshake attempt MAX_READ_LENGTH = 1024 * 1024 * 1024 * 1024, -- max bytes allowed to read from sockets MAX_SEND_LENGTH = 1024 * 1024 * 1024 * 1024, -- max bytes size of write buffer (for writing on sockets) ACCEPT_DELAY = 10, -- seconds to wait until the next attempt of a full server to accept @@ -136,7 +136,7 @@ function interface_mt:_start_connection(plainssl) -- should be called from addclient local callback = function( event ) - if EV_TIMEOUT == event then -- timout during connection + if EV_TIMEOUT == event then -- timeout during connection self.fatalerror = "connection timeout" self:ontimeout() -- call timeout listener self:_close() @@ -196,12 +196,12 @@ function( event ) local _, err local attempt = 0 - local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPS + local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPTS while attempt < maxattempt do -- no endless loop attempt = attempt + 1 - debug( "ssl handshake of client with id:"..tostring(self).."attemp:"..attempt ) + debug( "ssl handshake of client with id:"..tostring(self)..", attempt:"..attempt ) if attempt > maxattempt then - self.fatalerror = "max handshake attemps exceeded" + self.fatalerror = "max handshake attempts exceeded" elseif EV_TIMEOUT == event then self.fatalerror = "timeout during handshake" else @@ -570,7 +570,7 @@ return -1; end interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT ) -- reg a new timeout event - debug( "wantread during write attemp, reg it in readcallback but dont know what really happens next..." ) + debug( "wantread during write attempt, reg it in readcallback but dont know what really happens next..." ) -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent return -1 end @@ -631,7 +631,7 @@ interface:_close() end, cfg.READ_TIMEOUT ) - debug( "wantwrite during read attemp, reg it in writecallback but dont know what really happens next..." ) + debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." ) -- to be honest i dont know what happens next, if it is allowed to first read, the write etc... else -- connection was closed or fatal error interface.fatalerror = err @@ -693,7 +693,7 @@ if interface._connections >= cfg.MAX_CONNECTIONS then client:close( ) -- refuse connection debug( "maximal connections reached, refuse client connection; accept delay:", delay ) - return EV_TIMEOUT, delay -- delay for next accept attemp + return EV_TIMEOUT, delay -- delay for next accept attempt end local client_ip, client_port = client:getpeername( ) interface._connections = interface._connections + 1 -- increase connection count @@ -750,7 +750,7 @@ 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 -r 79ae027a3c09 -r fec74b84df61 plugins/mod_announce.lua --- a/plugins/mod_announce.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_announce.lua Tue Jun 29 20:23:11 2010 +0100 @@ -22,7 +22,6 @@ if not is_admin(stanza.attr.from) then -- Not an admin? Not allowed! module:log("warn", "Non-admin %s tried to send server announcement", tostring(jid.bare(stanza.attr.from))); - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end diff -r 79ae027a3c09 -r fec74b84df61 plugins/mod_bosh.lua --- a/plugins/mod_bosh.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_bosh.lua Tue Jun 29 20:23:11 2010 +0100 @@ -31,6 +31,8 @@ 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 = {} }; @@ -63,8 +65,11 @@ local session = sessions[request.sid]; if session then local requests = session.requests; - for i,r in pairs(requests) do - if r == request then requests[i] = nil; break; end + for i,r in ipairs(requests) do + if r == request then + t_remove(requests, i); + break; + end end -- If this session now has no requests open, mark it as inactive @@ -90,6 +95,8 @@ --log("debug", "Handling new request %s: %s\n----------", request.id, tostring(body)); request.notopen = true; request.log = log; + request.on_destroy = on_destroy_request; + local parser = lxp.new(init_xmlhandlers(request, stream_callbacks), "\1"); parser:parse(body); @@ -118,14 +125,21 @@ session.send(resp); end - if not request.destroyed and session.bosh_wait then - request.reply_before = os_time() + session.bosh_wait; - request.on_destroy = on_destroy_request; - waiting_requests[request] = true; + if not request.destroyed then + -- We're keeping this request open, to respond later + log("debug", "Have nothing to say, so leaving request unanswered for now"); + if session.bosh_wait then + 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 - log("debug", "Have nothing to say, so leaving request unanswered for now"); - return true; + return true; -- Inform httpserver we shall reply later end end @@ -162,10 +176,14 @@ -- New session sid = new_uuid(); - local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid), 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); @@ -174,11 +192,6 @@ function session.send(s) --log("debug", "Sending BOSH data: %s", tostring(s)); local oldest_request = r[1]; - while oldest_request and oldest_request.destroyed do - t_remove(r, 1); - waiting_requests[oldest_request] = nil; - oldest_request = r[1]; - end if oldest_request then log("debug", "We have an open request, so sending on that"); response.body = t_concat{"", tostring(s), "" }; @@ -193,7 +206,6 @@ else log("debug", "Destroying the request now..."); oldest_request:destroy(); - t_remove(r, 1); end elseif s ~= "" then log("debug", "Saved to send buffer because there are %d open requests", #r); @@ -235,8 +247,9 @@ session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid); elseif diff <= 0 then -- Repeated, ignore - session.log("debug", "rid repeated (on request %s), ignoring: %d", request.id, session.rid); + session.log("debug", "rid repeated (on request %s), ignoring: %s (diff %d)", request.id, session.rid, diff); request.notopen = nil; + request.sid = sid; t_insert(session.requests, request); return; end @@ -250,12 +263,6 @@ return; end - -- If session was inactive, make sure it is now marked as not - if #session.requests == 0 then - (session.log or log)("debug", "BOSH client now active again at %d", os_time()); - inactive_sessions[session] = nil; - end - if session.notopen then local features = st.stanza("stream:features"); hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); diff -r 79ae027a3c09 -r fec74b84df61 plugins/mod_compression.lua --- a/plugins/mod_compression.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_compression.lua Tue Jun 29 20:23:11 2010 +0100 @@ -55,7 +55,7 @@ local algorithm = a[1] if algorithm == "zlib" then session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib")) - session.log("info", "Enabled compression using zlib.") + session.log("debug", "Enabled compression using zlib.") return true; end end @@ -98,7 +98,6 @@ 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({ diff -r 79ae027a3c09 -r fec74b84df61 plugins/mod_console.lua --- a/plugins/mod_console.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_console.lua Tue Jun 29 20:23:11 2010 +0100 @@ -58,6 +58,7 @@ local session = console:new_session(conn); sessions[conn] = session; printbanner(session); + session.send(string.char(0)); end function console_listener.onincoming(conn, data) @@ -84,9 +85,10 @@ session.env._ = data; - local chunk, err = loadstring("return "..data); + local chunkname = "=console"; + local chunk, err = loadstring("return "..data, chunkname); if not chunk then - chunk, err = loadstring(data); + chunk, err = loadstring(data, chunkname); if not chunk then err = err:gsub("^%[string .-%]:%d+: ", ""); err = err:gsub("^:%d+: ", ""); diff -r 79ae027a3c09 -r fec74b84df61 plugins/mod_groups.lua --- a/plugins/mod_groups.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_groups.lua Tue Jun 29 20:23:11 2010 +0100 @@ -18,7 +18,7 @@ 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 @@ -- 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 @@ -- 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 @@ 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 -r 79ae027a3c09 -r fec74b84df61 plugins/mod_posix.lua --- a/plugins/mod_posix.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_posix.lua Tue Jun 29 20:23:11 2010 +0100 @@ -82,6 +82,7 @@ end pidfile = module:get_option("pidfile"); if pidfile then + local err; local mode = stat(pidfile) and "r+" or "w+"; pidfile_handle, err = io.open(pidfile, mode); if not pidfile_handle then diff -r 79ae027a3c09 -r fec74b84df61 plugins/mod_presence.lua --- a/plugins/mod_presence.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_presence.lua Tue Jun 29 20:23:11 2010 +0100 @@ -38,42 +38,23 @@ _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 @@ 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 @@ 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 + core_route_stanza(hosts[host], st.presence({from=to_bare, to=st_from, 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 -r 79ae027a3c09 -r fec74b84df61 plugins/mod_privacy.lua --- a/plugins/mod_privacy.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_privacy.lua Tue Jun 29 20:23:11 2010 +0100 @@ -13,7 +13,7 @@ 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 @@ 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 @@ -320,13 +301,13 @@ local origin, stanza = e.origin, e.stanza; local privacy_lists = datamanager.load(session.username, session.host, "privacy") or {}; local bare_jid = session.username.."@"..session.host; - local to = stanza.attr.to; + local to = stanza.attr.to or bare_jid; local from = stanza.attr.from; 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 -r 79ae027a3c09 -r fec74b84df61 plugins/mod_private.lua --- a/plugins/mod_private.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_private.lua Tue Jun 29 20:23:11 2010 +0100 @@ -26,7 +26,11 @@ 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 -r 79ae027a3c09 -r fec74b84df61 plugins/mod_register.lua --- a/plugins/mod_register.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_register.lua Tue Jun 29 20:23:11 2010 +0100 @@ -35,7 +35,7 @@ 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 + --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)); @@ -46,7 +46,7 @@ -- TODO datamanager should be able to delete all user data itself datamanager.store(username, host, "vcard", nil); datamanager.store(username, host, "private", nil); - datamanager.store(username, host, "offline", 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 @@ -59,6 +59,7 @@ 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 }); diff -r 79ae027a3c09 -r fec74b84df61 plugins/mod_saslauth.lua --- a/plugins/mod_saslauth.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/plugins/mod_saslauth.lua Tue Jun 29 20:23:11 2010 +0100 @@ -28,6 +28,12 @@ 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 @@ 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,21 +104,29 @@ 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 local username = nodeprep(session.sasl_handler.username); - if not username then -- TODO move this to sessionmanager - module:log("warn", "SASL succeeded but we didn't get a username!"); - session.sasl_handler = nil; - session:reset_stream(); - return; + + if not(require_provisioning) or usermanager_user_exists(username, session.host) then + local aret, err = sm_make_authenticated(session, session.sasl_handler.username); + if aret then + session.sasl_handler = nil; + session:reset_stream(); + else + module:log("warn", "SASL succeeded but username was invalid"); + session.sasl_handler = session.sasl_handler:clean_clone(); + return "failure", "not-authorized", "User authenticated successfully, but username was invalid"; + end + 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 @@ 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 -r 79ae027a3c09 -r fec74b84df61 prosody --- a/prosody Tue Jun 29 20:23:00 2010 +0100 +++ b/prosody Tue Jun 29 20:23:11 2010 +0100 @@ -143,6 +143,10 @@ debug.setupvalue(f, i, value); end end + function mt.__tostring(f) + local info = debug.getinfo(f); + return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined); + end debug.setmetatable(function() end, mt); end diff -r 79ae027a3c09 -r fec74b84df61 prosodyctl --- a/prosodyctl Tue Jun 29 20:23:00 2010 +0100 +++ b/prosodyctl Tue Jun 29 20:23:11 2010 +0100 @@ -499,37 +499,6 @@ return 1; end -local http_errors = { - [404] = "Plugin not found, did you type the address correctly?" - }; - -function commands.addplugin(arg) - if not arg[1] or arg[1] == "--help" then - show_usage("addplugin URL", "Download and install a plugin from a URL"); - return 1; - end - local url = arg[1]; - if url:match("^http://") then - local http = require "socket.http"; - show_message("Fetching..."); - local code, err = http.request(url); - if not code or not tostring(err):match("^[23]") then - show_message("Failed: "..(http_errors[err] or ("HTTP error "..err))); - return 1; - end - if url:match("%.lua$") then - local ok, err = datamanager.store(url:match("/mod_([^/]+)$"), "*", "plugins", {code}); - if not ok then - show_message("Failed to save to data store: "..err); - return 1; - end - end - show_message("Saved. Don't forget to load the module using the config file or admin console!"); - else - show_message("Sorry, I don't understand how to fetch plugins from there."); - end -end - --------------------- if command and command:match("^mod_") then -- Is a command in a module diff -r 79ae027a3c09 -r fec74b84df61 tools/xep227toprosody.lua diff -r 79ae027a3c09 -r fec74b84df61 util/datamanager.lua --- a/util/datamanager.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/datamanager.lua Tue Jun 29 20:23:11 2010 +0100 @@ -21,12 +21,13 @@ 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 @@ 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 @@ 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 -r 79ae027a3c09 -r fec74b84df61 util/pluginloader.lua --- a/util/pluginloader.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/pluginloader.lua Tue Jun 29 20:23:11 2010 +0100 @@ -16,12 +16,6 @@ module "pluginloader" -local function load_from_datastore(name) - local content = datamanager.load(name, nil, "plugins"); - if not content or not content[1] then return nil, "Resource not found"; end - return content[1], name; -end - local function load_file(name) local file, err = io_open(plugin_dir..name); if not file then return file, err; end @@ -40,26 +34,9 @@ if not content then content, err = loader(resource); end -- TODO add support for packed plugins - if not content and loader == load_file then - return load_resource(plugin, resource, load_from_datastore); - end - return content, err; end -function store_resource(plugin, resource, content, metadata) - if not resource then - resource = "mod_"..plugin..".lua"; - end - local store = { content }; - if metadata then - for k,v in pairs(metadata) do - store[k] = v; - end - end - datamanager.store(plugin.."/"..resource, nil, "plugins", store); -end - function load_code(plugin, resource) local content, err = load_resource(plugin, resource); if not content then return content, err; end diff -r 79ae027a3c09 -r fec74b84df61 util/sasl.lua --- a/util/sasl.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/sasl.lua Tue Jun 29 20:23:11 2010 +0100 @@ -41,27 +41,6 @@ state = false : disabled state = true : enabled state = nil : non-existant - -plain: - function(username, realm) - return password, state; - end - -plain-test: - function(username, realm, password) - return true or false, state; - end - -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: - function(username, domain, realm, encoding, digesthash) - return true or false, state; - end ]] local method = {}; diff -r 79ae027a3c09 -r fec74b84df61 util/sasl/anonymous.lua --- a/util/sasl/anonymous.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/sasl/anonymous.lua Tue Jun 29 20:23:11 2010 +0100 @@ -1,5 +1,5 @@ -- sasl.lua v0.4 --- Copyright (C) 2008-2009 Tobias Markmann +-- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- diff -r 79ae027a3c09 -r fec74b84df61 util/sasl/digest-md5.lua --- a/util/sasl/digest-md5.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/sasl/digest-md5.lua Tue Jun 29 20:23:11 2010 +0100 @@ -1,5 +1,5 @@ -- sasl.lua v0.4 --- Copyright (C) 2008-2009 Tobias Markmann +-- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- @@ -29,6 +29,21 @@ --========================= --SASL DIGEST-MD5 according to RFC 2831 +--[[ +Supported Authentication Backends + +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: + function(username, domain, realm, encoding, digesthash) + return true or false, state; + end +]] + local function digest(self, message) --TODO complete support for authzid diff -r 79ae027a3c09 -r fec74b84df61 util/sasl/plain.lua --- a/util/sasl/plain.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/sasl/plain.lua Tue Jun 29 20:23:11 2010 +0100 @@ -1,5 +1,5 @@ -- sasl.lua v0.4 --- Copyright (C) 2008-2009 Tobias Markmann +-- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- @@ -19,6 +19,21 @@ -- ================================ -- SASL PLAIN according to RFC 4616 + +--[[ +Supported Authentication Backends + +plain: + function(username, realm) + return password, state; + end + +plain_test: + function(username, realm, password) + return true or false, state; + end +]] + local function plain(self, message) if not message then return "failure", "malformed-request"; diff -r 79ae027a3c09 -r fec74b84df61 util/sasl/scram.lua --- a/util/sasl/scram.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/sasl/scram.lua Tue Jun 29 20:23:11 2010 +0100 @@ -1,15 +1,15 @@ -- sasl.lua v0.4 --- Copyright (C) 2008-2009 Tobias Markmann +-- 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 @@ -28,6 +28,17 @@ --========================= --SASL SCRAM-SHA-1 according to draft-ietf-sasl-scram-10 + +--[[ +Supported Authentication Backends + +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 +]] + local default_i = 4096 local function bp( b ) @@ -82,77 +93,130 @@ return username; end -local function scram_sha_1(self, message) - if not self.state then self["state"] = {} 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("warn", "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 not self.state.name then - -- we are processing client_first_message - local client_first_message = message; - 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 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["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 - if not self.state.name or not self.state.clientnonce then - return "failure", "malformed-request"; - end - - self.state.name = validate_username(self.state.name); - if not self.state.name then - log("debug", "Username violates either SASLprep or contains forbidden character sequences.") - return "failure", "malformed-request", "Invalid username."; - end - - self.state["servernonce"] = generate_uuid(); - self.state["salt"] = generate_uuid(); + self.state.name = validate_username(self.state.name); + if not self.state.name then + log("debug", "Username violates either SASLprep or contains forbidden character sequences.") + return "failure", "malformed-request", "Invalid username."; + end - local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i; - 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["servernonce"] = generate_uuid(); + + -- retreive credentials + if self.profile.plain then + local password, state = self.profile.plain(self.state.name, self.realm) + if state == nil then return "failure", "not-authorized" + elseif state == false then return "failure", "account-disabled" end + + password = saslprep(password); + if not password then + log("debug", "Password violates SASLprep."); + return "failure", "not-authorized", "Invalid password." + end + + self.state.salt = generate_uuid(); + self.state.iteration_count = default_i; + + 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 + + self.state.salted_password = salted_password; + self.state.iteration_count = iteration_count; + self.state.salt = salt + end - 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="); - 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 - - local password, state; - if self.profile.plain then - password, state = self.profile.plain(self.state.name, self.realm) - if state == nil then return "failure", "not-authorized" - elseif state == false then return "failure", "account-disabled" end - password = saslprep(password); - if not password then - log("debug", "Password violates SASLprep."); - return "failure", "not-authorized", "Invalid password." + local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count; + self.state["server_first_message"] = server_first_message; + return "challenge", server_first_message + else + -- we are processing client_final_message + local client_final_message = message; + + 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") + local StoredKey = H_f(ClientKey) + local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") + 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; + return "success", server_final_message; + else + return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; end end - - local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i) - local ClientKey = hmac_sha1(SaltedPassword, "Client Key") - local ServerKey = hmac_sha1(SaltedPassword, "Server Key") - local StoredKey = sha1(ClientKey) - local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") - local ClientSignature = hmac_sha1(StoredKey, AuthMessage) - local ClientProof = binaryXOR(ClientKey, ClientSignature) - local ServerSignature = hmac_sha1(ServerKey, AuthMessage) - - if base64.encode(ClientProof) == self.state.proof then - local server_final_message = "v="..base64.encode(ServerSignature); - self["username"] = self.state.name; - return "success", server_final_message; - else - return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; - end end + return scram_hash; end function init(registerMechanism) - registerMechanism("SCRAM-SHA-1", {"plain"}, scram_sha_1); + local function registerSCRAMMechanism(hash_name, 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 -r 79ae027a3c09 -r fec74b84df61 util/sasl_cyrus.lua --- a/util/sasl_cyrus.lua Tue Jun 29 20:23:00 2010 +0100 +++ b/util/sasl_cyrus.lua Tue Jun 29 20:23:11 2010 +0100 @@ -27,6 +27,47 @@ local pcall = pcall local s_match, s_gmatch = string.match, string.gmatch +local sasl_errstring = { + -- SASL result codes -- + [1] = "another step is needed in authentication"; + [0] = "successful result"; + [-1] = "generic failure"; + [-2] = "memory shortage failure"; + [-3] = "overflowed buffer"; + [-4] = "mechanism not supported"; + [-5] = "bad protocol / cancel"; + [-6] = "can't request info until later in exchange"; + [-7] = "invalid parameter supplied"; + [-8] = "transient failure (e.g., weak key)"; + [-9] = "integrity check failed"; + [-12] = "SASL library not initialized"; + + -- client only codes -- + [2] = "needs user interaction"; + [-10] = "server failed mutual authentication step"; + [-11] = "mechanism doesn't support requested feature"; + + -- server only codes -- + [-13] = "authentication failure"; + [-14] = "authorization failure"; + [-15] = "mechanism too weak for this user"; + [-16] = "encryption needed to use mechanism"; + [-17] = "One time use of a plaintext password will enable requested mechanism for user"; + [-18] = "passphrase expired, has to be reset"; + [-19] = "account disabled"; + [-20] = "user not found"; + [-23] = "version mismatch with plug-in"; + [-24] = "remote authentication server unavailable"; + [-26] = "user exists, but no verifier for user"; + + -- codes for password setting -- + [-21] = "passphrase locked"; + [-22] = "requested change was not needed"; + [-27] = "passphrase is too weak for security policy"; + [-28] = "user supplied passwords not permitted"; +}; +setmetatable(sasl_errstring, { __index = function() return "undefined error!" end }); + module "sasl_cyrus" local method = {}; @@ -45,10 +86,10 @@ 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; @@ -125,10 +166,10 @@ log("debug", "SASL mechanism not available from remote end") return "failure", "invalid-mechanism", "SASL mechanism not available" elseif (err == -13) then -- SASL_BADAUTH - return "failure", "not-authorized", cyrussasl.get_message( self.cyrus ) + return "failure", "not-authorized", sasl_errstring[err]; else - log("debug", "Got SASL error condition %d", err) - return "failure", "undefined-condition", cyrussasl.get_message( self.cyrus ) + log("debug", "Got SASL error condition %d: %s", err, sasl_errstring[err]); + return "failure", "undefined-condition", sasl_errstring[err]; end end