Merge with backout
authorMatthew Wild <mwild1@gmail.com>
Fri, 21 May 2010 18:45:33 +0000 (19:45 +0100)
committerMatthew Wild <mwild1@gmail.com>
Fri, 21 May 2010 18:45:33 +0000 (19:45 +0100)
13 files changed:
1  2 
core/configmanager.lua
core/eventmanager.lua
core/usermanager.lua
net/xmppclient_listener.lua
plugins/mod_offline.lua
plugins/mod_posix.lua
plugins/mod_presence.lua
plugins/muc/muc.lib.lua
prosody
prosodyctl
util/sasl.lua
util/sasl/digest-md5.lua
util/sasl/scram.lua

diff --combined core/configmanager.lua
index 0f20fd3ee124f8fc2b2a7c14b1cef243535d107f,9b03e1c7ad539eed1ed6526ebef5c3ce3164b5eb..54fb0a9ae82fdb61d22f4d1600f27855f84a3d46
@@@ -1,6 -1,6 +1,6 @@@
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  -- 
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
@@@ -30,10 -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; Component = true, component = true,
 -              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.Host(name)
+               function env.VirtualHost(name)
                        if rawget(config, name) and rawget(config[name].core, "component_module") then
                                error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
                                        name, config[name].core.component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
                        -- Needs at least one setting to logically exist :)
                        set(name or "*", "core", "defined", true);
                end
-               env.host = env.Host;
+               env.Host, env.host = env.VirtualHost, env.VirtualHost;
                
                function env.Component(name)
                        if rawget(config, name) and rawget(config[name].core, "defined") and not rawget(config[name].core, "component_module") then
diff --combined core/eventmanager.lua
index e1cc9d2e81a54db9f081aee704bbcee80fc85d9e,1f69c8e1843c3ae1472e2057cd3f5a6458792f1f..0e766c30881ffcee78a6a361ac08346b0fbec6b2
@@@ -1,6 -1,6 +1,6 @@@
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  -- 
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
  local t_insert = table.insert;
  local ipairs = ipairs;
  
 -local events = _G.prosody.events;
 -
  module "eventmanager"
  
  local event_handlers = {};
  
  function add_event_hook(name, handler)
 -      return events.add_handler(name, handler);
 +      if not event_handlers[name] then
 +              event_handlers[name] = {};
 +      end
 +      t_insert(event_handlers[name] , handler);
  end
  
  function fire_event(name, ...)
 -      return events.fire_event(name, ...);
 +      local event_handlers = event_handlers[name];
 +      if event_handlers then
 +              for name, handler in ipairs(event_handlers) do
 +                      handler(...);
 +              end
 +      end
  end
  
 -return _M;
 +return _M;
diff --combined core/usermanager.lua
index 925ac774071489eedae3dfae20bd3547d23f9452,4fef936ee9de5de04c28d49931de8d20de458b2b..42e49d389ad5f00115492a45f9ccc61bd9e72188
@@@ -1,6 -1,6 +1,6 @@@
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  --
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
@@@ -14,68 -14,136 +14,86 @@@ local ipairs = ipairs
  local hashes = require "util.hashes";
  local jid_bare = require "util.jid".bare;
  local config = require "core.configmanager";
+ local hosts = hosts;
+ local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
  
 -local prosody = _G.prosody;
 -
  module "usermanager"
  
 -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 not(require_provisioning) and 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 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 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 (datamanager.load(username, host, "accounts") or {}).password
 -      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 not(require_provisioning) and 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 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_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 3a0c65beaa98dfcbdf0a2a902edbe1b83465fb16,f3a372ae9c2f73f0e3e42a22a088bbe8022e8982..94daa2b2f7c95f8f846cf451da31ebe85620288a
