Merge 0.6->0.7
authorMatthew Wild <mwild1@gmail.com>
Wed, 1 Jun 2011 22:25:24 +0000 (23:25 +0100)
committerMatthew Wild <mwild1@gmail.com>
Wed, 1 Jun 2011 22:25:24 +0000 (23:25 +0100)
28 files changed:
1  2 
configure
core/configmanager.lua
core/rostermanager.lua
core/s2smanager.lua
core/sessionmanager.lua
core/usermanager.lua
core/xmlhandlers.lua
net/dns.lua
net/server_event.lua
net/server_select.lua
net/xmppclient_listener.lua
plugins/mod_announce.lua
plugins/mod_bosh.lua
plugins/mod_compression.lua
plugins/mod_groups.lua
plugins/mod_posix.lua
plugins/mod_presence.lua
plugins/mod_privacy.lua
plugins/mod_register.lua
plugins/mod_saslauth.lua
plugins/muc/muc.lib.lua
prosody
prosodyctl
tools/xep227toprosody.lua
util/sasl/anonymous.lua
util/sasl/plain.lua
util/sasl/scram.lua
util/sasl_cyrus.lua

diff --cc configure
index e9e2dc7f4647dbc0499123b5515fb67348d8ab83,f13695382369f8ef7222c72b2b5d4edc69bfe888..f2d8fc098624b48a289510e73285cdf9a0ecfa12
+++ b/configure
@@@ -26,7 -26,7 +26,7 @@@ Configure Prosody prior to building
  
  --help                      This help.
  --ostype=OS                 Use one of the OS presets.
-                             May be one of: debian, macosx
 -                            May be one of: debian, macosx, linux, freebsd
++                            May be one of: debian, macosx, linux
  --prefix=DIR                Prefix where Prosody should be installed.
                              Default is $PREFIX
  --sysconfdir=DIR            Location where the config file should be installed.
     shift
  done
  
-       LUA_INCDIR_SET=yes      
 +if [ "$OSTYPE_SET" = "yes" ]
 +then
 +      if [ "$OSTYPE" = "debian" ]
 +      then LUA_SUFFIX="5.1";
 +      LUA_SUFFIX_SET=yes
 +      LUA_INCDIR=/usr/include/lua5.1;
 +      LUA_INCDIR_SET=yes
 +      fi
 +      if [ "$OSTYPE" = "macosx" ]
 +      then LUA_INCDIR=/usr/local/include;
-       LFLAGS="-bundle -undefined dynamic_lookup"
-       fi      
++      LUA_INCDIR_SET=yes
 +      LUA_LIBDIR=/usr/local/lib
 +      LUA_LIBDIR_SET=yes
 +      CFLAGS="-Wall"
++      LDFLAGS="-bundle -undefined dynamic_lookup"
++      fi
++        if [ "$OSTYPE" = "linux" ]
++        then LUA_INCDIR=/usr/local/include;
++        LUA_INCDIR_SET=yes
++        LUA_LIBDIR=/usr/local/lib
++        LUA_LIBDIR_SET=yes
++        CFLAGS="-Wall -fPIC"
++        LDFLAGS="-shared"
++        fi
 +fi
 +
  if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
  then
     if [ "$PREFIX" = "/usr" ]
