Merge 0.7->0.8
authorMatthew Wild <mwild1@gmail.com>
Thu, 2 Jun 2011 14:28:12 +0000 (15:28 +0100)
committerMatthew Wild <mwild1@gmail.com>
Thu, 2 Jun 2011 14:28:12 +0000 (15:28 +0100)
15 files changed:
1  2 
certs/openssl.cnf
core/certmanager.lua
core/s2smanager.lua
core/sessionmanager.lua
net/dns.lua
net/server_select.lua
plugins/mod_admin_telnet.lua
plugins/mod_bosh.lua
plugins/mod_dialback.lua
plugins/mod_register.lua
plugins/mod_roster.lua
plugins/mod_saslauth.lua
plugins/muc/muc.lib.lua
util-src/encodings.c
util/xmppstream.lua

index db1640b9306f3290e2c9541ed91bbe244e960c61,db1640b9306f3290e2c9541ed91bbe244e960c61..44fc042415d2154c3c6b66263c96f68782a4f586
@@@ -43,10 -43,10 +43,10 @@@ subjectAltName   = @subject_alternative
  # See http://tools.ietf.org/html/draft-ietf-xmpp-3920bis#section-13.7.1.2 for more info.
  
  DNS.0       =                                           example.com
--otherName.0 =                 xmppAddr;FORMAT:UTF8,UTF8:example.com
++otherName.0 =                             xmppAddr;UTF8:example.com
  otherName.1 =            SRVName;IA5STRING:_xmpp-client.example.com
  otherName.2 =            SRVName;IA5STRING:_xmpp-server.example.com
  
  DNS.1       =                                conference.example.com
--otherName.3 =      xmppAddr;FORMAT:UTF8,UTF8:conference.example.com
++otherName.3 =                  xmppAddr;UTF8:conference.example.com
  otherName.4 = SRVName;IA5STRING:_xmpp-server.conference.example.com
index 0dc0bfd4bec49004bf6bd3dd84409572f5feb04b,3dd06585fa667e3cd2d96f8dc17eb43938861d30..7f1ca42eebaad0676caf017608b3f6c12a4d0e7d
@@@ -19,60 -9,51 +19,58 @@@ local config_path = prosody.paths.confi
  
  module "certmanager"
  
 --- These are the defaults if not overridden in the config
 -local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
 -local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
 -
 -local default_ssl_ctx_mt = { __index = default_ssl_ctx };
 -local default_ssl_ctx_in_mt = { __index = default_ssl_ctx_in };
 -
  -- Global SSL options if not overridden per-host
  local default_ssl_config = configmanager.get("*", "core", "ssl");
- local default_verify = (ssl and ssl.x509 and { "peer", "client_once", "continue", "ignore_purpose" }) or "none";
- local default_options = { "no_sslv2" };
 +local default_capath = "/etc/ssl/certs";
  
 -function create_context(host, mode, config)
 -      local ssl_config = config and config.core.ssl or default_ssl_config;
 -      if ssl and ssl_config then
 -              local ctx, err = ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt));
 -              if not ctx then
 -                      err = err or "invalid ssl config"
 -                      local file = err:match("^error loading (.-) %(");
 -                      if file then
 -                              if file == "private key" then
 -                                      file = ssl_config.key or "your private key";
 -                              elseif file == "certificate" then
 -                                      file = ssl_config.certificate or "your certificate file";
 -                              end
 -                              local reason = err:match("%((.+)%)$") or "some reason";
 -                              if reason == "Permission denied" then
 -                                      reason = "Check that the permissions allow Prosody to read this file.";
 -                              elseif reason == "No such file or directory" then
 -                                      reason = "Check that the path is correct, and the file exists.";
 -                              elseif reason == "system lib" then
 -                                      reason = "Previous error (see logs), or other system error.";
 -                              elseif reason == "(null)" or not reason then
 -                                      reason = "Check that the file exists and the permissions are correct";
 -                              else
 -                                      reason = "Reason: "..tostring(reason):lower();
 -                              end
 -                              log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
 +function create_context(host, mode, user_ssl_config)
 +      user_ssl_config = user_ssl_config or default_ssl_config;
 +
 +      if not ssl then return nil, "LuaSec (required for encryption) was not found"; end
 +      if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end
 +      
 +      local ssl_config = {
 +              mode = mode;
 +              protocol = user_ssl_config.protocol or "sslv23";
 +              key = resolve_path(config_path, user_ssl_config.key);
 +              password = user_ssl_config.password;
 +              certificate = resolve_path(config_path, user_ssl_config.certificate);
 +              capath = resolve_path(config_path, user_ssl_config.capath or default_capath);
 +              cafile = resolve_path(config_path, user_ssl_config.cafile);
-               verify = user_ssl_config.verify or default_verify;
-               options = user_ssl_config.options or default_options;
++              verify = user_ssl_config.verify or "none";
++              options = user_ssl_config.options or "no_sslv2";
 +              ciphers = user_ssl_config.ciphers;
 +              depth = user_ssl_config.depth;
 +      };
 +
 +      local ctx, err = ssl_newcontext(ssl_config);
 +      if not ctx then
 +              err = err or "invalid ssl config"
 +              local file = err:match("^error loading (.-) %(");
 +              if file then
 +                      if file == "private key" then
 +                              file = ssl_config.key or "your private key";
 +                      elseif file == "certificate" then
 +                              file = ssl_config.certificate or "your certificate file";
 +                      end
 +                      local reason = err:match("%((.+)%)$") or "some reason";
 +                      if reason == "Permission denied" then
 +                              reason = "Check that the permissions allow Prosody to read this file.";
 +                      elseif reason == "No such file or directory" then
 +                              reason = "Check that the path is correct, and the file exists.";
 +                      elseif reason == "system lib" then
 +                              reason = "Previous error (see logs), or other system error.";
 +                      elseif reason == "(null)" or not reason then
 +                              reason = "Check that the file exists and the permissions are correct";
                        else
 -                              log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
 +                              reason = "Reason: "..tostring(reason):lower();
                        end
 -              end
 -              return ctx, err;
 -      elseif not ssl then
 -              return nil, "LuaSec (required for encryption) was not found";
 +                      log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
 +              else
 +                      log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
 +              end
        end
 -      return nil, "No SSL/TLS configuration present for "..host;
 +      return ctx, err;
  end
  
  function reload_ssl_config()
