IQRouter.java 24.5 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) 2005-2008 Jive Software. All rights reserved.
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
19 20
 */

21
package org.jivesoftware.openfire;
22

23 24 25 26 27 28 29
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

30
import org.dom4j.Element;
31 32 33 34 35 36 37
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;
38
import org.jivesoftware.openfire.session.LocalClientSession;
39 40
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.user.UserManager;
Gaston Dombiak's avatar
Gaston Dombiak committed
41
import org.jivesoftware.util.LocaleUtils;
42
import org.jivesoftware.util.TaskEngine;
43 44
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
45 46 47 48 49 50
import org.xmpp.component.IQResultListener;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
51 52 53 54 55 56 57 58 59 60 61

/**
 * 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 {

62 63 64
	private static final Logger Log = LoggerFactory.getLogger(IQRouter.class);

	private RoutingTable routingTable;
65
    private MulticastRouter multicastRouter;
66 67 68
    private String serverName;
    private List<IQHandler> iqHandlers = new ArrayList<IQHandler>();
    private Map<String, IQHandler> namespace2Handlers = new ConcurrentHashMap<String, IQHandler>();
69 70
    private Map<String, IQResultListener> resultListeners = new ConcurrentHashMap<String, IQResultListener>();
    private Map<String, Long> resultTimeout = new ConcurrentHashMap<String, Long>();
71
    private SessionManager sessionManager;
72
    private UserManager userManager;
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

    /**
     * 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();
        }
98 99
        JID sender = packet.getFrom();
        ClientSession session = sessionManager.getSession(sender);
100 101 102 103 104 105 106 107 108 109 110
        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);
111
                session.process(reply);
112
                Log.warn("User tried to authenticate with this server using an unknown receipient: " +
113
                        packet.toXML());
114 115 116 117 118 119 120 121 122
            }
            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);
123
            } else if (packet.getType() == IQ.Type.get || packet.getType() == IQ.Type.set) {
124 125 126
                IQ reply = IQ.createResultIQ(packet);
                reply.setChildElement(packet.getChildElement().createCopy());
                reply.setError(PacketError.Condition.not_authorized);
127
                session.process(reply);
128 129 130
            }
            // Invoke the interceptors after we have processed the read packet
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, true);
131
        }
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
        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);
                }
            }
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 186 187 188 189 190 191 192 193 194 195
        }
    }

    /**
     * <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());
    }

    /**
196 197 198 199
	 * 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>
200
     *
201 202
	 * Once an IQ result was received, the listener will be invoked and removed
	 * from the list of listeners.<p>
203
	 *
204 205 206
	 * If no result was received within one minute, the timeout method of the
	 * listener will be invoked and the listener will be removed from the list
	 * of listeners.
207
     *
208 209 210 211 212 213
	 * @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
214
     */
215
    public void addIQResultListener(String id, IQResultListener listener) {
216 217 218 219 220 221 222 223
        addIQResultListener(id, listener, 60 * 1000);
    }

    /**
	 * 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>
224
	 *
225 226
	 * Once an IQ result was received, the listener will be invoked and removed
	 * from the list of listeners.<p>
227
	 *
228 229 230
	 * If no result was received within the specified amount of milliseconds,
	 * the timeout method of the listener will be invoked and the listener will
	 * be removed from the list of listeners.<p>
231
	 *
232 233 234 235
	 * Note that the listener will remain active for <em>at least</em> the
	 * specified timeout value. The listener will not be removed at the exact
	 * moment it times out. Instead, purging of timed out listeners is a
	 * periodic scheduled job.
236
	 *
237 238 239 240 241 242 243 244 245
	 * @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.
	 * @param timeoutmillis
	 *            The amount of milliseconds after which waiting for a response
	 *            should be stopped.
246
	 */
247
    public void addIQResultListener(String id, IQResultListener listener, long timeoutmillis) {
248
        resultListeners.put(id, listener);
249
        resultTimeout.put(id, System.currentTimeMillis() + timeoutmillis);
250 251
    }

252 253
    @Override
	public void initialize(XMPPServer server) {
254
        super.initialize(server);
255
        TaskEngine.getInstance().scheduleAtFixedRate(new TimeoutTask(), 5000, 5000);
256
        serverName = server.getServerInfo().getXMPPDomain();
257
        routingTable = server.getRoutingTable();
258
        multicastRouter = server.getMulticastRouter();
259 260
        iqHandlers.addAll(server.getIQHandlers());
        sessionManager = server.getSessionManager();
261
        userManager = server.getUserManager();
262 263 264 265 266
    }

    /**
     * A JID is considered local if:
     * 1) is null or
267 268 269 270 271 272
     * 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.
273 274
     */
    private boolean isLocalServer(JID recipientJID) {
275 276 277 278 279 280 281 282 283 284 285 286 287
        // 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;
288 289 290 291 292 293 294
    }

    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())) {
295 296 297 298 299 300 301
            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;
            }
