MessageRouter.java 12.9 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile: MessageRouter.java,v $
 * $Revision: 3007 $
 * $Date: 2005-10-31 13:29:25 -0300 (Mon, 31 Oct 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
import org.dom4j.QName;
import org.jivesoftware.openfire.carbons.Sent;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.forward.Forwarded;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
28
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
29 30 31
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.Session;
32
import org.jivesoftware.openfire.user.UserManager;
33
import org.jivesoftware.util.JiveGlobals;
34 35
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
36 37 38 39
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
40

41 42 43
import java.util.List;
import java.util.StringTokenizer;

44 45 46 47 48 49 50 51 52 53
/**
 * <p>Route message packets throughout the server.</p>
 * <p>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.</p>
 *
 * @author Iain Shigeoka
 */
public class MessageRouter extends BasicModule {
54 55
	
	private static Logger log = LoggerFactory.getLogger(MessageRouter.class); 
56 57 58 59

    private OfflineMessageStrategy messageStrategy;
    private RoutingTable routingTable;
    private SessionManager sessionManager;
60
    private MulticastRouter multicastRouter;
61
    private UserManager userManager;
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

    private String serverName;

    /**
     * Constructs a message router.
     */
    public MessageRouter() {
        super("XMPP Message 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(Message packet) {
        if (packet == null) {
            throw new NullPointerException();
        }
89
        ClientSession session = sessionManager.getSession(packet.getFrom());
90

91 92 93
        try {
            // Invoke the interceptors before we process the read packet
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, false);
94
            if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED) {
95
                JID recipientJID = packet.getTo();
96

97 98
                // If the server receives a message stanza with no 'to' attribute, it MUST treat the message as if the 'to' address were the bare JID <localpart@domainpart> of the sending entity.
                if (recipientJID == null) {
99
                    recipientJID = packet.getFrom().asBareJID();
100 101
                }

102
                // Check if the message was sent to the server hostname
103
                if (recipientJID.getNode() == null && recipientJID.getResource() == null &&
104 105 106 107 108 109 110 111 112 113 114 115
                        serverName.equals(recipientJID.getDomain())) {
                    if (packet.getElement().element("addresses") != null) {
                        // Message includes multicast processing instructions. Ask the multicastRouter
                        // to route this packet
                        multicastRouter.route(packet);
                    }
                    else {
                        // Message was sent to the server hostname so forward it to a configurable
                        // set of JID's (probably admin users)
                        sendMessageToAdmins(packet);
                    }
                    return;
116
                }
117

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
                boolean isAcceptable = true;
                if (session instanceof LocalClientSession) {
                    // Check if we could process messages 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
                    Message dummyMessage = packet.createCopy();
                    dummyMessage.setFrom(packet.getTo());
                    dummyMessage.setTo(packet.getFrom());
                    if (!((LocalClientSession) session).canProcess(dummyMessage)) {
                        packet.setTo(session.getAddress());
                        packet.setFrom((JID)null);
                        packet.setError(PacketError.Condition.not_acceptable);
                        session.process(packet);
                        isAcceptable = false;
                    }
133
                }
134
                if (isAcceptable) {
135
                    boolean isPrivate = packet.getElement().element(QName.get("private", "urn:xmpp:carbons:2")) != null;
136 137 138 139 140 141 142
                    try {
                        // Deliver stanza to requested route
                        routingTable.routePacket(recipientJID, packet, false);
                    } catch (Exception e) {
                        log.error("Failed to route packet: " + packet.toXML(), e);
                        routingFailed(recipientJID, packet);
                    }
143 144 145

                    // Sent carbon copies to other resources of the sender:
                    // When a client sends a <message/> of type "chat"
146
                    if (packet.getType() == Message.Type.chat && !isPrivate && session != null) { // && session.isMessageCarbonsEnabled() ??? // must the own session also be carbon enabled?
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
                        List<JID> routes = routingTable.getRoutes(packet.getFrom().asBareJID(), null);
                        for (JID route : routes) {
                            // The sending server SHOULD NOT send a forwarded copy to the sending full JID if it is a Carbons-enabled resource.
                            if (!route.equals(session.getAddress())) {
                                ClientSession clientSession = sessionManager.getSession(route);
                                if (clientSession != null && clientSession.isMessageCarbonsEnabled()) {
                                    Message message = new Message();
                                    // The wrapping message SHOULD maintain the same 'type' attribute value
                                    message.setType(packet.getType());
                                    // the 'from' attribute MUST be the Carbons-enabled user's bare JID
                                    message.setFrom(packet.getFrom().asBareJID());
                                    // and the 'to' attribute SHOULD be the full JID of the resource receiving the copy
                                    message.setTo(route);
                                    // The content of the wrapping message MUST contain a <sent/> element qualified by the namespace "urn:xmpp:carbons:2", which itself contains a <forwarded/> qualified by the namespace "urn:xmpp:forward:0" that contains the original <message/> stanza.
                                    message.addExtension(new Sent(new Forwarded(packet)));
                                    clientSession.process(message);
                                }
                            }
                        }
                    }
167
                }
168 169 170 171 172
            }
            else {
                packet.setTo(session.getAddress());
                packet.setFrom((JID)null);
                packet.setError(PacketError.Condition.not_authorized);
173 174
                session.process(packet);
            }
175 176 177 178 179 180 181 182 183 184 185 186 187 188
            // Invoke the interceptors after we have processed the read packet
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, true);
        } catch (PacketRejectedException e) {
            // An interceptor rejected this packet
            if (session != null && e.getRejectionMessage() != null && e.getRejectionMessage().trim().length() > 0) {
                // A message for the rejection will be sent to the sender of the rejected packet
                Message reply = new Message();
                reply.setID(packet.getID());
                reply.setTo(session.getAddress());
                reply.setFrom(packet.getTo());
                reply.setType(packet.getType());
                reply.setThread(packet.getThread());
                reply.setBody(e.getRejectionMessage());
                session.process(reply);
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
            }
        }
    }

    /**
     * Forwards the received message to the list of users defined in the property
     * <b>xmpp.forward.admins</b>. The property may include bare JIDs or just usernames separated
     * by commas or white spaces. When using bare JIDs the target user may belong to a remote
     * server.<p>
     *
     * If the property <b>xmpp.forward.admins</b> was not defined then the message will be sent
     * to all the users allowed to enter the admin console.
     *
     * @param packet the message to forward.
     */
    private void sendMessageToAdmins(Message packet) {
        String jids = JiveGlobals.getProperty("xmpp.forward.admins");
        if (jids != null && jids.trim().length() > 0) {
            // Forward the message to the users specified in the "xmpp.forward.admins" property
            StringTokenizer tokenizer = new StringTokenizer(jids, ", ");
            while (tokenizer.hasMoreTokens()) {
                String username = tokenizer.nextToken();
                Message forward = packet.createCopy();
                if (username.contains("@")) {
                    // Use the specified bare JID address as the target address
                    forward.setTo(username);
                }
                else {
                    forward.setTo(username + "@" + serverName);
                }
                route(forward);
            }
        }
        else {
            // Forward the message to the users allowed to log into the admin console
            for (JID jid : XMPPServer.getInstance().getAdmins()) {
                Message forward = packet.createCopy();
                forward.setTo(jid);
                route(forward);
            }
        }
    }

