/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * @(#)ServerLinkTable.java	1.21 09/11/07
 */ 

package com.sun.messaging.jmq.transport.httptunnel.servlet;

import com.sun.messaging.jmq.transport.httptunnel.HttpTunnelDefaults;
import com.sun.messaging.jmq.transport.httptunnel.HttpTunnelPacket;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;

import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import java.security.KeyStore;
import java.security.SecureRandom;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.net.ssl.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;


public class ServerLinkTable implements HttpTunnelDefaults {
    private static final int RUNNING = 0;
    private static final int SHUTTINGDOWN = 1;
    private static final int DESTROYED = 2;
    private int linkTableState = RUNNING;
    private int servletPort;
    private int rxBufSize;
    private String servletHost = null;
    private HelperThread helperThread;
    private Hashtable linkTable; // Maps serverName <--> ServerLink
    private Hashtable connTable; // Maps (serverName, connId) <--> Connection
    private Vector tmpList;
    private int nextConnId = 0;
    private long lastCheck = 0;
    protected ServletContext servletContext;

    public ServerLinkTable(ServletConfig cfg) throws Exception {
        this(cfg, false);
    }

    public ServerLinkTable(ServletConfig cfg, boolean useSSL)
        throws Exception {
        servletContext = cfg.getServletContext();
        linkTableState = RUNNING;
        nextConnId = (int) System.currentTimeMillis();
        lastCheck = 0;

        linkTable = new Hashtable();
        connTable = new Hashtable();
        tmpList = new Vector();

        rxBufSize = 0;

        String rxBufSizeStr = cfg.getInitParameter("rxBufSize");

        if (rxBufSizeStr != null) {
            try {
                rxBufSize = Integer.parseInt(rxBufSizeStr);
            } catch (Exception e) {
                servletContext.log("Exception in HttpTunnelServlet : " +
                    e.getMessage());
            }
        }

        // determine port number to use
        int defaultPort = (useSSL ? DEFAULT_HTTPS_TUNNEL_PORT
                                  : DEFAULT_HTTP_TUNNEL_PORT);

        String servletPortString = cfg.getInitParameter("servletPort");

        if (servletPortString == null) {
            //check if the user has specified "serverPort" as the servlet init parameters
            //TODO remove this line: this is kept for backward compatability, one shd use servletPort instead of serverPort
            servletPortString = cfg.getInitParameter("serverPort");
        }

        if (servletPortString != null) {
            try {
                servletPort = Integer.parseInt(servletPortString);
            } catch (Exception e) {
                servletPort = defaultPort;
            }
        } else {
            servletPort = defaultPort;
        }

        servletHost = cfg.getInitParameter("servletHost");

        if (servletHost == null) {
            //check if the user has specified "serverHost" as the servlet init parameters
            //TODO remove this line: this is kept for backward compatability, one shd use servletHost instead of serverHost
            servletHost = cfg.getInitParameter("serverHost");
        }

        if (useSSL) {
            String keystoreloc = cfg.getInitParameter("keystoreLocation");

            if ((keystoreloc == null) || keystoreloc.equals("")) {
                throw new Exception("keystore location not specified");
            }

            String kspassword = cfg.getInitParameter("keystorePassword");

            if ((kspassword == null) || kspassword.equals("")) {
                throw new Exception("keystore password not specified");
            }

            helperThread = new HelperThread(servletPort, servletHost,
                    rxBufSize, keystoreloc, kspassword, this);
        } else {
            helperThread = new HelperThread(servletPort, servletHost,
                    rxBufSize, this);
        }

        helperThread.start();
    }

    //called from Servlet.destroy() - may have service threads running
    public void shuttingDown() {
        //stop listen thread, no more server link
        helperThread.close();

        synchronized (linkTable) {
            linkTableState = SHUTTINGDOWN;
        }

        synchronized (linkTable) {
            String serverName;
            ServerLink link;
            Enumeration servers = linkTable.keys();

            while (servers.hasMoreElements()) {
                serverName = (String) servers.nextElement();
                link = (ServerLink) linkTable.get(serverName);
                linkTable.remove(serverName);
                link.shutdown(); //stop link reading thread
                link.linkDown(); //close link io/socket
            }
        }

        synchronized (tmpList) {
            ServerLink link;

            for (int i = tmpList.size() - 1; i >= 0; i--) {
                link = (ServerLink) tmpList.elementAt(i);
                link.shutdown();
                link.linkDown();
            }
        }

        synchronized (connTable) {
            Enumeration conns = connTable.keys();

            while (conns.hasMoreElements()) {
                ConnKey s = (ConnKey) conns.nextElement();
                Connection conn = (Connection) connTable.get(s);
                Vector pullQ = conn.getPullQ();

                synchronized (pullQ) {
                    pullQ.notifyAll();
                }
            }
        }
    }

    //called from Servlet.destroy() when no more service threads
    public void destroy() {
        linkTableState = DESTROYED;

        synchronized (linkTable) {
            linkTable.clear();
        }

        synchronized (connTable) {
            connTable.clear();
        }

        synchronized (tmpList) {
            tmpList.removeAllElements();
        }
    }
    
    /**
     * This method is to close the server socket.  
     * Any exception is ignored on purpose.
     */
    public void close() {
    	try {
    		this.helperThread.close();
    	} catch (Exception e) {
    		;
    	}
    }

    protected void addServer(Socket s) {
        try {
            ServerLink link = new ServerLink(s, this);

            synchronized (tmpList) {
                tmpList.addElement(link);
            }
        } catch (Exception e) {
        }
    }

    public void updateServerName(ServerLink link) throws IllegalStateException {
        String serverName = link.getServerName();

        synchronized (linkTable) {
            if (linkTableState >= SHUTTINGDOWN) {
                throw new IllegalStateException("HttpTunnelServlet: in destory");
            }

            ServerLink oldlink = (ServerLink) linkTable.get(serverName);

            if ((oldlink != null) && !oldlink.isDone()) {
                throw new IllegalStateException(
                    "HttpTunnelServlet: ServerName " + serverName +
                    " conflict");
            }

            linkTable.put(serverName, link);
        }

        synchronized (tmpList) {
            int i = tmpList.indexOf(link);

            if (i > -1) {
                tmpList.removeElementAt(i);
            }
        }
    }

    public void updateConnection(int connId, int pullPeriod, ServerLink link)
        throws IllegalStateException {
        Connection conn = new Connection(link);
        conn.setPullPeriod(pullPeriod);

        synchronized (connTable) {
            if (linkTableState >= SHUTTINGDOWN) {
                throw new IllegalStateException("HttpTunnelServlet: in destory");
            }

            connTable.put(new ConnKey(link.getServerName(), connId), conn);
        }
    }

    private boolean sameServerName(String serverName, Connection conn) {
        String servname = conn.getServerLink().getServerName();

        if ((servname == null) || (serverName == null) ||
                !servname.equals(serverName)) {
            return false;
        }

        return true;
    }

    protected void serverDown(ServerLink link) {
        String serverName = link.getServerName();

        if (serverName != null) {
            ServerLink curlink = null;

            synchronized (linkTable) {
                curlink = (ServerLink) linkTable.get(serverName);

                if (curlink == link) {
                    linkTable.remove(serverName);
                } else {
                    return;
                }
            }
        }

        link.shutdown();

        synchronized (connTable) {
            Enumeration conns = connTable.keys();

            while (conns.hasMoreElements()) {
                ConnKey s = (ConnKey) conns.nextElement();
                Connection conn = (Connection) connTable.get(s);

                if (conn.getServerLink() == link) {
                    int connId = s.getConnId();

                    Vector pullQ = conn.getPullQ();
                    abortClientConnection(connId, pullQ);
                }
            }
        }
    }

    private static HttpTunnelPacket genAbortPacket(int connId) {
        HttpTunnelPacket p = new HttpTunnelPacket();
        p.setPacketType(CONN_ABORT_PACKET);
        p.setConnId(connId);
        p.setSequence(0);
        p.setWinsize(0);
        p.setChecksum(0);
        p.setPacketBody(null);

        return p;
    }

    private void abortClientConnection(int connId, Vector pullQ) {
        HttpTunnelPacket p = genAbortPacket(connId);

        synchronized (pullQ) {
            pullQ.addElement(p);
            pullQ.notify();
        }
    }

    private void abortServerConnection(int connId, Connection conn) {
        HttpTunnelPacket p = genAbortPacket(connId);

        conn.getServerLink().sendPacket(p);
    }

    public String getDefaultServer() {
        String serverName = null;

        try {
            ServerLink link = (ServerLink) linkTable.elements().nextElement();

            if (link != null) {
                serverName = link.getServerName();
            }
        } catch (Exception e) {
        }

        return serverName;
    }

    public boolean getListenState(String serverName) {
        if (serverName == null) {
            return false;
        }

        // First find the ServerLink.
        ServerLink link = null;

        synchronized (linkTable) {
            if (linkTableState >= SHUTTINGDOWN) {
                return false;
            }

            link = (ServerLink) linkTable.get(serverName);

            if (link == null) {
                return false;
            }
        }

        return link.getListenState();
    }

    public int createNewConn(String serverName) {
        if (serverName == null) {
            return -1;
        }

        // First find the ServerLink.
        ServerLink link = null;

        synchronized (linkTable) {
            if (linkTableState >= SHUTTINGDOWN) {
                return -1;
            }

            link = (ServerLink) linkTable.get(serverName);

            if (link == null) {
                return -1;
            }
        }

        synchronized (connTable) {
            int connId = nextConnId++;
            ConnKey connKey = new ConnKey(link.getServerName(), connId);
            connTable.put(connKey, new Connection(link));

            return connId;
        }
    }

    public void destroyConn(int connId, String serverName) {
        if (serverName == null) {
            return;
        }

        synchronized (connTable) {
            connTable.remove(new ConnKey(serverName, connId));
        }
    }

    /**
     * Intercept the packets from client.
     */
    public void sendPacket(HttpTunnelPacket p, String serverName) {
        if (serverName == null) {
            return;
        }

        ConnKey connKey = new ConnKey(serverName, p.getConnId());

        Connection conn = (Connection) connTable.get(connKey);

        if (conn == null) {
            return;
        }

        if (!sameServerName(serverName, conn)) {
            return;
        }

        if (p.getPacketType() == CONN_OPTION_PACKET) {
            interceptConnOption(conn, p);
        }

        conn.getServerLink().sendPacket(p);
    }

    protected void receivePacket(HttpTunnelPacket p, ServerLink link) {
        Vector pullQ = null;

        synchronized (connTable) {
            ConnKey connKey = new ConnKey(link.getServerName(), p.getConnId());
            Connection conn = (Connection) connTable.get(connKey);

            if (conn == null) {
                return;
            }

            pullQ = conn.getPullQ();
        }

        synchronized (pullQ) {
            pullQ.addElement(p);
            pullQ.notify();
        }
    }

    private void interceptConnOption(Connection conn, HttpTunnelPacket p) {
        byte[] buf = p.getPacketBody();
        ByteArrayInputStream bis = new ByteArrayInputStream(buf);
        DataInputStream dis = new DataInputStream(bis);

        try {
            int optname = dis.readInt();

            switch (optname) {
            case CONOPT_PULL_PERIOD:

                int pullPeriod = dis.readInt();
                conn.setPullPeriod(pullPeriod);

                break;
            }
        } catch (Exception e) {
        }
    }

    public HttpTunnelPacket waitForPacket(String connIdStr, String serverName) {
        int connId = -1;

        try {
            connId = Integer.parseInt(connIdStr);
        } catch (Exception e) {
            return null;
        }

        if (serverName == null) {
            return genAbortPacket(connId);
        }

        ConnKey connKey = new ConnKey(serverName, connId);

        Connection conn = null;

        synchronized (connTable) {
            conn = (Connection) connTable.get(connKey);
        }

        if (conn == null) {
            if (linkTable.get(serverName) != null) {
                return genAbortPacket(connId);
            }

            return null;
        }

        if (!sameServerName(serverName, conn)) {
            return genAbortPacket(connId);
        }

        conn.setInUse(true);

        HttpTunnelPacket p = waitForPacket(connKey, conn);

        conn.setInUse(false);

        return p;
    }

    private HttpTunnelPacket waitForPacket(ConnKey connKey, Connection conn) {
        Vector pullQ = conn.getPullQ();
        int pullPeriod = conn.getPullPeriod();
        HttpTunnelPacket p = null;

        boolean removeConn = false;

        synchronized (pullQ) {
            if (pullPeriod > 0) {
                if (pullQ.isEmpty()) {
                    return null; // Don't tie-up web server resources...
                }
            }

            long startTime = System.currentTimeMillis();
            long maxwait = MAX_PULL_BLOCK_PERIOD;

            while (pullQ.isEmpty() && (linkTableState == RUNNING)) {
                try {
                    pullQ.wait(maxwait);
                } catch (Exception e) {
                }

                maxwait -= (System.currentTimeMillis() - startTime);

                if (maxwait <= 0) {
                    return null;
                }
            }

            if (pullQ.isEmpty()) {
                return null;
            }

            p = (HttpTunnelPacket) pullQ.elementAt(0);
            pullQ.removeElementAt(0);

            switch (p.getPacketType()) {
            case CONN_ABORT_PACKET:
                removeConn = true;
                pullQ.insertElementAt(p, 0); // Let all threads find the CONN_ABORT_PACKET
                pullQ.notifyAll();

                break;

            case CONN_SHUTDOWN:
                removeConn = true;
                pullQ.insertElementAt(p, 0); // Let all threads find the CONN_SHUTDOWN
                pullQ.notifyAll();
                p = null;

                break;

            case CONN_OPTION_PACKET:
                interceptConnOption(conn, p);

                break;
            }
        }

        //lock connTable outside pullQ lock 
        if (removeConn) {
            synchronized (connTable) {
                connTable.remove(connKey);
            }
        }

        return p;
    }

    public Vector waitForPackets(String connIdStr, String serverName) {
        int connId = -1;

        try {
            connId = Integer.parseInt(connIdStr);
        } catch (Exception e) {
            return null;
        }

        if (serverName == null) {
            Vector v1 = new Vector();
            v1.addElement(genAbortPacket(connId));

            return v1;
        }

        ConnKey connKey = new ConnKey(serverName, connId);

        Connection conn = null;

        synchronized (connTable) {
            conn = (Connection) connTable.get(connKey);
        }

        if (conn == null) {
            if (linkTable.get(serverName) != null) {
                Vector v2 = new Vector();
                v2.addElement(genAbortPacket(connId));

                return v2;
            }

            return null;
        }

        if (!sameServerName(serverName, conn)) {
            Vector v3 = new Vector();
            v3.addElement(genAbortPacket(connId));

            return v3;
        }

        conn.setInUse(true);

        Vector v = waitForPackets(connKey, conn);

        conn.setInUse(false);

        return v;
    }

    private Vector waitForPackets(ConnKey connKey, Connection conn) {
        Vector pullQ = conn.getPullQ();
        int pullPeriod = conn.getPullPeriod();

        Vector v = new Vector();
        boolean removeConn = false;

        synchronized (pullQ) {
            if (pullPeriod > 0) {
                if (pullQ.isEmpty()) {
                    return null; // Don't tie-up web server resources...
                }
            }

            long startTime = System.currentTimeMillis();
            long maxwait = MAX_PULL_BLOCK_PERIOD;

            while (pullQ.isEmpty() && (linkTableState == RUNNING)) {
                try {
                    pullQ.wait(maxwait);
                } catch (Exception e) {
                }

                maxwait -= (System.currentTimeMillis() - startTime);

                if (maxwait <= 0) {
                    return null;
                }
            }

            if (pullQ.isEmpty()) {
                return v;
            }

            int size = 0;

            while (true && (linkTableState == RUNNING)) {
                HttpTunnelPacket p = (HttpTunnelPacket) pullQ.elementAt(0);

                switch (p.getPacketType()) {
                case CONN_ABORT_PACKET:
                    removeConn = true;

                    // Let all threads find the CONN_ABORT_PACKET
                    pullQ.notifyAll();
                    v.addElement(p);

                    break;

                case CONN_SHUTDOWN:
                    removeConn = true;

                    // Let all threads find the CONN_SHUTDOWN
                    pullQ.notifyAll();

                    break;

                case CONN_OPTION_PACKET:
                    interceptConnOption(conn, p);

                    break;
                }

                if (removeConn) {
                    break;
                }

                if ((size > 0) &&
                        ((size + p.getPacketSize()) > MAX_PACKETSIZE)) {
                    break;
                }

                v.addElement(p);
                size += p.getPacketSize();

                pullQ.removeElementAt(0);

                if (p.getPacketType() == CONN_INIT_ACK) {
                    break;
                }

                if (pullQ.isEmpty()) {
                    break;
                }
            }
        }

        if (removeConn) {
            synchronized (connTable) {
                connTable.remove(connKey);
            }
        }

        return v;
    }

    public void retrySendPacket(HttpTunnelPacket p, String connIdStr,
        String serverName) {
        if (serverName == null) {
            return;
        }

        Vector pullQ = null;

        ConnKey connKey;

        try {
            connKey = new ConnKey(serverName, connIdStr);
        } catch (Exception e) {
            return;
        }

        Connection conn = null;

        synchronized (connTable) {
            conn = (Connection) connTable.get(connKey);
        }

        if (conn == null) {
            return;
        }

        if (!sameServerName(serverName, conn)) {
            return;
        }

        pullQ = conn.getPullQ();

        synchronized (pullQ) {
            pullQ.insertElementAt(p, 0);
            pullQ.notify();
        }
    }

    public void retrySendPackets(Vector v, String connIdStr, String serverName) {
        if (serverName == null) {
            return;
        }

        Vector pullQ = null;

        ConnKey connKey;

        try {
            connKey = new ConnKey(serverName, connIdStr);
        } catch (Exception e) {
            return;
        }

        Connection conn = null;

        synchronized (connTable) {
            conn = (Connection) connTable.get(connKey);
        }

        if (conn == null) {
            return;
        }

        if (!sameServerName(serverName, conn)) {
            return;
        }

        pullQ = conn.getPullQ();

        synchronized (pullQ) {
            for (int i = 0; i < v.size(); i++) {
                pullQ.insertElementAt(v.elementAt(i), i);
            }

            pullQ.notify();
        }
    }

    protected void checkConnectionTimeouts() {
        long now = System.currentTimeMillis();

        if ((lastCheck != 0) && ((now - lastCheck) < 5000)) {
            return; // Don't check timeouts too often.
        }

        lastCheck = now;

        Vector removeList = new Vector();

        synchronized (connTable) {
            Enumeration conns = connTable.keys();

            while (conns.hasMoreElements()) {
                ConnKey s = (ConnKey) conns.nextElement();
                Connection conn = (Connection) connTable.get(s);

                if (conn.checkConnectionTimeout(now)) {
                    removeList.addElement(s);
                }
            }
        }

        for (int i = 0; i < removeList.size(); i++) {
            ConnKey s = (ConnKey) removeList.elementAt(i);
            Connection conn = null;

            synchronized (connTable) {
                conn = (Connection) connTable.get(s);
            }

            int connId = s.getConnId();
            abortServerConnection(connId, conn);

            synchronized (connTable) {
                connTable.remove(removeList.elementAt(i));
            }
        }
    }

    public Vector getServerList() {
        Vector list = new Vector();

        synchronized (linkTable) {
            for (Enumeration e = linkTable.keys(); e.hasMoreElements();) {
                list.addElement(e.nextElement());
            }
        }

        return list;
    }

    public int getServletPort() {
        return servletPort;
    }
}


class ConnKey {
    private String serverName;
    private int connId = -1;

    public ConnKey(String serverName, int connId) {
        this.serverName = serverName;
        this.connId = connId;
    }

    public ConnKey(String serverName, String connIdStr)
        throws NumberFormatException {
        this.serverName = serverName;
        this.connId = Integer.parseInt(connIdStr);
    }

    public String getServerName() {
        return serverName;
    }

    public int getConnId() {
        return connId;
    }

    public int hashCode() {
        return (serverName.hashCode() + connId);
    }

    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        ConnKey key = (ConnKey) obj;

        return ((key.getServerName().equals(this.serverName)) &&
        (key.getConnId() == this.connId));
    }
}