index f49fae9dc5ed54afb9f0cee325f0cd4ecaff5a21,0c29da143bdde35a5e92ac9b1f9962d3953d8a15..fd9a72d06a258b54309b89e0d0946504461319d5
@@@ -27,9 -28,8 +27,8 @@@ local modulemanager = require "core.mod
  local st = require "stanza";
  local stanza = st.stanza;
  local nameprep = require "util.encodings".stringprep.nameprep;
- local cert_verify_identity = require "util.x509".verify_identity;
  
 -local fire_event = require "core.eventmanager".fire_event;
 +local fire_event = prosody.events.fire_event;
  local uuid_gen = require "util.uuid".generate;
  
  local logger_init = require "util.logger".init;
@@@ -441,26 -375,11 +409,23 @@@ function streamopened(session, attr
        
                session.streamid = uuid_gen();
                (session.log or log)("debug", "incoming s2s received <stream:stream>");
 -              if session.to_host and not hosts[session.to_host] then
 -                      -- Attempting to connect to a host we don't serve
 -                      session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
 -                      return;
 +              if session.to_host then
 +                      if not hosts[session.to_host] then
 +                              -- Attempting to connect to a host we don't serve
 +                              session:close({
 +                                      condition = "host-unknown";
 +                                      text = "This host does not serve "..session.to_host
 +                              });
 +                              return;
 +                      elseif hosts[session.to_host].disallow_s2s then
 +                              -- Attempting to connect to a host that disallows s2s
 +                              session:close({
 +                                      condition = "policy-violation";
 +                                      text = "Server-to-server communication is not allowed to this host";
 +                              });
 +                              return;
 +                      end
                end
-               if session.secure and not session.cert_chain_status then check_cert_status(session); end
                send("<?xml version='1.0'?>");
                send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
                                ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
@@@ -625,7 -529,7 +588,7 @@@ en
  
  function destroy_session(session, reason)
        if session.destroyed then return; end
-       (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or ""));
 -      (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
++      (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
        
        if session.direction == "outgoing" then
                hosts[session.from_host].s2sout[session.to_host] = nil;
Simple merge
diff --cc net/dns.lua
index 3f1cb4f687ffc917b53e2ca5dfadc7aba65082ca,c0de97fd426db6f3573332cda332072028bedc7c..c905f56c5d612565d416a2fcd85321cc64f58b7a
@@@ -482,12 -434,15 +474,12 @@@ function resolver:SRV(rr)    -- - - - 
          rr.srv.target   = self:name();
  end
  
 -
 -function SRV_tostring(rr)    -- - - - - - - - - - - - - - - - - - SRV_tostring
 -      local s = rr.srv;
 -      return string.format( '%5d %5d %5d %s', s.priority, s.weight, s.port, s.target );
 +function resolver:PTR(rr)
 +      rr.ptr = self:name();
  end
  
 -
  function resolver:TXT(rr)    -- - - - - - - - - - - - - - - - - - - - - -  TXT
-       rr.txt = self:sub (self:byte());
+       rr.txt = self:sub (rr.rdlength);
  end
  
  
Simple merge
index da40f57e0a675b871108e19dc7007da15c8003e8,da40f57e0a675b871108e19dc7007da15c8003e8..712e9eb731847f4563ca92fe970578aee79d563f
@@@ -19,7 -19,7 +19,6 @@@ local console_listener = { default_por
  require "util.iterators";
  local jid_bare = require "util.jid".bare;
  local set, array = require "util.set", require "util.array";
--local cert_verify_identity = require "util.x509".verify_identity;
  
  local commands = {};
  local def_env = {};
@@@ -499,7 -499,7 +498,7 @@@ function def_env.s2s:show(match_jid
                for remotehost, session in pairs(host_session.s2sout) do
                        if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
                                count_out = count_out + 1;
--                              print("    "..host.." -> "..remotehost..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
++                              print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
                                if session.sendq then
                                        print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
                                end
                                -- Pft! is what I say to list comprehensions
                                or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
                                count_in = count_in + 1;
--                              print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
++                              print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
                                if session.type == "s2sin_unauthed" then
                                                print("        Connection not yet authenticated");
                                end
        return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
  end
  
--local function print_subject(print, subject)
--      for _, entry in ipairs(subject) do
--              print(
--                      ("    %s: %q"):format(
--                              entry.name or entry.oid,
--                              entry.value:gsub("[\r\n%z%c]", " ")
--                      )
--              );
--      end
--end
--
--function def_env.s2s:showcert(domain)
--      local ser = require "util.serialization".serialize;
--      local print = self.session.print;
--      local domain_sessions = set.new(array.collect(keys(incoming_s2s)))
--              /function(session) return session.from_host == domain; end;
--      for local_host in values(prosody.hosts) do
--              local s2sout = local_host.s2sout;
--              if s2sout and s2sout[domain] then
--                      domain_sessions:add(s2sout[domain]);
--              end
--      end
--      local cert_set = {};
--      for session in domain_sessions do
--              local conn = session.conn;
--              conn = conn and conn:socket();
--              if not conn.getpeercertificate then
--                      if conn.dohandshake then
--                              error("This version of LuaSec does not support certificate viewing");
--                      end
--              else
--                      local cert = conn:getpeercertificate();
--                      if cert then
--                              local digest = cert:digest("sha1");
--                              if not cert_set[digest] then
--                                      local chain_valid, chain_err = conn:getpeerchainvalid();
--                                      cert_set[digest] = {
--                                              {
--                                                from = session.from_host,
--                                                to = session.to_host,
--                                                direction = session.direction
--                                              };
--                                              chain_valid = chain_valid;
--                                              chain_err = chain_err;
--                                              cert = cert;
--                                      };
--                              else
--                                      table.insert(cert_set[digest], {
--                                              from = session.from_host,
--                                              to = session.to_host,
--                                              direction = session.direction
--                                      });
--                              end
--                      end
--              end
--      end
--      local domain_certs = array.collect(values(cert_set));
--      -- Phew. We now have a array of unique certificates presented by domain.
--      local print = self.session.print;
--      local n_certs = #domain_certs;
--      
--      if n_certs == 0 then
--              return "No certificates found for "..domain;
--      end
--      
--      local function _capitalize_and_colon(byte)
--              return string.upper(byte)..":";
--      end
--      local function pretty_fingerprint(hash)
--              return hash:gsub("..", _capitalize_and_colon):sub(1, -2);
--      end
--      
--      for cert_info in values(domain_certs) do
--              local cert = cert_info.cert;
--              print("---")
--              print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1")));
--              print("");
--              local n_streams = #cert_info;
--              print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":");
--              for _, stream in ipairs(cert_info) do
--                      if stream.direction == "incoming" then
--                              print("    "..stream.to.." <- "..stream.from);
--                      else
--                              print("    "..stream.from.." -> "..stream.to);
--                      end
--              end
--              print("");
--              local chain_valid, err = cert_info.chain_valid, cert_info.chain_err;
--              local valid_identity = cert_verify_identity(domain, "xmpp-server", cert);
--              print("Trusted certificate: "..(chain_valid and "Yes" or ("No ("..err..")")));
--              print("Issuer: ");
--              print_subject(print, cert:issuer());
--              print("");
--              print("Valid for "..domain..": "..(valid_identity and "Yes" or "No"));
--              print("Subject:");
--              print_subject(print, cert:subject());
--      end
--      print("---");
--      return ("Showing "..n_certs.." certificate"
--              ..(n_certs==1 and "" or "s")
--              .." presented by "..domain..".");
--end
--
  function def_env.s2s:close(from, to)
        local print, count = self.session.print, 0;
        
index c2c7eae98d1b7174f749d2291b421d3a1b1f2a5b,66a79785dea936ba77779028c768039ccdb8fcb0..a747f3cbd4a09d121e08189c327faa13bb0dedf2
@@@ -160,56 -132,25 +160,51 @@@ function handle_request(method, body, r
                                request.reply_before = os_time() + session.bosh_wait;
                                waiting_requests[request] = true;
                        end
 -                      if inactive_sessions[session] then
 -                              -- Session was marked as inactive, since we have
 -                              -- a request open now, unmark it
 -                              inactive_sessions[session] = nil;
 -                      end
                end
                
-               if session.bosh_terminate then
-                       session.log("debug", "Closing session with %d requests open", #session.requests);
-                       session:close();
-                       return nil;
-               else
-                       return true; -- Inform httpserver we shall reply later
-               end
+               return true; -- Inform httpserver we shall reply later
        end
  end
  
  
  local function bosh_reset_stream(session) session.notopen = true; end
  
 +local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" };
 +
  local function bosh_close_stream(session, reason)
        (session.log or log)("info", "BOSH client disconnected");
 -      session_close_reply.attr.condition = reason;
 +      
 +      local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
 +              ["xmlns:streams"] = xmlns_streams });
 +      
 +
 +      if reason then
 +              close_reply.attr.condition = "remote-stream-error";
 +              if type(reason) == "string" then -- assume stream error
 +                      close_reply:tag("stream:error")
 +                              :tag(reason, {xmlns = xmlns_xmpp_streams});
 +              elseif type(reason) == "table" then
 +                      if reason.condition then
 +                              close_reply:tag("stream:error")
 +                                      :tag(reason.condition, stream_xmlns_attr):up();
 +                              if reason.text then
 +                                      close_reply:tag("text", stream_xmlns_attr):text(reason.text):up();
 +                              end
 +                              if reason.extra then
 +                                      close_reply:add_child(reason.extra);
 +                              end
 +                      elseif reason.name then -- a stanza
 +                              close_reply = reason;
 +                      end
 +              end
 +              log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply));
 +      end
 +
 +      local session_close_response = { headers = default_headers, body = tostring(close_reply) };
 +
++      --FIXME: Quite sure we shouldn't reply to all requests with the error
        for _, held_request in ipairs(session.requests) do
 -              held_request:send(session_close_reply);
 +              held_request:send(session_close_response);
                held_request:destroy();
        end
        sessions[session.sid]  = nil;
index 91291e2404ff90de8df874b99f0125aa81bb9152,91291e2404ff90de8df874b99f0125aa81bb9152..8c80dce6a57e87cd3d6bf158ca889b1404b40ec2
@@@ -131,22 -131,22 +131,12 @@@ module:hook("stanza/jabber:server:dialb
        end
  end);
  
--module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza)
--      if origin.external_auth == "failed" then
--              module:log("debug", "SASL EXTERNAL failed, falling back to dialback");
--              s2s_initiate_dialback(origin);
--              return true;
--      end
--end, 100);
--
  module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
