1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-14 16:22:07 +01:00

Process only requested roster results (SMACK-538)

Prior to this change, Smack processes each RosterPacket (which is not of
type IQ.Type.RESULT) as a roster result.

Any other client on the XMPP network can send such a packet (not only
our server). This allows a malicious party to overwrite our Roster.

This patch changes smack so that a RosterPacket is discarded if it is
not a reply to a roster request.
This commit is contained in:
Lars Noschinski 2014-03-04 14:14:12 +01:00 committed by Florian Schmaus
parent 6c7296a37b
commit 9ac882241a

View file

@ -115,7 +115,7 @@ public class Roster {
presenceMap = new ConcurrentHashMap<String, Map<String, Presence>>(); presenceMap = new ConcurrentHashMap<String, Map<String, Presence>>();
// Listen for any roster packets. // Listen for any roster packets.
PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class); PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class);
connection.addPacketListener(new RosterPacketListener(), rosterFilter); connection.addPacketListener(new RosterPushListener(), rosterFilter);
// Listen for any presence packets. // Listen for any presence packets.
PacketFilter presenceFilter = new PacketTypeFilter(Presence.class); PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
presencePacketListener = new PresencePacketListener(); presencePacketListener = new PresencePacketListener();
@ -200,9 +200,9 @@ public class Roster {
RosterPacket packet = new RosterPacket(); RosterPacket packet = new RosterPacket();
if (rosterStore != null && connection.isRosterVersioningSupported()) { if (rosterStore != null && connection.isRosterVersioningSupported()) {
packet.setVersion(rosterStore.getRosterVersion()); packet.setVersion(rosterStore.getRosterVersion());
}
PacketFilter filter = new IQReplyFilter(packet, connection); PacketFilter filter = new IQReplyFilter(packet, connection);
connection.addPacketListener(new RosterResultListener(), filter); connection.addPacketListener(new RosterResultListener(), filter);
}
connection.sendPacket(packet); connection.sendPacket(packet);
} }
@ -727,6 +727,37 @@ public class Roster {
} }
/**
* Removes all the groups with no entries.
*
* This is used by {@link RosterPushListener} and {@link RosterResultListener} to
* cleanup groups after removing contacts.
*/
private void removeEmptyGroups() {
// We have to do this because RosterGroup.removeEntry removes the entry immediately
// (locally) and the group could remain empty.
// TODO Check the performance/logic for rosters with large number of groups
for (RosterGroup group : getGroups()) {
if (group.getEntryCount() == 0) {
groups.remove(group.getName());
}
}
}
/**
* Ignore ItemTypes as of RFC 6121, 2.1.2.5.
*
* This is used by {@link RosterPushListener} and {@link RosterResultListener}.
* */
private boolean hasValidSubscriptionType(RosterPacket.Item item) {
return item.getItemType().equals(RosterPacket.ItemType.none)
|| item.getItemType().equals(RosterPacket.ItemType.from)
|| item.getItemType().equals(RosterPacket.ItemType.to)
|| item.getItemType().equals(RosterPacket.ItemType.both);
}
/** /**
* An enumeration for the subscription mode options. * An enumeration for the subscription mode options.
*/ */
@ -879,48 +910,98 @@ public class Roster {
@Override @Override
public void processPacket(Packet packet) { public void processPacket(Packet packet) {
connection.removePacketListener(this); connection.removePacketListener(this);
if (packet instanceof RosterPacket) {
// Non-empty roster results are processed by the RosterPacketListener class
return;
}
if (!(packet instanceof IQ)) { if (!(packet instanceof IQ)) {
return; return;
} }
IQ result = (IQ)packet; IQ result = (IQ)packet;
if(result.getType().equals(IQ.Type.RESULT)){ if (!result.getType().equals(IQ.Type.RESULT)) {
return;
}
Collection<String> addedEntries = new ArrayList<String>(); Collection<String> addedEntries = new ArrayList<String>();
Collection<String> updatedEntries = new ArrayList<String>(); Collection<String> updatedEntries = new ArrayList<String>();
for(RosterPacket.Item item : rosterStore.getEntries()){ Collection<String> deletedEntries = new ArrayList<String>();
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
item.getItemType(), item.getItemStatus(), Roster.this, connection); if (packet instanceof RosterPacket) {
addUpdateEntry(addedEntries,updatedEntries,item,entry); nonemptyResult((RosterPacket) packet, addedEntries, updatedEntries, deletedEntries);
} else {
emptyResult(addedEntries, updatedEntries);
} }
synchronized (Roster.this) { synchronized (Roster.this) {
rosterInitialized = true; rosterInitialized = true;
Roster.this.notifyAll(); Roster.this.notifyAll();
} }
fireRosterChangedEvent(addedEntries,updatedEntries, // Fire event for roster listeners.
Collections.<String>emptyList()); fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);
}
private void emptyResult(Collection<String> addedEntries, Collection<String> updatedEntries) {
for(RosterPacket.Item item : rosterStore.getEntries()){
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
item.getItemType(), item.getItemStatus(), Roster.this, connection);
addUpdateEntry(addedEntries,updatedEntries,item,entry);
} }
} }
private void addEntries(Collection<String> addedEntries, Collection<String> updatedEntries,
Collection<String> deletedEntries, String version, Collection<Item> items) {
for (RosterPacket.Item item : items) {
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
item.getItemType(), item.getItemStatus(), Roster.this, connection);
addUpdateEntry(addedEntries, updatedEntries, item, entry);
}
List<String> toDelete = new ArrayList<String>();
// Delete all entries which where not added or updated
for (RosterEntry entry : entries.values()) {
toDelete.add(entry.getUser());
}
toDelete.removeAll(addedEntries);
toDelete.removeAll(updatedEntries);
for (String user : toDelete) {
deleteEntry(deletedEntries, entries.get(user));
}
if (rosterStore != null) {
rosterStore.resetEntries(items, version);
}
}
private void nonemptyResult(RosterPacket packet, Collection<String> addedEntries, Collection<String> updatedEntries, Collection<String> deletedEntries) {
RosterPacket rosterPacket = (RosterPacket) packet;
String version = rosterPacket.getVersion();
// Ignore items without valid subscription type
ArrayList<Item> validItems = new ArrayList<RosterPacket.Item>();
for (RosterPacket.Item item : rosterPacket.getRosterItems()) {
if (hasValidSubscriptionType(item)) {
validItems.add(item);
}
}
addEntries(addedEntries, updatedEntries, deletedEntries, version, validItems);
removeEmptyGroups();
}
} }
/** /**
* Listens for all roster packets and processes them. * Listens for all roster pushes and processes them.
*/ */
private class RosterPacketListener implements PacketListener { private class RosterPushListener implements PacketListener {
public void processPacket(Packet packet) { public void processPacket(Packet packet) {
RosterPacket rosterPacket = (RosterPacket) packet; RosterPacket rosterPacket = (RosterPacket) packet;
Collection<String> addedEntries = new ArrayList<String>(); if (!rosterPacket.getType().equals(IQ.Type.SET)) {
Collection<String> updatedEntries = new ArrayList<String>(); return;
Collection<String> deletedEntries = new ArrayList<String>(); }
String version = rosterPacket.getVersion(); String version = rosterPacket.getVersion();
if (rosterPacket.getType().equals(IQ.Type.SET)) {
// Roster push (RFC 6121, 2.1.6) // Roster push (RFC 6121, 2.1.6)
// A roster push with a non-empty from not matching our address MUST be ignored // A roster push with a non-empty from not matching our address MUST be ignored
String jid = StringUtils.parseBareAddress(connection.getUser()); String jid = StringUtils.parseBareAddress(connection.getUser());
@ -934,40 +1015,17 @@ public class Roster {
if (items.size() != 1) { if (items.size() != 1) {
return; return;
} }
Collection<String> addedEntries = new ArrayList<String>();
Collection<String> updatedEntries = new ArrayList<String>();
Collection<String> deletedEntries = new ArrayList<String>();
Item item = items.iterator().next(); Item item = items.iterator().next();
processPushItem(addedEntries, updatedEntries, deletedEntries, version, item); processPushItem(addedEntries, updatedEntries, deletedEntries, version, item);
connection.sendPacket(IQ.createResultIQ(rosterPacket)); connection.sendPacket(IQ.createResultIQ(rosterPacket));
}
else {
// Roster result (RFC 6121, 2.1.4)
// Ignore items without valid subscription type removeEmptyGroups();
ArrayList<Item> validItems = new ArrayList<RosterPacket.Item>();
for (RosterPacket.Item item : rosterPacket.getRosterItems()) {
if (hasValidSubscriptionType(item)) {
validItems.add(item);
}
}
processResult(addedEntries, updatedEntries, deletedEntries, version, validItems);
}
// Remove all the groups with no entries. We have to do this because
// RosterGroup.removeEntry removes the entry immediately (locally) and the
// group could remain empty.
// TODO Check the performance/logic for rosters with large number of groups
for (RosterGroup group : getGroups()) {
if (group.getEntryCount() == 0) {
groups.remove(group.getName());
}
}
// Mark the roster as initialized.
synchronized (Roster.this) {
rosterInitialized = true;
Roster.this.notifyAll();
}
// Fire event for roster listeners. // Fire event for roster listeners.
fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries); fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);
@ -992,38 +1050,6 @@ public class Roster {
} }
} }
private void processResult(Collection<String> addedEntries, Collection<String> updatedEntries,
Collection<String> deletedEntries, String version, Collection<Item> items) {
for (RosterPacket.Item item : items) {
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
item.getItemType(), item.getItemStatus(), Roster.this, connection);
addUpdateEntry(addedEntries, updatedEntries, item, entry);
}
List<String> toDelete = new ArrayList<String>();
// Delete all entries which where not added or updated
for (RosterEntry entry : entries.values()) {
toDelete.add(entry.getUser());
}
toDelete.removeAll(addedEntries);
toDelete.removeAll(updatedEntries);
for (String user : toDelete) {
deleteEntry(deletedEntries, entries.get(user));
}
if (rosterStore != null) {
rosterStore.resetEntries(items, version);
}
}
/* Ignore ItemTypes as of RFC 6121, 2.1.2.5. */
private boolean hasValidSubscriptionType(RosterPacket.Item item) {
return item.getItemType().equals(RosterPacket.ItemType.none)
|| item.getItemType().equals(RosterPacket.ItemType.from)
|| item.getItemType().equals(RosterPacket.ItemType.to)
|| item.getItemType().equals(RosterPacket.ItemType.both);
}
} }
} }