/**
 * Listens for TCP connections from servers and periodically
 * checks for connection timeouts.
 */
class HelperThread extends Thread {
    private ServerSocket ss = null;
    private ServerLinkTable parent;
    private String servletName = null;
    private boolean closed = false;

    // start regular ServerSocket
    public HelperThread(int serverPort, String servletHost, int rxBufSize,
        ServerLinkTable p) throws IOException {
        this.parent = p;
        closed = false;

        //if (servletHost == null) {
        //    ss = new ServerSocket(serverPort);
        //} else {
        //    InetAddress listenAddr = InetAddress.getByName(servletHost);
        //    ss = new ServerSocket(serverPort, 50, listenAddr);

            // Why backlog = 50? According the JDK 1.4 javadoc,
            // that's the default value for ServerSocket().
        //}
        
        //create a regular server socket.  (SSLServerSocketFactory is null).
        ss = createServerSocket(null, serverPort, servletHost);
        
        try {
        ss.setSoTimeout(5000);
        } catch (SocketException e) {
        parent.servletContext.log("WARNING: HttpTunnelTcpListener["+
               ss.toString()+"]setSoTimeout("+5000+"): "+e.toString()); 
        }

        if (rxBufSize > 0) {
            try {
            ss.setReceiveBufferSize(rxBufSize);
            } catch (SocketException e) {
            parent.servletContext.log("WARNING: HttpTunnelTcpListener["+
                   ss.toString()+"]setReceiveBufferSize("+rxBufSize+"): "+e.toString()); 
            }
        }

        setName("HttpTunnelTcpListener");
        setDaemon(true);
        servletName = "HttpTunnelServlet";

        parent.servletContext.log(servletName + ": listening on port " +
            serverPort + " ...");
    }