@@@ -1,6 -1,6 +1,6 @@@
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  -- 
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
@@@ -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;
@@@ -33,13 -33,32 +33,32 @@@ local opt_keepalives = config.get("*", 
  local stream_callbacks = { default_ns = "jabber:client",
                streamopened = sm_streamopened, streamclosed = sm_streamclosed, handlestanza = core_process_stanza };
  
+ local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
  function stream_callbacks.error(session, error, data)
        if error == "no-stream" then
                session.log("debug", "Invalid opening stream header");
                session:close("invalid-namespace");
-       elseif session.close then
-               (session.log or log)("debug", "Client XML parse error: %s", tostring(error));
+       elseif error == "parse-error" then
+               (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
                session:close("xml-not-well-formed");
+       elseif error == "stream-error" then
+               local condition, text = "undefined-condition";
+               for child in data:children() do
+                       if child.attr.xmlns == xmlns_xmpp_streams then
+                               if child.name ~= "text" then
+                                       condition = child.name;
+                               else
+                                       text = child:get_text();
+                               end
+                               if condition ~= "undefined-condition" and text then
+                                       break;
+                               end
+                       end
+               end
+               text = condition .. (text and (" ("..text..")") or "");
+               session.log("info", "Session closed by remote with error: %s", text);
+               session:close(nil, text);
        end
  end
  
@@@ -53,23 -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)
@@@ -126,15 -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
        
diff --combined plugins/mod_offline.lua
index c74d011e7c0b2145bf66328d2c42c693e9bfcca4,24aef9ed58120f8802003f210bcf7aadf51eee35..0000000000000000000000000000000000000000
deleted file mode 100644,100644
+++ /dev/null
@@@ -1,56 -1,56 +1,0 @@@
---- Prosody IM
---- Copyright (C) 2008-2009 Matthew Wild
---- Copyright (C) 2008-2009 Waqas Hussain
---- 
---- This project is MIT/X11 licensed. Please see the
---- COPYING file in the source package for more information.
----
--
\r
- local datamanager = require "util.datamanager";\r
- local st = require "util.stanza";\r
- local datetime = require "util.datetime";\r
 -
 -local datamanager = require "util.datamanager";
 -local st = require "util.stanza";
 -local datetime = require "util.datetime";
--local ipairs = ipairs;
- local jid_split = require "util.jid".split;\r
\r
- module:add_feature("msgoffline");\r
\r
- module:hook("message/offline/store", function(event)\r
-       local origin, stanza = event.origin, event.stanza;\r
-       local to = stanza.attr.to;\r
-       local node, host;\r
-       if to then\r
-               node, host = jid_split(to)\r
-       else\r
-               node, host = origin.username, origin.host;\r
-       end\r
-       \r
-       stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();\r
-       local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));\r
-       stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;\r
-       \r
-       return true;\r
- end);\r
\r
- module:hook("message/offline/broadcast", function(event)\r
-       local origin = event.origin;\r
-       local node, host = origin.username, origin.host;\r
-       \r
-       local data = datamanager.list_load(node, host, "offline");\r
-       if not data then return true; end\r
-       for _, stanza in ipairs(data) do\r
-               stanza = st.deserialize(stanza);\r
-               stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203\r
-               stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)\r
-               stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;\r
-               origin.send(stanza);\r
-       end\r
-       return true;\r
- end);\r
\r
- module:hook("message/offline/delete", function(event)\r
-       local origin = event.origin;\r
-       local node, host = origin.username, origin.host;\r
\r
-       return datamanager.list_store(node, host, "offline", nil);\r
- end);\r
 -local jid_split = require "util.jid".split;
 -
 -module:add_feature("msgoffline");
 -
 -module:hook("message/offline/store", function(event)
 -      local origin, stanza = event.origin, event.stanza;
 -      local to = stanza.attr.to;
 -      local node, host;
 -      if to then
 -              node, host = jid_split(to)
 -      else
 -              node, host = origin.username, origin.host;
 -      end
 -      
 -      stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
 -      local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
 -      stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
 -      
 -      return true;
 -end);
 -
 -module:hook("message/offline/broadcast", function(event)
 -      local origin = event.origin;
 -      local node, host = origin.username, origin.host;
 -      
 -      local data = datamanager.list_load(node, host, "offline");
 -      if not data then return true; end
 -      for _, stanza in ipairs(data) do
 -              stanza = st.deserialize(stanza);
 -              stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
 -              stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
 -              stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
 -              origin.send(stanza);
 -      end
 -      return true;
 -end);
 -
 -module:hook("message/offline/delete", function(event)
 -      local origin = event.origin;
 -      local node, host = origin.username, origin.host;
 -
 -      return datamanager.list_store(node, host, "offline", nil);
 -end);