index 9b03e1c7ad539eed1ed6526ebef5c3ce3164b5eb,6b181443edf151133d4770bbde291210335d378f..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 e2a92696391063c72c55026e47db68d1871602e5,59ba6579a24af27fe78470de5473bb4ff1347698..506cf20593aa7da447d09ca5836517bbda1bab3b
@@@ -186,10 -190,22 +190,10 @@@ function process_inbound_unsubscribe(us
        end
  end
  
 -local function _get_online_roster_subscription(jidA, jidB)
 -      local user = bare_sessions[jidA];
 -      local item = user and (user.roster[jidB] or { subscription = "none" });
 -      return item and item.subscription;
 -end
  function is_contact_subscribed(username, host, jid)
-       local roster = load_roster(username, host);
 -      do
 -              local selfjid = username.."@"..host;
 -              local subscription = _get_online_roster_subscription(selfjid, jid);
 -              if subscription then return (subscription == "both" or subscription == "from"); end
 -              local subscription = _get_online_roster_subscription(jid, selfjid);
 -              if subscription then return (subscription == "both" or subscription == "to"); end
 -      end
+       local roster, err = load_roster(username, host);
        local item = roster[jid];
-       return item and (item.subscription == "from" or item.subscription == "both");
+       return item and (item.subscription == "from" or item.subscription == "both"), err;
  end
  
  function is_contact_pending_in(username, host, jid)
index ca87670a766f38a357ddfde21df266955e88640f,e5fb699bacfc648ae8a3de869fe98dafd75a2061..0c29da143bdde35a5e92ac9b1f9962d3953d8a15
@@@ -234,14 -251,6 +234,13 @@@ function attempt_connection(host_sessio
                        end
                end, "_xmpp-server._tcp."..connect_host..".", "SRV");
                
-               log("debug", "DNS lookup for %s sent, waiting for response before we can connect", to_host);
 +              -- Set handler for DNS timeout
 +              add_task(dns_timeout, function ()
 +                      if handle then
 +                              adns.cancel(handle, true);
 +                      end
 +              end);
 +              
                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;
index 6e771a8444819c261e114f7dbd0d8461b1efbf24,6376851533021b51b0a10b3f2730a8a41c3039a6..e1f1a80214e9540abcea1a17c023a8d49e0889f1
@@@ -25,7 -25,9 +25,8 @@@ local rm_load_roster = require "core.ro
  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 initialize_filters = require "util.filters".initialize;
  local fire_event = require "core.eventmanager".fire_event;
  local add_task = require "util.timer".add_task;
  local gettime = require "socket".gettime;
index a97e2ad701ce2551acee6c139bf6f54a9e74110f,43e43df9887e06e5cc564d28f95b30fb3e828051..698d2f10413676bc28f3779e94b0f5242fe73e78
@@@ -15,133 -16,120 +15,86 @@@ local hashes = require "util.hashes"
  local jid_bare = require "util.jid".bare;
  local config = require "core.configmanager";
  local hosts = hosts;
 -local sasl_new = require "util.sasl".new;
  
- local prosody = _G.prosody;
+ local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
  
 -local prosody = _G.prosody;
 -
 -local setmetatable = setmetatable;
 -
 -local default_provider = "internal_plain";
 -
  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
 -function new_null_provider()
 -      local function dummy() end;
 -      local function dummy_get_sasl_handler() return sasl_new(nil, {}); end
 -      return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, { __index = function() return dummy; end });
--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 initialize_host(host)
 -      local host_session = hosts[host];
 -      host_session.events.add_handler("item-added/auth-provider", function (event)
 -              local provider = event.item;
 -              local auth_provider = config.get(host, "core", "authentication") or default_provider;
 -              if provider.name == auth_provider then
 -                      host_session.users = provider;
 -              end
 -              if host_session.users ~= nil and host_session.users.name ~= nil then
 -                      log("debug", "host '%s' now set to use user provider '%s'", host, host_session.users.name);
 -              end
 -      end);
 -      host_session.events.add_handler("item-removed/auth-provider", function (event)
 -              local provider = event.item;
 -              if host_session.users == provider then
 -                      host_session.users = new_null_provider();
 -              end
 -      end);
 -      host_session.users = new_null_provider(); -- Start with the default usermanager provider
 -      local auth_provider = config.get(host, "core", "authentication") or default_provider;
 -      if auth_provider ~= "null" then
 -              modulemanager.load(host, "auth_"..auth_provider);
 -      end
 -end;
 -prosody.events.add_handler("host-activated", initialize_host, 100);
 -prosody.events.add_handler("component-activated", initialize_host, 100);
++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 {};
 -function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
 -
 -function test_password(username, host, password)
 -      return hosts[host].users.test_password(username, password);
++      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});
++      -- compare
++      if password == pwd then
++              return true;
++      else
++              return nil, "Auth failed. Invalid username or password.";
 +      end
-       function provider.get_supported_methods()
-               return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
-       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, password, host)
 -      return hosts[host].users.set_password(username, password);
 +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 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)
--      return hosts[host].users.create_user(username, password);
++      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
  
 -function get_sasl_handler(host)
 -      return hosts[host].users.get_sasl_handler();
 -end
 -
 -function get_provider(host)
 -      return hosts[host].users;
 +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
 -      local is_admin;
 -      jid = jid_bare(jid);
+       host = host or "*";
 -      
 -      local host_admins = config.get(host, "core", "admins");
 -      local global_admins = config.get("*", "core", "admins");
 -      
 -      if host_admins and host_admins ~= global_admins then
 -              if type(host_admins) == "table" then
 -                      for _,admin in ipairs(host_admins) do
 -                              if admin == jid then
 -                                      is_admin = true;
 -                                      break;
 -                              end
 -                      end
 -              elseif admins then
 -                      log("error", "Option 'admins' for host '%s' is not a list", host);
 -              end
++      local admins = config.get(host, "core", "admins");
++      if host ~= "*" and admins == config.get("*", "core", "admins") then
 +              return nil;
        end
 -      
 -      if not is_admin and global_admins then
 -              if type(global_admins) == "table" then
 -                      for _,admin in ipairs(global_admins) do
 -                              if admin == jid then
 -                                      is_admin = true;
 -                                      break;
 -                              end
 -                      end
 -              elseif admins then
 -                      log("error", "Global option 'admins' is not a list");
++      if type(admins) == "table" then
++              jid = jid_bare(jid);
++              for _,admin in ipairs(admins) do
++                      if admin == jid then return true; end
+               end
 -      end
 -      
 -      -- Still not an admin, check with auth provider
 -      if not is_admin and host ~= "*" and hosts[host].users.is_admin then
 -              is_admin = hosts[host].users.is_admin(jid);
 -      end
 -      return is_admin or false;