302
        }
303
        if (packet.getID() != null && (IQ.Type.result == packet.getType() || IQ.Type.error == packet.getType())) {
304 305 306
            // The server got an answer to an IQ packet that was sent from the server
            IQResultListener iqResultListener = resultListeners.remove(packet.getID());
            if (iqResultListener != null) {
307
                resultTimeout.remove(packet.getID());
308
                if (iqResultListener != null) {
309 310 311 312
                    try {
                        iqResultListener.receivedAnswer(packet);
                    }
                    catch (Exception e) {
313 314 315
                        Log.error(
                                "Error processing answer of remote entity. Answer: "
                                        + packet.toXML(), e);
316
                    }
317 318 319 320 321 322
                    return;
                }
            }
        }
        try {
            // Check for registered components, services or remote servers
323 324
            if (recipientJID != null &&
                    (routingTable.hasComponentRoute(recipientJID) || routingTable.hasServerRoute(recipientJID))) {
325 326 327
                // A component/service/remote server was found that can handle the Packet
                routingTable.routePacket(recipientJID, packet, false);
                return;
328
            }
329

330
           if (isLocalServer(recipientJID)) {
331 332 333 334 335 336 337
                // Let the server handle the Packet
                Element childElement = packet.getChildElement();
                String namespace = null;
                if (childElement != null) {
                    namespace = childElement.getNamespaceURI();
                }
                if (namespace == null) {
338
                    if (packet.getType() != IQ.Type.result && packet.getType() != IQ.Type.error) {
339
                        // Do nothing. We can't handle queries outside of a valid namespace
340
                        Log.warn("Unknown packet " + packet.toXML());
341 342 343
                    }
                }
                else {
344
                    // Check if communication to local users is allowed
Gaston Dombiak's avatar
Gaston Dombiak committed
345 346 347
                    if (recipientJID != null && userManager.isRegisteredUser(recipientJID.getNode())) {
                        PrivacyList list =
                                PrivacyListManager.getInstance().getDefaultPrivacyList(recipientJID.getNode());
348 349 350 351 352 353 354 355 356
                        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;
                        }
                    }
357 358 359 360
                    IQHandler handler = getHandler(namespace);
                    if (handler == null) {
                        if (recipientJID == null) {
                            // Answer an error since the server can't handle the requested namespace
361
                            sendErrorPacket(packet, PacketError.Condition.service_unavailable);
362 363 364 365
                        }
                        else if (recipientJID.getNode() == null ||
                                "".equals(recipientJID.getNode())) {
                            // Answer an error if JID is of the form <domain>
366
                            sendErrorPacket(packet, PacketError.Condition.feature_not_implemented);
367 368 369 370
                        }
                        else {
                            // JID is of the form <node@domain>
                            // Answer an error since the server can't handle packets sent to a node
371
                            sendErrorPacket(packet, PacketError.Condition.service_unavailable);
372 373 374 375 376 377 378 379
                        }
                    }
                    else {
                        handler.process(packet);
                    }
                }
            }
            else {
380 381 382 383 384 385 386 387 388 389

				// RFC 6121 8.5.1.  No Such User http://xmpp.org/rfcs/rfc6121.html#rules-localpart-nosuchuser
				// If the 'to' address specifies a bare JID <localpart@domainpart> or full JID <localpart@domainpart/resourcepart> where the domainpart of the JID matches a configured domain that is serviced by the server itself, the server MUST proceed as follows.
				// If the user account identified by the 'to' attribute does not exist, how the stanza is processed depends on the stanza type.
				if (recipientJID != null && recipientJID.getNode() != null && serverName.equals(recipientJID.getDomain()) && !userManager.isRegisteredUser(recipientJID.getNode()) && sessionManager.getSession(recipientJID) == null && (IQ.Type.set == packet.getType() || IQ.Type.get == packet.getType())) {
					// For an IQ stanza, the server MUST return a <service-unavailable/> stanza error to the sender.
					sendErrorPacket(packet, PacketError.Condition.service_unavailable);
					return;
				}

390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
                ClientSession session = sessionManager.getSession(packet.getFrom());
                boolean isAcceptable = true;
                if (session instanceof LocalClientSession) {
                    // Check if we could process IQ stanzas from the recipient.
                    // If not, return a not-acceptable error as per XEP-0016:
                    // If the user attempts to send an outbound stanza to a contact and that stanza type is blocked, the user's server MUST NOT route the stanza to the contact but instead MUST return a <not-acceptable/> error
                    IQ dummyIQ = packet.createCopy();
                    dummyIQ.setFrom(packet.getTo());
                    dummyIQ.setTo(packet.getFrom());
                    if (!((LocalClientSession) session).canProcess(dummyIQ)) {
                        packet.setTo(session.getAddress());
                        packet.setFrom((JID) null);
                        packet.setError(PacketError.Condition.not_acceptable);
                        session.process(packet);
                        isAcceptable = false;
                    }
                }

                if (isAcceptable) {
                    // JID is of the form <node@domain/resource> or belongs to a remote server
                    // or to an uninstalled component
                    routingTable.routePacket(recipientJID, packet, false);
                }
413 414 415 416 417 418
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error.routing"), e);
            Session session = sessionManager.getSession(packet.getFrom());
            if (session != null) {
419 420 421
                IQ reply = IQ.createResultIQ(packet);
                reply.setError(PacketError.Condition.internal_server_error);
                session.process(reply);
422 423 424 425
            }
        }
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
426
    private void sendErrorPacket(IQ originalPacket, PacketError.Condition condition) {
427
        if (IQ.Type.error == originalPacket.getType()) {
428
            Log.error("Cannot reply an IQ error to another IQ error: " + originalPacket.toXML());
429 430
            return;
        }
431 432 433
        IQ reply = IQ.createResultIQ(originalPacket);
        reply.setChildElement(originalPacket.getChildElement().createCopy());
        reply.setError(condition);
434 435 436 437 438 439
        // 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
440
        // Route the error packet to the original sender of the IQ.
441
        routingTable.routePacket(reply.getTo(), reply, true);
442 443
    }

