Merge 0.10->trunk
authorKim Alvefur <zash@zash.se>
Sat, 28 May 2016 10:51:12 +0000 (12:51 +0200)
committerKim Alvefur <zash@zash.se>
Sat, 28 May 2016 10:51:12 +0000 (12:51 +0200)
1  2 
plugins/muc/muc.lib.lua

index baa72a56f6da07648e8445581737c86377942797,4018489ad7832f70970813b7b15aa122309b398b..3ab9656c2f72619a18fce0ab25eb904664c2c519
@@@ -803,107 -739,213 +803,108 @@@ function room_mt:clear(x
  end
  
  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;
 +      local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
 +              :tag("item", { affiliation='none', role='none' }):up()
 +              :tag("destroy", {jid=newjid});
 +      if reason then x:tag("reason"):text(reason):up(); end
 +      if password then x:tag("password"):text(password):up(); end
 +      x:up();
 +      self:clear(x);
 +      module:fire_event("muc-room-destroyed", { room = self });
 +end
 +
 +function room_mt:handle_disco_info_get_query(origin, stanza)
 +      origin.send(self:get_disco_info(stanza));
 +      return true;
 +end
 +
 +function room_mt:handle_disco_items_get_query(origin, stanza)
 +      origin.send(self:get_disco_items(stanza));
 +      return true;
 +end
 +
 +function room_mt:handle_admin_query_set_command(origin, stanza)
 +      local item = stanza.tags[1].tags[1];
 +      if not item then
 +              origin.send(st.error_reply(stanza, "cancel", "bad-request"));
 +      end
 +      if item.attr.jid then -- Validate provided JID
 +              item.attr.jid = jid_prep(item.attr.jid);
 +              if not item.attr.jid then
 +                      origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
 +                      return true;
                end
 -              self._occupants[nick] = nil;
        end
 -      self:set_persistent(false);
 -      module:fire_event("muc-room-destroyed", { room = self });
 +      if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
 +              local occupant = self:get_occupant_by_nick(self.jid.."/"..item.attr.nick);
 +              if occupant then item.attr.jid = occupant.jid; end
 +      elseif not item.attr.nick and item.attr.jid then
 +              local nick = self:get_occupant_jid(item.attr.jid);
 +              if nick then item.attr.nick = select(3, jid_split(nick)); end
 +      end
 +      local actor = stanza.attr.from;
 +      local reason = item:get_child_text("reason");
 +      local success, errtype, err
 +      if item.attr.affiliation and item.attr.jid and not item.attr.role then
 +              success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, reason);
 +      elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
 +              success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, reason);
 +      else
 +              success, errtype, err = nil, "cancel", "bad-request";
 +      end
 +      self:save(true);
 +      if not success then
 +              origin.send(st.error_reply(stanza, errtype, err));
 +      else
 +              origin.send(st.reply(stanza));
 +      end
 +      return true;
  end
  
 -function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
 -      local type = stanza.attr.type;
 -      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" and not stanza.tags[1].attr.node then
 -                      origin.send(self:get_disco_info(stanza));
 -              elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then
 -                      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);
 -                      local current_nick = self._jid_nick[actor];
 -                      local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation);
 -                      local item = stanza.tags[1].tags[1];
 -                      if item and item.name == "item" then
 -                              if type == "set" then
 -                                      local callback = function() origin.send(st.reply(stanza)); end
 -                                      if item.attr.jid then -- Validate provided JID
 -                                              item.attr.jid = jid_prep(item.attr.jid);
 -                                              if not item.attr.jid then
 -                                                      origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
 -                                                      return;
 -                                              end
 -                                      end
 -                                      if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
 -                                              local occupant = self._occupants[self.jid.."/"..item.attr.nick];
 -                                              if occupant then item.attr.jid = occupant.jid; end
 -                                      elseif not item.attr.nick and item.attr.jid then
 -                                              local nick = self._jid_nick[item.attr.jid];
 -                                              if nick then item.attr.nick = select(3, jid_split(nick)); end
 -                                      end
 -                                      local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
 -                                      if item.attr.affiliation and item.attr.jid and not item.attr.role then
 -                                              local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason);
 -                                              if not success then origin.send(st.error_reply(stanza, errtype, err)); end
 -                                      elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
 -                                              local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason);
 -                                              if not success then origin.send(st.error_reply(stanza, errtype, err)); end
 -                                      else
 -                                              origin.send(st.error_reply(stanza, "cancel", "bad-request"));
 -                                      end
 -                              elseif type == "get" then
 -                                      local _aff = item.attr.affiliation;
 -                                      local _rol = item.attr.role;
 -                                      if _aff and not _rol then
 -                                              if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin")
 -                                              or (affiliation and affiliation ~= "outcast" and self:get_members_only() and self:get_whois() == "anyone") then
 -                                                      local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
 -                                                      for jid, affiliation in pairs(self._affiliations) do
 -                                                              if affiliation == _aff then
 -                                                                      reply:tag("item", {affiliation = _aff, jid = jid}):up();
 -                                                              end
 -                                                      end
 -                                                      origin.send(reply);
 -                                              else
 -                                                      origin.send(st.error_reply(stanza, "auth", "forbidden"));
 -                                              end
 -                                      elseif _rol and not _aff then
 -                                              if role == "moderator" then
 -                                                      -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway?
 -                                                      if _rol == "none" then _rol = nil; end
 -                                                      local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
 -                                                      for occupant_jid, occupant in pairs(self._occupants) do
 -                                                              if occupant.role == _rol then
 -                                                                      reply:tag("item", {
 -                                                                              nick = select(3, jid_split(occupant_jid)),
 -                                                                              role = _rol or "none",
 -                                                                              affiliation = occupant.affiliation or "none",
 -                                                                              jid = occupant.jid
 -                                                                              }):up();
 -                                                              end
 -                                                      end
 -                                                      origin.send(reply);
 -                                              else
 -                                                      origin.send(st.error_reply(stanza, "auth", "forbidden"));
 -                                              end
 -                                      else
 -                                              origin.send(st.error_reply(stanza, "cancel", "bad-request"));
 -                                      end
 -                              end
 -                      elseif type == "set" or type == "get" then
 -                              origin.send(st.error_reply(stanza, "cancel", "bad-request"));
 +function room_mt:handle_admin_query_get_command(origin, stanza)
 +      local actor = stanza.attr.from;
 +      local affiliation = self:get_affiliation(actor);
 +      local item = stanza.tags[1].tags[1];
 +      local _aff = item.attr.affiliation;
 +      local _aff_rank = valid_affiliations[_aff or "none"];
 +      local _rol = item.attr.role;
 +      if _aff and _aff_rank and not _rol then
 +              -- You need to be at least an admin, and be requesting info about your affifiliation or lower
 +              -- e.g. an admin can't ask for a list of owners
 +              local affiliation_rank = valid_affiliations[affiliation or "none"];
