// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
package net.sourceforge.jtds.util;

import java.sql.SQLException;

/**
 * A JNI client to SSPI based CPP program (DLL) that returns the user
 * credentials for NTLM authentication.
 * <p/>
 * The DLL name is ntlmauth.dll.
 *
 * @author Magendran Sathaiah (mahi@aztec.soft.net)
 */
public class SSPIJNIClient {
    /** Singleton instance. */
    private static SSPIJNIClient thisInstance;

    /** SSPI native library loaded flag. */
    private static boolean libraryLoaded;

    /** SSPI client initialized flag. */
    private boolean initialized;

    /** Initializes the SSPI client. */
    private native void initialize();

    /** Uninitializes the SSPI client. */
    private native void unInitialize();

    /**
     * Prepares the NTLM TYPE-1 message and returns it as a
     * <code>byte[]</code>.
     */
    private native byte[] prepareSSORequest();

    /**
     * Prepares the NTLM TYPE-3 message using the current user's credentials.
     * <p>
     * It needs the challenge BLOB and it's size as input. The challenge BLOB
     * is nothig but the TYPE-2 message that is received from the SQL Server.
     *
     * @param buf  challenge BLOB
     * @param size challenge BLOB size
     * @return NTLM TYPE-3 message
     */
    private native byte[] prepareSSOSubmit(byte[] buf, long size);

    static {
        try {
            System.loadLibrary("ntlmauth");
            SSPIJNIClient.libraryLoaded = true;
        } catch (UnsatisfiedLinkError err) {
            Logger.println("Unable to load library: " + err);
        }
    }

    /**
     * Private constructor for singleton.
     */
    private SSPIJNIClient() {
        //empty constructor
    }

    /**
     * Returns the singleton <code>SSPIJNIClient</code> instance.
     *
     * @throws SQLException if an error occurs during initialization
     */
    public static SSPIJNIClient getInstance() throws Exception {

        if (thisInstance == null) {
            if (!libraryLoaded) {
                throw new Exception("Native SSPI library not loaded. "
                        + "Check the java.library.path system property.");
            }
            thisInstance = new SSPIJNIClient();
            thisInstance.invokeInitialize();
        }
        return thisInstance;
    }

    /**
     * Calls {@link #initialize()} if the SSPI client is not already inited.
     */
    public void invokeInitialize() {
        if (!initialized) {
            initialize();
            initialized = true;
        }
    }

    /**
     * Calls {@link #unInitialize()} if the SSPI client is inited.
     */
    public void invokeUnInitialize() {
        if (initialized) {
            unInitialize();
            initialized = false;
        }
    }

    /**
     * Calls {@link #prepareSSORequest()} to prepare the NTLM TYPE-1 message.
     *
     * @throws Exception if an error occurs during the call or the SSPI client
     *                   is uninitialized
     */
    public byte[] invokePrepareSSORequest() throws Exception {
        if (!initialized) {
            throw new Exception("SSPI Not Initialized");
        }
        return prepareSSORequest();
    }

    /**
     * Calls {@link #prepareSSOSubmit(byte[], long)} to prepare the NTLM TYPE-3
     * message.
     *
     * @throws Exception if an error occurs during the call or the SSPI client
     *                   is uninitialized
     */
    public byte[] invokePrepareSSOSubmit(byte[] buf) throws Exception {
        if (!initialized) {
            throw new Exception("SSPI Not Initialized");
        }
        return prepareSSOSubmit(buf, buf.length);
    }
}