    // start SSLServerSocket
    public HelperThread(int serverPort, String servletHost, int rxBufSize,
        String ksloc, String password, ServerLinkTable p)
        throws IOException {
        this.parent = p;
        closed = false;

        SSLServerSocketFactory ssf = getServerSocketFactory(ksloc, password);

        //if (servletHost == null) {
        //    ss = (ServerSocket) ssf.createServerSocket(serverPort);
        //} else {
        //    InetAddress listenAddr = InetAddress.getByName(servletHost);
        //    ss = (ServerSocket) ssf.createServerSocket(serverPort, 50,
        //            listenAddr);

            // Why backlog = 50? According the JDK 1.4 javadoc,
            // that's the default value for ServerSocket().
        //}
        
        ss = createServerSocket(ssf, serverPort, servletHost);
        
        try {
        ss.setSoTimeout(5000);
        } catch (SocketException e) {
        parent.servletContext.log("WARNING: HttpsTunnelTcpListener["+
                   ss.toString()+"]setSoTimeout("+5000+"): "+e.toString()); 
        }

        if (rxBufSize > 0) {
            try {
            ss.setReceiveBufferSize(rxBufSize);
            } catch (SocketException e) {
            parent.servletContext.log("WARNING: HttpsTunnelTcpListener["+
                   ss.toString()+"]setReceiveBufferSize("+rxBufSize+"): "+e.toString()); 
            }
             
        }

        setName("HttpsTunnelTcpListener");
        setDaemon(true);
        servletName = "HttpsTunnelServlet";

        parent.servletContext.log(servletName + ": listening on port " +
            serverPort + " ...");
    }
        
    
    