diff --combined plugins/mod_posix.lua
index 55d52ccda1e09419a2d92b88e391b421de463562,52b1e0e6e510533ca87987ce01da71b0a1d02177..c38f7eba75399bb9d9e421595f278454c77ec573
@@@ -1,6 -1,6 +1,6 @@@
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  -- 
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
@@@ -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;
@@@ -82,6 -82,7 +82,7 @@@ local function write_pidfile(
        end
        pidfile = module:get_option("pidfile");
        if pidfile then
+               local err;
                local mode = stat(pidfile) and "r+" or "w+";
                pidfile_handle, err = io.open(pidfile, mode);
                if not pidfile_handle then
@@@ -140,7 -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();
diff --combined plugins/mod_presence.lua
index a39d9c19cd87d2de3b5d0e5ccb09e0cc17d4ca5b,5ad3bfdf28d07c6f108ef7b9939c320e15ca5590..108ab0d309cc355a034c7e14ebe1516a308d7722
@@@ -1,6 -1,6 +1,6 @@@
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  -- 
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
@@@ -38,23 -38,42 +38,23 @@@ function core_route_stanza(origin, stan
        _core_route_stanza(origin, stanza);
  end
  
 -local select_top_resources;
 -local bare_message_delivery_policy = module:get_option("bare_message_delivery_policy") or "priority";
 -if bare_message_delivery_policy == "broadcast" then
 -      function select_top_resources(user)
 -              local recipients = {};
 -              for _, session in pairs(user.sessions) do -- find resources with non-negative priority
 +local function select_top_resources(user)
 +      local priority = 0;
 +      local recipients = {};
 +      for _, session in pairs(user.sessions) do -- find resource with greatest priority
 +              if session.presence then
 +                      -- TODO check active privacy list for session
                        local p = session.priority;
 -                      if p and p >= 0 then
 +                      if p > priority then
 +                              priority = p;
 +                              recipients = {session};
 +                      elseif p == priority then
                                t_insert(recipients, session);
                        end
                end
 -              return recipients;
 -      end
 -else
 -      if bare_message_delivery_policy ~= "priority" then
 -              module:log("warn", "Invalid value for config option bare_message_delivery_policy");
 -      end
 -      function select_top_resources(user)
 -              local priority = 0;
 -              local recipients = {};
 -              for _, session in pairs(user.sessions) do -- find resource with greatest priority
 -                      if session.presence then
 -                              -- TODO check active privacy list for session
 -                              local p = session.priority;
 -                              if p > priority then
 -                                      priority = p;
 -                                      recipients = {session};
 -                              elseif p == priority then
 -                                      t_insert(recipients, session);
 -                              end
 -                      end
 -              end
 -              return recipients;
        end
 +      return recipients;
  end
 -
  local function recalc_resource_map(user)
        if user then
                user.top_resources = select_top_resources(user);
diff --combined plugins/muc/muc.lib.lua
index ad45bbfd462caed12d70937cd0d173a646c98765,273e21ce94214b585d54580b60eb658b6d59f5af..18c80325bbd1bce5db492b36643407d4d1b4bf65
@@@ -1,6 -1,6 +1,6 @@@
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  -- 
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
@@@ -122,9 -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
@@@ -151,12 -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
@@@ -319,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";
@@@ -514,6 -552,9 +519,9 @@@ function room_mt:handle_to_room(origin
                                        if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
                                                local occupant = self._occupants[self.jid.."/"..item.attr.nick];
                                                if occupant then item.attr.jid = occupant.jid; end
+                                       elseif not item.attr.nick and item.attr.jid then
+                                               local nick = self._jid_nick[item.attr.jid];
+                                               if nick then item.attr.nick = select(3, jid_split(nick)); end
                                        end
                                        local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
                                        if item.attr.affiliation and item.attr.jid and not item.attr.role then
@@@ -743,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
@@@ -796,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 --combined prosody
index 49580bd91deafd9b36a0eac4acaf6cb8a0319f48,517762f3c438d4d7061739f7a8f3ec95e86d3175..e4e821050d60829c9b8c652d33e003ec7bde470c
+++ b/prosody
@@@ -1,7 -1,7 +1,7 @@@
  #!/usr/bin/env lua
  -- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
  -- 
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
@@@ -22,9 -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"
  
@@@ -123,15 -124,38 +123,42 @@@ function sandbox_require(
        end
  end
  
+ function set_function_metatable()
+       local mt = {};
+       function mt.__index(f, upvalue)
+               local i, name, value = 0;
+               repeat
+                       i = i + 1;
+                       name, value = debug.getupvalue(f, i);
+               until name == upvalue or name == nil;
+               return value;
+       end
+       function mt.__newindex(f, upvalue, value)
+               local i, name = 0;
+               repeat
+                       i = i + 1;
+                       name = debug.getupvalue(f, i);
+               until name == upvalue or name == nil;
+               if name then
+                       debug.setupvalue(f, i, value);
+               end
+       end
+       function mt.__tostring(f)
+               local info = debug.getinfo(f);
+               return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
+       end
+       debug.setmetatable(function() end, mt);
+ end
  function init_global_state()
        bare_sessions = {};
        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
  
@@@ -264,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"
@@@ -310,7 -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
@@@ -418,6 -438,7 +445,7 @@@ read_config()
  init_logging();
  check_dependencies();
  sandbox_require();
+ set_function_metatable();
  load_libraries();
  init_global_state();
  read_version();
@@@ -427,14 -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 --combined prosodyctl
index 9d2df69ede44490de53a6b36dca856aa04595638,c0cd89a0c985c454332cdb8e77e9020f8d4a6d78..ccc1e2f9cfe5d86f8d3d55353d3842911aad35be
@@@ -29,6 -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
@@@ -63,8 -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);
  
@@@ -114,12 -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"
@@@ -231,16 -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
        
@@@ -269,6 -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;
@@@ -302,6 -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;
@@@ -433,6 -472,19 +433,19 @@@ function commands.stop(arg
        return 1;
  end
  
+ function commands.restart(arg)
+       if arg[1] == "--help" then
+               show_usage([[restart]], [[Restart a running Prosody server]]);
+               return 1;
+       end
+       
+       local ret = commands.stop(arg);
+       if ret == 0 then
+               ret = commands.start(arg);
+       end
+       return ret;
+ end
  -- ejabberdctl compatibility
  
  function commands.register(arg)
@@@ -491,6 -543,10 +504,10 @@@ local http_errors = 
        };
  
  function commands.addplugin(arg)
+       if not arg[1] or arg[1] == "--help" then
+               show_usage("addplugin URL", "Download and install a plugin from a URL");
+               return 1;
+       end
        local url = arg[1];
        if url:match("^http://") then
                local http = require "socket.http";
@@@ -562,8 -618,8 +579,8 @@@ if not commands[command] then -- Show h
        print("");
        print("Where COMMAND may be one of:\n");
  
-       local hidden_commands = require "util.set".new{ "register", "unregister" };
-       local commands_order = { "adduser", "passwd", "deluser" };
+       local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" };
+       local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart" };
  
        local done = {};
  
diff --combined util/sasl.lua
index 9c8fff7804a28edd6b7e4e430b846d3d23093696,306acc0c590998be8eb151e3ddc7cdf13ad12058..eb71956b692aa5942bf0690b4787865132f588ce
@@@ -1,5 -1,5 +1,5 @@@
  -- sasl.lua v0.4
- -- Copyright (C) 2008-2009 Tobias Markmann
+ -- Copyright (C) 2008-2010 Tobias Markmann
  --
  --    All rights reserved.
  --
@@@ -41,27 -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 = {};
@@@ -143,7 -122,7 +143,7 @@@ function method:process(message
  end
  
  -- load the mechanisms
- load_mechs = {"plain", "digest-md5", "anonymous", "scram"}
+ local load_mechs = {"plain", "digest-md5", "anonymous", "scram"}
  for _, mech in ipairs(load_mechs) do
        local name = "util.sasl."..mech;
        local m = require(name);
diff --combined util/sasl/digest-md5.lua
index 5b8f5c8a6ab4f6b28a73caee6f0981a79fb94964,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,14 -29,27 +29,12 @@@ 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
  
        local function serialize(message)
                local data = ""
  
-               if type(message) ~= "table" then error("serialize needs an argument of type table.") end
                -- testing all possible values
                if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
                if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
diff --combined util/sasl/scram.lua
index 4f80052908cd7148b8d123edfaa5e61f36d02a48,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.
  --
@@@ -15,7 -15,6 +15,6 @@@ local s_match = string.match
  local type = type
  local string = string
  local base64 = require "util.encodings".base64;
- local xor = require "bit".bxor
  local hmac_sha1 = require "util.hmac".sha1;
  local sha1 = require "util.hashes".sha1;
  local generate_uuid = require "util.uuid".generate;
@@@ -29,6 -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 )
@@@ -83,77 -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;