changeset 2871:9be643e70f42

6911951: NTLM should be a supported Java SASL mechanism Reviewed-by: vinnie, michaelm
author weijun
date Mon, 30 Aug 2010 14:37:43 +0800
parents 1470dffe6551
children 6586ab5b79f4
files src/share/classes/com/sun/security/ntlm/Client.java src/share/classes/com/sun/security/ntlm/NTLM.java src/share/classes/com/sun/security/ntlm/NTLMException.java src/share/classes/com/sun/security/ntlm/Server.java src/share/classes/com/sun/security/ntlm/Version.java src/share/classes/com/sun/security/sasl/Provider.java src/share/classes/com/sun/security/sasl/ntlm/FactoryImpl.java src/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java src/solaris/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java test/com/sun/security/sasl/ntlm/NTLMTest.java
diffstat 11 files changed, 1999 insertions(+), 218 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/ntlm/Client.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,212 @@
+/*
+ * 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.ntlm;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * The NTLM client. Not multi-thread enabled.<p>
+ * Example:
+ * <pre>
+ * Client client = new Client(null, "host", "dummy",
+ *       "REALM", "t0pSeCr3t".toCharArray());
+ * byte[] type1 = client.type1();
+ * // Send type1 to server and receive response as type2
+ * byte[] type3 = client.type3(type2, nonce);
+ * // Send type3 to server
+ * </pre>
+ */
+public final class Client extends NTLM {
+    final private String hostname;
+    final private String username;
+
+    private String domain;    // might be updated by Type 2 msg
+    private byte[] pw1, pw2;
+
+    /**
+     * Creates an NTLM Client instance.
+     * @param version the NTLM version to use, which can be:
+     * <ul>
+     * <li>LM/NTLM: Original NTLM v1
+     * <li>LM: Original NTLM v1, LM only
+     * <li>NTLM: Original NTLM v1, NTLM only
+     * <li>NTLM2: NTLM v1 with Client Challenge
+     * <li>LMv2/NTLMv2: NTLM v2
+     * <li>LMv2: NTLM v2, LM only
+     * <li>NTLMv2: NTLM v2, NTLM only
+     * </ul>
+     * If null, "LMv2/NTLMv2" will be used.
+     * @param hostname hostname of the client, can be null
+     * @param username username to be authenticated, must not be null
+     * @param domain domain of {@code username}, can be null
+     * @param password password for {@code username}, must not be not null.
+     * This method does not make any modification to this parameter, it neither
+     * needs to access the content of this parameter after this method call,
+     * so you are free to modify or nullify this parameter after this call.
+     * @throws NullPointerException if {@code username} or {@code password} is null.
+     * @throws NTLMException if {@code version} is illegal
+     */
+    public Client(String version, String hostname, String username,
+            String domain, char[] password) throws NTLMException {
+        super(version);
+        if ((username == null || password == null)) {
+            throw new NullPointerException("username/password cannot be null");
+        }
+        this.hostname = hostname;
+        this.username = username;
+        this.domain = domain;
+        this.pw1 = getP1(password);
+        this.pw2 = getP2(password);
+        debug("NTLM Client: (h,u,t,version(v)) = (%s,%s,%s,%s(%s))\n",
+                    hostname, username, domain, version, v.toString());
+    }
+
+    /**
+     * Generates the Type 1 message
+     * @return the message generated
+     */
+    public byte[] type1() {
+        Writer p = new Writer(1, 32);
+        int flags = 0x8203;
+        if (hostname != null) {
+            flags |= 0x2000;
+        }
+        if (domain != null) {
+            flags |= 0x1000;
+        }
+        if (v != Version.NTLM) {
+            flags |= 0x80000;
+        }
+        p.writeInt(12, flags);
+        p.writeSecurityBuffer(24, hostname, false);
+        p.writeSecurityBuffer(16, domain, false);
+        debug("NTLM Client: Type 1 created\n");
+        debug(p.getBytes());
+        return p.getBytes();
+    }
+
+    /**
+     * Generates the Type 3 message
+     * @param type2 the responding Type 2 message from server, must not be null
+     * @param nonce random 8-byte array to be used in message generation,
+     * must not be null except for original NTLM v1
+     * @return the message generated
+     * @throws NullPointerException if {@code type2} or {@code nonce} is null
+     * for NTLM v1.
+     * @throws NTLMException if the incoming message is invalid
+     */
+    public byte[] type3(byte[] type2, byte[] nonce) throws NTLMException {
+        if (type2 == null || (v != Version.NTLM && nonce == null)) {
+            throw new NullPointerException("type2 and nonce cannot be null");
+        }
+        debug("NTLM Client: Type 2 received\n");
+        debug(type2);
+        Reader r = new Reader(type2);
+        byte[] challenge = r.readBytes(24, 8);
+        int inputFlags = r.readInt(20);
+        boolean unicode = (inputFlags & 1) == 1;
+        String domainFromServer = r.readSecurityBuffer(12, unicode);
+        if (domainFromServer != null) {
+            domain = domainFromServer;
+        }
+        if (domain == null) {
+            throw new NTLMException(NTLMException.NO_DOMAIN_INFO,
+                    "No domain info");
+        }
+
+        int flags = 0x88200 | (inputFlags & 3);
+        Writer p = new Writer(3, 64);
+        byte[] lm = null, ntlm = null;
+
+        p.writeSecurityBuffer(28, domain, unicode);
+        p.writeSecurityBuffer(36, username, unicode);
+        p.writeSecurityBuffer(44, hostname, unicode);
+
+        if (v == Version.NTLM) {
+            byte[] lmhash = calcLMHash(pw1);
+            byte[] nthash = calcNTHash(pw2);
+            if (writeLM) lm = calcResponse (lmhash, challenge);
+            if (writeNTLM) ntlm = calcResponse (nthash, challenge);
+        } else if (v == Version.NTLM2) {
+            byte[] nthash = calcNTHash(pw2);
+            lm = ntlm2LM(nonce);
+            ntlm = ntlm2NTLM(nthash, nonce, challenge);
+        } else {
+            byte[] nthash = calcNTHash(pw2);
+            if (writeLM) lm = calcV2(nthash,
+                    username.toUpperCase(Locale.US)+domain, nonce, challenge);
+            if (writeNTLM) {
+                byte[] alist = type2.length > 48 ?
+                    r.readSecurityBuffer(40) : new byte[0];
+                byte[] blob = new byte[32+alist.length];
+                System.arraycopy(new byte[]{1,1,0,0,0,0,0,0}, 0, blob, 0, 8);
+                // TS
+                byte[] time = BigInteger.valueOf(new Date().getTime())
+                        .add(new BigInteger("11644473600000"))
+                        .multiply(BigInteger.valueOf(10000))
+                        .toByteArray();
+                for (int i=0; i<time.length; i++) {
+                    blob[8+time.length-i-1] = time[i];
+                }
+                System.arraycopy(nonce, 0, blob, 16, 8);
+                System.arraycopy(new byte[]{0,0,0,0}, 0, blob, 24, 4);
+                System.arraycopy(alist, 0, blob, 28, alist.length);
+                System.arraycopy(new byte[]{0,0,0,0}, 0,
+                        blob, 28+alist.length, 4);
+                ntlm = calcV2(nthash, username.toUpperCase(Locale.US)+domain,
+                        blob, challenge);
+            }
+        }
+        p.writeSecurityBuffer(12, lm);
+        p.writeSecurityBuffer(20, ntlm);
+        p.writeSecurityBuffer(52, new byte[0]);
+
+        p.writeInt(60, flags);
+        debug("NTLM Client: Type 3 created\n");
+        debug(p.getBytes());
+        return p.getBytes();
+    }
+
+    /**
+     * Returns the domain value provided by server after the authentication
+     * is complete, or the domain value provided by the client before it.
+     * @return the domain
+     */
+    public String getDomain() {
+        return domain;
+    }
+
+    /**
+     * Disposes any password-derived information.
+     */
+    public void dispose() {
+        Arrays.fill(pw1, (byte)0);
+        Arrays.fill(pw2, (byte)0);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/ntlm/NTLM.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,426 @@
+/*
+ * 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.ntlm;
+
+import static com.sun.security.ntlm.Version.*;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * NTLM authentication implemented according to MS-NLMP, version 12.1
+ * @since 1.7
+ */
+class NTLM {
+
+    private final SecretKeyFactory fac;
+    private final Cipher cipher;
+    private final MessageDigest md4;
+    private final Mac hmac;
+    private final MessageDigest md5;
+    private static final boolean DEBUG =
+            System.getProperty("ntlm.debug") != null;
+
+    final Version v;
+
+    final boolean writeLM;
+    final boolean writeNTLM;
+
+    protected NTLM(String version) throws NTLMException {
+        if (version == null) version = "LMv2/NTLMv2";
+        switch (version) {
+            case "LM": v = NTLM; writeLM = true; writeNTLM = false; break;
+            case "NTLM": v = NTLM; writeLM = false; writeNTLM = true; break;
+            case "LM/NTLM": v = NTLM; writeLM = writeNTLM = true; break;
+            case "NTLM2": v = NTLM2; writeLM = writeNTLM = true; break;
+            case "LMv2": v = NTLMv2; writeLM = true; writeNTLM = false; break;
+            case "NTLMv2": v = NTLMv2; writeLM = false; writeNTLM = true; break;
+            case "LMv2/NTLMv2": v = NTLMv2; writeLM = writeNTLM = true; break;
+            default: throw new NTLMException(NTLMException.BAD_VERSION,
+                    "Unknown version " + version);
+        }
+        try {
+            fac = SecretKeyFactory.getInstance ("DES");
+            cipher = Cipher.getInstance ("DES/ECB/NoPadding");
+            md4 = sun.security.provider.MD4.getInstance();
+            hmac = Mac.getInstance("HmacMD5");
+            md5 = MessageDigest.getInstance("MD5");
+        } catch (NoSuchPaddingException e) {
+            throw new AssertionError();
+        } catch (NoSuchAlgorithmException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Prints out a formatted string, called in various places inside then NTLM
+     * implementation for debugging/logging purposes. When the system property
+     * "ntlm.debug" is set, <code>System.out.printf(format, args)</code> is
+     * called. This method is designed to be overridden by child classes to
+     * match their own debugging/logging mechanisms.
+     * @param format a format string
+     * @param args the arguments referenced by <code>format</code>
+     * @see java.io.PrintStream#printf(java.lang.String, java.lang.Object[])
+     */
+    public void debug(String format, Object... args) {
+        if (DEBUG) {
+            System.out.printf(format, args);
+        }
+    }
+
+    /**
+     * Prints out the content of a byte array, called in various places inside
+     * the NTLM implementation for debugging/logging purposes. When the system
+     * property "ntlm.debug" is set, the hexdump of the array is printed into
+     * System.out. This method is designed to be overridden by child classes to
+     * match their own debugging/logging mechanisms.
+     * @param bytes the byte array to print out
+     */
+    public void debug(byte[] bytes) {
+        if (DEBUG) {
+            try {
+                new sun.misc.HexDumpEncoder().encodeBuffer(bytes, System.out);
+            } catch (IOException ioe) {
+                // Impossible
+            }
+        }
+    }
+
+    /**
+     * Reading an NTLM packet
+     */
+    static class Reader {
+
+        private final byte[] internal;
+
+        Reader(byte[] data) {
+            internal = data;
+        }
+
+        int readInt(int offset) throws NTLMException {
+            try {
+                return internal[offset] & 0xff +
+                        (internal[offset+1] & 0xff << 8) +
+                        (internal[offset+2] & 0xff << 16) +
+                        (internal[offset+3] & 0xff << 24);
+            } catch (ArrayIndexOutOfBoundsException ex) {
+                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
+                        "Input message incorrect size");
+            }
+        }
+
+        int readShort(int offset) throws NTLMException {
+            try {
+                return internal[offset] & 0xff +
+                        (internal[offset+1] & 0xff << 8);
+            } catch (ArrayIndexOutOfBoundsException ex) {
+                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
+                        "Input message incorrect size");
+            }
+        }
+
+        byte[] readBytes(int offset, int len) throws NTLMException {
+            try {
+                return Arrays.copyOfRange(internal, offset, offset + len);
+            } catch (ArrayIndexOutOfBoundsException ex) {
+                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
+                        "Input message incorrect size");
+            }
+        }
+
+        byte[] readSecurityBuffer(int offset) throws NTLMException {
+            int pos = readInt(offset+4);
+            if (pos == 0) return null;
+            try {
+                return Arrays.copyOfRange(
+                        internal, pos, pos + readShort(offset));
+            } catch (ArrayIndexOutOfBoundsException ex) {
+                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
+                        "Input message incorrect size");
+            }
+        }
+
+        String readSecurityBuffer(int offset, boolean unicode)
+                throws NTLMException {
+            byte[] raw = readSecurityBuffer(offset);
+            try {
+                return raw == null ? null : new String(
+                        raw, unicode ? "UnicodeLittleUnmarked" : "ISO8859_1");
+            } catch (UnsupportedEncodingException ex) {
+                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
+                        "Invalid input encoding");
+            }
+        }
+    }
+
+    /**
+     * Writing an NTLM packet
+     */
+    static class Writer {
+
+        private byte[] internal;    // buffer
+        private int current;        // current written content interface buffer
+
+        /**
+         * Starts writing a NTLM packet
+         * @param type NEGOTIATE || CHALLENGE || AUTHENTICATE
+         * @param len the base length, without security buffers
+         */
+        Writer(int type, int len) {
+            assert len < 256;
+            internal = new byte[256];
+            current = len;
+            System.arraycopy (
+                    new byte[] {'N','T','L','M','S','S','P',0,(byte)type},
+                    0, internal, 0, 9);
+        }
+
+        void writeShort(int offset, int number) {
+            internal[offset] = (byte)(number);
+            internal[offset+1] = (byte)(number >> 8);
+        }
+
+        void writeInt(int offset, int number) {
+            internal[offset] = (byte)(number);
+            internal[offset+1] = (byte)(number >> 8);
+            internal[offset+2] = (byte)(number >> 16);
+            internal[offset+3] = (byte)(number >> 24);
+        }
+
+        void writeBytes(int offset, byte[] data) {
+            System.arraycopy(data, 0, internal, offset, data.length);
+        }
+
+        void writeSecurityBuffer(int offset, byte[] data) {
+            if (data == null) {
+                writeShort(offset+4, current);
+            } else {
+                int len = data.length;
+                if (current + len > internal.length) {
+                    internal = Arrays.copyOf(internal, current + len + 256);
+                }
+                writeShort(offset, len);
+                writeShort(offset+2, len);
+                writeShort(offset+4, current);
+                System.arraycopy(data, 0, internal, current, len);
+                current += len;
+            }
+        }
+
+        void writeSecurityBuffer(int offset, String str, boolean unicode) {
+            try {
+                writeSecurityBuffer(offset, str == null ? null : str.getBytes(
+                        unicode ? "UnicodeLittleUnmarked" : "ISO8859_1"));
+            } catch (UnsupportedEncodingException ex) {
+                assert false;
+            }
+        }
+
+        byte[] getBytes() {
+            return Arrays.copyOf(internal, current);
+        }
+    }
+
+    // LM/NTLM
+
+    /* Convert a 7 byte array to an 8 byte array (for a des key with parity)
+     * input starts at offset off
+     */
+    byte[] makeDesKey (byte[] input, int off) {
+        int[] in = new int [input.length];
+        for (int i=0; i<in.length; i++ ) {
+            in[i] = input[i]<0 ? input[i]+256: input[i];
+        }
+        byte[] out = new byte[8];
+        out[0] = (byte)in[off+0];
+        out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
+        out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
+        out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
+        out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
+        out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
+        out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
+        out[7] = (byte)((in[off+6] << 1) & 0xFF);
+        return out;
+    }
+
+    byte[] calcLMHash (byte[] pwb) {
+        byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
+        byte[] pwb1 = new byte [14];
+        int len = pwb.length;
+        if (len > 14)
+            len = 14;
+        System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
+
+        try {
+            DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
+            DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
+
+            SecretKey key1 = fac.generateSecret (dks1);
+            SecretKey key2 = fac.generateSecret (dks2);
+            cipher.init (Cipher.ENCRYPT_MODE, key1);
+            byte[] out1 = cipher.doFinal (magic, 0, 8);
+            cipher.init (Cipher.ENCRYPT_MODE, key2);
+            byte[] out2 = cipher.doFinal (magic, 0, 8);
+            byte[] result = new byte [21];
+            System.arraycopy (out1, 0, result, 0, 8);
+            System.arraycopy (out2, 0, result, 8, 8);
+            return result;
+        } catch (InvalidKeyException ive) {
+            // Will not happen, all key material are 8 bytes
+            assert false;
+        } catch (InvalidKeySpecException ikse) {
+            // Will not happen, we only feed DESKeySpec to DES factory
+            assert false;
+        } catch (IllegalBlockSizeException ibse) {
+            // Will not happen, we encrypt 8 bytes
+            assert false;
+        } catch (BadPaddingException bpe) {
+            // Will not happen, this is encryption
+            assert false;
+        }
+        return null;    // will not happen, we returned already
+    }
+
+    byte[] calcNTHash (byte[] pw) {
+        byte[] out = md4.digest (pw);
+        byte[] result = new byte [21];
+        System.arraycopy (out, 0, result, 0, 16);
+        return result;
+    }
+
+    /* key is a 21 byte array. Split it into 3 7 byte chunks,
+     * Convert each to 8 byte DES keys, encrypt the text arg with
+     * each key and return the three results in a sequential []
+     */
+    byte[] calcResponse (byte[] key, byte[] text) {
+        try {
+            assert key.length == 21;
+            DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
+            DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
+            DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
+            SecretKey key1 = fac.generateSecret(dks1);
+            SecretKey key2 = fac.generateSecret(dks2);
+            SecretKey key3 = fac.generateSecret(dks3);
+            cipher.init(Cipher.ENCRYPT_MODE, key1);
+            byte[] out1 = cipher.doFinal(text, 0, 8);
+            cipher.init(Cipher.ENCRYPT_MODE, key2);
+            byte[] out2 = cipher.doFinal(text, 0, 8);
+            cipher.init(Cipher.ENCRYPT_MODE, key3);
+            byte[] out3 = cipher.doFinal(text, 0, 8);
+            byte[] result = new byte[24];
+            System.arraycopy(out1, 0, result, 0, 8);
+            System.arraycopy(out2, 0, result, 8, 8);
+            System.arraycopy(out3, 0, result, 16, 8);
+            return result;
+        } catch (IllegalBlockSizeException ex) {    // None will happen
+            assert false;
+        } catch (BadPaddingException ex) {
+            assert false;
+        } catch (InvalidKeySpecException ex) {
+            assert false;
+        } catch (InvalidKeyException ex) {
+            assert false;
+        }
+        return null;
+    }
+
+    // LMv2/NTLMv2
+
+    byte[] hmacMD5(byte[] key, byte[] text) {
+        try {
+            SecretKeySpec skey =
+                    new SecretKeySpec(Arrays.copyOf(key, 16), "HmacMD5");
+            hmac.init(skey);
+            return hmac.doFinal(text);
+        } catch (InvalidKeyException ex) {
+            assert false;
+        } catch (RuntimeException e) {
+            assert false;
+        }
+        return null;
+    }
+
+    byte[] calcV2(byte[] nthash, String text, byte[] blob, byte[] challenge) {
+        try {
+            byte[] ntlmv2hash = hmacMD5(nthash,
+                    text.getBytes("UnicodeLittleUnmarked"));
+            byte[] cn = new byte[blob.length+8];
+            System.arraycopy(challenge, 0, cn, 0, 8);
+            System.arraycopy(blob, 0, cn, 8, blob.length);
+            byte[] result = new byte[16+blob.length];
+            System.arraycopy(hmacMD5(ntlmv2hash, cn), 0, result, 0, 16);
+            System.arraycopy(blob, 0, result, 16, blob.length);
+            return result;
+        } catch (UnsupportedEncodingException ex) {
+            assert false;
+        }
+        return null;
+    }
+
+    // NTLM2 LM/NTLM
+
+    static byte[] ntlm2LM(byte[] nonce) {
+        return Arrays.copyOf(nonce, 24);
+    }
+
+    byte[] ntlm2NTLM(byte[] ntlmHash, byte[] nonce, byte[] challenge) {
+        byte[] b = Arrays.copyOf(challenge, 16);
+        System.arraycopy(nonce, 0, b, 8, 8);
+        byte[] sesshash = Arrays.copyOf(md5.digest(b), 8);
+        return calcResponse(ntlmHash, sesshash);
+    }
+
+    // Password in ASCII and UNICODE
+
+    static byte[] getP1(char[] password) {
+        try {
+            return new String(password).toUpperCase().getBytes("ISO8859_1");
+        } catch (UnsupportedEncodingException ex) {
+            return null;
+        }
+    }
+
+    static byte[] getP2(char[] password) {
+        try {
+            return new String(password).getBytes("UnicodeLittleUnmarked");
+        } catch (UnsupportedEncodingException ex) {
+            return null;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/ntlm/NTLMException.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,88 @@
+/*
+ * 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.ntlm;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * An NTLM-related Exception
+ */
+public final class NTLMException extends GeneralSecurityException {
+
+    /**
+     * If the incoming packet is invalid.
+     */
+    public final static int PACKET_READ_ERROR = 1;
+
+    /**
+     * If the client cannot get a domain value from the server and the
+     * caller has not provided one.
+     */
+    public final static int NO_DOMAIN_INFO = 2;
+
+    /**
+     * If the domain provided by the client does not match the one received
+     * from server.
+     */
+    //public final static int DOMAIN_UNMATCH = 3;
+
+    /**
+     * If the client name is not found on server's user database.
+     */
+    public final static int USER_UNKNOWN = 3;
+
+    /**
+     * If authentication fails.
+     */
+    public final static int AUTH_FAILED = 4;
+
+    /**
+     * If an illegal version string is provided.
+     */
+    public final static int BAD_VERSION = 5;
+
+    private int errorCode;
+
+    /**
+     * Constructs an NTLMException object.
+     * @param errorCode the error code, which can be retrieved by
+     * the {@link #errorCode() } method.
+     * @param msg the string message, which can be retrived by
+     * the {@link Exception#getMessage() } method.
+     */
+    public NTLMException(int errorCode, String msg) {
+        super(msg);
+        this.errorCode = errorCode;
+    }
+
+    /**
+     * Returns the error code associated with this NTLMException.
+     * @return the error code
+     */
+    public int errorCode() {
+        return errorCode;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/ntlm/Server.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,205 @@
+/*
+ * 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.ntlm;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * The NTLM server, not multi-thread enabled.<p>
+ * Example:
+ * <pre>
+ * Server server = new Server(null, "REALM") {
+ *     public char[] getPassword(String ntdomain, String username) {
+ *         switch (username) {
+ *             case "dummy": return "t0pSeCr3t".toCharArray();
+ *             case "guest": return "".toCharArray();
+ *             default: return null;
+ *         }
+ *     }
+ * };
+ * // Receive client request as type1
+ * byte[] type2 = server.type2(type1, nonce);
+ * // Send type2 to client and receive type3
+ * verify(type3, nonce);
+ * </pre>
+ */
+public abstract class Server extends NTLM {
+    final private String domain;
+    final private boolean allVersion;
+    /**
+     * Creates a Server instance.
+     * @param version the NTLM version to use, which can be:
+     * <ul>
+     * <li>NTLM: Original NTLM v1
+     * <li>NTLM2: NTLM v1 with Client Challenge
+     * <li>NTLMv2: NTLM v2
+     * </ul>
+     * If null, all versions will be supported. Please note that unless NTLM2
+     * is selected, authentication succeeds if one of LM (or LMv2) or
+     * NTLM (or NTLMv2) is verified.
+     * @param domain the domain, must not be null
+     * @throws NullPointerException if {@code domain} is null.
+     */
+    public Server(String version, String domain) throws NTLMException {
+        super(version);
+        if (domain == null) {
+            throw new NullPointerException("domain cannot be null");
+        }
+        this.allVersion = (version == null);
+        this.domain = domain;
+        debug("NTLM Server: (t,version) = (%s,%s)\n", domain, version);
+    }
+
+    /**
+     * Generates the Type 2 message
+     * @param type1 the Type1 message received, must not be null
+     * @param nonce the random 8-byte array to be used in message generation,
+     * must not be null
+     * @return the message generated
+     * @throws NullPointerException if type1 or nonce is null
+     * @throws NTLMException if the incoming message is invalid
+     */
+    public byte[] type2(byte[] type1, byte[] nonce) {
+        if (nonce == null) {
+            throw new NullPointerException("nonce cannot be null");
+        }
+        debug("NTLM Server: Type 1 received\n");
+        if (type1 != null) debug(type1);
+        Writer p = new Writer(2, 32);
+        int flags = 0x80205;
+        p.writeSecurityBuffer(12, domain, true);
+        p.writeInt(20, flags);
+        p.writeBytes(24, nonce);
+        debug("NTLM Server: Type 2 created\n");
+        debug(p.getBytes());
+        return p.getBytes();
+    }
+
+    /**
+     * Verifies the Type3 message received from client and returns
+     * various negotiated information.
+     * @param type3 the incoming Type3 message from client, must not be null
+     * @param nonce the same nonce provided in {@link #type2}, must not be null
+     * @return username and hostname of the client in a byte array
+     * @throws NullPointerException if {@code type3} or {@code nonce} is null
+     * @throws NTLMException if the incoming message is invalid
+     */
+    public String[] verify(byte[] type3, byte[] nonce)
+            throws NTLMException {
+        if (type3 == null || nonce == null) {
+            throw new NullPointerException("type1 or nonce cannot be null");
+        }
+        debug("NTLM Server: Type 3 received\n");
+        if (type3 != null) debug(type3);
+        Reader r = new Reader(type3);
+        String username = r.readSecurityBuffer(36, true);
+        String hostname = r.readSecurityBuffer(44, true);
+        String incomingDomain = r.readSecurityBuffer(28, true);
+        /*if (incomingDomain != null && !incomingDomain.equals(domain)) {
+            throw new NTLMException(NTLMException.DOMAIN_UNMATCH,
+                    "Wrong domain: " + incomingDomain +
+                    " vs " + domain); // Needed?
+        }*/
+        boolean verified = false;
+        char[] password = getPassword(domain, username);
+        if (password == null) {
+            throw new NTLMException(NTLMException.USER_UNKNOWN,
+                    "Unknown user");
+        }
+        byte[] incomingLM = r.readSecurityBuffer(12);
+        byte[] incomingNTLM = r.readSecurityBuffer(20);
+
+        if (!verified && (allVersion || v == Version.NTLM)) {
+            if (incomingLM.length > 0) {
+                byte[] pw1 = getP1(password);
+                byte[] lmhash = calcLMHash(pw1);
+                byte[] lmresponse = calcResponse (lmhash, nonce);
+                if (Arrays.equals(lmresponse, incomingLM)) {
+                    verified = true;
+                }
+            }
+            if (incomingNTLM.length > 0) {
+                byte[] pw2 = getP2(password);
+                byte[] nthash = calcNTHash(pw2);
+                byte[] ntresponse = calcResponse (nthash, nonce);
+                if (Arrays.equals(ntresponse, incomingNTLM)) {
+                    verified = true;
+                }
+            }
+            debug("NTLM Server: verify using NTLM: " + verified  + "\n");
+        }
+        if (!verified && (allVersion || v == Version.NTLM2)) {
+            byte[] pw2 = getP2(password);
+            byte[] nthash = calcNTHash(pw2);
+            byte[] clientNonce = Arrays.copyOf(incomingLM, 8);
+            byte[] ntlmresponse = ntlm2NTLM(nthash, clientNonce, nonce);
+            if (Arrays.equals(incomingNTLM, ntlmresponse)) {
+                verified = true;
+            }
+            debug("NTLM Server: verify using NTLM2: " + verified + "\n");
+        }
+        if (!verified && (allVersion || v == Version.NTLMv2)) {
+            byte[] pw2 = getP2(password);
+            byte[] nthash = calcNTHash(pw2);
+            if (incomingLM.length > 0) {
+                byte[] clientNonce = Arrays.copyOfRange(
+                        incomingLM, 16, incomingLM.length);
+                byte[] lmresponse = calcV2(nthash,
+                        username.toUpperCase(Locale.US)+incomingDomain,
+                        clientNonce, nonce);
+                if (Arrays.equals(lmresponse, incomingLM)) {
+                    verified = true;
+                }
+            }
+            if (incomingNTLM.length > 0) {
+                byte[] clientBlob = Arrays.copyOfRange(
+                        incomingNTLM, 16, incomingNTLM.length);
+                byte[] ntlmresponse = calcV2(nthash,
+                        username.toUpperCase(Locale.US)+incomingDomain,
+                        clientBlob, nonce);
+                if (Arrays.equals(ntlmresponse, incomingNTLM)) {
+                    verified = true;
+                }
+            }
+            debug("NTLM Server: verify using NTLMv2: " + verified + "\n");
+        }
+        if (!verified) {
+            throw new NTLMException(NTLMException.AUTH_FAILED,
+                    "None of LM and NTLM verified");
+        }
+        return new String[] {username, hostname};
+    }
+
+    /**
+     * Retrieves the password for a given user. This method should be
+     * overridden in a concrete class.
+     * @param domain can be null
+     * @param username must not be null
+     * @return the password for the user, or null if unknown
+     */
+    public abstract char[] getPassword(String domain, String username);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/ntlm/Version.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,30 @@
+/*
+ * 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.ntlm;
+
+enum Version {
+    NTLM, NTLM2, NTLMv2
+}
--- a/src/share/classes/com/sun/security/sasl/Provider.java	Sat Aug 28 12:15:52 2010 -0700
+++ b/src/share/classes/com/sun/security/sasl/Provider.java	Mon Aug 30 14:37:43 2010 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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
@@ -35,10 +35,12 @@
  * - CRAM-MD5
  * - DIGEST-MD5
  * - GSSAPI/Kerberos v5
+ * - NTLM
  * And server support for
  * - CRAM-MD5
  * - DIGEST-MD5
  * - GSSAPI/Kerberos v5
+ * - NTLM
  */
 
 public final class Provider extends java.security.Provider {
@@ -47,8 +49,8 @@
 
     private static final String info = "Sun SASL provider" +
         "(implements client mechanisms for: " +
-        "DIGEST-MD5, GSSAPI, EXTERNAL, PLAIN, CRAM-MD5;" +
-        " server mechanisms for: DIGEST-MD5, GSSAPI, CRAM-MD5)";
+        "DIGEST-MD5, GSSAPI, EXTERNAL, PLAIN, CRAM-MD5, NTLM;" +
+        " server mechanisms for: DIGEST-MD5, GSSAPI, CRAM-MD5, NTLM)";
 
     public Provider() {
         super("SunSASL", 1.7d, info);
@@ -58,6 +60,8 @@
                 // Client mechanisms
                 put("SaslClientFactory.DIGEST-MD5",
                     "com.sun.security.sasl.digest.FactoryImpl");
+                put("SaslClientFactory.NTLM",
+                    "com.sun.security.sasl.ntlm.FactoryImpl");
                 put("SaslClientFactory.GSSAPI",
                     "com.sun.security.sasl.gsskerb.FactoryImpl");
 
@@ -75,6 +79,8 @@
                     "com.sun.security.sasl.gsskerb.FactoryImpl");
                 put("SaslServerFactory.DIGEST-MD5",
                     "com.sun.security.sasl.digest.FactoryImpl");
+                put("SaslServerFactory.NTLM",
+                    "com.sun.security.sasl.ntlm.FactoryImpl");
                 return null;
             }
         });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/sasl/ntlm/FactoryImpl.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,119 @@
+/*
+ * 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 java.util.Map;
+
+import javax.security.sasl.*;
+import javax.security.auth.callback.CallbackHandler;
+
+import com.sun.security.sasl.util.PolicyUtils;
+
+
+/**
+  * Client and server factory for NTLM SASL client/server mechanisms.
+  * See NTLMClient and NTLMServer for input requirements.
+  *
+  * @since 1.7
+  */
+
+public final class FactoryImpl implements SaslClientFactory,
+SaslServerFactory{
+
+    private static final String myMechs[] = { "NTLM" };
+    private static final int mechPolicies[] = {
+            PolicyUtils.NOPLAINTEXT|PolicyUtils.NOANONYMOUS
+    };
+
+    /**
+      * Empty constructor.
+      */
+    public FactoryImpl() {
+    }
+
+    /**
+     * Returns a new instance of the NTLM SASL client mechanism.
+     * Argument checks are performed in SaslClient's constructor.
+     * @returns a new SaslClient ; otherwise null if unsuccessful.
+     * @throws SaslException If there is an error creating the NTLM
+     * SASL client.
+     */
+    public SaslClient createSaslClient(String[] mechs,
+         String authorizationId, String protocol, String serverName,
+         Map<String,?> props, CallbackHandler cbh)
+         throws SaslException {
+
+         for (int i=0; i<mechs.length; i++) {
+            if (mechs[i].equals("NTLM") &&
+                    PolicyUtils.checkPolicy(mechPolicies[0], props)) {
+
+                return new NTLMClient(mechs[i], authorizationId,
+                    protocol, serverName, props, cbh);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a new instance of the NTLM SASL server mechanism.
+     * Argument checks are performed in SaslServer's constructor.
+     * @returns a new SaslServer ; otherwise null if unsuccessful.
+     * @throws SaslException If there is an error creating the NTLM
+     * SASL server.
+     */
+    public SaslServer createSaslServer(String mech,
+         String protocol, String serverName, Map<String,?> props, CallbackHandler cbh)
+         throws SaslException {
+
+         if (mech.equals("NTLM") &&
+                 PolicyUtils.checkPolicy(mechPolicies[0], props)) {
+             if (props != null) {
+                 String qop = (String)props.get(Sasl.QOP);
+                 if (qop != null && !qop.equals("auth")) {
+                     throw new SaslException("NTLM only support auth");
+                 }
+             }
+             if (cbh == null) {
+                 throw new SaslException(
+                        "Callback handler with support for AuthorizeCallback, "+
+                        "RealmCallback, NameCallback, and PasswordCallback " +
+                        "required");
+             }
+             return new NTLMServer(mech, protocol, serverName, props, cbh);
+         }
+         return null;
+    }
+
+    /**
+      * Returns the authentication mechanisms that this factory can produce.
+      *
+      * @returns String[] {"NTLM"} if policies in env match those of this
+      * factory.
+      */
+    public String[] getMechanismNames(Map<String,?> env) {
+        return PolicyUtils.filterMechs(myMechs, mechPolicies, env);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,231 @@
+/*
+ * 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);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,226 @@
+/*
+ * 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.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 specfied, 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;
+
+    /**
+     * @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, but will throw NPE in auth for NTLM
+     * @throws SaslException
+     */
+    NTLMServer(String mech, String protocol, String serverName,
+            Map 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 NullPointerException("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 = 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: Invalid version string: " + version, 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];
+                return null;
+            }
+        } catch (GeneralSecurityException ex) {
+            throw new SaslException("", ex);
+        }
+    }
+
+    @Override
+    public boolean isComplete() {
+        return step >= 2;
+    }
+
+    @Override
+    public String getAuthorizationID() {
+        return authzId;
+    }
+
+    @Override
+    public byte[] unwrap(byte[] incoming, int offset, int len)
+            throws SaslException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public byte[] wrap(byte[] outgoing, int offset, int len)
+            throws SaslException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Object getNegotiatedProperty(String propName) {
+        if (propName.equals(Sasl.QOP)) {
+            return "auth";
+        } else if (propName.equals(NTLM_HOSTNAME)) {
+            return hostname;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void dispose() throws SaslException {
+        return;
+    }
+}
--- a/src/solaris/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java	Sat Aug 28 12:15:52 2010 -0700
+++ b/src/solaris/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java	Mon Aug 30 14:37:43 2010 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 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
@@ -25,20 +25,14 @@
 
 package sun.net.www.protocol.http.ntlm;
 
+import com.sun.security.ntlm.Client;
+import com.sun.security.ntlm.NTLMException;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
 import java.net.PasswordAuthentication;
 import java.net.UnknownHostException;
 import java.net.URL;
 import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.DESKeySpec;
 
 import sun.net.www.HeaderParser;
 import sun.net.www.protocol.http.AuthenticationInfo;
@@ -72,14 +66,8 @@
  */
 
 public class NTLMAuthentication extends AuthenticationInfo {
-    private static final long serialVersionUID = -2403849171106437142L;
+    private static final long serialVersionUID = 170L;
 
-    private byte[] type1;
-    private byte[] type3;
-
-    private SecretKeyFactory fac;
-    private Cipher cipher;
-    private MessageDigest md4;
     private String hostname;
     private static String defaultDomain; /* Domain to use if not specified by user */
 
@@ -94,53 +82,28 @@
     }
 
     private void init0() {
-        type1 = new byte[256];
-        type3 = new byte[256];
-        System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,1}, 0, type1, 0, 9);
-        type1[12] = (byte) 3;
-        type1[13] = (byte) 0xb2;
-        type1[28] = (byte) 0x20;
-        System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,3}, 0, type3, 0, 9);
-        type3[12] = (byte) 0x18;
-        type3[14] = (byte) 0x18;
-        type3[20] = (byte) 0x18;
-        type3[22] = (byte) 0x18;
-        type3[32] = (byte) 0x40;
-        type3[60] = (byte) 1;
-        type3[61] = (byte) 0x82;
 
-        try {
-            hostname = java.security.AccessController.doPrivileged(
-                new java.security.PrivilegedAction<String>() {
-                public String run() {
-                    String localhost;
-                    try {
-                        localhost = InetAddress.getLocalHost().getHostName().toUpperCase();
-                    } catch (UnknownHostException e) {
-                         localhost = "localhost";
-                    }
-                    return localhost;
+        hostname = java.security.AccessController.doPrivileged(
+            new java.security.PrivilegedAction<String>() {
+            public String run() {
+                String localhost;
+                try {
+                    localhost = InetAddress.getLocalHost().getHostName().toUpperCase();
+                } catch (UnknownHostException e) {
+                     localhost = "localhost";
                 }
-            });
-            int x = hostname.indexOf ('.');
-            if (x != -1) {
-                hostname = hostname.substring (0, x);
+                return localhost;
             }
-            fac = SecretKeyFactory.getInstance ("DES");
-            cipher = Cipher.getInstance ("DES/ECB/NoPadding");
-            md4 = sun.security.provider.MD4.getInstance();
-        } catch (NoSuchPaddingException e) {
-            assert false;
-        } catch (NoSuchAlgorithmException e) {
-            assert false;
+        });
+        int x = hostname.indexOf ('.');
+        if (x != -1) {
+            hostname = hostname.substring (0, x);
         }
     };
 
     PasswordAuthentication pw;
-    String username;
-    String ntdomain;
-    String password;
 
+    Client client;
     /**
      * Create a NTLMAuthentication:
      * Username may be specified as domain<BACKSLASH>username in the application Authenticator.
@@ -156,6 +119,9 @@
     }
 
     private void init (PasswordAuthentication pw) {
+        String username;
+        String ntdomain;
+        char[] password;
         this.pw = pw;
         String s = pw.getUserName();
         int i = s.indexOf ('\\');
@@ -166,8 +132,19 @@
             ntdomain = s.substring (0, i).toUpperCase();
             username = s.substring (i+1);
         }
-        password = new String (pw.getPassword());
+        password = pw.getPassword();
         init0();
+        try {
+            client = new Client(System.getProperty("ntlm.version"), hostname,
+                    username, ntdomain, password);
+        } catch (NTLMException ne) {
+            try {
+                client = new Client(null, hostname, username, ntdomain, password);
+            } catch (NTLMException ne2) {
+                // Will never happen
+                throw new AssertionError("Really?");
+            }
+        }
     }
 
    /**
@@ -240,181 +217,26 @@
         }
     }
 
-    private void copybytes (byte[] dest, int destpos, String src, String enc) {
-        try {
-            byte[] x = src.getBytes(enc);
-            System.arraycopy (x, 0, dest, destpos, x.length);
-        } catch (UnsupportedEncodingException e) {
-            assert false;
-        }
-    }
-
     private String buildType1Msg () {
-        int dlen = ntdomain.length();
-        type1[16]= (byte) (dlen % 256);
-        type1[17]= (byte) (dlen / 256);
-        type1[18] = type1[16];
-        type1[19] = type1[17];
-
-        int hlen = hostname.length();
-        type1[24]= (byte) (hlen % 256);
-        type1[25]= (byte) (hlen / 256);
-        type1[26] = type1[24];
-        type1[27] = type1[25];
-
-        copybytes (type1, 32, hostname, "ISO8859_1");
-        copybytes (type1, hlen+32, ntdomain, "ISO8859_1");
-        type1[20] = (byte) ((hlen+32) % 256);
-        type1[21] = (byte) ((hlen+32) / 256);
-
-        byte[] msg = new byte [32 + hlen + dlen];
-        System.arraycopy (type1, 0, msg, 0, 32 + hlen + dlen);
+        byte[] msg = client.type1();
         String result = "NTLM " + (new B64Encoder()).encode (msg);
         return result;
     }
 
-
-    /* Convert a 7 byte array to an 8 byte array (for a des key with parity)
-     * input starts at offset off
-     */
-    private byte[] makeDesKey (byte[] input, int off) {
-        int[] in = new int [input.length];
-        for (int i=0; i<in.length; i++ ) {
-            in[i] = input[i]<0 ? input[i]+256: input[i];
-        }
-        byte[] out = new byte[8];
-        out[0] = (byte)in[off+0];
-        out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
-        out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
-        out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
-        out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
-        out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
-        out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
-        out[7] = (byte)((in[off+6] << 1) & 0xFF);
-        return out;
-    }
-
-    private byte[] calcLMHash () throws GeneralSecurityException {
-        byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
-        byte[] pwb = password.toUpperCase ().getBytes();
-        byte[] pwb1 = new byte [14];
-        int len = password.length();
-        if (len > 14)
-            len = 14;
-        System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
-
-        DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
-        DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
-
-        SecretKey key1 = fac.generateSecret (dks1);
-        SecretKey key2 = fac.generateSecret (dks2);
-        cipher.init (Cipher.ENCRYPT_MODE, key1);
-        byte[] out1 = cipher.doFinal (magic, 0, 8);
-        cipher.init (Cipher.ENCRYPT_MODE, key2);
-        byte[] out2 = cipher.doFinal (magic, 0, 8);
-
-        byte[] result = new byte [21];
-        System.arraycopy (out1, 0, result, 0, 8);
-        System.arraycopy (out2, 0, result, 8, 8);
-        return result;
-    }
-
-    private byte[] calcNTHash () throws GeneralSecurityException {
-        byte[] pw = null;
-        try {
-            pw = password.getBytes ("UnicodeLittleUnmarked");
-        } catch (UnsupportedEncodingException e) {
-            assert false;
-        }
-        byte[] out = md4.digest (pw);
-        byte[] result = new byte [21];
-        System.arraycopy (out, 0, result, 0, 16);
-        return result;
-    }
-
-    /* key is a 21 byte array. Split it into 3 7 byte chunks,
-     * Convert each to 8 byte DES keys, encrypt the text arg with
-     * each key and return the three results in a sequential []
-     */
-    private byte[] calcResponse (byte[] key, byte[] text)
-    throws GeneralSecurityException {
-        assert key.length == 21;
-        DESKeySpec dks1 = new DESKeySpec (makeDesKey (key, 0));
-        DESKeySpec dks2 = new DESKeySpec (makeDesKey (key, 7));
-        DESKeySpec dks3 = new DESKeySpec (makeDesKey (key, 14));
-        SecretKey key1 = fac.generateSecret (dks1);
-        SecretKey key2 = fac.generateSecret (dks2);
-        SecretKey key3 = fac.generateSecret (dks3);
-        cipher.init (Cipher.ENCRYPT_MODE, key1);
-        byte[] out1 = cipher.doFinal (text, 0, 8);
-        cipher.init (Cipher.ENCRYPT_MODE, key2);
-        byte[] out2 = cipher.doFinal (text, 0, 8);
-        cipher.init (Cipher.ENCRYPT_MODE, key3);
-        byte[] out3 = cipher.doFinal (text, 0, 8);
-        byte[] result = new byte [24];
-        System.arraycopy (out1, 0, result, 0, 8);
-        System.arraycopy (out2, 0, result, 8, 8);
-        System.arraycopy (out3, 0, result, 16, 8);
-        return result;
-    }
-
     private String buildType3Msg (String challenge) throws GeneralSecurityException,
                                                            IOException  {
         /* First decode the type2 message to get the server nonce */
         /* nonce is located at type2[24] for 8 bytes */
 
         byte[] type2 = (new sun.misc.BASE64Decoder()).decodeBuffer (challenge);
-        byte[] nonce = new byte [8];
-        System.arraycopy (type2, 24, nonce, 0, 8);
-
-        int ulen = username.length()*2;
-        type3[36] = type3[38] = (byte) (ulen % 256);
-        type3[37] = type3[39] = (byte) (ulen / 256);
-        int dlen = ntdomain.length()*2;
-        type3[28] = type3[30] = (byte) (dlen % 256);
-        type3[29] = type3[31] = (byte) (dlen / 256);
-        int hlen = hostname.length()*2;
-        type3[44] = type3[46] = (byte) (hlen % 256);
-        type3[45] = type3[47] = (byte) (hlen / 256);
-
-        int l = 64;
-        copybytes (type3, l, ntdomain, "UnicodeLittleUnmarked");
-        type3[32] = (byte) (l % 256);
-        type3[33] = (byte) (l / 256);
-        l += dlen;
-        copybytes (type3, l, username, "UnicodeLittleUnmarked");
-        type3[40] = (byte) (l % 256);
-        type3[41] = (byte) (l / 256);
-        l += ulen;
-        copybytes (type3, l, hostname, "UnicodeLittleUnmarked");
-        type3[48] = (byte) (l % 256);
-        type3[49] = (byte) (l / 256);
-        l += hlen;
-
-        byte[] lmhash = calcLMHash();
-        byte[] lmresponse = calcResponse (lmhash, nonce);
-        byte[] nthash = calcNTHash();
-        byte[] ntresponse = calcResponse (nthash, nonce);
-        System.arraycopy (lmresponse, 0, type3, l, 24);
-        type3[16] = (byte) (l % 256);
-        type3[17] = (byte) (l / 256);
-        l += 24;
-        System.arraycopy (ntresponse, 0, type3, l, 24);
-        type3[24] = (byte) (l % 256);
-        type3[25] = (byte) (l / 256);
-        l += 24;
-        type3[56] = (byte) (l % 256);
-        type3[57] = (byte) (l / 256);
-
-        byte[] msg = new byte [l];
-        System.arraycopy (type3, 0, msg, 0, l);
+        byte[] nonce = new byte[8];
+        new java.util.Random().nextBytes(nonce);
+        byte[] msg = client.type3(type2, nonce);
         String result = "NTLM " + (new B64Encoder()).encode (msg);
         return result;
     }
-
 }
 
-
 class B64Encoder extends sun.misc.BASE64Encoder {
     /* to force it to to the entire encoding in one line */
     protected int bytesPerLine () {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/com/sun/security/sasl/ntlm/NTLMTest.java	Mon Aug 30 14:37:43 2010 +0800
@@ -0,0 +1,416 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 6911951
+ * @summary NTLM should be a supported Java SASL mechanism
+ */
+import java.io.IOException;
+import javax.security.sasl.*;
+import javax.security.auth.callback.*;
+import java.util.*;
+
+import com.sun.security.ntlm.NTLMException;
+
+public class NTLMTest {
+
+    private static final String MECH = "NTLM";
+    private static final String REALM = "REALM";
+    private static final String PROTOCOL = "jmx";
+    private static final byte[] EMPTY = new byte[0];
+
+    private static final String USER1 = "dummy";
+    private static final char[] PASS1 = "bogus".toCharArray();
+    private static final String USER2 = "foo";
+    private static final char[] PASS2 = "bar".toCharArray();
+
+    private static final Map<String,char[]> maps =
+            new HashMap<String,char[]>();
+    static {
+        maps.put(USER1, PASS1);
+        maps.put(USER2, PASS2);
+    }
+
+    static char[] getPass(String d, String u) {
+        if (!d.equals(REALM)) return null;
+        return maps.get(u);
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        checkAuthOnly();
+        checkClientNameOverride();
+        checkServerDomainOverride();
+        checkClientDomainOverride();
+        checkVersions();
+        checkClientHostname();
+    }
+
+    static void checkVersions() throws Exception {
+        // Server accepts all version
+        checkVersion(null, null);
+        checkVersion("LM/NTLM", null);
+        checkVersion("LM", null);
+        checkVersion("NTLM", null);
+        checkVersion("NTLM2", null);
+        checkVersion("LMv2/NTLMv2", null);
+        checkVersion("LMv2", null);
+        checkVersion("NTLMv2", null);
+
+        // Client's default version is LMv2
+        checkVersion(null, "LMv2");
+
+        // Also works if they specified identical versions
+        checkVersion("LM/NTLM", "LM");
+        checkVersion("LM", "LM");
+        checkVersion("NTLM", "LM");
+        checkVersion("NTLM2", "NTLM2");
+        checkVersion("LMv2/NTLMv2", "LMv2");
+        checkVersion("LMv2", "LMv2");
+        checkVersion("NTLMv2", "LMv2");
+
+        // But should not work if different
+        try {
+            checkVersion("LM/NTLM", "LMv2");
+            throw new Exception("Should not succeed");
+        } catch (SaslException se) {
+            NTLMException ne = (NTLMException)se.getCause();
+            if (ne.errorCode() != NTLMException.AUTH_FAILED) {
+                throw new Exception("Failed false");
+            }
+        }
+        try {
+            checkVersion("LMv2/NTLMv2", "LM");
+            throw new Exception("Should not succeed");
+        } catch (SaslException se) {
+            NTLMException ne = (NTLMException)se.getCause();
+            if (ne.errorCode() != NTLMException.AUTH_FAILED) {
+                throw new Exception("Failed false");
+            }
+        }
+
+    }
+
+    /**
+     * A test on version matching
+     * @param vc ntlm version specified for client
+     * @param vs ntlm version specified for server
+     * @throws Exception
+     */
+    private static void checkVersion(String vc, String vs) throws Exception {
+        Map<String,Object> pc = new HashMap<>();
+        pc.put("com.sun.security.sasl.ntlm.version", vc);
+        Map<String,Object> ps = new HashMap<>();
+        ps.put("com.sun.security.sasl.ntlm.version", vs);
+        SaslClient clnt = Sasl.createSaslClient(
+                new String[]{MECH}, USER1, PROTOCOL, null, pc,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                NameCallback ncb = (NameCallback)cb;
+                                ncb.setName(ncb.getDefaultName());
+                            } else if (cb instanceof PasswordCallback) {
+                                ((PasswordCallback)cb).setPassword(PASS1);
+                            }
+                        }
+                    }
+                });
+
+        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, ps,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        String domain = null, name = null;
+                        PasswordCallback pcb = null;
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                name = ((NameCallback)cb).getDefaultName();
+                            } else if (cb instanceof RealmCallback) {
+                                domain = ((RealmCallback)cb).getDefaultText();
+                            } else if (cb instanceof PasswordCallback) {
+                                pcb = (PasswordCallback)cb;
+                            }
+                        }
+                        if (pcb != null) {
+                            pcb.setPassword(getPass(domain, name));
+                        }
+                    }
+                });
+
+        handshake(clnt, srv);
+    }
+
+    private static void checkClientHostname() throws Exception {
+        Map<String,Object> pc = new HashMap<>();
+        pc.put("com.sun.security.sasl.ntlm.hostname", "this.is.com");
+        SaslClient clnt = Sasl.createSaslClient(
+                new String[]{MECH}, USER1, PROTOCOL, null, pc,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                NameCallback ncb = (NameCallback)cb;
+                                ncb.setName(ncb.getDefaultName());
+                            } else if (cb instanceof PasswordCallback) {
+                                ((PasswordCallback)cb).setPassword(PASS1);
+                            }
+                        }
+                    }
+                });
+
+        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        String domain = null, name = null;
+                        PasswordCallback pcb = null;
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                name = ((NameCallback)cb).getDefaultName();
+                            } else if (cb instanceof RealmCallback) {
+                                domain = ((RealmCallback)cb).getDefaultText();
+                            } else if (cb instanceof PasswordCallback) {
+                                pcb = (PasswordCallback)cb;
+                            }
+                        }
+                        if (pcb != null) {
+                            pcb.setPassword(getPass(domain, name));
+                        }
+                    }
+                });
+
+        handshake(clnt, srv);
+        if (!"this.is.com".equals(
+                srv.getNegotiatedProperty("com.sun.security.sasl.ntlm.hostname"))) {
+            throw new Exception("Hostname not trasmitted to server");
+        }
+    }
+
+    /**
+     * Client realm override, but finally overridden by server response
+     */
+    private static void checkClientDomainOverride() throws Exception {
+        SaslClient clnt = Sasl.createSaslClient(
+                new String[]{MECH}, USER1, PROTOCOL, "ANOTHERREALM", null,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                NameCallback ncb = (NameCallback)cb;
+                                ncb.setName(ncb.getDefaultName());
+                            } else if(cb instanceof RealmCallback) {
+                                RealmCallback dcb = (RealmCallback)cb;
+                                dcb.setText("THIRDDOMAIN");
+                            } else if (cb instanceof PasswordCallback) {
+                                ((PasswordCallback)cb).setPassword(PASS1);
+                            }
+                        }
+                    }
+                });
+
+        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        String domain = null, name = null;
+                        PasswordCallback pcb = null;
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                name = ((NameCallback)cb).getDefaultName();
+                            } else if (cb instanceof RealmCallback) {
+                                domain = ((RealmCallback)cb).getDefaultText();
+                            } else if (cb instanceof PasswordCallback) {
+                                pcb = (PasswordCallback)cb;
+                            }
+                        }
+                        if (pcb != null) {
+                            pcb.setPassword(getPass(domain, name));
+                        }
+                    }
+                });
+
+        handshake(clnt, srv);
+    }
+
+    /**
+     * Client side user name provided in callback.
+     * @throws Exception
+     */
+    private static void checkClientNameOverride() throws Exception {
+        SaslClient clnt = Sasl.createSaslClient(
+                new String[]{MECH}, null, PROTOCOL, null, null,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                NameCallback ncb = (NameCallback)cb;
+                                ncb.setName(USER1);
+                            } else if (cb instanceof PasswordCallback) {
+                                ((PasswordCallback)cb).setPassword(PASS1);
+                            }
+                        }
+                    }
+                });
+
+        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        String domain = null, name = null;
+                        PasswordCallback pcb = null;
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                name = ((NameCallback)cb).getDefaultName();
+                            } else if (cb instanceof RealmCallback) {
+                                domain = ((RealmCallback)cb).getDefaultText();
+                            } else if (cb instanceof PasswordCallback) {
+                                pcb = (PasswordCallback)cb;
+                            }
+                        }
+                        if (pcb != null) {
+                            pcb.setPassword(getPass(domain, name));
+                        }
+                    }
+                });
+
+        handshake(clnt, srv);
+    }
+
+    /**
+     * server side domain provided in props.
+     * @throws Exception
+     */
+    private static void checkServerDomainOverride() throws Exception {
+        SaslClient clnt = Sasl.createSaslClient(
+                new String[]{MECH}, USER1, PROTOCOL, null, null,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                NameCallback ncb = (NameCallback)cb;
+                                ncb.setName(ncb.getDefaultName());
+                            } else if (cb instanceof PasswordCallback) {
+                                ((PasswordCallback)cb).setPassword(PASS1);
+                            }
+                        }
+                    }
+                });
+
+        Map<String,Object> ps = new HashMap<>();
+        ps.put("com.sun.security.sasl.ntlm.domain", REALM);
+        SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, null, ps,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        String domain = null, name = null;
+                        PasswordCallback pcb = null;
+                        for (Callback cb: callbacks) {
+                            if (cb instanceof NameCallback) {
+                                name = ((NameCallback)cb).getDefaultName();
+                            } else if (cb instanceof RealmCallback) {
+                                domain = ((RealmCallback)cb).getDefaultText();
+                            } else if (cb instanceof PasswordCallback) {
+                                pcb = (PasswordCallback)cb;
+                            }
+                        }
+                        if (pcb != null) {
+                            pcb.setPassword(getPass(domain, name));
+                        }
+                    }
+                });
+
+        handshake(clnt, srv);
+    }
+
+    private static void checkAuthOnly() throws Exception {
+        Map<String,Object> props = new HashMap<>();
+        props.put(Sasl.QOP, "auth-conf");
+        try {
+            Sasl.createSaslClient(
+                    new String[]{MECH}, USER2, PROTOCOL, REALM, props, null);
+            throw new Exception("NTLM should not support auth-conf");
+        } catch (SaslException se) {
+            // Normal
+        }
+    }
+
+    private static void handshake(SaslClient clnt, SaslServer srv)
+            throws Exception {
+        if (clnt == null) {
+            throw new IllegalStateException(
+                    "Unable to find client impl for " + MECH);
+        }
+        if (srv == null) {
+            throw new IllegalStateException(
+                    "Unable to find server impl for " + MECH);
+        }
+
+        byte[] response = (clnt.hasInitialResponse()
+                ? clnt.evaluateChallenge(EMPTY) : EMPTY);
+        System.out.println("Initial:");
+        new sun.misc.HexDumpEncoder().encodeBuffer(response, System.out);
+        byte[] challenge;
+
+        while (!clnt.isComplete() || !srv.isComplete()) {
+            challenge = srv.evaluateResponse(response);
+            response = null;
+            if (challenge != null) {
+                System.out.println("Challenge:");
+                new sun.misc.HexDumpEncoder().encodeBuffer(challenge, System.out);
+                response = clnt.evaluateChallenge(challenge);
+            }
+            if (response != null) {
+                System.out.println("Response:");
+                new sun.misc.HexDumpEncoder().encodeBuffer(response, System.out);
+            }
+        }
+
+        if (clnt.isComplete() && srv.isComplete()) {
+            System.out.println("SUCCESS");
+            if (!srv.getAuthorizationID().equals(USER1)) {
+                throw new Exception("Not correct user");
+            }
+        } else {
+            throw new IllegalStateException(
+                    "FAILURE: mismatched state:"
+                    + " client complete? " + clnt.isComplete()
+                    + " server complete? " + srv.isComplete());
+        }
+
+        if (!clnt.getNegotiatedProperty(Sasl.QOP).equals("auth") ||
+                !srv.getNegotiatedProperty(Sasl.QOP).equals("auth") ||
+                !clnt.getNegotiatedProperty(
+                    "com.sun.security.sasl.ntlm.domain").equals(REALM)) {
+            throw new Exception("Negotiated property error");
+        }
+        clnt.dispose();
+        srv.dispose();
+    }
+}