Commit bcacd508 authored by Dele Olajide's avatar Dele Olajide

Merge pull request #47 from surevine/s2s-fixes

S2s fixes. Thanks for doing this. I am experimenting with websockets for Openfire s2s and I need these fixes.
parents 873a5f98 5eb60080
...@@ -13,6 +13,9 @@ clean: ...@@ -13,6 +13,9 @@ clean:
dpkg: dpkg:
cd build && ant installer.debian cd build && ant installer.debian
plugins:
cd build && ant plugins
eclipse: .settings .classpath .project eclipse: .settings .classpath .project
.settings: .settings:
......
...@@ -1773,9 +1773,13 @@ server.session.details.info=Below are details about the sessions with the remote ...@@ -1773,9 +1773,13 @@ server.session.details.info=Below are details about the sessions with the remote
server.session.details.hostname=Remote server IP / Hostname: server.session.details.hostname=Remote server IP / Hostname:
server.session.details.incoming_session=Incoming Session Details server.session.details.incoming_session=Incoming Session Details
server.session.details.streamid=Stream ID server.session.details.streamid=Stream ID
server.session.details.incoming_statistics=Statistics (Packets Received) server.session.details.authentication=Authentication
server.session.details.dialback=Dialback
server.session.details.tlsauth=Certificate
server.session.details.cipher=Cipher Suite
server.session.details.incoming_statistics=Packets RX
server.session.details.outgoing_session=Outgoing Session Details server.session.details.outgoing_session=Outgoing Session Details
server.session.details.outgoing_statistics=Statistics (Packets Sent) server.session.details.outgoing_statistics=Packets TX
# External Component Session summary Page # External Component Session summary Page
......
...@@ -528,29 +528,14 @@ public class SASLAuthentication { ...@@ -528,29 +528,14 @@ public class SASLAuthentication {
if (!verify) { if (!verify) {
authenticationSuccessful(session, hostname, null); authenticationSuccessful(session, hostname, null);
return Status.authenticated; return Status.authenticated;
} } else if(verifyCertificates(session.getConnection().getPeerCertificates(), hostname)) {
// Check that hostname matches the one provided in a certificate
Connection connection = session.getConnection();
try {
X509Certificate trusted = CertificateManager.getEndEntityCertificate(connection.getPeerCertificates(), SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
if (trusted != null) {
for (String identity : CertificateManager.getPeerIdentities(trusted)) {
// Verify that either the identity is the same as the hostname, or for wildcarded
// identities that the hostname ends with .domainspecified or -is- domainspecified.
if ((identity.startsWith("*.")
&& (hostname.endsWith(identity.replace("*.", "."))
|| hostname.equals(identity.replace("*.", ""))))
|| hostname.equals(identity)) {
authenticationSuccessful(session, hostname, null); authenticationSuccessful(session, hostname, null);
return Status.authenticated; LocalIncomingServerSession s = (LocalIncomingServerSession)session;
} if (s != null) {
s.tlsAuth();
} }
return Status.authenticated;
} }
} catch(IOException e) {
/// Keystore problem.
}
} }
else if (session instanceof LocalClientSession) { else if (session instanceof LocalClientSession) {
// Client EXTERNALL login // Client EXTERNALL login
...@@ -618,6 +603,28 @@ public class SASLAuthentication { ...@@ -618,6 +603,28 @@ public class SASLAuthentication {
return Status.failed; return Status.failed;
} }
public static boolean verifyCertificates(Certificate[] chain, String hostname) {
try {
X509Certificate trusted = CertificateManager.getEndEntityCertificate(chain, SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
if (trusted != null) {
for (String identity : CertificateManager.getPeerIdentities(trusted)) {
// Verify that either the identity is the same as the hostname, or for wildcarded
// identities that the hostname ends with .domainspecified or -is- domainspecified.
if ((identity.startsWith("*.")
&& (hostname.endsWith(identity.replace("*.", "."))
|| hostname.equals(identity.replace("*.", ""))))
|| hostname.equals(identity)) {
return true;
}
}
}
} catch(IOException e) {
Log.warn("Keystore issue while verifying certificate chain: {}", e.getMessage());
}
return false;
}
private static Status doSharedSecretAuthentication(LocalSession session, Element doc) private static Status doSharedSecretAuthentication(LocalSession session, Element doc)
throws UnsupportedEncodingException throws UnsupportedEncodingException
{ {
...@@ -686,6 +693,7 @@ public class SASLAuthentication { ...@@ -686,6 +693,7 @@ public class SASLAuthentication {
// Add the validated domain as a valid domain. The remote server can // Add the validated domain as a valid domain. The remote server can
// now send packets from this address // now send packets from this address
((LocalIncomingServerSession) session).addValidatedDomain(hostname); ((LocalIncomingServerSession) session).addValidatedDomain(hostname);
Log.info("Inbound Server {} authenticated (via TLS)", username);
} }
} }
......
...@@ -98,116 +98,7 @@ public class ServerTrustManager implements X509TrustManager { ...@@ -98,116 +98,7 @@ public class ServerTrustManager implements X509TrustManager {
*/ */
public void checkServerTrusted(X509Certificate[] x509Certificates, String string) public void checkServerTrusted(X509Certificate[] x509Certificates, String string)
throws CertificateException { throws CertificateException {
// Do nothing here. As before, the certificate will be validated when the remote server authenticates.
// Flag that indicates if certificates of the remote server should be validated. Disabling
// certificate validation is not recommended for production environments.
boolean verify = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true);
if (verify) {
int nSize = x509Certificates.length;
if (Log.isDebugEnabled()) {
Log.debug("Certificate chain:");
for (int i=1; i<= nSize; i++) {
Log.debug("Certificate " + i + ": " + x509Certificates[i-1].toString());
}
}
List<String> peerIdentities = CertificateManager.getPeerIdentities(x509Certificates[0]);
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, true)) {
Log.debug("Verifying certificate chain...");
// Working down the chain, for every certificate in the chain,
// verify that the subject of the certificate is the issuer of the
// next certificate in the chain.
Principal principalLast = null;
for (int i = nSize -1; i >= 0 ; i--) {
X509Certificate x509certificate = x509Certificates[i];
Principal principalIssuer = x509certificate.getIssuerDN();
Principal principalSubject = x509certificate.getSubjectDN();
Log.debug("Certificate " + (i+1) + " issuer: '" + principalIssuer + "' subject: '" + principalSubject + "'");
if (principalLast != null) {
if (principalIssuer.equals(principalLast)) {
try {
PublicKey publickey =
x509Certificates[i + 1].getPublicKey();
x509Certificates[i].verify(publickey);
}
catch (GeneralSecurityException generalsecurityexception) {
throw new CertificateException(
"signature verification failed of " + peerIdentities, generalsecurityexception);
}
}
else {
throw new CertificateException(
"subject/issuer verification failed of " + peerIdentities + ". In certificate "
+ (i+1) + " of the chain, I expected the issuer to be '" + principalLast
+"' but was '"+principalIssuer+"'.");
}
}
principalLast = principalSubject;
}
}
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_ROOT_VERIFY, true)) {
Log.debug("Verifying certificate chain root certificate...");
// Verify that the the last certificate in the chain was issued
// by a third-party that the client trusts.
boolean trusted = false;
try {
trusted = trustStore.getCertificateAlias(x509Certificates[nSize - 1]) != null;
// Keep track if the other peer presented a self-signed certificate
connection.setUsingSelfSignedCertificate(!trusted && nSize == 1);
if (!trusted && nSize == 1 && JiveGlobals
.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, false))
{
Log.warn("Accepting self-signed certificate of remote server: " +
peerIdentities);
trusted = true;
}
}
catch (KeyStoreException e) {
Log.error(e.getMessage(), e);
}
if (!trusted) {
throw new CertificateException("Root certificate (subject: "+x509Certificates[nSize - 1].getSubjectX500Principal()
+ ") of " + peerIdentities + " not trusted.");
}
}
// Verify that the server either matches an identity from the chain, or
// a wildcard.
Boolean found = false;
for (String identity : peerIdentities) {
// Verify that either the identity is the same as the hostname, or for wildcarded
// identities that the hostname ends with .domainspecified or -is- domainspecified.
if ((identity.startsWith("*.")
&& (server.endsWith(identity.replace("*.", "."))
|| server.equals(identity.replace("*.", ""))))
|| server.equals(identity)) {
found = true;
break;
}
}
if (!found) {
throw new CertificateException("target verification failed of " + peerIdentities);
}
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY_VALIDITY, true)) {
Log.debug("Verifying certificate chain validity (by date)...");
// For every certificate in the chain, verify that the certificate
// is valid at the current time.
Date date = new Date();
for (X509Certificate x509Certificate : x509Certificates) {
try {
x509Certificate.checkValidity(date);
} catch (GeneralSecurityException generalsecurityexception) {
throw new CertificateException("invalid date of " + peerIdentities);
}
}
}
}
} }
public X509Certificate[] getAcceptedIssuers() { public X509Certificate[] getAcceptedIssuers() {
......
...@@ -32,6 +32,7 @@ import java.util.List; ...@@ -32,6 +32,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import org.dom4j.Document;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader; import org.dom4j.io.XMPPPacketReader;
...@@ -44,6 +45,7 @@ import org.jivesoftware.openfire.XMPPServer; ...@@ -44,6 +45,7 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory; import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.net.DNSUtil; import org.jivesoftware.openfire.net.DNSUtil;
import org.jivesoftware.openfire.net.MXParser; import org.jivesoftware.openfire.net.MXParser;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.ServerTrafficCounter; import org.jivesoftware.openfire.net.ServerTrafficCounter;
import org.jivesoftware.openfire.net.SocketConnection; import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.session.ConnectionSettings; import org.jivesoftware.openfire.session.ConnectionSettings;
...@@ -61,6 +63,7 @@ import org.xmlpull.v1.XmlPullParser; ...@@ -61,6 +63,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.StreamError; import org.xmpp.packet.StreamError;
/** /**
...@@ -85,7 +88,12 @@ import org.xmpp.packet.StreamError; ...@@ -85,7 +88,12 @@ import org.xmpp.packet.StreamError;
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class ServerDialback { public class ServerDialback {
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); private static final Logger Log = LoggerFactory.getLogger(ServerDialback.class);
/** /**
...@@ -475,6 +483,17 @@ public class ServerDialback { ...@@ -475,6 +483,17 @@ public class ServerDialback {
} }
} }
/**
* 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 * Returns true if the domain requested by the remote server was validated by the Authoritative
* Server. To validate the domain a new TCP connection will be established to the * Server. To validate the domain a new TCP connection will be established to the
...@@ -482,7 +501,7 @@ public class ServerDialback { ...@@ -482,7 +501,7 @@ public class ServerDialback {
* some other machine in the Originating Server's network.<p> * 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 * 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 doc the request for validating the new domain.
* @param streamID the stream id generated by this server for the Originating Server. * @param streamID the stream id generated by this server for the Originating Server.
...@@ -495,7 +514,7 @@ public class ServerDialback { ...@@ -495,7 +514,7 @@ public class ServerDialback {
Log.debug("ServerDialback: RS - Received dialback key from host: " + hostname + " to: " + recipient); Log.debug("ServerDialback: RS - Received dialback key from host: " + hostname + " to: " + recipient);
if (!RemoteServerManager.canAccess(hostname)) { if (!RemoteServerManager.canAccess(hostname)) {
// Remote server is not allowed to establish a connection to this server // 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 // Close the underlying connection
connection.close(); connection.close();
Log.debug("ServerDialback: RS - Error, hostname is not allowed to establish a connection to " + Log.debug("ServerDialback: RS - Error, hostname is not allowed to establish a connection to " +
...@@ -504,10 +523,7 @@ public class ServerDialback { ...@@ -504,10 +523,7 @@ public class ServerDialback {
return false; return false;
} }
else if (isHostUnknown(recipient)) { else if (isHostUnknown(recipient)) {
// address does not match a recognized hostname dialbackError(recipient, hostname, new PacketError(PacketError.Condition.item_not_found, PacketError.Type.cancel, "Service not hosted here"));
connection.deliverRawText(new StreamError(StreamError.Condition.host_unknown).toXML());
// Close the underlying connection
connection.close();
Log.debug("ServerDialback: RS - Error, hostname not recognized: " + recipient); Log.debug("ServerDialback: RS - Error, hostname not recognized: " + recipient);
return false; return false;
} }
...@@ -520,15 +536,23 @@ public class ServerDialback { ...@@ -520,15 +536,23 @@ public class ServerDialback {
} }
} }
if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) { if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) {
// Remote server already has a IncomingServerSession created dialbackError(recipient, hostname, new PacketError(PacketError.Condition.resource_constraint, PacketError.Type.cancel, "Incoming session already exists"));
connection.deliverRawText(
new StreamError(StreamError.Condition.not_authorized).toXML());
// Close the underlying connection
connection.close();
Log.debug("ServerDialback: RS - Error, incoming connection already exists from: " + hostname); Log.debug("ServerDialback: RS - Error, incoming connection already exists from: " + hostname);
return false; return false;
} }
else { else {
if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname)) {
// If the remote host passes strong auth, just skip the dialback.
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=\"valid\"");
sb.append("/>");
connection.deliverRawText(sb.toString());
return true;
}
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 // Get a list of real hostnames and try to connect using DNS lookup of the specified domain
...@@ -556,13 +580,23 @@ public class ServerDialback { ...@@ -556,13 +580,23 @@ public class ServerDialback {
} }
} }
if (!socket.isConnected()) { 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); 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; return false;
} }
try { try {
boolean valid = verifyKey(key, streamID.toString(), recipient, hostname, socket); 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); 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");
...@@ -578,16 +612,15 @@ public class ServerDialback { ...@@ -578,16 +612,15 @@ public class ServerDialback {
connection.close(); connection.close();
} }
return valid; return valid;
default:
break;
}
dialbackError(recipient, hostname, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server returned error"));
return false;
} }
catch (Exception e) { 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); 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; return false;
} }
} }
...@@ -608,13 +641,14 @@ public class ServerDialback { ...@@ -608,13 +641,14 @@ public class ServerDialback {
/** /**
* Verifies the key with the Authoritative Server. * 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, Socket socket) throws IOException, XmlPullParserException,
RemoteConnectionFailedException { RemoteConnectionFailedException {
XMPPPacketReader reader; XMPPPacketReader reader;
Writer writer = null; Writer writer = null;
// Set a read timeout // Set a read timeout
socket.setSoTimeout(RemoteServerManager.getSocketTimeout()); socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
VerifyResult result = VerifyResult.error;
try { try {
reader = new XMPPPacketReader(); reader = new XMPPPacketReader();
reader.setXPPFactory(FACTORY); reader.setXPPFactory(FACTORY);
...@@ -630,7 +664,8 @@ public class ServerDialback { ...@@ -630,7 +664,8 @@ public class ServerDialback {
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\"");
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(" version=\"1.0\">");
writer.write(stream.toString()); writer.write(stream.toString());
writer.flush(); writer.flush();
...@@ -639,6 +674,23 @@ public class ServerDialback { ...@@ -639,6 +674,23 @@ 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").equals("1.0")) {
Document doc;
try {
doc = reader.parseDocument();
} catch (DocumentException e) {
// TODO Auto-generated catch block
Log.warn("XML Error!", e);
return VerifyResult.error;
}
Element features = doc.getRootElement();
Element starttls = features.element("starttls");
if (starttls != null) {
if (starttls.element("required") != null) {
Log.error("TLS required for db:verify but cannot yet do this.");
}
}
}
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("ServerDialback: RS - Asking AS to verify dialback key for id" + streamID);
// Request for verification of the key // Request for verification of the key
...@@ -681,12 +733,17 @@ public class ServerDialback { ...@@ -681,12 +733,17 @@ public class ServerDialback {
// condition is sent to the Originating Server // condition is sent to the Originating Server
throw new RemoteConnectionFailedException("Invalid From"); 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 { else {
boolean valid = "valid".equals(doc.attributeValue("type")); Log.debug("ServerDialback: RS - Key was ERRORED by the Authoritative Server for: {}", hostname);
Log.debug("ServerDialback: RS - Key was " + (valid ? "" : "NOT ") + result = VerifyResult.error;
"VERIFIED by the Authoritative Server for: " +
hostname);
return valid;
} }
} }
else { else {
...@@ -725,7 +782,7 @@ public class ServerDialback { ...@@ -725,7 +782,7 @@ public class ServerDialback {
// Do nothing // Do nothing
} }
} }
return false; return result;
} }
/** /**
......
...@@ -43,7 +43,7 @@ import java.util.Collection; ...@@ -43,7 +43,7 @@ import java.util.Collection;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public interface IncomingServerSession extends Session { public interface IncomingServerSession extends ServerSession {
/** /**
* Returns a collection with all the domains, subdomains and virtual hosts that where * Returns a collection with all the domains, subdomains and virtual hosts that where
......
...@@ -68,7 +68,7 @@ import org.xmpp.packet.Packet; ...@@ -68,7 +68,7 @@ import org.xmpp.packet.Packet;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class LocalIncomingServerSession extends LocalSession implements IncomingServerSession { public class LocalIncomingServerSession extends LocalServerSession implements IncomingServerSession {
private static final Logger Log = LoggerFactory.getLogger(LocalIncomingServerSession.class); private static final Logger Log = LoggerFactory.getLogger(LocalIncomingServerSession.class);
...@@ -368,9 +368,13 @@ public class LocalIncomingServerSession extends LocalSession implements Incoming ...@@ -368,9 +368,13 @@ public class LocalIncomingServerSession extends LocalSession implements Incoming
} }
if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) { 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(); return sb.toString();
} }
public void tlsAuth() {
usingServerDialback = false;
}
} }
...@@ -44,6 +44,7 @@ import org.jivesoftware.openfire.XMPPServer; ...@@ -44,6 +44,7 @@ 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.DNSUtil;
import org.jivesoftware.openfire.net.MXParser; import org.jivesoftware.openfire.net.MXParser;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.SocketConnection; 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;
...@@ -87,7 +88,7 @@ import com.jcraft.jzlib.ZInputStream; ...@@ -87,7 +88,7 @@ import com.jcraft.jzlib.ZInputStream;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class LocalOutgoingServerSession extends LocalSession implements OutgoingServerSession { public class LocalOutgoingServerSession extends LocalServerSession implements OutgoingServerSession {
private static final Logger Log = LoggerFactory.getLogger(LocalOutgoingServerSession.class); private static final Logger Log = LoggerFactory.getLogger(LocalOutgoingServerSession.class);
...@@ -99,10 +100,6 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -99,10 +100,6 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
private Collection<String> authenticatedDomains = new HashSet<String>(); private Collection<String> authenticatedDomains = new HashSet<String>();
private final Collection<String> hostnames = new HashSet<String>(); private final Collection<String> hostnames = new HashSet<String>();
private OutgoingServerSocketReader socketReader; private OutgoingServerSocketReader socketReader;
/**
* Flag that indicates if the session was created using server-dialback.
*/
private boolean usingServerDialback = true;
/** /**
* Creates a new outgoing connection to the specified hostname if no one exists. The port of * Creates a new outgoing connection to the specified hostname if no one exists. The port of
...@@ -173,57 +170,12 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -173,57 +170,12 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
// 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);
return true; return true;
} else {
// Ensure that the hostname is not an IP address (i.e. contains chars)
if (!pattern.matcher(hostname).find()) {
return false;
}
// Check if hostname is a subdomain of an existing outgoing session
for (String otherHost : sessionManager.getOutgoingServers()) {
if (hostname.contains(otherHost)) {
session = sessionManager.getOutgoingServerSession(otherHost);
// Add the new hostname to the found session
session.addHostname(hostname);
return true;
}
}
// Try to establish a connection to candidate hostnames. Iterate on the
// substring after the . and try to establish a connection. If a
// connection is established then the same session will be used for
// sending packets to the "candidate hostname" as well as for the
// requested hostname (i.e. the subdomain of the candidate hostname)
// This trick is useful when remote servers haven't registered in their
// DNSs an entry for their subdomains
int index = hostname.indexOf('.');
while (index > -1 && index < hostname.length()) {
String newHostname = hostname.substring(index + 1);
String serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
if ("com".equals(newHostname) || "net".equals(newHostname) ||
"org".equals(newHostname) ||
"gov".equals(newHostname) ||
"edu".equals(newHostname) ||
serverName.equals(newHostname)) {
return false;
} }
session = createOutgoingSession(domain, newHostname, port);
if (session != null) {
// Add the validated domain as an authenticated domain
session.addAuthenticatedDomain(domain);
// Add the new hostname to the list of names that the server may have
session.addHostname(hostname);
// Add the new hostname to the found session
session.addHostname(newHostname);
// Notify the SessionManager that a new session has been created
sessionManager.outgoingServerSessionCreated((LocalOutgoingServerSession) session);
return true;
} else { } else {
index = hostname.indexOf('.', index + 1); Log.warn("Fail to connect to {} for {}", hostname, domain);
}
}
return false; return false;
} }
} }
}
// A session already exists. The session was established using server dialback so // A session already exists. The session was established using server dialback so
// it is possible to do piggybacking to authenticate more domains // 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)) {
...@@ -427,11 +379,25 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -427,11 +379,25 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
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("Negotiating TLS...");
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, hostname, needed ? Connection.ClientAuth.needed : Connection.ClientAuth.wanted); connection.startTLS(true, hostname, needed ? Connection.ClientAuth.needed : Connection.ClientAuth.wanted);
} catch(Exception e) {
log.debug("Got an exception whilst negotiating TLS: " + e.getMessage());
throw e;
}
log.debug("TLS negotiation was successful."); log.debug("TLS negotiation was successful.");
if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname)) {
log.debug("X.509/PKIX failure on outbound session");
if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
log.debug("Will continue with dialback.");
} else {
log.warn("No TLS auth, but TLS auth required.");
return null;
}
}
// TLS negotiation was successful so initiate a new stream // TLS negotiation was successful so initiate a new stream
connection.deliverRawText(openingStream.toString()); connection.deliverRawText(openingStream.toString());
...@@ -462,6 +428,10 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -462,6 +428,10 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
zlibSupported = true; zlibSupported = true;
} }
} }
if (zlibSupported) {
log.debug("Suppressing request to perform compression; unsupported in this version.");
zlibSupported = false;
}
if (zlibSupported) { if (zlibSupported) {
log.debug("Requesting stream compression (zlib)."); log.debug("Requesting stream compression (zlib).");
connection.deliverRawText("<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>"); connection.deliverRawText("<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>");
...@@ -506,6 +476,7 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -506,6 +476,7 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
// 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()) {
...@@ -516,19 +487,13 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -516,19 +487,13 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
} }
} }
} }
}
final boolean dialbackOffered = features.element("dialback") != null; final boolean dialbackOffered = features.element("dialback") != null;
final boolean usesSelfSigned = connection.isUsingSelfSignedCertificate();
log.debug("Offering dialback functionality: {}",dialbackOffered); log.debug("Offering dialback functionality: {}",dialbackOffered);
log.debug("Offering EXTERNAL SASL: {}", saslEXTERNALoffered); log.debug("Offering EXTERNAL SASL: {}", saslEXTERNALoffered);
log.debug("Is using a self-signed certificate: {}", usesSelfSigned);
// Skip SASL EXTERNAL and use server dialback over TLS when using self-signed certificates
LocalOutgoingServerSession result = null; LocalOutgoingServerSession result = null;
if (usesSelfSigned) {
log.debug("As remote server is using self-signed certificate, SASL EXTERNAL is skipped. Attempting dialback over TLS instead.");
result = attemptDialbackOverTLS(connection, reader, domain, hostname, id);
} else {
// first, try SASL // first, try SASL
if (saslEXTERNALoffered) { if (saslEXTERNALoffered) {
result = attemptSASLexternal(connection, xpp, reader, domain, hostname, id, openingStream); result = attemptSASLexternal(connection, xpp, reader, domain, hostname, id, openingStream);
...@@ -537,7 +502,6 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -537,7 +502,6 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
// SASL unavailable or failed, try dialback. // SASL unavailable or failed, try dialback.
result = attemptDialbackOverTLS(connection, reader, domain, hostname, id); result = attemptDialbackOverTLS(connection, reader, domain, hostname, id);
} }
}
return result; return result;
} }
...@@ -587,7 +551,9 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -587,7 +551,9 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
connection.deliverRawText(openingStream.toString()); connection.deliverRawText(openingStream.toString());
// Reset the parser // Reset the parser
xpp.resetInput(); //xpp.resetInput();
// // Reset the parser to use the new secured reader
xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), CHARSET));
// Skip the opening stream sent by the server // Skip the opening stream sent by the server
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next(); eventType = xpp.next();
...@@ -739,8 +705,4 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -739,8 +705,4 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
// Nothing special to add // Nothing special to add
return null; return null;
} }
public boolean isUsingServerDialback() {
return usingServerDialback;
}
} }
/**
*
*/
package org.jivesoftware.openfire.session;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.xmpp.packet.Packet;
/**
* @author dwd
*
*/
public class LocalServerSession extends LocalSession implements ServerSession {
protected boolean usingServerDialback = true;
protected boolean outboundAllowed = false;
protected boolean inboundAllowed = false;
public LocalServerSession(String serverName, Connection connection,
StreamID streamID) {
super(serverName, connection, streamID);
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.session.LocalSession#canProcess(org.xmpp.packet.Packet)
*/
@Override
boolean canProcess(Packet packet) {
// TODO Auto-generated method stub
return false;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.session.LocalSession#deliver(org.xmpp.packet.Packet)
*/
@Override
void deliver(Packet packet) throws UnauthorizedException {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.session.LocalSession#getAvailableStreamFeatures()
*/
@Override
public String getAvailableStreamFeatures() {
// TODO Auto-generated method stub
return null;
}
public boolean isUsingServerDialback() {
return usingServerDialback;
}
}
...@@ -21,6 +21,8 @@ import java.util.Date; ...@@ -21,6 +21,8 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.net.ssl.SSLSession;
import org.jivesoftware.openfire.Connection; import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager; import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID; import org.jivesoftware.openfire.StreamID;
...@@ -28,6 +30,8 @@ import org.jivesoftware.openfire.XMPPServer; ...@@ -28,6 +30,8 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.interceptor.InterceptorManager; import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.jivesoftware.openfire.interceptor.PacketRejectedException; import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.net.TLSStreamHandler;
import org.jivesoftware.openfire.spi.RoutingTableImpl; import org.jivesoftware.openfire.spi.RoutingTableImpl;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -387,4 +391,22 @@ public abstract class LocalSession implements Session { ...@@ -387,4 +391,22 @@ public abstract class LocalSession implements Session {
public boolean isUsingSelfSignedCertificate() { public boolean isUsingSelfSignedCertificate() {
return conn.isUsingSelfSignedCertificate(); return conn.isUsingSelfSignedCertificate();
} }
/**
* Returns a String representing the Cipher Suite Name, or "NONE".
* @return String
*/
public String getCipherSuiteName() {
SocketConnection s = (SocketConnection)getConnection();
if (s != null) {
TLSStreamHandler t = s.getTLSStreamHandler();
if (t != null) {
SSLSession ssl = t.getSSLSession();
if (ssl != null) {
return ssl.getCipherSuite();
}
}
}
return "NONE";
}
} }
...@@ -36,8 +36,7 @@ import java.util.Collection; ...@@ -36,8 +36,7 @@ import java.util.Collection;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public interface OutgoingServerSession extends Session { public interface OutgoingServerSession extends ServerSession {
/** /**
* Returns a collection with all the domains, subdomains and virtual hosts that where * Returns a collection with all the domains, subdomains and virtual hosts that where
* authenticated. The remote server will accept packets sent from any of these domains, * authenticated. The remote server will accept packets sent from any of these domains,
...@@ -84,11 +83,4 @@ public interface OutgoingServerSession extends Session { ...@@ -84,11 +83,4 @@ public interface OutgoingServerSession extends Session {
* @return True if the subdomain was authenticated by the remote server. * @return True if the subdomain was authenticated by the remote server.
*/ */
boolean authenticateSubdomain(String domain, String hostname); boolean authenticateSubdomain(String domain, String hostname);
/**
* Returns true if this outgoing session was established using server dialback.
*
* @return true if this outgoing session was established using server dialback.
*/
boolean isUsingServerDialback();
} }
package org.jivesoftware.openfire.session;
public interface ServerSession extends Session {
/**
* Returns true if this outgoing session was established using server dialback.
*
* @return true if this outgoing session was established using server dialback.
*/
boolean isUsingServerDialback();
}
...@@ -195,4 +195,11 @@ public interface Session extends RoutableChannelHandler { ...@@ -195,4 +195,11 @@ public interface Session extends RoutableChannelHandler {
* @return true if the socket remains valid, false otherwise. * @return true if the socket remains valid, false otherwise.
*/ */
public boolean validate(); public boolean validate();
/**
* Returns the TLS cipher suite name, if any.
* Always returns a valid string, though the string may be "NONE"
* @return cipher suite name.
*/
public String getCipherSuiteName();
} }
\ No newline at end of file
...@@ -123,9 +123,12 @@ ...@@ -123,9 +123,12 @@
<table cellpadding="3" cellspacing="1" border="0" width="100%"> <table cellpadding="3" cellspacing="1" border="0" width="100%">
<tr> <tr>
<th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th> <th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th>
<th width="10%"><fmt:message key="server.session.details.authentication"/></th>
<th width="10%"><fmt:message key="server.session.details.cipher"/></th>
<th width="20%"><fmt:message key="server.session.label.creation" /></th> <th width="20%"><fmt:message key="server.session.label.creation" /></th>
<th width="20%"><fmt:message key="server.session.label.last_active" /></th> <th width="20%"><fmt:message key="server.session.label.last_active" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.incoming_statistics" /></th> <th width="25%" nowrap><fmt:message key="server.session.details.incoming_statistics" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.outgoing_statistics" /></th>
</tr> </tr>
<tr> <tr>
<% if (inSession.isSecure()) { %> <% if (inSession.isSecure()) { %>
...@@ -151,9 +154,12 @@ ...@@ -151,9 +154,12 @@
boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR); boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR);
%> %>
<td><%= inSession.getStreamID()%></td> <td><%= inSession.getStreamID()%></td>
<td><% if (inSession.isUsingServerDialback()) { %><fmt:message key="server.session.details.dialback"/><% } else { %><fmt:message key="server.session.details.tlsauth"/><% } %></td>
<td><%= inSession.getCipherSuiteName() %></td>
<td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td> <td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td>
<td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td> <td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td>
<td align="center"><%= numFormatter.format(inSession.getNumClientPackets()) %></td> <td align="center"><%= numFormatter.format(inSession.getNumClientPackets()) %></td>
<td align="center"><%= numFormatter.format(inSession.getNumServerPackets()) %></td>
</tr> </tr>
</table> </table>
</div> </div>
...@@ -169,8 +175,11 @@ ...@@ -169,8 +175,11 @@
<table cellpadding="3" cellspacing="1" border="0" width="100%"> <table cellpadding="3" cellspacing="1" border="0" width="100%">
<tr> <tr>
<th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th> <th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th>
<th width="10%"><fmt:message key="server.session.details.authentication"/></th>
<th width="10%"><fmt:message key="server.session.details.cipher"/></th>
<th width="20%"><fmt:message key="server.session.label.creation" /></th> <th width="20%"><fmt:message key="server.session.label.creation" /></th>
<th width="20%"><fmt:message key="server.session.label.last_active" /></th> <th width="20%"><fmt:message key="server.session.label.last_active" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.incoming_statistics" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.outgoing_statistics" /></th> <th width="25%" nowrap><fmt:message key="server.session.details.outgoing_statistics" /></th>
</tr> </tr>
<tr> <tr>
...@@ -197,8 +206,11 @@ ...@@ -197,8 +206,11 @@
boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR); boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR);
%> %>
<td><%= outSession.getStreamID()%></td> <td><%= outSession.getStreamID()%></td>
<td><% if (outSession.isUsingServerDialback()) { %><fmt:message key="server.session.details.dialback"/><% } else { %><fmt:message key="server.session.details.tlsauth"/><% } %></td>
<td><%= outSession.getCipherSuiteName() %></td>
<td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td> <td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td>
<td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td> <td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td>
<td align="center"><%= numFormatter.format(outSession.getNumClientPackets()) %></td>
<td align="center"><%= numFormatter.format(outSession.getNumServerPackets()) %></td> <td align="center"><%= numFormatter.format(outSession.getNumServerPackets()) %></td>
</tr> </tr>
</table> </table>
......
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