/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
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
/**
* 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;
Params: - mech – not null
- protocol – not null for Sasl, ignored in NTLM
- serverName – not null for Sasl, can be null in NTLM. If non-null,
might be used as domain if not provided in props
- props – can be null
- cbh – can be null for Sasl, already null-checked in factory
Throws:
/**
* @param mech not null
* @param protocol not null for Sasl, ignored in NTLM
* @param serverName not null for Sasl, can be null in NTLM. If non-null,
* might be used as domain if not provided in props
* @param props can be null
* @param cbh can be null for Sasl, already null-checked in factory
* @throws SaslException
*/
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;
}
}