--      if not origin.external_auth or origin.external_auth == "failed" then
--              s2s_initiate_dialback(origin);
--              return true;
--      end
++      s2s_initiate_dialback(origin);
++      return true;
  end, 100);
  
  -- Offer dialback to incoming hosts
  module:hook("s2s-stream-features", function (data)
--      data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up();
++      data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up();
  end);
index 8a818d027ca0f71c7a2f95770a22af5f8f530d2d,2818e3368522068d4f50391f6e4ed4e21e72ef31..25c09dfa69a2c0013bd5e474a04ccb50348c7913
@@@ -13,86 -13,72 +13,73 @@@ local datamanager = require "util.datam
  local usermanager_user_exists = require "core.usermanager".user_exists;
  local usermanager_create_user = require "core.usermanager".create_user;
  local usermanager_set_password = require "core.usermanager".set_password;
 -local datamanager_store = require "util.datamanager".store;
 +local usermanager_delete_user = require "core.usermanager".delete_user;
  local os_time = os.time;
  local nodeprep = require "util.encodings".stringprep.nodeprep;
- local allow_registration = module:get_option_boolean("allow_registration", false);
 +local jid_bare = require "util.jid".bare;
 +
 +local compat = module:get_option_boolean("registration_compat", true);
  
  module:add_feature("jabber:iq:register");
  
