Commit 2e1f93f0 authored by Dave Cridland's avatar Dave Cridland Committed by Guus der Kinderen

OF-1309 Move to using DomainPairs exclusively

parent 528f8cd8
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -12,18 +12,6 @@ public class DomainPair { ...@@ -12,18 +12,6 @@ public class DomainPair {
this.remote = remote; this.remote = remote;
} }
public int hashCode() {
return toString().hashCode();
}
public boolean equals(Object other) {
if (other instanceof DomainPair) {
DomainPair domainPair = (DomainPair)other;
return domainPair.local.equals(this.local) && domainPair.remote.equals(this.remote);
}
return false;
}
public String toString() { public String toString() {
return "{" + local + " -> " + remote + "}"; return "{" + local + " -> " + remote + "}";
} }
...@@ -35,4 +23,22 @@ public class DomainPair { ...@@ -35,4 +23,22 @@ public class DomainPair {
public String getRemote() { public String getRemote() {
return remote; return remote;
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DomainPair that = (DomainPair) o;
if (!local.equals(that.local)) return false;
return remote.equals(that.remote);
}
@Override
public int hashCode() {
int result = local.hashCode();
result = 31 * result + remote.hashCode();
return result;
}
} }
/* /*
* Copyright (C) 2005-2008 Jive Software. All rights reserved. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.session; package org.jivesoftware.openfire.session;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.*;
import java.util.Collections; import java.util.regex.Pattern;
import java.util.HashSet;
import java.util.Iterator; import javax.net.ssl.SSLHandshakeException;
import java.util.regex.Pattern;
import org.dom4j.DocumentException;
import javax.net.ssl.SSLHandshakeException; import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader;
import org.dom4j.DocumentException; import org.jivesoftware.openfire.Connection;
import org.dom4j.Element; import org.jivesoftware.openfire.RoutingTable;
import org.dom4j.io.XMPPPacketReader; import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.Connection; import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.RoutingTable; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.SessionManager; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.StreamID; import org.jivesoftware.openfire.net.*;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.server.OutgoingServerSocketReader;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.server.RemoteServerManager;
import org.jivesoftware.openfire.net.*; import org.jivesoftware.openfire.server.ServerDialback;
import org.jivesoftware.openfire.server.OutgoingServerSocketReader; import org.jivesoftware.openfire.spi.BasicStreamIDFactory;
import org.jivesoftware.openfire.server.RemoteServerManager; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.openfire.server.ServerDialback; import org.jivesoftware.util.StringUtils;
import org.jivesoftware.openfire.spi.BasicStreamIDFactory; import org.slf4j.Logger;
import org.jivesoftware.util.JiveGlobals; import org.slf4j.LoggerFactory;
import org.jivesoftware.util.StringUtils; import org.xmlpull.v1.XmlPullParser;
import org.slf4j.Logger; import org.xmlpull.v1.XmlPullParserException;
import org.slf4j.LoggerFactory; import org.xmpp.packet.IQ;
import org.xmlpull.v1.XmlPullParser; import org.xmpp.packet.JID;
import org.xmlpull.v1.XmlPullParserException; import org.xmpp.packet.Message;
import org.xmpp.packet.IQ; import org.xmpp.packet.Packet;
import org.xmpp.packet.JID; import org.xmpp.packet.PacketError;
import org.xmpp.packet.Message; import org.xmpp.packet.Presence;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError; /**
import org.xmpp.packet.Presence; * Server-to-server communication is done using two TCP connections between the servers. One
* connection is used for sending packets while the other connection is used for receiving packets.
/** * The <tt>OutgoingServerSession</tt> represents the connection to a remote server that will only
* Server-to-server communication is done using two TCP connections between the servers. One * be used for sending packets.<p>
* connection is used for sending packets while the other connection is used for receiving packets. *
* The <tt>OutgoingServerSession</tt> represents the connection to a remote server that will only * Currently only the Server Dialback method is being used for authenticating with the remote
* be used for sending packets.<p> * server. Use {@link #authenticateDomain(String, String)} to create a new connection to a remote
* * server that will be used for sending packets to the remote server from the specified domain.
* Currently only the Server Dialback method is being used for authenticating with the remote * Only the authenticated domains with the remote server will be able to effectively send packets
* server. Use {@link #authenticateDomain(String, String)} to create a new connection to a remote * to the remote server. The remote server will reject and close the connection if a
* server that will be used for sending packets to the remote server from the specified domain. * non-authenticated domain tries to send a packet through this connection.<p>
* Only the authenticated domains with the remote server will be able to effectively send packets *
* to the remote server. The remote server will reject and close the connection if a * Once the connection has been established with the remote server and at least a domain has been
* non-authenticated domain tries to send a packet through this connection.<p> * authenticated then a new route will be added to the routing table for this connection. For
* * optimization reasons the same outgoing connection will be used even if the remote server has
* Once the connection has been established with the remote server and at least a domain has been * several hostnames. However, different routes will be created in the routing table for each
* authenticated then a new route will be added to the routing table for this connection. For * hostname of the remote server.
* optimization reasons the same outgoing connection will be used even if the remote server has *
* several hostnames. However, different routes will be created in the routing table for each * @author Gaston Dombiak
* hostname of the remote server. */
* public class LocalOutgoingServerSession extends LocalServerSession implements OutgoingServerSession {
* @author Gaston Dombiak
*/ private static final Logger Log = LoggerFactory.getLogger(LocalOutgoingServerSession.class);
public class LocalOutgoingServerSession extends LocalServerSession implements OutgoingServerSession {
/**
private static final Logger Log = LoggerFactory.getLogger(LocalOutgoingServerSession.class); * Regular expression to ensure that the hostname contains letters.
*/
/** private static Pattern pattern = Pattern.compile("[a-zA-Z]");
* Regular expression to ensure that the hostname contains letters.
*/ private OutgoingServerSocketReader socketReader;
private static Pattern pattern = Pattern.compile("[a-zA-Z]"); private Collection<DomainPair> outgoingDomainPairs = new HashSet<>();
private Collection<String> authenticatedDomains = new HashSet<>(); /**
private final Collection<String> hostnames = new HashSet<>(); * Authenticates the local domain to the remote domain. Once authenticated the remote domain can be expected to
private OutgoingServerSocketReader socketReader; * start accepting data from the local domain.
private Collection<DomainPair> outgoingDomainPairs = new HashSet<>(); *
* This implementation will attempt to re-use an existing connection. An connection is deemed re-usable when it is either:
/** * <ul>
* Authenticates the local domain to the remote domain. Once authenticated the remote domain can be expected to * <li>authenticated to the remote domain itself, or:</li>
* start accepting data from the local domain. * <li>authenticated to a sub- or superdomain of the remote domain AND offers dialback.</li>
* * </ul>
* This implementation will attempt to re-use an existing connection. An connection is deemed re-usable when it is either: *
* <ul> * When no re-usable connection exists, a new connection will be created.
* <li>authenticated to the remote domain itself, or:</li> *
* <li>authenticated to a sub- or superdomain of the remote domain AND offers dialback.</li> * DNS will be used to find hosts for the remote domain. When DNS records do not specify a port, port 5269 will be
* </ul> * used unless this default is overridden by the <b>xmpp.server.socket.remotePort</b> property.
* *
* When no re-usable connection exists, a new connection will be created. * @param localDomain the local domain to authenticate with the remote server.
* * @param remoteDomain the remote server, to which the local domain intends to send data.
* DNS will be used to find hosts for the remote domain. When DNS records do not specify a port, port 5269 will be * @return True if the domain was authenticated by the remote server.
* used unless this default is overridden by the <b>xmpp.server.socket.remotePort</b> property. */
* public static boolean authenticateDomain(final String localDomain, final String remoteDomain) {
* @param localDomain the local domain to authenticate with the remote server. final Logger log = LoggerFactory.getLogger( Log.getName() + "[Authenticate local domain: '" + localDomain + "' to remote domain: '" + remoteDomain + "']" );
* @param remoteDomain the remote server, to which the local domain intends to send data.
* @return True if the domain was authenticated by the remote server. log.debug( "Start domain authentication ..." );
*/ if (remoteDomain == null || remoteDomain.length() == 0 || remoteDomain.trim().indexOf(' ') > -1) {
public static boolean authenticateDomain(final String localDomain, final String remoteDomain) { // Do nothing if the target domain is empty, null or contains whitespaces
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Authenticate local domain: '" + localDomain + "' to remote domain: '" + remoteDomain + "']" ); log.warn( "Unable to authenticate: remote domain is invalid." );
return false;
log.debug( "Start domain authentication ..." ); }
if (remoteDomain == null || remoteDomain.length() == 0 || remoteDomain.trim().indexOf(' ') > -1) { try {
// Do nothing if the target domain is empty, null or contains whitespaces // Check if the remote domain is in the blacklist
log.warn( "Unable to authenticate: remote domain is invalid." ); if (!RemoteServerManager.canAccess(remoteDomain)) {
return false; log.info( "Unable to authenticate: Remote domain is not accessible according to our configuration (typical causes: server federation is disabled, or domain is blacklisted)." );
} return false;
try { }
// Check if the remote domain is in the blacklist
if (!RemoteServerManager.canAccess(remoteDomain)) { log.debug( "Searching for pre-existing outgoing sessions to the remote domain (if one exists, it will be re-used) ..." );
log.info( "Unable to authenticate: Remote domain is not accessible according to our configuration (typical causes: server federation is disabled, or domain is blacklisted)." ); OutgoingServerSession session;
return false; SessionManager sessionManager = SessionManager.getInstance();
} if (sessionManager == null) {
// Server is shutting down while we are trying to create a new s2s connection
log.debug( "Searching for pre-existing outgoing sessions to the remote domain (if one exists, it will be re-used) ..." ); log.warn( "Unable to authenticate: a SessionManager instance is not available. This should not occur unless Openfire is starting up or shutting down." );
OutgoingServerSession session; return false;
SessionManager sessionManager = SessionManager.getInstance(); }
if (sessionManager == null) { session = sessionManager.getOutgoingServerSession(remoteDomain);
// Server is shutting down while we are trying to create a new s2s connection
log.warn( "Unable to authenticate: a SessionManager instance is not available. This should not occur unless Openfire is starting up or shutting down." ); if (session == null)
return false; {
} log.debug( "There are no pre-existing outgoing sessions to the remote domain itself. Searching for pre-existing outgoing sessions to super- or subdomains of the remote domain (if one exists, it might be re-usable) ..." );
session = sessionManager.getOutgoingServerSession(remoteDomain);
for ( IncomingServerSession incomingSession : sessionManager.getIncomingServerSessions( remoteDomain ) )
if (session == null) {
{ // These are the remote domains that are allowed to send data to the local domain - expected to be sub- or superdomains of remoteDomain
log.debug( "There are no pre-existing outgoing sessions to the remote domain itself. Searching for pre-existing outgoing sessions to super- or subdomains of the remote domain (if one exists, it might be re-usable) ..." ); for ( String otherRemoteDomain : incomingSession.getValidatedDomains() )
{
for ( IncomingServerSession incomingSession : sessionManager.getIncomingServerSessions( remoteDomain ) ) // See if there's an outgoing session to any of the (other) domains hosted by the remote domain.
{ session = sessionManager.getOutgoingServerSession( otherRemoteDomain );
// These are the remote domains that are allowed to send data to the local domain - expected to be sub- or superdomains of remoteDomain if (session != null)
for ( String otherRemoteDomain : incomingSession.getValidatedDomains() ) {
{ log.debug( "An outgoing session to a different domain ('{}') hosted on the remote domain was found.", otherRemoteDomain );
// See if there's an outgoing session to any of the (other) domains hosted by the remote domain.
session = sessionManager.getOutgoingServerSession( otherRemoteDomain ); // As this sub/superdomain is different from the original remote domain, we need to check if it supports dialback.
if (session != null) if ( session.isUsingServerDialback() )
{ {
log.debug( "An outgoing session to a different domain ('{}') hosted on the remote domain was found.", otherRemoteDomain ); log.debug( "Dialback was used for '{}'. This session can be re-used.", otherRemoteDomain );
break;
// As this sub/superdomain is different from the original remote domain, we need to check if it supports dialback. }
if ( session.isUsingServerDialback() ) else
{ {
log.debug( "Dialback was used for '{}'. This session can be re-used.", otherRemoteDomain ); log.debug( "Dialback was not used for '{}'. This session cannot be re-used.", otherRemoteDomain );
break; session = null;
} }
else }
{ }
log.debug( "Dialback was not used for '{}'. This session cannot be re-used.", otherRemoteDomain ); }
session = null;
} if (session == null) {
} log.debug( "There are no pre-existing session to other domains hosted on the remote domain." );
} }
} }
if (session == null) { if ( session != null )
log.debug( "There are no pre-existing session to other domains hosted on the remote domain." ); {
} log.debug( "A pre-existing session can be re-used. The session was established using server dialback so it is possible to do piggybacking to authenticate more domains." );
} if ( session.checkOutgoingDomainPair(localDomain, remoteDomain) )
{
if ( session != null ) // Do nothing since the domain has already been authenticated.
{ log.debug( "Authentication successful (domain was already authenticated in the pre-existing session)." );
log.debug( "A pre-existing session can be re-used. The session was established using server dialback so it is possible to do piggybacking to authenticate more domains." ); return true;
if ( session.checkOutgoingDomainPair(localDomain, remoteDomain) ) }
{
// Do nothing since the domain has already been authenticated. // A session already exists so authenticate the domain using that session.
log.debug( "Authentication successful (domain was already authenticated in the pre-existing session)." ); if ( session.authenticateSubdomain( localDomain, remoteDomain ) )
return true; {
} log.debug( "Authentication successful (domain authentication was added using a pre-existing session)." );
return true;
// A session already exists so authenticate the domain using that session. }
if ( session.authenticateSubdomain( localDomain, remoteDomain ) ) else
{ {
log.debug( "Authentication successful (domain authentication was added using a pre-existing session)." ); log.warn( "Unable to authenticate: Unable to add authentication to pre-exising session." );
return true; return false;
} }
else }
{ else
log.warn( "Unable to authenticate: Unable to add authentication to pre-exising session." ); {
return false; log.debug( "Unable to re-use an existing session. Creating a new session ..." );
} int port = RemoteServerManager.getPortForServer(remoteDomain);
} session = createOutgoingSession(localDomain, remoteDomain, port);
else if (session != null) {
{ log.debug( "Created a new session." );
log.debug( "Unable to re-use an existing session. Creating a new session ..." );
int port = RemoteServerManager.getPortForServer(remoteDomain); session.addOutgoingDomainPair(localDomain, remoteDomain);
session = createOutgoingSession(localDomain, remoteDomain, port); sessionManager.outgoingServerSessionCreated((LocalOutgoingServerSession) session);
if (session != null) { log.debug( "Authentication successful." );
log.debug( "Created a new session." ); return true;
} else {
// Add the validated domain as an authenticated domain log.warn( "Unable to authenticate: Fail to create new session." );
session.addAuthenticatedDomain(localDomain); return false;
// Add the new domain to the list of names that the server may have }
session.addHostname(remoteDomain); }
// Notify the SessionManager that a new session has been created }
sessionManager.outgoingServerSessionCreated((LocalOutgoingServerSession) session); catch (Exception e)
log.debug( "Authentication successful." ); {
return true; log.error( "An exception occurred while authenticating remote domain!", e );
} else { return false;
log.warn( "Unable to authenticate: Fail to create new session." ); }
return false; }
}
} /**
} * Establishes a new outgoing session to a remote domain. If the remote domain supports TLS and SASL then the new
catch (Exception e) * outgoing connection will be secured with TLS and authenticated using SASL. However, if TLS or SASL is not
{ * supported by the remote domain or if an error occurred while securing or authenticating the connection using SASL
log.error( "An exception occurred while authenticating remote domain!", e ); * then server dialback will be used.
return false; *
} * @param localDomain the local domain to authenticate with the remote domain.
} * @param remoteDomain the remote domain.
* @param port default port to use to establish the connection.
/** * @return new outgoing session to a remote domain, or null.
* Establishes a new outgoing session to a remote domain. If the remote domain supports TLS and SASL then the new */
* outgoing connection will be secured with TLS and authenticated using SASL. However, if TLS or SASL is not private static LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) {
* supported by the remote domain or if an error occurred while securing or authenticating the connection using SASL final Logger log = LoggerFactory.getLogger( Log.getName() + "[Create outgoing session for: " + localDomain + " to " + remoteDomain + "]" );
* then server dialback will be used.
* log.debug( "Creating new session..." );
* @param localDomain the local domain to authenticate with the remote domain.
* @param remoteDomain the remote domain. // Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or server dialback)
* @param port default port to use to establish the connection. log.debug( "Creating plain socket connection to a host that belongs to the remote XMPP domain." );
* @return new outgoing session to a remote domain, or null. final Socket socket = SocketUtil.createSocketToXmppDomain( remoteDomain, port );
*/
private static LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) { if ( socket == null ) {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Create outgoing session for: " + localDomain + " to " + remoteDomain + "]" ); log.info( "Unable to create new session: Cannot create a plain socket connection with any applicable remote host." );
return null;
log.debug( "Creating new session..." ); }
// Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or server dialback) SocketConnection connection = null;
log.debug( "Creating plain socket connection to a host that belongs to the remote XMPP domain." ); try {
final Socket socket = SocketUtil.createSocketToXmppDomain( remoteDomain, port ); connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false);
if ( socket == null ) { log.debug( "Send the stream header and wait for response..." );
log.info( "Unable to create new session: Cannot create a plain socket connection with any applicable remote host." ); StringBuilder openingStream = new StringBuilder();
return null; openingStream.append("<stream:stream");
} openingStream.append(" xmlns:db=\"jabber:server:dialback\"");
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
SocketConnection connection = null; openingStream.append(" xmlns=\"jabber:server\"");
try { openingStream.append(" from=\"").append(localDomain).append("\""); // OF-673
connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false); openingStream.append(" to=\"").append(remoteDomain).append("\"");
openingStream.append(" version=\"1.0\">");
log.debug( "Send the stream header and wait for response..." ); connection.deliverRawText(openingStream.toString());
StringBuilder openingStream = new StringBuilder();
openingStream.append("<stream:stream"); // Set a read timeout (of 5 seconds) so we don't keep waiting forever
openingStream.append(" xmlns:db=\"jabber:server:dialback\""); int soTimeout = socket.getSoTimeout();
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); socket.setSoTimeout(5000);
openingStream.append(" xmlns=\"jabber:server\"");
openingStream.append(" from=\"").append(localDomain).append("\""); // OF-673 XMPPPacketReader reader = new XMPPPacketReader();
openingStream.append(" to=\"").append(remoteDomain).append("\""); reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
openingStream.append(" version=\"1.0\">"); StandardCharsets.UTF_8));
connection.deliverRawText(openingStream.toString()); // Get the answer from the Receiving Server
XmlPullParser xpp = reader.getXPPParser();
// Set a read timeout (of 5 seconds) so we don't keep waiting forever for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
int soTimeout = socket.getSoTimeout(); eventType = xpp.next();
socket.setSoTimeout(5000); }
XMPPPacketReader reader = new XMPPPacketReader(); String serverVersion = xpp.getAttributeValue("", "version");
reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(), String id = xpp.getAttributeValue("", "id");
StandardCharsets.UTF_8)); log.debug( "Got a response (stream ID: {}, version: {}). Check if the remote server is XMPP 1.0 compliant...", id, serverVersion );
// Get the answer from the Receiving Server
XmlPullParser xpp = reader.getXPPParser(); if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) {
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { log.debug( "The remote server is XMPP 1.0 compliant (or at least reports to be)." );
eventType = xpp.next();
} // Restore default timeout
socket.setSoTimeout(soTimeout);
String serverVersion = xpp.getAttributeValue("", "version");
String id = xpp.getAttributeValue("", "id"); log.debug( "Processing stream features of the remote domain..." );
log.debug( "Got a response (stream ID: {}, version: {}). Check if the remote server is XMPP 1.0 compliant...", id, serverVersion ); Element features = reader.parseDocument().getRootElement();
if (features != null) {
if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) { log.debug( "Check if both us as well as the remote server have enabled STARTTLS and/or dialback ..." );
log.debug( "The remote server is XMPP 1.0 compliant (or at least reports to be)." ); final boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true);
if (useTLS && features.element("starttls") != null) {
// Restore default timeout log.debug( "Both us and the remote server support the STARTTLS feature. Secure and authenticate the connection with TLS & SASL..." );
socket.setSoTimeout(soTimeout); LocalOutgoingServerSession answer = secureAndAuthenticate(remoteDomain, connection, reader, openingStream, localDomain);
if (answer != null) {
log.debug( "Processing stream features of the remote domain..." ); log.debug( "Successfully secured/authenticated the connection with TLS/SASL)!" );
Element features = reader.parseDocument().getRootElement(); // Everything went fine so return the secured and
if (features != null) { // authenticated connection
log.debug( "Check if both us as well as the remote server have enabled STARTTLS and/or dialback ..." ); log.debug( "Successfully created new session!" );
final boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true); return answer;
if (useTLS && features.element("starttls") != null) { }
log.debug( "Both us and the remote server support the STARTTLS feature. Secure and authenticate the connection with TLS & SASL..." ); log.debug( "Unable to secure and authenticate the connection with TLS & SASL." );
LocalOutgoingServerSession answer = secureAndAuthenticate(remoteDomain, connection, reader, openingStream, localDomain); }
if (answer != null) { else if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
log.debug( "Successfully secured/authenticated the connection with TLS/SASL)!" ); log.debug("I have no StartTLS yet I must TLS");
// Everything went fine so return the secured and connection.close();
// authenticated connection return null;
log.debug( "Successfully created new session!" ); }
return answer; // Check if we are going to try server dialback (XMPP 1.0)
} else if (ServerDialback.isEnabled() && features.element("dialback") != null) {
log.debug( "Unable to secure and authenticate the connection with TLS & SASL." ); log.debug( "Both us and the remote server support the 'dialback' feature. Authenticate the connection with dialback..." );
} ServerDialback method = new ServerDialback(connection, localDomain);
else if (connection.getTlsPolicy() == Connection.TLSPolicy.required) { OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
log.debug("I have no StartTLS yet I must TLS"); if (method.authenticateDomain(newSocketReader, localDomain, remoteDomain, id)) {
connection.close(); log.debug( "Successfully authenticated the connection with dialback!" );
return null; StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
} LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, newSocketReader, streamID);
// Check if we are going to try server dialback (XMPP 1.0) connection.init(session);
else if (ServerDialback.isEnabled() && features.element("dialback") != null) { // Set the hostname as the address of the session
log.debug( "Both us and the remote server support the 'dialback' feature. Authenticate the connection with dialback..." ); session.setAddress(new JID(null, remoteDomain, null));
ServerDialback method = new ServerDialback(connection, localDomain); log.debug( "Successfully created new session!" );
OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader); return session;
if (method.authenticateDomain(newSocketReader, localDomain, remoteDomain, id)) { }
log.debug( "Successfully authenticated the connection with dialback!" ); else {
StreamID streamID = new BasicStreamIDFactory().createStreamID(id); log.debug( "Unable to authenticate the connection with dialback." );
LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, newSocketReader, streamID); }
connection.init(session); }
// Set the hostname as the address of the session }
session.setAddress(new JID(null, remoteDomain, null)); else {
log.debug( "Successfully created new session!" ); log.debug( "Error! No data from the remote server (expected a 'feature' element).");
return session; }
} } else {
else { log.debug( "The remote server is not XMPP 1.0 compliant." );
log.debug( "Unable to authenticate the connection with dialback." ); }
}
} log.debug( "Something went wrong so close the connection and try server dialback over a plain connection" );
} if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
else { log.debug("I have no StartTLS yet I must TLS");
log.debug( "Error! No data from the remote server (expected a 'feature' element)."); connection.close();
} return null;
} else { }
log.debug( "The remote server is not XMPP 1.0 compliant." ); connection.close();
} }
catch (SSLHandshakeException e)
log.debug( "Something went wrong so close the connection and try server dialback over a plain connection" ); {
if (connection.getTlsPolicy() == Connection.TLSPolicy.required) { // This is a failure as described in RFC3620, section 5.4.3.2 "STARTTLS Failure".
log.debug("I have no StartTLS yet I must TLS"); log.info( "STARTTLS negotiation failed. Closing connection (without sending any data such as <failure/> or </stream>).", e );
connection.close();
return null; // The receiving entity is expected to close the socket *without* sending any more data (<failure/> nor </stream>).
} // It is probably (see OF-794) best if we, as the initiating entity, therefor don't send any data either.
connection.close(); if (connection != null) {
} connection.forceClose();
catch (SSLHandshakeException e) }
{ }
// This is a failure as described in RFC3620, section 5.4.3.2 "STARTTLS Failure". catch (Exception e)
log.info( "STARTTLS negotiation failed. Closing connection (without sending any data such as <failure/> or </stream>).", e ); {
// This might be RFC3620, section 5.4.2.2 "Failure Case" or even an unrelated problem. Handle 'normally'.
// The receiving entity is expected to close the socket *without* sending any more data (<failure/> nor </stream>). log.warn( "An exception occurred while creating an encrypted session. Closing connection.", e );
// It is probably (see OF-794) best if we, as the initiating entity, therefor don't send any data either.
if (connection != null) { if (connection != null) {
connection.forceClose(); connection.close();
} }
} }
catch (Exception e)
{ if (ServerDialback.isEnabled())
// This might be RFC3620, section 5.4.2.2 "Failure Case" or even an unrelated problem. Handle 'normally'. {
log.warn( "An exception occurred while creating an encrypted session. Closing connection.", e ); log.debug( "Unable to create a new session. Going to try connecting using server dialback as a fallback." );
if (connection != null) { // Use server dialback (pre XMPP 1.0) over a plain connection
connection.close(); final LocalOutgoingServerSession outgoingSession = new ServerDialback().createOutgoingSession( localDomain, remoteDomain, port );
} if ( outgoingSession != null) { // TODO this success handler behaves differently from a similar success handler above. Shouldn't those be the same?
} log.debug( "Successfully created new session (using dialback as a fallback)!" );
return outgoingSession;
if (ServerDialback.isEnabled()) } else {
{ log.warn( "Unable to create a new session: Dialback (as a fallback) failed." );
log.debug( "Unable to create a new session. Going to try connecting using server dialback as a fallback." ); return null;
}
// Use server dialback (pre XMPP 1.0) over a plain connection }
final LocalOutgoingServerSession outgoingSession = new ServerDialback().createOutgoingSession( localDomain, remoteDomain, port ); else
if ( outgoingSession != null) { // TODO this success handler behaves differently from a similar success handler above. Shouldn't those be the same? {
log.debug( "Successfully created new session (using dialback as a fallback)!" ); log.warn( "Unable to create a new session: exhausted all options (not trying dialback as a fallback, as server dialback is disabled by configuration." );
return outgoingSession; return null;
} else { }
log.warn( "Unable to create a new session: Dialback (as a fallback) failed." ); }
return null;
} private static LocalOutgoingServerSession secureAndAuthenticate(String remoteDomain, SocketConnection connection, XMPPPacketReader reader, StringBuilder openingStream, String localDomain) throws Exception {
} final Logger log = LoggerFactory.getLogger(Log.getName() + "[Secure/Authenticate connection for: " + localDomain + " to: " + remoteDomain + "]" );
else Element features;
{
log.warn( "Unable to create a new session: exhausted all options (not trying dialback as a fallback, as server dialback is disabled by configuration." ); log.debug( "Securing and authenticating connection ...");
return null;
} log.debug( "Indicating we want TLS and wait for response." );
} connection.deliverRawText( "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" );
private static LocalOutgoingServerSession secureAndAuthenticate(String remoteDomain, SocketConnection connection, XMPPPacketReader reader, StringBuilder openingStream, String localDomain) throws Exception { MXParser xpp = reader.getXPPParser();
final Logger log = LoggerFactory.getLogger(Log.getName() + "[Secure/Authenticate connection for: " + localDomain + " to: " + remoteDomain + "]" ); // Wait for the <proceed> response
Element features; Element proceed = reader.parseDocument().getRootElement();
if (proceed != null && proceed.getName().equals("proceed")) {
log.debug( "Securing and authenticating connection ..."); log.debug( "Received 'proceed' from remote server. Negotiating TLS..." );
try {
log.debug( "Indicating we want TLS and wait for response." ); // boolean needed = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true) &&
connection.deliverRawText( "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" ); // JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, true) &&
// !JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, false);
MXParser xpp = reader.getXPPParser(); connection.startTLS(true);
// Wait for the <proceed> response } catch(Exception e) {
Element proceed = reader.parseDocument().getRootElement(); log.debug("TLS negotiation failed: " + e.getMessage());
if (proceed != null && proceed.getName().equals("proceed")) { throw e;
log.debug( "Received 'proceed' from remote server. Negotiating TLS..." ); }
try { log.debug( "TLS negotiation was successful. Connection secured. Proceeding with authentication..." );
// boolean needed = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true) && if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), remoteDomain, true)) {
// JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, true) && if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
// !JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, false); log.debug( "SASL authentication failed. Will continue with dialback." );
connection.startTLS(true); } else {
} catch(Exception e) { log.warn( "Unable to authenticated the connection: SASL authentication failed (and dialback is not available)." );
log.debug("TLS negotiation failed: " + e.getMessage()); return null;
throw e; }
} }
log.debug( "TLS negotiation was successful. Connection secured. Proceeding with authentication..." );
if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), remoteDomain, true)) { log.debug( "TLS negotiation was successful so initiate a new stream." );
if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) { connection.deliverRawText( openingStream.toString() );
log.debug( "SASL authentication failed. Will continue with dialback." );
} else { // Reset the parser to use the new secured reader
log.warn( "Unable to authenticated the connection: SASL authentication failed (and dialback is not available)." ); xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8));
return null; // Skip new stream element
} for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
} eventType = xpp.next();
}
log.debug( "TLS negotiation was successful so initiate a new stream." ); // Get the stream ID
connection.deliverRawText( openingStream.toString() ); String id = xpp.getAttributeValue("", "id");
// Get new stream features
// Reset the parser to use the new secured reader features = reader.parseDocument().getRootElement();
xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8)); if (features != null) {
// Skip new stream element // Bookkeeping: determine what functionality the remote server offers.
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { boolean saslEXTERNALoffered = false;
eventType = xpp.next(); if (features.element("mechanisms") != null) {
} Iterator<Element> it = features.element("mechanisms").elementIterator();
// Get the stream ID while (it.hasNext()) {
String id = xpp.getAttributeValue("", "id"); Element mechanism = it.next();
// Get new stream features if ("EXTERNAL".equals(mechanism.getTextTrim())) {
features = reader.parseDocument().getRootElement(); saslEXTERNALoffered = true;
if (features != null) { break;
// Bookkeeping: determine what functionality the remote server offers. }
boolean saslEXTERNALoffered = false; }
if (features.element("mechanisms") != null) { }
Iterator<Element> it = features.element("mechanisms").elementIterator(); final boolean dialbackOffered = features.element("dialback") != null;
while (it.hasNext()) {
Element mechanism = it.next(); log.debug("Remote server is offering dialback: {}, EXTERNAL SASL:", dialbackOffered, saslEXTERNALoffered );
if ("EXTERNAL".equals(mechanism.getTextTrim())) {
saslEXTERNALoffered = true; LocalOutgoingServerSession result = null;
break;
} // first, try SASL
} if (saslEXTERNALoffered) {
} log.debug( "Trying to authenticate with EXTERNAL SASL." );
final boolean dialbackOffered = features.element("dialback") != null; result = attemptSASLexternal(connection, xpp, reader, localDomain, remoteDomain, id, openingStream);
if (result == null) {
log.debug("Remote server is offering dialback: {}, EXTERNAL SASL:", dialbackOffered, saslEXTERNALoffered ); log.debug( "Failed to authenticate with EXTERNAL SASL." );
} else {
LocalOutgoingServerSession result = null; log.debug( "Successfully authenticated with EXTERNAL SASL." );
}
// first, try SASL }
if (saslEXTERNALoffered) {
log.debug( "Trying to authenticate with EXTERNAL SASL." ); // SASL unavailable or failed, try dialback.
result = attemptSASLexternal(connection, xpp, reader, localDomain, remoteDomain, id, openingStream); if (result == null) {
if (result == null) { log.debug( "Trying to authenticate with dialback." );
log.debug( "Failed to authenticate with EXTERNAL SASL." ); result = attemptDialbackOverTLS(connection, reader, localDomain, remoteDomain, id);
} else { if (result == null) {
log.debug( "Successfully authenticated with EXTERNAL SASL." ); log.debug( "Failed to authenticate with dialback." );
} } else {
} log.debug( "Successfully authenticated with dialback." );
}
// SASL unavailable or failed, try dialback. }
if (result == null) {
log.debug( "Trying to authenticate with dialback." ); if ( result != null ) {
result = attemptDialbackOverTLS(connection, reader, localDomain, remoteDomain, id); log.debug( "Successfully secured and authenticated connection!" );
if (result == null) { return result;
log.debug( "Failed to authenticate with dialback." ); } else {
} else { log.warn( "Unable to secure and authenticate connection: Exhausted all options." );
log.debug( "Successfully authenticated with dialback." ); return null;
} }
} }
else {
if ( result != null ) { log.debug( "Failed to secure and authenticate connection: neither SASL mechanisms nor SERVER DIALBACK were offered by the remote host." );
log.debug( "Successfully secured and authenticated connection!" ); return null;
return result; }
} else { }
log.warn( "Unable to secure and authenticate connection: Exhausted all options." ); else {
return null; log.debug( "Failed to secure and authenticate connection: <proceed> was not received!" );
} return null;
} }
else { }
log.debug( "Failed to secure and authenticate connection: neither SASL mechanisms nor SERVER DIALBACK were offered by the remote host." );
return null; private static LocalOutgoingServerSession attemptDialbackOverTLS(Connection connection, XMPPPacketReader reader, String localDomain, String remoteDomain, String id) {
} final Logger log = LoggerFactory.getLogger( Log.getName() + "[Dialback over TLS for: " + localDomain + " to: " + remoteDomain + " (Stream ID: " + id + ")]" );
}
else { if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
log.debug( "Failed to secure and authenticate connection: <proceed> was not received!" ); log.debug("Trying to connecting using dialback over TLS.");
return null; ServerDialback method = new ServerDialback(connection, localDomain);
} OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
} if (method.authenticateDomain(newSocketReader, localDomain, remoteDomain, id)) {
log.debug("Dialback over TLS was successful.");
private static LocalOutgoingServerSession attemptDialbackOverTLS(Connection connection, XMPPPacketReader reader, String localDomain, String remoteDomain, String id) { StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Dialback over TLS for: " + localDomain + " to: " + remoteDomain + " (Stream ID: " + id + ")]" ); LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, newSocketReader, streamID);
connection.init(session);
if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) { // Set the hostname as the address of the session
log.debug("Trying to connecting using dialback over TLS."); session.setAddress(new JID(null, remoteDomain, null));
ServerDialback method = new ServerDialback(connection, localDomain); return session;
OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader); }
if (method.authenticateDomain(newSocketReader, localDomain, remoteDomain, id)) { else {
log.debug("Dialback over TLS was successful."); log.debug("Dialback over TLS failed");
StreamID streamID = new BasicStreamIDFactory().createStreamID(id); return null;
LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, newSocketReader, streamID); }
connection.init(session); }
// Set the hostname as the address of the session else {
session.setAddress(new JID(null, remoteDomain, null)); log.debug("Skipping server dialback attempt as it has been disabled by local configuration.");
return session; return null;
} }
else { }
log.debug("Dialback over TLS failed");
return null; private static LocalOutgoingServerSession attemptSASLexternal(SocketConnection connection, MXParser xpp, XMPPPacketReader reader, String localDomain, String remoteDomain, String id, StringBuilder openingStream) throws DocumentException, IOException, XmlPullParserException {
} final Logger log = LoggerFactory.getLogger( Log.getName() + "[EXTERNAL SASL for: " + localDomain + " to: " + remoteDomain + " (Stream ID: " + id + ")]" );
}
else { log.debug("Starting EXTERNAL SASL.");
log.debug("Skipping server dialback attempt as it has been disabled by local configuration."); if (doExternalAuthentication(localDomain, connection, reader)) {
return null; log.debug("EXTERNAL SASL was successful.");
} // SASL was successful so initiate a new stream
} connection.deliverRawText(openingStream.toString());
private static LocalOutgoingServerSession attemptSASLexternal(SocketConnection connection, MXParser xpp, XMPPPacketReader reader, String localDomain, String remoteDomain, String id, StringBuilder openingStream) throws DocumentException, IOException, XmlPullParserException { // Reset the parser
final Logger log = LoggerFactory.getLogger( Log.getName() + "[EXTERNAL SASL for: " + localDomain + " to: " + remoteDomain + " (Stream ID: " + id + ")]" ); //xpp.resetInput();
// // Reset the parser to use the new secured reader
log.debug("Starting EXTERNAL SASL."); xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8));
if (doExternalAuthentication(localDomain, connection, reader)) { // Skip the opening stream sent by the server
log.debug("EXTERNAL SASL was successful."); for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
// SASL was successful so initiate a new stream eventType = xpp.next();
connection.deliverRawText(openingStream.toString()); }
// Reset the parser // SASL authentication was successful so create new OutgoingServerSession
//xpp.resetInput(); id = xpp.getAttributeValue("", "id");
// // Reset the parser to use the new secured reader StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8)); LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, new OutgoingServerSocketReader(reader), streamID);
// Skip the opening stream sent by the server connection.init(session);
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { // Set the hostname as the address of the session
eventType = xpp.next(); session.setAddress(new JID(null, remoteDomain, null));
} // Set that the session was created using TLS+SASL (no server dialback)
session.usingServerDialback = false;
// SASL authentication was successful so create new OutgoingServerSession return session;
id = xpp.getAttributeValue("", "id"); }
StreamID streamID = new BasicStreamIDFactory().createStreamID(id); else {
LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, new OutgoingServerSocketReader(reader), streamID); log.debug("EXTERNAL SASL failed.");
connection.init(session); return null;
// Set the hostname as the address of the session }
session.setAddress(new JID(null, remoteDomain, null)); }
// Set that the session was created using TLS+SASL (no server dialback)
session.usingServerDialback = false; private static boolean doExternalAuthentication(String localDomain, SocketConnection connection,
return session; XMPPPacketReader reader) throws DocumentException, IOException, XmlPullParserException {
}
else { StringBuilder sb = new StringBuilder();
log.debug("EXTERNAL SASL failed."); sb.append("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"EXTERNAL\">");
return null; sb.append(StringUtils.encodeBase64(localDomain));
} sb.append("</auth>");
} connection.deliverRawText(sb.toString());
private static boolean doExternalAuthentication(String localDomain, SocketConnection connection, Element response = reader.parseDocument().getRootElement();
XMPPPacketReader reader) throws DocumentException, IOException, XmlPullParserException { return response != null && "success".equals(response.getName());
}
StringBuilder sb = new StringBuilder();
sb.append("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"EXTERNAL\">"); public LocalOutgoingServerSession(String localDomain, Connection connection, OutgoingServerSocketReader socketReader, StreamID streamID) {
sb.append(StringUtils.encodeBase64(localDomain)); super(localDomain, connection, streamID);
sb.append("</auth>"); this.socketReader = socketReader;
connection.deliverRawText(sb.toString()); socketReader.setSession(this);
}
Element response = reader.parseDocument().getRootElement();
return response != null && "success".equals(response.getName()); @Override
} boolean canProcess(Packet packet) {
final String senderDomain = packet.getFrom().getDomain();
public LocalOutgoingServerSession(String localDomain, Connection connection, OutgoingServerSocketReader socketReader, StreamID streamID) { final String recipDomain = packet.getTo().getDomain();
super(localDomain, connection, streamID); boolean processed = true;
this.socketReader = socketReader; if (!checkOutgoingDomainPair(senderDomain, recipDomain)) {
socketReader.setSession(this); synchronized (("Auth::" + senderDomain).intern()) {
} if (!checkOutgoingDomainPair(senderDomain, recipDomain) &&
!authenticateSubdomain(senderDomain, packet.getTo().getDomain())) {
@Override // Return error since sender domain was not validated by remote server
boolean canProcess(Packet packet) { processed = false;
String senderDomain = packet.getFrom().getDomain(); }
boolean processed = true; }
if (!getAuthenticatedDomains().contains(senderDomain)) { }
synchronized (("Auth::" + senderDomain).intern()) { if (!processed) {
if (!getAuthenticatedDomains().contains(senderDomain) && returnErrorToSender(packet);
!authenticateSubdomain(senderDomain, packet.getTo().getDomain())) { }
// Return error since sender domain was not validated by remote server return processed;
processed = false; }
}
} @Override
} void deliver(Packet packet) throws UnauthorizedException {
if (!processed) { if (!conn.isClosed()) {
returnErrorToSender(packet); conn.deliver(packet);
} }
return processed; }
}
@Override
@Override public boolean authenticateSubdomain(String localDomain, String remoteDomain) {
void deliver(Packet packet) throws UnauthorizedException { if (!usingServerDialback) {
if (!conn.isClosed()) { // Using SASL so just assume that the domain was validated
conn.deliver(packet); // (note: this may not be correct)
} addOutgoingDomainPair(localDomain, remoteDomain);
} return true;
}
@Override ServerDialback method = new ServerDialback(getConnection(), localDomain);
public boolean authenticateSubdomain(String localDomain, String remoteDomain) { if (method.authenticateDomain(socketReader, localDomain, remoteDomain, getStreamID().getID())) {
if (!usingServerDialback) { // Add the validated domain as an authenticated domain
// Using SASL so just assume that the domain was validated addOutgoingDomainPair(localDomain, remoteDomain);
// (note: this may not be correct) return true;
addAuthenticatedDomain(localDomain); }
addHostname(remoteDomain); return false;
return true; }
}
ServerDialback method = new ServerDialback(getConnection(), localDomain); private void returnErrorToSender(Packet packet) {
if (method.authenticateDomain(socketReader, localDomain, remoteDomain, getStreamID().getID())) { RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable();
// Add the validated domain as an authenticated domain if (packet.getError() != null) {
addAuthenticatedDomain(localDomain); Log.debug("Possible double bounce: " + packet.toXML());
addHostname(remoteDomain); }
addOutgoingDomainPair(localDomain, remoteDomain); try {
return true; if (packet instanceof IQ) {
} if (((IQ) packet).isResponse()) {
return false; Log.debug("XMPP specs forbid us to respond with an IQ error to: " + packet.toXML());
} return;
}
private void returnErrorToSender(Packet packet) { IQ reply = new IQ();
RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable(); reply.setID(packet.getID());
if (packet.getError() != null) { reply.setTo(packet.getFrom());
Log.debug("Possible double bounce: " + packet.toXML()); reply.setFrom(packet.getTo());
} reply.setChildElement(((IQ) packet).getChildElement().createCopy());
try { reply.setType(IQ.Type.error);
if (packet instanceof IQ) { reply.setError(PacketError.Condition.remote_server_not_found);
if (((IQ) packet).isResponse()) { routingTable.routePacket(reply.getTo(), reply, true);
Log.debug("XMPP specs forbid us to respond with an IQ error to: " + packet.toXML()); }
return; else if (packet instanceof Presence) {
} if (((Presence)packet).getType() == Presence.Type.error) {
IQ reply = new IQ(); Log.debug("Double-bounce of presence: " + packet.toXML());
reply.setID(packet.getID()); return;
reply.setTo(packet.getFrom()); }
reply.setFrom(packet.getTo()); Presence reply = new Presence();
reply.setChildElement(((IQ) packet).getChildElement().createCopy()); reply.setID(packet.getID());
reply.setType(IQ.Type.error); reply.setTo(packet.getFrom());
reply.setError(PacketError.Condition.remote_server_not_found); reply.setFrom(packet.getTo());
routingTable.routePacket(reply.getTo(), reply, true); reply.setType(Presence.Type.error);
} reply.setError(PacketError.Condition.remote_server_not_found);
else if (packet instanceof Presence) { routingTable.routePacket(reply.getTo(), reply, true);
if (((Presence)packet).getType() == Presence.Type.error) { }
Log.debug("Double-bounce of presence: " + packet.toXML()); else if (packet instanceof Message) {
return; if (((Message)packet).getType() == Message.Type.error){
} Log.debug("Double-bounce of message: " + packet.toXML());
Presence reply = new Presence(); return;
reply.setID(packet.getID()); }
reply.setTo(packet.getFrom()); Message reply = new Message();
reply.setFrom(packet.getTo()); reply.setID(packet.getID());
reply.setType(Presence.Type.error); reply.setTo(packet.getFrom());
reply.setError(PacketError.Condition.remote_server_not_found); reply.setFrom(packet.getTo());
routingTable.routePacket(reply.getTo(), reply, true); reply.setType(Message.Type.error);
} reply.setThread(((Message)packet).getThread());
else if (packet instanceof Message) { reply.setError(PacketError.Condition.remote_server_not_found);
if (((Message)packet).getType() == Message.Type.error){ routingTable.routePacket(reply.getTo(), reply, true);
Log.debug("Double-bounce of message: " + packet.toXML()); }
return; }
} catch (Exception e) {
Message reply = new Message(); Log.error("Error returning error to sender. Original packet: " + packet, e);
reply.setID(packet.getID()); }
reply.setTo(packet.getFrom()); }
reply.setFrom(packet.getTo());
reply.setType(Message.Type.error); @Override
reply.setThread(((Message)packet).getThread()); public String getAvailableStreamFeatures() {
reply.setError(PacketError.Condition.remote_server_not_found); // Nothing special to add
routingTable.routePacket(reply.getTo(), reply, true); return null;
} }
}
catch (Exception e) { @Override
Log.error("Error returning error to sender. Original packet: " + packet, e); public void addOutgoingDomainPair(String localDomain, String remoteDomain) {
} outgoingDomainPairs.add(new DomainPair(localDomain, remoteDomain));
} boolean found = false;
for (DomainPair domainPair : outgoingDomainPairs) {
@Override if (domainPair.getRemote().equals(remoteDomain)) found = true;
public Collection<String> getAuthenticatedDomains() { }
return Collections.unmodifiableCollection(authenticatedDomains); if (!found) {
} XMPPServer.getInstance().getRoutingTable().addServerRoute(new JID(null, remoteDomain, null, true), this);
}
@Override }
public void addAuthenticatedDomain(String domain) {
authenticatedDomains.add(domain); @Override
} public boolean checkOutgoingDomainPair(String localDomain, String remoteDomain) {
return outgoingDomainPairs.contains(new DomainPair(localDomain, remoteDomain));
@Override }
public Collection<String> getHostnames() {
synchronized (hostnames) { @Override
return Collections.unmodifiableCollection(hostnames); public Collection<DomainPair> getOutgoingDomainPairs() {
} return outgoingDomainPairs;
} }
}
@Override
public void addHostname(String hostname) {
synchronized (hostnames) {
hostnames.add(hostname);
}
// Add a new route for this new session
XMPPServer.getInstance().getRoutingTable().addServerRoute(new JID(null, hostname, null, true), this);
}
@Override
public String getAvailableStreamFeatures() {
// Nothing special to add
return null;
}
public void addOutgoingDomainPair(String localDomain, String remoteDomain) {
outgoingDomainPairs.add(new DomainPair(localDomain, remoteDomain));
}
@Override
public boolean checkOutgoingDomainPair(String localDomain, String remoteDomain) {
return outgoingDomainPairs.contains(new DomainPair(localDomain, remoteDomain));
}
}
/* /*
* Copyright (C) 2005-2008 Jive Software. All rights reserved. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.session; package org.jivesoftware.openfire.session;
import java.util.Collection; import java.util.Collection;
/** /**
* Server-to-server communication is done using two TCP connections between the servers. One * Server-to-server communication is done using two TCP connections between the servers. One
* connection is used for sending packets while the other connection is used for receiving packets. * connection is used for sending packets while the other connection is used for receiving packets.
* The <tt>OutgoingServerSession</tt> represents the connection to a remote server that will only * The <tt>OutgoingServerSession</tt> represents the connection to a remote server that will only
* be used for sending packets.<p> * be used for sending packets.<p>
* *
* Once the connection has been established with the remote server and at least a domain has been * Once the connection has been established with the remote server and at least a domain has been
* authenticated then a new route will be added to the routing table for this connection. For * authenticated then a new route will be added to the routing table for this connection. For
* optimization reasons the same outgoing connection will be used even if the remote server has * optimization reasons the same outgoing connection will be used even if the remote server has
* several hostnames. However, different routes will be created in the routing table for each * several hostnames. However, different routes will be created in the routing table for each
* hostname of the remote server. * hostname of the remote server.
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public interface OutgoingServerSession extends ServerSession { public interface OutgoingServerSession extends ServerSession {
/** /**
* Returns a collection with all the domains, subdomains and virtual hosts that where * Authenticates a subdomain of this server with the specified remote server over an exsiting
* authenticated. The remote server will accept packets sent from any of these domains, * outgoing connection. If the existing session was using server dialback then a new db:result
* subdomains and virtual hosts. * is going to be sent to the remote server. But if the existing session was TLS+SASL based
* * then just assume that the subdomain was authenticated by the remote server.
* @return domains, subdomains and virtual hosts that where validated. *
*/ * @param domain the locally domain to authenticate with the remote server.
Collection<String> getAuthenticatedDomains(); * @param hostname the domain of the remote server.
* @return True if the domain was authenticated by the remote server.
/** */
* Adds a new authenticated domain, subdomain or virtual host to the list of boolean authenticateSubdomain(String domain, String hostname);
* authenticated domains for the remote server. The remote server will accept packets
* sent from this new authenticated domain. /**
* * Checks to see if a pair of domains has previously been authenticated.
* @param domain the new authenticated domain, subdomain or virtual host to add. *
*/ * Since domains are authenticated as pairs, authenticating A->B does
void addAuthenticatedDomain(String domain); * not imply anything about A-->C or D->B.
*
/** * @param local the local domain (previously: authenticated domain)
* Returns the list of hostnames related to the remote server. This tracking is useful for * @param remote the remote domain (previous: hostname)
* reusing the same session for the same remote server even if the server has many names. * @return True if the pair of domains has been authenticated.
* */
* @return the list of hostnames related to the remote server. boolean checkOutgoingDomainPair(String local, String remote);
*/
Collection<String> getHostnames(); /**
* Marks a domain pair as being authenticated.
/** *
* Adds a new hostname to the list of known hostnames of the remote server. This tracking is * @param local the locally hosted domain.
* useful for reusing the same session for the same remote server even if the server has * @param remote the remote domain.
* many names. */
* void addOutgoingDomainPair(String local, String remote);
* @param hostname the new known name of the remote server
*/ /**
void addHostname(String hostname); * Obtains all authenticated domain pairs.
*
/** * Most callers should avoid accessing this and use a simple check as above.
* Authenticates a subdomain of this server with the specified remote server over an exsiting *
* outgoing connection. If the existing session was using server dialback then a new db:result * @return collection of authenticated DomainPairs
* is going to be sent to the remote server. But if the existing session was TLS+SASL based */
* then just assume that the subdomain was authenticated by the remote server. Collection<DomainPair> getOutgoingDomainPairs();
* }
* @param domain the local subdomain to authenticate with the remote server.
* @param hostname the hostname of the remote server.
* @return True if the subdomain was authenticated by the remote server.
*/
boolean authenticateSubdomain(String domain, String hostname);
boolean checkOutgoingDomainPair(String local, String remote);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment