IQRouter.java 17.8 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile: IQRouter.java,v $
 * $Revision: 3135 $
 * $Date: 2005-12-01 02:03:04 -0300 (Thu, 01 Dec 2005) $
 *
6
 * Copyright (C) 2007 Jive Software. All rights reserved.
7 8 9 10 11
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

12
package org.jivesoftware.openfire;
13 14

import org.dom4j.Element;
15 16 17 18 19 20 21 22 23
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.privacy.PrivacyList;
import org.jivesoftware.openfire.privacy.PrivacyListManager;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.user.UserManager;
Gaston Dombiak's avatar
Gaston Dombiak committed
24 25 26
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.xmpp.packet.*;
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Routes iq packets throughout the server. Routing is based on the recipient
 * and sender addresses. The typical packet will often be routed twice, once
 * from the sender to some internal server component for handling or processing,
 * and then back to the router to be delivered to it's final destination.
 *
 * @author Iain Shigeoka
 */
public class IQRouter extends BasicModule {

    private RoutingTable routingTable;
44
    private MulticastRouter multicastRouter;
45 46 47 48 49 50
    private String serverName;
    private List<IQHandler> iqHandlers = new ArrayList<IQHandler>();
    private Map<String, IQHandler> namespace2Handlers = new ConcurrentHashMap<String, IQHandler>();
    private Map<String, IQResultListener> resultListeners =
            new ConcurrentHashMap<String, IQResultListener>();
    private SessionManager sessionManager;
51
    private UserManager userManager;
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

    /**
     * Creates a packet router.
     */
    public IQRouter() {
        super("XMPP IQ Router");
    }

    /**
     * <p>Performs the actual packet routing.</p>
     * <p>You routing is considered 'quick' and implementations may not take
     * excessive amounts of time to complete the routing. If routing will take
     * a long amount of time, the actual routing should be done in another thread
     * so this method returns quickly.</p>
     * <h2>Warning</h2>
     * <p>Be careful to enforce concurrency DbC of concurrent by synchronizing
     * any accesses to class resources.</p>
     *
     * @param packet The packet to route
     * @throws NullPointerException If the packet is null
     */
    public void route(IQ packet) {
        if (packet == null) {
            throw new NullPointerException();
        }
77 78
        JID sender = packet.getFrom();
        ClientSession session = sessionManager.getSession(sender);
79 80 81 82 83 84 85 86 87 88 89
        try {
            // Invoke the interceptors before we process the read packet
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, false);
            JID to = packet.getTo();
            if (session != null && to != null && session.getStatus() == Session.STATUS_CONNECTED &&
                    !serverName.equals(to.toString())) {
                // User is requesting this server to authenticate for another server. Return
                // a bad-request error
                IQ reply = IQ.createResultIQ(packet);
                reply.setChildElement(packet.getChildElement().createCopy());
                reply.setError(PacketError.Condition.bad_request);
90
                session.process(reply);
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
                Log.warn("User tried to authenticate with this server using an unknown receipient: " +
                        packet);
            }
            else if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED || (
                    isLocalServer(to) && (
                            "jabber:iq:auth".equals(packet.getChildElement().getNamespaceURI()) ||
                                    "jabber:iq:register"
                                            .equals(packet.getChildElement().getNamespaceURI()) ||
                                    "urn:ietf:params:xml:ns:xmpp-bind"
                                            .equals(packet.getChildElement().getNamespaceURI())))) {
                handle(packet);
            }
            else {
                IQ reply = IQ.createResultIQ(packet);
                reply.setChildElement(packet.getChildElement().createCopy());
                reply.setError(PacketError.Condition.not_authorized);
107
                session.process(reply);
108 109 110
            }
            // Invoke the interceptors after we have processed the read packet
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, true);
111
        }
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
        catch (PacketRejectedException e) {
            if (session != null) {
                // An interceptor rejected this packet so answer a not_allowed error
                IQ reply = new IQ();
                reply.setChildElement(packet.getChildElement().createCopy());
                reply.setID(packet.getID());
                reply.setTo(session.getAddress());
                reply.setFrom(packet.getTo());
                reply.setError(PacketError.Condition.not_allowed);
                session.process(reply);
                // Check if a message notifying the rejection should be sent
                if (e.getRejectionMessage() != null && e.getRejectionMessage().trim().length() > 0) {
                    // A message for the rejection will be sent to the sender of the rejected packet
                    Message notification = new Message();
                    notification.setTo(session.getAddress());
                    notification.setFrom(packet.getTo());
                    notification.setBody(e.getRejectionMessage());
                    session.process(notification);
                }
            }
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
        }
    }

    /**
     * <p>Adds a new IQHandler to the list of registered handler. The new IQHandler will be
     * responsible for handling IQ packet whose namespace matches the namespace of the
     * IQHandler.</p>
     *
     * An IllegalArgumentException may be thrown if the IQHandler to register was already provided
     * by the server. The server provides a certain list of IQHandlers when the server is
     * started up.
     *
     * @param handler the IQHandler to add to the list of registered handler.
     */
    public void addHandler(IQHandler handler) {
        if (iqHandlers.contains(handler)) {
            throw new IllegalArgumentException("IQHandler already provided by the server");
        }
        // Ask the handler to be initialized
        handler.initialize(XMPPServer.getInstance());
        // Register the handler as the handler of the namespace
        namespace2Handlers.put(handler.getInfo().getNamespace(), handler);
    }

    /**
     * <p>Removes an IQHandler from the list of registered handler. The IQHandler to remove was
     * responsible for handling IQ packet whose namespace matches the namespace of the
     * IQHandler.</p>
     *
     * An IllegalArgumentException may be thrown if the IQHandler to remove was already provided
     * by the server. The server provides a certain list of IQHandlers when the server is
     * started up.
     *
     * @param handler the IQHandler to remove from the list of registered handler.
     */
    public void removeHandler(IQHandler handler) {
        if (iqHandlers.contains(handler)) {
            throw new IllegalArgumentException("Cannot remove an IQHandler provided by the server");
        }
        // Unregister the handler as the handler of the namespace
        namespace2Handlers.remove(handler.getInfo().getNamespace());
    }

    /**
     * Adds an {@link IQResultListener} that will be invoked when an IQ result is sent to the
     * server itself and is of type result or error. This is a nice way for the server to
     * send IQ packets to other XMPP entities and be waked up when a response is received back.<p>
     *
     * Once an IQ result was received, the listener will be invoked and removed from
     * the list of listeners.
     *
     * @param id the id of the IQ packet being sent from the server to an XMPP entity.
     * @param listener the IQResultListener that will be invoked when an answer is received
     */