    private ServerSocket createServerSocket(SSLServerSocketFactory ssf, int serverPort,
			String servletHost) throws IOException {

		ServerSocket serverSocket = null;
		int retryCount = 0;

		while (serverSocket == null) {

			retryCount++;

			try {
				
				if (ssf != null) {
					serverSocket = doCreateSSLServerSocket(ssf, serverPort, servletHost);
				} else {
					serverSocket = doCreateServerSocket(serverPort, servletHost);
				}
				
			} catch (java.net.BindException ioe) {

				// we only retry if it is a BindException.
				if (retryCount > 7) {
					throw ioe;
				} else {
					parent.servletContext.log(ioe.toString(), ioe);
				}

				pause(3000);
			}
		}

		return serverSocket;
	}
    
    private ServerSocket 
    doCreateServerSocket (int serverPort, String servletHost) throws IOException {
    	ServerSocket serverSocket = null;
    	
    	if (servletHost == null) {
    		serverSocket = new ServerSocket(serverPort);
        } else {
            InetAddress listenAddr = InetAddress.getByName(servletHost);
            serverSocket = new ServerSocket(serverPort, 50, listenAddr);

            // Why backlog = 50? According the JDK 1.4 javadoc,
            // that's the default value for ServerSocket().
        }

    	return serverSocket;
    }
    
