jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java
author weijun
Mon, 30 Aug 2010 14:37:43 +0800
changeset 6517 151856936fd8
child 10336 0bb1999251f8
permissions -rw-r--r--
6911951: NTLM should be a supported Java SASL mechanism Reviewed-by: vinnie, michaelm

/*
 * 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);
            }
        }
    }
}