Merge 0.6->0.7
authorMatthew Wild <mwild1@gmail.com>
Tue, 18 May 2010 22:29:21 +0000 (23:29 +0100)
committerMatthew Wild <mwild1@gmail.com>
Tue, 18 May 2010 22:29:21 +0000 (23:29 +0100)
13 files changed:
1  2 
configure
core/configmanager.lua
core/usermanager.lua
net/xmppclient_listener.lua
plugins/mod_posix.lua
plugins/muc/muc.lib.lua
prosody
prosodyctl
util/sasl.lua
util/sasl/anonymous.lua
util/sasl/digest-md5.lua
util/sasl/plain.lua
util/sasl/scram.lua

diff --cc configure
Simple merge
index 9b03e1c7ad539eed1ed6526ebef5c3ce3164b5eb,9b03e1c7ad539eed1ed6526ebef5c3ce3164b5eb..54fb0a9ae82fdb61d22f4d1600f27855f84a3d46
@@@ -30,11 -30,11 +30,10 @@@ local host_mt = { __index = global_conf
  -- When key not found in section, check key in global's section
  function section_mt(section_name)
        return { __index =      function (t, k)
--                                      local section = rawget(global_config, section_name);
--                                      if not section then return nil; end
--                                      return section[k];
--                              end
--      };
++                                                                      local section = rawget(global_config, section_name);
++                                                                      if not section then return nil; end
++                                                                      return section[k];
++                                                      end };
  end
  
  function getconfig()
        function parsers.lua.load(data, filename)
                local env;
                -- The ' = true' are needed so as not to set off __newindex when we assign the functions below
--              env = setmetatable({
--                      Host = true, host = true, VirtualHost = true,
--                      Component = true, component = true,
--                      Include = true, include = true, RunScript = dofile }, {
--                              __index = function (t, k)
--                                      return rawget(_G, k) or
--                                              function (settings_table)
--                                                      config[__currenthost or "*"][k] = settings_table;
--                                              end;
--                              end,
--                              __newindex = function (t, k, v)
--                                      set(env.__currenthost or "*", "core", k, v);
--                              end
--              });
++              env = setmetatable({ Host = true, host = true, VirtualHost = true, Component = true, component = true,
++                                                      Include = true, include = true, RunScript = dofile }, { __index = function (t, k)
++                                                                                              return rawget(_G, k) or
++                                                                                                              function (settings_table)
++                                                                                                                      config[__currenthost or "*"][k] = settings_table;
++                                                                                                              end;
++                                                                              end,
++                                                              __newindex = function (t, k, v)
++                                                                                      set(env.__currenthost or "*", "core", k, v);
++                                                                              end});
                
                rawset(env, "__currenthost", "*") -- Default is global
                function env.VirtualHost(name)
index a97e2ad701ce2551acee6c139bf6f54a9e74110f,a97e2ad701ce2551acee6c139bf6f54a9e74110f..8d7270c2e59ee797341b87f0839931de88bbf162
@@@ -16,132 -16,132 +16,82 @@@ local jid_bare = require "util.jid".bar
  local config = require "core.configmanager";
  local hosts = hosts;
  
--local prosody = _G.prosody;
--
  module "usermanager"
  
--local new_default_provider;
--
--local function host_handler(host)
--      local host_session = hosts[host];
--      host_session.events.add_handler("item-added/auth-provider", function (provider)
--              if config.get(host, "core", "authentication") == provider.name then
--                      host_session.users = provider;
--              end
--      end);
--      host_session.events.add_handler("item-removed/auth-provider", function (provider)
--              if host_session.users == provider then
--                      host_session.users = new_default_provider(host);
--              end
--      end);
--      host_session.users = new_default_provider(host); -- Start with the default usermanager provider
--end
--prosody.events.add_handler("host-activated", host_handler);
--prosody.events.add_handler("component-activated", host_handler);
--
  local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
  
--function new_default_provider(host)
--      local provider = { name = "default" };
--      
--      function provider.test_password(username, password)
--              if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
--              local credentials = datamanager.load(username, host, "accounts") or {};
--      
++function validate_credentials(host, username, password, method)
++      log("debug", "User '%s' is being validated", username);
++      if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
++      local credentials = datamanager.load(username, host, "accounts") or {};
++
++      if method == nil then method = "PLAIN"; end
++      if method == "PLAIN" and credentials.password then -- PLAIN, do directly
                if password == credentials.password then
                        return true;
                else
                        return nil, "Auth failed. Invalid username or password.";
                end