    private ServerSocket 
    doCreateSSLServerSocket (SSLServerSocketFactory ssf, int serverPort, String servletHost) throws IOException {
    	
    	ServerSocket serverSocket = null;
    	
    	if (servletHost == null) {
    		serverSocket = (ServerSocket) ssf.createServerSocket(serverPort);
        } else {
            InetAddress listenAddr = InetAddress.getByName(servletHost);
            serverSocket = (ServerSocket) ssf.createServerSocket(serverPort, 50,
                    listenAddr);

            // Why backlog = 50? According the JDK 1.4 javadoc,
            // that's the default value for ServerSocket().
        }

    	return serverSocket;
    }
    
    /**
     * pause for the specified milli seconds.
     * @param ptime
     */
    private void pause (long ptime) {
    	try {
    		Thread.sleep(ptime);
    	} catch (Exception e) {
    		;
    	}
    }

    public void run() {
        while (!closed) {
            try {
                Socket s = ss.accept();

                synchronized (this) {
                    if (closed) {
                        s.close();

                        break;
                    }

                    parent.addServer(s);
                }

                parent.servletContext.log(servletName +
                    ": accepted socket connection. rcvbuf = " +
                    s.getReceiveBufferSize());
            } catch (InterruptedIOException e1) {
                parent.checkConnectionTimeouts();
            } catch (Exception e2) {
                parent.servletContext.log(servletName + ": accept(): " +
                    e2.getMessage());
            }
        }

        parent.servletContext.log(servletName + ": listen socket closed");
    }

