Merge 0.6->0.7
authorMatthew Wild <mwild1@gmail.com>
Wed, 24 Mar 2010 22:34:59 +0000 (22:34 +0000)
committerMatthew Wild <mwild1@gmail.com>
Wed, 24 Mar 2010 22:34:59 +0000 (22:34 +0000)
1  2 
plugins/mod_tls.lua
plugins/muc/muc.lib.lua
prosody

diff --combined plugins/mod_tls.lua
index b30ad3f324c6a6a60d8f807c16d50b815892c050,f68552fac336178c8632d001efd26318c12cc7a0..8b96aa157922c7bf1f63613a0e000e3baf72851f
  
  local st = require "util.stanza";
  
 -local xmlns_stream = 'http://etherx.jabber.org/streams';
 -local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
 -
  local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
  local secure_s2s_only = module:get_option("s2s_require_encryption");
++local allow_s2s_tls = module:get_option("s2s_allow_encryption") ~= false;
  
 -local host = hosts[module.host];
 -
 +local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
  local starttls_attr = { xmlns = xmlns_starttls };
 +local starttls_proceed = st.stanza("proceed", starttls_attr);
 +local starttls_failure = st.stanza("failure", starttls_attr);
 +local c2s_feature = st.stanza("starttls", starttls_attr);
 +local s2s_feature = st.stanza("starttls", starttls_attr);
 +if secure_auth_only then c2s_feature:tag("required"):up(); end
 +if secure_s2s_only then s2s_feature:tag("required"):up(); end
  
 ---- Client-to-server TLS handling
 -module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
 -              function (session, stanza)
 -                      if session.conn.starttls and host.ssl_ctx_in then
 -                              session.send(st.stanza("proceed", starttls_attr));
 -                              session:reset_stream();
 -                              if session.host and hosts[session.host].ssl_ctx_in then
 -                                      session.conn.set_sslctx(hosts[session.host].ssl_ctx_in);
 -                              end
 -                              session.conn.starttls();
 -                              session.log("info", "TLS negotiation started...");
 -                              session.secure = false;
 -                      else
 -                              session.log("warn", "Attempt to start TLS, but TLS is not available on this connection");
 -                              (session.sends2s or session.send)(st.stanza("failure", starttls_attr));
 -                              session:close();
 -                      end
 -              end);
 +local global_ssl_ctx = prosody.global_ssl_ctx;
  
 -module:add_event_hook("stream-features", 
 -              function (session, features)
 -                      if session.conn.starttls then
 -                              features:tag("starttls", starttls_attr);
 -                              if secure_auth_only then
 -                                      features:tag("required"):up():up();
 -                              else
 -                                      features:up();
 -                              end
 -                      end
 -              end);
 ----
 +local host = hosts[module.host];
  
 --- Stop here if the user doesn't want to allow s2s encryption
 -if module:get_option("s2s_allow_encryption") == false then
 -      return;
 +local function can_do_tls(session)
 +      if session.type == "c2s_unauthed" then
 +              return session.conn.starttls and host.ssl_ctx_in;
-       elseif session.type == "s2sin_unauthed" then
++      elseif session.type == "s2sin_unauthed" and allow_s2s_tls then
 +              return session.conn.starttls and host.ssl_ctx_in;
