/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package net.jini.jeri.kerberos;

import com.sun.jini.action.GetIntegerAction;
import com.sun.jini.discovery.internal.EndpointInternals;
import com.sun.jini.discovery.kerberos.EndpointInternalsAccess;
import com.sun.jini.jeri.internal.connection.BasicConnManagerFactory;
import com.sun.jini.jeri.internal.connection.ConnManager;
import com.sun.jini.jeri.internal.connection.ConnManagerFactory;
import com.sun.jini.jeri.internal.connection.ServerConnManager;
import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.logging.Levels;
import net.jini.core.constraint.ClientAuthentication;
import net.jini.core.constraint.ClientMaxPrincipal;
import net.jini.core.constraint.ClientMaxPrincipalType;
import net.jini.core.constraint.ClientMinPrincipal;
import net.jini.core.constraint.ClientMinPrincipalType;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.ConnectionAbsoluteTime;
import net.jini.core.constraint.ConstraintAlternatives;
import net.jini.core.constraint.Delegation;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.core.constraint.ServerMinPrincipal;
import net.jini.io.UnsupportedConstraintException;
import net.jini.jeri.Endpoint;
import net.jini.jeri.OutboundRequest;
import net.jini.jeri.OutboundRequestIterator;
import net.jini.jeri.connection.OutboundRequestHandle;
import net.jini.jeri.connection.Connection;
import net.jini.jeri.connection.ConnectionEndpoint;
import net.jini.jeri.kerberos.KerberosUtil.Config;
import net.jini.jeri.kerberos.KerberosUtil.ConfigIter;
import net.jini.jeri.ServerEndpoint;
import net.jini.security.AuthenticationPermission;
import net.jini.security.proxytrust.TrustEquivalence;
import net.jini.security.Security;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import javax.net.SocketFactory;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.Subject;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;

/**
 * An {@link Endpoint} implementation that uses Kerberos as the
 * underlying network security protocol to support security related
 * invocation constraints its caller specified for the corresponding
 * remote request.  Instances of this class are referred to as the
 * endpoints of the Kerberos provider, while instances of {@link
 * KerberosServerEndpoint} are referred to as the server endpoints of
 * the provider.<p>
 *
 * Instances of this class are intended to be created by the {@link
 * net.jini.jeri.BasicJeriExporter} class when it calls {@link
 * net.jini.jeri.ServerEndpoint#enumerateListenEndpoints
 * enumerateListenEndpoints} on instances of
 * <code>KerberosServerEndpoint</code>. <p>
 *
 * The {@link KerberosTrustVerifier} may be used for establishing
 * trust in remote proxies that use instances of this class. <p>
 *
 * This class supports at least the following standard constraints: <p>
 *
 * <ul>
 * <li>{@link net.jini.core.constraint.Integrity#YES}
 * <li>{@link net.jini.core.constraint.Confidentiality}
 * <li>{@link net.jini.core.constraint.ClientAuthentication#YES}
 * <li>{@link net.jini.core.constraint.ConnectionAbsoluteTime}
 * <li>{@link net.jini.core.constraint.ServerAuthentication#YES}
 * <li>{@link net.jini.core.constraint.ClientMaxPrincipal}, when it
 *     contains at least one {@link KerberosPrincipal}
 * <li>{@link net.jini.core.constraint.ClientMaxPrincipalType}, when
 *     it contains the <code>KerberosPrincipal</code> class
 * <li>{@link net.jini.core.constraint.ClientMinPrincipal}, when it
 *     contains exactly one <code>KerberosPrincipal</code>
 * <li>{@link net.jini.core.constraint.ClientMinPrincipalType}, when
 *     it contains only the <code>KerberosPrincipal</code> class
 * <li>{@link net.jini.core.constraint.ServerMinPrincipal}, when it
 *     contains exactly one <code>KerberosPrincipal</code>
 * <li>{@link net.jini.core.constraint.Delegation}
 * <li>{@link net.jini.core.constraint.ConstraintAlternatives}, if the
 *     elements all have the same actual class and at least one
 *     element is supported
 * </ul> <p>
 *
 * An endpoint of this provider uses Kerberos Ticket Granting Tickets
 * (TGTs), which are stored as private credentials in the {@link
 * Subject} associated with the access control context of the current
 * thread, to authenticate the caller of the remote request to the
 * server. <p>
 *
 * A TGT is an instance of {@link KerberosTicket} whose server
 * principal is of the form <code>"krbtgt/REALM@REALM"</code>.  The
 * client principal of the TGT indicates what principal the endpoint
 * can use the TGT to authenticate the caller as to the server. <p>
 *
 * Instances of this class contain a server host name, a server TCP
 * port number, a server principal, as well as an optional {@link
 * SocketFactory} for customizing the type of <code>Socket</code> to
 * use. <p>
 *
 * A <code>SocketFactory</code> used with instances of this class
 * should be serializable, and should implement {@link Object#equals
 * Object.equals} to return <code>true</code> when passed an instance
 * that represents the same (functionally equivalent) socket
 * factory. <p>
 *
 * To make a remote request through an endpoint of this provider, the
 * caller or initiator of the request supplies the endpoint with a set
 * of <code>InvocationConstraints</code>.  The client principal used
 * for the request is a <code>KerberosPrincipal</code> that appears in
 * the principal set of the current subject, allowable by the given
 * set of constraints, whose corresponding TGT is in the private
 * credential set of the subject, and the <code>connect</code>
 * <code>AuthenticationPermission</code> has been granted to the
 * current access control context with the client principal as the
 * <code>local</code> principal and the <code>serverPrincipal</code>
 * contained in this endpoint as the <code>peer</code> principal. <p>
 *
 * If the set of constraints for the request specifies that encryption
 * and/or delegation is required or preferred, they will be enforced
 * by the provider.  If encryption is unspecified in the constraints,
 * the provider may or may not encrypt messages exchanged with the
 * server.  If delegation is unspecified in the constraints, the
 * provider will not do delegation for the request.  For delegation to
 * happen, the TGT for the request has to be forwardable. <p>
 *
 * This class uses the <a
 * href="../connection/doc-files/mux.html">Jini(TM) extensible remote
 * invocation (Jini ERI) multiplexing protocol</a> to map outgoing
 * requests to the underlying secure connection streams. <p>
 *
 * The secure connection streams in this provider are implemented
 * using the Kerberos Version 5 GSS-API Mechanism, defined in <a
 * href="http://www.ietf.org/rfc/rfc1964.txt">RFC 1964</a>, over
 * socket connections between client and server endpoints. <p>
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * This Kerberos provider implementation uses the <a
 * href="http://www.ietf.org/rfc/rfc2853.txt"> Java(TM) GSS-API</a> to
 * provide the underlying Kerberos network authentication protocol
 * support. <p>
 *
 * The implementation does not automatically renew any renewable TGTs
 * in the <code>Subject</code> corresponding to any outbound
 * request. The assumption is that an endpoint of this provider should
 * merely be a consumer of the principals and credentials of the
 * <code>Subject</code>, and never change its content. But if new TGTs
 * are added into the <code>Subject</code> or old TGTs in the
 * <code>Subject</code> are renewed by means outside this provider,
 * the endpoint will pick up and use these new TGTs for new requests
 * after the old ones have expired. <p>
 *
 * This class uses the following {@link Logger} to log information at
 * the following logging levels: <p>
 *
 * <table border="1" cellpadding="5" summary="Describes logging to the
 *     client logger performed by endpoint classes in this package at
 *     different logging levels">
 * 
 *     <caption halign="center" valign="top"><b><code>
 * 	net.jini.jeri.kerberos.client</code></b></caption>
 * 
 *     <tr> <th scope="col"> Level <th scope="col"> Description
 * 
 *     <tr> <td> {@link java.util.logging.Level#WARNING WARNING}
 *          <td> failure to register with discovery provider
 *     <tr> <td> {@link com.sun.jini.logging.Levels#FAILED FAILED}
 *          <td> problem to support constraint requirements, connect to
 *               server through socket, establish {@link
 *               org.ietf.jgss.GSSContext} to server over established
 *               connections, or wrap/unwrap GSS tokens <tr> <td> {@link
 *               com.sun.jini.logging.Levels#HANDLED HANDLED} <td>
 *               failure to set TCP no delay or keep alive properties on
 *               sockets
 *     <tr> <td> {@link java.util.logging.Level#FINE FINE}
 *          <td> endpoint creation, {@link
 *               net.jini.jeri.Endpoint#newRequest newRequest}
 *               invocation, request handle creation, connection
 *               configuration decesions, socket creation, connection
 *               open/close, connection reuse decesions,
 *               <code>GSSContext</code> establishment
 *     <tr> <td> {@link java.util.logging.Level#FINEST FINEST}
 *          <td> data message encoding/decoding using
 *               <code>GSSContext</code>
 * </table> <p>
 *
 * Instances of this class recognize the following system properties:
 * <p>
 * 
 * <ul>
 * <li>com.sun.jini.jeri.kerberos.KerberosEndpoint.minGssContextLifetime -
 *     Minimum number of seconds of remaining lifetime a {@link
 *     GSSContext} of an existing connection has to have before it can
 *     be considered as a candidate connection to be chosen for a new
 *     request. The default is 30.
 * <li>com.sun.jini.jeri.kerberos.KerberosEndpoint.maxGssContextRetries -
 *     <a href="http://www.ietf.org/rfc/rfc1510.txt">RFC 1510</a>
 *     specifies that if a KDC or server receives two authenticators
 *     with the same client and server pair and timestamps of the
 *     same microsecond, the second will be considered a replay
 *     and will be rejected.  This means if multiple session ticket
 *     requests of the same client and server principal pair and
 *     microsecond timestamps are received at a KDC, only the first
 *     one will succeed, and the rest will be considered replays
 *     and will be rejected by the KDC.  For this reason, the Kerberos
 *     provider catches the "replay" exception and retries the
 *     corresponding <code>GSSContext</code> initialization
 *     handshake.  This system property controls the maximum number
 *     of retries a <code>KerberosEndpoint</code> will conduct.  The
 *     default is 3.
 * </ul> <p>
 *
 * @author Sun Microsystems, Inc.
 * @see KerberosServerEndpoint
 * @see KerberosTrustVerifier
 * @since 2.0
 */
