Commit 056012e1 authored by Dave Cridland's avatar Dave Cridland

Merge pull request #479 from guusdk/s2s

S2S logging
parents 39d00f52 2490aba5
package org.jivesoftware.openfire.net;
import org.jivesoftware.openfire.server.RemoteServerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
/**
* Utility class to generate Socket instances.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class SocketUtil
{
private final static Logger Log = LoggerFactory.getLogger( SocketUtil.class );
/**
* Creates a socket connection to an XMPP domain.
*
* This implementation uses DNS SRV records to find a list of remote hosts for the XMPP domain (as implemented by
* {@link DNSUtil#resolveXMPPDomain(String, int)}. It then iteratively tries to create a socket connection to each
* of them, until one socket connection succeeds.
*
* Either the connected Socket instance is returned, or null if no connection could be established.
*
* Note that this method blocks while performing network IO. The timeout as defined by
* {@link RemoteServerManager#getSocketTimeout()} is observed.
*
* @param xmppDomain The XMPP domain to connect to.
* @param port The port to connect to when DNS resolution fails.
* @return a Socket instance that is connected, or null.
* @see DNSUtil#resolveXMPPDomain(String, int)
*/
public static Socket createSocketToXmppDomain( String xmppDomain, int port )
{
Log.debug( "Creating a socket connection to XMPP domain '{}' ...", xmppDomain );
Log.debug( "Use DNS to resolve remote hosts for the provided XMPP domain '{}' (default port: {}) ...", xmppDomain, port );
final List<DNSUtil.HostAddress> remoteHosts = DNSUtil.resolveXMPPDomain( xmppDomain, port );
Log.debug( "Found {} host(s) for XMPP domain '{}'.", remoteHosts.size(), xmppDomain );
Socket socket = null;
final int socketTimeout = RemoteServerManager.getSocketTimeout();
for ( DNSUtil.HostAddress remoteHost : remoteHosts )
{
final String realHostname = remoteHost.getHost();
final int realPort = remoteHost.getPort();
try
{
// (re)initialize the socket.
socket = new Socket();
Log.debug( "Trying to create socket connection to XMPP domain '{}' using remote host: {}:{} (blocks up to {} ms) ...", xmppDomain, realHostname, realPort, socketTimeout );
socket.connect( new InetSocketAddress( realHostname, realPort ), socketTimeout );
Log.debug( "Successfully created socket connection to XMPP domain '{}' using remote host: {}:{}!", xmppDomain, realHostname, realPort );
return socket;
}
catch ( Exception e )
{
Log.debug( "An exception occurred while trying to create a socket connection to XMPP domain '{}' using remote host {}:{}", xmppDomain, realHostname, realPort, e );
Log.warn( "Unable to create a socket connection to XMPP domain '{}' using remote host: {}:{}. Cause: {} (a full stacktrace is logged on debug level)", xmppDomain, realHostname, realPort, e.getMessage() );
try
{
if ( socket != null )
{
socket.close();
socket = null;
}
}
catch ( IOException ex )
{
Log.debug( "An additional exception occurred while trying to close a socket when creating a connection to {}:{} failed.", realHostname, realPort, ex );
}
}
}
Log.warn( "Unable to create a socket connection to XMPP domain '{}': Unable to connect to any of its remote hosts.", xmppDomain );
return null;
}
}
...@@ -208,7 +208,7 @@ public class ServerDialback { ...@@ -208,7 +208,7 @@ public class ServerDialback {
* @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none. * @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none.
*/ */
public LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) { public LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Create Outgoing Session from: " + localDomain + " to: " + remoteDomain + " (port: " + port+ ")]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Originating Server: Create Outgoing Session from: " + localDomain + " to RS at: " + remoteDomain + " (port: " + port+ ")]" );
log.debug( "Creating new outgoing session..." ); log.debug( "Creating new outgoing session..." );
...@@ -216,29 +216,14 @@ public class ServerDialback { ...@@ -216,29 +216,14 @@ public class ServerDialback {
int realPort = port; int realPort = port;
try { try {
// Establish a TCP connection to the Receiving Server // Establish a TCP connection to the Receiving Server
Socket socket = new Socket(); final Socket socket = SocketUtil.createSocketToXmppDomain( remoteDomain, port );
log.debug( "Get a list of real hostnames to connect to using DNS lookup of the specified hostname." );
List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(remoteDomain, port); if ( socket == null ) {
for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) { log.info( "Unable to create new outgoing session: Cannot create a plain socket connection with any applicable remote host." );
try { return null;
DNSUtil.HostAddress address = it.next();
hostname = address.getHost();
realPort = address.getPort();
log.debug( "Trying to create plain socket connection to: {}:{} ...", hostname, realPort );
// Establish a TCP connection to the Receiving Server
socket.connect(new InetSocketAddress(hostname, realPort), RemoteServerManager.getSocketTimeout());
log.debug( "Plain socket connection to {}:{} successful!", hostname, realPort );
break;
}
catch (Exception e) {
log.debug( "An exception occurred while trying to create a plain socket connection to: {}:{}", hostname, realPort, e );
log.warn( "Unable to create plain socket connection to: {}:{}. Cause: {} (a full stacktrace is logged on debug level)", hostname, realPort, e.getMessage() );
// TODO should this not stop processing? lots of this code is very similar to the implementation in LocalOutgoingServerSession. Should implementations be merged?
}
} }
connection =
new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false);
false);
log.debug( "Send the stream header and wait for response..." ); log.debug( "Send the stream header and wait for response..." );
StringBuilder stream = new StringBuilder(); StringBuilder stream = new StringBuilder();
...@@ -319,14 +304,14 @@ public class ServerDialback { ...@@ -319,14 +304,14 @@ public class ServerDialback {
* Most probably the Originating Server machine will be the Authoritative Server too. * Most probably the Originating Server machine will be the Authoritative Server too.
* *
* @param socketReader the reader to use for reading the answer from the Receiving Server. * @param socketReader the reader to use for reading the answer from the Receiving Server.
* @param domain the domain to authenticate. * @param localDomain the domain to authenticate.
* @param hostname the hostname of the remote server (i.e. Receiving Server). * @param remoteDomain the domain of the remote server (i.e. Receiving Server).
* @param id the stream id to be used for creating the dialback key. * @param id the stream id to be used for creating the dialback key.
* @return true if the Receiving Server authenticated the domain with the Authoritative Server. * @return true if the Receiving Server authenticated the domain with the Authoritative Server.
*/ */
public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String domain, String hostname, String id) { public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String localDomain, String remoteDomain, String id) {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Authenticate domain: " + domain + " (id " + id + ") with hostname: " + hostname + "]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Originating Server: Authenticate domain: " + localDomain + " with RS: " + remoteDomain + " (id: " + id + ")]" );
log.debug( "Authenticating domain ..." ); log.debug( "Authenticating domain ..." );
...@@ -336,8 +321,8 @@ public class ServerDialback { ...@@ -336,8 +321,8 @@ public class ServerDialback {
log.debug( "Sending dialback key and wait for the validation response..." ); log.debug( "Sending dialback key and wait for the validation response..." );
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("<db:result"); sb.append("<db:result");
sb.append(" from=\"").append(domain).append("\""); sb.append(" from=\"").append(localDomain).append("\"");
sb.append(" to=\"").append(hostname).append("\">"); sb.append(" to=\"").append(remoteDomain).append("\">");
sb.append(key); sb.append(key);
sb.append("</db:result>"); sb.append("</db:result>");
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
...@@ -495,44 +480,44 @@ public class ServerDialback { ...@@ -495,44 +480,44 @@ public class ServerDialback {
public boolean validateRemoteDomain(Element doc, StreamID streamID) { public boolean validateRemoteDomain(Element doc, StreamID streamID) {
StringBuilder sb; StringBuilder sb;
String recipient = doc.attributeValue("to"); String recipient = doc.attributeValue("to");
String hostname = doc.attributeValue("from"); String remoteDomain = doc.attributeValue("from");
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Validate remote domain:" + recipient + "(id " + streamID + ") for: " + hostname + "]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Receiving Server: Validate domain:" + recipient + "(id " + streamID + ") for OS: " + remoteDomain + "]" );
log.debug( "Validating domain..."); log.debug( "Validating domain...");
if (!RemoteServerManager.canAccess(hostname)) { if (!RemoteServerManager.canAccess(remoteDomain)) {
connection.deliverRawText(new StreamError(StreamError.Condition.policy_violation).toXML()); connection.deliverRawText(new StreamError(StreamError.Condition.policy_violation).toXML());
// Close the underlying connection // Close the underlying connection
connection.close(); connection.close();
log.debug( "Unable to validate domain: Remote server is not allowed to establish a connection to this server." ); log.debug( "Unable to validate domain: Remote domain is not allowed to establish a connection to this server." );
return false; return false;
} }
else if (isHostUnknown(recipient)) { else if (isHostUnknown(recipient)) {
dialbackError(recipient, hostname, new PacketError(PacketError.Condition.item_not_found, PacketError.Type.cancel, "Service not hosted here")); dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.item_not_found, PacketError.Type.cancel, "Service not hosted here"));
log.debug( "Unable to validate domain: Hostname not recognized." ); log.debug( "Unable to validate domain: recipient not recognized as a local domain." );
return false; return false;
} }
else { else {
log.debug( "Check if the remote server already has a connection to the target domain/subdomain" ); log.debug( "Check if the remote domain already has a connection to the target domain/subdomain" );
boolean alreadyExists = false; boolean alreadyExists = false;
for (IncomingServerSession session : sessionManager.getIncomingServerSessions(hostname)) { for (IncomingServerSession session : sessionManager.getIncomingServerSessions(remoteDomain)) {
if (recipient.equals(session.getLocalDomain())) { if (recipient.equals(session.getLocalDomain())) {
alreadyExists = true; alreadyExists = true;
} }
} }
if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) { if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) {
dialbackError(recipient, hostname, new PacketError(PacketError.Condition.resource_constraint, PacketError.Type.cancel, "Incoming session already exists")); dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.resource_constraint, PacketError.Type.cancel, "Incoming session already exists"));
log.debug( "Unable to validate domain: An incoming connection already exists from this remote host, and multiple connections are not allowed." ); log.debug( "Unable to validate domain: An incoming connection already exists from this remote domain, and multiple connections are not allowed." );
return false; return false;
} }
else { else {
log.debug( "Checking to see if the remote host provides stronger authentication based on SASL. If that's the case, dialback-based authentication can be skipped." ); log.debug( "Checking to see if the remote server provides stronger authentication based on SASL. If that's the case, dialback-based authentication can be skipped." );
if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) { if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), remoteDomain, true)) {
log.debug( "Host authenticated based on SASL. Weaker dialback-based authentication is skipped." ); log.debug( "Host authenticated based on SASL. Weaker dialback-based authentication is skipped." );
sb = new StringBuilder(); sb = new StringBuilder();
sb.append("<db:result"); sb.append("<db:result");
sb.append(" from=\"").append(recipient).append("\""); sb.append(" from=\"").append(recipient).append("\"");
sb.append(" to=\"").append(hostname).append("\""); sb.append(" to=\"").append(remoteDomain).append("\"");
sb.append(" type=\"valid\""); sb.append(" type=\"valid\"");
sb.append("/>"); sb.append("/>");
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
...@@ -545,51 +530,28 @@ public class ServerDialback { ...@@ -545,51 +530,28 @@ public class ServerDialback {
String key = doc.getTextTrim(); String key = doc.getTextTrim();
log.debug( "Get a list of real hostnames and try to using DNS lookup of the specified domain." ); final Socket socket = SocketUtil.createSocketToXmppDomain( remoteDomain, RemoteServerManager.getPortForServer(remoteDomain) );
List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, RemoteServerManager.getPortForServer(hostname));
Socket socket = new Socket(); if ( socket == null )
String realHostname = null; {
int realPort;
for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) { // TODO Remove code duplication (also in LocalOutgoingServerSession)
try {
DNSUtil.HostAddress address = it.next();
realHostname = address.getHost();
realPort = address.getPort();
log.debug( "Trying to create plain socket connection to: {}:{} ...", realHostname, realPort );
// Establish a TCP connection to the Receiving Server
socket.connect(new InetSocketAddress(realHostname, realPort), RemoteServerManager.getSocketTimeout());
log.debug( "Plain socket connection to {}:{} successful!", realHostname, realPort );
break;
}
catch (Exception e) {
log.debug( "An exception occurred while trying to create a plain socket connection to: {}", realHostname, e );
log.warn( "Unable to create plain socket connection to: {}. Cause: {} (a full stacktrace is logged on debug level)", realHostname, e.getMessage() );
}
}
if (!socket.isConnected()) {
log.debug( "Unable to validate domain: No server available for verifying key of remote server." ); log.debug( "Unable to validate domain: No server available for verifying key of remote server." );
dialbackError(recipient, hostname, new PacketError(PacketError.Condition.remote_server_not_found, PacketError.Type.cancel, "Unable to connect to authoritative server")); dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.remote_server_not_found, PacketError.Type.cancel, "Unable to connect to authoritative server"));
try {
socket.close();
} catch (IOException e) {
log.warn("Socket error on close", e);
}
return false; return false;
} }
try { try {
log.debug( "Verifying dialback key..." ); log.debug( "Verifying dialback key..." );
VerifyResult result = verifyKey(key, streamID.toString(), recipient, hostname, socket); VerifyResult result = verifyKey(key, streamID.toString(), recipient, remoteDomain, socket);
switch(result) { switch(result) {
case valid: case valid:
case invalid: case invalid:
boolean valid = (result == VerifyResult.valid); boolean valid = (result == VerifyResult.valid);
log.debug( "Dialback key is" + (valid? "valid":"invalid") + ". Sending verification result to remote host." ); log.debug( "Dialback key is" + (valid? "valid":"invalid") + ". Sending verification result to remote domain." );
sb = new StringBuilder(); sb = new StringBuilder();
sb.append("<db:result"); sb.append("<db:result");
sb.append(" from=\"").append(recipient).append("\""); sb.append(" from=\"").append(recipient).append("\"");
sb.append(" to=\"").append(hostname).append("\""); sb.append(" to=\"").append(remoteDomain).append("\"");
sb.append(" type=\""); sb.append(" type=\"");
sb.append(valid ? "valid" : "invalid"); sb.append(valid ? "valid" : "invalid");
sb.append("\"/>"); sb.append("\"/>");
...@@ -608,11 +570,11 @@ public class ServerDialback { ...@@ -608,11 +570,11 @@ public class ServerDialback {
break; break;
} }
log.debug( "Unable to validate domain: key verification did not complete (the AS likely returned an error or a time out occurred)." ); log.debug( "Unable to validate domain: key verification did not complete (the AS likely returned an error or a time out occurred)." );
dialbackError(recipient, hostname, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server returned error")); dialbackError( recipient, remoteDomain, new PacketError( PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server returned error" ) );
return false; return false;
} }
catch (Exception e) { catch (Exception e) {
dialbackError(recipient, hostname, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server failed")); dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server failed"));
log.warn( "Unable to validate domain: An exception occurred while verifying the dialback key.", e ); log.warn( "Unable to validate domain: An exception occurred while verifying the dialback key.", e );
return false; return false;
} }
...@@ -631,8 +593,8 @@ public class ServerDialback { ...@@ -631,8 +593,8 @@ public class ServerDialback {
return host_unknown; return host_unknown;
} }
private VerifyResult sendVerifyKey(String key, String streamID, String recipient, String hostname, Writer writer, XMPPPacketReader reader, Socket socket) throws IOException, XmlPullParserException, RemoteConnectionFailedException { private VerifyResult sendVerifyKey(String key, String streamID, String recipient, String remoteDomain, Writer writer, XMPPPacketReader reader, Socket socket) throws IOException, XmlPullParserException, RemoteConnectionFailedException {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Verify key with AS: " + hostname + " for: " + recipient + " (id " + streamID + ")]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Receiving Server: Verify key with AS: " + remoteDomain + " for OS: " + recipient + " (id " + streamID + ")]" );
VerifyResult result = VerifyResult.error; VerifyResult result = VerifyResult.error;
TLSStreamHandler tlsStreamHandler; TLSStreamHandler tlsStreamHandler;
...@@ -644,7 +606,7 @@ public class ServerDialback { ...@@ -644,7 +606,7 @@ public class ServerDialback {
stream.append(" xmlns=\"jabber:server\""); stream.append(" xmlns=\"jabber:server\"");
stream.append(" xmlns:db=\"jabber:server:dialback\""); stream.append(" xmlns:db=\"jabber:server:dialback\"");
stream.append(" to=\""); stream.append(" to=\"");
stream.append(hostname); stream.append(remoteDomain);
stream.append("\""); stream.append("\"");
stream.append(" from=\""); stream.append(" from=\"");
stream.append(recipient); stream.append(recipient);
...@@ -697,7 +659,7 @@ public class ServerDialback { ...@@ -697,7 +659,7 @@ public class ServerDialback {
reader.getXPPParser().setInput(new InputStreamReader(tlsStreamHandler.getInputStream(),StandardCharsets.UTF_8)); reader.getXPPParser().setInput(new InputStreamReader(tlsStreamHandler.getInputStream(),StandardCharsets.UTF_8));
log.debug( "Successfully negotiated TLS with AS... " ); log.debug( "Successfully negotiated TLS with AS... " );
/// Recurses! /// Recurses!
return sendVerifyKey(key, streamID, recipient, hostname, writer, reader, socket); return sendVerifyKey(key, streamID, recipient, remoteDomain, writer, reader, socket);
} }
} }
if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) { if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
...@@ -705,7 +667,7 @@ public class ServerDialback { ...@@ -705,7 +667,7 @@ public class ServerDialback {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("<db:verify"); sb.append("<db:verify");
sb.append(" from=\"").append(recipient).append("\""); sb.append(" from=\"").append(recipient).append("\"");
sb.append(" to=\"").append(hostname).append("\""); sb.append(" to=\"").append(remoteDomain).append("\"");
sb.append(" id=\"").append(streamID).append("\">"); sb.append(" id=\"").append(streamID).append("\">");
sb.append(key); sb.append(key);
sb.append("</db:verify>"); sb.append("</db:verify>");
...@@ -723,7 +685,7 @@ public class ServerDialback { ...@@ -723,7 +685,7 @@ public class ServerDialback {
// condition is sent to the Originating Server // condition is sent to the Originating Server
throw new RemoteConnectionFailedException("Invalid ID"); throw new RemoteConnectionFailedException("Invalid ID");
} }
else if (isHostUnknown(doc.attributeValue("to"))) { else if (isHostUnknown( doc.attributeValue( "to" ) )) {
// Include the host-unknown stream error condition in the response // Include the host-unknown stream error condition in the response
writer.write( writer.write(
new StreamError(StreamError.Condition.host_unknown).toXML()); new StreamError(StreamError.Condition.host_unknown).toXML());
...@@ -732,7 +694,7 @@ public class ServerDialback { ...@@ -732,7 +694,7 @@ public class ServerDialback {
// condition is sent to the Originating Server // condition is sent to the Originating Server
throw new RemoteConnectionFailedException("Host unknown"); throw new RemoteConnectionFailedException("Host unknown");
} }
else if (!hostname.equals(doc.attributeValue("from"))) { else if (!remoteDomain.equals(doc.attributeValue("from"))) {
// Include the invalid-from stream error condition in the response // Include the invalid-from stream error condition in the response
writer.write( writer.write(
new StreamError(StreamError.Condition.invalid_from).toXML()); new StreamError(StreamError.Condition.invalid_from).toXML());
...@@ -780,9 +742,9 @@ public class ServerDialback { ...@@ -780,9 +742,9 @@ public class ServerDialback {
/** /**
* Verifies the key with the Authoritative Server. * Verifies the key with the Authoritative Server.
*/ */
private VerifyResult verifyKey(String key, String streamID, String recipient, String hostname, Socket socket) throws IOException, XmlPullParserException, RemoteConnectionFailedException { private VerifyResult verifyKey(String key, String streamID, String recipient, String remoteDomain, Socket socket) throws IOException, XmlPullParserException, RemoteConnectionFailedException {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Verify key with AS: " + hostname + " for: " + recipient + " (id " + streamID + ")]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Receiving Server: Verify key with AS: " + remoteDomain + " for OS: " + recipient + " (id " + streamID + ")]" );
log.debug( "Verifying key ..." ); log.debug( "Verifying key ..." );
XMPPPacketReader reader; XMPPPacketReader reader;
...@@ -797,7 +759,7 @@ public class ServerDialback { ...@@ -797,7 +759,7 @@ public class ServerDialback {
reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(), CHARSET)); reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(), CHARSET));
// Get a writer for sending the open stream tag // Get a writer for sending the open stream tag
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), CHARSET)); writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), CHARSET));
result = sendVerifyKey(key, streamID, recipient, hostname, writer, reader, socket); result = sendVerifyKey(key, streamID, recipient, remoteDomain, writer, reader, socket);
} }
finally { finally {
try { try {
...@@ -848,7 +810,7 @@ public class ServerDialback { ...@@ -848,7 +810,7 @@ public class ServerDialback {
String key = doc.getTextTrim(); String key = doc.getTextTrim();
String id = doc.attributeValue("id"); String id = doc.attributeValue("id");
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Verify key for RS: " + verifyFROM + " (id " + id+ ")]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Authoritative Server: Verify key sent by RS: " + verifyFROM + " (id " + id+ ")]" );
log.debug( "Verifying key... "); log.debug( "Verifying key... ");
......
...@@ -43,10 +43,7 @@ import org.jivesoftware.openfire.SessionManager; ...@@ -43,10 +43,7 @@ import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID; import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.net.DNSUtil; import org.jivesoftware.openfire.net.*;
import org.jivesoftware.openfire.net.MXParser;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.server.OutgoingServerSocketReader; import org.jivesoftware.openfire.server.OutgoingServerSocketReader;
import org.jivesoftware.openfire.server.RemoteServerConfiguration; import org.jivesoftware.openfire.server.RemoteServerConfiguration;
import org.jivesoftware.openfire.server.RemoteServerManager; import org.jivesoftware.openfire.server.RemoteServerManager;
...@@ -105,80 +102,119 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -105,80 +102,119 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
private OutgoingServerSocketReader socketReader; private OutgoingServerSocketReader socketReader;
/** /**
* Creates a new outgoing connection to the specified hostname if no one exists. The port of * Authenticates the local domain to the remote domain. Once authenticated the remote domain can be expected to
* the remote server could be configured by setting the <b>xmpp.server.socket.remotePort</b> * start accepting data from the local domain.
* property or otherwise the standard port 5269 will be used. Either a new connection was
* created or already existed the specified hostname will be authenticated with the remote
* server. Once authenticated the remote server will start accepting packets from the specified
* domain.<p>
* *
* The Server Dialback method is currently the only implemented method for server-to-server * This implementation will attempt to re-use an existing connection. An connection is deemed re-usable when it is either:
* authentication. This implies that the remote server will ask the Authoritative Server * <ul>
* to verify the domain to authenticate. Most probably this (local) server will act as the * <li>authenticated to the remote domain itself, or:</li>
* Authoritative Server. See {@link IncomingServerSession} for more information. * <li>authenticated to a sub- or superdomain of the remote domain AND offers dialback.</li>
* </ul>
* *
* @param domain the local domain to authenticate with the remote server. * When no re-usable connection exists, a new connection will be created.
* @param hostname the hostname of the remote server. *
* DNS will be used to find hosts for the remote domain. When DNS records do not specify a port, port 5269 will be
* used unless this default is overridden by the <b>xmpp.server.socket.remotePort</b> property.
*
* @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.
* @return True if the domain was authenticated by the remote server. * @return True if the domain was authenticated by the remote server.
*/ */
public static boolean authenticateDomain(final String domain, final String hostname) { public static boolean authenticateDomain(final String localDomain, final String remoteDomain) {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Authenticate domain: " + domain + " (hostname: " + hostname + ")]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Authenticate local domain: '" + localDomain + "' to remote domain: '" + remoteDomain + "']" );
log.debug( "Authenticating... " ); log.debug( "Start domain authentication ..." );
if (hostname == null || hostname.length() == 0 || hostname.trim().indexOf(' ') > -1) { if (remoteDomain == null || remoteDomain.length() == 0 || remoteDomain.trim().indexOf(' ') > -1) {
// Do nothing if the target hostname is empty, null or contains whitespaces // Do nothing if the target domain is empty, null or contains whitespaces
log.warn( "Unable to authenticate a domain when an empty hostname is provided!" ); log.warn( "Unable to authenticate: remote domain is invalid." );
return false; return false;
} }
try { try {
// Check if the remote hostname is in the blacklist // Check if the remote domain is in the blacklist
if (!RemoteServerManager.canAccess(hostname)) { if (!RemoteServerManager.canAccess(remoteDomain)) {
log.info( "Unable to authenticate: Hostname is not accessible according to our configuration (typical causes: server federation is disabled, or hostname is blacklisted)." ); 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; return false;
} }
log.debug( "Searching for a pre-existing session to this hostname... If one exists, it will be re-used to authenticate this domain." ); log.debug( "Searching for pre-existing outgoing sessions to the remote domain (if one exists, it will be re-used) ..." );
OutgoingServerSession session; OutgoingServerSession session;
SessionManager sessionManager = SessionManager.getInstance(); SessionManager sessionManager = SessionManager.getInstance();
if (sessionManager == null) { if (sessionManager == null) {
// Server is shutting down while we are trying to create a new s2s connection // Server is shutting down while we are trying to create a new s2s connection
log.warn( "Unable to authenticate: the SessionManager instance is not available. This should not occur unless Openfire is starting up or shutting down." ); log.warn( "Unable to authenticate: a SessionManager instance is not available. This should not occur unless Openfire is starting up or shutting down." );
return false; return false;
} }
session = sessionManager.getOutgoingServerSession(hostname); session = sessionManager.getOutgoingServerSession(remoteDomain);
if (session == null) {
log.debug( "There is no pre-existing session to this hostname." ); if (session == null)
{
log.debug( "Searching for pre-existing sessions to other hostnames that previously authenticated this domain... If one exists, it will be re-used to authenticate this domain." ); 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) ..." );
// Try locating if the remote server has previously authenticated with this server
for (IncomingServerSession incomingSession : sessionManager.getIncomingServerSessions(hostname)) { for ( IncomingServerSession incomingSession : sessionManager.getIncomingServerSessions( remoteDomain ) )
for (String otherHostname : incomingSession.getValidatedDomains()) { {
session = sessionManager.getOutgoingServerSession(otherHostname); // 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() )
if (session.isUsingServerDialback()) { {
log.debug( "A session to the same remote server but with different hostname ('{}') was found. This session will be re-used.", otherHostname ); // See if there's an outgoing session to any of the (other) domains hosted by the remote domain.
session = sessionManager.getOutgoingServerSession( otherRemoteDomain );
if (session != null)
{
log.debug( "An outgoing session to a different domain ('{}') hosted on the remote domain was found.", otherRemoteDomain );
// As this sub/superdomain is different from the original remote domain, we need to check if it supports dialback.
if ( session.isUsingServerDialback() )
{
log.debug( "Dialback was used for '{}'. This session can be re-used.", otherRemoteDomain );
break; break;
} else { }
else
{
log.debug( "Dialback was not used for '{}'. This session cannot be re-used.", otherRemoteDomain );
session = null; session = null;
} }
} }
} }
} }
if (session == null) { if (session == null) {
log.debug( "There are no pre-existing session to other hostnames for this domain." ); log.debug( "There are no pre-existing session to other domains hosted on the remote domain." );
}
}
if ( session != null )
{
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.getAuthenticatedDomains().contains( localDomain ) && session.getHostnames().contains( remoteDomain ) )
{
// Do nothing since the domain has already been authenticated.
log.debug( "Authentication successful (domain was already authenticated in the pre-existing session)." );
return true;
}
// A session already exists so authenticate the domain using that session.
if ( session.authenticateSubdomain( localDomain, remoteDomain ) )
{
log.debug( "Authentication successful (domain authentication was added using a pre-existing session)." );
return true;
}
else
{
log.warn( "Unable to authenticate: Unable to add authentication to pre-exising session." );
return false;
} }
} }
if (session == null) { else
log.debug( "Creating new authenticated session..." ); {
int port = RemoteServerManager.getPortForServer(hostname); log.debug( "Unable to re-use an existing session. Creating a new session ..." );
session = createOutgoingSession(domain, hostname, port); int port = RemoteServerManager.getPortForServer(remoteDomain);
session = createOutgoingSession(localDomain, remoteDomain, port);
if (session != null) { if (session != null) {
log.debug( "Created a new session." ); log.debug( "Created a new session." );
// Add the validated domain as an authenticated domain // Add the validated domain as an authenticated domain
session.addAuthenticatedDomain(domain); session.addAuthenticatedDomain(localDomain);
// Add the new hostname to the list of names that the server may have // Add the new domain to the list of names that the server may have
session.addHostname(hostname); session.addHostname(remoteDomain);
// Notify the SessionManager that a new session has been created // Notify the SessionManager that a new session has been created
sessionManager.outgoingServerSessionCreated((LocalOutgoingServerSession) session); sessionManager.outgoingServerSessionCreated((LocalOutgoingServerSession) session);
log.debug( "Authentication successful." ); log.debug( "Authentication successful." );
...@@ -188,95 +224,42 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -188,95 +224,42 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
return false; return false;
} }
} }
log.debug( "A session already exists. The session was established using server dialback so it is possible to do piggybacking to authenticate more domains." );
if (session.getAuthenticatedDomains().contains(domain) && session.getHostnames().contains(hostname)) {
// Do nothing since the domain has already been authenticated
log.debug( "Authentication successful (domain was already authenticated in the pre-existing session)." );
return true;
}
// A session already exists so authenticate the domain using that session
if ( session.authenticateSubdomain( domain, hostname ) ) {
log.debug( "Authentication successful (domain authentication was added using a pre-existing session)." );
return true;
} else {
log.warn( "Unable to authenticate: Unable to add authentication to pre-exising session." );
return false;
}
} }
catch (Exception e) { catch (Exception e)
log.error( "An exception occurred while authenticating domain with remote server!", e ); {
log.error( "An exception occurred while authenticating remote domain!", e );
return false;
} }
log.warn( "Unable to authenticate: exhausted session (re-)usage options." );
return false;
} }
/** /**
* Establishes a new outgoing session to a remote server. If the remote server supports TLS * Establishes a new outgoing session to a remote domain. If the remote domain supports TLS and SASL then the new
* and SASL then the new outgoing connection will be secured with TLS and authenticated * outgoing connection will be secured with TLS and authenticated using SASL. However, if TLS or SASL is not
* using SASL. However, if TLS or SASL is not supported by the remote server or if an * supported by the remote domain or if an error occurred while securing or authenticating the connection using SASL
* error occured while securing or authenticating the connection using SASL then server * then server dialback will be used.
* dialback method will be used.
* *
* @param domain the local domain to authenticate with the remote server. * @param localDomain the local domain to authenticate with the remote domain.
* @param hostname the hostname of the remote server. * @param remoteDomain the remote domain.
* @param port default port to use to establish the connection. * @param port default port to use to establish the connection.
* @return new outgoing session to a remote server. * @return new outgoing session to a remote domain, or null.
*/ */
private static LocalOutgoingServerSession createOutgoingSession(String domain, String hostname, int port) { private static LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Create outgoing session to: " + domain + " (" + hostname + ":"+ port+")]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Create outgoing session for: " + localDomain + " to " + remoteDomain + "]" );
log.debug( "Creating new session..." ); log.debug( "Creating new session..." );
String localDomainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true);
RemoteServerConfiguration configuration = RemoteServerManager.getConfiguration(hostname);
if (configuration != null) {
// TODO Use the specific TLS configuration for this remote server
//useTLS = configuration.isTLSEnabled();
}
// Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or server dialback) // Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or server dialback)
String realHostname = null; log.debug( "Creating plain socket connection to a host that belongs to the remote XMPP domain." );
int realPort = port; final Socket socket = SocketUtil.createSocketToXmppDomain( remoteDomain, port );
Socket socket = null;
log.debug( "Get a list of real hostnames to connect to using DNS lookup of the specified hostname." ); if ( socket == null ) {
List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, port); log.info( "Unable to create new session: Cannot create a plain socket connection with any applicable remote host." );
for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
try {
socket = new Socket();
DNSUtil.HostAddress address = it.next();
realHostname = address.getHost();
realPort = address.getPort();
log.debug( "Trying to create plain socket connection to: {}:{} ...", realHostname, realPort );
socket.connect(new InetSocketAddress(realHostname, realPort), RemoteServerManager.getSocketTimeout());
log.debug( "Plain socket connection to {}:{} successful!", realHostname, realPort );
break;
}
catch (Exception e) {
log.debug( "An exception occurred while trying to create a plain socket connection to: {}:{}", realHostname, realPort, e );
log.warn( "Unable to create plain socket connection to: {}:{}. Cause: {} (a full stacktrace is logged on debug level)", realHostname, realPort, e.getMessage() );
try {
if (socket != null) {
socket.close();
}
}
catch (IOException ex) {
log.debug( "Additional exception while trying to close socket when connection to remote server failed.", ex);
}
}
}
if (!socket.isConnected()) {
log.info( "Unable to create new session: Cannot create a plain socket connection with any applicable host." );
return null; return null;
} }
SocketConnection connection = null; SocketConnection connection = null;
try { try {
connection = connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false);
new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
false);
log.debug( "Send the stream header and wait for response..." ); log.debug( "Send the stream header and wait for response..." );
StringBuilder openingStream = new StringBuilder(); StringBuilder openingStream = new StringBuilder();
...@@ -284,8 +267,8 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -284,8 +267,8 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
openingStream.append(" xmlns:db=\"jabber:server:dialback\""); openingStream.append(" xmlns:db=\"jabber:server:dialback\"");
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
openingStream.append(" xmlns=\"jabber:server\""); openingStream.append(" xmlns=\"jabber:server\"");
openingStream.append(" from=\"").append(localDomainName).append("\""); // OF-673 openingStream.append(" from=\"").append(XMPPServer.getInstance().getServerInfo().getXMPPDomain()).append("\""); // OF-673
openingStream.append(" to=\"").append(hostname).append("\""); openingStream.append(" to=\"").append(remoteDomain).append("\"");
openingStream.append(" version=\"1.0\">"); openingStream.append(" version=\"1.0\">");
connection.deliverRawText(openingStream.toString()); connection.deliverRawText(openingStream.toString());
...@@ -312,13 +295,14 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -312,13 +295,14 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
// Restore default timeout // Restore default timeout
socket.setSoTimeout(soTimeout); socket.setSoTimeout(soTimeout);
log.debug( "Processing stream features of the remote server..." ); log.debug( "Processing stream features of the remote domain..." );
Element features = reader.parseDocument().getRootElement(); Element features = reader.parseDocument().getRootElement();
if (features != null) { if (features != null) {
log.debug( "Check if both us as well as the remote server have enabled STARTTLS and/or dialback ..." ); log.debug( "Check if both us as well as the remote server have enabled STARTTLS and/or dialback ..." );
final boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true);
if (useTLS && features.element("starttls") != null) { 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( "Both us and the remote server support the STARTTLS feature. Secure and authenticate the connection with TLS & SASL..." );
LocalOutgoingServerSession answer = secureAndAuthenticate(hostname, connection, reader, openingStream, domain); LocalOutgoingServerSession answer = secureAndAuthenticate(remoteDomain, connection, reader, openingStream, localDomain);
if (answer != null) { if (answer != null) {
log.debug( "Successfully secured/authenticated the connection with TLS/SASL)!" ); log.debug( "Successfully secured/authenticated the connection with TLS/SASL)!" );
// Everything went fine so return the secured and // Everything went fine so return the secured and
...@@ -331,15 +315,15 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -331,15 +315,15 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
// Check if we are going to try server dialback (XMPP 1.0) // Check if we are going to try server dialback (XMPP 1.0)
else if (ServerDialback.isEnabled() && features.element("dialback") != null) { else if (ServerDialback.isEnabled() && features.element("dialback") != null) {
log.debug( "Both us and the remote server support the 'dialback' feature. Authenticate the connection with dialback..." ); log.debug( "Both us and the remote server support the 'dialback' feature. Authenticate the connection with dialback..." );
ServerDialback method = new ServerDialback(connection, domain); ServerDialback method = new ServerDialback(connection, localDomain);
OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader); OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
if (method.authenticateDomain(newSocketReader, domain, hostname, id)) { if (method.authenticateDomain(newSocketReader, localDomain, remoteDomain, id)) {
log.debug( "Successfully authenticated the connection with dialback!" ); log.debug( "Successfully authenticated the connection with dialback!" );
StreamID streamID = new BasicStreamIDFactory().createStreamID(id); StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
LocalOutgoingServerSession session = new LocalOutgoingServerSession(domain, connection, newSocketReader, streamID); LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, newSocketReader, streamID);
connection.init(session); connection.init(session);
// Set the hostname as the address of the session // Set the hostname as the address of the session
session.setAddress(new JID(null, hostname, null)); session.setAddress(new JID(null, remoteDomain, null));
log.debug( "Successfully created new session!" ); log.debug( "Successfully created new session!" );
return session; return session;
} }
...@@ -363,7 +347,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -363,7 +347,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
catch (SSLHandshakeException e) catch (SSLHandshakeException e)
{ {
// This is a failure as described in RFC3620, section 5.4.3.2 "STARTTLS Failure". // This is a failure as described in RFC3620, section 5.4.3.2 "STARTTLS Failure".
log.info( "STARTTLS negotiation (with {}:{}) failed. Closing connection (without sending any data such as <failure/> or </stream>).", realHostname, realPort, e ); log.info( "STARTTLS negotiation failed. Closing connection (without sending any data such as <failure/> or </stream>).", e );
// The receiving entity is expected to close the socket *without* sending any more data (<failure/> nor </stream>). // 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. // It is probably (see OF-794) best if we, as the initiating entity, therefor don't send any data either.
...@@ -374,7 +358,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -374,7 +358,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
catch (Exception e) catch (Exception e)
{ {
// This might be RFC3620, section 5.4.2.2 "Failure Case" or even an unrelated problem. Handle 'normally'. // 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 (with {}:{}). Closing connection.", realHostname, realPort, e ); log.warn( "An exception occurred while creating an encrypted session. Closing connection.", e );
if (connection != null) { if (connection != null) {
connection.close(); connection.close();
...@@ -386,7 +370,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -386,7 +370,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
log.debug( "Unable to create a new session. Going to try connecting using server dialback as a fallback." ); log.debug( "Unable to create a new session. Going to try connecting using server dialback as a fallback." );
// Use server dialback (pre XMPP 1.0) over a plain connection // Use server dialback (pre XMPP 1.0) over a plain connection
final LocalOutgoingServerSession outgoingSession = new ServerDialback().createOutgoingSession( domain, hostname, port ); 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? 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.debug( "Successfully created new session (using dialback as a fallback)!" );
return outgoingSession; return outgoingSession;
...@@ -402,11 +386,11 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -402,11 +386,11 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
} }
} }
private static LocalOutgoingServerSession secureAndAuthenticate(String hostname, SocketConnection connection, XMPPPacketReader reader, StringBuilder openingStream, String domain) throws Exception { 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 to: " + domain + " (" + hostname + ")]" ); final Logger log = LoggerFactory.getLogger(Log.getName() + "[Secure/Authenticate connection for: " + localDomain + " to: " + remoteDomain + "]" );
Element features; Element features;
log.debug( "Securing and authenticating connection..."); log.debug( "Securing and authenticating connection ...");
log.debug( "Indicating we want TLS and wait for response." ); log.debug( "Indicating we want TLS and wait for response." );
connection.deliverRawText( "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" ); connection.deliverRawText( "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" );
...@@ -415,7 +399,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -415,7 +399,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
// Wait for the <proceed> response // Wait for the <proceed> response
Element proceed = reader.parseDocument().getRootElement(); Element proceed = reader.parseDocument().getRootElement();
if (proceed != null && proceed.getName().equals("proceed")) { if (proceed != null && proceed.getName().equals("proceed")) {
log.debug( "Recevied 'proceed' from remote server. Negotiating TLS..." ); log.debug( "Received 'proceed' from remote server. Negotiating TLS..." );
try { try {
// boolean needed = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true) && // boolean needed = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true) &&
// JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, true) && // JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, true) &&
...@@ -426,7 +410,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -426,7 +410,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
throw e; throw e;
} }
log.debug( "TLS negotiation was successful. Connection secured. Proceeding with authentication..." ); log.debug( "TLS negotiation was successful. Connection secured. Proceeding with authentication..." );
if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) { if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), remoteDomain, true)) {
if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) { if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
log.debug( "SASL authentication failed. Will continue with dialback." ); log.debug( "SASL authentication failed. Will continue with dialback." );
} else { } else {
...@@ -449,77 +433,15 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -449,77 +433,15 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
// Get new stream features // Get new stream features
features = reader.parseDocument().getRootElement(); features = reader.parseDocument().getRootElement();
if (features != null) { if (features != null) {
// Check if we can use stream compression
final Connection.CompressionPolicy compressionPolicy = connection.getConfiguration().getCompressionPolicy();
if (Connection.CompressionPolicy.optional == compressionPolicy) {
// Verify if the remote server supports stream compression
Element compression = features.element("compression");
if (compression != null) {
boolean zlibSupported = false;
Iterator it = compression.elementIterator("method");
while (it.hasNext()) {
Element method = (Element) it.next();
if ("zlib".equals(method.getTextTrim())) {
zlibSupported = true;
}
}
if (zlibSupported) {
log.debug("Suppressing request to perform compression; unsupported in this version.");
zlibSupported = false;
}
if (zlibSupported) {
log.debug("Requesting stream compression (zlib).");
connection.deliverRawText("<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>");
// Check if we are good to start compression
Element answer = reader.parseDocument().getRootElement();
if ("compressed".equals(answer.getName())) {
// Server confirmed that we can use zlib compression
connection.addCompression();
connection.startCompression();
log.debug("Stream compression was successful.");
// Stream compression was successful so initiate a new stream
connection.deliverRawText(openingStream.toString());
// Reset the parser to use stream compression over TLS
ZInputStream in = new ZInputStream(
connection.getTLSStreamHandler().getInputStream());
in.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
xpp.setInput(new InputStreamReader(in, StandardCharsets.UTF_8));
// Skip the opening stream sent by the server
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;)
{
eventType = xpp.next();
}
// Get new stream features
features = reader.parseDocument().getRootElement();
if (features == null) {
log.debug("Error, EXTERNAL SASL was not offered.");
return null;
}
}
else {
log.debug("Stream compression was rejected by " + hostname);
}
}
else {
log.debug("Stream compression found but zlib method is not supported by " + hostname);
}
}
else {
log.debug("Stream compression not supported by " + hostname);
}
}
// Bookkeeping: determine what functionality the remote server offers. // Bookkeeping: determine what functionality the remote server offers.
boolean saslEXTERNALoffered = false; boolean saslEXTERNALoffered = false;
if (features != null) { if (features.element("mechanisms") != null) {
if (features.element("mechanisms") != null) { Iterator<Element> it = features.element("mechanisms").elementIterator();
Iterator<Element> it = features.element("mechanisms").elementIterator(); while (it.hasNext()) {
while (it.hasNext()) { Element mechanism = it.next();
Element mechanism = it.next(); if ("EXTERNAL".equals(mechanism.getTextTrim())) {
if ("EXTERNAL".equals(mechanism.getTextTrim())) { saslEXTERNALoffered = true;
saslEXTERNALoffered = true; break;
break;
}
} }
} }
} }
...@@ -532,7 +454,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -532,7 +454,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
// first, try SASL // first, try SASL
if (saslEXTERNALoffered) { if (saslEXTERNALoffered) {
log.debug( "Trying to authenticate with EXTERNAL SASL." ); log.debug( "Trying to authenticate with EXTERNAL SASL." );
result = attemptSASLexternal(connection, xpp, reader, domain, hostname, id, openingStream); result = attemptSASLexternal(connection, xpp, reader, localDomain, remoteDomain, id, openingStream);
if (result == null) { if (result == null) {
log.debug( "Failed to authenticate with EXTERNAL SASL." ); log.debug( "Failed to authenticate with EXTERNAL SASL." );
} else { } else {
...@@ -543,7 +465,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -543,7 +465,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
// SASL unavailable or failed, try dialback. // SASL unavailable or failed, try dialback.
if (result == null) { if (result == null) {
log.debug( "Trying to authenticate with dialback." ); log.debug( "Trying to authenticate with dialback." );
result = attemptDialbackOverTLS(connection, reader, domain, hostname, id); result = attemptDialbackOverTLS(connection, reader, localDomain, remoteDomain, id);
if (result == null) { if (result == null) {
log.debug( "Failed to authenticate with dialback." ); log.debug( "Failed to authenticate with dialback." );
} else { } else {
...@@ -570,20 +492,20 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -570,20 +492,20 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
} }
} }
private static LocalOutgoingServerSession attemptDialbackOverTLS(Connection connection, XMPPPacketReader reader, String domain, String hostname, String id) { 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: " + domain + " (" + hostname + ") Stream ID: " + id + "]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Dialback over TLS for: " + localDomain + " to: " + remoteDomain + " (Stream ID: " + id + ")]" );
if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) { if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
log.debug("Trying to connecting using dialback over TLS."); log.debug("Trying to connecting using dialback over TLS.");
ServerDialback method = new ServerDialback(connection, domain); ServerDialback method = new ServerDialback(connection, localDomain);
OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader); OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
if (method.authenticateDomain(newSocketReader, domain, hostname, id)) { if (method.authenticateDomain(newSocketReader, localDomain, remoteDomain, id)) {
log.debug("Dialback over TLS was successful."); log.debug("Dialback over TLS was successful.");
StreamID streamID = new BasicStreamIDFactory().createStreamID(id); StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
LocalOutgoingServerSession session = new LocalOutgoingServerSession(domain, connection, newSocketReader, streamID); LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, newSocketReader, streamID);
connection.init(session); connection.init(session);
// Set the hostname as the address of the session // Set the hostname as the address of the session
session.setAddress(new JID(null, hostname, null)); session.setAddress(new JID(null, remoteDomain, null));
return session; return session;
} }
else { else {
...@@ -597,11 +519,11 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -597,11 +519,11 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
} }
} }
private static LocalOutgoingServerSession attemptSASLexternal(SocketConnection connection, MXParser xpp, XMPPPacketReader reader, String domain, String hostname, String id, StringBuilder openingStream) throws DocumentException, IOException, XmlPullParserException { 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: " + domain + " (" + hostname + ") Stream ID: " + id + "]" ); final Logger log = LoggerFactory.getLogger( Log.getName() + "[EXTERNAL SASL for: " + localDomain + " to: " + remoteDomain + " (Stream ID: " + id + ")]" );
log.debug("Starting EXTERNAL SASL."); log.debug("Starting EXTERNAL SASL.");
if (doExternalAuthentication(domain, connection, reader)) { if (doExternalAuthentication(localDomain, connection, reader)) {
log.debug("EXTERNAL SASL was successful."); log.debug("EXTERNAL SASL was successful.");
// SASL was successful so initiate a new stream // SASL was successful so initiate a new stream
connection.deliverRawText(openingStream.toString()); connection.deliverRawText(openingStream.toString());
...@@ -618,11 +540,10 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -618,11 +540,10 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
// SASL authentication was successful so create new OutgoingServerSession // SASL authentication was successful so create new OutgoingServerSession
id = xpp.getAttributeValue("", "id"); id = xpp.getAttributeValue("", "id");
StreamID streamID = new BasicStreamIDFactory().createStreamID(id); StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
LocalOutgoingServerSession session = new LocalOutgoingServerSession(domain, LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, new OutgoingServerSocketReader(reader), streamID);
connection, new OutgoingServerSocketReader(reader), streamID);
connection.init(session); connection.init(session);
// Set the hostname as the address of the session // Set the hostname as the address of the session
session.setAddress(new JID(null, hostname, null)); session.setAddress(new JID(null, remoteDomain, null));
// Set that the session was created using TLS+SASL (no server dialback) // Set that the session was created using TLS+SASL (no server dialback)
session.usingServerDialback = false; session.usingServerDialback = false;
return session; return session;
...@@ -633,12 +554,12 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -633,12 +554,12 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
} }
} }
private static boolean doExternalAuthentication(String domain, SocketConnection connection, private static boolean doExternalAuthentication(String localDomain, SocketConnection connection,
XMPPPacketReader reader) throws DocumentException, IOException, XmlPullParserException { XMPPPacketReader reader) throws DocumentException, IOException, XmlPullParserException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"EXTERNAL\">"); sb.append("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"EXTERNAL\">");
sb.append(StringUtils.encodeBase64(domain)); sb.append(StringUtils.encodeBase64(localDomain));
sb.append("</auth>"); sb.append("</auth>");
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
...@@ -646,9 +567,8 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -646,9 +567,8 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
return response != null && "success".equals(response.getName()); return response != null && "success".equals(response.getName());
} }
public LocalOutgoingServerSession(String serverName, Connection connection, public LocalOutgoingServerSession(String localDomain, Connection connection, OutgoingServerSocketReader socketReader, StreamID streamID) {
OutgoingServerSocketReader socketReader, StreamID streamID) { super(localDomain, connection, streamID);
super(serverName, connection, streamID);
this.socketReader = socketReader; this.socketReader = socketReader;
socketReader.setSession(this); socketReader.setSession(this);
} }
...@@ -680,19 +600,19 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -680,19 +600,19 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
} }
@Override @Override
public boolean authenticateSubdomain(String domain, String hostname) { public boolean authenticateSubdomain(String localDomain, String remoteDomain) {
if (!usingServerDialback) { if (!usingServerDialback) {
// Using SASL so just assume that the domain was validated // Using SASL so just assume that the domain was validated
// (note: this may not be correct) // (note: this may not be correct)
addAuthenticatedDomain(domain); addAuthenticatedDomain(localDomain);
addHostname(hostname); addHostname(remoteDomain);
return true; return true;
} }
ServerDialback method = new ServerDialback(getConnection(), domain); ServerDialback method = new ServerDialback(getConnection(), localDomain);
if (method.authenticateDomain(socketReader, domain, hostname, getStreamID().getID())) { if (method.authenticateDomain(socketReader, localDomain, remoteDomain, getStreamID().getID())) {
// Add the validated domain as an authenticated domain // Add the validated domain as an authenticated domain
addAuthenticatedDomain(domain); addAuthenticatedDomain(localDomain);
addHostname(hostname); addHostname(remoteDomain);
return true; return true;
} }
return false; return false;
......
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