-       elseif session.direction == "outgoing" then
++      elseif session.direction == "outgoing" and allow_s2s_tls then
 +              return session.conn.starttls and host.ssl_ctx;
 +      end
 +      return false;
  end
  
 ---- Server-to-server TLS handling
 -module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
 -              function (session, stanza)
 -                      if session.conn.starttls and host.ssl_ctx_in then
 -                              session.sends2s(st.stanza("proceed", starttls_attr));
 -                              session:reset_stream();
 -                              if session.to_host and hosts[session.to_host].ssl_ctx_in then
 -                                      session.conn.set_sslctx(hosts[session.to_host].ssl_ctx_in);
 -                              end
 -                              session.conn.starttls();
 -                              session.log("info", "TLS negotiation started for incoming s2s...");
 -                              session.secure = false;
 -                      else
 -                              session.log("warn", "Attempt to start TLS, but TLS is not available on this s2s connection");
 -                              (session.sends2s or session.send)(st.stanza("failure", starttls_attr));
 -                              session:close();
 -                      end
 -              end);
 -
 +-- Hook <starttls/>
 +module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event)
 +      local origin = event.origin;
 +      if can_do_tls(origin) then
 +              (origin.sends2s or origin.send)(starttls_proceed);
 +              origin:reset_stream();
 +              local host = origin.to_host or origin.host;
 +              local ssl_ctx = host and hosts[host].ssl_ctx_in or global_ssl_ctx;
 +              origin.conn:starttls(ssl_ctx);
 +              origin.log("info", "TLS negotiation started for %s...", origin.type);
 +              origin.secure = false;
 +      else
 +              origin.log("warn", "Attempt to start TLS, but TLS is not available on this %s connection", origin.type);
 +              (origin.sends2s or origin.send)(starttls_failure);
 +              origin:close();
 +      end
 +      return true;
 +end);
  
 -module:hook("s2s-stream-features", 
 -              function (data)
 -                      local session, features = data.session, data.features;
 -                      if session.to_host and session.conn.starttls then
 -                              features:tag("starttls", starttls_attr);
 -                              if secure_s2s_only then
 -                                      features:tag("required"):up():up();
 -                              else
 -                                      features:up();
 -                              end
 -                      end
 -              end);
 +-- Advertize stream feature
 +module:hook("stream-features", function(event)
 +      local origin, features = event.origin, event.features;
 +      if can_do_tls(origin) then
 +              features:add_child(c2s_feature);
 +      end
 +end);
 +module:hook("s2s-stream-features", function(event)
 +      local origin, features = event.origin, event.features;
 +      if can_do_tls(origin) then
 +              features:add_child(s2s_feature);
 +      end
 +end);
  
  -- For s2sout connections, start TLS if we can
 -module:hook_stanza(xmlns_stream, "features",
 -              function (session, stanza)
 -                      module:log("debug", "Received features element");
 -                      if session.conn.starttls and stanza:child_with_ns(xmlns_starttls) then
 -                              module:log("%s is offering TLS, taking up the offer...", session.to_host);
 -                              session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
 -                              return true;
 -                      end
 -              end, 500);
 +module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
 +      module:log("debug", "Received features element");
 +      if can_do_tls(session) and stanza:child_with_ns(xmlns_starttls) then
 +              module:log("%s is offering TLS, taking up the offer...", session.to_host);
 +              session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
 +              return true;
 +      end
 +end, 500);
  
 -module:hook_stanza(xmlns_starttls, "proceed",
 -              function (session, stanza)
 -                      module:log("debug", "Proceeding with TLS on s2sout...");
 -                      local format, to_host, from_host = string.format, session.to_host, session.from_host;
 -                      local ssl_ctx = session.from_host and hosts[session.from_host].ssl_ctx or global_ssl_ctx;
 -                      session.conn.set_sslctx(ssl_ctx);
 -                      session:reset_stream();
 -                      session.conn.starttls(true);
 -                      session.secure = false;
 -                      return true;
 -              end);
 +module:hook_stanza(xmlns_starttls, "proceed", function (session, stanza)
 +      module:log("debug", "Proceeding with TLS on s2sout...");
 +      session:reset_stream();
 +      local ssl_ctx = session.from_host and hosts[session.from_host].ssl_ctx or global_ssl_ctx;
 +      session.conn:starttls(ssl_ctx, true);
 +      session.secure = false;
 +      return true;
 +end);
diff --combined plugins/muc/muc.lib.lua
index 273e21ce94214b585d54580b60eb658b6d59f5af,1ec2349a23971ae3fabefd22c7c00b25be084c3e..1cc001bb0fc8ead4aca56c7e9420f0a0edadbf33
@@@ -59,12 -59,19 +59,12 @@@ local kickable_error_conditions = 
        ["service-unavailable"] = true;
        ["malformed error"] = true;
  };
 +
  local function get_error_condition(stanza)
 -      for _, tag in ipairs(stanza.tags) do
 -              if tag.name == "error" and (not(tag.attr.xmlns) or tag.attr.xmlns == "jabber:client") then
 -                      for _, cond in ipairs(tag.tags) do
 -                              if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
 -                                      return cond.name;
 -                              end
 -                      end
 -                      return "malformed error";
 -              end
 -      end
 -      return "malformed error";
 +      local _, condition = stanza:get_error();
 +      return condition or "malformed error";
  end
 +
  local function is_kickable_error(stanza)
        local cond = get_error_condition(stanza);
        return kickable_error_conditions[cond] and cond;