public final class KerberosEndpoint
    implements Endpoint, TrustEquivalence, Serializable
{
    private static final long serialVersionUID = -880347439811805543L;

    /** JERI Kerberos client transport logger */
    private static final Logger logger =
	Logger.getLogger("net.jini.jeri.kerberos.client");

    /** 
     * Name or ip address of the server host.
     *
     * @serial
     */
    private final String serverHost;

    /**
     * Port that the server is listening on for incoming connection
     * requests.
     *
     * @serial
     */
    private final int serverPort;

    /**
     * Principal of which the server is capable of authenticating as.
     *
     * @serial
     */
    private final KerberosPrincipal serverPrincipal;

    /**
     * The socket factory that this <code>KerberosEndpoint</code> uses
     * to create <code>java.net.Socket</code> objects.
     *
     * @serial
     */
    private final SocketFactory csf;

    /** GSSManager instance used by all endpoints in a JVM */
    private static final GSSManager gssManager = GSSManager.getInstance();

    /**
     * Maximum number of entries allowed in the soft cache of a
     * Kerberos endpoint. The default is 64.
     */
    private static final int maxCacheSize =
	((Integer) AccessController.doPrivileged(
	    new GetIntegerAction(
		"com.sun.jini.jeri.kerberos.KerberosEndpoint.maxCacheSize",
		64))).intValue();

    /**
     * Minimum number of seconds of life time a {@link GSSContext} of
     * an existing connection has to have before it can be considered
     * as a candidate connection to be chosen for a new
     * request. The default is 30.
     */
    private static final int minGssContextLifetime =
	((Integer) AccessController.doPrivileged(
	    new GetIntegerAction(
		"com.sun.jini.jeri.kerberos.KerberosEndpoint." +
		"minGssContextLifetime", 30))).intValue();

    /** Maximum retries for initial {@link GSSContext} handshake. */
    private static final int maxGssContextRetries =
	((Integer) AccessController.doPrivileged(
	    new GetIntegerAction(
		"com.sun.jini.jeri.kerberos.KerberosEndpoint." +
		"maxGssContextRetries", 3))).intValue();

    /**
     * A cache maintains soft reference to its values. This cache is
     * keyed by security constraints and subject used for the
     * corresponding request, its value entries encapsulate the result
     * of the analysis of the constraints and principals in the
     * subject, without checking permissions and private credentials.
     */
    private transient KerberosUtil.SoftCache softCache;

    /**
     * The <code>ConnectionEndpoint</code> this endpoint passes
     * to its <code>connManager</code> to create connections.
     */
    private transient ConnectionEndpointImpl connectionEndpoint;

    /**
     * The <code>ConnManager</code> this endpoint uses to create
     * connections.
     */
    private transient ConnManager connManager;

    /**
     * If set to true, causes the endpoint not to connect sockets it
     * obtains from its socket factory.
     */
    private transient boolean disableSocketConnect;

    /**
     * Weak set of canonical instances; in order to use WeakHashMap,
     * maps canonical instances to weak references to themselves.
     */
    private static final Map internTable = new WeakHashMap(5);

    /* Register a back door interface for use by discovery providers. */
    static {
	KerberosEndpointInternals.registerDiscoveryBackDoor();
    }

    //-----------------------------------
    //           constructors
    //-----------------------------------

    /**
     * Creates an endpoint of this Kerberos provider.
     *
     * @param serverHost the name or ip address of the server host
     *        this endpoint will connect to
     * @param serverPort the server port
     * @param serverPrincipal principal the server can authenticate as
     * @param csf the <code>SocketFactory</code> to use for this
     *        <code>KerberosEndpoint</code>, or <code>null</code>
     * @throws NullPointerException if <code>serverHost</code> or
     *         <code>serverPrincipal</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>serverPort</code> is
     *         not in the range of <code>1</code> to
     *         <code>65535</code>
     */
    private KerberosEndpoint(String serverHost, int serverPort,
			     KerberosPrincipal serverPrincipal,
			     SocketFactory csf)
    {
	if (serverHost == null)
	    throw new NullPointerException("serverHost is null");

	if (serverPort <= 0 || serverPort > 0xFFFF) {
	    throw new IllegalArgumentException(
	        "server port number out of range 1-65535: serverPort = " +
		serverPort);
	}

	if (serverPrincipal == null)
	    throw new NullPointerException("serverPrincipal is null");

	this.serverHost = serverHost;
	this.serverPort = serverPort;
	this.serverPrincipal = serverPrincipal;
	this.csf = csf;
	logger.log(Level.FINE, "created {0}", this);
    }

    //-----------------------------------
    //          public methods
    //-----------------------------------

    /**
     * Returns a <code>KerberosEndpoint</code> instance for the given
     * server host name, TCP port number, and server
     * principal. Internally this endpoint uses {@link Socket} objects
     * to connect to its server endpoint.
     *
     * @param serverHost the host for the endpoint to connect to
     * @param serverPort the TCP port on the given host for the
     *        endpoint to connect to
     * @param serverPrincipal principal the server can authenticate as
     * @return a <code>KerberosEndpoint</code> instance
     * @throws NullPointerException if <code>serverHost</code> or
     *         <code>serverPrincipal</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>serverPort</code> is
     *         not in the range of <code>1</code> to
     *         <code>65535</code>
     */
    public static KerberosEndpoint
	getInstance(String serverHost, int serverPort,
		    KerberosPrincipal serverPrincipal)
    {
	return intern(new KerberosEndpoint(serverHost, serverPort,
					   serverPrincipal, null));
    }

    /**
     * Returns a <code>KerberosEndpoint</code> instance for the given
     * server host name, TCP port number, server principal, and
     * <code>SocketFactory</code>.
     *
     * <p>If the socket factory argument is <code>null</code>, then
     * this endpoint will create {@link Socket} objects directly.
     *
     * @param serverHost the host for the endpoint to connect to
     * @param serverPort the TCP port on the given host for the
     *        endpoint to connect to
     * @param serverPrincipal principal the server can authenticate as
     * @param csf the <code>SocketFactory</code> to use for this
     *        <code>KerberosEndpoint</code>, or <code>null</code>
     * @return a <code>KerberosEndpoint</code> instance
     * @throws NullPointerException if <code>serverHost</code> or
     *         <code>serverPrincipal</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>serverPort</code> is
     *         not in the range of <code>1</code> to
     *         <code>65535</code>
     *
     * @see javax.net.SocketFactory
     */
    public static KerberosEndpoint
	getInstance(String serverHost, int serverPort,
		    KerberosPrincipal serverPrincipal,
		    SocketFactory csf)
    {
	return intern(new KerberosEndpoint(serverHost, serverPort,
					   serverPrincipal, csf));
    }

    /**
     * Returns the server host that this endpoint connects to.
     *
     * @return the server host that this endpoint connects to
     */
    public String getHost() {
	return serverHost;
    }

    /**
     * Returns the TCP port that this endpoint connects to.
     *
     * @return the TCP port that this endpoint connects to
     */
    public int getPort() {
	return serverPort;
    }

    /**
     * Returns the principal this endpoint requires the server
     * to authenticate as.
     *
     * @return the server principal
     */
    public KerberosPrincipal getPrincipal() {
	return serverPrincipal;
    }

    /**
     * Returns the <code>SocketFactory</code> that this endpoint uses
     * to create {@link Socket} objects.
     *
     * @return the socket factory that this endpoint uses to create
     *         sockets, or <code>null</code> if no factory is used
     */
    public SocketFactory getSocketFactory() {
	return csf;
    }

    // javadoc is inherited from the Endpoint interface
    public OutboundRequestIterator newRequest(
	InvocationConstraints constraints)
    {
        if (constraints == null)
	    throw new NullPointerException("constraints cannot be null");

	logger.log(Level.FINE, "newRequest requested with constraints:\n" +
		   "{0}", constraints);

	Subject clientSubject = (Subject) Security.doPrivileged(
	    new PrivilegedAction() {
		    public Object run() {
			return Subject.getSubject(
			    AccessController.getContext());
		    }
		});

	CacheKey key = new CacheKey(clientSubject, constraints);
	RequestHandleImpl handle = (RequestHandleImpl) softCache.get(key);

	if (handle == null || !handle.reusable(clientSubject)) {
	    handle = new RequestHandleImpl(clientSubject, constraints);
	    
	    if (logger.isLoggable(Level.FINE)) {
		logger.log(Level.FINE, "new request handle has been " +
			   "constructed:\n{0}", new Object[] {handle});
	    }
	    
	    softCache.put(key, handle);
	}

	return connManager.newRequest(handle);
    }

    /**
     * Returns <code>true</code> if the argument is an instance of
     * <code>KerberosEndpoint</code> with the same values for server
     * principal, server host, and port; and either both this instance
     * and the argument have <code>null</code> socket factories, or
     * the factories have the same actual class and are equal; and
     * returns <code>false</code> otherwise.
     */
    public boolean checkTrustEquivalence(Object obj) {
	return equals(obj);
    }

    /** Returns a hash code value for this object. */
    public int hashCode() {
	return getClass().getName().hashCode() ^
	    serverPrincipal.hashCode() ^ serverHost.hashCode() ^ serverPort ^
	    (csf != null ? csf.hashCode() : 0);
    }

    /**
     * Two instances of this class are equal if they contain the same
     * server principal, host, and port, and their socket factories
     * are both <code>null</code> or have the same actual class and
     * are equal.
     */
    public boolean equals(Object obj) {
	if (obj == this) {
	    return true;
	} else if (!(obj instanceof KerberosEndpoint)) {
	    return false;
	}
	KerberosEndpoint oep = (KerberosEndpoint) obj;
	return serverPrincipal.equals(oep.serverPrincipal) && 
	    serverHost.equals(oep.serverHost) &&
	    serverPort == oep.serverPort &&
	    Util.sameClassAndEquals(csf, oep.csf);
    }

    /** Returns a string representation of this endpoint. */
    public String toString() {
	return "KerberosEndpoint[serverHost=" + serverHost + 
	    " serverPort=" + serverPort + 
	    " serverPrincipal=" + serverPrincipal + 
	    (csf == null ? "" : "csf = " + csf.toString()) + "]";
    }

    //-----------------------------------
    //          private methods
    //-----------------------------------

    /** Resolves deserialized instance to equivalent canonical instance. */
    private Object readResolve() {
	return intern(this);
    }

    /**
     * Read in a serialized instance and check that the deserialized
     * instance has the right fields.
     */
    private void readObject(ObjectInputStream ois)
        throws IOException, ClassNotFoundException
    {
	ois.defaultReadObject();
	
	if (serverHost == null) {
	    throw new InvalidObjectException("serverHost is null");
	} else if (serverPort <= 0 || serverPort > 0xFFFF) {
	    throw new InvalidObjectException(
		"server port number out of range 1-65535: " +
		"serverPort = : " + serverPort);
	} else if (serverPrincipal == null) {
	    throw new InvalidObjectException("serverPrincipal is null");
	}
    }

    /** Returns canonical instance equivalent to given instance. */
    private static KerberosEndpoint intern(KerberosEndpoint endpoint) {
	synchronized (internTable) {
	    Reference ref = (WeakReference) internTable.get(endpoint);
	    if (ref != null) {
		KerberosEndpoint canonical = (KerberosEndpoint) ref.get();
		if (canonical != null) {
		    return canonical;
		}
	    }

	    // construct these only if endpoint passes intern test
	    endpoint.softCache =
		new KerberosUtil.SoftCache(maxCacheSize);
	    endpoint.connectionEndpoint =
		endpoint.new ConnectionEndpointImpl();
	    endpoint.connManager = new BasicConnManagerFactory().create(
		endpoint.connectionEndpoint);

	    internTable.put(endpoint, new WeakReference(endpoint));
	    return endpoint;
	}
    }

    /** 
     * Make sure that the passed in endpoint instance equals the
     * enclosing endpoint instance.
     */
    private void checkEndpoint(KerberosEndpoint ep) {
	if (!this.equals(ep)) {
	    throw new IllegalArgumentException(
		"endpoint mismatch, this endpoint is: " + this +
		", passed in endpoint is: " + ep);
	}
    }

    /**
     * Make sure that the passed in request handle has the right type,
     * and was previously instantiated in this endpoint.
     */
    private RequestHandleImpl checkRequestHandleImpl(Object h) {
	if (h == null) {
	    throw new NullPointerException("Handle cannot be null");
	} else if (!(h instanceof RequestHandleImpl)) {
	    throw new IllegalArgumentException("Unexpected handle type: " + h);
	}
	RequestHandleImpl rh = (RequestHandleImpl) h;
	checkEndpoint(rh.getEndpoint());
	return rh;
    }

    /**
     * Make sure that the passed in context has the right type, and
     * was previously instantiated in this endpoint.
     */
    private ConnectionImpl checkConnection(Object c) {
	if (!(c instanceof ConnectionImpl)) {
	    throw new IllegalArgumentException(
		"Expected connection type is " + ConnectionImpl.class + 
		", while " + c + " is passed in.");
	}
	ConnectionImpl conn = (ConnectionImpl) c;
	checkEndpoint(conn.getEndpoint());
	return conn;
    }
    
    //-----------------------------------
    //       private inner classes
    //-----------------------------------

    /** Support EndpointInternals, for use by discovery providers */
    private static final class KerberosEndpointInternals
	implements EndpointInternals
    {
	/** Register back door. */
	static void registerDiscoveryBackDoor() {
	    final KerberosEndpointInternals backDoor =
		new KerberosEndpointInternals();
	    try {
		Security.doPrivileged(new PrivilegedAction() {
		    public Object run() {
			EndpointInternalsAccess.set(backDoor);
			return null;
		    }
		});
	    } catch (Throwable t) {
		logger.log(Level.WARNING,
			   "Problem registering with discovery provider", t);
	    }
	}

	/* -- Implement EndpointInternals -- */

	public void disableSocketConnect(Endpoint endpoint) {
	    ((KerberosEndpoint) endpoint).disableSocketConnect = true;
	}

	public void setConnManagerFactory(Endpoint endpoint,
					  ConnManagerFactory factory)
	{
	    KerberosEndpoint kep = (KerberosEndpoint) endpoint;
	    kep.connManager = factory.create(kep.connectionEndpoint);
	}

	public void setServerConnManager(ServerEndpoint endpoint,
					 ServerConnManager manager)
	{
	    KerberosServerEndpoint ksep = (KerberosServerEndpoint) endpoint;
	    ksep.serverConnManager = manager;
	}

	public InvocationConstraints getUnfulfilledConstraints(
	    OutboundRequestHandle handle)
	{
	    return ((RequestHandleImpl) handle).unfulfilledConstraints;
	}
    }

    // error code constants
    private static final int NO_ERROR = -1;
    private static final int UNSUPPORTABLE_CONSTRAINT_REQUIRED = 0;
    private static final int NULL_SUBJECT = 1;
    private static final int NO_CLIENT_PRINCIPAL = 2;
    private static final int UNSATISFIABLE_CONSTRAINT_REQUIRED = 3;

    private static final String[] ERROR_STRINGS = {
	"UNSUPPORTABLE_CONSTRAINT_REQUIRED", "NULL_SUBJECT",
	"NO_CLIENT_PRINCIPAL", "UNSATISFIABLE_CONSTRAINT_REQUIRED"};

    /** <code>OutboundRequestHandle</code> implementation */
    private final class RequestHandleImpl implements OutboundRequestHandle {

	/** Subject from which private credentials will be extracted */
	private Subject clientSubject;

	/** Constraints of this request handle */
	private InvocationConstraints constraints;

	/** True if the subject is readonly when this handle is instantiated */
	private boolean subjectReadOnly;

	/**
	 * In case of subject is not readonly, snapshot its Kerberos
	 * principals
	 */
	private Set subjectClientPrincipals;

	/**
	 * The set of Kerberos principals allowed by the constraint
	 * requirements and found in the principal set of the subject.
	 */
	private Set clientPrincipals;

	/**
	 * Error code of this request handle.  A request will not
	 * succeed if its handle's errorCode != NO_ERROR.
	 */
	private int errorCode = NO_ERROR;

	/**
	 * The message explains the reason of the failure, later on an
	 * <code>UnsupportedConstraintException</code> will be
	 * instantiated using this message and conditionally thrown to
	 * the caller, depending on whether the caller has the
	 * "getSubject" <code>AuthPermission</code>.
	 */
	private String detailedExceptionMsg;

	/**
	 * Set of configurations that can satisfy the given set of
	 * constraints using principals in the current subject.  The
	 * set is composed without checking the private credentials of
	 * the subject and AuthenticationPermissions of the caller.
	 */
	private Config[] configs;

	/**
	 * Constraints that must be partially or fully implemented by
	 * higher layers for an outbound request using this handle.
	 */
	private InvocationConstraints unfulfilledConstraints;

	/** Absolute time by when the connection must be established */
        long connectionAbsoluteTime;

	/**
	 * Construct a <code>RequestHandleImpl</code>. <p>
	 *
	 * For each outgoing request, the computation took to
         * determine the {@link KerberosUtil.Config} for the {@link
         * ConnectionImpl Connection} carrying it is divided into two
         * phases.  The first phase includes steps that no
         * <code>AuthenticationPermission</code> checks are needed,
         * which are done once for each constraints and subject pair
         * in this constructor.  The second phase contains steps that
         * require <code>AuthenticationPermission</code> checks, which
         * are done for each request in {@link
         * RequestHandleImpl#getConfigs}.  All problems, even
         * encountered in the first phase, are reported in the second
         * phase in <code>getConfigs</code>. <p>
	 *
	 * The computation steps taken in phase one are listed as the
	 * following:
	 *
	 * <ul>
	 * <li> Check for unsupportable requirements
	 * <li> Determine client principal candidate set based on
	 *      client principal constraints in requirements and
	 *      current subject
	 * <li> Generate all possible <code>Configs</code> based on
	 *      the set of client principal candidates, and whether
	 *      encryption and delegation are mentioned/allowed by the
	 *      constraints
	 * <li> Pass each <code>Config</code> through the constraints
	 *      and filter out those that conflict with requirements
	 * <li> Reorder the remaining <code>Config</code> list by
	 *      preferences
	 * </ul> <p>
	 *
	 * @param clientSubject the client subject that contains
	 *        client principals and TGTs, can not be
	 *        <code>null</code>.
	 * @param constraints the security constraint set, can not be
	 *        <code>null</code>
	 */
	RequestHandleImpl(Subject clientSubject,
			  InvocationConstraints constraints)
	{
	    /* unsupportable constraint has to be checked before any
	       other security sensitive things are checked, so a
	       detailed exception regarding to it can always be thrown
	       regardless whether the caller has the getSubject
	       permission */
	    for (Iterator iter = constraints.requirements().iterator();
		 iter.hasNext(); )
	    {
		InvocationConstraint c = (InvocationConstraint) iter.next();
		if (!KerberosUtil.isSupportableConstraint(c)) {
		    errorCode = UNSUPPORTABLE_CONSTRAINT_REQUIRED;
		    detailedExceptionMsg = "A constraint unsupportable by " +
			"this endpoint has been required: " + c;
		    return;
		}
	    }

	    /* All Kerberos principals allowed by the constraints.  If
	       the resulting set is empty, it means no client min/max
	       principal constraints found in the constraints, instead
	       of no principals allowed. */
	    clientPrincipals = new HashSet();
	    for (Iterator iter = constraints.requirements().iterator();
		 iter.hasNext(); )
	    {
		if (!KerberosUtil.collectCpCandidates(
		    (InvocationConstraint) iter.next(),
		    clientPrincipals))
		{
		    errorCode = UNSUPPORTABLE_CONSTRAINT_REQUIRED;
		    detailedExceptionMsg = "Client principal constraint " +
			"related conflicts found in the given set of " +
			"constraints: " + constraints;
		    return;
		}
	    }

	    if (clientSubject == null) {
		errorCode = NULL_SUBJECT;
		detailedExceptionMsg = "JAAS login has not been done " +
		    "properly, the subject associated with the current " +
		    "AccessControlContext is null.";
		return;
	    }

	    this.clientSubject = clientSubject;
	    this.constraints = constraints;

	    subjectReadOnly = clientSubject.isReadOnly();
	    subjectClientPrincipals = getClientPrincipals(clientSubject);

	    if (subjectClientPrincipals.size() == 0) {
		errorCode = NO_CLIENT_PRINCIPAL;
		detailedExceptionMsg = "JAAS login has not been done " +
		    "properly, the subject associated with the current " +
		    "AccessControlContext contains no KerberosPrincipal.";
		return;
	    }

	    if (clientPrincipals.size() > 0) {
		clientPrincipals.retainAll(subjectClientPrincipals);
	    } else {
		clientPrincipals = subjectClientPrincipals;
	    }

	    boolean canDeleg = false;
	    if (KerberosUtil.containsConstraint(
		constraints.requirements(), Delegation.YES) ||
		KerberosUtil.containsConstraint(
		    constraints.preferences(), Delegation.YES))
	    {
		canDeleg = true;
	    }

	    // enumerate all possible configs and filter them by constraints
	    ArrayList configArr = new ArrayList();
	  outer:
	    for (ConfigIter citer = new ConfigIter(
		     clientPrincipals, serverPrincipal, canDeleg);
		 citer.hasNext(); )
	    {
		Config config = citer.next();
		for (Iterator jter = constraints.requirements().iterator();
		     jter.hasNext(); )
		{
		    InvocationConstraint c =
			(InvocationConstraint) jter.next();
		    if (!KerberosUtil.isSatisfiable(config, c))
			continue outer;
		}
		configArr.add(config);
	    }

	    if (configArr.size() == 0) {
		errorCode = UNSATISFIABLE_CONSTRAINT_REQUIRED;
		detailedExceptionMsg = "Constraints unsatisfiable by this " +
		    "endpoint with the current subject have been required: " +
		    constraints + ", while the KerberosPrincipal set of " +
		    "the subject is: " + subjectClientPrincipals;
		return;
	    }

	    configs = (Config[]) configArr.toArray(
		new Config[configArr.size()]);

	    // reorder configs by the num of preferences a config can satisfy
	    for (int i = 0; i < configs.length; i++) {
		for (Iterator iter = constraints.preferences().iterator();
		     iter.hasNext(); )
	        {
		    InvocationConstraint c =
			(InvocationConstraint) iter.next();
		    if (KerberosUtil.isSatisfiable(configs[i], c))
			configs[i].prefCount++;
		}
	    }
	    Arrays.sort(configs, new Comparator() {
		    public int compare(Object o1, Object o2) {
			Config config1 = (Config) o1;
			Config config2 = (Config) o2;
			// sort to descending order by prefCount
			return config2.prefCount - config1.prefCount;
		    }
		});

	    if (KerberosUtil.containsConstraint(
		constraints.requirements(), Integrity.YES))
	    {
		unfulfilledConstraints =
		    KerberosUtil.INTEGRITY_REQUIRED_CONSTRAINTS;
	    } else if (KerberosUtil.containsConstraint(
		constraints.preferences(), Integrity.YES))
	    {
		unfulfilledConstraints =
		    KerberosUtil.INTEGRITY_PREFERRED_CONSTRAINTS;
	    } else {
		unfulfilledConstraints = InvocationConstraints.EMPTY;
	    }

	    connectionAbsoluteTime = Math.min(
		computeConnectionTimeLimit(constraints.requirements()),
		computeConnectionTimeLimit(constraints.preferences()));
	}

	/** Returns a string representation of this request handle. */
	public String toString() {
	    StringBuffer b = new StringBuffer(
		"KerberosEndpoint.RequestHandleImpl[\n");
	    if (errorCode != NO_ERROR) {
		b.append("errorCode=" + ERROR_STRINGS[errorCode]);
		b.append(" errorExceptionMsg=" + detailedExceptionMsg);
	    } else {
		b.append("constraints=" + constraints);
		b.append("\nprincipalsInSubject=" + subjectClientPrincipals);
		b.append("\nallowedConfigs=[\n");
		if (configs.length > 0)
		    b.append(configs[0]);
		for (int i = 1; i < configs.length; i++) {
		    b.append(",\n" + configs[i]);
		}
		b.append("],");
		b.append("\nunfulfilledConstraints=" + unfulfilledConstraints);
		b.append("\nconnectionAbsoluteTime=");
		if (connectionAbsoluteTime == Long.MAX_VALUE) {
		    b.append("NO_LIMIT");
		} else {
		    b.append(new Date(connectionAbsoluteTime));
		}
	    }
	    b.append(']');
	    return b.toString();
	}

	/**
	 * Check whether this cached request handle can be used for
	 * the given subject. It is assumed that the caller has
	 * already checked <code>==</code> on both the security
	 * constraints and subject.
	 */
	boolean reusable(Subject subject) {
	    if (subject == null || subjectReadOnly)
		return true; // null subject means error, reuse handle
	    Set cps = getClientPrincipals(subject);
	    return cps.equals(subjectClientPrincipals);
	}

	/**
	 * Get a list of satisfiable configurations.  Elements of the
	 * list is from the configs array of this handle, with those
	 * who failed their corresponding AuthenticationPermission
	 * and private credential (TGT) checks filtered out.  The
	 * returned list is ordered by decreasing preference.
	 *
	 * @return a list of satisfiable configurations in decreasing
	 *         preference order.
	 * @throws UnsupportedConstraintException if the caller has
	 *         required unsupported constraints, or there are
	 *         conflicts or unsatisfiable constraint in the
	 *         requirements, or the JAAS login has not been done
	 *         (Subject.getSubject(AccessController.getContext())
	 *         returns <code>null</code>), or no appropriate
	 *         Kerberos principal and corresponding TGT allowed by
	 *         the requirements can be found in the current
	 *         subject.  If the caller has not been granted
	 *         <code>javax.security.auth.AuthPermission("getSubject")
	 *         </code> and cause is not unsupported constraints
	 *         being required, the exception message will be
	 *         generic and enumerate all these possible causes.
	 *         Otherwise, the message will spell out the reason
	 *         caused the exception.
	 * @throws SecurityException if there is a security manager
	 *         and the caller has
	 *         <code>javax.security.auth.AuthPermission("getSubject")
	 *         </code> but not any
	 *         <code>AuthenticationPermission</code> whose local
	 *         principal is a member of the client principal
	 *         candidate set.  The action of the
	 *         <code>AuthenticationPermission</code> is either
	 *         <code>connect</code> or <code>delegate</code>,
	 *         determined by the requirements of the constraints.
	 */
        List getConfigs() throws UnsupportedConstraintException {

	    if (errorCode != NO_ERROR) {
		throw new UnsupportedConstraintException(
		    detailedExceptionMsg);
	    }

	    KerberosTicket[] tickets =
		(KerberosTicket[]) AccessController.doPrivileged(
		    new PrivilegedAction() {
			    public Object run() {
				return getTickets();
			    }
			});

	    ArrayList configList = new ArrayList(configs.length);

	    /* This illustrates how a detailed failure msg is derived:
	     *
	     *               |<-- stepsFromSuccess -->|
	     *
	     *                               TGT.forwardable
	     *                      TGT.yes
	     *            perm.yes           TGT.unforwardable
	     *                      TGT.no
	     * deleg.yes 
	     *            perm.no
	     *
	     *-------------------------------------------------------
	     *
	     *                      TGT.yes
	     *            perm.yes
	     *                      TGT.no
	     * deleg.no 
	     *            perm.no
	     *
	     */
	    int delegYesStepsFromSuccess = 3;
	    KerberosPrincipal delegYesCp = null;

	    int delegNoStepsFromSuccess = 2;
	    KerberosPrincipal delegNoCp = null;

	    HashMap hasPermMap = new HashMap();
	    for (int i = 0; i < configs.length; i++) {
		AuthenticationPermission perm = getAuthenticationPermission(
		    configs[i].clientPrincipal, configs[i].deleg);
		Boolean hasPerm = (Boolean) hasPermMap.get(perm);
		if (hasPerm == null) {
		    try {
			KerberosUtil.checkAuthPermission(perm);
			hasPermMap.put(perm, Boolean.TRUE); // check succeed
		    } catch (SecurityException e) {
			hasPermMap.put(perm, Boolean.FALSE); // check failed
			continue;
		    }
		} else if (hasPerm == Boolean.FALSE) {
		    continue;
		} // else: permission check has been done and succeeded

		if (configs[i].deleg) {
		    if (delegYesStepsFromSuccess > 2) {
			delegYesStepsFromSuccess = 2; // record the 1st
			delegYesCp = configs[i].clientPrincipal;
		    }
		    KerberosTicket t = findTicket(
			tickets, configs[i].clientPrincipal);
		    if (t != null) {
			if (delegYesStepsFromSuccess > 1) {
			    delegYesStepsFromSuccess = 1; // record the 1st
			    delegYesCp = configs[i].clientPrincipal;
			}
			if (t.isForwardable())
			    configList.add(configs[i]);
		    }
		} else {
		    if (delegNoStepsFromSuccess > 1) {
			delegNoStepsFromSuccess = 1; // record the 1st
			delegNoCp = configs[i].clientPrincipal;
		    }
		    if (findTicket(tickets, configs[i].clientPrincipal) !=
			null)
		    {
			configList.add(configs[i]);
		    }
		}
	    }

	    if (configList.size() == 0) { // no valid config found
		if (delegNoStepsFromSuccess < delegYesStepsFromSuccess) {
		    switch (delegNoStepsFromSuccess) {
		    case 1:
			throw new UnsupportedConstraintException(
			    "JAAS login has not been done properly, the " +
			    "subject associated with the current " +
			    "AccessControlContext does not contain a valid " +
			    "TGT for " + delegNoCp.getName());
		    case 2:
			throw new SecurityException(
			    "Caller does not have any of the following " +
			    "acceptable permissions: " +
			    hasPermMap.keySet());
		    default:
			throw new AssertionError("should not reach here");
		    }
		} else {
		    switch (delegYesStepsFromSuccess) {
		    case 1:
			throw new UnsupportedConstraintException(
			    "JAAS login has not been done properly, the " +
			    "subject associated with the current " +
			    "AccessControlContext contains a valid TGT for " +
			    delegYesCp.getName() + ", but the TGT is not " +
			    "forwardable.");
		    case 2:
			throw new UnsupportedConstraintException(
			    "JAAS login has not been done properly, the " +
			    "subject associated with the current " +
			    "AccessControlContext does not contain a valid " +
			    "TGT for " + delegYesCp.getName());
		    default:
			throw new AssertionError("should not reach here");
		    }
		}
	    }

	    return configList;
	}	

	/** Get the enclosing endpoint instance */
	KerberosEndpoint getEndpoint() {
	    return KerberosEndpoint.this;
	}

	/**
	 * Return the set of Kerberos principals contained in the
	 * given subject.
	 *
	 * @param subj the subject whose principals will be extracted
	 * @return the set of Kerberos principals
	 */
	private Set getClientPrincipals(Subject subj) {
	    Set cpset = subj.getPrincipals();
	    synchronized (cpset) {
		HashSet set = new HashSet(cpset.size());
		for (Iterator iter = cpset.iterator(); iter.hasNext();) {
		    Object p = iter.next();
		    if (p instanceof KerberosPrincipal)
			set.add(p);
		}
		return set;
	    }
	}

	/**
	 * Compute the connection time limit basing on the specified
	 * set of constraints.
	 *
	 * @param constraints the set of constraints based on which
	 * the connection time limit will be computed
	 * @return the resulting connection time limit
	 */
	private long computeConnectionTimeLimit(Set constraints) {
	    long timeLimit = Long.MAX_VALUE;
 	outer:
 	    for (Iterator iter = constraints.iterator(); iter.hasNext(); ) {
 		Object c = iter.next();
 		long constraintTimeLimit = Long.MIN_VALUE;
 		if (c instanceof ConstraintAlternatives) {
 		    // homogeneous constraint alternatives is assumed
 		    Set alts = ((ConstraintAlternatives) c).elements();
 		    for (Iterator jter = alts.iterator(); jter.hasNext(); ) {
 			Object alt = jter.next();
 			if (alt instanceof ConnectionAbsoluteTime) {
 			    long t = ((ConnectionAbsoluteTime) alt).getTime();
 			    if (constraintTimeLimit < t)
 				constraintTimeLimit = t;
 			} else {
 			    continue outer;
 			}
 		    }
 		} else if (c instanceof ConnectionAbsoluteTime) {
 		    constraintTimeLimit = 
 			((ConnectionAbsoluteTime) c).getTime();
 		} else {
 		    continue;
 		}
 
 		if (constraintTimeLimit < timeLimit) {
 		    timeLimit = constraintTimeLimit;
 		}
 	    }

	    return timeLimit;
	}

	/**
	 * Return all valid Ticket Granting Tickets (TGTs) in the
	 * clientSubject as an array. The server name of a TGT starts
	 * with "krbtgt/".
	 * 
	 * @return an array of valid TGTs
	 */
	private KerberosTicket[] getTickets() {
	    ArrayList tlist = new ArrayList();
	    Set creds = clientSubject.getPrivateCredentials();
	    synchronized (creds) {
		for (Iterator iter = creds.iterator(); iter.hasNext(); ) {
		    Object cred = iter.next();
		    if (cred instanceof KerberosTicket) {
			KerberosTicket ticket = (KerberosTicket) cred;
			if (ticket.getServer().getName().startsWith(
			    "krbtgt/") && !ticket.isDestroyed() &&
			    ticket.isCurrent())
			{
			    tlist.add(ticket);
			}
		    }
		}
	    }
	    return (KerberosTicket[]) tlist.toArray(
                new KerberosTicket[tlist.size()]);
	}

	private KerberosTicket findTicket(
	    KerberosTicket[] tickets, KerberosPrincipal p)
	{
	    String crealm = p.getRealm();
	    String srealm = serverPrincipal.getRealm();
	    String tgtName = "krbtgt/" + srealm + "@" + crealm;
	    for (int i = 0; i < tickets.length; i++) {
		if (tickets[i].getClient().equals(p) && 
		    tickets[i].getServer().getName().equals(tgtName))
		{
		    return tickets[i];
		}
	    }
	    return null;
	}

	private AuthenticationPermission getAuthenticationPermission(
	    KerberosPrincipal client, boolean deleg)
	{
	    String act;
	    if (deleg) {
		act = "delegate";
	    } else {
		act = "connect";
	    }
	    Set locals = Collections.singleton(client);
	    Set peers = Collections.singleton(serverPrincipal);
	    return new AuthenticationPermission(locals, peers, act);
	}
    }

    /** ConnectionEndpoint implementation class for this end point */
    private final class ConnectionEndpointImpl implements ConnectionEndpoint {

	// javadoc is inherited from the ConnectionEndpoint interface
	public Connection connect(OutboundRequestHandle handle)
	    throws IOException
	{
	    RequestHandleImpl rh = checkRequestHandleImpl(handle);
	    Config config = null;
	    Exception exceptionCaught = null;
	    try {
		/* do permission and credential check, pick the config
		   to be used */
		List configs = rh.getConfigs();
		config = (Config) configs.get(0);
		if (logger.isLoggable(Level.FINE)) {
		    logger.log(Level.FINE, "Passed in request handle " +
			       "is:\n{0},\nconfiguration list returned by " +
			       "getConfigs is:\n{1},\nin which the first " +
			       "one will be used.",
			       new Object[] {rh, configs});
		}
	    } catch (UnsupportedConstraintException e) {
		exceptionCaught = e;
	    } catch (SecurityException e) {
		exceptionCaught = e;
	    }

	    if (exceptionCaught != null) {
		if (logger.isLoggable(Levels.FAILED)) {
		    KerberosUtil.logThrow(
			logger, Levels.FAILED, this.getClass(), 
			"connect", "failed to find a supportable " +
			"connection configuration for the request", null, 
			exceptionCaught);
		}
		if (rh.errorCode == UNSUPPORTABLE_CONSTRAINT_REQUIRED)
		    throw (UnsupportedConstraintException) exceptionCaught;
		UnsupportedConstraintException genericException =
		    new UnsupportedConstraintException(
                        "Either there are conflicting or unsatisfiable " +
			"constraint requirements, " +
			"or the JAAS login has not been " +
			"done (Subject.getSubject(AccessController." +
			"getContext()) returns null), or no appropriate " +
                        "Kerberos principal and corresponding TGT " +
			"allowed by the requirements can be found in " +
			"the current subject. " + rh.constraints);
		KerberosUtil.secureThrow(exceptionCaught, genericException);
	    }

	    Socket sock;
	    if (csf != null) {
		try {
		    sock = csf.createSocket();
		} catch (IOException e) {
		    if (logger.isLoggable(Levels.FAILED)) {
			KerberosUtil.logThrow(
			    logger, Levels.FAILED, this.getClass(), 
			    "connect", "failed to create socket " +
			    "using the given SocketFactory {0}",
			    new Object[] {csf}, e);
		    }
		    throw e;
		}

		if (logger.isLoggable(Level.FINE)) {
		    logger.log(Level.FINE, "created socket {0} using " + 
			       "factory {1}", new Object[]{sock, csf});
		}
	    } else {
		sock = new Socket();
		logger.log(Level.FINE, "created socket {0}", sock);
	    }

	    try {
		sock.setTcpNoDelay(true);
	    } catch (SocketException e) {
		if (logger.isLoggable(Levels.HANDLED)) {
		    KerberosUtil.
			logThrow(logger, Levels.HANDLED, this.getClass(), 
				 "connect", "failed to setTcpNoDelay " +
				 "option for {0}", new Object[] {sock}, e);
		}
	    }

	    try {
		sock.setKeepAlive(true);
	    } catch (SocketException e) {
		if (logger.isLoggable(Levels.HANDLED)) {
		    KerberosUtil.
			logThrow(logger, Levels.HANDLED, this.getClass(), 
				 "connect", "failed to setKeepAlive " +
				 "options for {0}", new Object[] {sock}, e);
		}
	    }
	    
	    if (!disableSocketConnect) {
		try {
		    long timeout = rh.connectionAbsoluteTime -
			System.currentTimeMillis();

		    if (timeout <= 0) {
			throw new SocketTimeoutException(
			    "connection timeout passed before socket." +
			    "connect is called");
		    } else if (timeout > Integer.MAX_VALUE) {
			sock.connect(new InetSocketAddress(
			    serverHost, serverPort));
		    } else {
			sock.connect(new InetSocketAddress(
			    serverHost, serverPort), (int) timeout);
		    }
		} catch (IOException e) {
		    if (logger.isLoggable(Levels.FAILED)) {
			KerberosUtil.logThrow(
			    logger, Levels.FAILED, this.getClass(), "connect",
			    "failed to connect to server", null, e);
		    }
		    throw e;
		}
	    }
	    
	    Connection c = new ConnectionImpl(sock, config);
	    if (logger.isLoggable(Level.FINE)) {
		logger.log(Level.FINE, "New connection established:\n{0}",
			   new Object[] {c});
	    }
	    return c;
	}

	// javadoc is inherited from the ConnectionEndpoint interface
	public Connection connect(OutboundRequestHandle handle,
				  Collection active, Collection idle)
	{
	    RequestHandleImpl rh = checkRequestHandleImpl(handle);

	    if (active == null) {
		throw new NullPointerException(
		    "active collection cannot be null");
	    } else if (idle == null) {
		throw new NullPointerException(
		    "idle collection cannot be null");
	    }

	    // do permission and credential check, get all possible configs
	    List configList;
	    try {
		configList = rh.getConfigs();
	    } catch (Exception e) {
		return null; // the other connect will be called later
	    }

	    SecurityManager sm = System.getSecurityManager();

	    for (Iterator i = configList.iterator(); i.hasNext(); ) {
		Config config = (Config) i.next();
		for (Iterator j = active.iterator(); j.hasNext(); ) {
		    ConnectionImpl c = checkConnection(j.next());
		    if (c.satisfies(config)) {
			if (logger.isLoggable(Level.FINE)) {
			    logger.log(Level.FINE, "found an active " +
				       "connection for reusing:\n{0}\n{1}",
				       new Object[] {c, config});
			}
			if (sm != null)
			    sm.checkConnect(serverHost, serverPort);
			return c;
		    }
		}
	    }

	    for (Iterator i = configList.iterator(); i.hasNext(); ) {
		Config config = (Config) i.next();
		for (Iterator j = idle.iterator(); j.hasNext(); ) {
		    ConnectionImpl c = checkConnection(j.next());
		    if (c.switchTo(config)) {
			if (logger.isLoggable(Level.FINE)) {
			    logger.log(Level.FINE, "found an idle " +
				       "connection for reusing:\n{0}\n{1}",
				       new Object[] {c, config});
			}
			if (sm != null)
			    sm.checkConnect(serverHost, serverPort);
			return c;
		    }
		}
	    }

	    return null;
	}
    }

    /** Implementation class of the client side connection abstraction */
    private final class ConnectionImpl extends KerberosUtil.Connection
	implements Connection
    {
	/** Kerberos error code defined in RFC1510 (Request is a replay) */
	private static final int KRB_AP_ERR_REPEAT = 34;

	/** InputStream this connection will provide to the upper layer */
	private InputStream istream;

	/** OutputStream this connection will provide to the upper layer */
	private OutputStream ostream;

	/** 
	 * Establish a connection to server.
	 *
	 * @param socket socket over which the connection traffic will
	 *        be carried
	 * @param config a configuration object that specifies how the
	 *        connection should be setup
	 * @throws IOException if the connection establishment
	 *         handshake fails
	 */
	ConnectionImpl(Socket sock, Config config)
	    throws IOException
	{
	    super(sock);

	    clientPrincipal = config.clientPrincipal;
	    doEncryption = config.encry;
	    doDelegation = config.deleg;
	    connectionLogger = logger;

	    boolean done = false;
	    try {
		Security.doPrivileged(new PrivilegedExceptionAction() {
			public Object run() throws IOException, GSSException {
			    establishContext();
			    return null;
			}
		    });
		ostream = new KerberosUtil.ConnectionOutputStream(this);
		istream = new KerberosUtil.ConnectionInputStream(this);
		done = true;
	    } catch (PrivilegedActionException e) {
		Exception ex = e.getException();

		if (logger.isLoggable(Levels.FAILED)) {
		    KerberosUtil.logThrow(
			logger, Levels.FAILED, this.getClass(), "constructor",
			"failed to establish GSSContext for this connection " +
			"with {0}.", new Object[] {config}, ex);
		}

		if (ex instanceof GSSException) {
		    IOException ioe =
			new IOException("Failed to establish GSS " +
					"context for this connection.");
		    ioe.initCause(ex);
		    throw ioe;
		} else {
		    throw (IOException) ex;
		}
	    } finally {
		if (!done)
		    close();
	    }
	}

	// javadoc is inherited from the Connection interface
	public OutputStream getOutputStream() throws IOException {
	    return ostream;
	}

	// javadoc is inherited from the Connection interface
	public InputStream getInputStream() throws IOException {
	    return istream;
	}

	// javadoc is inherited from the Connection interface
	public SocketChannel getChannel() {
	    return null; // does not support channel for now
	}

	// javadoc is inherited from the Connection interface
	public void populateContext(
	    OutboundRequestHandle handle, Collection context)
	{
	    if (handle == null) {
		throw new NullPointerException("handle is null");
	    } else if (context == null) {
		throw new NullPointerException("context is null");
	    }
	}
	
	// javadoc is inherited from the Connection interface
	public InvocationConstraints getUnfulfilledConstraints(
	    OutboundRequestHandle handle)
	{
	    RequestHandleImpl rh = checkRequestHandleImpl(handle);
	    return rh.unfulfilledConstraints;
	}

	// javadoc is inherited from the Connection interface
	public void writeRequestData(OutboundRequestHandle handle,
				     OutputStream out)
	{
	    // got nothing to do here for this provider
	}

	// javadoc is inherited from the Connection interface
	public IOException readResponseData(OutboundRequestHandle handle,
				     InputStream in)
	{
	    // got nothing to do here for this provider
	    return null;
	}

	/** Returns a string representation of this connection. */
	public String toString() {
	    StringBuffer b = 
		new StringBuffer("KerberosEndpoint.ConnectionImpl");
	    b.append("[clientPrincipal=" + clientPrincipal);
	    b.append(" serverPrincipal=" + serverPrincipal);
	    b.append(" doEncryption=" + doEncryption);
	    b.append(" doDelegation=" + doDelegation);
	    b.append(" client=" + sock.getLocalAddress().getHostName());
	    b.append(":" + sock.getLocalPort());
	    b.append(" server=" + sock.getInetAddress().getHostName());
	    b.append(":" + sock.getPort());
	    b.append(']');
	    return b.toString();
	}

	/**
	 * Check whether this connection can satisfy the requirements
	 * specified by the given configuration.
	 */
	boolean satisfies(Config config) {
	    return gssContext.getLifetime() >= minGssContextLifetime &&
		clientPrincipal.equals(config.clientPrincipal) &&
		doEncryption == config.encry && doDelegation == config.deleg;
	}

	/**
	 * Switch an idle connection to satisfy the given
	 * configuration.
	 *
	 * @param config target configuration
	 * @return true if succeed, false otherwise.  If false is
	 *         returned, the connection is unchanged.
	 */
	boolean switchTo(Config config) {
	    if (gssContext.getLifetime() < minGssContextLifetime)
		return false;
	    if (clientPrincipal.equals(config.clientPrincipal) &&
		doDelegation == config.deleg)
	    {
		doEncryption = config.encry;
		return true;
	    }
	    return false;
	}

	/** Get the enclosing endpoint instance */
	KerberosEndpoint getEndpoint() {
	    return KerberosEndpoint.this;
	}

	/**
	 * Exchange handshake messages with server to establish the
	 * session or context of the connection.
	 */
	private void establishContext() throws IOException, GSSException {
	    GSSName clientName = gssManager.createName(
		clientPrincipal.getName(), KerberosUtil.krb5NameType);

	    GSSCredential clientCred = gssManager.createCredential(
		clientName, GSSCredential.INDEFINITE_LIFETIME,
		KerberosUtil.krb5MechOid, GSSCredential.INITIATE_ONLY);

	    GSSName serverName = gssManager.createName(
		serverPrincipal.getName(), KerberosUtil.krb5NameType);

	    for (int i = maxGssContextRetries; i > 0; i--) {
		gssContext = gssManager.createContext(
		    serverName, KerberosUtil.krb5MechOid, clientCred,
		    GSSContext.DEFAULT_LIFETIME);
		/*
		 * Set the desired optional features on the
		 * context. The client chooses these options.
		 */
		gssContext.requestMutualAuth(true); // Mutual authentication
		gssContext.requestInteg(true);      // Will use integrity later
		gssContext.requestConf(true);       // always enable encryption
		gssContext.requestCredDeleg(doDelegation);
		gssContext.requestReplayDet(true);
		gssContext.requestSequenceDet(true);
		
		// Do the context establishment loop
		byte[] token = new byte[0];
		try {
		    while (true) {
			// token is ignored on the first call
			token = gssContext.initSecContext(
			    token, 0, token.length);
			/*
			 * Send a token to the server if one was generated by
			 * initSecContext
			 */
			if (token != null) {
			    dos.writeInt(token.length);
			    dos.write(token);
			    dos.flush();
			}
			
			if (gssContext.isEstablished()) {
			    break;
			} else {
			    token = new byte[dis.readInt()];
			    dis.readFully(token);
			}
		    }
		    break; // done gssContext establishment
		} catch (GSSException ge) {
		    if ((ge.getMessage().indexOf("34") >= 0 ||
			 ge.getMajor() == GSSException.DUPLICATE_TOKEN ||
			 ge.getMinor() == KRB_AP_ERR_REPEAT ||
			 ge.getMessage().indexOf("Request is a replay") >= 0)
			&& i != 1) // will throw ge if loop has terminated
		    {
			/* this could caused by our authenticator
                         * being time stamped to the same micro-second
                         * as those of other concurrent connections,
                         * retry for better luck */
			continue;
		    }
		    throw ge;
		}
	    }

	    if (!gssContext.getIntegState() ||
		(doEncryption && !gssContext.getConfState()) ||
		(doDelegation && !gssContext.getCredDelegState()) ||
		!gssContext.getTargName().toString().
		equals(serverPrincipal.getName()))
	    {
		throw new IOException("Failed to establish gss context " +
				      "for connection");
	    }
	    logger.log(Level.FINE, "GSSContext established for {0}", this);
	}
    }

    /**
     * The key used for the softcache of this endpoint. It
     * encapsulates the <code>clientSubject</code> and the
     * <code>InvocationConstraints</code> associated with a request.  To
     * compute a key's <code>hashcode</code>
     * <code>identityHashCode</code> of its contents are
     * <code>XOR</code>ed together.  For <code>equals</code>,
     * <code>==</code> are used.
     */
    private static final class CacheKey {

	private final Subject subject;
	private final InvocationConstraints constraints;

	/** Construct a Key object */
	CacheKey(Subject subject, InvocationConstraints constraints) {
	    this.subject = subject;
	    this.constraints = constraints;
	}

	public int hashCode() {
            // identityHashCode() should be faster
            return System.identityHashCode(subject) ^
		System.identityHashCode(constraints);
	}

	/** Use <code>==</code> to compare content */
	public boolean equals(Object o) {
	    if (o == this) {
		return true;
	    } else if (!(o instanceof CacheKey)) {
		return false;
	    }
	    CacheKey okey = (CacheKey) o;
	    return subject == okey.subject && constraints == okey.constraints;
	}
    }
}