- local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up();
- module:hook("stream-features", function(event)
-         local session, features = event.origin, event.features;
-       -- Advertise registration to unauthorized clients only.
-       if not(allow_registration) or session.type ~= "c2s_unauthed" then
-               return
-       end
-       features:add_child(register_stream_feature);
- end);
 -module:add_iq_handler("c2s", "jabber:iq:register", function (session, stanza)
 -      if stanza.tags[1].name == "query" then
 -              local query = stanza.tags[1];
 -              if stanza.attr.type == "get" then
 -                      local reply = st.reply(stanza);
 -                      reply:tag("query", {xmlns = "jabber:iq:register"})
 -                              :tag("registered"):up()
 -                              :tag("username"):text(session.username):up()
 -                              :tag("password"):up();
 -                      session.send(reply);
 -              elseif stanza.attr.type == "set" then
 -                      if query.tags[1] and query.tags[1].name == "remove" then
 -                              -- TODO delete user auth data, send iq response, kick all user resources with a <not-authorized/>, delete all user data
 -                              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
 -                              -- 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));
 -                              local roster = session.roster;
 -                              for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources
 -                                      session:close({condition = "not-authorized", text = "Account deleted"});
 -                              end
 -                              -- TODO datamanager should be able to delete all user data itself
 -                              datamanager.store(username, host, "vcard", nil);
 -                              datamanager.store(username, host, "private", nil);
 -                              datamanager.list_store(username, host, "offline", nil);
 -                              local bare = username.."@"..host;
 -                              for jid, item in pairs(roster) do
 -                                      if jid and jid ~= "pending" then
 -                                              if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
 -                                                      core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
 -                                              end
 -                                              if item.subscription == "both" or item.subscription == "to" or item.ask then
 -                                                      core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
 -                                              end
 +local function handle_registration_stanza(event)
 +      local session, stanza = event.origin, event.stanza;
 +
 +      local query = stanza.tags[1];
 +      if stanza.attr.type == "get" then
 +              local reply = st.reply(stanza);
 +              reply:tag("query", {xmlns = "jabber:iq:register"})
 +                      :tag("registered"):up()
 +                      :tag("username"):text(session.username):up()
 +                      :tag("password"):up();
 +              session.send(reply);
 +      else -- stanza.attr.type == "set"
 +              if query.tags[1] and query.tags[1].name == "remove" then
 +                      -- TODO delete user auth data, send iq response, kick all user resources with a <not-authorized/>, delete all user data
 +                      local username, host = session.username, session.host;
 +                      
 +                      local ok, err = usermanager_delete_user(username, host);
 +                      
 +                      if not ok then
 +                              module:log("debug", "Removing user account %s@%s failed: %s", username, host, err);
 +                              session.send(st.error_reply(stanza, "cancel", "service-unavailable", err));
 +                              return true;
 +                      end
 +                      
 +                      session.send(st.reply(stanza));
 +                      local roster = session.roster;
 +                      for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources
 +                              session:close({condition = "not-authorized", text = "Account deleted"});
 +                      end
 +                      -- TODO datamanager should be able to delete all user data itself
 +                      datamanager.store(username, host, "vcard", nil);
 +                      datamanager.store(username, host, "private", nil);
 +                      datamanager.list_store(username, host, "offline", nil);
 +                      local bare = username.."@"..host;
 +                      for jid, item in pairs(roster) do
 +                              if jid and jid ~= "pending" then
 +                                      if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
 +                                              core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
 +                                      end
 +                                      if item.subscription == "both" or item.subscription == "to" or item.ask then
 +                                              core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
                                        end
                                end
 -                              datamanager.store(username, host, "roster", nil);
 -                              datamanager.store(username, host, "privacy", nil);
 -                              datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end
 -                              module:log("info", "User removed their account: %s@%s", username, host);
 -                              module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
 -                      else
 -                              local username = query:child_with_name("username");
 -                              local password = query:child_with_name("password");
 -                              if username and password then
 -                                      -- FIXME shouldn't use table.concat
 -                                      username = nodeprep(table.concat(username));
 -                                      password = table.concat(password);
 -                                      if username == session.username then
 -                                              if usermanager_set_password(username, session.host, password) then
 -                                                      session.send(st.reply(stanza));
 -                                              else
 -                                                      -- TODO unable to write file, file may be locked, etc, what's the correct error?
 -                                                      session.send(st.error_reply(stanza, "wait", "internal-server-error"));
 -                                              end
 +                      end
 +                      datamanager.store(username, host, "roster", nil);
 +                      datamanager.store(username, host, "privacy", nil);
 +                      module:log("info", "User removed their account: %s@%s", username, host);
 +                      module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
 +              else
 +                      local username = nodeprep(query:get_child("username"):get_text());
 +                      local password = query:get_child("password"):get_text();
 +                      if username and password then
 +                              if username == session.username then
 +                                      if usermanager_set_password(username, password, session.host) then
 +                                              session.send(st.reply(stanza));
                                        else
 -                                              session.send(st.error_reply(stanza, "modify", "bad-request"));
 +                                              -- TODO unable to write file, file may be locked, etc, what's the correct error?
 +                                              session.send(st.error_reply(stanza, "wait", "internal-server-error"));
                                        end
                                else
                                        session.send(st.error_reply(stanza, "modify", "bad-request"));