++      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 6a5cac85908f0ce7af41e01e0fbf82bd46eb0bb9,b7992f77a817cdf8e7bc279ec4f92a3a3c2768cb..47db75d3963c3e8a4c749926586c4504bfe3669b
@@@ -143,19 -124,21 +134,33 @@@ function init_xmlhandlers(session, stre
                        else
                                stanza:up();
                        end
+               else
+                       if tagname == stream_tag then
+                               if cb_streamclosed then
+                                       cb_streamclosed(session);
+                               end
+                       else
+                               local curr_ns,name = tagname:match(ns_pattern);
+                               if name == "" then
+                                       curr_ns, name = "", curr_ns;
+                               end
+                               cb_error(session, "parse-error", "unexpected-element-close", name);
+                       end
+                       stanza, chardata = nil, {};
                end
-               local function restricted_handler()
-                       cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
-               end
-               
-               if lxp_supports_doctype then
-                       xml_handlers.StartDoctypeDecl = restricted_handler;
-               end
-               xml_handlers.Comment = restricted_handler;
-               xml_handlers.StartCdataSection = restricted_handler;
-               xml_handlers.ProcessingInstruction = restricted_handler;
-       
+       end
++
++      local function restricted_handler()
++              cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
++      end
++
++      if lxp_supports_doctype then
++              xml_handlers.StartDoctypeDecl = restricted_handler;
++      end
++      xml_handlers.Comment = restricted_handler;
++      xml_handlers.StartCdataSection = restricted_handler;
++      xml_handlers.ProcessingInstruction = restricted_handler;
 +
        return xml_handlers;
  end
  
diff --cc net/dns.lua
Simple merge
Simple merge
index e3a389a878864aa959d40fd3338170aa5be1c5c4,51ae4e66a1e1ccbbf990ef6356c2cb9b58303dda..298e560aa00cc6a5ab2eebde12b7b53634641a08
@@@ -484,6 -483,7 +483,7 @@@ wrapconnection = function( server, list
                        if drain then
                                drain(handler)
                        end
 -                      _ = needtls and handler:starttls(nil)
++                      _ = needtls and handler:starttls(nil, true)
                        _ = toclose and handler:close( )
                        return true
                elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write
index f3a372ae9c2f73f0e3e42a22a088bbe8022e8982,7572697244e3abb0dd32e87385f196de0a467279..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 -75,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)
@@@ -111,46 -114,54 +128,32 @@@ en
  
  -- End of session methods --
  
 -function xmppclient.onconnect(conn)
 -      local session = sm_new_session(conn);
 -      sessions[conn] = session;
 -      
 -      session.log("info", "Client connected");
 -      
 -      -- Client is using legacy SSL (otherwise mod_tls sets this flag)
 -      if conn:ssl() then
 -              session.secure = true;
 -      end
 -      
 -      if opt_keepalives ~= nil then
 -              conn:setoption("keepalive", opt_keepalives);
 -      end
 -      
 -      session.close = session_close;
 -      
 -      local stream = new_xmpp_stream(session, stream_callbacks);
 -      session.stream = stream;
 -      
 -      session.notopen = true;
 -      
 -      function session.reset_stream()
 -              session.notopen = true;
 -              session.stream:reset();
 -      end
 -      
 -      local filter = session.filter;
 -      function session.data(data)
 -              data = filter("bytes/in", data);
 -              if data then
 -                      local ok, err = stream:feed(data);
 -                      if ok then return; end
 -                      log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
 -                      session:close("xml-not-well-formed");
 -              end
 -      end
 -      
 -      local handlestanza = stream_callbacks.handlestanza;
 -      function session.dispatch_stanza(session, stanza)
 -              return handlestanza(session, stanza);
 -      end
 -end
 -
  function xmppclient.onincoming(conn, data)
        local session = sessions[conn];
 -      if session then
 -              session.data(data);
 +      if not session then
 +              session = sm_new_session(conn);
 +              sessions[conn] = session;
 +
 +              session.log("info", "Client connected");
 +              
 +              -- Client is using legacy SSL (otherwise mod_tls sets this flag)
 +              if conn:ssl() then
 +                      session.secure = true;
 +              end
 +              
 +              if opt_keepalives ~= nil then
 +                      conn:setoption("keepalive", opt_keepalives);
 +              end
 +              
++              session.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 7f08a6e0ecad85980e7b87a2c3fa8f6dbde96ade,77555bec73b6aec191bc8dd1a8d594b66948aa06..d3017f6c994f0cc33ac5f495f5b8b333cf2d9d40
@@@ -21,8 -45,7 +21,7 @@@ function handle_announcement(data
        
        if not is_admin(stanza.attr.from) then
                -- Not an admin? Not allowed!
 -              module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from);
 +              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
        
Simple merge
index c2e84f2b022b73892cf062997caac8ae488678d1,d890931fe8bce1f5e2001208efc3932b31dadb9e..533414924af23973fa8b1f4051d024369d217e30
@@@ -94,26 -95,19 +94,25 @@@ en
  
  -- setup compression for a stream
  local function setup_compression(session, deflate_stream)
 -      add_filter(session, "bytes/out", function(t)
 -              local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
 -              if status == false then
 -                      module:log("warn", "%s", tostring(compressed));
 -                      session:close({
 -                              condition = "undefined-condition";
 -                              text = compressed;
 -                              extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
 -                      });
 -                      return;
 -              end
 -              return compressed;
 -      end);   
 +      local old_send = (session.sends2s or session.send);
 +      
 +      local new_send = function(t)
 +                      --TODO: Better code injection in the sending process
-                       session.log(t)
 +                      local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
 +                      if status == false then
 +                              session:close({
 +                                      condition = "undefined-condition";
 +                                      text = compressed;
 +                                      extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
 +                              });
 +                              module:log("warn", "%s", tostring(compressed));
 +                              return;
 +                      end
 +                      session.conn:write(compressed);
 +              end;
 +      
 +      if session.sends2s then session.sends2s = new_send
 +      elseif session.send then session.send = new_send end
  end
  
  -- setup decompression for a stream
Simple merge
index 52b1e0e6e510533ca87987ce01da71b0a1d02177,77b2f2a4ee25ae16d23d7198e834be09f7b13e10..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 -150,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 5ad3bfdf28d07c6f108ef7b9939c320e15ca5590,8e6ef85a35ab97ab928f6a7e7a4dcbd6d9c5c492..4fb8c3e4ec8323cebab4ac0a5e1e2cb893a88bc3
@@@ -24,28 -24,17 +24,31 @@@ local rostermanager = require "core.ros
  local sessionmanager = require "core.sessionmanager";
  local offlinemanager = require "core.offlinemanager";
  
- 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 _core_route_stanza = core_route_stanza;
 +local core_route_stanza;
 +function core_route_stanza(origin, stanza)
 +      if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
 +              local node, host = jid_split(stanza.attr.to);
 +              host = hosts[host];
 +              if node and host and host.type == "local" then
 +                      handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
 +                      return;
 +              end
 +      end
 +      _core_route_stanza(origin, stanza);
 +end
 +
+ 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
@@@ -81,7 -48,17 +62,17 @@@ local function recalc_resource_map(user
        end
  end
  
 -function handle_normal_presence(origin, stanza)
+ 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,17 -217,14 +227,14 @@@ function handle_inbound_presence_subscr
        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) then
 -                              core_post_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"}), true); -- TODO send last activity
 +                      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_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}), true);
 +                      core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
                end
        elseif stanza.attr.type == "subscribe" then
                if rostermanager.is_contact_subscribed(node, host, from_bare) then