    public synchronized void close() {
        closed = true;

        try {
            if (ss != null) {
                ss.close();
            }
        } catch (Exception e) {
        }
    }

    private SSLServerSocketFactory getServerSocketFactory(String ksloc,
        String password) throws IOException {
        SSLServerSocketFactory ssf = null;

        try {
            // set up key manager to do server authentication
            // Don't i18n Strings here.  They are key words
            SSLContext ctx;
            KeyManagerFactory kmf;
            KeyStore ks;

            // Get Keystore Location and  Passphrase here .....
            // Check if the keystore exists.  If not throw exception.
            // This is done first as if the keystore does not exist, then
            // there is no point in going further.
            File kf = new File(ksloc);

            if (!kf.exists()) {
                throw new IOException("Keystore does not exist - " + ksloc);
            }

            char[] passphrase = password.toCharArray();

            // Magic key to select the TLS protocol needed by JSSE
            // do not i18n these key strings.
            ctx = SSLContext.getInstance("TLS");
            kmf = KeyManagerFactory.getInstance("SunX509"); // Cert type
            ks = KeyStore.getInstance("JKS"); // Keystore type

            ks.load(new FileInputStream(ksloc), passphrase);
            kmf.init(ks, passphrase);

            TrustManager[] tm = new TrustManager[1];
            tm[0] = new DefaultTrustManager();

            // SHA1 random number generator
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");

            ctx.init(kmf.getKeyManagers(), tm, random);

            ssf = ctx.getServerSocketFactory();

            return ssf;
        } catch (IOException e) {
            throw e;
        } catch (Exception ex) {
            throw new IOException(ex.getMessage());
        }
    }
}


class Connection {
    private Vector pullQ = new Vector();
    private int pullPeriod = -1;
    private ServerLink link = null;
    private boolean inUse = false;
    private long lastRequestTime = 0;

    public Connection(ServerLink link) {
        this.link = link;
        lastRequestTime = System.currentTimeMillis();
    }

    public Vector getPullQ() {
        return pullQ;
    }

    public int getPullPeriod() {
        return pullPeriod;
    }

    public synchronized void setInUse(boolean inUse) {
        this.inUse = inUse;

        if (inUse == false) {
            this.lastRequestTime = System.currentTimeMillis();
        }
    }

    public synchronized boolean checkConnectionTimeout(long now) {
        if (inUse) {
            return false;
        }

        long timeout = 0;

        if (pullPeriod > 0) {
            timeout = (pullPeriod * 5);
        } else {
            timeout = HttpTunnelDefaults.DEFAULT_CONNECTION_TIMEOUT_INTERVAL;
        }

        timeout = timeout * 1000;

        return ((now - lastRequestTime) > timeout);
    }

    public ServerLink getServerLink() {
        return link;
    }

    public void setPullPeriod(int pullPeriod) {
        this.pullPeriod = pullPeriod;
    }
}

/*
 * EOF
 */
