001/**
002 *
003 * Copyright 2014 Lars Noschinski
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.filter;
018
019import java.util.logging.Level;
020import java.util.logging.Logger;
021
022import org.jivesoftware.smack.XMPPConnection;
023import org.jivesoftware.smack.packet.IQ;
024import org.jivesoftware.smack.packet.Stanza;
025
026import org.jxmpp.jid.DomainBareJid;
027import org.jxmpp.jid.EntityFullJid;
028import org.jxmpp.jid.Jid;
029
030/**
031 * Filters for packets which are a valid reply to an IQ request.
032 * <p>
033 * Such a stanza must have the same stanza id and must be an IQ stanza of type
034 * <code>RESULT</code> or <code>ERROR</code>. Moreover, it is necessary to check
035 * the <code>from</code> address to ignore forged replies.
036 * <p>
037 * We accept a <code>from</code> address if one of the following is true:
038 * <ul>
039 * <li>It matches the <code>to</code> address of the request.
040 * <li>The <code>to</code> address of the request was empty and the
041 * <code>from</code> address matches either the bare jid of the server or the
042 * (bare or full jid) of the client.
043 * <li>To <code>to</code> was our bare address and the <code>from</code> is empty.
044 * </ul>
045 * <p>
046 * For a discussion of the issues, see the thread "Spoofing of iq ids and
047 * misbehaving servers" from 2014-01 on the jdev@jabber.org mailing list
048 * and following discussion in February and March.
049 *
050 * @author Lars Noschinski
051 *
052 */
053public class IQReplyFilter implements StanzaFilter {
054    private static final Logger LOGGER = Logger.getLogger(IQReplyFilter.class.getName());
055
056    private final StanzaFilter iqAndIdFilter;
057    private final OrFilter fromFilter;
058    private final Jid to;
059    private final EntityFullJid local;
060    private final DomainBareJid server;
061    private final String packetId;
062
063    /**
064     * Filters for packets which are a valid reply to an IQ request.
065     * <p>
066     * Such a stanza must have the same stanza id and must be an IQ stanza of type
067     * <code>RESULT</code> or <code>ERROR</code>. Moreover, it is necessary to check
068     * the <code>from</code> address to ignore forged replies.
069     * <p>
070     * We accept a <code>from</code> address if one of the following is true:
071     * <ul>
072     * <li>It matches the <code>to</code> address of the request.
073     * <li>The <code>to</code> address of the request was empty and the
074     * <code>from</code> address matches either the bare jid of the server or the
075     * (bare or full jid) of the client.
076     * <li>To <code>to</code> was our bare address and the <code>from</code> is empty.
077     * </ul>
078     * <p>
079     * For a discussion of the issues, see the thread "Spoofing of iq ids and
080     * misbehaving servers" from 2014-01 on the jdev@jabber.org mailing list
081     * and following discussion in February and March.
082     *
083     * @param iqPacket An IQ request. Filter for replies to this packet.
084     * @param conn connection.
085     */
086    public IQReplyFilter(IQ iqPacket, XMPPConnection conn) {
087        if (!iqPacket.isRequestIQ()) {
088            throw new IllegalArgumentException("IQ must be a request IQ, i.e. of type 'get' or 'set'.");
089        }
090        to = iqPacket.getTo();
091        local = conn.getUser();
092        if (local == null) {
093            throw new IllegalArgumentException("Must have a local (user) JID set. Either you didn't configure one or you where not connected at least once");
094        }
095
096        server = conn.getXMPPServiceDomain();
097        packetId = iqPacket.getStanzaId();
098
099        StanzaFilter iqFilter = new OrFilter(IQTypeFilter.ERROR, IQTypeFilter.RESULT);
100        StanzaFilter idFilter = new StanzaIdFilter(iqPacket);
101        iqAndIdFilter = new AndFilter(iqFilter, idFilter);
102        fromFilter = new OrFilter();
103        fromFilter.addFilter(FromMatchesFilter.createFull(to));
104        if (to == null) {
105            fromFilter.addFilter(FromMatchesFilter.createBare(local));
106            fromFilter.addFilter(FromMatchesFilter.createFull(server));
107        }
108        else if (to.equals(local.asBareJid())) {
109            fromFilter.addFilter(FromMatchesFilter.createFull(null));
110        }
111    }
112
113    @Override
114    public boolean accept(Stanza packet) {
115        // First filter out everything that is not an IQ stanza and does not have the correct ID set.
116        if (!iqAndIdFilter.accept(packet))
117            return false;
118
119        // Second, check if the from attributes are correct and log potential IQ spoofing attempts
120        if (fromFilter.accept(packet)) {
121            return true;
122        } else {
123            String msg = String.format("Rejected potentially spoofed reply to IQ-packet. Filter settings: "
124                            + "packetId=%s, to=%s, local=%s, server=%s. Received packet with from=%s",
125                            packetId, to, local, server, packet.getFrom());
126            LOGGER.log(Level.WARNING, msg , packet);
127            return false;
128        }
129    }
130
131    @Override
132    public String toString() {
133        StringBuilder sb = new StringBuilder();
134        sb.append(getClass().getSimpleName());
135        sb.append(": iqAndIdFilter (").append(iqAndIdFilter.toString()).append("), ");
136        sb.append(": fromFilter (").append(fromFilter.toString()).append(')');
137        return sb.toString();
138    }
139}