IQReplyFilter.java

  1. /**
  2.  *
  3.  * Copyright 2014 Lars Noschinski
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.filter;

  18. import java.util.logging.Level;
  19. import java.util.logging.Logger;

  20. import org.jivesoftware.smack.XMPPConnection;
  21. import org.jivesoftware.smack.packet.IQ;
  22. import org.jivesoftware.smack.packet.Stanza;

  23. import org.jxmpp.jid.DomainBareJid;
  24. import org.jxmpp.jid.EntityFullJid;
  25. import org.jxmpp.jid.Jid;

  26. /**
  27.  * Filters for packets which are a valid reply to an IQ request.
  28.  * <p>
  29.  * Such a stanza must have the same stanza id and must be an IQ stanza of type
  30.  * <code>RESULT</code> or <code>ERROR</code>. Moreover, it is necessary to check
  31.  * the <code>from</code> address to ignore forged replies.
  32.  * <p>
  33.  * We accept a <code>from</code> address if one of the following is true:
  34.  * <ul>
  35.  * <li>It matches the <code>to</code> address of the request.
  36.  * <li>The <code>to</code> address of the request was empty and the
  37.  * <code>from</code> address matches either the bare jid of the server or the
  38.  * (bare or full jid) of the client.
  39.  * <li>To <code>to</code> was our bare address and the <code>from</code> is empty.
  40.  * </ul>
  41.  * <p>
  42.  * For a discussion of the issues, see the thread "Spoofing of iq ids and
  43.  * misbehaving servers" from 2014-01 on the jdev@jabber.org mailing list
  44.  * and following discussion in February and March.
  45.  *
  46.  * @author Lars Noschinski
  47.  *
  48.  */
  49. public class IQReplyFilter implements StanzaFilter {
  50.     private static final Logger LOGGER = Logger.getLogger(IQReplyFilter.class.getName());

  51.     private final StanzaFilter iqAndIdFilter;
  52.     private final OrFilter fromFilter;
  53.     private final Jid to;
  54.     private final EntityFullJid local;
  55.     private final DomainBareJid server;
  56.     private final String packetId;

  57.     /**
  58.      * Filters for packets which are a valid reply to an IQ request.
  59.      * <p>
  60.      * Such a stanza must have the same stanza id and must be an IQ stanza of type
  61.      * <code>RESULT</code> or <code>ERROR</code>. Moreover, it is necessary to check
  62.      * the <code>from</code> address to ignore forged replies.
  63.      * <p>
  64.      * We accept a <code>from</code> address if one of the following is true:
  65.      * <ul>
  66.      * <li>It matches the <code>to</code> address of the request.
  67.      * <li>The <code>to</code> address of the request was empty and the
  68.      * <code>from</code> address matches either the bare jid of the server or the
  69.      * (bare or full jid) of the client.
  70.      * <li>To <code>to</code> was our bare address and the <code>from</code> is empty.
  71.      * </ul>
  72.      * <p>
  73.      * For a discussion of the issues, see the thread "Spoofing of iq ids and
  74.      * misbehaving servers" from 2014-01 on the jdev@jabber.org mailing list
  75.      * and following discussion in February and March.
  76.      *
  77.      * @param iqPacket An IQ request. Filter for replies to this packet.
  78.      * @param conn connection.
  79.      */
  80.     public IQReplyFilter(IQ iqPacket, XMPPConnection conn) {
  81.         if (!iqPacket.isRequestIQ()) {
  82.             throw new IllegalArgumentException("IQ must be a request IQ, i.e. of type 'get' or 'set'.");
  83.         }
  84.         to = iqPacket.getTo();
  85.         local = conn.getUser();
  86.         if (local == null) {
  87.             throw new IllegalArgumentException("Must have a local (user) JID set. Either you didn't configure one or you where not connected at least once");
  88.         }

  89.         server = conn.getXMPPServiceDomain();
  90.         packetId = iqPacket.getStanzaId();

  91.         StanzaFilter iqFilter = new OrFilter(IQTypeFilter.ERROR, IQTypeFilter.RESULT);
  92.         StanzaFilter idFilter = new StanzaIdFilter(iqPacket);
  93.         iqAndIdFilter = new AndFilter(iqFilter, idFilter);
  94.         fromFilter = new OrFilter();
  95.         fromFilter.addFilter(FromMatchesFilter.createFull(to));
  96.         if (to == null) {
  97.             fromFilter.addFilter(FromMatchesFilter.createBare(local));
  98.             fromFilter.addFilter(FromMatchesFilter.createFull(server));
  99.         }
  100.         else if (to.equals(local.asBareJid())) {
  101.             fromFilter.addFilter(FromMatchesFilter.createFull(null));
  102.         }
  103.     }

  104.     @Override
  105.     public boolean accept(Stanza packet) {
  106.         // First filter out everything that is not an IQ stanza and does not have the correct ID set.
  107.         if (!iqAndIdFilter.accept(packet))
  108.             return false;

  109.         // Second, check if the from attributes are correct and log potential IQ spoofing attempts
  110.         if (fromFilter.accept(packet)) {
  111.             return true;
  112.         } else {
  113.             String msg = String.format("Rejected potentially spoofed reply to IQ-packet. Filter settings: "
  114.                             + "packetId=%s, to=%s, local=%s, server=%s. Received packet with from=%s",
  115.                             packetId, to, local, server, packet.getFrom());
  116.             LOGGER.log(Level.WARNING, msg , packet);
  117.             return false;
  118.         }
  119.     }

  120.     @Override
  121.     public String toString() {
  122.         StringBuilder sb = new StringBuilder();
  123.         sb.append(getClass().getSimpleName());
  124.         sb.append(": iqAndIdFilter (").append(iqAndIdFilter.toString()).append("), ");
  125.         sb.append(": fromFilter (").append(fromFilter.toString()).append(')');
  126.         return sb.toString();
  127.     }
  128. }