Commit 8ad81f2f authored by Guus der Kinderen's avatar Guus der Kinderen

Improving logging for S2S / dialback

The S2S (federation) code was not big on logging. This commit
introduces new logging, and structures the logged messages.
This commit does not introduce functional changes.
parent 80386d5f
...@@ -208,36 +208,39 @@ public class ServerDialback { ...@@ -208,36 +208,39 @@ 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+ ")]" );
log.debug( "Creating new outgoing session..." );
String hostname = null; String hostname = null;
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(); Socket socket = new Socket();
// Get a list of real hostnames to connect to using DNS lookup of the specified hostname 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); List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(remoteDomain, port);
for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) { for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
try { try {
DNSUtil.HostAddress address = it.next(); DNSUtil.HostAddress address = it.next();
hostname = address.getHost(); hostname = address.getHost();
realPort = address.getPort(); realPort = address.getPort();
Log.debug("ServerDialback: OS - Trying to connect to " + remoteDomain + ":" + port + log.debug( "Trying to create plain socket connection to: {}:{} ...", hostname, realPort );
"(DNS lookup: " + hostname + ":" + realPort + ")");
// Establish a TCP connection to the Receiving Server // Establish a TCP connection to the Receiving Server
socket.connect(new InetSocketAddress(hostname, realPort), socket.connect(new InetSocketAddress(hostname, realPort), RemoteServerManager.getSocketTimeout());
RemoteServerManager.getSocketTimeout()); log.debug( "Plain socket connection to {}:{} successful!", hostname, realPort );
Log.debug("ServerDialback: OS - Connection to " + remoteDomain + ":" + port + " successful");
break; break;
} }
catch (Exception e) { catch (Exception e) {
Log.warn("Error trying to connect to remote server: " + remoteDomain + log.debug( "An exception occurred while trying to create a plain socket connection to: {}:{}", hostname, realPort, e );
"(DNS lookup: " + 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 = connection =
new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
false); false);
// Get a writer for sending the open stream tag
// Send to the Receiving Server a stream header log.debug( "Send the stream header and wait for response..." );
StringBuilder stream = new StringBuilder(); StringBuilder stream = new StringBuilder();
stream.append("<stream:stream"); stream.append("<stream:stream");
stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
...@@ -261,27 +264,34 @@ public class ServerDialback { ...@@ -261,27 +264,34 @@ public class ServerDialback {
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next(); eventType = xpp.next();
} }
log.debug( "Got a response. Check if the remote server supports dialback..." );
if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) { if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
log.debug( "Dialback seems to be supported by the remote server." );
// Restore default timeout // Restore default timeout
socket.setSoTimeout(soTimeout); socket.setSoTimeout(soTimeout);
String id = xpp.getAttributeValue("", "id"); String id = xpp.getAttributeValue("", "id");
OutgoingServerSocketReader socketReader = new OutgoingServerSocketReader(reader); OutgoingServerSocketReader socketReader = new OutgoingServerSocketReader(reader);
if (authenticateDomain(socketReader, localDomain, remoteDomain, id)) { if (authenticateDomain(socketReader, localDomain, remoteDomain, id)) {
log.debug( "Successfully authenticated the connection with dialback." );
// Domain was validated so create a new OutgoingServerSession // Domain was validated so create a new OutgoingServerSession
StreamID streamID = new BasicStreamIDFactory().createStreamID(id); StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, socketReader, streamID); LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, socketReader, 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, remoteDomain, null)); session.setAddress(new JID(null, remoteDomain, null));
log.debug( "Successfully created new outgoing session!" );
return session; return session;
} }
else { else {
log.debug( "Failed to authenticate the connection with dialback." );
// Close the connection // Close the connection
connection.close(); connection.close();
} }
} }
else { else {
Log.debug("ServerDialback: OS - Invalid namespace in packet: " + xpp.getText()); log.debug("Error! Invalid namespace in packet: '{}'. Closing connection.", xpp.getText() );
// Send an invalid-namespace stream error condition in the response // Send an invalid-namespace stream error condition in the response
connection.deliverRawText( connection.deliverRawText(
new StreamError(StreamError.Condition.invalid_namespace).toXML()); new StreamError(StreamError.Condition.invalid_namespace).toXML());
...@@ -289,25 +299,14 @@ public class ServerDialback { ...@@ -289,25 +299,14 @@ public class ServerDialback {
connection.close(); connection.close();
} }
} }
catch (IOException e) {
Log.debug("ServerDialback: Error connecting to the remote server: " + remoteDomain + "(DNS lookup: " +
hostname + ":" + realPort + ")", e);
// Close the connection
if (connection != null) {
connection.close();
}
}
catch (Exception e) { catch (Exception e) {
Log.error("Error creating outgoing session to remote server: " + remoteDomain + log.error( "An exception occurred while creating outgoing session to remote server:", e );
"(DNS lookup: " +
hostname +
")",
e);
// Close the connection // Close the connection
if (connection != null) { if (connection != null) {
connection.close(); connection.close();
} }
} }
log.warn( "Unable to create a new outgoing session" );
return null; return null;
} }
...@@ -325,14 +324,16 @@ public class ServerDialback { ...@@ -325,14 +324,16 @@ public class ServerDialback {
* @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, public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String domain, String hostname, String id) {
String hostname, String id) {
String key = AuthFactory.createDigest(id, getSecretkey()); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Authenticate domain: " + domain + " (id " + id + ") with hostname: " + hostname + "]" );
Log.debug("ServerDialback: OS - Sent dialback key to host: " + hostname + " id: " + id + " from domain: " +
domain); log.debug( "Authenticating domain ..." );
String key = AuthFactory.createDigest( id, getSecretkey() );
synchronized (socketReader) { synchronized (socketReader) {
// Send a dialback key to the Receiving Server 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(domain).append("\"");
...@@ -344,42 +345,27 @@ public class ServerDialback { ...@@ -344,42 +345,27 @@ public class ServerDialback {
// Process the answer from the Receiving Server // Process the answer from the Receiving Server
try { try {
while (true) { while (true) {
Element doc = socketReader.getElement(RemoteServerManager.getSocketTimeout(), Element doc = socketReader.getElement(RemoteServerManager.getSocketTimeout(), TimeUnit.MILLISECONDS);
TimeUnit.MILLISECONDS);
if (doc == null) { if (doc == null) {
Log.debug("ServerDialback: OS - Time out waiting for answer in validation from: " + hostname + log.debug( "Failed to authenticate domain: Time out waiting for validation response." );
" id: " +
id +
" for domain: " +
domain);
return false; return false;
} }
else if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) { else if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
boolean success = "valid".equals(doc.attributeValue("type")); if ( "valid".equals(doc.attributeValue("type")) ) {
Log.debug("ServerDialback: OS - Validation " + (success ? "GRANTED" : "FAILED") + " from: " + log.debug( "Authenticated succeeded!" );
hostname + return true;
" id: " + } else {
id + log.debug( "Failed to authenticate domain: the validation response was received, but did not grant authentication." );
" for domain: " + return false;
domain); }
return success;
} }
else { else {
Log.warn("ServerDialback: OS - Ignoring unexpected answer in validation from: " + hostname + " id: " + log.warn( "Ignoring unexpected answer while waiting for dialback validation: " + doc.asXML() );
id +
" for domain: " +
domain +
" answer:" +
doc.asXML());
} }
} }
} }
catch (InterruptedException e) { catch (InterruptedException e) {
Log.debug("ServerDialback: OS - Validation FAILED from: " + hostname + log.debug( "Failed to authenticate domain: An interrupt was received while waiting for validation response (is Openfire shutting down?)" );
" id: " +
id +
" for domain: " +
domain, e);
return false; return false;
} }
} }
...@@ -510,24 +496,24 @@ public class ServerDialback { ...@@ -510,24 +496,24 @@ public class ServerDialback {
StringBuilder sb; StringBuilder sb;
String recipient = doc.attributeValue("to"); String recipient = doc.attributeValue("to");
String hostname = doc.attributeValue("from"); String hostname = doc.attributeValue("from");
Log.debug("ServerDialback: RS - Received dialback key from host: " + hostname + " to: " + recipient);
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Validate remote domain:" + recipient + "(id " + streamID + ") for: " + hostname + "]" );
log.debug( "Validating domain...");
if (!RemoteServerManager.canAccess(hostname)) { if (!RemoteServerManager.canAccess(hostname)) {
// Remote server is not allowed to establish a connection to this server
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("ServerDialback: RS - Error, hostname is not allowed to establish a connection to " + log.debug( "Unable to validate domain: Remote server is not allowed to establish a connection to this server." );
"this server: " +
recipient);
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, hostname, new PacketError(PacketError.Condition.item_not_found, PacketError.Type.cancel, "Service not hosted here"));
Log.debug("ServerDialback: RS - Error, hostname not recognized: " + recipient); log.debug( "Unable to validate domain: Hostname not recognized." );
return false; return false;
} }
else { else {
// Check if the remote server already has a connection to the target domain/subdomain log.debug( "Check if the remote server 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(hostname)) {
if (recipient.equals(session.getLocalDomain())) { if (recipient.equals(session.getLocalDomain())) {
...@@ -536,13 +522,13 @@ public class ServerDialback { ...@@ -536,13 +522,13 @@ public class ServerDialback {
} }
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, hostname, new PacketError(PacketError.Condition.resource_constraint, PacketError.Type.cancel, "Incoming session already exists"));
Log.debug("ServerDialback: RS - Error, incoming connection already exists from: " + hostname); log.debug( "Unable to validate domain: An incoming connection already exists from this remote host, 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." );
if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) { if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) {
// If the remote host passes strong auth, just skip the dialback. log.debug( "Host authenticated based on SASL. Weaker dialback-based authentication is skipped." );
Log.debug("ServerDialback: RS - Sending key verification result to OS: " + hostname);
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("\"");
...@@ -550,53 +536,56 @@ public class ServerDialback { ...@@ -550,53 +536,56 @@ public class ServerDialback {
sb.append(" type=\"valid\""); sb.append(" type=\"valid\"");
sb.append("/>"); sb.append("/>");
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
log.debug( "Domain validated successfully!" );
return true; return true;
} }
log.debug( "Unable to authenticate host based on stronger SASL. Proceeding with dialback..." );
String key = doc.getTextTrim(); String key = doc.getTextTrim();
// Get a list of real hostnames and try to connect using DNS lookup of the specified domain log.debug( "Get a list of real hostnames and try to using DNS lookup of the specified domain." );
List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, RemoteServerManager.getPortForServer(hostname));
RemoteServerManager.getPortForServer(hostname));
Socket socket = new Socket(); Socket socket = new Socket();
String realHostname = null; String realHostname = null;
int realPort; int realPort;
for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) { for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) { // TODO Remove code duplication (also in LocalOutgoingServerSession)
try { try {
DNSUtil.HostAddress address = it.next(); DNSUtil.HostAddress address = it.next();
realHostname = address.getHost(); realHostname = address.getHost();
realPort = address.getPort(); realPort = address.getPort();
Log.debug("ServerDialback: RS - Trying to connect to Authoritative Server: " + hostname + log.debug( "Trying to create plain socket connection to: {}:{} ...", realHostname, realPort );
"(DNS lookup: " + realHostname + ":" + realPort + ")");
// Establish a TCP connection to the Receiving Server // Establish a TCP connection to the Receiving Server
socket.connect(new InetSocketAddress(realHostname, realPort), socket.connect(new InetSocketAddress(realHostname, realPort), RemoteServerManager.getSocketTimeout());
RemoteServerManager.getSocketTimeout()); log.debug( "Plain socket connection to {}:{} successful!", realHostname, realPort );
Log.debug("ServerDialback: RS - Connection to AS: " + hostname + " successful");
break; break;
} }
catch (Exception e) { catch (Exception e) {
Log.warn("Error trying to connect to remote server: " + hostname + log.debug( "An exception occurred while trying to create a plain socket connection to: {}", realHostname, e );
"(DNS lookup: " + 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()) { if (!socket.isConnected()) {
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, hostname, new PacketError(PacketError.Condition.remote_server_not_found, PacketError.Type.cancel, "Unable to connect to authoritative server"));
Log.warn("No server available for verifying key of remote server: " + hostname);
try { try {
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
Log.warn("Socket error on close", e); log.warn("Socket error on close", e);
} }
return false; return false;
} }
try { try {
log.debug( "Verifying dialback key..." );
VerifyResult result = verifyKey(key, streamID.toString(), recipient, hostname, socket); VerifyResult result = verifyKey(key, streamID.toString(), recipient, hostname, 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("ServerDialback: RS - Sending key verification result to OS: " + hostname); log.debug( "Dialback key is" + (valid? "valid":"invalid") + ". Sending verification result to remote host." );
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("\"");
...@@ -607,19 +596,24 @@ public class ServerDialback { ...@@ -607,19 +596,24 @@ public class ServerDialback {
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
if (!valid) { if (!valid) {
// Close the underlying connection log.debug( "Close the underlying connection as key verification failed." );
connection.close(); connection.close();
log.debug( "Unable to validate domain: dialback key is invalid." );
return false;
} else {
log.debug( "Successfully validated domain!" );
return true;
} }
return valid;
default: default:
break; break;
} }
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, hostname, 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, hostname, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server failed"));
Log.warn("Error verifying key of remote server: " + hostname, e); log.warn( "Unable to validate domain: An exception occurred while verifying the dialback key.", e );
return false; return false;
} }
} }
...@@ -638,9 +632,12 @@ public class ServerDialback { ...@@ -638,9 +632,12 @@ public class ServerDialback {
} }
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 hostname, 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 + ")]" );
VerifyResult result = VerifyResult.error; VerifyResult result = VerifyResult.error;
TLSStreamHandler tlsStreamHandler; TLSStreamHandler tlsStreamHandler;
// Send the Authoritative Server a stream header
log.debug( "Send the Authoritative Server a stream header and wait for answer." );
StringBuilder stream = new StringBuilder(); StringBuilder stream = new StringBuilder();
stream.append("<stream:stream"); stream.append("<stream:stream");
stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
...@@ -661,13 +658,15 @@ public class ServerDialback { ...@@ -661,13 +658,15 @@ public class ServerDialback {
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next(); eventType = xpp.next();
} }
if ((xpp.getAttributeValue("", "version") != null) &&
(xpp.getAttributeValue("", "version").equals("1.0"))) { log.debug( "Got a response. Check if the remote server is XMPP 1.0 compliant..." ); // TODO there's code duplication here with LocalOutgoingServerSession.
if ((xpp.getAttributeValue("", "version") != null) && (xpp.getAttributeValue("", "version").equals("1.0"))) {
log.debug( "The remote server is XMPP 1.0 compliant (or at least reports to be).");
Document doc; Document doc;
try { try {
doc = reader.parseDocument(); doc = reader.parseDocument();
} catch (DocumentException e) { } catch (DocumentException e) {
Log.warn("XML Error!", e); log.warn("Unable to verify key: XML Error!", e);
return VerifyResult.error; return VerifyResult.error;
} }
Element features = doc.getRootElement(); Element features = doc.getRootElement();
...@@ -678,14 +677,16 @@ public class ServerDialback { ...@@ -678,14 +677,16 @@ public class ServerDialback {
try { try {
doc = reader.parseDocument(); doc = reader.parseDocument();
} catch (DocumentException e) { } catch (DocumentException e) {
Log.warn("XML Error!", e); log.warn("Unable to verify key: XML Error!", e);
return VerifyResult.error; return VerifyResult.error;
} }
if (!doc.getRootElement().getName().equals("proceed")) { if (!doc.getRootElement().getName().equals("proceed")) {
Log.warn("Got {} instead of proceed for starttls", doc.getRootElement().getName()); log.warn("Unable to verify key: Got {} instead of proceed for starttls", doc.getRootElement().getName());
Log.debug("Like this: {}", doc.asXML()); log.debug("Like this: {}", doc.asXML());
return VerifyResult.error; return VerifyResult.error;
} }
log.debug( "Negotiating TLS with AS... " );
// Ugly hacks, apparently, copied from SocketConnection. // Ugly hacks, apparently, copied from SocketConnection.
final ConnectionManagerImpl connectionManager = ((ConnectionManagerImpl) XMPPServer.getInstance().getConnectionManager()); final ConnectionManagerImpl connectionManager = ((ConnectionManagerImpl) XMPPServer.getInstance().getConnectionManager());
tlsStreamHandler = new TLSStreamHandler(socket, connectionManager.getListener( ConnectionType.SOCKET_S2S, false ).generateConnectionConfiguration(), true); tlsStreamHandler = new TLSStreamHandler(socket, connectionManager.getListener( ConnectionType.SOCKET_S2S, false ).generateConnectionConfiguration(), true);
...@@ -693,15 +694,14 @@ public class ServerDialback { ...@@ -693,15 +694,14 @@ public class ServerDialback {
tlsStreamHandler.start(); tlsStreamHandler.start();
// Use new wrapped writers // Use new wrapped writers
writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), StandardCharsets.UTF_8)); writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), StandardCharsets.UTF_8));
reader.getXPPParser().setInput(new InputStreamReader(tlsStreamHandler.getInputStream(), reader.getXPPParser().setInput(new InputStreamReader(tlsStreamHandler.getInputStream(),StandardCharsets.UTF_8));
StandardCharsets.UTF_8)); log.debug( "Successfully negotiated TLS with AS... " );
/// Recurses! /// Recurses!
return sendVerifyKey(key, streamID, recipient, hostname, writer, reader, socket); return sendVerifyKey(key, streamID, recipient, hostname, writer, reader, socket);
} }
} }
if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) { if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
Log.debug("ServerDialback: RS - Asking AS to verify dialback key for id" + streamID); log.debug("Request for verification of the key and wait for response");
// Request for verification of the key
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("\"");
...@@ -742,24 +742,24 @@ public class ServerDialback { ...@@ -742,24 +742,24 @@ public class ServerDialback {
throw new RemoteConnectionFailedException("Invalid From"); throw new RemoteConnectionFailedException("Invalid From");
} }
else if ("valid".equals(doc.attributeValue("type"))){ else if ("valid".equals(doc.attributeValue("type"))){
Log.debug("ServerDialback: RS - Key was VERIFIED by the Authoritative Server for: {}", hostname); log.debug("Key was VERIFIED by the Authoritative Server.");
result = VerifyResult.valid; result = VerifyResult.valid;
} }
else if ("invalid".equals(doc.attributeValue("type"))){ else if ("invalid".equals(doc.attributeValue("type"))){
Log.debug("ServerDialback: RS - Key was NOT VERIFIED by the Authoritative Server for: {}", hostname); log.debug("Key was NOT VERIFIED by the Authoritative Server.");
result = VerifyResult.invalid; result = VerifyResult.invalid;
} }
else { else {
Log.debug("ServerDialback: RS - Key was ERRORED by the Authoritative Server for: {}", hostname); log.debug("Key was ERRORED by the Authoritative Server.");
result = VerifyResult.error; result = VerifyResult.error;
} }
} }
else { else {
Log.debug("ServerDialback: db:verify answer was: " + doc.asXML()); log.debug("db:verify answer was: " + doc.asXML());
} }
} }
catch (DocumentException | RuntimeException e) { catch (DocumentException | RuntimeException e) {
Log.error("An error occured connecting to the Authoritative Server", e); log.error("An error occurred while connecting to the Authoritative Server:", e);
// Thrown an error so <remote-connection-failed/> stream error condition is // Thrown an error so <remote-connection-failed/> stream error condition is
// sent to the Originating Server // sent to the Originating Server
throw new RemoteConnectionFailedException("Error connecting to the Authoritative Server"); throw new RemoteConnectionFailedException("Error connecting to the Authoritative Server");
...@@ -780,9 +780,11 @@ public class ServerDialback { ...@@ -780,9 +780,11 @@ 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, private VerifyResult verifyKey(String key, String streamID, String recipient, String hostname, Socket socket) throws IOException, XmlPullParserException, RemoteConnectionFailedException {
Socket socket) throws IOException, XmlPullParserException,
RemoteConnectionFailedException { final Logger log = LoggerFactory.getLogger( Log.getName() + "[Verify key with AS: " + hostname + " for: " + recipient + " (id " + streamID + ")]" );
log.debug( "Verifying key ..." );
XMPPPacketReader reader; XMPPPacketReader reader;
Writer writer = null; Writer writer = null;
// Set a read timeout // Set a read timeout
...@@ -792,17 +794,13 @@ public class ServerDialback { ...@@ -792,17 +794,13 @@ public class ServerDialback {
reader = new XMPPPacketReader(); reader = new XMPPPacketReader();
reader.setXPPFactory(FACTORY); reader.setXPPFactory(FACTORY);
reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(), reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(), CHARSET));
CHARSET));
// Get a writer for sending the open stream tag // Get a writer for sending the open stream tag
writer = writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), CHARSET));
new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),
CHARSET));
result = sendVerifyKey(key, streamID, recipient, hostname, writer, reader, socket); result = sendVerifyKey(key, streamID, recipient, hostname, writer, reader, socket);
} }
finally { finally {
try { try {
Log.debug("ServerDialback: RS - Closing connection to Authoritative Server: " + hostname);
// Close the stream // Close the stream
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("</stream:stream>"); sb.append("</stream:stream>");
...@@ -815,6 +813,22 @@ public class ServerDialback { ...@@ -815,6 +813,22 @@ public class ServerDialback {
// Do nothing // Do nothing
} }
} }
switch ( result ) {
case valid:
log.debug( "Successfully verified key!" );
break;
case invalid:
log.debug( "Unable to verify key: AS reports that the key is invalid." );
break;
default:
case decline:
case error:
log.debug( "Unable to verify key: An error occurred." );
break;
}
return result; return result;
} }
...@@ -833,7 +847,10 @@ public class ServerDialback { ...@@ -833,7 +847,10 @@ public class ServerDialback {
String verifyTO = doc.attributeValue("to"); String verifyTO = doc.attributeValue("to");
String key = doc.getTextTrim(); String key = doc.getTextTrim();
String id = doc.attributeValue("id"); String id = doc.attributeValue("id");
Log.debug("ServerDialback: AS - Verifying key for host: " + verifyFROM + " id: " + id);
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Verify key for RS: " + verifyFROM + " (id " + id+ ")]" );
log.debug( "Verifying key... ");
// TODO If the value of the 'to' address does not match a recognized hostname, // TODO If the value of the 'to' address does not match a recognized hostname,
// then generate a <host-unknown/> stream error condition // then generate a <host-unknown/> stream error condition
...@@ -855,10 +872,7 @@ public class ServerDialback { ...@@ -855,10 +872,7 @@ public class ServerDialback {
sb.append(verified ? "valid" : "invalid"); sb.append(verified ? "valid" : "invalid");
sb.append("\" id=\"").append(id).append("\"/>"); sb.append("\" id=\"").append(id).append("\"/>");
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
Log.debug("ServerDialback: AS - Key was: " + (verified ? "VALID" : "INVALID") + " for host: " + log.debug("Verification successful! Key was: " + (verified ? "VALID" : "INVALID"));
verifyFROM +
" id: " +
id);
return verified; return verified;
} }
......
...@@ -122,35 +122,41 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -122,35 +122,41 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
* @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 domain, final String hostname) {
final Logger log = LoggerFactory.getLogger( Log.getName() + "[Authenticate domain: " + domain + " (hostname: " + hostname + ")]" );
log.debug( "Authenticating... " );
if (hostname == null || hostname.length() == 0 || hostname.trim().indexOf(' ') > -1) { if (hostname == null || hostname.length() == 0 || hostname.trim().indexOf(' ') > -1) {
// Do nothing if the target hostname is empty, null or contains whitespaces // Do nothing if the target hostname is empty, null or contains whitespaces
log.warn( "Unable to authenticate a domain when an empty hostname is provided!" );
return false; return false;
} }
try { try {
// Check if the remote hostname is in the blacklist // Check if the remote hostname is in the blacklist
if (!RemoteServerManager.canAccess(hostname)) { if (!RemoteServerManager.canAccess(hostname)) {
log.info( "Unable to authenticate: Hostname is not accessible according to our configuration (typical causes: server federation is disabled, or hostname 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." );
OutgoingServerSession session; OutgoingServerSession session;
// Check if a session, that is using server dialback, already exists to the desired
// hostname (i.e. remote server). If no one exists then create a new session. The same
// session will be used for the same hostname for all the domains to authenticate
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." );
return false; return false;
} }
session = sessionManager.getOutgoingServerSession(hostname); session = sessionManager.getOutgoingServerSession(hostname);
if (session == null) { if (session == null) {
log.debug( "There is no pre-existing session to this hostname." );
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." );
// Try locating if the remote server has previously authenticated with this server // Try locating if the remote server has previously authenticated with this server
for (IncomingServerSession incomingSession : sessionManager.getIncomingServerSessions(hostname)) { for (IncomingServerSession incomingSession : sessionManager.getIncomingServerSessions(hostname)) {
for (String otherHostname : incomingSession.getValidatedDomains()) { for (String otherHostname : incomingSession.getValidatedDomains()) {
session = sessionManager.getOutgoingServerSession(otherHostname); session = sessionManager.getOutgoingServerSession(otherHostname);
if (session != null) { if (session != null) {
if (session.isUsingServerDialback()) { if (session.isUsingServerDialback()) {
// A session to the same remote server but with different hostname log.debug( "A session to the same remote server but with different hostname ('{}') was found. This session will be re-used.", otherHostname );
// was found. Use this session.
break; break;
} else { } else {
session = null; session = null;
...@@ -158,35 +164,51 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -158,35 +164,51 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
} }
} }
} }
if (session == null) {
log.debug( "There are no pre-existing session to other hostnames for this domain." );
}
} }
if (session == null) { if (session == null) {
log.debug( "Creating new authenticated session..." );
int port = RemoteServerManager.getPortForServer(hostname); int port = RemoteServerManager.getPortForServer(hostname);
session = createOutgoingSession(domain, hostname, port); session = createOutgoingSession(domain, hostname, port);
if (session != null) { if (session != null) {
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(domain);
// Add the new hostname to the list of names that the server may have // Add the new hostname to the list of names that the server may have
session.addHostname(hostname); session.addHostname(hostname);
// 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." );
return true; return true;
} else { } else {
Log.warn("Fail to connect to {} for {}", hostname, domain); log.warn( "Unable to authenticate: Fail to create new session." );
return false; return false;
} }
} }
// A session already exists. The session was established using server dialback so
// it is possible to do piggybacking to authenticate more domains 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)) { if (session.getAuthenticatedDomains().contains(domain) && session.getHostnames().contains(hostname)) {
// Do nothing since the domain has already been authenticated // Do nothing since the domain has already been authenticated
log.debug( "Authentication successful (domain was already authenticated in the pre-existing session)." );
return true; return true;
} }
// A session already exists so authenticate the domain using that session // A session already exists so authenticate the domain using that session
return session.authenticateSubdomain(domain, hostname); 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("Error authenticating domain with remote server: " + hostname, e); log.error( "An exception occurred while authenticating domain with remote server!", e );
} }
log.warn( "Unable to authenticate: exhausted session (re-)usage options." );
return false; return false;
} }
...@@ -202,8 +224,10 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -202,8 +224,10 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
* @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 server.
*/ */
private static LocalOutgoingServerSession createOutgoingSession(String domain, String hostname, private static LocalOutgoingServerSession createOutgoingSession(String domain, String hostname, int port) {
int port) { final Logger log = LoggerFactory.getLogger( Log.getName() + "[Create outgoing session to: " + domain + " (" + hostname + ":"+ port+")]" );
log.debug( "Creating new session..." );
String localDomainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); String localDomainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true); boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true);
...@@ -217,7 +241,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -217,7 +241,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
String realHostname = null; String realHostname = null;
int realPort = port; int realPort = port;
Socket socket = null; Socket socket = null;
// Get a list of real hostnames to connect to using DNS lookup of the specified hostname log.debug( "Get a list of real hostnames to connect to using DNS lookup of the specified hostname." );
List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, port); List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, port);
for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) { for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
try { try {
...@@ -225,28 +249,26 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -225,28 +249,26 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
DNSUtil.HostAddress address = it.next(); DNSUtil.HostAddress address = it.next();
realHostname = address.getHost(); realHostname = address.getHost();
realPort = address.getPort(); realPort = address.getPort();
Log.debug("LocalOutgoingServerSession: OS - Trying to connect to " + hostname + ":" + port + log.debug( "Trying to create plain socket connection to: {}:{} ...", realHostname, realPort );
"(DNS lookup: " + realHostname + ":" + realPort + ")"); socket.connect(new InetSocketAddress(realHostname, realPort), RemoteServerManager.getSocketTimeout());
// Establish a TCP connection to the Receiving Server log.debug( "Plain socket connection to {}:{} successful!", realHostname, realPort );
socket.connect(new InetSocketAddress(realHostname, realPort),
RemoteServerManager.getSocketTimeout());
Log.debug("LocalOutgoingServerSession: OS - Plain connection to " + hostname + ":" + port + " successful");
break; break;
} }
catch (Exception e) { catch (Exception e) {
Log.warn("Error trying to connect to remote server: " + hostname + log.debug( "An exception occurred while trying to create a plain socket connection to: {}:{}", realHostname, realPort, e );
"(DNS lookup: " + realHostname + ":" + realPort + "): " + e.toString()); log.warn( "Unable to create plain socket connection to: {}:{}. Cause: {} (a full stacktrace is logged on debug level)", realHostname, realPort, e.getMessage() );
try { try {
if (socket != null) { if (socket != null) {
socket.close(); socket.close();
} }
} }
catch (IOException ex) { catch (IOException ex) {
Log.debug("Additional exception while trying to close socket when connection to remote server failed: " + ex.toString()); log.debug( "Additional exception while trying to close socket when connection to remote server failed.", ex);
} }
} }
} }
if (!socket.isConnected()) { if (!socket.isConnected()) {
log.info( "Unable to create new session: Cannot create a plain socket connection with any applicable host." );
return null; return null;
} }
...@@ -256,7 +278,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -256,7 +278,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
false); false);
// Send the stream header log.debug( "Send the stream header and wait for response..." );
StringBuilder openingStream = new StringBuilder(); StringBuilder openingStream = new StringBuilder();
openingStream.append("<stream:stream"); openingStream.append("<stream:stream");
openingStream.append(" xmlns:db=\"jabber:server:dialback\""); openingStream.append(" xmlns:db=\"jabber:server:dialback\"");
...@@ -282,51 +304,58 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -282,51 +304,58 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
String serverVersion = xpp.getAttributeValue("", "version"); String serverVersion = xpp.getAttributeValue("", "version");
String id = xpp.getAttributeValue("", "id"); String id = xpp.getAttributeValue("", "id");
log.debug( "Got a response (stream ID: {}, version: {}). Check if the remote server is XMPP 1.0 compliant...", id, serverVersion );
// Check if the remote server is XMPP 1.0 compliant
if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) { if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) {
log.debug( "The remote server is XMPP 1.0 compliant (or at least reports to be)." );
// Restore default timeout // Restore default timeout
socket.setSoTimeout(soTimeout); socket.setSoTimeout(soTimeout);
// Get the stream features
log.debug( "Processing stream features of the remote server..." );
Element features = reader.parseDocument().getRootElement(); Element features = reader.parseDocument().getRootElement();
if (features != null) { if (features != null) {
// Check if TLS is enabled log.debug( "Check if both us as well as the remote server have enabled STARTTLS and/or dialback ..." );
if (useTLS && features.element("starttls") != null) { if (useTLS && features.element("starttls") != null) {
// Secure the connection with TLS and authenticate using SASL log.debug( "Both us and the remote server support the STARTTLS feature. Secure and authenticate the connection with TLS & SASL..." );
LocalOutgoingServerSession answer; LocalOutgoingServerSession answer = secureAndAuthenticate(hostname, connection, reader, openingStream, domain);
answer = secureAndAuthenticate(hostname, connection, reader, openingStream,
domain);
if (answer != null) { if (answer != null) {
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
// authenticated connection // authenticated connection
log.debug( "Successfully created new session!" );
return answer; return answer;
} }
log.debug( "Unable to secure and authenticate the connection with TLS & SASL." );
} }
// 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("LocalOutgoingServerSession: OS - About to try connecting using server dialback XMPP 1.0 with: " + hostname); 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, domain);
OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader); OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
if (method.authenticateDomain(newSocketReader, domain, hostname, id)) { if (method.authenticateDomain(newSocketReader, domain, hostname, id)) {
Log.debug("LocalOutgoingServerSession: OS - SERVER DIALBACK XMPP 1.0 with " + hostname + " was successful"); 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(domain, 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, hostname, null));
log.debug( "Successfully created new session!" );
return session; return session;
} }
else { else {
Log.debug("LocalOutgoingServerSession: OS - Error, SERVER DIALBACK with " + hostname + " failed"); log.debug( "Unable to authenticate the connection with dialback." );
} }
} }
} }
else { else {
Log.debug("LocalOutgoingServerSession: OS - Error, <starttls> was not received"); log.debug( "Error! No data from the remote server (expected a 'feature' element).");
} }
} else {
log.debug( "The remote server is not XMPP 1.0 compliant." );
} }
// Something went wrong so close the connection and try server dialback over
// a plain connection log.debug( "Something went wrong so close the connection and try server dialback over a plain connection" );
if (connection != null) { if (connection != null) {
connection.close(); connection.close();
} }
...@@ -334,7 +363,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -334,7 +363,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 {} at {}:{}) failed.", hostname, realHostname, realPort, e ); log.info( "STARTTLS negotiation (with {}:{}) failed. Closing connection (without sending any data such as <failure/> or </stream>).", realHostname, realPort, 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.
...@@ -345,56 +374,69 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -345,56 +374,69 @@ 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 {} at {}:{})", hostname, realHostname, realPort, e ); log.warn( "An exception occurred while creating an encrypted session (with {}:{}). Closing connection.", realHostname, realPort, e );
if (connection != null) { if (connection != null) {
connection.close(); connection.close();
} }
} }
if (ServerDialback.isEnabled()) { if (ServerDialback.isEnabled())
Log.debug("LocalOutgoingServerSession: OS - Going to try connecting using server dialback with: " + hostname); {
// Use server dialback (pre XMPP 1.0) over a plain connection log.debug( "Unable to create a new session. Going to try connecting using server dialback as a fallback." );
return new ServerDialback().createOutgoingSession(domain, hostname, port);
// Use server dialback (pre XMPP 1.0) over a plain connection
final LocalOutgoingServerSession outgoingSession = new ServerDialback().createOutgoingSession( domain, hostname, 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;
} else {
log.warn( "Unable to create a new session: Dialback (as a fallback) failed." );
return null;
}
}
else
{
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 null;
} }
return null;
} }
private static LocalOutgoingServerSession secureAndAuthenticate(String hostname, private static LocalOutgoingServerSession secureAndAuthenticate(String hostname, SocketConnection connection, XMPPPacketReader reader, StringBuilder openingStream, String domain) throws Exception {
SocketConnection connection, XMPPPacketReader reader, StringBuilder openingStream, final Logger log = LoggerFactory.getLogger(Log.getName() + "[Secure/Authenticate connection to: " + domain + " (" + hostname + ")]" );
String domain) throws Exception {
final Logger log = LoggerFactory.getLogger(LocalOutgoingServerSession.class.getName()+"['"+hostname+"']");
Element features; Element features;
log.debug("Indicating we want TLS to " + hostname);
connection.deliverRawText("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); log.debug( "Securing and authenticating connection...");
log.debug( "Indicating we want TLS and wait for response." );
connection.deliverRawText( "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" );
MXParser xpp = reader.getXPPParser(); MXParser xpp = reader.getXPPParser();
// 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("Negotiating TLS..."); log.debug( "Recevied '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) &&
// !JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, false); // !JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, false);
connection.startTLS(true); connection.startTLS(true);
} catch(Exception e) { } catch(Exception e) {
log.debug("Got an exception whilst negotiating TLS: " + e.getMessage()); log.debug("TLS negotiation failed: " + e.getMessage());
throw e; throw e;
} }
log.debug("TLS negotiation was successful."); log.debug( "TLS negotiation was successful. Connection secured. Proceeding with authentication..." );
if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) { if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) {
log.debug("X.509/PKIX failure on outbound session");
if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) { if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
log.debug("Will continue with dialback."); log.debug( "SASL authentication failed. Will continue with dialback." );
} else { } else {
log.warn("No TLS auth, but TLS auth required."); log.warn( "Unable to authenticated the connection: SASL authentication failed (and dialback is not available)." );
return null; return null;
} }
} }
// TLS negotiation was successful so initiate a new stream log.debug( "TLS negotiation was successful so initiate a new stream." );
connection.deliverRawText(openingStream.toString()); connection.deliverRawText( openingStream.toString() );
// Reset the parser to use the new secured reader // Reset the parser to use the new secured reader
xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8)); xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8));
...@@ -483,34 +525,54 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -483,34 +525,54 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
} }
final boolean dialbackOffered = features.element("dialback") != null; final boolean dialbackOffered = features.element("dialback") != null;
log.debug("Offering dialback functionality: {}",dialbackOffered); log.debug("Remote server is offering dialback: {}, EXTERNAL SASL:", dialbackOffered, saslEXTERNALoffered );
log.debug("Offering EXTERNAL SASL: {}", saslEXTERNALoffered);
LocalOutgoingServerSession result = null; LocalOutgoingServerSession result = null;
// first, try SASL
// first, try SASL
if (saslEXTERNALoffered) { if (saslEXTERNALoffered) {
log.debug( "Trying to authenticate with EXTERNAL SASL." );
result = attemptSASLexternal(connection, xpp, reader, domain, hostname, id, openingStream); result = attemptSASLexternal(connection, xpp, reader, domain, hostname, id, openingStream);
if (result == null) {
log.debug( "Failed to authenticate with EXTERNAL SASL." );
} else {
log.debug( "Successfully authenticated with EXTERNAL SASL." );
}
} }
// SASL unavailable or failed, try dialback.
if (result == null) { if (result == null) {
// SASL unavailable or failed, try dialback. log.debug( "Trying to authenticate with dialback." );
result = attemptDialbackOverTLS(connection, reader, domain, hostname, id); result = attemptDialbackOverTLS(connection, reader, domain, hostname, id);
if (result == null) {
log.debug( "Failed to authenticate with dialback." );
} else {
log.debug( "Successfully authenticated with dialback." );
}
} }
return result; if ( result != null ) {
log.debug( "Successfully secured and authenticated connection!" );
return result;
} else {
log.warn( "Unable to secure and authenticate connection: Exhausted all options." );
return null;
}
} }
else { else {
log.debug("Cannot create outgoing server session, as neither SASL mechanisms nor SERVER DIALBACK were offered by " + hostname); log.debug( "Failed to secure and authenticate connection: neither SASL mechanisms nor SERVER DIALBACK were offered by the remote host." );
return null; return null;
} }
} }
else { else {
log.debug("Error, <proceed> was not received!"); log.debug( "Failed to secure and authenticate connection: <proceed> was not received!" );
return null; return null;
} }
} }
private static LocalOutgoingServerSession attemptDialbackOverTLS(Connection connection, XMPPPacketReader reader, String domain, String hostname, String id) { private static LocalOutgoingServerSession attemptDialbackOverTLS(Connection connection, XMPPPacketReader reader, String domain, String hostname, String id) {
final Logger log = LoggerFactory.getLogger(LocalOutgoingServerSession.class.getName()+"['"+hostname+"']"); final Logger log = LoggerFactory.getLogger( Log.getName() + "[Dialback over TLS for: " + domain + " (" + hostname + ") 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, domain);
...@@ -536,7 +598,8 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou ...@@ -536,7 +598,8 @@ 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 domain, String hostname, String id, StringBuilder openingStream) throws DocumentException, IOException, XmlPullParserException {
final Logger log = LoggerFactory.getLogger(LocalOutgoingServerSession.class.getName()+"['"+hostname+"']"); final Logger log = LoggerFactory.getLogger( Log.getName() + "[EXTERNAL SASL for: " + domain + " (" + hostname + ") Stream ID: " + id + "]" );
log.debug("Starting EXTERNAL SASL."); log.debug("Starting EXTERNAL SASL.");
if (doExternalAuthentication(domain, connection, reader)) { if (doExternalAuthentication(domain, connection, reader)) {
log.debug("EXTERNAL SASL was successful."); log.debug("EXTERNAL SASL was successful.");
......
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