@@@ -82,6 -89,17 +82,6 @@@ local function getTag(stanza, path) ret
  local function getText(stanza, path) return getUsingPath(stanza, path, true); end
  -----------
  
 ---[[function get_room_disco_info(room, stanza)
 -      return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
 -              :tag("identity", {category='conference', type='text', name=room._data["name"]):up()
 -              :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
 -end
 -function get_room_disco_items(room, stanza)
 -      return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
 -end -- TODO allow non-private rooms]]
 -
 ---
 -
  local room_mt = {};
  room_mt.__index = room_mt;
  
@@@ -122,13 -140,9 +122,9 @@@ function room_mt:broadcast_message(stan
                local history = self._data['history'];
                if not history then history = {}; self._data['history'] = history; end
                stanza = st.clone(stanza);
-               stanza.attr.to = "";
-               local stamp = datetime.datetime();
-               local chars = #tostring(stanza);
-               stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
+               stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
                stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
-               local entry = { stanza = stanza, stamp = stamp };
-               t_insert(history, entry);
+               t_insert(history, st.preserialize(stanza));
                while #history > history_length do t_remove(history, 1) end
        end
  end
@@@ -155,46 -169,12 +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
        end
  end
  
 -local function room_get_disco_info(self, stanza)
 +function room_mt:get_disco_info(stanza)
        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"});
  end
 -local function room_get_disco_items(self, stanza)
 +function room_mt:get_disco_items(stanza)
        local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
        for room_jid in pairs(self._occupants) do
                reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up();
@@@ -226,16 -206,6 +188,16 @@@ function room_mt:set_subject(current_ni
        return true;
  end
  
 +local function build_unavailable_presence_from_error(stanza)
 +      local type, condition, text = stanza:get_error();
 +      local error_message = "Kicked: "..condition:gsub("%-", " ");
 +      if text then
 +              error_message = error_message..": "..text;
 +      end
 +      return st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
 +              :tag('status'):text(error_message);
 +end
 +
  function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
        local from, to = stanza.attr.from, stanza.attr.to;
        local room = jid_bare(to);
                if type == "error" then -- error, kick em out!
                        if current_nick then
                                log("debug", "kicking %s from %s", current_nick, room);
 -                              self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
 -                                      :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
 +                              self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza));
                        end
                elseif type == "unavailable" then -- unavailable
                        if current_nick then
                                                                :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
                                                                :tag("status", {code='110'}));
                                                end
