Commit 937cdc6a authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Added TLS and SASL support. JM-387 JM-7

git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@2776 b35dd754-fafc-0310-a699-88a17e54d16e
parent 180f5579
......@@ -11,28 +11,28 @@
package org.jivesoftware.messenger;
import org.dom4j.io.XPPPacketReader;
import org.jivesoftware.messenger.auth.AuthToken;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.net.SocketConnection;
import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.net.SocketConnection;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.JiveGlobals;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
import org.xmpp.packet.StreamError;
import org.dom4j.io.XPPPacketReader;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParser;
import java.io.Writer;
import java.io.IOException;
import java.util.Map;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
/**
......@@ -44,13 +44,6 @@ public class ClientSession extends Session {
private static final String ETHERX_NAMESPACE = "http://etherx.jabber.org/streams";
private static final String FLASH_NAMESPACE = "http://www.jabber.com/streams/flash";
private static final String TLS_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
/**
* Version of the XMPP spec supported as MAJOR_VERSION.MINOR_VERSION (e.g. 1.0).
*/
private static final int MAJOR_VERSION = 0;
private static final int MINOR_VERSION = 0;
/**
* Keep the list of IP address that are allowed to connect to the server. If the list is
......@@ -242,6 +235,7 @@ public class ClientSession extends Session {
sb = new StringBuilder();
sb.append("<stream:features>");
sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
// TODO Consider that STARTTLS may be optional (add TLS options to the AC - disabled, optional, required)
// sb.append("<required/>");
sb.append("</starttls></stream:features>");
......@@ -252,24 +246,6 @@ public class ClientSession extends Session {
}
writer.flush();
boolean done = false;
while (!done) {
if (xpp.next() == XmlPullParser.START_TAG) {
done = true;
if (xpp.getName().equals("starttls") &&
xpp.getNamespace(xpp.getPrefix()).equals(TLS_NAMESPACE))
{
writer.write("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
if (isFlashClient) {
writer.write('\0');
}
writer.flush();
// TODO: setup SSLEngine and negotiate TLS.
}
}
}
return session;
}
......@@ -335,6 +311,17 @@ public class ClientSession extends Session {
return getAddress().getNode();
}
/**
* Sets the new Authorization Token for this session. The session is not yet considered fully
* authenticated (i.e. active) since a resource has not been binded at this point. This
* message will be sent after SASL authentication was successful but yet resource binding
* is required.
*
* @param auth the authentication token obtained from SASL authentication.
*/
public void setAuthToken(AuthToken auth) {
authToken = auth;
}
/**
* Initialize the session with a valid authentication token and
......
......@@ -14,6 +14,7 @@ package org.jivesoftware.messenger.net;
import org.dom4j.Element;
import org.jivesoftware.messenger.ClientSession;
import org.jivesoftware.messenger.PacketRouter;
import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.IQ;
......@@ -76,4 +77,29 @@ public class ClientSocketReader extends SocketReader {
return false;
}
String getNamespace() {
return "jabber:client";
}
protected String getAvailableStreamFeatures() {
StringBuilder sb = new StringBuilder();
// TODO Create and use #hasSASLAuthentication
if (((ClientSession)session).getAuthToken() == null) {
// Advertise that the server supports Non-SASL Authentication
if (XMPPServer.getInstance().getIQAuthHandler().isAllowAnonymous()) {
sb.append("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
}
// Advertise that the server supports In-Band Registration
if (XMPPServer.getInstance().getIQRegisterHandler().isInbandRegEnabled()) {
sb.append("<register xmlns=\"http://jabber.org/features/iq-register\"/>");
}
}
else {
// If the session has been authenticated then offer resource binding
// and session establishment
sb.append("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
sb.append("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
}
return sb.toString();
}
}
/**
* $RCSfile$
* $RCSfile: ComponentSocketReader.java,v $
* $Revision$
* $Date$
*
......@@ -53,4 +53,13 @@ public class ComponentSocketReader extends SocketReader {
}
return false;
}
String getNamespace() {
return "jabber:component:accept";
}
String getAvailableStreamFeatures() {
// Nothing special to add
return null;
}
}
/**
* $RCSfile$
* $RCSfile: ServerSocketReader.java,v $
* $Revision$
* $Date$
*
......@@ -210,4 +210,13 @@ public class ServerSocketReader extends SocketReader {
}
return false;
}
String getNamespace() {
return "jabber:server";
}
String getAvailableStreamFeatures() {
// Nothing special to add
return null;
}
}
......@@ -58,6 +58,7 @@ public class SocketConnection implements Connection {
private int majorVersion = 1;
private int minorVersion = 0;
private String language = null;
private TLSStreamHandler tlsStreamHandler;
/**
* Create a new session using the supplied socket.
......@@ -81,6 +82,31 @@ public class SocketConnection implements Connection {
xmlSerializer = new XMLSocketWriter(writer, socket);
}
/**
* Returns the stream handler responsible for securing the plain connection and providing
* the corresponding input and output streams.
*
* @return the stream handler responsible for securing the plain connection and providing
* the corresponding input and output streams.
*/
public TLSStreamHandler getTLSStreamHandler() {
return tlsStreamHandler;
}
/**
* Secures the plain connection by negotiating TLS with the client.
*
* @throws IOException if an error occured while securing the connection.
*/
public void startTLS() throws IOException {
if (!secure) {
secure = true;
tlsStreamHandler = new TLSStreamHandler(socket);
writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), CHARSET));
xmlSerializer = new XMLSocketWriter(writer, socket);
}
}
public boolean validate() {
if (isClosed()) {
return false;
......
......@@ -11,25 +11,26 @@
package org.jivesoftware.messenger.net;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParser;
import org.jivesoftware.messenger.*;
import org.dom4j.Element;
import org.dom4j.io.XPPPacketReader;
import org.jivesoftware.messenger.PacketRouter;
import org.jivesoftware.messenger.Session;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.interceptor.InterceptorManager;
import org.jivesoftware.messenger.interceptor.PacketRejectedException;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.LocaleUtils;
import org.dom4j.io.XPPPacketReader;
import org.dom4j.Element;
import org.jivesoftware.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.*;
import java.net.Socket;
import java.net.SocketException;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.net.Socket;
import java.net.SocketException;
/**
* A SocketReader creates the appropriate {@link Session} based on the defined namespace in the
......@@ -228,8 +229,35 @@ public abstract class SocketReader implements Runnable {
continue;
}
processIQ(packet);
}
else if ("starttls".equals(tag)) {
// Client requested to secure the connection using TLS
connection.deliverRawText("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
// Negotiate TLS
if (negotiateTLS()) {
tlsNegotiated();
}
else {
open = false;
session = null;
}
continue;
}
else {
else if ("auth".equals(tag)) {
// User is trying to authenticate using SASL
SASLAuthentication saslAuth = new SASLAuthentication(session, reader);
if (saslAuth.doHandshake(doc)) {
// SASL authentication was successful so open a new stream and offer
// resource binding and session establishment (to client sessions only)
saslSuccessful();
}
else {
open = false;
session = null;
}
continue;
}
else {
if (!processUnknowPacket(doc)) {
Log.warn(LocaleUtils.getLocalizedString("admin.error.packet.tag") +
doc.asXML());
......@@ -397,7 +425,9 @@ public abstract class SocketReader implements Runnable {
eventType = xpp.next();
}
// Create the correct session based on the sent namespace
// Create the correct session based on the sent namespace. At this point the server
// may offer the client to secure the connection. If the client decides to secure
// the connection then a <starttls> stanza should be received
if (!createSession(xpp.getNamespace(null))) {
// No session was created because of an invalid namespace prefix so answer a stream
// error and close the underlying connection
......@@ -416,6 +446,123 @@ public abstract class SocketReader implements Runnable {
}
}
/**
* Tries to secure the connection using TLS. If the connection is secured then reset
* the parser to use the new secured reader. But if the connection failed to be secured
* then send a <failure> stanza and close the connection.
*
* @return true if the connection was secured.
* @throws IOException if an I/O error occures while parsing the input stream.
* @throws XmlPullParserException if an error occures while parsing.
*/
private boolean negotiateTLS() throws IOException, XmlPullParserException {
// Negotiate TLS.
try {
connection.startTLS();
}
catch (IOException e) {
connection.deliverRawText("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
connection.close();
return false;
}
XmlPullParser xpp = reader.getXPPParser();
// Reset the parser to use the new 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();
}
return true;
}
/**
* TLS negotiation was successful so open a new stream and offer the new stream features.
* The new stream features will include available SASL mechanisms and specific features
* depending on the session type such as auth for Non-SASL authentication and register
* for in-band registration.
*/
private void tlsNegotiated() {
// Offer stream features including SASL Mechanisms
StringBuilder sb = new StringBuilder();
sb.append(geStreamHeader());
sb.append("<stream:features>");
// Include available SASL Mechanisms
sb.append(SASLAuthentication.getSASLMechanisms(session));
// Include specific features such as auth and register for client sessions
String specificFeatures = getAvailableStreamFeatures();
if (specificFeatures != null) {
sb.append(specificFeatures);
}
sb.append("</stream:features>");
connection.deliverRawText(sb.toString());
}
/**
* After SASL authentication was successful we should open a new stream and offer
* new stream features such as resource binding and session establishment. Notice that
* resource binding and session establishment should only be offered to clients (i.e. not
* to servers or external components)
*/
private void saslSuccessful() throws XmlPullParserException, IOException {
StringBuilder sb = new StringBuilder();
sb.append(geStreamHeader());
sb.append("<stream:features>");
// Include specific features such as resource binding and session establishment
// for client sessions
String specificFeatures = getAvailableStreamFeatures();
if (specificFeatures != null) {
sb.append(specificFeatures);
}
sb.append("</stream:features>");
connection.deliverRawText(sb.toString());
// Skip the opening stream sent by the client
XmlPullParser xpp = reader.getXPPParser();
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
}
/**
* Returns a text with the available stream features. Each subclass may return different
* values depending whether the session has been authenticated or not.
*
* @return a text with the available stream features or <tt>null</tt> to add nothing.
*/
abstract String getAvailableStreamFeatures();
/**
* Returns the stream namespace. (E.g. jabber:client, jabber:server, etc.).
*
* @return the stream namespace.
*/
abstract String getNamespace();
private String geStreamHeader() {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version='1.0' encoding='");
sb.append(CHARSET);
sb.append("'?>");
if (connection.isFlashClient()) {
sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" ");
} else {
sb.append("<stream:stream ");
}
sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"");
sb.append(getNamespace());
sb.append("\" from=\"");
sb.append(session.getServerName());
sb.append("\" id=\"");
sb.append(session.getStreamID().toString());
sb.append("\" xml:lang=\"");
sb.append(connection.getLanguage());
sb.append("\" version=\"");
sb.append(Session.MAJOR_VERSION).append(".").append(Session.MINOR_VERSION);
sb.append("\">");
return sb.toString();
}
/**
* Notification message indicating that the SocketReader is shutting down. The thread will
* stop reading and processing new requests. Subclasses may want to redefine this message
......
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