232 233
    @Override
	public void initialize(XMPPServer server) {
234 235 236 237
        super.initialize(server);
        messageStrategy = server.getOfflineMessageStrategy();
        routingTable = server.getRoutingTable();
        sessionManager = server.getSessionManager();
238
        multicastRouter = server.getMulticastRouter();
239
        userManager = server.getUserManager();
240
        serverName = server.getServerInfo().getXMPPDomain();
241
    }
Gaston Dombiak's avatar
Gaston Dombiak committed
242 243

    /**
244
     * Notification message indicating that a packet has failed to be routed to the recipient.
Gaston Dombiak's avatar
Gaston Dombiak committed
245
     *
246 247
     * @param recipient address of the entity that failed to receive the packet.
     * @param packet Message packet that failed to be sent to the recipient.
Gaston Dombiak's avatar
Gaston Dombiak committed
248
     */
249
    public void routingFailed(JID recipient, Packet packet) {
250
        // If message was sent to an unavailable full JID of a user then retry using the bare JID
251 252
        if (serverName.equals(recipient.getDomain()) && recipient.getResource() != null &&
                userManager.isRegisteredUser(recipient.getNode())) {
253 254 255 256 257 258 259
            Message msg = (Message)packet;
            if (msg.getType().equals(Message.Type.chat)) {
                routingTable.routePacket(recipient.asBareJID(), packet, false);
            } else {
                // Delegate to offline message strategy, which will either bounce or ignore the message depending on user settings.
                messageStrategy.storeOffline((Message) packet);
            }
260 261 262 263
        } else {
            // Just store the message offline
            messageStrategy.storeOffline((Message) packet);
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
264
    }
265
}