Commit cc561652 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Included GSSAPI changes and other fixes/improvements. Last check-in before refactoring work.

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3851 b35dd754-fafc-0310-a699-88a17e54d16e
parent 1d71cd35
...@@ -34,9 +34,7 @@ import javax.security.sasl.SaslServer; ...@@ -34,9 +34,7 @@ import javax.security.sasl.SaslServer;
import java.io.IOException; import java.io.IOException;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Map; import java.util.*;
import java.util.StringTokenizer;
import java.util.TreeMap;
/** /**
* SASLAuthentication is responsible for returning the available SASL mechanisms to use and for * SASLAuthentication is responsible for returning the available SASL mechanisms to use and for
...@@ -61,6 +59,8 @@ public class SASLAuthentication { ...@@ -61,6 +59,8 @@ public class SASLAuthentication {
private static Map<String, ElementType> typeMap = new TreeMap<String, ElementType>(); private static Map<String, ElementType> typeMap = new TreeMap<String, ElementType>();
private static Collection<String> mechanisms = null;
public enum ElementType { public enum ElementType {
AUTH("auth"), RESPONSE("response"), CHALLENGE("challenge"), FAILURE("failure"), UNDEF(""); AUTH("auth"), RESPONSE("response"), CHALLENGE("challenge"), FAILURE("failure"), UNDEF("");
...@@ -107,21 +107,33 @@ public class SASLAuthentication { ...@@ -107,21 +107,33 @@ public class SASLAuthentication {
* @return a string with the valid SASL mechanisms available for the specified session. * @return a string with the valid SASL mechanisms available for the specified session.
*/ */
public static String getSASLMechanisms(Session session) { public static String getSASLMechanisms(Session session) {
if (!(session instanceof ClientSession) && !(session instanceof IncomingServerSession)) {
return "";
}
StringBuilder sb = new StringBuilder(195); StringBuilder sb = new StringBuilder(195);
sb.append("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"); sb.append("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
if (session.getConnection().isSecure() && session instanceof IncomingServerSession) { if (session.getConnection().isSecure() && session instanceof IncomingServerSession) {
// Server connections dont follow the same rules as clients
sb.append("<mechanism>EXTERNAL</mechanism>"); sb.append("<mechanism>EXTERNAL</mechanism>");
} }
else { else {
// Check if the user provider in use supports passwords retrieval. Accessing to the users for (String mech : getSupportedMechanisms()) {
// passwords will be required by the CallbackHandler if (mech.equals("CRAM-MD5") || mech.equals("DIGEST-MD5")) {
if (UserManager.getUserProvider().supportsPasswordRetrieval()) { // Check if the user provider in use supports passwords retrieval. Accessing
sb.append("<mechanism>CRAM-MD5</mechanism>"); // to the users passwords will be required by the CallbackHandler
sb.append("<mechanism>DIGEST-MD5</mechanism>"); if (!UserManager.getUserProvider().supportsPasswordRetrieval()) {
continue;
} }
sb.append("<mechanism>PLAIN</mechanism>"); }
if (XMPPServer.getInstance().getIQAuthHandler().isAllowAnonymous()) { else if (mech.equals("ANONYMOUS")) {
sb.append("<mechanism>ANONYMOUS</mechanism>"); // Check anonymous is supported
if (!XMPPServer.getInstance().getIQAuthHandler().isAllowAnonymous()) {
continue;
}
}
sb.append("<mechanism>");
sb.append(mech);
sb.append("</mechanism>");
} }
} }
sb.append("</mechanisms>"); sb.append("</mechanisms>");
...@@ -140,12 +152,25 @@ public class SASLAuthentication { ...@@ -140,12 +152,25 @@ public class SASLAuthentication {
switch (type) { switch (type) {
case AUTH: case AUTH:
String mechanism = doc.attributeValue("mechanism"); String mechanism = doc.attributeValue("mechanism");
//Log.debug("SASLAuthentication.doHandshake() AUTH entered: "+mechanism);
if (mechanism.equalsIgnoreCase("PLAIN")) { if (mechanism.equalsIgnoreCase("PLAIN")) {
if (getSupportedMechanisms().contains("PLAIN")) {
success = doPlainAuthentication(doc); success = doPlainAuthentication(doc);
}
else {
// TODO Send auth failed before closing connection
success = false;
}
isComplete = true; isComplete = true;
} }
else if (mechanism.equalsIgnoreCase("ANONYMOUS")) { else if (mechanism.equalsIgnoreCase("ANONYMOUS")) {
if (getSupportedMechanisms().contains("ANONYMOUS")) {
success = doAnonymousAuthentication(); success = doAnonymousAuthentication();
}
else {
// TODO Send auth failed before closing connection
success = false;
}
isComplete = true; isComplete = true;
} }
else if (mechanism.equalsIgnoreCase("EXTERNAL")) { else if (mechanism.equalsIgnoreCase("EXTERNAL")) {
...@@ -155,9 +180,13 @@ public class SASLAuthentication { ...@@ -155,9 +180,13 @@ public class SASLAuthentication {
else { else {
// The selected SASL mechanism requires the server to send a challenge // The selected SASL mechanism requires the server to send a challenge
// to the client // to the client
if (getSupportedMechanisms().contains(mechanism)) {
try { try {
Map<String, String> props = new TreeMap<String, String>(); Map<String, String> props = new TreeMap<String, String>();
props.put(Sasl.QOP, "auth"); props.put(Sasl.QOP, "auth");
if (mechanism.equals("GSSAPI")) {
props.put(Sasl.SERVER_AUTH, "TRUE");
}
SaslServer ss = Sasl.createSaslServer(mechanism, "xmpp", SaslServer ss = Sasl.createSaslServer(mechanism, "xmpp",
session.getServerName(), props, session.getServerName(), props,
new XMPPCallbackHandler()); new XMPPCallbackHandler());
...@@ -165,7 +194,7 @@ public class SASLAuthentication { ...@@ -165,7 +194,7 @@ public class SASLAuthentication {
byte[] token = new byte[0]; byte[] token = new byte[0];
if (doc.isTextOnly()) { if (doc.isTextOnly()) {
// If auth request includes a value then validate it // If auth request includes a value then validate it
token = StringUtils.decodeBase64(doc.getText()); token = StringUtils.decodeBase64(doc.getTextTrim());
if (token == null) { if (token == null) {
token = new byte[0]; token = new byte[0];
} }
...@@ -182,6 +211,14 @@ public class SASLAuthentication { ...@@ -182,6 +211,14 @@ public class SASLAuthentication {
authenticationFailed(); authenticationFailed();
} }
} }
else {
// TODO Send auth failed before closing connection
Log.warn("Client wants to do a MECH we don't support: '" +
mechanism + "'");
isComplete = true;
success = false;
}
}
break; break;
case RESPONSE: case RESPONSE:
SaslServer ss = (SaslServer) session.getSessionData("SaslServer"); SaslServer ss = (SaslServer) session.getSessionData("SaslServer");
...@@ -189,22 +226,29 @@ public class SASLAuthentication { ...@@ -189,22 +226,29 @@ public class SASLAuthentication {
boolean ssComplete = ss.isComplete(); boolean ssComplete = ss.isComplete();
String response = doc.getTextTrim(); String response = doc.getTextTrim();
try { try {
if (ssComplete) {
authenticationSuccessful(ss.getAuthorizationID(), null);
success = true;
isComplete = true;
}
else {
byte[] data = StringUtils.decodeBase64(response); byte[] data = StringUtils.decodeBase64(response);
if (data == null) { if (data == null) {
data = new byte[0]; data = new byte[0];
} }
byte[] challenge = ss.evaluateResponse(data);
if (ssComplete) { if (ss.isComplete()) {
authenticationSuccessful(ss.getAuthorizationID()); authenticationSuccessful(ss.getAuthorizationID(),
challenge);
success = true; success = true;
isComplete = true; isComplete = true;
} }
else { else {
byte[] challenge = ss.evaluateResponse(data);
// Send the challenge // Send the challenge
sendChallenge(challenge); sendChallenge(challenge);
} }
} }
}
catch (SaslException e) { catch (SaslException e) {
isComplete = true; isComplete = true;
Log.warn("SaslException", e); Log.warn("SaslException", e);
...@@ -244,7 +288,7 @@ public class SASLAuthentication { ...@@ -244,7 +288,7 @@ public class SASLAuthentication {
private boolean doAnonymousAuthentication() { private boolean doAnonymousAuthentication() {
if (XMPPServer.getInstance().getIQAuthHandler().isAllowAnonymous()) { if (XMPPServer.getInstance().getIQAuthHandler().isAllowAnonymous()) {
// Just accept the authentication :) // Just accept the authentication :)
authenticationSuccessful(null); authenticationSuccessful(null, null);
return true; return true;
} }
else { else {
...@@ -282,7 +326,7 @@ public class SASLAuthentication { ...@@ -282,7 +326,7 @@ public class SASLAuthentication {
} }
try { try {
AuthToken token = AuthFactory.authenticate(username, password); AuthToken token = AuthFactory.authenticate(username, password);
authenticationSuccessful(token.getUsername()); authenticationSuccessful(token.getUsername(), null);
return true; return true;
} }
catch (UnauthorizedException e) { catch (UnauthorizedException e) {
...@@ -318,7 +362,7 @@ public class SASLAuthentication { ...@@ -318,7 +362,7 @@ public class SASLAuthentication {
boolean verify = boolean verify =
JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify", true); JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify", true);
if (!verify) { if (!verify) {
authenticationSuccessful(hostname); authenticationSuccessful(hostname, null);
return true; return true;
} }
} }
...@@ -326,7 +370,7 @@ public class SASLAuthentication { ...@@ -326,7 +370,7 @@ public class SASLAuthentication {
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) { for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
if (TLSStreamHandler.getPeerIdentities((X509Certificate) certificate) if (TLSStreamHandler.getPeerIdentities((X509Certificate) certificate)
.contains(hostname)) { .contains(hostname)) {
authenticationSuccessful(hostname); authenticationSuccessful(hostname, null);
return true; return true;
} }
} }
...@@ -337,6 +381,9 @@ public class SASLAuthentication { ...@@ -337,6 +381,9 @@ public class SASLAuthentication {
private void sendChallenge(byte[] challenge) { private void sendChallenge(byte[] challenge) {
StringBuilder reply = new StringBuilder(250); StringBuilder reply = new StringBuilder(250);
if(challenge == null) {
challenge = new byte[0];
}
String challenge_b64 = StringUtils.encodeBase64(challenge).trim(); String challenge_b64 = StringUtils.encodeBase64(challenge).trim();
if ("".equals(challenge_b64)) { if ("".equals(challenge_b64)) {
challenge_b64 = "="; // Must be padded if null challenge_b64 = "="; // Must be padded if null
...@@ -348,8 +395,16 @@ public class SASLAuthentication { ...@@ -348,8 +395,16 @@ public class SASLAuthentication {
connection.deliverRawText(reply.toString()); connection.deliverRawText(reply.toString());
} }
private void authenticationSuccessful(String username) { private void authenticationSuccessful(String username, byte[] successData) {
connection.deliverRawText("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>"); StringBuilder reply = new StringBuilder(80);
reply.append("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"");
if (successData != null) {
reply.append(">").append(successData).append("</success>");
}
else {
reply.append("/>");
}
connection.deliverRawText(reply.toString());
// We only support SASL for c2s // We only support SASL for c2s
if (session instanceof ClientSession) { if (session instanceof ClientSession) {
((ClientSession) session).setAuthToken(new AuthToken(username)); ((ClientSession) session).setAuthToken(new AuthToken(username));
...@@ -383,4 +438,45 @@ public class SASLAuthentication { ...@@ -383,4 +438,45 @@ public class SASLAuthentication {
connection.close(); connection.close();
} }
} }
public static Collection<String> getSupportedMechanisms() {
if (mechanisms == null) {
mechanisms = new ArrayList<String>();
String available = JiveGlobals.getXMLProperty("sasl.mechs");
if (available == null) {
mechanisms.add("ANONYMOUS");
mechanisms.add("PLAIN");
mechanisms.add("DIGEST-MD5");
mechanisms.add("CRAM-MD5");
return mechanisms;
}
StringTokenizer st = new StringTokenizer(available, " ,\t\n\r\f");
while (st.hasMoreTokens()) {
String mech = st.nextToken().toUpperCase();
// Check that the mech is a supported mechansim. Maybe we shouldnt check this and allow any?
if(mech.equals("ANONYMOUS") ||
mech.equals("PLAIN") ||
mech.equals("DIGEST-MD5") ||
mech.equals("CRAM-MD5") ||
mech.equals("GSSAPI") ) {
Log.debug("SASLAuthentication: Added "+mech+" to mech list");
mechanisms.add(mech);
}
}
if(getSupportedMechanisms().contains("GSSAPI")) {
if(JiveGlobals.getXMLProperty("sasl.gssapi.config") != null) {
System.setProperty("java.security.krb5.debug", JiveGlobals.getXMLProperty("sasl.gssapi.debug","false"));
System.setProperty("java.security.auth.login.config",JiveGlobals.getXMLProperty("sasl.gssapi.config"));
System.setProperty("javax.security.auth.useSubjectCredsOnly",JiveGlobals.getXMLProperty("sasl.gssapi.useSubjectCredsOnly","false"));
} else {
//Not configured, remove the option.
Log.debug("SASLAuthentication: Removed GSSAPI from mech list");
mechanisms.remove("GSSAPI");
}
}
}
return mechanisms;
}
} }
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