IQRouter.java 24.6 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
        Element childElement = packet.getChildElement(); // may be null
101 102 103 104 105 106 107 108 109
        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);
110 111 112
                if (childElement != null) {
                    reply.setChildElement(childElement.createCopy());
                }
113
                reply.setError(PacketError.Condition.bad_request);
114
                session.process(reply);
115
                Log.warn("User tried to authenticate with this server using an unknown receipient: " +
116
                        packet.toXML());
117 118
            }
            else if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED || (
119 120 121 122
                    childElement != null && isLocalServer(to) && (
                        "jabber:iq:auth".equals(childElement.getNamespaceURI()) ||
                        "jabber:iq:register".equals(childElement.getNamespaceURI()) ||
                        "urn:ietf:params:xml:ns:xmpp-bind".equals(childElement.getNamespaceURI())))) {
123
                handle(packet);
124
            } else if (packet.getType() == IQ.Type.get || packet.getType() == IQ.Type.set) {
125
                IQ reply = IQ.createResultIQ(packet);
126 127 128
                if (childElement != null) {
                    reply.setChildElement(childElement.createCopy());
                }
129
                reply.setError(PacketError.Condition.not_authorized);
130
                session.process(reply);
131 132 133
            }
            // Invoke the interceptors after we have processed the read packet
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, true);
134
        }
135 136 137 138
        catch (PacketRejectedException e) {
            if (session != null) {
                // An interceptor rejected this packet so answer a not_allowed error
                IQ reply = new IQ();
139 140 141
                if (childElement != null) {
                    reply.setChildElement(childElement.createCopy());
                }
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
                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);
                }
            }
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 196 197 198 199 200
        }
    }

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

    /**
201 202 203 204
	 * 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>
205
     *
206 207
	 * Once an IQ result was received, the listener will be invoked and removed
	 * from the list of listeners.<p>
208
	 *
209 210 211
	 * 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.
212
     *
213 214 215 216 217 218
	 * @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
219
     */
220
    public void addIQResultListener(String id, IQResultListener listener) {
221 222 223 224 225 226 227 228
        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>
229
	 *
230 231
	 * Once an IQ result was received, the listener will be invoked and removed
	 * from the list of listeners.<p>
232
	 *
233 234 235
	 * 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>
236
	 *
237 238 239 240
	 * 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.
241
	 *
242 243 244 245 246 247 248 249 250
	 * @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.
251
	 */
252
    public void addIQResultListener(String id, IQResultListener listener, long timeoutmillis) {
253
        resultListeners.put(id, listener);
254
        resultTimeout.put(id, System.currentTimeMillis() + timeoutmillis);
255 256
    }

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

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

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

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

				// 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;
				}

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
                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);
                }
418 419 420 421 422 423
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error.routing"), e);
            Session session = sessionManager.getSession(packet.getFrom());
            if (session != null) {
424 425 426
                IQ reply = IQ.createResultIQ(packet);
                reply.setError(PacketError.Condition.internal_server_error);
                session.process(reply);
427 428 429 430
            }
        }
    }

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

449 450 451 452 453 454 455 456 457 458 459 460 461 462
    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
463 464 465 466

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

483 484 485 486 487
    /**
	 * 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)}.
488
	 *
489 490 491 492 493 494
	 * @author Guus der Kinderen, guus@nimbuzz.com
	 */
    private class TimeoutTask extends TimerTask {

        /**
         * Iterates over and removes all timed out results.<p>
495
         *
496 497 498 499 500 501 502 503 504 505 506 507 508 509
         * 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()) {
510 511
                    // This entry has not expired yet. Ignore it.
                    continue;
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
                }

                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
529
}