/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jini.jeri.kerberos; import com.sun.jini.action.GetIntegerAction; import com.sun.jini.discovery.internal.EndpointInternals; import com.sun.jini.discovery.internal.KerberosEndpointInternalsAccess; 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 com.sun.jini.logging.LogUtil; 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.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.channels.SocketChannel; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.SocketFactory; import javax.security.auth.Subject; import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.kerberos.KerberosTicket; 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.io.UnsupportedConstraintException; import net.jini.jeri.Endpoint; import net.jini.jeri.OutboundRequestIterator; import net.jini.jeri.ServerEndpoint; import net.jini.jeri.connection.Connection; import net.jini.jeri.connection.ConnectionEndpoint; import net.jini.jeri.connection.OutboundRequestHandle; import net.jini.jeri.kerberos.KerberosUtil.Config; import net.jini.jeri.kerberos.KerberosUtil.ConfigIter; import net.jini.security.AuthenticationPermission; import net.jini.security.Security; import net.jini.security.proxytrust.TrustEquivalence; 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/REALM1@REALM2"</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 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> * * Note that, because Kerberos inherently requires client authentication, * this transport provider does not support distributed garbage collection * (DGC); if DGC is enabled using {@link net.jini.jeri.BasicJeriExporter}, * all DGC remote calls through this provider will silently fail. * * @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> exceptions caught attempting to set TCP no delay or keep * alive properties on sockets, connect a socket, or reuse * a connection * <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; /** Internal lock for class-wide synchronizaton */ private static final Object classLock = new Object(); /** GSSManager instance used by all endpoints in a JVM */ private static GSSManager gssManager; /** * 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; } /** * {@inheritDoc} * * <p>The returned <code>OutboundRequestIterator</code>'s {@link * OutboundRequestIterator#next next} method behaves as follows: * * <blockquote> * * Initiates an attempt to communicate the request to this remote * endpoint. * * <p>When the implementation of this method needs to create a new * <code>Socket</code>, it will do so by invoking one of the * <code>createSocket</code> methods on the * <code>SocketFactory</code> of this <code>KerberosEndpoint</code> * (which produced this iterator) if non-<code>null</code>, or it * will create a <code>Socket</code> directly otherwise. * * <p>When the implementation needs to connect a * <code>Socket</code>, if the host name to connect to (this * <code>KerberosEndpoint</code>'s host name) resolves to multiple * addresses (according to {@link InetAddress#getAllByName * InetAddress.getAllByName}), it attempts to connect to the first * resolved address; if that attempt fails with an * <code>IOException</code> or a <code>SecurityException</code>, * it then attempts to connect to the next address; and this * iteration continues as long as there is another resolved * address and the attempt to connect to the previous address * fails with an <code>IOException</code> or a * <code>SecurityException</code>. If the host name resolves to * just one address, the implementation makes one attempt to * connect to that address. If the host name does not resolve to * any addresses (<code>InetAddress.getAllByName</code> would * throw an <code>UnknownHostException</code>), the implementation * still makes an attempt to connect the <code>Socket</code> to * that host name, which could result in an * <code>UnknownHostException</code>. If the final connection * attempt fails with an <code>IOException</code> or a * <code>SecurityException</code>, then if any connection attempt * failed with an <code>IOException</code>, this method throws an * <code>IOException</code>, and otherwise (if all connection * attempts failed with a <code>SecurityException</code>), this * method throws a <code>SecurityException</code>. * * <p>If there is a security manager: * * <ul> * * <li>If a new connection is to be created, the security * manager's {@link SecurityManager#checkConnect(String,int) * checkConnect} method is invoked with this * <code>KerberosEndpoint</code>'s host and <code>-1</code> for the * port; if this results in a <code>SecurityException</code>, this * method throws that exception. <code>checkConnect</code> is * also invoked for each connection attempt, with the remote IP * address (or the host name, if it could not be resolved) and * port to connect to; this could result in a * <code>SecurityException</code> for that attempt. (Note that * the implementation may carry out these security checks * indirectly, such as through invocations of * <code>InetAddress.getAllByName</code> or <code>Socket</code>'s * constructors or <code>connect</code> method.) * * <li><p>In order to reuse an existing connection for the * communication, the current security context must have the * <code>KerberosPrincipal</code>, and permissions required to use it, * that would be necessary if the the connection were being created. In * addition, it must be possible to invoke <code>checkConnect</code> in the * current security context with this <code>KerberosEndpoint</code>'s host * and <code>-1</code> for the port without resulting in a * <code>SecurityException</code>, and it also must be possible to invoke * <code>checkConnect</code> with the remote IP address and port * of the <code>Socket</code> without resulting in a * <code>SecurityException</code> (if the remote socket address is * unresolved, its host name is used instead). If no existing * connection satisfies these requirements, then this method must * behave as if there are no existing connections. * * </ul> * * <p>Throws {@link java.util.NoSuchElementException} * if this iterator does not support making another attempt to communicate * the request (that is, if <code>hasNext</code> would return * <code>false</code>). * * <p>Throws {@link IOException} if an I/O exception occurs while * performing this operation, such as if a connection attempt * timed out or was refused or there are unsupported or conflicting * constraints. * * <p>Throws {@link SecurityException} if there is a security * manager and an invocation of its <code>checkConnect</code> * method fails. Also, a <code>SecurityException</code> may be thrown if * the caller does not have the appropriate * <code>AuthenticationPermission</code>. * * </blockquote> * * @throws NullPointerException {@inheritDoc} **/ 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() { KerberosEndpointInternalsAccess.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 (!disableSocketConnect) { sock = connectToHost(rh); } else { sock = newSocket(); } 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; } private Socket connectToHost(RequestHandleImpl rh) throws IOException { InetAddress[] addresses; try { addresses = InetAddress.getAllByName(serverHost); } catch (UnknownHostException uhe) { try { /* * Creating the InetSocketAddress attempts to * resolve the host again; in J2SE 5.0, there is a * factory method for creating an unresolved * InetSocketAddress directly. */ return connectToSocketAddress( new InetSocketAddress(serverHost, serverPort), rh); } catch (IOException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, ConnectionEndpointImpl.class, "connectToHost", "exception connecting to unresolved host {0}", new Object[] { serverHost + ":" + serverPort }, e); } throw e; } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, ConnectionEndpointImpl.class, "connectToHost", "exception connecting to unresolved host {0}", new Object[] { serverHost + ":" + serverPort }, e); } throw e; } } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, ConnectionEndpointImpl.class, "connectToHost", "exception resolving host {0}", new Object[] { serverHost }, e); } throw e; } IOException lastIOException = null; SecurityException lastSecurityException = null; for (int i = 0; i < addresses.length; i++) { SocketAddress socketAddress = new InetSocketAddress(addresses[i], serverPort); try { return connectToSocketAddress(socketAddress, rh); } catch (IOException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, ConnectionEndpointImpl.class, "connectToHost", "exception connecting to {0}", new Object[] { socketAddress }, e); } lastIOException = e; if (e instanceof SocketTimeoutException) { break; } } catch (SecurityException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, ConnectionEndpointImpl.class, "connectToHost", "exception connecting to {0}", new Object[] { socketAddress }, e); } lastSecurityException = e; } } if (lastIOException != null) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, ConnectionEndpointImpl.class, "connectToHost", "exception connecting to {0}", new Object[] { serverHost + ":" + serverPort }, lastIOException); } throw lastIOException; } assert lastSecurityException != null; if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, ConnectionEndpointImpl.class, "connectToHost", "exception connecting to {0}", new Object[] { serverHost + ":" + serverPort }, lastSecurityException); } throw lastSecurityException; } /** * Returns a socket connected to the specified address, with a * timeout governed by the constraints in the request handle. **/ private Socket connectToSocketAddress(SocketAddress socketAddress, RequestHandleImpl rh) throws IOException { long timeout = rh.connectionAbsoluteTime - System.currentTimeMillis(); if (timeout <= 0) { throw new SocketTimeoutException( "connection timeout passed before socket." + "connect is called"); } Socket sock = newSocket(); boolean ok = false; try { if (timeout > Integer.MAX_VALUE) { sock.connect(socketAddress); } else { sock.connect(socketAddress, (int) timeout); } ok = true; return sock; } finally { if (!ok) { try { sock.close(); } catch (IOException e) { } } } } /** * Returns a new unconnected socket, using this endpoint's * socket factory if non-null. **/ private Socket newSocket() throws IOException { Socket sock; if (csf != null) { try { sock = csf.createSocket(); } catch (IOException e) { if (logger.isLoggable(Levels.FAILED)) { KerberosUtil.logThrow( logger, Levels.FAILED, this.getClass(), "newSocket", "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); } setSocketOptions(sock); return sock; } private void setSocketOptions(Socket 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); } } } // 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 } boolean checkedResolvePermission = false; 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 (!checkedResolvePermission) { try { checkResolvePermission(); } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, ConnectionEndpointImpl.class, "connect", "exception resolving host {0}", new Object[] { serverHost }, e); } throw e; } checkedResolvePermission = true; } try { c.checkConnectPermission(); return c; } catch (SecurityException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, ConnectionEndpointImpl.class, "connect", "access to reuse connection {0} denied", new Object[] { c.sock }, e); } } } } } 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 (!checkedResolvePermission) { try { checkResolvePermission(); } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { LogUtil.logThrow(logger, Levels.FAILED, ConnectionEndpointImpl.class, "connect", "exception resolving host {0}", new Object[] { serverHost }, e); } throw e; } checkedResolvePermission = true; } try { c.checkConnectPermission(); return c; } catch (SecurityException e) { if (logger.isLoggable(Levels.HANDLED)) { LogUtil.logThrow(logger, Levels.HANDLED, ConnectionEndpointImpl.class, "connect", "access to reuse connection {0} denied", new Object[] { c.sock }, e); } } } } } return null; } private void checkResolvePermission() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkConnect(serverHost, -1); } } } /** 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; } /** * Check whether the caller has sufficient permissions to reuse the * socket that this connection has connected with. */ void checkConnectPermission() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { InetSocketAddress address = (InetSocketAddress) sock.getRemoteSocketAddress(); if (address.isUnresolved()) { sm.checkConnect(address.getHostName(), sock.getPort()); } else { sm.checkConnect(address.getAddress().getHostAddress(), sock.getPort()); } } } /** * 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 { synchronized (classLock) { if (gssManager == null) { gssManager = GSSManager.getInstance(); } } 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; } } }