Commit 026c3f2f authored by Dave Cridland's avatar Dave Cridland

Support dialback errors

See XEP-0220, Dialback Errors.

This reduces disconnect in the case of piggybacking errors, and provides better
diagnostics.
parent d02e7f9e
......@@ -61,6 +61,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.StreamError;
/**
......@@ -85,8 +86,13 @@ import org.xmpp.packet.StreamError;
* @author Gaston Dombiak
*/
public class ServerDialback {
private static final Logger Log = LoggerFactory.getLogger(ServerDialback.class);
private enum VerifyResult {
decline, // For some reason, we declined to do the verify.
error, // Remote error from the authoritative server.
valid, // Explicitly valid.
invalid // Invalid.
}
private static final Logger Log = LoggerFactory.getLogger(ServerDialback.class);
/**
* The utf-8 charset for decoding and encoding Jabber packet streams.
......@@ -474,6 +480,17 @@ public class ServerDialback {
return null;
}
}
/**
* Send a dialback error.
*
* @param from From
* @param to To
* @param err Error type.
*/
protected void dialbackError(String from, String to, PacketError err) {
connection.deliverRawText("<db:result type=\"error\" from=\"" + from + "\" to=\"" + to + "\">" + err.toXML() + "</db:result>");
}
/**
* Returns true if the domain requested by the remote server was validated by the Authoritative
......@@ -482,7 +499,7 @@ public class ServerDialback {
* some other machine in the Originating Server's network.<p>
*
* If the domain was not valid or some error occurred while validating the domain then the
* underlying TCP connection will be closed.
* underlying TCP connection may be closed.
*
* @param doc the request for validating the new domain.
* @param streamID the stream id generated by this server for the Originating Server.
......@@ -495,7 +512,7 @@ public class ServerDialback {
Log.debug("ServerDialback: RS - Received dialback key from host: " + hostname + " to: " + recipient);
if (!RemoteServerManager.canAccess(hostname)) {
// Remote server is not allowed to establish a connection to this server
connection.deliverRawText(new StreamError(StreamError.Condition.host_unknown).toXML());
connection.deliverRawText(new StreamError(StreamError.Condition.policy_violation).toXML());
// Close the underlying connection
connection.close();
Log.debug("ServerDialback: RS - Error, hostname is not allowed to establish a connection to " +
......@@ -504,10 +521,7 @@ public class ServerDialback {
return false;
}
else if (isHostUnknown(recipient)) {
// address does not match a recognized hostname
connection.deliverRawText(new StreamError(StreamError.Condition.host_unknown).toXML());
// Close the underlying connection
connection.close();
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);
return false;
}
......@@ -556,38 +570,47 @@ public class ServerDialback {
}
}
if (!socket.isConnected()) {
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 {
socket.close();
} catch (IOException e) {
Log.warn("Socket error on close", e);
}
return false;
}
try {
boolean valid = verifyKey(key, streamID.toString(), recipient, hostname, socket);
Log.debug("ServerDialback: RS - Sending key verification result to OS: " + hostname);
sb = new StringBuilder();
sb.append("<db:result");
sb.append(" from=\"").append(recipient).append("\"");
sb.append(" to=\"").append(hostname).append("\"");
sb.append(" type=\"");
sb.append(valid ? "valid" : "invalid");
sb.append("\"/>");
connection.deliverRawText(sb.toString());
if (!valid) {
// Close the underlying connection
connection.close();
VerifyResult result = verifyKey(key, streamID.toString(), recipient, hostname, socket);
switch(result) {
case valid:
case invalid:
boolean valid = (result == VerifyResult.valid);
Log.debug("ServerDialback: RS - Sending key verification result to OS: " + hostname);
sb = new StringBuilder();
sb.append("<db:result");
sb.append(" from=\"").append(recipient).append("\"");
sb.append(" to=\"").append(hostname).append("\"");
sb.append(" type=\"");
sb.append(valid ? "valid" : "invalid");
sb.append("\"/>");
connection.deliverRawText(sb.toString());
if (!valid) {
// Close the underlying connection
connection.close();
}
return valid;
default:
break;
}
return valid;
dialbackError(recipient, hostname, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server returned error"));
return false;
}
catch (Exception e) {
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);
// Send a <remote-connection-failed/> stream error condition
// and terminate both the XML stream and the underlying
// TCP connection
connection.deliverRawText(new StreamError(
StreamError.Condition.remote_connection_failed).toXML());
// Close the underlying connection
connection.close();
return false;
}
}
......@@ -608,13 +631,14 @@ public class ServerDialback {
/**
* Verifies the key with the Authoritative Server.
*/
private boolean 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 {
XMPPPacketReader reader;
Writer writer = null;
// Set a read timeout
socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
VerifyResult result = VerifyResult.error;
try {
reader = new XMPPPacketReader();
reader.setXPPFactory(FACTORY);
......@@ -681,12 +705,17 @@ public class ServerDialback {
// condition is sent to the Originating Server
throw new RemoteConnectionFailedException("Invalid From");
}
else if ("valid".equals(doc.attributeValue("type"))){
Log.debug("ServerDialback: RS - Key was VERIFIED by the Authoritative Server for: {}", hostname);
result = VerifyResult.valid;
}
else if ("invalid".equals(doc.attributeValue("type"))){
Log.debug("ServerDialback: RS - Key was NOT VERIFIED by the Authoritative Server for: {}", hostname);
result = VerifyResult.invalid;
}
else {
boolean valid = "valid".equals(doc.attributeValue("type"));
Log.debug("ServerDialback: RS - Key was " + (valid ? "" : "NOT ") +
"VERIFIED by the Authoritative Server for: " +
hostname);
return valid;
Log.debug("ServerDialback: RS - Key was ERRORED by the Authoritative Server for: {}", hostname);
result = VerifyResult.error;
}
}
else {
......@@ -725,7 +754,7 @@ public class ServerDialback {
// Do nothing
}
}
return false;
return result;
}
/**
......
......@@ -368,7 +368,7 @@ public class LocalIncomingServerSession extends LocalSession implements Incoming
}
if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) {
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>");
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>");
}
return sb.toString();
......
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