Commit 3ef81832 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Added support for TLS and SASL. JM-395 and JM-396

git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@2869 b35dd754-fafc-0310-a699-88a17e54d16e
parent 9d655236
...@@ -11,16 +11,32 @@ ...@@ -11,16 +11,32 @@
package org.jivesoftware.messenger.server; package org.jivesoftware.messenger.server;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.XPPPacketReader;
import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.net.DNSUtil;
import org.jivesoftware.messenger.net.SocketConnection;
import org.jivesoftware.messenger.spi.BasicStreamIDFactory;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
...@@ -109,8 +125,7 @@ public class OutgoingServerSession extends Session { ...@@ -109,8 +125,7 @@ public class OutgoingServerSession extends Session {
synchronized (hostname.intern()) { synchronized (hostname.intern()) {
session = sessionManager.getOutgoingServerSession(hostname); session = sessionManager.getOutgoingServerSession(hostname);
if (session == null) { if (session == null) {
session = session = createOutgoingSession(domain, hostname, port);
new ServerDialback().createOutgoingSession(domain, hostname, port);
if (session != null) { if (session != null) {
// 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);
...@@ -153,8 +168,7 @@ public class OutgoingServerSession extends Session { ...@@ -153,8 +168,7 @@ public class OutgoingServerSession extends Session {
serverName.equals(newHostname)) { serverName.equals(newHostname)) {
return false; return false;
} }
session = session = createOutgoingSession(domain, newHostname, port);
new ServerDialback().createOutgoingSession(domain, newHostname, port);
if (session != null) { if (session != null) {
// 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);
...@@ -194,6 +208,208 @@ public class OutgoingServerSession extends Session { ...@@ -194,6 +208,208 @@ public class OutgoingServerSession extends Session {
return false; return false;
} }
/**
* Establishes a new outgoing session to a remote server. If the remote server supports TLS
* and SASL then the new outgoing connection will be secured with TLS and authenticated
* using SASL. However, if TLS or SASL is not supported by the remote server or if an
* error occured while securing or authenticating the connection using SASL then server
* dialback method will be used.
*
* @param domain the local domain to authenticate with the remote server.
* @param hostname the hostname of the remote server.
* @param port default port to use to establish the connection.
* @return new outgoing session to a remote server.
*/
private static OutgoingServerSession createOutgoingSession(String domain, String hostname,
int port) {
boolean useTLS = JiveGlobals.getBooleanProperty("xmpp.server.tls.enabled", false);
RemoteServerConfiguration configuration = RemoteServerManager.getConfiguration(hostname);
if (configuration != null) {
// TODO Use the specific TLS configuration for this remote server
//useTLS = configuration.isTLSEnabled();
}
if (useTLS) {
// Connect to remote server using TLS + SASL
SocketConnection connection = null;
String realHostname = null;
int realPort = port;
Socket socket = new Socket();
try {
Log.debug("OS - Trying to connect to " + hostname + ":" + port);
// Get the real hostname to connect to using DNS lookup of the specified hostname
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname, port);
realHostname = address.getHost();
realPort = address.getPort();
// Establish a TCP connection to the Receiving Server
socket.connect(new InetSocketAddress(realHostname, realPort),
RemoteServerManager.getSocketTimeout());
Log.debug("OS - Plain connection to " + hostname + ":" + port + " successful");
}
catch (Exception e) {
Log.error("Error trying to connect to remote server: " + hostname +
"(DNS lookup: " + realHostname + ":" + realPort + ")", e);
return null;
}
try {
connection =
new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
false);
// Send the stream header
StringBuilder openingStream = new StringBuilder();
openingStream.append("<stream:stream");
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
openingStream.append(" xmlns=\"jabber:server\"");
openingStream.append(" to=\"").append(hostname).append("\"");
openingStream.append(" version=\"1.0\">");
connection.deliverRawText(openingStream.toString());
XPPPacketReader reader = new XPPPacketReader();
reader.setXPPFactory(XmlPullParserFactory.newInstance());
reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
CHARSET));
// Get the answer from the Receiving Server
XmlPullParser xpp = reader.getXPPParser();
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
String serverVersion = xpp.getAttributeValue("", "version");
// Check if the remote server is XMPP 1.0 compliant
if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) {
// Get the stream features
Element features = reader.parseDocument().getRootElement();
// Check if TLS is enabled
if (features != null && features.element("starttls") != null) {
// Secure the connection with TLS and authenticate using SASL
OutgoingServerSession answer;
answer = secureAndAuthenticate(hostname, connection, reader, openingStream,
xpp, domain);
if (answer != null) {
// Everything went fine so return the secured and
// authenticated connection
return answer;
}
}
else {
Log.debug("OS - Error, <starttls> was not received");
}
}
// Something went wrong so close the connection and try server dialback over
// a plain connection
if (connection != null) {
connection.close();
}
}
catch (Exception e) {
Log.error("Error creating secured outgoing session to remote server: " + hostname +
"(DNS lookup: " + realHostname + ":" + realPort + ")", e);
// Close the connection
if (connection != null) {
connection.close();
}
}
}
Log.debug("OS - Going to try connecting using server dialback");
// Use server dialback over a plain connection
return new ServerDialback().createOutgoingSession(domain, hostname, port);
}
private static OutgoingServerSession secureAndAuthenticate(String hostname,
SocketConnection connection, XPPPacketReader reader, StringBuilder openingStream,
XmlPullParser xpp, String domain) throws Exception {
Element features;
Log.debug("OS - Indicating we want TLS to " + hostname);
connection.deliverRawText("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
// Wait for the <proceed> response
Element proceed = reader.parseDocument().getRootElement();
if (proceed != null && proceed.getName().equals("proceed")) {
Log.debug("OS - Negotiating TLS with " + hostname);
connection.startTLS(true);
Log.debug("OS - TLS negotiation with " + hostname + " was successful");
// TLS negotiation was successful so initiate a new stream
connection.deliverRawText(openingStream.toString());
// Reset the parser to use the new secured reader
xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(),
CHARSET));
// Skip new stream element
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
// Get new stream features
features = reader.parseDocument().getRootElement();
if (features != null && features.element("mechanisms") != null) {
Iterator it = features.element("mechanisms").elementIterator();
while (it.hasNext()) {
Element mechanism = (Element) it.next();
if ("EXTERNAL".equals(mechanism.getTextTrim())) {
Log.debug("OS - Starting EXTERNAL SASL with " + hostname);
if (doExternalAuthentication(domain, connection, reader)) {
Log.debug("OS - EXTERNAL SASL with " + hostname + " was successful");
// SASL was successful so initiate a new stream
connection.deliverRawText(openingStream.toString());
// 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
for (int eventType = xpp.getEventType();
eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
// SASL authentication was successful so create new
// OutgoingServerSession
String id = xpp.getAttributeValue("", "id");
StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
OutgoingServerSession session = new OutgoingServerSession(domain,
connection, new OutgoingServerSocketReader(reader), streamID);
connection.init(session);
// Set the hostname as the address of the session
session.setAddress(new JID(null, hostname, null));
return session;
}
else {
Log.debug("OS - Error, EXTERNAL SASL authentication with " + hostname +
" failed");
}
}
}
Log.debug("OS - Error, EXTERNAL SASL was not offered by " + hostname);
}
else {
Log.debug("OS - Error, no SASL mechanisms were offered by " + hostname);
}
}
else {
Log.debug("OS - Error, <proceed> was not received");
}
return null;
}
private static boolean doExternalAuthentication(String domain, SocketConnection connection,
XPPPacketReader reader) throws DocumentException, IOException, XmlPullParserException {
StringBuilder sb = new StringBuilder();
sb.append("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"EXTERNAL\">");
sb.append(StringUtils.encodeBase64(domain));
sb.append("</auth>");
connection.deliverRawText(sb.toString());
Element response = reader.parseDocument().getRootElement();
if (response != null && "success".equals(response.getName())) {
return true;
}
return false;
}
OutgoingServerSession(String serverName, Connection connection, OutgoingServerSession(String serverName, Connection connection,
OutgoingServerSocketReader socketReader, StreamID streamID) { OutgoingServerSocketReader socketReader, StreamID streamID) {
super(serverName, connection, streamID); super(serverName, connection, streamID);
......
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