/*
* 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.jeri.internal.connection.BasicServerConnManager;
import com.sun.jini.jeri.internal.connection.ServerConnManager;
import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.thread.Executor;
import com.sun.jini.thread.GetThreadPoolAction;
import com.sun.jini.logging.Levels;
import com.sun.security.jgss.GSSUtil;
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.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.RequestDispatcher;
import net.jini.jeri.ServerEndpoint;
import net.jini.jeri.connection.InboundRequestHandle;
import net.jini.jeri.connection.ServerConnection;
import net.jini.jeri.kerberos.KerberosUtil.Config;
import net.jini.jeri.kerberos.KerberosUtil.ConfigIter;
import net.jini.security.Security;
import net.jini.security.SecurityContext;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Set;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import javax.security.auth.AuthPermission;
import javax.net.SocketFactory;
import javax.net.ServerSocketFactory;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosKey;
import javax.security.auth.kerberos.KerberosPrincipal;
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;
/**
* A {@link ServerEndpoint} implementation that uses Kerberos as the
* underlying network security protocol to support security related
* invocation constraints for remote requests. Instances of this
* class are referred to as the server endpoints of the Kerberos
* provider, while instances of {@link KerberosEndpoint} are referred
* to as the endpoints of the provider. <p>
*
* Instances of this class are intended to be created for use with the
* {@link net.jini.jeri.BasicJeriExporter} class. Calls to {@link
* #enumerateListenEndpoints enumerateListenEndpoints} return
* instances of {@link KerberosEndpoint}. <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},
* trivially, since this only takes effect on the client side
* <li>{@link net.jini.core.constraint.ConnectionRelativeTime},
* trivially, since this only takes effect on the client side
* <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>
*
* To get an instance of <code>KerberosServerEndpoint</code>, one of
* the <code>getInstance</code> methods of the class has to be
* invoked. The returned server endpoint instance encapsulates a set
* of properties that it will later use to receive and dispatch
* inbound requests. The following describes how some of these
* properties are chosen: <p>
*
* <ul>
* <li><code>serverSubject</code> - The {@link Subject} that contains
* the principal and credential to be used by the server endpoint
* to authenticate itself to its remote callers. The subject is
* either provided by the caller of the <code>getInstance</code>
* method as a non-<code>null</code> argument, or extracted from
* the access control context of the current thread when the
* <code>getInstance</code> method is called. The later value is
* also referred to as the default server subject.
* <li><code>serverPrincipal</code> - The
* <code>KerberosPrincipal</code> that the server endpoint will
* authenticate itself as to all clients. If the caller of the
* <code>getInstance</code> method provides a
* non-<code>null</code> <code>serverPrincipal</code> argument, it
* is used without any further checking; otherwise the default
* server principal will be used. The default server principal
* can be any <code>KerberosPrincipal</code> instance in the
* <code>serverSubject</code>'s principal set, whose corresponding
* <code>KerberosKey</code> is found in the subject's private
* credential set and is still valid, provided the caller has been
* granted the {@link net.jini.security.AuthenticationPermission}
* with the principal as its local principal and
* <code>listen</code> as its action.
* <li><code>serverHost</code> - The host name that will be
* encapsulated in <code>KerberosEndpoint</code> instances created
* by the server endpoint. If a non-<code>null serverHost</code>
* is provided by the caller, it will be used; otherwise the
* default value is used, which is the IP address of the local
* host, as obtained from {@link InetAddress#getLocalHost
* InetAddress.getLocalHost}. The host name does not affect the
* behavior of the listen operation itself, which always listens
* on all of the local system's network addresses, unless a
* <code>ServerSocketFactory</code> is provided by the caller, in
* which case the factory will be in charge.
* </ul>
*
* This class permits specifying a {@link SocketFactory} for creating
* the {@link Socket} instances that the associated
* <code>KerberosEndpoint</code> instances use to make remote
* connections back to the server, and a {@link ServerSocketFactory}
* for creating the {@link ServerSocket} instances that the server
* endpoint uses to accept remote connections. <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. A <code>ServerSocketFactory</code> used with instances of
* this class should implement <code>Object.equals</code> to return
* <code>true</code> when passed an instance that represents the same
* (functionally equivalent) server socket factory. <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 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
* server logger performed by endpoint classes in this package at
* different logging levels">
*
* <caption halign="center" valign="top"><b><code>
* net.jini.jeri.kerberos.server</code></b></caption>
*
* <tr> <th scope="col"> Level <th scope="col"> Description
* <tr> <td> {@link java.util.logging.Level#WARNING WARNING}
* <td> unexpected failure while accepting connections on the created
* <code>ServerSocket</code>.
* <tr> <td> {@link com.sun.jini.logging.Levels#FAILED FAILED}
*
* <td> problems with permission checking, server principal and
* Kerberos key presence checking, {@link
* org.ietf.jgss.GSSCredential} creation, socket connect
* acception, {@link org.ietf.jgss.GSSContext}
* establishment, credential expiration, 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> server endpoint creation, {@link
* net.jini.jeri.ServerCapabilities#checkConstraints
* checkConstraints} results, server socket creation,
* socket connect acceptance, server connection
* creation/destruction, <code>GSSContext</code>
* establishment
* <tr> <td> {@link java.util.logging.Level#FINEST FINEST}
* <td> data message encoding/decoding using
* <code>GSSContext</code>
* </table> <p>
*
* When the <code>ListenEndpoint.listen</code> method of this
* implementation is invoked, a search is conducted on the private
* credentials of the <code>serverSubject</code>, the first valid
* <code>KerberosKey</code> whose principal equals to the
* <code>serverPrincipal</code> is chosen as the server credential for
* the listen operation. The presence of this server credential in
* the <code>serverSubject</code> as well as its validity are checked
* both when a new incoming connection is received and a new request
* arrives on an established connection; if the checks fail, the
* listen operation or the connection will be aborted permanently. <p>
*
* This implementation uses the standard <a
* href="http://www.ietf.org/rfc/rfc2853.txt">Java(TM) GSS-API</a>.
* Additionally, for each inbound connection established, it invokes
* {@link GSSUtil#createSubject GSSUtil.createSubject} to construct a
* <code>Subject</code> instance, which encapsulates the principal and
* delegated credential, if any, of the corresponding remote caller.
*
* @author Sun Microsystems, Inc.
* @see KerberosEndpoint
* @see KerberosTrustVerifier
* @since 2.0
*/
public final class KerberosServerEndpoint implements ServerEndpoint {
/** JERI Kerberos server transport logger */
private static final Logger logger =
Logger.getLogger("net.jini.jeri.kerberos.server");
/**
* Pool of threads for executing tasks in system thread group:
* used for the accept threads
*/
private static final Executor systemThreadPool =
(Executor) Security.doPrivileged(new GetThreadPoolAction(false));
/** The server subject */
private Subject serverSubject;
/** Internal lock for class-wide synchronizaton */
private static final Object classLock = new Object();
/** GSSManager instance used by this server endpoint */
private static GSSManager gssManager;
/** Maximum size of the soft cache */
private static final int maxCacheSize = ((Integer) Security.doPrivileged(
new GetIntegerAction("com.sun.jini.jeri.kerberos." +
"KerberosServerEndpoint.maxCacheSize",
256))).intValue();
/** Access control context cache, keyed by constraints */
private final KerberosUtil.SoftCache softCache;
/** The principal used for server authentication */
private KerberosPrincipal serverPrincipal;
/** The host name that clients should use to connect to this server. */
private String serverHost;
/** The server port */
private final int port;
/**
* the socket factory that <code>KerberosEndpoint</code> objects
* created by listening on this
* <code>KerberosServerEndpoint</code> will use to create
* {@link Socket}s
*/
private final SocketFactory csf;
/**
* the socket factory that this
* <code>KerberosServerEndpoint</code> uses to create {@link
* ServerSocket}s
*/
private final ServerSocketFactory ssf;
/** ListenEndpoint instance used by this endpoint */
private final ListenEndpointImpl listenEndpoint;
/**
* ServerConnManager instance used by this server endpoint. This
* field is not private or static or final because it might have
* to be reset for discovery usage in
* <code>KerberosEndpoint</code>.
*/
ServerConnManager serverConnManager = new BasicServerConnManager();
/** Static constraints will be used for unfulfilledConstraints */
private static final InvocationConstraints INTEGRITY_REQUIRED_CONSTRAINTS =
new InvocationConstraints(Integrity.YES, null);
/** Static constraints will be used for unfulfilledConstraints */
private static final InvocationConstraints
INTEGRITY_PREFERRED_CONSTRAINTS =
new InvocationConstraints(null, Integrity.YES);
//-----------------------------------
// constructors
//-----------------------------------
/**
* Creates a Kerberos server endpoint with the specified subject,
* server principal, server host, port, and socket factories. <p>
*
* @param serverSubject the server subject to use for
* authenticating the server. If <code>null</code>, the
* subject associated with the current access control
* context will be used.
* @param serverPrincipal the principal server should authenticate
* as. If <code>null</code>, then the default server
* principal will be used.
* @param serverHost the name or IP address of the server host the
* <code>KerberosEndpoint</code> instances created by this
* server endpoint will connect to. If <code>null</code>,
* the default server host will be used.
* @param port the port this server endpoint will listen on, 0 to
* use any free port
* @param csf the <code>SocketFactory</code> to be used by the
* <code>KerberosEndpoint</code> created by this server
* endpoint to create sockets, or <code>null</code> to let
* the <code>KerberosEndpoint</code> create {@link Socket}s
* directly.
* @param ssf the <code>ServerSocketFactory</code> to use for this
* <code>KerberosServerEndpoint</code>, or
* <code>null</code> to let the
* <code>KerberosServerEndpoint</code> create {@link
* ServerSocket}s directly.
* @throws UnsupportedConstraintException if the caller has not
* been granted the right
* <code>AuthenticationPermission</code>, or there is no
* default server subject
* (<code>serverSubject.getSubject(AccessController.getContext())
* </code> returns <code>null</code>), or no appropriate
* Kerberos principal and corresponding Kerberos key can
* be found in the server subject
* @throws SecurityException if there is a security manager and
* the following condition is true:
*
* <ul>
* <li>The passed in serverPrincipal is <code>null</code>,
* the caller has been granted {@link
* AuthPermission}<code>("getSubject") </code>, but
* no <code>listen</code>
* <code>AuthenticationPermission</code> whose local
* principal is a principal in the server subject's
* principal set, which is required for accessing any
* private credentials corresponding to the principal
* in the server subject.
* </ul>
*
* @throws IllegalArgumentException if <code>serverPort</code> is
* not in the range of <code>0</code> to
* <code>65535</code>
*/
private KerberosServerEndpoint(Subject serverSubject,
KerberosPrincipal serverPrincipal,
String serverHost, int port,
SocketFactory csf, ServerSocketFactory ssf)
throws UnsupportedConstraintException
{
boolean useCurrentSubject = serverSubject == null;
boolean usePrincipalInSubject = serverPrincipal == null;
if (useCurrentSubject) {
final AccessControlContext acc = AccessController.getContext();
serverSubject = (Subject) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Subject.getSubject(acc);
}
});
}
Exception detailedException = null;
if (usePrincipalInSubject) {
if (serverSubject == null) {
detailedException = new UnsupportedConstraintException(
"Forgot JAAS login? Using default " +
"serverSubject but no subject is associated " +
"with the current access control context.");
} else {
try {
serverPrincipal = findServerPrincipal(serverSubject);
} catch (Exception e) {
detailedException = e;
}
}
} else if (useCurrentSubject) {
try {
/* caller provided principal, but want to use the
current subject, should only proceed if caller has
the listen AuthenticationPermission */
KerberosUtil.checkAuthPermission(
serverPrincipal, null, "listen");
if (serverSubject == null) {
detailedException = new UnsupportedConstraintException(
"Forgot JAAS login? Using default " +
"serverSubject but no subject is associated " +
"with the current access control context.");
}
} catch (SecurityException e) {
serverSubject = null;
// will throw a SecurityException in enumerateListenEndpoints
}
}
if (detailedException != null) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"constructor", "construction failed", null,
detailedException);
}
KerberosUtil.secureThrow(
detailedException, new UnsupportedConstraintException(
"Either the caller has not been granted the right " +
"AuthenticationPermission, or there is no default " +
"server subject (<code>Subject.getSubject(" +
"AccessController.getContext())</code> returns " +
"<code>null</code>), or no appropriate Kerberos " +
"principal and its corresponding key can be found in " +
"the current subject."));
}
// have succeeded principal, subject, and auth permission checks
this.serverSubject = serverSubject;
this.serverPrincipal = serverPrincipal;
if (port < 0 || port > 0xFFFF) {
throw new IllegalArgumentException(
"port number out of range 0-65535: port = " + port);
}
this.serverHost = serverHost;
this.port = port;
this.csf = csf;
this.ssf = ssf;
softCache = new KerberosUtil.SoftCache(maxCacheSize);
listenEndpoint = new ListenEndpointImpl();
logger.log(Level.FINE, "created {0}", this);
}
//-----------------------------------
// public methods
//-----------------------------------
/**
* Returns a <code>KerberosServerEndpoint</code> instance with the
* specified port, using the default server subject, server
* principal, and server host.
*
* @param port the port this server endpoint will listen on, 0 to
* use any free port
* @return a <code>KerberosServerEndpoint</code> instance
* @throws UnsupportedConstraintException if the caller has not
* been granted the right
* <code>AuthenticationPermission</code>, or there is no
* default server subject
* (<code>serverSubject.getSubject(AccessController.getContext())
* </code> returns <code>null</code>), or no appropriate
* Kerberos principal and corresponding Kerberos key can
* be found in the server subject
* @throws SecurityException if there is a security manager and
* the following condition is true:
*
* <ul>
* <li>The caller has been granted
* {@link AuthPermission}<code>("getSubject") </code>,
* but no <code>listen</code>
* <code>AuthenticationPermission</code> whose local
* principal is a principal in the server subject's
* principal set, which is required for accessing any
* private credentials corresponding to the principal
* in the server subject.
* </ul>
*
* @throws IllegalArgumentException if <code>serverPort</code> is
* not in the range of <code>0</code> to
* <code>65535</code>
*/
public static KerberosServerEndpoint getInstance(int port)
throws UnsupportedConstraintException
{
return new KerberosServerEndpoint(null, null, null, port, null, null);
}
/**
* Returns a <code>KerberosServerEndpoint</code> instance with the
* specified server host and port, using the default server
* subject and server principal.
*
* @param serverHost the name or IP address of the server host the
* <code>KerberosEndpoint</code> instances created by this
* server endpoint will connect to. If <code>null</code>,
* the default server host will be used.
* @param port the port this server endpoint will listen on, 0 to
* use any free port
* @return a <code>KerberosServerEndpoint</code> instance
* @throws UnsupportedConstraintException if the caller has not
* been granted the right
* <code>AuthenticationPermission</code>, or there is no
* default server subject
* (<code>serverSubject.getSubject(AccessController.getContext())
* </code> returns <code>null</code>), or no appropriate
* Kerberos principal and corresponding Kerberos key can
* be found in the server subject
* @throws SecurityException if there is a security manager and
* the following condition is true:
*
* <ul>
* <li>The caller has been granted
* {@link AuthPermission}<code>("getSubject") </code>,
* but no <code>listen</code>
* <code>AuthenticationPermission</code> whose local
* principal is a principal in the server subject's
* principal set, which is required for accessing any
* private credentials corresponding to the principal
* in the server subject.
* </ul>
*
* @throws IllegalArgumentException if <code>serverPort</code> is
* not in the range of <code>0</code> to
* <code>65535</code>
*/
public static KerberosServerEndpoint getInstance(
String serverHost, int port)
throws UnsupportedConstraintException
{
return new KerberosServerEndpoint(
null, null, serverHost, port, null, null);
}
/**
* Returns a <code>KerberosServerEndpoint</code> instance with the
* specified server host, port, and socket factories, using the
* default server subject and server principal.
*
* @param serverHost the name or IP address of the server host the
* <code>KerberosEndpoint</code> instances created by this
* server endpoint will connect to. If <code>null</code>,
* the default server host will be used.
* @param port the port this server endpoint will listen on, 0 to
* use any free port
* @param csf the <code>SocketFactory</code> to be used by the
* <code>KerberosEndpoint</code> created by this server
* endpoint to create sockets, or <code>null</code> to let
* the <code>KerberosEndpoint</code> create {@link Socket}s
* directly.
* @param ssf the <code>ServerSocketFactory</code> to use for this
* <code>KerberosServerEndpoint</code>, or
* <code>null</code> to let the
* <code>KerberosServerEndpoint</code> create {@link
* ServerSocket}s directly.
* @return a <code>KerberosServerEndpoint</code> instance
* @throws UnsupportedConstraintException if the caller has not
* been granted the right
* <code>AuthenticationPermission</code>, or there is no
* default server subject
* (<code>serverSubject.getSubject(AccessController.getContext())
* </code> returns <code>null</code>), or no appropriate
* Kerberos principal and corresponding Kerberos key can
* be found in the server subject
* @throws SecurityException if there is a security manager and
* the following condition is true:
*
* <ul>
* <li>The caller has been granted
* {@link AuthPermission}<code>("getSubject") </code>,
* but no <code>listen</code>
* <code>AuthenticationPermission</code> whose local
* principal is a principal in the server subject's
* principal set, which is required for accessing any
* private credentials corresponding to the principal
* in the server subject.
* </ul>
*
* @throws IllegalArgumentException if <code>serverPort</code> is
* not in the range of <code>0</code> to
* <code>65535</code>
*/
public static KerberosServerEndpoint getInstance(
String serverHost, int port, SocketFactory csf,
ServerSocketFactory ssf)
throws UnsupportedConstraintException
{
return new KerberosServerEndpoint(
null, null, serverHost, port, csf, ssf);
}
/**
* Returns a <code>KerberosServerEndpoint</code> instance with the
* specified server subject, server principal, server host, and
* port.
*
* @param serverSubject the server subject to use for
* authenticating the server. If <code>null</code>, the
* subject associated with the current access control
* context will be used.
* @param serverPrincipal the principal server should authenticate
* as. If <code>null</code>, then the default server
* principal will be used.
* @param serverHost the name or IP address of the server host the
* <code>KerberosEndpoint</code> instances created by this
* server endpoint will connect to. If <code>null</code>,
* the default server host will be used.
* @param port the port this server endpoint will listen on, 0 to
* use any free port
* @return a <code>KerberosServerEndpoint</code> instance
* @throws UnsupportedConstraintException if the caller has not
* been granted the right
* <code>AuthenticationPermission</code>, or there is no
* default server subject
* (<code>serverSubject.getSubject(AccessController.getContext())
* </code> returns <code>null</code>), or no appropriate
* Kerberos principal and corresponding Kerberos key can
* be found in the server subject
* @throws SecurityException if there is a security manager and
* the following condition is true:
*
* <ul>
* <li>The passed in serverPrincipal is <code>null</code>,
* the caller has the
* {@link AuthPermission}<code>("getSubject")
* </code>, but no <code>listen</code>
* <code>AuthenticationPermission</code> whose local
* principal is a principal in the server subject's
* principal set, which is required for accessing any
* private credentials corresponding to the principal
* in the server subject.
* </ul>
*
* @throws IllegalArgumentException if <code>serverPort</code> is
* not in the range of <code>0</code> to
* <code>65535</code>
*/
public static KerberosServerEndpoint getInstance(
Subject serverSubject, KerberosPrincipal serverPrincipal,
String serverHost, int port)
throws UnsupportedConstraintException
{
return new KerberosServerEndpoint(
serverSubject, serverPrincipal, serverHost, port, null, null);
}
/**
* Returns a <code>KerberosServerEndpoint</code> instance with the
* specified server subject, server principal, server host, port,
* and socket factories.
*
* @param serverSubject the server subject to use for
* authenticating the server. If <code>null</code>, the
* subject associated with the current access control
* context will be used.
* @param serverPrincipal the principal server should authenticate
* as. If <code>null</code>, then the default server
* principal will be used.
* @param serverHost the name or IP address of the server host the
* <code>KerberosEndpoint</code> instances created by this
* server endpoint will connect to. If <code>null</code>,
* the default server host will be used.
* @param port the port this server endpoint will listen on, 0 to
* use any free port
* @param csf the <code>SocketFactory</code> to be used by the
* <code>KerberosEndpoint</code> created by this server
* endpoint to create sockets, or <code>null</code> to let
* the <code>KerberosEndpoint</code> create {@link Socket}s
* directly.
* @param ssf the <code>ServerSocketFactory</code> to use for this
* <code>KerberosServerEndpoint</code>, or
* <code>null</code> to let the
* <code>KerberosServerEndpoint</code> create {@link
* ServerSocket}s directly.
* @return a <code>KerberosServerEndpoint</code> instance
* @throws UnsupportedConstraintException if the caller has not
* been granted the right
* <code>AuthenticationPermission</code>, or there is no
* default server subject
* (<code>serverSubject.getSubject(AccessController.getContext())
* </code> returns <code>null</code>), or no appropriate
* Kerberos principal and corresponding Kerberos key can
* be found in the server subject
* @throws SecurityException if there is a security manager and
* the following condition is true:
*
* <ul>
* <li>The passed in serverPrincipal is <code>null</code>,
* the caller has the {@link
* AuthPermission}<code>("getSubject") </code>, but
* no <code>listen</code>
* <code>AuthenticationPermission</code> whose local
* principal is a principal in the server subject's
* principal set, which is required for accessing any
* private credentials corresponding to the principal
* in the server subject.
* </ul>
*
* @throws IllegalArgumentException if <code>serverPort</code> is
* not in the range of <code>0</code> to
* <code>65535</code>
*/
public static KerberosServerEndpoint getInstance(
Subject serverSubject, KerberosPrincipal serverPrincipal,
String serverHost, int port, SocketFactory csf,
ServerSocketFactory ssf)
throws UnsupportedConstraintException
{
return new KerberosServerEndpoint(
serverSubject, serverPrincipal, serverHost, port, csf, ssf);
}
/**
* Returns the host name that will be used in {@link
* KerberosEndpoint} instances created by listening on this
* object.
*
* @return the host name to use in <code>KerberosEndpoint</code>
* instances created by listening on this object
*/
public String getHost() {
return serverHost;
}
/**
* Returns the TCP port that the <code>ListenEndpoint</code>s
* created by this server endpoint listen on.
*
* @return the TCP port that the endpoints listen on
*/
public int getPort() {
return port;
}
/**
* Returns the principal that this server endpoint will
* authenticate itself as.
*
* @return the principal that this server endpoint will
* authenticate as
*/
public KerberosPrincipal getPrincipal() {
return serverPrincipal;
}
/**
* Returns the socket factory that the associated {@link
* KerberosEndpoint} instances, which are created by listening on
* the <code>ListenEndpoint</code> instances of this server
* endpoint, use to create {@link Socket} instances, or
* <code>null</code> if they use default sockets.
*
* @return the socket factory that associated endpoints use to
* create sockets, or <code>null</code> if they use
* default sockets
*/
public SocketFactory getSocketFactory() {
return csf;
}
/**
* Returns the server socket factory that this server endpoint
* uses to create {@link ServerSocket} instances, or
* <code>null</code> if it uses default server sockets.
*
* @return the server socket factory that this server endpoint
* uses to create server sockets, or <code>null</code> if
* it uses default server sockets
*/
public ServerSocketFactory getServerSocketFactory() {
return ssf;
}
/* -- Implement ServerCapabilities -- */
// Javadoc is inherited from the ServerCapabilities interface
public InvocationConstraints checkConstraints(
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
if (constraints == null)
throw new NullPointerException();
InvocationConstraints unfulfilledConstraints;
try {
// check for unsupportable constraints
for (Iterator iter = constraints.requirements().iterator();
iter.hasNext(); )
{
InvocationConstraint c = (InvocationConstraint)iter.next();
if (!KerberosUtil.isSupportableConstraint(c)) {
throw new UnsupportedConstraintException(
"A constraint unsupportable by this endpoint " +
"has been required: " + c);
}
}
if (getKey(serverSubject, serverPrincipal) == null) {
throw new UnsupportedConstraintException(
"Failed to find a valid Kerberos key " +
"corresponding to serverPrincipal (" + serverPrincipal +
") in serverSubject.");
}
// now check whether the constraints are satisfiable
// first find all client principal candidates in constraints
HashSet cpCandidates = new HashSet();
for (Iterator iter = constraints.requirements().iterator();
iter.hasNext(); )
{
if (!KerberosUtil.collectCpCandidates(
(InvocationConstraint) iter.next(), cpCandidates))
{
throw new UnsupportedConstraintException(
"Client principal constraint related conflicts " +
"found in the given set of constraints: " +
constraints);
}
}
if (cpCandidates.size() == 0) {
/* no client principal constraints is required, anyone
will pass */
cpCandidates.add(new KerberosPrincipal("anyone"));
}
// look for a satisfiable config
boolean doable = false;
ConfigIter citer =
new ConfigIter(cpCandidates, serverPrincipal, true);
outer:
while (citer.hasNext()) {
Config config = citer.next();
for (Iterator iter = constraints.requirements().iterator();
iter.hasNext(); )
{
InvocationConstraint c =
(InvocationConstraint) iter.next();
if (!KerberosUtil.isSatisfiable(config, c))
continue outer;
}
doable = true;
break;
}
if (!doable) {
throw new UnsupportedConstraintException(
"Conflicts found in the given set of constraints: " +
constraints);
}
unfulfilledConstraints = InvocationConstraints.EMPTY;
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;
}
} catch (UnsupportedConstraintException uce) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"checkConstraints", "check constraints for " +
"{0}\nwith {1}\nthrows",
new Object[] {this, constraints}, uce);
}
throw uce;
} catch (SecurityException se) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"checkConstraints", "check constraints for " +
"{0}\nwith {1}\nthrows",
new Object[] {this, constraints}, se);
}
throw se;
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "checkConstraints() has determined " +
"that this endpoint can support the given " +
"constraints:\n{0}.\nWhile assistances are needed " +
"from upper layers to satisfy constraints:\n{1}",
new Object[] {constraints, unfulfilledConstraints});
}
return unfulfilledConstraints;
}
/* -- Implement ServerEndpoint -- */
/**
* Passes the {@link net.jini.jeri.ServerEndpoint.ListenEndpoint
* ListenEndpoint} for this <code>KerberosServerEndpoint</code> to
* <code>listenContext</code>, which will ensure an active listen
* operation on the endpoint, and returns a <code>KerberosEndpoint</code>
* instance corresponding to the listen operation chosen by
* <code>listenContext</code>. <p>
*
* If this server endpoint's server host is <code>null</code>,
* then the endpoint returned will contain the default server
* host. This method computes the default by invoking {@link
* InetAddress#getLocalHost InetAddress.getLocalHost} to obtain an
* <code>InetAddress</code> for the local host. If
* <code>InetAddress.getLocalHost</code> throws an
* <code>UnknownHostException</code>, this method throws an
* <code>UnknownHostException</code>.
* The default host name will be the string returned by invoking
* {@link InetAddress#getHostAddress getHostAddress} on the obtained
* <code>InetAddress</code>. If there is a security manager, its {@link
* SecurityManager#checkConnect(String,int) checkConnect} method
* will be invoked with the string returned by invoking {@link
* InetAddress#getHostName getHostName} on that same
* <code>InetAddress</code> as the host argument and
* <code>-1</code> as the port argument; this could result in a
* <code>SecurityException</code>. <p>
*
* This method invokes <code>addListenEndpoint</code> on
* <code>listenContext</code> once, passing a <code>ListenEndpoint</code>
* as described below. If <code>addListenEndpoint</code> throws an
* exception, then this method throws that exception. Otherwise, this
* method returns a <code>KerberosEndpoint</code> instance with the host
* name described above, the TCP port number bound by the listen operation
* represented by the {@link net.jini.jeri.ServerEndpoint.ListenHandle
* ListenHandle} returned by <code>addListenEndpoint</code>, and the same
* server principal and <code>SocketFactory</code> as this
* <code>KerberosServerEndpoint</code>. <p>
*
* The <code>ListenEndpoint</code> passed to
* <code>addListenEndpoint</code> represents the server subject, server
* principal, TCP port number, and <code>ServerSocketFactory</code> of this
* <code>KerberosServerEndpoint</code>. Its methods behave as follows:
* <p>
*
* {@link net.jini.jeri.ServerEndpoint.ListenEndpoint#listen ListenHandle
* listen(RequestDispatcher)}:
*
* <blockquote>
*
* Listens for requests received on this endpoint's TCP port, dispatching
* them to the supplied <code>RequestDispatcher</code> in the form of
* {@link net.jini.jeri.InboundRequest} instances. <p>
*
* When the implementation of this method needs to create a new
* <code>ServerSocket</code>, it will do so by invoking one of the
* <code>createServerSocket</code> methods that returns a bound server
* socket on the contained <code>ServerSocketFactory</code> if
* non-<code>null</code>, or it will create a <code>ServerSocket</code>
* directly otherwise. <p>
*
* If there is a security manager, its {@link SecurityManager#checkListen
* checkListen} method will be invoked with this endpoint's TCP port; this
* could result in a <code>SecurityException</code>. In addition, the
* security manager's {@link SecurityManager#checkPermission
* checkPermission} method will be invoked with an {@link
* net.jini.security.AuthenticationPermission} containing the server
* principal of this endpoint and the <code>listen</code> action; this
* could also result in a <code>SecurityException</code>. Furthermore,
* before a given <code>InboundRequest</code> gets dispatched to the
* supplied request dispatcher, the security manager's {@link
* SecurityManager#checkAccept checkAccept} method must have been
* successfully invoked in the security context of this
* <code>listen</code> invocation with the remote IP address and port of
* the <code>Socket</code> used to receive the request, and the security
* manager's <code>checkPermission</code> method must have been
* successfully invoked in the same context with an
* <code>AuthenticationPermission</code> containing the server principal
* of this endpoint as local principal, the client's authenticated
* principal as peer principal, and the <code>accept</code> action. The
* <code>checkPermissions</code> method of the dispatched
* <code>InboundRequest</code> also performs these latter security checks.
* (Note that in some cases, the implementation may carry out some of
* these security checks indirectly, such as through invocations of
* <code>ServerSocket</code>'s constructors or <code>accept</code>
* method.) <p>
*
* Once a <code>ListenEndpoint</code> of this provider starts to
* listen, each time a new inbound request comes in, it verifies
* that the <code>serverSubject</code> of the corresponding server
* endpoint still holds a valid <code>KerberosKey</code>
* corresponding to its <code>serverPrincipal</code>. It rejects
* the request if the verification fails. This guarantees that as
* soon as the <code>KerberosKey</code> for the principal in the
* <code>Subject</code> is destroyed (logout) or expired, no more
* new requests will be accepted. <p>
*
* Requests will be dispatched in a {@link PrivilegedAction} wrapped by a
* {@link SecurityContext} obtained when this method was invoked, with the
* {@link AccessControlContext} of that <code>SecurityContext</code> in
* effect. <p>
*
* Dispatched requests will implement {@link
* net.jini.jeri.InboundRequest#populateContext populateContext} to
* populate the given collection with an element that implements the
* {@link net.jini.io.context.ClientHost} interface, and an element that
* implements the {@link net.jini.io.context.ClientSubject} interface. The
* <code>ClientHost</code> element implements {@link
* net.jini.io.context.ClientHost#getClientHost getClientHost} to return
* the IP address of the <code>Socket</code> that the request was received
* over (see {@link Socket#getInetAddress}). <p>
*
* Throws {@link IOException} if an I/O exception occurs while performing
* this operation, such as if the TCP port is already in use. <p>
*
* Throws {@link SecurityException} if there is a security manager and an
* invocation of its <code>checkListen</code> or
* <code>checkPermission</code> method fails. <p>
*
* Throws {@link NullPointerException} if <code>requestDispatcher</code>
* is <code>null</code>
*
* </blockquote>
*
* {@link net.jini.jeri.ServerEndpoint.ListenEndpoint#checkPermissions
* void checkPermissions()}:
*
* <blockquote>
*
* Verifies that the current security context has all of the security
* permissions necessary to listen for requests on this endpoint. <p>
*
* If there is a security manager, its <code>checkListen</code> method
* will be invoked with this endpoint's TCP port; this could result in a
* <code>SecurityException</code>. In addition, the security manager's
* {@link SecurityManager#checkPermission checkPermission} method will be
* invoked with an {@link net.jini.security.AuthenticationPermission}
* containing the server principal of this endpoint and the
* <code>listen</code> action; this could also result in a
* <code>SecurityException</code>. <p>
*
* Throws {@link SecurityException} if there is a security manager and an
* invocation of its <code>checkListen</code> or
* <code>checkPermission</code> method fails.
*
* </blockquote>
*
* {@link Object#equals boolean equals(Object)}:
*
* <blockquote>
*
* Compares the specified object with this <code>ListenEndpoint</code> for
* equality. <p>
*
* This method returns <code>true</code> if and only if the specified
* object is also a <code>ListenEndpoint</code> produced by a
* <code>KerberosServerEndpoint</code>, and the two listen endpoints both
* have server subjects that compare equal using <code>==</code>; have the
* same server principal; have the same values for TCP port; and have
* server socket factories that are either both <code>null</code>, or have
* the same actual class and are equal.
*
* </blockquote>
*
* @throws SecurityException if there is a security manager, and
* either its {@link SecurityManager#checkListen
* checkListen} method fails, or <code>serverHost</code>
* is <code>null</code> and the security manager's {@link
* SecurityManager#checkConnect checkConnect} method
* fails; or if the calling thread does not have
* permission to authenticate as the endpoint's server
* principal when listening for connections
* @throws IllegalArgumentException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws UnknownHostException if this instance's server host
* is <code>null</code> and
* <code>InetAddress.getLocalHost</code> throws an
* <code>UnknownHostException</code>
* @throws UnsupportedConstraintException if the server subject is
* missing any of the endpoint's server principals or
* the associated credentials
*/
public Endpoint enumerateListenEndpoints(ListenContext listenContext)
throws IOException
{
if (serverHost == null) {
InetAddress localAddr;
try {
localAddr = (InetAddress) AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws UnknownHostException {
return InetAddress.getLocalHost();
}
});
} catch (PrivilegedActionException e) {
UnknownHostException uhe = (UnknownHostException) e.getCause();
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"enumerateListenEndpoints",
"InetAddress.getLocalHost() throws", null, uhe);
}
// Remove host information if caller does not have privileges
// to see it.
try {
InetAddress.getLocalHost();
} catch (UnknownHostException te) {
throw te;
}
throw new UnknownHostException("Host name cleared due to " +
"insufficient caller " +
"permissions");
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkConnect(localAddr.getHostName(), -1);
} catch (SecurityException e) {
SecurityException se = new SecurityException(
"Access to resolve local host denied");
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"enumerateListenEndpoints",
"caller does not have permission to resolve " +
"local host", null, se);
}
throw se;
}
}
serverHost = localAddr.getHostAddress();
}
ListenCookieImpl cookie = checkListenCookie(
listenContext.addListenEndpoint(listenEndpoint));
return KerberosEndpoint.getInstance(
serverHost, cookie.getLocalPort(), serverPrincipal, csf);
}
/** Returns a hash code value for this object. */
public int hashCode() {
return getClass().getName().hashCode() ^
System.identityHashCode(serverSubject) ^
serverPrincipal.hashCode() ^
(serverHost != null ? serverHost.hashCode() : 0) ^ port ^
(ssf != null ? ssf.hashCode() : 0) ^
(csf != null ? csf.hashCode() : 0);
}
/**
* Two instances of this class are equal if they have server
* subjects that compare equal using <code>==</code>; have the
* same server principal; have the same values for server host and
* port; have socket factories that are either both
* <code>null</code>, or have the same actual class and are equal;
* and have server socket factories that are either 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 KerberosServerEndpoint)) {
return false;
}
KerberosServerEndpoint ose = (KerberosServerEndpoint) obj;
return serverSubject == ose.serverSubject &&
serverPrincipal.equals(ose.serverPrincipal) &&
Util.equals(serverHost, ose.serverHost) &&
port == ose.port &&
Util.sameClassAndEquals(csf, ose.csf) &&
Util.sameClassAndEquals(ssf, ose.ssf);
}
/** Returns a string representation of this object. */
public String toString() {
return "KerberosServerEndpoint[serverPrincipal=" + serverPrincipal +
" serverHost= " + serverHost +
" serverPort= " + port +
(ssf == null ? "" : " ssf = " + ssf.toString()) +
(csf == null ? "" : " csf = " + csf.toString()) + "]";
}
//-----------------------------------
// private methods
//-----------------------------------
/** Pick the serverPrincipal from the given subject. */
private static KerberosPrincipal findServerPrincipal(Subject subject)
throws UnsupportedConstraintException
{
Set pset = subject.getPrincipals();
Set kpset = new HashSet(pset.size()); // Kerberos principals
synchronized (pset) {
for (Iterator iter = pset.iterator(); iter.hasNext();) {
Object p = iter.next();
if (p instanceof KerberosPrincipal)
kpset.add(p);
}
}
if (kpset.isEmpty()) {
throw new UnsupportedConstraintException(
"No KerberosPrincipal found in the serverSubject.");
}
boolean hasAuthPerm = false;
for (Iterator iter = kpset.iterator(); iter.hasNext();) {
KerberosPrincipal kp = (KerberosPrincipal) iter.next();
try {
if (getKey(subject, kp) != null) {
return kp;
} else {
// if reach here, has auth perm, but no key
hasAuthPerm = true;
}
} catch (SecurityException e) {}
}
// note that exceptions throw here are logged in the constructor
if (hasAuthPerm) {
throw new UnsupportedConstraintException(
"Cannot find any Kerberos key in the serverSubject " +
"corresponding to one of its principals.");
} else {
throw new SecurityException(
"Caller does not have AuthenticationPermission " +
"to access Kerberos keys of any principal in " +
"the serverSubject.");
}
}
/**
* Get the a valid Kerberos key corresponding to the specified
* server principal from the given subject.
*
* @param subject the subject from which the key will be
* extracted, cannot be null
* @param principal the principal of the requested key, can not be
* null
* @return the first valid Kerberos key found in the server
* subject corresponding to the given principal , or null
* if none can be found.
* @throws SecurityException if caller does not have the required
* AuthenticationPermission
*/
private static KerberosKey getKey(
final Subject subject, final KerberosPrincipal principal)
{
// SecurityException will be logged by callers
KerberosUtil.checkAuthPermission(principal, null, "listen");
if (subject == null)
return null; // did not properly get the serverSubject
return (KerberosKey) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
Set creds = subject.getPrivateCredentials();
synchronized (creds) {
for (Iterator iter = creds.iterator();
iter.hasNext();)
{
Object cred = iter.next();
if (cred instanceof KerberosKey) {
KerberosKey key = (KerberosKey) cred;
if (!key.isDestroyed() &&
key.getPrincipal().equals(principal))
{
return key;
}
}
}
}
return null;
}
});
}
/**
* Make sure that the passed in listen cookie has the right type,
* and has an equivalent ListenEndpoint to this KerberosServerEndpoint's
* listenEndpoint.
*/
private ListenCookieImpl checkListenCookie(Object cookie) {
if (!(cookie instanceof ListenCookieImpl)) {
throw new IllegalArgumentException(
"Cookie with unexpected type: " + cookie);
}
ListenCookieImpl listenCookie = (ListenCookieImpl) cookie;
if (!listenEndpoint.equals(listenCookie.getListenEndpoint())) {
throw new IllegalArgumentException(
"ListenEndpoint mis-match, enclosing lep is:\n" +
listenEndpoint + "\nwhile cookie's enclosing lep is:\n" +
listenCookie.getListenEndpoint());
}
return listenCookie;
}
//-----------------------------------
// private inner classes
//-----------------------------------
private final class ListenEndpointImpl implements ListenEndpoint {
// Javadoc is inherited from the ListenEndpoint interface
public void checkPermissions() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkListen(port);
KerberosUtil.checkAuthPermission(
serverPrincipal, null, "listen");
} catch (SecurityException se) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"checkPermissions", "check permissions for " +
"{0}\nthrows", new Object[] {this}, se);
}
throw se;
}
}
}
// Javadoc is inherited from the ListenEndpoint interface
public ListenHandle listen(RequestDispatcher requestDispatcher)
throws IOException
{
if (requestDispatcher == null)
throw new NullPointerException("null dispatcher is passed in");
KerberosKey serverKey;
GSSCredential serverCred;
try {
// make sure that serverPrincipal is in serverSubject
if (serverSubject != null &&
!serverSubject.getPrincipals().contains(serverPrincipal))
{
throw new UnsupportedConstraintException(
"Failed to find serverPrincipal " + serverPrincipal +
"in serverSubject's principal set, cannot listen.");
}
// getKey checks AuthenticationPermission "listen"
serverKey = getKey(serverSubject, serverPrincipal);
if (serverKey == null) {
throw new UnsupportedConstraintException(
"No valid Kerberos key in the server subject for " +
serverPrincipal + ", cannot listen.");
}
synchronized (classLock) {
if (gssManager == null) {
gssManager = GSSManager.getInstance();
}
}
try {
serverCred = (GSSCredential) Security.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws GSSException {
return KerberosUtil.getGSSCredential(
serverSubject, serverPrincipal,
gssManager, GSSCredential.ACCEPT_ONLY);
}
});
} catch (PrivilegedActionException pe) {
GSSException ge = (GSSException) pe.getException();
throw new UnsupportedConstraintException(
"Failed to get GSSCredential for server principal: " +
serverPrincipal, ge);
}
} catch (UnsupportedConstraintException uce) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"listen", "listen for {0}\nthrows",
new Object[] {this}, uce);
}
throw uce;
} catch (SecurityException se) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"listen", "listen for {0}\nthrows",
new Object[] {this}, se);
}
throw se;
}
ServerSocket serverSocket;
boolean done = false;
try {
if (ssf != null) {
serverSocket = ssf.createServerSocket(port);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "created {0} using factory " +
"{1}", new Object[]{serverSocket, ssf});
}
} else {
serverSocket = new ServerSocket(port);
logger.log(Level.FINE, "created {0}", serverSocket);
}
done = true;
} finally {
if (!done) {
try {
serverCred.dispose();
} catch (GSSException e) {}
}
}
ListenHandleImpl listenHandle = new ListenHandleImpl(
requestDispatcher, serverKey, serverCred, serverSocket,
Security.getContext());
listenHandle.startAccepting();
return listenHandle;
}
/** Returns a hash code value for this object. */
public int hashCode() {
int hash = getClass().getName().hashCode() ^
System.identityHashCode(serverSubject) ^
serverPrincipal.hashCode() ^ port;
if (ssf != null)
hash ^= ssf.hashCode();
return hash;
}
/**
* Two instances of this class are equal if they are instances
* of the same concrete class; have server subjects that
* compare equal using <code>==</code>; have the same server
* principal; have the same port; and have server socket
* factories that are both null, or have the same actual class
* and are equal. Note that two listen endpoints can be equal
* without having the same server host or client socket
* factory.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof ListenEndpointImpl)) {
return false;
}
ListenEndpointImpl ole = (ListenEndpointImpl) obj;
KerberosServerEndpoint ose = ole.getServerEndpoint();
return serverSubject == ose.serverSubject &&
serverPrincipal.equals(ose.serverPrincipal) &&
port == ose.port &&
Util.sameClassAndEquals(ssf, ose.ssf);
}
/** Returns a string representation of this object. */
public String toString() {
return "KerberosServerEndpoint.ListenEndpointImpl" +
"[serverPrincipal=" + serverPrincipal +
" serverPort = " + port +
(ssf == null ? "" : " ssf = " + ssf.toString()) +
(csf == null ? "" : " csf = " + csf.toString()) + "]";
}
private KerberosServerEndpoint getServerEndpoint() {
return KerberosServerEndpoint.this;
}
}
private final class ListenCookieImpl implements ListenCookie {
int localPort;
ListenCookieImpl(int localPort) {
this.localPort = localPort;
}
ListenEndpointImpl getListenEndpoint() {
return listenEndpoint;
}
int getLocalPort() {
return localPort;
}
}
/**
* A runnable class which creates a server socket and listens for
* client connections. It also maintains a reference to each of
* the accepted connections that have not been closed yet.
*/
private final class ListenHandleImpl implements ListenHandle {
private final RequestDispatcher dispatcher;
/** The security context at the time of the listen. */
private final SecurityContext securityContext;
/** The Kerberos key used for server authentication */
private KerberosKey serverKey; // cached for the calling listen
/** The credential this server uses to authenticate itself */
final GSSCredential serverCred; // cached for establishContext in conn
/** ListenCookie of the listen operation */
ListenCookieImpl listenCookie;
private final ServerSocket serverSocket;
private final Set connections = new HashSet();
private final Object lock = new Object();
private boolean closed = false;
private long acceptFailureTime = 0L; // local to accept thread
private int acceptFailureCount; // local to accept thread
ListenHandleImpl(RequestDispatcher dispatcher, KerberosKey serverKey,
GSSCredential serverCred, ServerSocket serverSocket,
SecurityContext securityContext)
{
this.dispatcher = dispatcher;
this.serverKey = serverKey;
this.serverCred = serverCred;
this.serverSocket = serverSocket;
this.securityContext = securityContext;
listenCookie = new ListenCookieImpl(serverSocket.getLocalPort());
}
/**
* Starts the accept loop.
*/
void startAccepting() {
systemThreadPool.execute(
new Runnable() {
public void run() {
executeAcceptLoop();
}
}, toString() + " accept loop");
}
/**
* Executes the accept loop.
*
* The accept loop runs with the full privileges of this code;
* "accept" SocketPermissions are checked against the direct
* user of this endpoint later, when the
* ServerConnectionManager calls checkPermissions on the
* InboundRequest before passing it to the RequestDispatcher.
*/
private void executeAcceptLoop() {
while (true) {
Socket socket = null;
ServerConnectionImpl connection = null;
boolean done = false;
try {
socket = serverSocket.accept();
synchronized (lock) {
if (closed)
break;
connection = new ServerConnectionImpl(socket, this);
connections.add(connection);
}
logger.log(Level.FINE, "{0} accepted", connection);
/* instead of checking server key presence here,
it will be checked in processRequestData */
ConnectionHandler handler = new ConnectionHandler(
connection, dispatcher, securityContext);
systemThreadPool.execute(handler, handler.toString());
done = true;
} catch (Throwable t) {
synchronized (lock) {
if (closed)
break;
}
try {
if (logger.isLoggable(Level.WARNING)) {
KerberosUtil.logThrow(
logger, Level.WARNING, this.getClass(),
"executeAcceptLoop",
"accept loop for {0} throws",
new Object[] {this}, t);
}
} catch (Throwable tt) {}
boolean knownFailure =
(t instanceof Exception ||
t instanceof OutOfMemoryError ||
t instanceof NoClassDefFoundError);
if (!(knownFailure && continueAfterAcceptFailure(t))) {
/*
* REMIND: Do we really want to give up here,
* for other Errors?
*
* Note: if reach here, we are in trouble already.
* Close serverSocket instead of the listen
* operation completely will free the port
* but left the established connections alone.
*/
try {
serverSocket.close();
} catch (IOException e) {}
if (knownFailure)
return;
throw (Error) t;
}
} finally {
if (!done) {
if (connection != null) {
connection.close();
} else if (socket != null) {
try {
socket.close();
} catch (IOException e) {}
}
}
}
}
}
// Javadoc is inherited from the ListenHandle interface
public ListenCookie getCookie() {
return listenCookie;
}
/** Close the server socket and all accepted connections */
public void close() {
synchronized (lock) {
if (closed)
return;
closed = true;
}
/*
* Iterating over connections without synchronization is
* safe at this point because no other thread will access
* it without verifying that closed is false in a
* synchronized block first.
*/
for (Iterator iter = connections.iterator(); iter.hasNext(); ) {
((ServerConnectionImpl) iter.next()).close();
}
connections.clear();
try {
serverSocket.close();
} catch (IOException e) {}
try {
serverCred.dispose();
} catch (GSSException e) {}
logger.log(Level.FINE, "Listen operation {0} has been closed",
this);
}
/** Returns a string representation of this listen handle. */
public String toString() {
return "KerberosServerEndpoint.ListenHandleImpl" +
"[serverPrincipal=" + serverPrincipal +
" portListening = " + serverSocket.getLocalPort() +
(ssf == null ? "" : " ssf = " + ssf.toString()) +
(csf == null ? "" : " csf = " + csf.toString()) + "]";
}
/**
* Remove a server connection from the connection list of this
* listen operation. Only effective if the listen operation
* has not been closed yet.
*/
void remove(ServerConnectionImpl connection) {
synchronized (lock) {
if (!closed)
connections.remove(connection);
}
}
/**
* Check whether the server key for this listen operation is
* still valid and present in the server subject.
*
* @return true if the server key is still valid and present
* in the server subject, false otherwise.
*/
private boolean checkKey() {
if (serverKey.isDestroyed())
return false;
// caller is responsible for checking AuthenticationPermission
return ((Boolean) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
Set creds = serverSubject.getPrivateCredentials();
synchronized (creds) {
for (Iterator iter = creds.iterator();
iter.hasNext(); )
{
if (serverKey == iter.next())
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
})).booleanValue();
}
/**
* Throttles the accept loop after ServerSocket.accept throws
* an exception, and decides whether to continue at all. The
* current code is borrowed from the JRMP implementation; it
* always continues, but it delays the loop after bursts of
* failed accepts. <p>
*
* This method is copied from
* net.jini.jeri.tcp.TcpServerEndpoint. <p>
*/
private boolean continueAfterAcceptFailure(Throwable t) {
/*
* If we get a burst of NFAIL failures in NMSEC
* milliseconds, then wait for ten seconds. This is to
* ensure that individual failures don't cause hiccups,
* but sustained failures don't hog the CPU in futile
* accept-fail-retry looping.
*/
final int NFAIL = 10;
final int NMSEC = 5000;
long now = System.currentTimeMillis();
if (acceptFailureTime == 0L ||
(now - acceptFailureTime) > NMSEC)
{
// failure time is very old, or this is first failure
acceptFailureTime = now;
acceptFailureCount = 0;
} else {
// failure window was started recently
acceptFailureCount++;
if (acceptFailureCount >= NFAIL) {
try {
Thread.sleep(10000);
} catch (InterruptedException ignore) {
}
// no need to reset counter/timer
}
}
return true;
}
}
/**
* A class responsible for establishing a GSS context for the
* corresponding connection, and supply the connection to this
* endpoint's <code>ServerConnManager</code> for asynchronous
* processing of incoming secure remote calls.
*/
private final class ConnectionHandler implements Runnable {
/** Server connection handled by this handler */
private final ServerConnectionImpl connection;
/** Request dispatcher for the connection */
private final RequestDispatcher dispatcher;
/** The security context at listen time */
private final SecurityContext securityContext;
ConnectionHandler(ServerConnectionImpl connection,
RequestDispatcher dispatcher,
SecurityContext securityContext) {
this.connection = connection;
this.dispatcher = dispatcher;
this.securityContext = securityContext;
}
public void run() {
Throwable t = null;
try {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
// JDK1.4.2 jgss requires current subject
// to be set right during the whole
// process of context establishment.
try {
Subject.doAs(
serverSubject,
new PrivilegedExceptionAction() {
public Object run()
throws IOException,
GSSException
{
connection.establishContext();
return null;
}
});
} catch (PrivilegedActionException pe) {
throw pe.getException();
}
logger.log(Level.FINE, "established " +
"GSSContext for {0}", connection);
return null;
}
});
AccessController.doPrivileged(securityContext.wrap(
new PrivilegedAction() {
public Object run() {
serverConnManager.handleConnection(
connection, dispatcher);
return null;
}
}), securityContext.getAccessControlContext());
} catch (PrivilegedActionException pe) {
t = pe.getException();
} catch (Throwable throwable) {
t = throwable;
}
if (t != null) {
if (logger.isLoggable(Levels.HANDLED)) {
KerberosUtil.logThrow(
logger, Levels.HANDLED, this.getClass(), "run",
"connection handling thread {0} throws",
new Object[] {this}, t);
}
connection.close();
}
}
public String toString() {
return "KerberosServerEndpoint.ConnectionHandler" +
"[serverPrincipal=" + serverPrincipal +
" localPort=" + connection.sock.getLocalPort() +
" remotePort=" + connection.sock.getPort() + "]";
}
}
/** Implementation class of the server side connection abstraction */
private final class ServerConnectionImpl extends KerberosUtil.Connection
implements ServerConnection
{
private final ListenHandleImpl listenHandle; // used in close()
private GSSCredential clientCred;
private Subject clientSubject;
private InputStream istream; // input/output streams exported to
private OutputStream ostream; // upper layers
private InboundRequestHandleImpl handleWithEncryption;
private InboundRequestHandleImpl handleWithoutEncryption;
private final Object lock = new Object();
private boolean closed;
/** Construct a server connection */
ServerConnectionImpl(Socket sock, ListenHandleImpl listenHandle)
throws IOException
{
super(sock);
this.listenHandle = listenHandle;
connectionLogger = logger;
try {
sock.setTcpNoDelay(true);
} catch (SocketException e) {
if (logger.isLoggable(Levels.HANDLED)) {
KerberosUtil.logThrow(
logger, Levels.HANDLED, this.getClass(), "constructor",
"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(), "constructor",
"failed to setKeepAlive option for {0}",
new Object[] {sock}, e);
}
}
istream = new KerberosUtil.ConnectionInputStream(this);
ostream = new KerberosUtil.ConnectionOutputStream(this);
}
// Javadoc is inherited from the ServerConnection interface
public InputStream getInputStream() throws IOException {
return istream;
}
// Javadoc is inherited from the ServerConnection interface
public OutputStream getOutputStream() throws IOException {
return ostream;
}
// javadoc is inherited from the Connection interface
public SocketChannel getChannel() {
return null; // does not support channel for now
}
// Javadoc is inherited from the ServerConnection interface
public InboundRequestHandle processRequestData(
InputStream in, OutputStream out) throws IOException
{
try {
if (clientCred != null) {
try {
if (clientCred.getRemainingLifetime() <= 0) {
close();
throw new SecurityException(
"Delegated client credential expired.");
}
} catch (GSSException e) {
close();
SecurityException se = new SecurityException(
"Failed to getRemainingLifetime from the " +
"delegated client credential.");
se.initCause(e);
throw se;
}
}
if (!serverSubject.getPrincipals().contains(serverPrincipal)) {
throw new SecurityException(
"serverSubject no longer contains serverPrincipal: " +
serverPrincipal + ", failing the connection...");
} else if (!listenHandle.checkKey()) {
// AuthenticationPermission has been checked in listen
throw new SecurityException(
"serverSubject no longer contains the server key " +
"or the server key has been destroyed, failing " +
"the connection...");
}
return doEncryption ? handleWithEncryption :
handleWithoutEncryption;
} catch (SecurityException se) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"processRequestData", "connection {0} throws",
new Object[] {this}, se);
}
close();
throw se;
}
}
// Javadoc is inherited from the ServerConnection interface
public void checkPermissions(InboundRequestHandle handle) {
checkRequestHandle(handle);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkAccept(sock.getInetAddress().getHostAddress(),
sock.getPort());
KerberosUtil.checkAuthPermission(
serverPrincipal, clientPrincipal, "accept");
}
}
// Javadoc is inherited from the ServerConnection interface
public InvocationConstraints checkConstraints(
InboundRequestHandle handle,
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
InboundRequestHandleImpl rh = checkRequestHandle(handle);
if (constraints == null) {
throw new NullPointerException(
"constraints can not be null");
}
CacheKey key = new CacheKey(rh, constraints);
Object result = softCache.get(key);
if (result != null) {
if (result instanceof UnsupportedConstraintException)
throw (UnsupportedConstraintException) result;
return (InvocationConstraints) result;
}
// no cached result, has to do the whole analysis
for (Iterator iter = constraints.requirements().iterator();
iter.hasNext(); )
{
try {
InvocationConstraint c =
(InvocationConstraint) iter.next();
if (!KerberosUtil.isSupportableConstraint(c)) {
UnsupportedConstraintException e =
new UnsupportedConstraintException(
"A constraint unsupportable by this " +
"endpoint has been required: " + c);
softCache.put(key, e);
throw e;
}
if (!KerberosUtil.isSatisfiable(rh.config, c)) {
UnsupportedConstraintException e =
new UnsupportedConstraintException(
"A required constraint (" + c + ") is not " +
"satisfied by this connection: " + this);
softCache.put(key, e);
throw e;
}
} catch (UnsupportedConstraintException uce) {
if (logger.isLoggable(Levels.FAILED)) {
KerberosUtil.logThrow(
logger, Levels.FAILED, this.getClass(),
"checkConstraints", "connection {0}\ndoes not " +
"satisfies {1},\nthrows",
new Object[] {this, constraints}, uce);
}
throw uce;
}
}
// constraints need upper layer help to be fully fulfilled
InvocationConstraints unfulfilledConstraints =
InvocationConstraints.EMPTY;
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;
}
softCache.put(key, unfulfilledConstraints);
return unfulfilledConstraints;
}
// javadoc is inherited from the ServerConnection interface
public void populateContext(
InboundRequestHandle handle, Collection context)
{
checkRequestHandle(handle);
Util.populateContext(context, sock.getInetAddress());
Util.populateContext(context, clientSubject);
}
// Javadoc is inherited from the ServerConnection interface
public void close() {
synchronized (lock) {
if (closed)
return;
closed = true;
}
listenHandle.remove(this);
super.close(); // super.close() logs this event
}
/** Returns a string representation of this object. */
public String toString() {
StringBuffer b = new StringBuffer(
"KerberosServerEndpoint.ServerConnectionImpl[");
b.append("clientPrincipal=" + clientPrincipal);
b.append(" serverPrincipal=" + serverPrincipal);
b.append(" doEncryption=" + doEncryption);
b.append(" doDelegation=" + doDelegation);
b.append(" client=" + sock.getInetAddress().getHostName());
b.append(":" + sock.getPort());
b.append(" server=" + sock.getLocalAddress().getHostName());
b.append(":" + sock.getLocalPort());
b.append(']');
return b.toString();
}
/** Carry out the GSS context establishment message exchanges */
void establishContext() throws IOException, GSSException {
/* gssContext is defined in parent class for receiving
incoming requests from the client */
gssContext = gssManager.createContext(listenHandle.serverCred);
byte[] token = null;
while (!gssContext.isEstablished()) {
token = new byte[dis.readInt()];
dis.readFully(token);
token = gssContext.acceptSecContext(token, 0, token.length);
/*
* Send a token to the peer if one was generated by
* acceptSecContext
*/
if (token != null) {
dos.writeInt(token.length);
dos.write(token);
dos.flush();
}
}
if (!gssContext.getIntegState()) {
// this exception is logged by caller of this method
throw new IOException("Established GSSContext does not " +
"support integrity.");
}
/*
* Note that gssContext.getConfState() on client and
* server side might not match each other, the meaningful
* value will have to be acquired from the message
* property of each token received.
*/
doEncryption = gssContext.getConfState();
doDelegation = gssContext.getCredDelegState();
GSSName clientName = gssContext.getSrcName();
clientPrincipal = new KerberosPrincipal(clientName.toString());
if (gssContext.getCredDelegState())
clientCred = gssContext.getDelegCred();
clientSubject = GSSUtil.createSubject(clientName, clientCred);
clientSubject.setReadOnly();
/* these handles need to be initialized after context
establishment, which sets client principal and deleg */
handleWithEncryption = new InboundRequestHandleImpl(true);
handleWithoutEncryption = new InboundRequestHandleImpl(false);
}
/**
* Make sure that the passed in inbound request handle has the
* right type, and was previously instantiated in this
* connection.
*/
private InboundRequestHandleImpl checkRequestHandle(Object h) {
if (h != handleWithEncryption && h != handleWithoutEncryption) {
throw new IllegalArgumentException(
"Unknown InboundRequestHandle: " + h);
}
return (InboundRequestHandleImpl) h;
}
private final class InboundRequestHandleImpl
implements InboundRequestHandle
{
final Config config;
InboundRequestHandleImpl(boolean encry) {
/* doEncryption is switchable, cannot be directly
extracted from the enclosing connection */
config = new Config(clientPrincipal, serverPrincipal,
encry, doDelegation);
}
}
/** The key used for the softcache of this server endpoint. */
private final class CacheKey {
private final InboundRequestHandleImpl handle;
private final InvocationConstraints constraints;
/** Construct a Key object */
CacheKey(InboundRequestHandleImpl handle,
InvocationConstraints constraints)
{
this.handle = handle;
this.constraints = constraints;
}
public int hashCode() {
// identityHashCode() should be faster
return handle.hashCode() ^
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 handle == okey.handle &&
constraints == okey.constraints;
}
}
}
}