++  end
++      -- must do md5
++      -- make credentials md5
++      local pwd = credentials.password;
++      if not pwd then pwd = credentials.md5; else pwd = hashes.md5(pwd, true); end
++      -- make password md5
++      if method == "PLAIN" then
++              password = hashes.md5(password or "", true);
++      elseif method ~= "DIGEST-MD5" then
++              return nil, "Unsupported auth method";
        end
--
--      function provider.get_password(username)
--              if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
--              return (datamanager.load(username, host, "accounts") or {}).password;
--      end
--      
--      function provider.set_password(username, password)
--              if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
--              local account = datamanager.load(username, host, "accounts");
--              if account then
--                      account.password = password;
--                      return datamanager.store(username, host, "accounts", account);
--              end
--              return nil, "Account not available.";
--      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
--      end
--
--      function provider.create_user(username, password)
--              if is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
--              return datamanager.store(username, host, "accounts", {password = password});
--      end
--
--      function provider.get_supported_methods()
--              return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
++      -- compare
++      if password == pwd then
++              return true;
++      else
++              return nil, "Auth failed. Invalid username or password.";
        end
--
--      function provider.is_admin(jid)
--              local admins = config.get(host, "core", "admins");
--              if admins ~= config.get("*", "core", "admins") then
--                      if type(admins) == "table" then
--                              jid = jid_bare(jid);
--                              for _,admin in ipairs(admins) do
--                                      if admin == jid then return true; end
--                              end
--                      elseif admins then
--                              log("error", "Option 'admins' for host '%s' is not a table", host);
--                      end
--              end
--              return is_admin(jid); -- Test whether it's a global admin instead
--      end
--      return provider;
--end
--
--function validate_credentials(host, username, password, method)
--      return hosts[host].users.test_password(username, password);
  end
  
  function get_password(username, host)
--      return hosts[host].users.get_password(username);
++      if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
++      return (datamanager.load(username, host, "accounts") or {}).password
  end
--
  function set_password(username, host, password)
--      return hosts[host].users.set_password(username, password);
++      if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
++      local account = datamanager.load(username, host, "accounts");
++      if account then
++              account.password = password;
++              return datamanager.store(username, host, "accounts", account);
++      end
++      return nil, "Account not available.";
  end
  
  function user_exists(username, host)
--      return hosts[host].users.user_exists(username);
++      if is_cyrus(host) then return true; end
++      return datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials
  end
  
  function create_user(username, password, host)
--      return hosts[host].users.create_user(username, password);
++      if is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
++      return datamanager.store(username, host, "accounts", {password = password});
  end
  
  function get_supported_methods(host)
--      return hosts[host].users.get_supported_methods();
++      return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
  end
  
  function is_admin(jid, host)
--      if host and host ~= "*" then
--              return hosts[host].users.is_admin(jid);
--      else -- Test only whether this JID is a global admin
--              local admins = config.get("*", "core", "admins");
--              if type(admins) == "table" then
--                      jid = jid_bare(jid);
--                      for _,admin in ipairs(admins) do
--                              if admin == jid then return true; end
--                      end
--              elseif admins then
--                      log("error", "Option 'admins' for host '%s' is not a table", host);
--              end
++      host = host or "*";
++      local admins = config.get(host, "core", "admins");
++      if host ~= "*" and admins == config.get("*", "core", "admins") then
                return nil;
        end
++      if type(admins) == "table" then
++              jid = jid_bare(jid);
++              for _,admin in ipairs(admins) do
++                      if admin == jid then return true; end
++              end
++      elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
++      return nil;
  end
  
--_M.new_default_provider = new_default_provider;
--
  return _M;
index f3a372ae9c2f73f0e3e42a22a088bbe8022e8982,f3a372ae9c2f73f0e3e42a22a088bbe8022e8982..94daa2b2f7c95f8f846cf451da31ebe85620288a
@@@ -11,7 -11,7 +11,7 @@@
  local logger = require "logger";
  local log = logger.init("xmppclient_listener");
  local lxp = require "lxp"