-                                               self:send_history(from, stanza);
+                                               self:send_history(from);
                                        else -- banned
                                                local reply = st.error_reply(stanza, "auth", "forbidden"):up();
                                                reply.tags[1].attr.code = "403";
                origin.send(st.error_reply(stanza, "modify", "bad-request"));
        elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
                log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
 -              self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
 -                      :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
 +              self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
        else -- private stanza
                local o_data = self._occupants[to];
                if o_data then
        end
  end
  
 -function room_mt:handle_form(origin, stanza)
 -      if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end
 -      if stanza.attr.type == "get" then
 -              local title = "Configuration for "..self.jid;
 -              origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
 -                      :tag("x", {xmlns='jabber:x:data', type='form'})
 -                              :tag("title"):text(title):up()
 -                              :tag("instructions"):text(title):up()
 -                              :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
 -                              :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
 -                                      :tag("value"):text(self._data.persistent and "1" or "0"):up()
 +function room_mt:send_form(origin, stanza)
 +      local title = "Configuration for "..self.jid;
 +      origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
 +              :tag("x", {xmlns='jabber:x:data', type='form'})
 +                      :tag("title"):text(title):up()
 +                      :tag("instructions"):text(title):up()
 +                      :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
 +                      :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
 +                              :tag("value"):text(self._data.persistent and "1" or "0"):up()
 +                      :up()
 +                      :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
 +                              :tag("value"):text(self._data.hidden and "0" or "1"):up()
 +                      :up()
 +                      :tag("field", {type='list-single', label='Who May Discover Real JIDs?', var='muc#roomconfig_whois'})
 +                          :tag("value"):text(self._data.whois or 'moderators'):up()
 +                          :tag("option", {label = 'Moderators Only'})
 +                              :tag("value"):text('moderators'):up()
                                :up()
 -                              :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
 -                                      :tag("value"):text(self._data.hidden and "0" or "1"):up()
 +                          :tag("option", {label = 'Anyone'})
 +                              :tag("value"):text('anyone'):up()
                                :up()
 -              );
 -      elseif stanza.attr.type == "set" then
 -              local query = stanza.tags[1];
 -              local form;
 -              for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
 -              if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
 -              if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
 -              if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 -              local fields = {};
 -              for _, field in pairs(form.tags) do
 -                      if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
 -                              fields[field.attr.var] = field.tags[1][1] or "";
 -                      end
 +                      :up()
 +      );
 +end
 +
 +local valid_whois = {
 +    moderators = true,
 +    anyone = true,
 +}
 +
 +function room_mt:process_form(origin, stanza)
 +      local query = stanza.tags[1];
 +      local form;
 +      for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
 +      if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
 +      if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
 +      if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 +      local fields = {};
 +      for _, field in pairs(form.tags) do
 +              if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
 +                      fields[field.attr.var] = field.tags[1][1] or "";
                end
 -              if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 +      end
 +      if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 +
 +      local dirty = false
 +
 +      local persistent = fields['muc#roomconfig_persistentroom'];
 +      if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
 +      else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 +      dirty = dirty or (self._data.persistent ~= persistent)
 +      self._data.persistent = persistent;
 +      module:log("debug", "persistent=%s", tostring(persistent));
  
 -              local persistent = fields['muc#roomconfig_persistentroom'];
 -              if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
 -              else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 -              self._data.persistent = persistent;
 -              module:log("debug", "persistent=%s", tostring(persistent));
 +      local public = fields['muc#roomconfig_publicroom'];
 +      if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
 +      else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 +      dirty = dirty or (self._data.hidden ~= (not public and true or nil))
 +      self._data.hidden = not public and true or nil;
  
 -              local public = fields['muc#roomconfig_publicroom'];
 -              if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
 -              else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 -              self._data.hidden = not public and true or nil;
 +      local whois = fields['muc#roomconfig_whois'];
 +      if not valid_whois[whois] then
 +          origin.send(st.error_reply(stanza, 'cancel', 'bad-request'));
 +          return;
 +      end
 +      local whois_changed = self._data.whois ~= whois
 +      self._data.whois = whois
 +      module:log('debug', 'whois=%s', tostring(whois))
 +
 +      if self.save then self:save(true); end
 +      origin.send(st.reply(stanza));
 +
 +      if dirty or whois_changed then
 +          local msg = st.message({type='groupchat', from=self.jid})
 +                  :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up()
 +
 +          if dirty then
 +              msg.tags[1]:tag('status', {code = '104'})
 +          end
 +          if whois_changed then
 +              local code = (whois == 'moderators') and 173 or 172
 +              msg.tags[1]:tag('status', {code = code})
 +          end
 +
 +          self:broadcast_message(msg, false)
 +      end
 +end
  
 -              if self.save then self:save(true); end
 -              origin.send(st.reply(stanza));
 +function room_mt:destroy(newjid, reason, password)
 +      local pr = st.presence({type = "unavailable"})
 +              :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
 +                      :tag("item", { affiliation='none', role='none' }):up()
 +                      :tag("destroy", {jid=newjid})
 +      if reason then pr:tag("reason"):text(reason):up(); end
 +      if password then pr:tag("password"):text(password):up(); end
 +      for nick, occupant in pairs(self._occupants) do
 +              pr.attr.from = nick;
 +              for jid in pairs(occupant.sessions) do
 +                      pr.attr.to = jid;
 +                      self:_route_stanza(pr);
 +                      self._jid_nick[jid] = nil;
 +              end
 +              self._occupants[nick] = nil;
        end
 +      self._data.persistent = nil;
 +      if self.save then self:save(true); end
  end
  
  function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
        local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
        if stanza.name == "iq" then
                if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" then
 -                      origin.send(room_get_disco_info(self, stanza));
 +                      origin.send(self:get_disco_info(stanza));
                elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" then
 -                      origin.send(room_get_disco_items(self, stanza));
 +                      origin.send(self:get_disco_items(stanza));
                elseif xmlns == "http://jabber.org/protocol/muc#admin" then
                        local actor = stanza.attr.from;
                        local affiliation = self:get_affiliation(actor);
                                origin.send(st.error_reply(stanza, "cancel", "bad-request"));
                        end
                elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
 -                      self:handle_form(origin, stanza);
 +                      if self:get_affiliation(stanza.attr.from) ~= "owner" then
 +                              origin.send(st.error_reply(stanza, "auth", "forbidden"));
 +                      elseif stanza.attr.type == "get" then
 +                              self:send_form(origin, stanza);
 +                      elseif stanza.attr.type == "set" then
 +                              local child = stanza.tags[1].tags[1];
 +                              if not child then
 +                                      origin.send(st.error_reply(stanza, "auth", "bad-request"));
 +                              elseif child.name == "destroy" then
 +                                      local newjid = child.attr.jid;
 +                                      local reason, password;
 +                                      for _,tag in ipairs(child.tags) do
 +                                              if tag.name == "reason" then
 +                                                      reason = #tag.tags == 0 and tag[1];
 +                                              elseif tag.name == "password" then
 +                                                      password = #tag.tags == 0 and tag[1];
 +                                              end
 +                                      end
 +                                      self:destroy(newjid, reason, password);
 +                                      origin.send(st.reply(stanza));
 +                              else
 +                                      self:process_form(origin, stanza);
 +                              end
 +                      end
                elseif type == "set" or type == "get" then
                        origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                end
        elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
                local current_nick = self._jid_nick[stanza.attr.from];
                log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
 -              self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
 -                      :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
 +              self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
        elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
                local to = stanza.attr.to;
                local current_nick = self._jid_nick[stanza.attr.from];
