|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.sun.security.sasl.ntlm; |
|
|
|
import com.sun.security.ntlm.NTLMException; |
|
import com.sun.security.ntlm.Server; |
|
import java.io.IOException; |
|
import java.security.GeneralSecurityException; |
|
import java.util.Map; |
|
import java.util.Random; |
|
import javax.security.auth.callback.Callback; |
|
import javax.security.auth.callback.CallbackHandler; |
|
import javax.security.auth.callback.NameCallback; |
|
import javax.security.auth.callback.PasswordCallback; |
|
import javax.security.auth.callback.UnsupportedCallbackException; |
|
import javax.security.sasl.*; |
|
|
|
/** |
|
* Required callbacks: |
|
* - RealmCallback |
|
* used as key by handler to fetch password, optional |
|
* - NameCallback |
|
* used as key by handler to fetch password |
|
* - PasswordCallback |
|
* handler must enter password for username/realm supplied |
|
* |
|
* Environment properties that affect the implementation: |
|
* |
|
* javax.security.sasl.qop |
|
* String, quality of protection; only "auth" is accepted, default "auth" |
|
* |
|
* com.sun.security.sasl.ntlm.version |
|
* String, name a specific version to accept: |
|
* LM/NTLM: Original NTLM v1 |
|
* LM: Original NTLM v1, LM only |
|
* NTLM: Original NTLM v1, NTLM only |
|
* NTLM2: NTLM v1 with Client Challenge |
|
* LMv2/NTLMv2: NTLM v2 |
|
* LMv2: NTLM v2, LM only |
|
* NTLMv2: NTLM v2, NTLM only |
|
* If not specified, use system property "ntlm.version". If also |
|
* not specified, all versions are accepted. |
|
* |
|
* com.sun.security.sasl.ntlm.domain |
|
* String, the domain of the server, default is server name (fqdn parameter) |
|
* |
|
* com.sun.security.sasl.ntlm.random |
|
* java.util.Random, the nonce source. Default null, an internal |
|
* java.util.Random object will be used |
|
* |
|
* Negotiated Properties: |
|
* |
|
* javax.security.sasl.qop |
|
* Always "auth" |
|
* |
|
* com.sun.security.sasl.ntlm.hostname |
|
* The hostname for the user, provided by the client |
|
* |
|
*/ |
|
|
|
final class NTLMServer implements SaslServer { |
|
|
|
private final static String NTLM_VERSION = |
|
"com.sun.security.sasl.ntlm.version"; |
|
private final static String NTLM_DOMAIN = |
|
"com.sun.security.sasl.ntlm.domain"; |
|
private final static String NTLM_HOSTNAME = |
|
"com.sun.security.sasl.ntlm.hostname"; |
|
private static final String NTLM_RANDOM = |
|
"com.sun.security.sasl.ntlm.random"; |
|
|
|
private final Random random; |
|
private final Server server; |
|
private byte[] nonce; |
|
private int step = 0; |
|
private String authzId; |
|
private final String mech; |
|
private String hostname; |
|
private String target; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
NTLMServer(String mech, String protocol, String serverName, |
|
Map<String, ?> props, final CallbackHandler cbh) |
|
throws SaslException { |
|
|
|
this.mech = mech; |
|
String version = null; |
|
String domain = null; |
|
Random rtmp = null; |
|
|
|
if (props != null) { |
|
domain = (String) props.get(NTLM_DOMAIN); |
|
version = (String)props.get(NTLM_VERSION); |
|
rtmp = (Random)props.get(NTLM_RANDOM); |
|
} |
|
random = rtmp != null ? rtmp : new Random(); |
|
|
|
if (version == null) { |
|
version = System.getProperty("ntlm.version"); |
|
} |
|
if (domain == null) { |
|
domain = serverName; |
|
} |
|
if (domain == null) { |
|
throw new SaslException("Domain must be provided as" |
|
+ " the serverName argument or in props"); |
|
} |
|
|
|
try { |
|
server = new Server(version, domain) { |
|
public char[] getPassword(String ntdomain, String username) { |
|
try { |
|
RealmCallback rcb = |
|
(ntdomain == null || ntdomain.isEmpty()) |
|
? new RealmCallback("Domain: ") |
|
: new RealmCallback("Domain: ", ntdomain); |
|
NameCallback ncb = new NameCallback( |
|
"Name: ", username); |
|
PasswordCallback pcb = new PasswordCallback( |
|
"Password: ", false); |
|
cbh.handle(new Callback[] { rcb, ncb, pcb }); |
|
char[] passwd = pcb.getPassword(); |
|
pcb.clearPassword(); |
|
return passwd; |
|
} catch (IOException ioe) { |
|
return null; |
|
} catch (UnsupportedCallbackException uce) { |
|
return null; |
|
} |
|
} |
|
}; |
|
} catch (NTLMException ne) { |
|
throw new SaslException( |
|
"NTLM: server creation failure", ne); |
|
} |
|
nonce = new byte[8]; |
|
} |
|
|
|
@Override |
|
public String getMechanismName() { |
|
return mech; |
|
} |
|
|
|
@Override |
|
public byte[] evaluateResponse(byte[] response) throws SaslException { |
|
try { |
|
step++; |
|
if (step == 1) { |
|
random.nextBytes(nonce); |
|
return server.type2(response, nonce); |
|
} else { |
|
String[] out = server.verify(response, nonce); |
|
authzId = out[0]; |
|
hostname = out[1]; |
|
target = out[2]; |
|
return null; |
|
} |
|
} catch (NTLMException ex) { |
|
throw new SaslException("NTLM: generate response failure", ex); |
|
} |
|
} |
|
|
|
@Override |
|
public boolean isComplete() { |
|
return step >= 2; |
|
} |
|
|
|
@Override |
|
public String getAuthorizationID() { |
|
if (!isComplete()) { |
|
throw new IllegalStateException("authentication not complete"); |
|
} |
|
return authzId; |
|
} |
|
|
|
@Override |
|
public byte[] unwrap(byte[] incoming, int offset, int len) |
|
throws SaslException { |
|
throw new IllegalStateException("Not supported yet."); |
|
} |
|
|
|
@Override |
|
public byte[] wrap(byte[] outgoing, int offset, int len) |
|
throws SaslException { |
|
throw new IllegalStateException("Not supported yet."); |
|
} |
|
|
|
@Override |
|
public Object getNegotiatedProperty(String propName) { |
|
if (!isComplete()) { |
|
throw new IllegalStateException("authentication not complete"); |
|
} |
|
switch (propName) { |
|
case Sasl.QOP: |
|
return "auth"; |
|
case Sasl.BOUND_SERVER_NAME: |
|
return target; |
|
case NTLM_HOSTNAME: |
|
return hostname; |
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
@Override |
|
public void dispose() throws SaslException { |
|
return; |
|
} |
|
} |