Simple merge
index b8d142f7c5b0c865e8d0ffa65371dd7a284386ba,25f47a9ed1ce43034199564c7e51b0105646744d..2818e3368522068d4f50391f6e4ed4e21e72ef31
@@@ -35,7 -34,7 +35,7 @@@ module:add_iq_handler("c2s", "jabber:iq
                                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, nil, host); -- 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));
index c03605536e59fe47dde473afdadc02a2f36c467f,a02c1ec4223af7174ba22257e4793d7a7af12778..d407e5da8588ff5bc8787e05fb5232c34326b03e
@@@ -27,7 -21,14 +27,13 @@@ local config = require "core.configmana
  
  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";
 -local anonymous_login = module:get_option("anonymous_login");
  
+ -- 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';
@@@ -99,16 -89,24 +109,24 @@@ local function handle_status(session, s
                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 ok, err = sm_make_authenticated(session, session.sasl_handler.username);
 -                      if ok 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)
index 273e21ce94214b585d54580b60eb658b6d59f5af,9cdf4d189a4b5ccbfc47a573c9b7bde94257458a..18c80325bbd1bce5db492b36643407d4d1b4bf65
@@@ -122,14 -129,14 +122,10 @@@ 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);
 -              while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
++              t_insert(history, st.preserialize(stanza));
 +              while #history > history_length do t_remove(history, 1) end
        end
  end
  function room_mt:broadcast_except_nick(stanza, nick)
@@@ -155,46 -162,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 -431,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,10 -888,26 +751,10 @@@ 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
 -      local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role);
 -      if not allowed then return allowed, err_type, err_condition; 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
        local p = st.presence({from = occupant_jid})
                :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
                        :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"})
@@@ -837,9 -957,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,6ec9131e5a27aa49b196326ecb6e7db986d2d245..0bcfce4b3f5ca696eb2b54f7fc2574c43a9bc36c
+++ 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.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
                          plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
        
 -      local path_sep = package.config:sub(1,1);
 -      local rel_path_start = ".."..path_sep;
 -      function prosody.resolve_relative_path(path)
 -              if path then
 -                      local is_relative;
 -                      if path_sep == "/" and path:sub(1,1) ~= "/" then
 -                              is_relative = true;
 -                      elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\") then
 -                              is_relative = true;
 -                      end
 -                      if is_relative then
 -                              return CFG_CONFIGDIR..path_sep..path;
 -                      end
 -              end
 -              return path;
 -      end
 -      
        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 -302,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"