@@@ -818,11 -707,13 +780,11 @@@ function room_mt:_route_stanza(stanza
        local from_occupant = self._occupants[stanza.attr.from];
        if stanza.name == "presence" then
                if to_occupant and from_occupant then
 -                      if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then
 -                              for i=#stanza.tags,1,-1 do
 -                                      local tag = stanza.tags[i];
 -                                      if tag.name == "x" and tag.attr.xmlns == "http://jabber.org/protocol/muc#user" then
 -                                              muc_child = tag;
 -                                              break;
 -                                      end
 +                      if self._data.whois == 'anyone' then
 +                          muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
 +                      else
 +                              if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then
 +                                      muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
                                end
                        end
                end
                                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
@@@ -858,9 -746,7 +820,9 @@@ function _M.new_room(jid
                jid = jid;
                _jid_nick = {};
                _occupants = {};
 -              _data = {};
 +              _data = {
 +                  whois = 'moderators',
 +              };
                _affiliations = {};
        }, room_mt);
  end
diff --combined prosody
index 0f705b62bc905cd4f1019b6cf049f7dede10f54c,b87388dfa454d77cf214a319906b9b41a8c16797..46f3331f004bc9030f5866c5c50a6bcb3e104dc2
+++ b/prosody
@@@ -16,28 -16,50 +16,31 @@@ CFG_DATADIR=os.getenv("PROSODY_DATADIR"
  
  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  
 +-- Tell Lua where to find our libraries
  if CFG_SOURCEDIR then
        package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
        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
                CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
        end
  end
  
 --- Required to be able to find packages installed with luarocks
 -if not pcall(require, "luarocks.loader") then -- Try LuaRocks 2.x
 -      pcall(require, "luarocks.require") -- Try LuaRocks 1.x
 -end
 -
 --- Replace require with one that doesn't pollute _G
 -do
 -      local _realG = _G;
 -      local _real_require = require;
 -      function require(...)
 -              local curr_env = getfenv(2);
 -              local curr_env_mt = getmetatable(getfenv(2));
 -              local _realG_mt = getmetatable(_realG);
 -              if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
 -                      local old_newindex
 -                      old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
 -                      local ret = _real_require(...);
 -                      _realG_mt.__newindex = old_newindex;
 -                      return ret;
 -              end
 -              return _real_require(...);
 -      end
 -end
 -
 -
 +-- Load the config-parsing module
  config = require "core.configmanager"
  
 +-- -- -- --
 +-- Define the functions we call during startup, the 
 +-- actual startup happens right at the end, where these
 +-- functions get called
 +
  function read_config()
 -      -- TODO: Check for other formats when we add support for them
 -      -- Use lfs? Make a new conf/ dir?
        local filenames = {};
        
        local filename;
@@@ -47,9 -69,7 +50,9 @@@
                        table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
                end
        else
 -              table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
 +              for _, format in ipairs(config.parsers()) do
 +                      table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
 +              end
        end
        for _,_filename in ipairs(filenames) do
                filename = _filename;
  end
  
  function load_libraries()
 -      -- Initialize logging
 -      require "core.loggingmanager"
 -      
 -      -- Check runtime dependencies
 -      require "util.dependencies"
 -      
        -- Load socket framework
        server = require "net.server"
  end   
  
 +function init_logging()
 +      -- Initialize logging
 +      require "core.loggingmanager"
 +end
 +
 +function check_dependencies()
 +      -- Check runtime dependencies
 +      if not require "util.dependencies".check_dependencies() then
 +              os.exit(1);
 +      end
 +end
 +
 +function sandbox_require()
 +      -- Replace require() with one that doesn't pollute _G, required
 +      -- for neat sandboxing of modules
 +      local _realG = _G;
 +      local _real_require = require;
 +      function require(...)
 +              local curr_env = getfenv(2);
 +              local curr_env_mt = getmetatable(getfenv(2));
 +              local _realG_mt = getmetatable(_realG);
 +              if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
 +                      local old_newindex
 +                      old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
 +                      local ret = _real_require(...);
 +                      _realG_mt.__newindex = old_newindex;
 +                      return ret;
 +              end
 +              return _real_require(...);
 +      end
 +end
 +
  function init_global_state()
        bare_sessions = {};
        full_sessions = {};
        end
  
        -- Load SSL settings from config, and create a ctx table
 -      local global_ssl_ctx = rawget(_G, "ssl") and config.get("*", "core", "ssl");
 -      if global_ssl_ctx then
 -              local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
 -              setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
 -      end
 +      local certmanager = require "core.certmanager";
 +      local global_ssl_ctx = certmanager.create_context("*", "server");
 +      prosody.global_ssl_ctx = global_ssl_ctx;
  
        local cl = require "net.connlisteners";
        function prosody.net_activate_ports(option, listener, default, conntype)
                conntype = conntype or (global_ssl_ctx and "tls") or "tcp";
 +              local ports_option = option and option.."_ports" or "ports";
                if not cl.get(listener) then return; end
 -              local ports = config.get("*", "core", option.."_ports") or default;
 +              local ports = config.get("*", "core", ports_option) or default;
                if type(ports) == "number" then ports = {ports} end;
                
                if type(ports) ~= "table" then
 -                      log("error", "core."..option.." is not a table");
 +                      log("error", "core."..ports_option.." is not a table");
                else
                        for _, port in ipairs(ports) do
                                port = tonumber(port);
                                if type(port) ~= "number" then
 -                                      log("error", "Non-numeric "..option.."_ports: "..tostring(port));
 +                                      log("error", "Non-numeric "..ports_option..": "..tostring(port));
                                else
                                        local ok, err = cl.start(listener, {
 -                                              ssl = conntype ~= "tcp" and global_ssl_ctx,
 +                                              ssl = conntype == "ssl" and global_ssl_ctx,
                                                port = port,
                                                interface = (option and config.get("*", "core", option.."_interface"))
                                                        or cl.get(listener).default_interface
@@@ -305,23 -300,15 +308,23 @@@ function init_data_store(
  end
  
  function prepare_to_start()
 +      log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
        -- Signal to modules that we are ready to start
        eventmanager.fire_event("server-starting");
        prosody.events.fire_event("server-starting");
  
        -- start listening on sockets
 -      prosody.net_activate_ports("c2s", "xmppclient", {5222});
 -      prosody.net_activate_ports("s2s", "xmppserver", {5269});
 -      prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
 -      prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
 +      if config.get("*", "core", "ports") then
 +              prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
 +              if config.get("*", "core", "ssl_ports") then
 +                      prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
 +              end
 +      else
 +              prosody.net_activate_ports("c2s", "xmppclient", {5222});
 +              prosody.net_activate_ports("s2s", "xmppserver", {5269});
 +              prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
 +              prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
 +      end
  
        prosody.start_time = os.time();
  end   
@@@ -408,13 -395,7 +411,13 @@@ function cleanup(
        server.setquitting(true);
  end
  
 +-- Are you ready? :)
 +-- These actions are in a strict order, as many depend on
 +-- previous steps to have already been performed
  read_config();
 +init_logging();
 +check_dependencies();
 +sandbox_require();
  load_libraries();
  init_global_state();
  read_version();