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 }