/* * Copyright (c) 2010, 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.Client;import com.sun.security.ntlm.NTLMException;import java.io.IOException;import java.net.InetAddress;import java.net.UnknownHostException;import java.util.Map;import java.util.Random;import javax.security.auth.callback.Callback; import javax.security.sasl.*;import javax.security.auth.callback.CallbackHandler;import javax.security.auth.callback.NameCallback;import javax.security.auth.callback.PasswordCallback;import javax.security.auth.callback.UnsupportedCallbackException; /** * Required callbacks: * - RealmCallback * handle can provide domain info for authentication, optional * - NameCallback * handler must enter username to use for authentication * - PasswordCallback * handler must enter password for username to use for authentication * * Environment properties that affect behavior of 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 use; can be: * 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 * still not specified, use default value "LMv2/NTLMv2". * * com.sun.security.sasl.ntlm.random * java.util.Random, the nonce source to be used in NTLM v2 or NTLM v1 with * Client Challenge. Default null, an internal java.util.Random object * will be used * * Negotiated Properties: * * javax.security.sasl.qop * Always "auth" * * com.sun.security.sasl.html.domain * The domain for the user, provided by the server * * @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a> * - Simple Authentication and Security Layer (SASL) * */final class NTLMClient implements SaslClient { private static final String NTLM_VERSION = "com.sun.security.sasl.ntlm.version"; private static final String NTLM_RANDOM = "com.sun.security.sasl.ntlm.random"; 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 final Client client; private final String mech; private final Random random; private int step = 0; // 0-start,1-nego,2-auth,3-done /** * @param mech non-null * @param authorizationId can be null or empty and ignored * @param protocol non-null for Sasl, useless for NTLM * @param serverName non-null for Sasl, but can be null for NTLM * @param props can be null * @param cbh can be null for Sasl, but will throw NPE for NTLM * @throws SaslException */ NTLMClient(String mech, String authzid, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException { this.mech = mech; String version = null; Random rtmp = null; String hostname = null; if (props != null) { String qop = (String)props.get(Sasl.QOP); if (qop != null && !qop.equals("auth")) { throw new SaslException("NTLM only support auth"); } version = (String)props.get(NTLM_VERSION); rtmp = (Random)props.get(NTLM_RANDOM); hostname = (String)props.get(NTLM_HOSTNAME); } this.random = rtmp != null ? rtmp : new Random(); if (version == null) { version = System.getProperty("ntlm.version"); } RealmCallback dcb = (serverName != null && !serverName.isEmpty())? new RealmCallback("Realm: ", serverName) : new RealmCallback("Realm: "); NameCallback ncb = (authzid != null && !authzid.isEmpty()) ? new NameCallback("User name: ", authzid) : new NameCallback("User name: "); PasswordCallback pcb = new PasswordCallback("Password: ", false); try { cbh.handle(new Callback[] {dcb, ncb, pcb}); } catch (UnsupportedCallbackException e) { throw new SaslException("NTLM: Cannot perform callback to " + "acquire realm, username or password", e); } catch (IOException e) { throw new SaslException( "NTLM: Error acquiring realm, username or password", e); } if (hostname == null) { try { hostname = InetAddress.getLocalHost().getCanonicalHostName(); } catch (UnknownHostException e) { hostname = "localhost"; } } try { client = new Client(version, hostname, ncb.getName(), dcb.getText(), pcb.getPassword()); } catch (NTLMException ne) { throw new SaslException( "NTLM: Invalid version string: " + version, ne); } } @Override public String getMechanismName() { return mech; } @Override public boolean isComplete() { return step >= 2; } @Override public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { throw new UnsupportedOperationException("Not supported."); } @Override public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { throw new UnsupportedOperationException("Not supported."); } @Override public Object getNegotiatedProperty(String propName) { if (propName.equals(Sasl.QOP)) { return "auth"; } else if (propName.equals(NTLM_DOMAIN)) { return client.getDomain(); } else { return null; } } @Override public void dispose() throws SaslException { client.dispose(); } @Override public boolean hasInitialResponse() { return true; } @Override public byte[] evaluateChallenge(byte[] challenge) throws SaslException { step++; if (step == 1) { return client.type1(); } else { try { byte[] nonce = new byte[8]; random.nextBytes(nonce); return client.type3(challenge, nonce); } catch (NTLMException ex) { throw new SaslException("Type3 creation failed", ex); } } }}
/*
* Copyright (c) 2010, 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.Client;
import com.sun.security.ntlm.NTLMException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Random;
import javax.security.auth.callback.Callback;
import javax.security.sasl.*;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
/**
* Required callbacks:
* - RealmCallback
* handle can provide domain info for authentication, optional
* - NameCallback
* handler must enter username to use for authentication
* - PasswordCallback
* handler must enter password for username to use for authentication
*
* Environment properties that affect behavior of 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 use; can be:
* 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
* still not specified, use default value "LMv2/NTLMv2".
*
* com.sun.security.sasl.ntlm.random
* java.util.Random, the nonce source to be used in NTLM v2 or NTLM v1 with
* Client Challenge. Default null, an internal java.util.Random object
* will be used
*
* Negotiated Properties:
*
* javax.security.sasl.qop
* Always "auth"
*
* com.sun.security.sasl.html.domain
* The domain for the user, provided by the server
*
* @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>
* - Simple Authentication and Security Layer (SASL)
*
*/
final class NTLMClient implements SaslClient {
private static final String NTLM_VERSION =
"com.sun.security.sasl.ntlm.version";
private static final String NTLM_RANDOM =
"com.sun.security.sasl.ntlm.random";
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 final Client client;
private final String mech;
private final Random random;
private int step = 0; // 0-start,1-nego,2-auth,3-done
/**
* @param mech non-null
* @param authorizationId can be null or empty and ignored
* @param protocol non-null for Sasl, useless for NTLM
* @param serverName non-null for Sasl, but can be null for NTLM
* @param props can be null
* @param cbh can be null for Sasl, but will throw NPE for NTLM
* @throws SaslException
*/
NTLMClient(String mech, String authzid, String protocol, String serverName,
Map props, CallbackHandler cbh) throws SaslException {
this.mech = mech;
String version = null;
Random rtmp = null;
String hostname = null;
if (props != null) {
String qop = (String)props.get(Sasl.QOP);
if (qop != null && !qop.equals("auth")) {
throw new SaslException("NTLM only support auth");
}
version = (String)props.get(NTLM_VERSION);
rtmp = (Random)props.get(NTLM_RANDOM);
hostname = (String)props.get(NTLM_HOSTNAME);
}
this.random = rtmp != null ? rtmp : new Random();
if (version == null) {
version = System.getProperty("ntlm.version");
}
RealmCallback dcb = (serverName != null && !serverName.isEmpty())?
new RealmCallback("Realm: ", serverName) :
new RealmCallback("Realm: ");
NameCallback ncb = (authzid != null && !authzid.isEmpty()) ?
new NameCallback("User name: ", authzid) :
new NameCallback("User name: ");
PasswordCallback pcb =
new PasswordCallback("Password: ", false);
try {
cbh.handle(new Callback[] {dcb, ncb, pcb});
} catch (UnsupportedCallbackException e) {
throw new SaslException("NTLM: Cannot perform callback to " +
"acquire realm, username or password", e);
} catch (IOException e) {
throw new SaslException(
"NTLM: Error acquiring realm, username or password", e);
}
if (hostname == null) {
try {
hostname = InetAddress.getLocalHost().getCanonicalHostName();
} catch (UnknownHostException e) {
hostname = "localhost";
}
}
try {
client = new Client(version, hostname,
ncb.getName(),
dcb.getText(),
pcb.getPassword());
} catch (NTLMException ne) {
throw new SaslException(
"NTLM: Invalid version string: " + version, ne);
}
}
@Override
public String getMechanismName() {
return mech;
}
@Override
public boolean isComplete() {
return step >= 2;
}
@Override
public byte[] unwrap(byte[] incoming, int offset, int len)
throws SaslException {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public byte[] wrap(byte[] outgoing, int offset, int len)
throws SaslException {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public Object getNegotiatedProperty(String propName) {
if (propName.equals(Sasl.QOP)) {
return "auth";
} else if (propName.equals(NTLM_DOMAIN)) {
return client.getDomain();
} else {
return null;
}
}
@Override
public void dispose() throws SaslException {
client.dispose();
}
@Override
public boolean hasInitialResponse() {
return true;
}
@Override
public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
step++;
if (step == 1) {
return client.type1();
} else {
try {
byte[] nonce = new byte[8];
random.nextBytes(nonce);
return client.type3(challenge, nonce);
} catch (NTLMException ex) {
throw new SaslException("Type3 creation failed", ex);
}
}
}
}