--local new_xmpp_stream = require "util.xmppstream".new;
++local init_xmlhandlers = require "core.xmlhandlers"
  local sm_new_session = require "core.sessionmanager".new_session;
  
  local connlisteners_register = require "net.connlisteners".register;
@@@ -72,6 -72,6 +72,23 @@@ local xmppclient = { default_port = 522
  
  -- 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
++                      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
++              
++              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)
@@@ -128,29 -128,29 +145,15 @@@ function xmppclient.onincoming(conn, da
                        conn:setoption("keepalive", opt_keepalives);
                end
                
++              session.reset_stream = session_reset_stream;
                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;
--                      session.stream:reset();
--              end
--              
--              function session.data(data)
--                      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_reset_stream(session); -- Initialise, ready for use
                
                session.dispatch_stanza = stream_callbacks.handlestanza;
        end
        if data then
--              session.data(data);
++              session.data(conn, data);
        end
  end
        
index 52b1e0e6e510533ca87987ce01da71b0a1d02177,52b1e0e6e510533ca87987ce01da71b0a1d02177..c38f7eba75399bb9d9e421595f278454c77ec573
@@@ -54,16 -54,16 +54,16 @@@ module:add_event_hook("server-started"
        end);
  
  -- Don't even think about it!
--if not prosody.start_time then -- server-starting
--      local suid = module:get_option("setuid");
--      if not suid or suid == 0 or suid == "root" then
--              if pposix.getuid() == 0 and not module:get_option("run_as_root") then
--                      module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
--                      module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
--                      prosody.shutdown("Refusing to run as root");
++module:add_event_hook("server-starting", function ()
++              local suid = module:get_option("setuid");
++              if not suid or suid == 0 or suid == "root" then
++                      if pposix.getuid() == 0 and not module:get_option("run_as_root") then
++                              module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
++                              module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
++                              prosody.shutdown("Refusing to run as root");
++                      end
                end
--      end
--end
++      end);
  
  local pidfile;
  local pidfile_handle;
@@@ -141,9 -141,9 +141,7 @@@ if daemonize the
                        write_pidfile();
                end
        end
--      if not prosody.start_time then -- server-starting
--              daemonize_server();
--      end
++      module:add_event_hook("server-starting", daemonize_server);
  else
        -- Not going to daemonize, so write the pid of this process
        write_pidfile();
index 273e21ce94214b585d54580b60eb658b6d59f5af,273e21ce94214b585d54580b60eb658b6d59f5af..18c80325bbd1bce5db492b36643407d4d1b4bf65
@@@ -122,13 -122,13 +122,9 @@@ function room_mt:broadcast_message(stan
                local history = self._data['history'];
                if not history then history = {}; self._data['history'] = history; end
                stanza = st.clone(stanza);
--              stanza.attr.to = "";
--              local stamp = datetime.datetime();
--              local chars = #tostring(stanza);
--              stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
++              stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
                stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
--              local entry = { stanza = stanza, stamp = stamp };
--              t_insert(history, entry);
++              t_insert(history, st.preserialize(stanza));
                while #history > history_length do t_remove(history, 1) end
        end
  end
@@@ -155,46 -155,46 +151,12 @@@ function room_mt:send_occupant_list(to
                end
        end
  end
--function room_mt:send_history(to, stanza)
++function room_mt:send_history(to)
        local history = self._data['history']; -- send discussion history
        if history then
--              local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
--              local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
--              
--              local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
--              if maxchars then maxchars = math.floor(maxchars); end
--              
--              local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
--              if not history_tag then maxstanzas = 20; end
--
--              local seconds = history_tag and tonumber(history_tag.attr.seconds);
--              if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end
--
--              local since = history_tag and history_tag.attr.since;
--              if since and not since:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%dZ$") then since = nil; end -- FIXME timezone support
--              if seconds and (not since or since < seconds) then since = seconds; end
--
--              local n = 0;
--              local charcount = 0;
--              local stanzacount = 0;
--              
--              for i=#history,1,-1 do
--                      local entry = history[i];
--                      if maxchars then
--                              if not entry.chars then
--                                      entry.stanza.attr.to = "";
--                                      entry.chars = #tostring(entry.stanza);
--                              end
--                              charcount = charcount + entry.chars + #to;
--                              if charcount > maxchars then break; end
--                      end
--                      if since and since > entry.stamp then break; end
--                      if n + 1 > maxstanzas then break; end
--                      n = n + 1;
--              end
--              for i=#history-n+1,#history do
--                      local msg = history[i].stanza;
--                      msg.attr.to = to;
++              for _, msg in ipairs(history) do
++                      msg = st.deserialize(msg);
++                      msg.attr.to=to;
                        self:_route_stanza(msg);
                end
        end
@@@ -357,7 -357,7 +319,12 @@@ function room_mt:handle_to_occupant(ori
                                                                :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
                                                                :tag("status", {code='110'}));
                                                end
--                                              self:send_history(from, stanza);
++                                              if self._data.whois == 'anyone' then -- non-anonymous?
++                                                      self:_route_stanza(st.stanza("message", {from=to, to=from, type='groupchat'})
++                                                              :tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
++                                                              :tag("status", {code='100'}));
++                                              end
++                                              self:send_history(from);
                                        else -- banned
                                                local reply = st.error_reply(stanza, "auth", "forbidden"):up();
                                                reply.tags[1].attr.code = "403";
@@@ -784,7 -784,7 +751,7 @@@ en
  function room_mt:set_role(actor, occupant_jid, role, callback, reason)
        if role == "none" then role = nil; end
        if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
--      if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
++      if self:get_role(self._jid_nick[actor]) ~= "moderator" then return nil, "cancel", "not-allowed"; end
        local occupant = self._occupants[occupant_jid];
        if not occupant then return nil, "modify", "not-acceptable"; end
        if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end
@@@ -837,9 -837,9 +804,6 @@@ function room_mt:_route_stanza(stanza
                                end
                        end
                end
--              if self._data.whois == 'anyone' then
--                  muc_child:tag('status', { code = '100' });
--              end
        end
        self:route_stanza(stanza);
        if muc_child then
diff --cc prosody
index 517762f3c438d4d7061739f7a8f3ec95e86d3175,517762f3c438d4d7061739f7a8f3ec95e86d3175..e4e821050d60829c9b8c652d33e003ec7bde470c
+++ b/prosody
@@@ -22,6 -22,6 +22,9 @@@ if CFG_SOURCEDIR the
        package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
  end
  
++package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
++package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
++
  -- Substitute ~ with path to home directory in data path
  if CFG_DATADIR then
        if os.getenv("HOME") then
        end
  end
  
---- Global 'prosody' object
--prosody = { events = require "util.events".new(); };
--local prosody = prosody;
--
  -- Load the config-parsing module
  config = require "core.configmanager"
  
@@@ -156,6 -156,6 +155,10 @@@ function init_global_state(
        full_sessions = {};
        hosts = {};
  
++      -- Global 'prosody' object
++      prosody = {};
++      local prosody = prosody;
++      
        prosody.bare_sessions = bare_sessions;
        prosody.full_sessions = full_sessions;
        prosody.hosts = hosts;
        
        prosody.arg = _G.arg;
  
++      prosody.events = require "util.events".new();
++      
        prosody.platform = "unknown";
        if os.getenv("WINDIR") then
                prosody.platform = "windows";
        -- Function to reopen logfiles
        function prosody.reopen_logfiles()
                log("info", "Re-opening log files");
++              eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
                prosody.events.fire_event("reopen-log-files");
        end
  
@@@ -285,9 -285,9 +291,9 @@@ en
  function load_secondary_libraries()
        --- Load and initialise core modules
        require "util.import"
--      require "util.xmppstream"
        require "core.xmlhandlers"
        require "core.rostermanager"
++      require "core.eventmanager"
        require "core.hostmanager"
        require "core.modulemanager"
        require "core.usermanager"
@@@ -331,6 -331,6 +337,7 @@@ en
  function prepare_to_start()
        log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
        -- Signal to modules that we are ready to start
++      eventmanager.fire_event("server-starting");
        prosody.events.fire_event("server-starting");
  
        -- start listening on sockets
@@@ -448,12 -448,12 +455,14 @@@ init_data_store()
  init_global_protection();
  prepare_to_start();
  
++eventmanager.fire_event("server-started");
  prosody.events.fire_event("server-started");
  
  loop();
  
  log("info", "Shutting down...");
  cleanup();
++eventmanager.fire_event("server-stopped");
  prosody.events.fire_event("server-stopped");
  log("info", "Shutdown complete");
  
diff --cc prosodyctl
index c0cd89a0c985c454332cdb8e77e9020f8d4a6d78,c0cd89a0c985c454332cdb8e77e9020f8d4a6d78..ccc1e2f9cfe5d86f8d3d55353d3842911aad35be
@@@ -29,14 -29,14 +29,6 @@@ if CFG_DATADIR the
        end
  end
  
---- Global 'prosody' object
--prosody = {
--      hosts = {},
--      events = require "util.events".new(),
--      platform = "posix"
--};
--local prosody = prosody;
--
  config = require "core.configmanager"
  
  do
@@@ -71,6 -71,6 +63,8 @@@ if not require "util.dependencies".chec
        os.exit(1);
  end
  
++prosody = { hosts = {}, events = events, platform = "posix" };
++
  local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
  require "util.datamanager".set_data_path(data_path);
  
@@@ -120,14 -120,14 +114,12 @@@ local error_messages = setmetatable(
                ["not-running"] = "Prosody is not running";
                }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
  
--hosts = prosody.hosts;
++local events = require "util.events".new();
  
--local function make_host(hostname)
--      return { events = prosody.events, users = require "core.usermanager".new_default_provider(hostname) };
--end
++hosts = prosody.hosts;
  
  for hostname, config in pairs(config.getconfig()) do
--      hosts[hostname] = make_host(hostname);
++      hosts[hostname] = { events = events };
  end
        
  require "core.modulemanager"
@@@ -239,23 -239,23 +231,16 @@@ function commands.adduser(arg
                return 1;
        end
        
--      if not hosts[host] then
--              show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
--              show_warning("The user will not be able to log in until this is changed.");
--              hosts[host] = make_host(host);
--      elseif config.get(host, "core", "authentication")
--              and config.get(host, "core", "authentication") ~= "default" then
--              show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
--                      config.get(host, "core", "authentication"));
--              show_warning("prosodyctl currently only supports the default provider, sorry :(");
--              return 1;
--      end
--      
        if prosodyctl.user_exists{ user = user, host = host } then
                show_message [[That user already exists]];
                return 1;
        end
        
++      if not hosts[host] then
++              show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
++              show_warning("The user will not be able to log in until this is changed.");
++      end
++      
        local password = read_password();
        if not password then return 1; end
        
@@@ -284,18 -284,18 +269,6 @@@ function commands.passwd(arg
                return 1;
        end
        
--      if not hosts[host] then
--              show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
--              show_warning("The user will not be able to log in until this is changed.");
--              hosts[host] = make_host(host);
--      elseif config.get(host, "core", "authentication")
--              and config.get(host, "core", "authentication") ~= "default" then
--              show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
--                      config.get(host, "core", "authentication"));
--              show_warning("prosodyctl currently only supports the default provider, sorry :(");
--              return 1;
--      end
--      
        if not prosodyctl.user_exists { user = user, host = host } then
                show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
                return 1;
@@@ -329,18 -329,18 +302,6 @@@ function commands.deluser(arg
                return 1;
        end
        
--      if not hosts[host] then
--              show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
--              show_warning("The user will not be able to log in until this is changed.");
--              hosts[host] = make_host(host);
--      elseif config.get(host, "core", "authentication")
--              and config.get(host, "core", "authentication") ~= "default" then
--              show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
--                      config.get(host, "core", "authentication"));
--              show_warning("prosodyctl currently only supports the default provider, sorry :(");
--              return 1;
--      end
--
        if not prosodyctl.user_exists { user = user, host = host } then
                show_message [[That user does not exist on this server]]
                return 1;
diff --cc util/sasl.lua
index 306acc0c590998be8eb151e3ddc7cdf13ad12058,306acc0c590998be8eb151e3ddc7cdf13ad12058..eb71956b692aa5942bf0690b4787865132f588ce
@@@ -41,6 -41,6 +41,27 @@@ Authentication Backend Prototypes
  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 = {};
index f3e31a7f0d7f7783a1bc08d68c0588a864108664,f3e31a7f0d7f7783a1bc08d68c0588a864108664..656502945252d738139f4cfd2d981db3a3c4b400
@@@ -1,5 -1,5 +1,5 @@@
  -- sasl.lua v0.4
---- Copyright (C) 2008-2010 Tobias Markmann
++-- Copyright (C) 2008-2009 Tobias Markmann
  --
  --    All rights reserved.
  --
@@@ -20,16 -20,16 +20,6 @@@ module "anonymous
  
  --=========================
  --SASL ANONYMOUS according to RFC 4505
--
----[[
--Supported Authentication Backends
--
--anonymous:
--      function(username, realm)
--              return true; --for normal usage just return true; if you don't like the supplied username you can return false.
--      end
--]]
--
  local function anonymous(self, message)
        local username;
        repeat
index 8986ca4567f3884be857d7c576df1950a96c10df,8986ca4567f3884be857d7c576df1950a96c10df..04acf04dcde50bbc9735c4c16eb78df5aceff663
@@@ -1,5 -1,5 +1,5 @@@
  -- sasl.lua v0.4
---- Copyright (C) 2008-2010 Tobias Markmann
++-- Copyright (C) 2008-2009 Tobias Markmann
  --
  --    All rights reserved.
  --
@@@ -29,21 -29,21 +29,6 @@@ module "digest-md5
  --=========================
  --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
  
index 2abbc53a1688dd66c2d02253676a69446a5aac85,2abbc53a1688dd66c2d02253676a69446a5aac85..ae5c777a77423f466ecd5977f696e0f9bec7e313
@@@ -1,5 -1,5 +1,5 @@@
  -- sasl.lua v0.4
---- Copyright (C) 2008-2010 Tobias Markmann
++-- Copyright (C) 2008-2009 Tobias Markmann
  --
  --    All rights reserved.
  --
@@@ -19,26 -19,26 +19,6 @@@ module "plain
  
  -- ================================
  -- 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
--      
--plain-hashed:
--      function(username, realm)
--              return hashed_password, hash_function, state;
--      end
--]]
--
  local function plain(self, message)
        if not message then
                return "failure", "malformed-request";
                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 -85,7 +61,7 @@@
  end
  
  function init(registerMechanism)
--      registerMechanism("PLAIN", {"plain", "plain_test", "plain_hashed"}, plain);
++      registerMechanism("PLAIN", {"plain", "plain_test"}, plain);
  end
  
  return _M;
index ed7d7bc385a0de3fbfd33c8b4223c0deff1db26d,ed7d7bc385a0de3fbfd33c8b4223c0deff1db26d..103e8a905d65373efb062cdd5020495773760e75
@@@ -1,5 -1,5 +1,5 @@@
  -- sasl.lua v0.4
---- Copyright (C) 2008-2010 Tobias Markmann
++-- Copyright (C) 2008-2009 Tobias Markmann
  --
  --    All rights reserved.
  --
@@@ -28,16 -28,16 +28,6 @@@ module "scram
  
  --=========================
  --SASL SCRAM-SHA-1 according to draft-ietf-sasl-scram-10
--
----[[
--Supported Authentication Backends
--
--scram-{MECH}:
--      function(username, realm)
--              return salted_password, iteration_count, salt, state;
--      end
--]]
--
  local default_i = 4096
  
  local function bp( b )
@@@ -92,95 -92,95 +82,77 @@@ local function validate_username(userna
        return username;
  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
++local function scram_sha_1(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 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 not self.state.name or not self.state.clientnonce then
--                              return "failure", "malformed-request";
--                      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.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();
--                      
--                      -- 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;
--                              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);
--                              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["servernonce"] = generate_uuid();
++              self.state["salt"] = generate_uuid();
                
--                      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
--                      if type(message) ~= "string" then return "failure", "malformed-request" end
--                      -- we are processing client_final_message
--                      local client_final_message = message;
++              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["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.";
++              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."
                        end
++              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)
++              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
++              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)
--      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));
--      end
--      
--      registerSCRAMMechanism("SHA-1", sha1, hmac_sha1);
++      registerMechanism("SCRAM-SHA-1", {"plain"}, scram_sha_1);
  end
  
  return _M;