/*
 * @(#)IpClientSsl.java
 *
 * Copyright (c) 2005-2006 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */
package com.jscape.inet.ipclientssl;

import com.jscape.inet.ipclient.IpClient;
import com.jscape.inet.ipclient.IpClientException;
import com.jscape.inet.util.TimedSocket;

import javax.net.ssl.SSLContext;


/**
 * Implements basic functionality of a secure TLS/SSL TCP/IP client connection. <P>
 * Example usage: <P>
 * <pre>
 * try {
 *   // create new IpClientSsl instance with hostname, port and timeout arguments
 *   IpClientSsl client = new IpClientSsl("10.0.0.1",993,5000);
 *
 *   // establish connection with server
 *   client.connect();
 *
 *   // obtain OutputStream for sending data to server
 *   OutputStream out = client.getOutputStream();
 *
 *   // obtain InputStream for reading data from server
 *   InputStream in = client.getInputStream();
 *
 *   // disconnect from server
 *   client.disconnect();
 *
 * } catch(IpClientException ipe) {
 *   ipe.printStackTrace();
 * }
 *
 * </pre>
 */
public class IpClientSsl
        extends IpClient {

    private SSLContext context = null;
    private String clientFilename = null;
    private String clientPassword = null;
    private String clientStoretype = null;
    private String serverFilename = null;
    private String serverPassword = null;
    private String serverStoretype = null;

    private static final int DEFAULT_PROXY_PORT = 1080;
    private static final String DEFAULT_PROXY_TYPE = "SOCKS5";
    private String proxyHostname = null;
    private int proxyPort = DEFAULT_PROXY_PORT;
    private String proxyUsername = null;
    private String proxyPassword = null;
    private String proxyType = DEFAULT_PROXY_TYPE;

    /**
     * Creates a new IpClientSsl instance.
     *
     * @param hostname the server hostname or IP address
     * @param port     the server port
     */
    public IpClientSsl(String hostname, int port) {
        super(hostname, port);
    }

    /**
     * Creates a new IpClientSsl instance.
     *
     * @param hostname the server hostname or IP address
     * @param port     the server port
     * @param context  an SSLContext
     */
    public IpClientSsl(String hostname, int port, SSLContext context) {
        this(hostname, port);
        this.context = context;
    }

    /**
     * Creates a new IpClientSsl instance.
     *
     * @param hostname the server hostname or IP address
     * @param port     the server port
     * @param timeout  the timeout for establishing a connection specified in ms (milliseconds)
     */
    public IpClientSsl(String hostname, int port, int timeout) {
        this(hostname, port, timeout, null);
    }

    /**
     * Creates a new IpClientSsl instance.
     *
     * @param hostname the server hostname or IP address
     * @param port     the server port
     * @param timeout  the timeout for establishing a connection specified in ms (milliseconds)
     * @param context  an SSLContext
     *
     * @see SSLContext
     */
    public IpClientSsl(String hostname, int port, int timeout,
                       SSLContext context) {
        super(hostname, port, timeout);
        this.context = context;
    }

    /**
     * Sets optional client certificate to be used during authentication.
     * Default expected keystore format is JKS format. Existing certificate may be imported into keystore using
     * the <code>keytool</code> utility provided in the Sun JDK.
     * <p/>
     * Example:
     * <p/>
     * <pre>
     * keytool.exe -import -file x509.cer -keystore your_store_name
     * </pre>
     * <p/>
     * The example above imports the certificate x509.cer into a keystore named your_store_name
     * <p/>
     *
     * @param filename the keystore file containing the client certificate
     * @param password the keystore password
     */
    public void setClientCertificates(String filename, String password) {
        this.clientFilename = filename;
        this.clientPassword = password;
    }

    /**
     * Sets optional client certificate to be used during authentication.
     * Default expected keystore format is JKS format. Existing certificate may be imported into keystore using
     * the <code>keytool</code> utility provided in the Sun JDK.
     * <p/>
     * Example:
     * <p/>
     * <pre>
     * keytool.exe -import -file x509.cer -keystore your_store_name
     * </pre>
     * <p/>
     * The example above imports the certificate x509.cer into a keystore named your_store_name
     * <p/>
     *
     * @param filename  the keystore file containing the client certificate
     * @param password  the keystore password
     * @param storetype the keystore type valid values include jks and pkcs12
     */
    public void setClientCertificates(String filename, String password,
                                      String storetype) {
        this.clientFilename = filename;
        this.clientPassword = password;
        this.clientStoretype = storetype;
    }

    /**
     * Sets optional server certificate to be used during authentication.
     * Default expected keystore format is JKS format. Existing certificate may be imported into keystore using
     * the <code>keytool</code> utility provided in the Sun JDK.
     * <p/>
     * Example:
     * <p/>
     * <pre>
     * keytool.exe -import -file x509.cer -keystore your_store_name
     * </pre>
     * <p/>
     * The example above imports the certificate x509.cer into a keystore named your_store_name
     * <p/>
     *
     * @param filename the keystore file containing the server certificate
     * @param password the keystore password
     */
    public void setServerCertificates(String filename, String password) {
        this.serverFilename = filename;
        this.serverPassword = password;
    }

    /**
     * Sets optional server certificate to be used during authentication.
     * Default expected keystore format is JKS format. Existing certificate may be imported into keystore using
     * the <code>keytool</code> utility provided in the Sun JDK.
     * <p/>
     * Example:
     * <p/>
     * <pre>
     * keytool.exe -import -file x509.cer -keystore your_store_name
     * </pre>
     * <p/>
     * The example above imports the certificate x509.cer into a keystore named your_store_name
     * <p/>
     *
     * @param filename  the keystore file containing the server certificate
     * @param password  the keystore password
     * @param storetype the keystore type valid values include jks and pkcs12
     */
    public void setServerCertificates(String filename, String password,
                                      String storetype) {
        this.serverFilename = filename;
        this.serverPassword = password;
        this.serverStoretype = storetype;
    }

    /**
     * Sets the username and password to use when for authentication with
     * proxy server.
     * To clear these settings invoke the <code>#clearProxySettings</code> method.
     *
     * @param proxyUsername the proxy username
     * @param proxyPassword the proxy password
     *
     * @see #clearProxySettings
     */
    public void setProxyAuthentication(String proxyUsername,
                                       String proxyPassword) {
        this.proxyUsername = proxyUsername;
        this.proxyPassword = proxyPassword;
    }

    // PROXY CONTROL

    /**
     * Sets the proxy hostname and port for this connection.
     * To clear these settings invoke the <code>#clearProxySettings</code> method.
     *
     * @param proxyHostname the hostname or ip address of the proxy server
     * @param proxyPort     the port of the proxy server
     *
     * @see #clearProxySettings
     */
    public void setProxyHost(String proxyHostname, int proxyPort) {
        this.proxyHostname = proxyHostname;
        this.proxyPort = proxyPort;
    }

    /**
     * Sets the proxy type will be used for this connection.
     *
     * @param proxyType The proxy type. Valid values: HTTP, SOCKS5
     */
    public void setProxyType(String proxyType) {
        this.proxyType = proxyType;
    }

    /**
     * Clears proxy server values.
     */
    public void clearProxySettings() {
        setProxyAuthentication(null, null);
        setProxyHost(null, DEFAULT_PROXY_PORT);
        setProxyType(DEFAULT_PROXY_TYPE);
    }

    /**
     * Establishes connection with server.
     *
     * @throws IpClientException if an I/O or network related error occurs
     */
    public IpClientSsl connect()
            throws IpClientException {
        try {
            // create the socket
            if (this.proxyHostname == null) {
                this.socket = TimedSocket.getSocket(hostname, port, connectTimeout);
            } else {
                this.socket = TimedSocket.getSocket(hostname, port, connectTimeout,
                                                    this.proxyHostname, this.proxyPort, this.proxyUsername,
                                                    this.proxyPassword, this.proxyType);
            }

            if (readTimeout >= 0) {
                this.socket.setSoTimeout(readTimeout);
            }

            // secure the socket
            SecureSocket ss = new SecureSocket(socket, context);

            if ((this.clientFilename != null) && (this.clientPassword != null)) {
                ss.setClientCertificates(this.clientFilename,
                                         this.clientPassword, this.clientStoretype);
            }

            if ((this.serverFilename != null) && (this.serverPassword != null)) {
                ss.setServerCertificates(this.serverFilename,
                                         this.serverPassword, this.serverStoretype);
            }

            this.socket = ss.getSocket();

            // get streams
            this.input = socket.getInputStream();
            this.output = socket.getOutputStream();
            fireIpConnected();

            return this;
        } catch (Exception e) {
            throw new IpClientException(e.getMessage(), e);
        }
    }

    /**
     * Closes connection with server.
     *
     * @throws IpClientException if an I/O or network related error occurs
     */
    public void disconnect()
            throws IpClientException {
        synchronized (this) {
            try {
                socket.close();
                fireIpDisconnected();
            } catch (Exception e) {
                throw new IpClientException(e.getMessage(), e);
            }
        }
    }
}