-               if affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank then
++              if affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank
++              or self:get_members_only() and self:get_whois() == "anyone" and affiliation_rank >= valid_affiliations.member then
 +                      local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
 +                      for jid in self:each_affiliation(_aff or "none") do
 +                              reply:tag("item", {affiliation = _aff, jid = jid}):up();
                        end
 -              elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
 -                      if self:get_affiliation(stanza.attr.from) ~= "owner" then
 -                              origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms"));
 -                      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, "modify", "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 == "groupchat" then
 -              local from = stanza.attr.from;
 -              local current_nick = self._jid_nick[from];
 -              local occupant = self._occupants[current_nick];
 -              if not occupant then -- not in room
 -                      origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
 -              elseif occupant.role == "visitor" then
 -                      origin.send(st.error_reply(stanza, "auth", "forbidden"));
 +                      origin.send(reply:up());
 +                      return true;
                else
 -                      local from = stanza.attr.from;
 -                      stanza.attr.from = current_nick;
 -                      local subject = stanza:get_child_text("subject");
 -                      if subject then
 -                              if occupant.role == "moderator" or
 -                                      ( self._data.changesubject and occupant.role == "participant" ) then -- and participant
 -                                      self:set_subject(current_nick, subject);
 -                              else
 -                                      stanza.attr.from = from;
 -                                      origin.send(st.error_reply(stanza, "auth", "forbidden"));
 -                              end
 -                      else
 -                              self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body"));
 -                      end
 -                      stanza.attr.from = from;
 -              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, 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];
 -              if current_nick then
 -                      stanza.attr.to = current_nick;
 -                      self:handle_to_occupant(origin, stanza);
 -                      stanza.attr.to = to;
 -              elseif type ~= "error" and type ~= "result" then
 -                      origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 +                      origin.send(st.error_reply(stanza, "auth", "forbidden"));
 +                      return true;
                end
 -      elseif stanza.name == "message" and not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1
 -              and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then
 -              local x = stanza.tags[1];
 -              local payload = (#x.tags == 1 and x.tags[1]);
 -              if payload and payload.name == "invite" and payload.attr.to then
 -                      local _from, _to = stanza.attr.from, stanza.attr.to;
 -                      local _invitee = jid_prep(payload.attr.to);
 -                      if _invitee then
 -                              local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1];
 -                              local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id})
 -                                      :tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
 -                                              :tag('invite', {from=_from})
 -                                                      :tag('reason'):text(_reason or ""):up()
 -                                              :up();
 -                                              if self:get_password() then
 -                                                      invite:tag("password"):text(self:get_password()):up();
 -                                              end
 -                                      invite:up()
 -                                      :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this
 -                                              :text(_reason or "")
 -                                      :up()
 -                                      :tag('body') -- Add a plain message for clients which don't support invites
 -                                              :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
 -                                      :up();
 -                              if self:get_members_only() and not self:get_affiliation(_invitee) then
 -                                      log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
 -                                      self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
 +      elseif _rol and valid_roles[_rol or "none"] and not _aff then
 +              local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation);
 +              if valid_roles[role or "none"] >= valid_roles.moderator then
 +                      if _rol == "none" then _rol = nil; end
 +                      local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
 +                      -- TODO: whois check here? (though fully anonymous rooms are not supported)
 +                      for occupant_jid, occupant in self:each_occupant() do
 +                              if occupant.role == _rol then
 +                                      local nick = select(3,jid_split(occupant_jid));
 +                                      self:build_item_list(occupant, reply, false, nick);
                                end
 -                              self:_route_stanza(invite);
 -                      else
 -                              origin.send(st.error_reply(stanza, "cancel", "jid-malformed"));
                        end
 +                      origin.send(reply:up());
 +                      return true;
                else
 -                      origin.send(st.error_reply(stanza, "cancel", "bad-request"));
 +                      origin.send(st.error_reply(stanza, "auth", "forbidden"));
 +                      return true;
                end
        else
 -              if type == "error" or type == "result" then return; end
 -              origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 +              origin.send(st.error_reply(stanza, "cancel", "bad-request"));
 +              return true;
        end
  end