@@@ -124,12 -99,10 +111,12 @@@ local blacklisted_ips = module:get_opti
  for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end
  for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end
  
 -module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, stanza)
 -      if module:get_option("allow_registration") == false then
 +module:hook("stanza/iq/jabber:iq:register:query", function(event)
 +      local session, stanza = event.origin, event.stanza;
 +
-       if not(allow_registration) or session.type ~= "c2s_unauthed" then
++      if module:get_option("allow_registration") == false or session.type ~= "c2s_unauthed" then
                session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 -      elseif stanza.tags[1].name == "query" then
 +      else
                local query = stanza.tags[1];
                if stanza.attr.type == "get" then
                        local reply = st.reply(stanza);
Simple merge
index 1c0d0673fc1ebeafb92f81ea24fd2d52a616581d,d407e5da8588ff5bc8787e05fb5232c34326b03e..4906d01f6fa9f6d52ad7f3448268c13615b879cf
  local st = require "util.stanza";
  local sm_bind_resource = require "core.sessionmanager".bind_resource;
  local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
- local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
  local base64 = require "util.encodings".base64;
  
- local cert_verify_identity = require "util.x509".verify_identity;
  local nodeprep = require "util.encodings".stringprep.nodeprep;
 -local datamanager_load = require "util.datamanager".load;
 -local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
 -local usermanager_get_supported_methods = require "core.usermanager".get_supported_methods;
 -local usermanager_user_exists = require "core.usermanager".user_exists;
 -local usermanager_get_password = require "core.usermanager".get_password;
 -local t_concat, t_insert = table.concat, table.insert;
 +local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
  local tostring = tostring;
 -local jid_split = require "util.jid".split;
 -local md5 = require "util.hashes".md5;
 -local config = require "core.configmanager";
  
  local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
 -local sasl_backend = module:get_option("sasl_backend") or "builtin";
 -
 --- Cyrus config options
 -local require_provisioning = module:get_option("cyrus_require_provisioning") or false;
 -local cyrus_service_realm = module:get_option("cyrus_service_realm");
 -local cyrus_service_name = module:get_option("cyrus_service_name");
 -local cyrus_application_name = module:get_option("cyrus_application_name");
 +local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth")
  
  local log = module._log;
  
@@@ -81,160 -164,11 +78,45 @@@ local function sasl_process_cdata(sessi
        local s = build_reply(status, ret, err_msg);
        log("debug", "sasl reply: %s", tostring(s));
        session.send(s);
 +      return true;
  end
  
- module:hook_stanza(xmlns_sasl, "success", function (session, stanza)
-       if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
-       module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host);
-       session.external_auth = "succeeded"
-       session:reset_stream();
-       local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
-                                   ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
-       session.sends2s("<?xml version='1.0'?>");
-       session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
-       s2s_make_authenticated(session, session.to_host);
-       return true;
- end)
- module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
-       if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
-       module:log("info", "SASL EXTERNAL with %s failed", session.to_host)
-       -- TODO: Log the failure reason
-       session.external_auth = "failed"
- end, 500)
- module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
-       -- TODO: Dialback wasn't loaded.  Do something useful.
- end, 90)
- module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
-       if session.type ~= "s2sout_unauthed" or not session.secure then return; end
-       local mechanisms = stanza:get_child("mechanisms", xmlns_sasl)
-       if mechanisms then
-               for mech in mechanisms:childtags() do
-                       if mech[1] == "EXTERNAL" then
-                               module:log("debug", "Initiating SASL EXTERNAL with %s", session.to_host);
-                               local reply = st.stanza("auth", {xmlns = xmlns_sasl, mechanism = "EXTERNAL"});
-                               reply:text(base64.encode(session.from_host))
-                               session.sends2s(reply)
-                               session.external_auth = "attempting"
-                               return true
-                       end
-               end
-       end
- end, 150);
- local function s2s_external_auth(session, stanza)
-       local mechanism = stanza.attr.mechanism;
-       if not session.secure then
-               if mechanism == "EXTERNAL" then
-                       session.sends2s(build_reply("failure", "encryption-required"))
-               else
-                       session.sends2s(build_reply("failure", "invalid-mechanism"))
-               end
-               return true;
-       end
-       if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then
-               session.sends2s(build_reply("failure", "invalid-mechanism"))
-               return true;
-       end
-       local text = stanza[1]
-       if not text then
-               session.sends2s(build_reply("failure", "malformed-request"))
-               return true
-       end
-       -- Either the value is "=" and we've already verified the external
-       -- cert identity, or the value is a string and either matches the
-       -- from_host (
-       text = base64.decode(text)
-       if not text then
-               session.sends2s(build_reply("failure", "incorrect-encoding"))
-               return true;
-       end
-       if session.cert_identity_status == "valid" then
-               if text ~= "" and text ~= session.from_host then
-                       session.sends2s(build_reply("failure", "invalid-authzid"))
-                       return true
-               end
-       else
-               if text == "" then
-                       session.sends2s(build_reply("failure", "invalid-authzid"))
-                       return true
-               end
-               local cert = session.conn:socket():getpeercertificate()
-               if (cert_verify_identity(text, "xmpp-server", cert)) then
-                       session.cert_identity_status = "valid"
-               else
-                       session.cert_identity_status = "invalid"
-                       session.sends2s(build_reply("failure", "invalid-authzid"))
-                       return true
-               end
-       end
-       session.external_auth = "succeeded"
-       if not session.from_host then
-               session.from_host = text;
-       end
-       session.sends2s(build_reply("success"))
-       module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host);
-       s2s_make_authenticated(session, text or session.from_host)
-       session:reset_stream();
-       return true
- end
 -module:add_handler("c2s_unauthed", "auth", xmlns_sasl, sasl_handler);
 -module:add_handler("c2s_unauthed", "abort", xmlns_sasl, sasl_handler);
 -module:add_handler("c2s_unauthed", "response", xmlns_sasl, sasl_handler);
 +module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
 +      local session, stanza = event.origin, event.stanza;