186
    public void addIQResultListener(String id, IQResultListener listener) {
187 188 189
        // TODO Add a check that if no IQ reply was received for a while then an IQ error should
        // be generated by the server and simulate like the client sent it. This will let listeners
        // react and be removed from the collection
190 191 192 193 194 195 196
        resultListeners.put(id, listener);
    }

    public void initialize(XMPPServer server) {
        super.initialize(server);
        serverName = server.getServerInfo().getName();
        routingTable = server.getRoutingTable();
197
        multicastRouter = server.getMulticastRouter();
198 199
        iqHandlers.addAll(server.getIQHandlers());
        sessionManager = server.getSessionManager();
200
        userManager = server.getUserManager();
201 202 203 204 205
    }

    /**
     * A JID is considered local if:
     * 1) is null or
206 207 208 209 210 211
     * 2) has no domain or domain is empty
     * or
     * if it's not a full JID and it was sent to the server itself.
     *
     * @param recipientJID address to check.
     * @return true if the specified address belongs to the local server.
212 213
     */
    private boolean isLocalServer(JID recipientJID) {
214 215 216 217 218 219 220 221 222 223 224 225 226
        // Check if no address was specified in the IQ packet
        boolean implicitServer =
                recipientJID == null || recipientJID.getDomain() == null || "".equals(recipientJID.getDomain());
        if (!implicitServer) {
            // We found an address. Now check if it's a bare or full JID
            if (recipientJID.getNode() == null || recipientJID.getResource() == null) {
                // Address is a bare JID so check if it was sent to the server itself
                return serverName.equals(recipientJID.getDomain());
            }
            // Address is a full JID. IQ packets sent to full JIDs are not handle by the server
            return false;
        }
        return true;
227 228 229 230 231 232 233
    }

    private void handle(IQ packet) {
        JID recipientJID = packet.getTo();
        // Check if the packet was sent to the server hostname
        if (recipientJID != null && recipientJID.getNode() == null &&
                recipientJID.getResource() == null && serverName.equals(recipientJID.getDomain())) {
234 235 236 237 238 239 240 241
            Element childElement = packet.getChildElement();
            if (childElement != null && childElement.element("addresses") != null) {
                // Packet includes multicast processing instructions. Ask the multicastRouter
                // to route this packet
                multicastRouter.route(packet);
                return;
            }
            else if (IQ.Type.result == packet.getType() || IQ.Type.error == packet.getType()) {
242 243 244
                // The server got an answer to an IQ packet that was sent from the server
                IQResultListener iqResultListener = resultListeners.remove(packet.getID());
                if (iqResultListener != null) {
245 246 247 248 249 250
                    try {
                        iqResultListener.receivedAnswer(packet);
                    }
                    catch (Exception e) {
                        Log.error("Error processing answer of remote entity", e);
                    }
251 252 253 254 255 256 257
                    return;
                }
            }
        }
        try {
            // Check for registered components, services or remote servers
            if (recipientJID != null) {
Gaston Dombiak's avatar
Gaston Dombiak committed
258
                if (routingTable.hasComponentRoute(recipientJID) || routingTable.hasServerRoute(recipientJID)) {
259
                    // A component/service/remote server was found that can handle the Packet
Gaston Dombiak's avatar
Gaston Dombiak committed
260
                    routingTable.routePacket(recipientJID, packet, false);
261 262 263 264 265 266 267 268 269 270 271
                    return;
                }
            }
            if (isLocalServer(recipientJID)) {
                // Let the server handle the Packet
                Element childElement = packet.getChildElement();
                String namespace = null;
                if (childElement != null) {
                    namespace = childElement.getNamespaceURI();
                }
                if (namespace == null) {
272
                    if (packet.getType() != IQ.Type.result && packet.getType() != IQ.Type.error) {
273 274 275 276 277
                        // Do nothing. We can't handle queries outside of a valid namespace
                        Log.warn("Unknown packet " + packet);
                    }
                }
                else {
278
                    // Check if communication to local users is allowed
Gaston Dombiak's avatar
Gaston Dombiak committed
279 280 281
                    if (recipientJID != null && userManager.isRegisteredUser(recipientJID.getNode())) {
                        PrivacyList list =
                                PrivacyListManager.getInstance().getDefaultPrivacyList(recipientJID.getNode());
282 283 284 285 286 287 288 289 290
                        if (list != null && list.shouldBlockPacket(packet)) {
                            // Communication is blocked
                            if (IQ.Type.set == packet.getType() || IQ.Type.get == packet.getType()) {
                                // Answer that the service is unavailable
                                sendErrorPacket(packet, PacketError.Condition.service_unavailable);
                            }
                            return;
                        }
                    }
291 292 293 294
                    IQHandler handler = getHandler(namespace);
                    if (handler == null) {
                        if (recipientJID == null) {
                            // Answer an error since the server can't handle the requested namespace
295
                            sendErrorPacket(packet, PacketError.Condition.service_unavailable);
296 297 298 299
                        }
                        else if (recipientJID.getNode() == null ||
                                "".equals(recipientJID.getNode())) {
                            // Answer an error if JID is of the form <domain>
300
                            sendErrorPacket(packet, PacketError.Condition.feature_not_implemented);
301 302 303 304
                        }
                        else {
                            // JID is of the form <node@domain>
                            // Answer an error since the server can't handle packets sent to a node
305
                            sendErrorPacket(packet, PacketError.Condition.service_unavailable);
306 307 308 309 310 311 312 313
                        }
                    }
                    else {
                        handler.process(packet);
                    }
                }
            }
            else {
314
                // JID is of the form <node@domain/resource> or belongs to a remote server
Gaston Dombiak's avatar
Gaston Dombiak committed
315
                routingTable.routePacket(recipientJID, packet, false);
316 317 318 319 320 321
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error.routing"), e);
            Session session = sessionManager.getSession(packet.getFrom());
            if (session != null) {
322 323 324
                IQ reply = IQ.createResultIQ(packet);
                reply.setError(PacketError.Condition.internal_server_error);
                session.process(reply);
325 326 327 328
            }
        }
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
329
    private void sendErrorPacket(IQ originalPacket, PacketError.Condition condition) {
330 331 332 333
        if (IQ.Type.error == originalPacket.getType()) {
            Log.error("Cannot reply an IQ error to another IQ error: " + originalPacket);
            return;
        }
334 335 336
        IQ reply = IQ.createResultIQ(originalPacket);
        reply.setChildElement(originalPacket.getChildElement().createCopy());
        reply.setError(condition);
337 338 339 340 341 342
        // Check if the server was the sender of the IQ
        if (serverName.equals(originalPacket.getFrom().toString())) {
            // Just let the IQ router process the IQ error reply
            handle(reply);
            return;
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
343
        // Route the error packet to the original sender of the IQ.
Gaston Dombiak's avatar
Gaston Dombiak committed
344
        routingTable.routePacket(reply.getTo(), reply, false);
345 346
    }

347 348 349 350 351 352 353 354 355 356 357 358 359 360
    private IQHandler getHandler(String namespace) {
        IQHandler handler = namespace2Handlers.get(namespace);
        if (handler == null) {
            for (IQHandler handlerCandidate : iqHandlers) {
                IQHandlerInfo handlerInfo = handlerCandidate.getInfo();
                if (handlerInfo != null && namespace.equalsIgnoreCase(handlerInfo.getNamespace())) {
                    handler = handlerCandidate;
                    namespace2Handlers.put(namespace, handler);
                    break;
                }
            }
        }
        return handler;
    }
Gaston Dombiak's avatar
Gaston Dombiak committed
361 362 363 364

    /**
     * Notification message indicating that a packet has failed to be routed to the receipient.
     *
365
     * @param receipient address of the entity that failed to receive the packet.
Gaston Dombiak's avatar
Gaston Dombiak committed
366 367
     * @param packet IQ packet that failed to be sent to the receipient.
     */
368
    public void routingFailed(JID receipient, Packet packet) {
Gaston Dombiak's avatar
Gaston Dombiak committed
369 370 371 372 373 374 375 376 377 378 379 380
        IQ iq = (IQ) packet;
        // If a route to the target address was not found then try to answer a
        // service_unavailable error code to the sender of the IQ packet
        if (IQ.Type.result != iq.getType() && IQ.Type.error != iq.getType()) {
            Log.info("Packet sent to unreachable address " + packet);
            sendErrorPacket(iq, PacketError.Condition.service_unavailable);
        }
        else {
            Log.warn("Error or result packet could not be delivered " + packet);
        }
    }
}