444 445 446 447 448 449 450 451 452 453 454 455 456 457
    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
458 459 460 461

    /**
     * Notification message indicating that a packet has failed to be routed to the receipient.
     *
462
     * @param receipient address of the entity that failed to receive the packet.
Gaston Dombiak's avatar
Gaston Dombiak committed
463 464
     * @param packet IQ packet that failed to be sent to the receipient.
     */
465
    public void routingFailed(JID receipient, Packet packet) {
Gaston Dombiak's avatar
Gaston Dombiak committed
466 467 468 469
        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()) {
470
            Log.info("Packet sent to unreachable address " + packet.toXML());
Gaston Dombiak's avatar
Gaston Dombiak committed
471 472 473
            sendErrorPacket(iq, PacketError.Condition.service_unavailable);
        }
        else {
474
            Log.warn("Error or result packet could not be delivered " + packet.toXML());
Gaston Dombiak's avatar
Gaston Dombiak committed
475 476
        }
    }
477

478 479 480 481 482
    /**
	 * Timer task that will remove Listeners that wait for results to IQ stanzas
	 * that have timed out. Time out values can be set to each listener
	 * individually by adjusting the timeout value in the third parameter of
	 * {@link IQRouter#addIQResultListener(String, IQResultListener, long)}.
483
	 *
484 485 486 487 488 489
	 * @author Guus der Kinderen, guus@nimbuzz.com
	 */
    private class TimeoutTask extends TimerTask {

        /**
         * Iterates over and removes all timed out results.<p>
490
         *
491 492 493 494 495 496 497 498 499 500 501 502 503 504
         * The map that keeps track of timeout values is ordered by timeout
         * date. This way, iteration can be stopped as soon as the first value
         * has been found that didn't timeout yet.
         */
        @Override
        public void run() {
            // Use an Iterator to allow changes to the Map that is backing
            // the Iterator.
            final Iterator<Map.Entry<String, Long>> it = resultTimeout.entrySet().iterator();

            while (it.hasNext()) {
                final Map.Entry<String, Long> pointer = it.next();

                if (System.currentTimeMillis() < pointer.getValue()) {
505 506
                    // This entry has not expired yet. Ignore it.
                    continue;
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
                }

                final String packetId = pointer.getKey();

                // remove this listener from the list
                final IQResultListener listener = resultListeners.remove(packetId);
                if (listener != null) {
                    // notify listener of the timeout.
                    listener.answerTimeout(packetId);
                }

                // remove the packet from the list that's used to track
                // timeouts
                it.remove();
            }
        }
	}
Gaston Dombiak's avatar
Gaston Dombiak committed
524
}