-       if session.type == "s2sin_unauthed" then
-               return s2s_external_auth(session, stanza)
-       end
 +      if session.type ~= "c2s_unauthed" then return; end
 +
 +      if session.sasl_handler and session.sasl_handler.selected then
 +              session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one
 +      end
 +      if not session.sasl_handler then
 +              session.sasl_handler = usermanager_get_sasl_handler(module.host);
 +      end
 +      local mechanism = stanza.attr.mechanism;
 +      if not session.secure and (secure_auth_only or (mechanism == "PLAIN" and not allow_unencrypted_plain_auth)) then
 +              session.send(build_reply("failure", "encryption-required"));
 +              return true;
 +      end
 +      local valid_mechanism = session.sasl_handler:select(mechanism);
 +      if not valid_mechanism then
 +              session.send(build_reply("failure", "invalid-mechanism"));
 +              return true;
 +      end
 +      return sasl_process_cdata(session, stanza);
 +end);
 +module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event)
 +      local session = event.origin;
 +      if not(session.sasl_handler and session.sasl_handler.selected) then
 +              session.send(build_reply("failure", "not-authorized", "Out of order SASL element"));
 +              return true;
 +      end
 +      return sasl_process_cdata(session, event.stanza);
 +end);
 +module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event)
 +      local session = event.origin;
 +      session.sasl_handler = nil;
 +      session.send(build_reply("failure", "aborted"));
 +      return true;
 +end);
  
  local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
  local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' };
@@@ -259,22 -199,8 +141,8 @@@ module:hook("stream-features", function
        end
  end);
  
- module:hook("s2s-stream-features", function(event)
-       local origin, features = event.origin, event.features;
-       if origin.secure and origin.type == "s2sin_unauthed" then
-               -- Offer EXTERNAL if chain is valid and either we didn't validate
-               -- the identity or it passed.
-               if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable
-                       module:log("debug", "Offering SASL EXTERNAL")
-                       features:tag("mechanisms", { xmlns = xmlns_sasl })
-                               :tag("mechanism"):text("EXTERNAL")
-                       :up():up();
-               end
-       end
- end);
 -module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", function(session, stanza)
 -      log("debug", "Client requesting a resource bind");
 +module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event)
 +      local origin, stanza = event.origin, event.stanza;
        local resource;
        if stanza.attr.type == "set" then
                local bind = stanza.tags[1];
