/* * Copyright (C) 2005-2008 Jive Software, 2017-2025 Ignite Realtime Foundation. All rights reserved. * * 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. */ package org.jivesoftware.openfire; import org.jivesoftware.openfire.carbons.Sent; import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.forward.Forwarded; import org.jivesoftware.openfire.interceptor.InterceptorManager; import org.jivesoftware.openfire.interceptor.PacketRejectedException; import org.jivesoftware.openfire.session.ClientSession; import org.jivesoftware.openfire.session.LocalClientSession; import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.*; import java.util.List; import java.util.StringTokenizer; /** *
Route message 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 MessageRouter extends BasicModule { private static Logger log = LoggerFactory.getLogger(MessageRouter.class); private OfflineMessageStrategy messageStrategy; private RoutingTable routingTable; private SessionManager sessionManager; private MulticastRouter multicastRouter; private UserManager userManager; private String serverName; /** * Constructs a message router. */ public MessageRouter() { super("XMPP Message Router"); } /** *Performs the actual packet routing.
*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.
*Be careful to enforce concurrency DbC of concurrent by synchronizing * any accesses to class resources.
* * @param packet The packet to route * @throws NullPointerException If the packet is null */ public void route(Message packet) { if (packet == null) { throw new NullPointerException(); } ClientSession session = sessionManager.getSession(packet.getFrom()); try { // Invoke the interceptors before we process the read packet InterceptorManager.getInstance().invokeInterceptors(packet, session, true, false); if (session == null || session.isAuthenticated()) { JID recipientJID = packet.getTo(); // 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
*
* If the property xmpp.forward.admins 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().isEmpty()) {
// 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);
}
}
}
@Override
public void initialize(XMPPServer server) {
super.initialize(server);
messageStrategy = server.getOfflineMessageStrategy();
routingTable = server.getRoutingTable();
sessionManager = server.getSessionManager();
multicastRouter = server.getMulticastRouter();
userManager = server.getUserManager();
serverName = server.getServerInfo().getXMPPDomain();
}
/**
* Notification message indicating that a packet has failed to be routed to the recipient.
*
* @param recipient address of the entity that failed to receive the packet.
* @param packet Message packet that failed to be sent to the recipient.
*/
public void routingFailed( JID recipient, Packet packet )
{
log.debug( "Message sent to unreachable address: " + packet.toXML() );
final Message msg = (Message) packet;
if ( msg.getType().equals( Message.Type.chat ) && serverName.equals( recipient.getDomain() ) && recipient.getResource() != null ) {
// Find an existing AVAILABLE session with non-negative priority.
for (JID address : routingTable.getRoutes(recipient.asBareJID(), packet.getFrom())) {
ClientSession session = routingTable.getClientRoute(address);
if (session != null && session.isInitialized()) {
if (session.getPresence().getPriority() >= 0) {
// If message was sent to an unavailable full JID of a user then retry using the bare JID.
routingTable.routePacket(recipient.asBareJID(), packet);
return;
}
}
}
}
if ( serverName.equals( recipient.getDomain() ) )
{
// Delegate to offline message strategy, which will either bounce or ignore the message depending on user settings.
log.trace( "Delegating to offline message strategy." );
messageStrategy.storeOffline( (Message) packet );
}
else
{
// Recipient is not a local user. Bounce the message.
// Note: this is similar, but not equal, to handling of message handling to local users in OfflineMessageStrategy.
// 8.5.2. localpart@domainpart
// 8.5.2.2. No Available or Connected Resources
if (recipient.getResource() == null) {
if (msg.getType() == Message.Type.headline || msg.getType() == Message.Type.error) {
// For a message stanza of type "headline" or "error", the server MUST silently ignore the message.
log.trace( "Not bouncing a message stanza to a bare JID of non-local user, of type {}", msg.getType() );
return;
}
} else {
// 8.5.3. localpart@domainpart/resourcepart
// 8.5.3.2.1. Message
// For a message stanza of type "error", the server MUST silently ignore the stanza.
if (msg.getType() == Message.Type.error) {
log.trace( "Not bouncing a message stanza to a full JID of non-local user, of type {}", msg.getType() );
return;
}
}
bounce( msg );
}
}
private void bounce(Message message) {
// The bouncing behavior as implemented beyond this point was introduced as part
// of OF-1852. This kill-switch allows it to be disabled again in case it
// introduces unwanted side-effects.
if ( !JiveGlobals.getBooleanProperty( "xmpp.message.bounce", true ) ) {
log.trace( "Not bouncing a message stanza, as bouncing is disabled by configuration." );
return;
}
// Do nothing if the packet included an error. This intends to prevent scenarios
// where a stanza that is bounced itself gets bounced, causing a loop.
if (message.getError() != null) {
log.trace( "Not bouncing a stanza that included an error (to prevent never-ending loops of bounces-of-bounces)." );
return;
}
// Do nothing if the sender was the server itself
if (message.getFrom() == null || message.getFrom().toString().equals( serverName )) {
log.trace( "Not bouncing a stanza that was sent by the server itself." );
return;
}
try {
log.trace( "Bouncing a message stanza: {}", message);
// Generate a rejection response to the sender
final Message errorResponse = message.createCopy();
// return an error stanza to the sender, which SHOULD be