001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.protocols.jmx;
028    
029    import static org.opends.server.loggers.debug.DebugLogger.*;
030    import org.opends.server.loggers.debug.DebugTracer;
031    import org.opends.server.types.DebugLogLevel;
032    
033    import java.io.IOException;
034    
035    import java.io.Serializable;
036    import java.net.Socket;
037    import java.rmi.server.RMIClientSocketFactory;
038    
039    import java.util.Map;
040    
041    // JSSE
042    import javax.net.ssl.SSLContext;
043    import javax.net.ssl.TrustManager;
044    
045    import javax.net.ssl.SSLSocket;
046    import javax.net.ssl.SSLSocketFactory;
047    
048    /**
049     * A <code>DirectoryRMIClientSocketFactory</code> instance is used by the
050     * RMI runtime in order to obtain client sockets for RMI calls via SSL.
051     * <p>
052     * This class implements <code>RMIClientSocketFactory</code> over the
053     * Secure Sockets Layer (SSL) or Transport Layer Security (TLS) protocols.
054     * </p>
055     */
056    public class DirectoryRMIClientSocketFactory implements
057        RMIClientSocketFactory, Serializable
058    {
059      /**
060       * The tracer object for the debug logger.
061       */
062      private static final DebugTracer TRACER = getTracer();
063    
064    
065    
066      /**
067       * The serial version identifier required to satisfy the compiler because
068       * this class implements the <CODE>java.io.Serializable</CODE> interface.
069       * This value was generated using the <CODE>serialver</CODE> command-line
070       * utility included with the Java SDK.
071       */
072      private static final long serialVersionUID = -6701450600497520362L;
073    
074      /**
075       * the static thread-local connection environment used by the RMI
076       * client to configure this factory.
077       */
078      private static InheritableThreadLocal<Map> tlMapConnectionEnv =
079        new InheritableThreadLocal<Map>();
080    
081      /**
082       * The static thread-local target server hostname used by the RMI
083       * client to configure this factory.
084       */
085      private static InheritableThreadLocal<String> tlStrServerHostname =
086        new InheritableThreadLocal<String>();
087    
088      /**
089       * the connection mode. <code>true</code> indicates that the client
090       * will be authenticated by its certificate (SSL protocol).
091       * <code>false</code> indicates that we have to perform an lDAP
092       * authentication
093       */
094      private final boolean needClientCertificate;
095    
096      /**
097       * the ssl socket factory (do not serialize).
098       */
099      private transient SSLSocketFactory sslSocketFactory = null;
100    
101      /**
102       * the host to connect to (do not serialize).
103       */
104      private transient String serverHostname = null;
105    
106      /**
107       * Constructs a new <code>DirectoryRMIClientSocketFactory</code>.
108       *
109       * @param wellknown
110       *            <code>true</code> for wellknown, <code>false</code>
111       *            otherwise
112       */
113      public DirectoryRMIClientSocketFactory(boolean wellknown)
114      {
115        this.needClientCertificate = wellknown;
116    
117        // We don't force the initialization of the SSLSocketFactory
118        // at construction time - because the RMI client socket factory is
119        // created on the server side, where that initialization is a
120        // priori
121        // meaningless, unless both server and client run in the same JVM.
122        // So contrarily to what we do for the server side, the
123        // initialization
124        // of the SSLSocketFactory will be delayed until the first time
125        // createSocket() is called.
126      }
127    
128      /**
129       * Sets the thread-local connection environment.
130       *
131       * @param connectionEnv the new connection env
132       */
133      public static void setConnectionEnv(Map connectionEnv)
134      {
135        tlMapConnectionEnv.set(connectionEnv);
136      }
137    
138      /**
139       * Returns the thread-local connection environment.
140       *
141       * @return Map the connection environment used by new connections
142       */
143      public static Map getConnectionEnv()
144      {
145        return tlMapConnectionEnv.get();
146      }
147    
148      /**
149       * Sets the thread-local target server hostname.
150       *
151       * @param serverHostname
152       *            the target server hostname
153       */
154      public static void setServerHostname(String serverHostname)
155      {
156        tlStrServerHostname.set(serverHostname);
157      }
158    
159      /**
160       * Returns the thread-local target server hostname.
161       *
162       * @return String the target server hostname
163       */
164      public static String getServerHostname()
165      {
166        return tlStrServerHostname.get();
167      }
168    
169      /**
170       * Returns the connection mode as configured at construction time.
171       *
172       * @return boolean <code>true</code> for wellknown,
173       *         <code>false</code> otherwise
174       */
175      public boolean getNeedClientCertificate()
176      {
177        return needClientCertificate;
178      }
179    
180      /**
181       * Creates an SSL socket configured with the right trust stores and the
182       * right target host.
183       * <p>
184       * The keystore and truststore used come from client configuration and
185       * depend on the connection mode specified at construction time.
186       * <p>
187       * The target host is the host specified except if a different host was
188       * specified in the connection environment.
189       *
190       * @param host
191       *            the target host as known by the RMI stack
192       * @param port
193       *            the target port number
194       * @return an SSL socket
195       *
196       * @throws IOException
197       *             if an error occurs while configuring the socket
198       */
199      public Socket createSocket(String host, int port) throws IOException
200      {
201        //
202        // gets ssl socket factory
203        SSLSocketFactory sslSocketFactory = getSSLSocketFactory();
204        String realhost = getRealServerHostname(host);
205    
206        final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
207            realhost,
208            port);
209    
210        return sslSocket;
211      }
212    
213      /**
214       * Indicates whether some other object is "equal to" this one.
215       * <p>
216       * Because each RMI client might have its own configuration, we do not
217       * try to optimize anything. Each RMI connector uses its own RMI client
218       * socket factory. In other words, Directory RMI clients never share
219       * the same client socket factory.
220       * </p>
221       *
222       * @param obj
223       *            the reference object with which to compare
224       * @return <code>true</code> if this object is the same as the obj
225       *         argument <code>false</code> otherwise
226       */
227      public boolean equals(Object obj)
228      {
229        return super.equals(obj);
230      }
231    
232      /**
233       * Returns a hash code value for this
234       * <code>DirectoryRMIClientSocketFactory</code>.
235       *
236       * @return a hash code value for this
237       *         <code>DirectoryRMIClientSocketFactory</code>
238       */
239      public int hashCode()
240      {
241        return super.hashCode();
242      }
243    
244      /**
245       * Returns the real server hostname to connect to.
246       *
247       * @param rmiHostname
248       *            the target hostname as known by RMI stack
249       * @return String the real hostname to connect to
250       * @throws IOException
251       *             if an error occurs
252       */
253      private synchronized String getRealServerHostname(String rmiHostname)
254          throws IOException
255      {
256        if (serverHostname == null)
257        {
258          //
259          // does the client specify another host by
260          // using thread-local static parameter ?
261          serverHostname = getServerHostname();
262    
263          //
264          // if not found here, don't look for real host in static
265          // anymore
266          if (serverHostname == null)
267          {
268            serverHostname = "";
269          }
270        }
271    
272        if (serverHostname.length() > 0)
273        {
274          return serverHostname;
275        }
276        else
277        {
278          return rmiHostname;
279        }
280      }
281    
282      /**
283       * Returns the ssl socket factory used by this RMI client socket
284       * factory.
285       *
286       * @return SSLSocketFactory the ssl socket factory
287       * @throws IOException
288       *             if an error occurs
289       */
290      private synchronized SSLSocketFactory getSSLSocketFactory()
291          throws IOException
292      {
293        if (sslSocketFactory == null)
294        {
295          if (debugEnabled())
296          {
297            TRACER.debugVerbose("sslSocketFactory is null, get a new one");
298          }
299    
300          // socket factory not yet initialized
301          // initialize the trust
302          Map connectionEnv = getConnectionEnv();
303          TrustManager[] tms = null;
304    
305          //
306          // Check if a trust manager array was provided in the
307          // connection
308          // Env. If yes, use it for this SSL Connection
309          if ((connectionEnv != null)
310              && (connectionEnv
311              .containsKey(JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY)))
312          {
313            try
314            {
315              tms = (TrustManager[]) connectionEnv
316                  .get(JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY);
317            }
318            catch (Exception e)
319            {
320              if (debugEnabled())
321              {
322                TRACER.debugCaught(DebugLogLevel.ERROR, e);
323              }
324              tms = null;
325            }
326    
327            if (tms == null)
328            {
329              //
330              // Why? The value is not invalid ?
331              // Too bad: we raised an exception
332              throw new IOException("invalid type or null value for key ["
333                  + JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY
334                  + "] in connection environment : "
335                  + connectionEnv
336                  .get(JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY));
337            }
338          }
339    
340          // now we have the array of trust Manager
341          // we can initialize the ssl ctx
342          SSLContext ctx = null;
343          try
344          {
345            ctx = SSLContext.getInstance("TLSv1");
346            ctx.init(null, tms, null);
347          }
348          catch (Exception e)
349          {
350            if (debugEnabled())
351            {
352              TRACER.debugCaught(DebugLogLevel.ERROR, e);
353            }
354            throw new IOException("Unable to initialize SSL context : "
355                + e.getMessage());
356          }
357    
358          // create the SSLSocket
359          sslSocketFactory = ctx.getSocketFactory();
360        }
361        return sslSocketFactory;
362      }
363    }