index ec85d185d301645cc8b27daac668ef5380d6d60b,18c80325bbd1bce5db492b36643407d4d1b4bf65..647bf9152cd705c986f9594a412a19b06cae7dda
@@@ -213,22 -166,9 +213,20 @@@ function room_mt:send_history(to, stanz
  end
  
  function room_mt:get_disco_info(stanza)
-       local count = 0; for _ in pairs(self._occupants) do count = count + 1; end
        return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
 -              :tag("identity", {category="conference", type="text"}):up()
 -              :tag("feature", {var="http://jabber.org/protocol/muc"});
 +              :tag("identity", {category="conference", type="text", name=self:get_name()}):up()
 +              :tag("feature", {var="http://jabber.org/protocol/muc"}):up()
 +              :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
 +              :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
 +              :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up()
 +              :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up()
 +              :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up()
 +              :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
 +              :add_child(dataform.new({
 +                      { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
-                       { name = "muc#roominfo_description", label = "Description"},
-                       { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }
++                      { name = "muc#roominfo_description", label = "Description"}
 +              }):form({["muc#roominfo_description"] = self:get_description()}, 'result'))
 +      ;
  end
  function room_mt:get_disco_items(stanza)
        local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
index 9b6c6cf4c2cf8d22a9937af55977954e8e706297,9b6c6cf4c2cf8d22a9937af55977954e8e706297..2a4653fbf15979ca525046dc48d112686ecf9063
@@@ -117,8 -117,8 +117,55 @@@ static const luaL_Reg Reg_base64[] 
  };
  
  /***************** STRINGPREP *****************/
--#ifdef USE_STRINGPREP_ICU
++#ifndef USE_STRINGPREP_ICU
++/****************** libidn ********************/
++
++#include <stringprep.h>
++
++static int stringprep_prep(lua_State *L, const Stringprep_profile *profile)
++{
++      size_t len;
++      const char *s;
++      char string[1024];
++      int ret;
++      if(!lua_isstring(L, 1)) {
++              lua_pushnil(L);
++              return 1;
++      }
++      s = lua_tolstring(L, 1, &len);
++      if (len >= 1024) {
++              lua_pushnil(L);
++              return 1; /* TODO return error message */
++      }
++      strcpy(string, s);
++      ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile);
++      if (ret == STRINGPREP_OK) {
++              lua_pushstring(L, string);
++              return 1;
++      } else {
++              lua_pushnil(L);
++              return 1; /* TODO return error message */
++      }
++}
++
++#define MAKE_PREP_FUNC(myFunc, prep) \
++static int myFunc(lua_State *L) { return stringprep_prep(L, prep); }
++
++MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep)             /** stringprep.nameprep(s) */
++MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep)                /** stringprep.nodeprep(s) */
++MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep)                /** stringprep.resourceprep(s) */
++MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep)             /** stringprep.saslprep(s) */
++
++static const luaL_Reg Reg_stringprep[] =
++{
++      { "nameprep",   Lstringprep_nameprep    },
++      { "nodeprep",   Lstringprep_nodeprep    },
++      { "resourceprep",       Lstringprep_resourceprep        },
++      { "saslprep",   Lstringprep_saslprep    },
++      { NULL,         NULL    }
++};
  
++#else
  #include <unicode/usprep.h>
  #include <unicode/ustring.h>
  #include <unicode/utrace.h>
@@@ -145,17 -145,17 +192,13 @@@ static int icu_stringprep_prep(lua_Stat
                return 1;
        }
        u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err);
--      if (U_FAILURE(err)) {
--              luah_pushnil(L);
--              return 1;
--      }
        prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, 0, NULL, &err);
        if (U_FAILURE(err)) {
                lua_pushnil(L);
                return 1;
        } else {
                u_strToUTF8(output, 1024, &output_len, prepped, prepped_len, &err);
--              if (U_SUCCESS(err) && output_len < 1024)
++              if(output_len < 1024)
                        lua_pushlstring(L, output, output_len);
                else
                        lua_pushnil(L);
@@@ -196,58 -196,58 +239,49 @@@ static const luaL_Reg Reg_stringprep[] 
        { "saslprep",   Lstringprep_saslprep    },
        { NULL,         NULL    }
  };
--#else /* USE_STRINGPREP_ICU */
++#endif
  
++/***************** IDNA *****************/
++#ifndef USE_STRINGPREP_ICU
  /****************** libidn ********************/
  
--#include <stringprep.h>
++#include <idna.h>
++#include <idn-free.h>
  
--static int stringprep_prep(lua_State *L, const Stringprep_profile *profile)
++static int Lidna_to_ascii(lua_State *L)               /** idna.to_ascii(s) */
  {
        size_t len;
--      const char *s;
--      char string[1024];
--      int ret;
--      if(!lua_isstring(L, 1)) {
--              lua_pushnil(L);
++      const char *s = luaL_checklstring(L, 1, &len);
++      char* output = NULL;
++      int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
++      if (ret == IDNA_SUCCESS) {
++              lua_pushstring(L, output);
++              idn_free(output);
                return 1;
--      }
--      s = lua_tolstring(L, 1, &len);
--      if (len >= 1024) {
++      } else {
                lua_pushnil(L);
++              idn_free(output);
                return 1; /* TODO return error message */
        }
--      strcpy(string, s);
--      ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile);
--      if (ret == STRINGPREP_OK) {
--              lua_pushstring(L, string);
++}
++
++static int Lidna_to_unicode(lua_State *L)             /** idna.to_unicode(s) */
++{
++      size_t len;
++      const char *s = luaL_checklstring(L, 1, &len);
++      char* output = NULL;
++      int ret = idna_to_unicode_8z8z(s, &output, 0);
++      if (ret == IDNA_SUCCESS) {
++              lua_pushstring(L, output);
++              idn_free(output);
                return 1;
        } else {
                lua_pushnil(L);
++              idn_free(output);
                return 1; /* TODO return error message */
        }
  }
--
--#define MAKE_PREP_FUNC(myFunc, prep) \
--static int myFunc(lua_State *L) { return stringprep_prep(L, prep); }
--
--MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep)             /** stringprep.nameprep(s) */
--MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep)                /** stringprep.nodeprep(s) */
--MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep)                /** stringprep.resourceprep(s) */
--MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep)             /** stringprep.saslprep(s) */
--
--static const luaL_Reg Reg_stringprep[] =
--{
--      { "nameprep",   Lstringprep_nameprep    },
--      { "nodeprep",   Lstringprep_nodeprep    },
--      { "resourceprep",       Lstringprep_resourceprep        },
--      { "saslprep",   Lstringprep_saslprep    },
--      { NULL,         NULL    }
--};
--#endif
--
--/***************** IDNA *****************/
--#ifdef USE_STRINGPREP_ICU
++#else
  #include <unicode/ustdio.h>
  #include <unicode/uidna.h>
  /* IDNA2003 or IDNA2008 ? ? ? */