@@@ -329,8 -346,8 +335,9 @@@ function init_data_store(
  end
  
  function prepare_to_start()
-       log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
+       log("info", "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 -465,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,9bb8d4ad7173137887bd36d9e00b8e36d3eaf245..26183b21204af1db3b7ea35f26ea8256e2157645
@@@ -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 -73,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 -161,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) };
 -      return { events = prosody.events, users = require "core.usermanager".new_null_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 -280,17 +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 -319,12 +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 -358,12 +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;
index 313b2194f19731105cf75cce1fe5f4b63556500e,313b2194f19731105cf75cce1fe5f4b63556500e..313b2194f19731105cf75cce1fe5f4b63556500e
mode 100644,100644..100755
index f3e31a7f0d7f7783a1bc08d68c0588a864108664,6e6f0949414b86d47ddf55794f0b4144aa64e0f0..7b5a5081f8d743b2891afa1b613cbbea4bd47c73
@@@ -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 2abbc53a1688dd66c2d02253676a69446a5aac85,1a2ba01e406ac1a2b7f63f81fea28c1b3116e7f6..3982118299ac562ab2ccb5b02fe306aee4dd6992
@@@ -28,15 -28,10 +28,10 @@@ plain
                return password, state;
        end
  
- plain-test:
+ plain_test:
 -      function(username, password, realm)
 +      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 self.profile.plain then
                local correct_password;
                correct_password, state = self.profile.plain(authentication, self.realm);
 -              correct = (correct_password == password);
 +              if correct_password == password then correct = true; else correct = false; end
        elseif self.profile.plain_test then
 -              correct, state = self.profile.plain_test(authentication, password, self.realm);
 +              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
index ed7d7bc385a0de3fbfd33c8b4223c0deff1db26d,de848b3da32726de6ce22a5f0a1eb235709fce7a..1340423cd932e3d3ae1117dd57bdb2ba80bd2e26
@@@ -32,9 -32,10 +32,10 @@@ module "scram
  --[[
  Supported Authentication Backends
  
- scram-{MECH}:
+ scram_{MECH}:
+       -- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_'
        function(username, realm)
 -              return stored_key, server_key, iteration_count, salt, state;
 +              return salted_password, iteration_count, salt, state;
        end
  ]]
  
@@@ -92,6 -93,23 +93,24 @@@ local function validate_username(userna
        return username;
  end
  
 -local function hashprep(hashname)
 -      return hashname:lower():gsub("-", "_");
++local function hashprep( hashname ) 
++      local hash = hashname:lower()
++      hash = hash:gsub("-", "_")
++      return hash
+ end
 -function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
++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
 -      local salted_password = Hi(hmac_sha1, password, salt, iteration_count);
 -      local stored_key = sha1(hmac_sha1(salted_password, "Client Key"))
 -      local server_key = hmac_sha1(salted_password, "Server Key");
 -      return true, stored_key, server_key
++
++      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
                                        log("debug", "Password violates SASLprep.");
                                        return "failure", "not-authorized", "Invalid password."
                                end
                                self.state.salt = generate_uuid();
                                self.state.iteration_count = default_i;
-                               self.state.salted_password = Hi(HMAC_f, password, self.state.salt, default_i);
-                       elseif self.profile["scram_"..hash_name] then
-                               local salted_password, iteration_count, salt, state = self.profile["scram-"..hash_name](self.state.name, self.realm);
+                               local succ = false;
 -                              succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count);
++                              succ, self.state.salted_password = saltedPasswordSHA1(password, self.state.salt, default_i, self.state.iteration_count);
+                               if not succ then
 -                                      log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key);
++                                      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 stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self.state.name, self.realm);
++                              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
                                
                        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 ServerKey = self.state.server_key;
 -                      local StoredKey = self.state.stored_key;
 -                      
 +                      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 ClientKey = binaryXOR(ClientSignature, base64.decode(self.state.proof))
 +                      local ClientProof     = binaryXOR(ClientKey, ClientSignature)
                        local ServerSignature = HMAC_f(ServerKey, AuthMessage)
-               
 -                      if StoredKey == H_f(ClientKey) then
 +                      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;
Simple merge