@@@ -262,18 -262,18 +296,13 @@@ static int Lidna_to_ascii(lua_State *L
        char output[1024];
  
        u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
--      if (U_FAILURE(err)) {
--              lua_pushnil(L);
--              return 1;
--      }
--
        dest_len = uidna_IDNToASCII(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err);
        if (U_FAILURE(err)) {
                lua_pushnil(L);
                return 1;
        } else {
                u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err);
--              if (U_SUCCESS(err) && output_len < 1024)
++              if(output_len < 1024)
                        lua_pushlstring(L, output, output_len);
                else
                        lua_pushnil(L);
@@@ -286,70 -286,70 +315,25 @@@ static int Lidna_to_unicode(lua_State *
        size_t len;
        int32_t ulen, dest_len, output_len;
        const char *s = luaL_checklstring(L, 1, &len);
--      UChar ustr[1024];
++      UChar* ustr;
        UErrorCode err = U_ZERO_ERROR;
        UChar dest[1024];
        char output[1024];
  
        u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
--      if (U_FAILURE(err)) {
--              lua_pushnil(L);
--              return 1;
--      }
--
        dest_len = uidna_IDNToUnicode(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err);
        if (U_FAILURE(err)) {
                lua_pushnil(L);
                return 1;
        } else {
                u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err);
--              if (U_SUCCESS(err) && output_len < 1024)
++              if(output_len < 1024)
                        lua_pushlstring(L, output, output_len);
                else
                        lua_pushnil(L);
                return 1;
        }
  }
--
--#else /* USE_STRINGPREP_ICU */
--/****************** libidn ********************/
--
--#include <idna.h>
--#include <idn-free.h>
--
--static int Lidna_to_ascii(lua_State *L)               /** idna.to_ascii(s) */
--{
--      size_t len;
--      const char *s = luaL_checklstring(L, 1, &len);
--      char* output = NULL;
--      int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
--      if (ret == IDNA_SUCCESS) {
--              lua_pushstring(L, output);
--              idn_free(output);
--              return 1;
--      } else {
--              lua_pushnil(L);
--              idn_free(output);
--              return 1; /* TODO return error message */
--      }
--}
--
--static int Lidna_to_unicode(lua_State *L)             /** idna.to_unicode(s) */
--{
--      size_t len;
--      const char *s = luaL_checklstring(L, 1, &len);
--      char* output = NULL;
--      int ret = idna_to_unicode_8z8z(s, &output, 0);
--      if (ret == IDNA_SUCCESS) {
--              lua_pushstring(L, output);
--              idn_free(output);
--              return 1;
--      } else {
--              lua_pushnil(L);
--              idn_free(output);
--              return 1; /* TODO return error message */
--      }
--}
  #endif
  
  static const luaL_Reg Reg_idna[] =
index cf485daceb5e1000c2fe1feb957b2b365469e839,cf485daceb5e1000c2fe1feb957b2b365469e839..69e7690d3b891cfe8919e20edb75d36e2997469a
@@@ -9,13 -9,13 +9,10 @@@
  
  local lxp = require "lxp";
  local st = require "util.stanza";
--local stanza_mt = st.stanza_mt;
  
  local tostring = tostring;
  local t_insert = table.insert;
  local t_concat = table.concat;
--local t_remove = table.remove;
--local setmetatable = setmetatable;
  
  local default_log = require "util.logger".init("xmppstream");
  
@@@ -66,13 -66,13 +63,12 @@@ function new_sax_handlers(session, stre
        
        local stream_default_ns = stream_callbacks.default_ns;
        
--      local stack = {};
        local chardata, stanza = {};
        local non_streamns_depth = 0;
        function xml_handlers:StartElement(tagname, attr)
                if stanza and #chardata > 0 then
                        -- We have some character data in the buffer
--                      t_insert(stanza, t_concat(chardata));
++                      stanza:text(t_concat(chardata));
                        chardata = {};
                end
                local curr_ns,name = tagname:match(ns_pattern);
                                cb_error(session, "invalid-top-level-element");
                        end
                        
--                      stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
++                      stanza = st.stanza(name, attr);
                else -- we are inside a stanza, so add a tag
--                      t_insert(stack, stanza);
--                      local oldstanza = stanza;
--                      stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
--                      t_insert(oldstanza, stanza);
--                      t_insert(oldstanza.tags, stanza);
++                      stanza:tag(name, attr);
                end
        end
        function xml_handlers:CharacterData(data)
                if stanza then
                        if #chardata > 0 then
                                -- We have some character data in the buffer
--                              t_insert(stanza, t_concat(chardata));
++                              stanza:text(t_concat(chardata));
                                chardata = {};
                        end
                        -- Complete stanza
--                      if #stack == 0 then
++                      local last_add = stanza.last_add;
++                      if not last_add or #last_add == 0 then
                                if tagname ~= stream_error_tag then
                                        cb_handlestanza(session, stanza);
                                else
                                end
                                stanza = nil;
                        else
--                              stanza = t_remove(stack);
++                              stanza:up();
                        end
                else
                        if tagname == stream_tag then
                                cb_error(session, "parse-error", "unexpected-element-close", name);
                        end
                        stanza, chardata = nil, {};
--                      stack = {};
                end
        end
--      
--      local function restricted_handler()
++
++      local function restricted_handler(parser)
                cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
++              if not parser:stop() then
++                      error("Failed to abort parsing");
++              end
        end
        
        if lxp_supports_doctype then
        
        local function reset()
                stanza, chardata = nil, {};
--              stack = {};
        end
        
        local function set_session(stream, new_session)