001: /*
002: * JacORB - a free Java ORB
003: *
004: * Copyright (C) 1997-2004 Gerald Brose.
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Library General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Library General Public License for more details.
015: *
016: * You should have received a copy of the GNU Library General Public
017: * License along with this library; if not, write to the Free
018: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
019: */
020:
021: package org.jacorb.orb.iiop;
022:
023: import java.io.BufferedOutputStream;
024: import java.io.IOException;
025: import java.net.SocketTimeoutException;
026: import java.util.ArrayList;
027: import java.util.Iterator;
028: import java.util.List;
029:
030: import org.apache.avalon.framework.configuration.Configurable;
031: import org.apache.avalon.framework.configuration.Configuration;
032: import org.apache.avalon.framework.configuration.ConfigurationException;
033: import org.jacorb.orb.CDRInputStream;
034: import org.jacorb.orb.factory.SocketFactory;
035: import org.jacorb.orb.giop.TransportManager;
036: import org.jacorb.orb.listener.TCPConnectionEvent;
037: import org.jacorb.orb.listener.TCPConnectionListener;
038: import org.omg.CORBA.TIMEOUT;
039: import org.omg.CSIIOP.CompoundSecMechList;
040: import org.omg.CSIIOP.CompoundSecMechListHelper;
041: import org.omg.CSIIOP.Confidentiality;
042: import org.omg.CSIIOP.DetectMisordering;
043: import org.omg.CSIIOP.DetectReplay;
044: import org.omg.CSIIOP.EstablishTrustInClient;
045: import org.omg.CSIIOP.EstablishTrustInTarget;
046: import org.omg.CSIIOP.Integrity;
047: import org.omg.CSIIOP.TAG_CSI_SEC_MECH_LIST;
048: import org.omg.CSIIOP.TAG_TLS_SEC_TRANS;
049: import org.omg.CSIIOP.TLS_SEC_TRANS;
050: import org.omg.CSIIOP.TLS_SEC_TRANSHelper;
051: import org.omg.SSLIOP.SSL;
052: import org.omg.SSLIOP.SSLHelper;
053: import org.omg.SSLIOP.TAG_SSL_SEC_TRANS;
054:
055: /**
056: * @author Nicolas Noffke
057: * @author Andre Spiegel
058: * @version $Id: ClientIIOPConnection.java,v 1.32 2006/07/25 15:43:21 alphonse.bendt Exp $
059: */
060: public class ClientIIOPConnection extends IIOPConnection implements
061: Configurable {
062: private int timeout = 0;
063:
064: private int ssl_port = -1;
065: private int noOfRetries = 5;
066: private int retryInterval = 0;
067: private boolean doSupportSSL = false;
068: private TransportManager transportManager;
069: private TCPConnectionListener connectionListener;
070: private boolean keepAlive;
071:
072: //for testing purposes only: # of open transports
073: //used by org.jacorb.test.orb.connection[Client|Server]ConnectionTimeoutTest
074: public static int openTransports = 0;
075:
076: //for storing exceptions during connect
077: private Exception exception = null;
078:
079: public ClientIIOPConnection() {
080: super ();
081:
082: use_ssl = false;
083: }
084:
085: public void configure(Configuration configuration)
086: throws ConfigurationException {
087: super .configure(configuration);
088: //get the client-side timeout property value
089:
090: timeout = configuration.getAttributeAsInteger(
091: "jacorb.connection.client.idle_timeout", 0);
092: noOfRetries = configuration.getAttributeAsInteger(
093: "jacorb.retries", 5);
094: retryInterval = configuration.getAttributeAsInteger(
095: "jacorb.retry_interval", 500);
096: doSupportSSL = configuration.getAttribute(
097: "jacorb.security.support_ssl", "off").equals("on");
098: transportManager = this .configuration.getORB()
099: .getTransportManager();
100:
101: keepAlive = configuration.getAttributeAsBoolean(
102: "jacorb.connection.client.keepalive", false);
103:
104: connectionListener = transportManager.getSocketFactoryManager()
105: .getTCPListener();
106: }
107:
108: /**
109: * Attempts to establish a 1-to-1 connection with a server using the
110: * Listener endpoint from the given Profile description. It shall
111: * throw a COMM_FAILURE exception if it fails (e.g. if the endpoint
112: * is unreachable) or a TIMEOUT exception if the given time_out period
113: * has expired before a connection is established. If the connection
114: * is successfully established it shall store the used Profile data.
115: *
116: */
117: public synchronized void connect(
118: org.omg.ETF.Profile server_profile, long time_out) {
119: if (!connected) {
120: if (server_profile instanceof IIOPProfile) {
121: this .profile = (IIOPProfile) server_profile;
122: } else {
123: throw new org.omg.CORBA.BAD_PARAM(
124: "attempt to connect an IIOP connection "
125: + "to a non-IIOP profile: "
126: + server_profile.getClass());
127: }
128:
129: final IIOPLoopback loopback = getLocalLoopback();
130: if (loopback != null) {
131: final IIOPLoopbackInputStream lis = new IIOPLoopbackInputStream();
132: final IIOPLoopbackOutputStream los = new IIOPLoopbackOutputStream();
133:
134: loopback.initLoopback(lis, los);
135:
136: in_stream = lis;
137: out_stream = los;
138:
139: connected = true;
140:
141: //for testing purposes
142: ++openTransports;
143:
144: return;
145: }
146:
147: checkSSL();
148:
149: int retries = noOfRetries;
150: while (retries >= 0) {
151: try {
152: createSocket(time_out);
153:
154: if (timeout != 0) {
155: /* re-set the socket timeout */
156: socket
157: .setSoTimeout(timeout /*note: this is another timeout!*/);
158: }
159:
160: socket.setKeepAlive(keepAlive);
161:
162: in_stream = socket.getInputStream();
163:
164: out_stream = new BufferedOutputStream(socket
165: .getOutputStream());
166:
167: if (logger.isInfoEnabled()) {
168: logger.info("Connected to "
169: + connection_info
170: + " from local port "
171: + socket.getLocalPort()
172: + (this .isSSL() ? " via SSL" : "")
173: + ((timeout == 0) ? "" : " Timeout: "
174: + timeout));
175: }
176:
177: connected = true;
178:
179: //for testing purposes
180: ++openTransports;
181:
182: return;
183: } catch (IOException c) {
184: logger.debug("Exception", c);
185:
186: //only sleep and print message if we're actually
187: //going to retry
188: retries--;
189: if (retries >= 0) {
190: if (logger.isInfoEnabled()) {
191: logger.info("Retrying to connect to "
192: + connection_info);
193: }
194:
195: try {
196: Thread.sleep(retryInterval);
197: } catch (InterruptedException i) {
198: }
199: }
200: } catch (TIMEOUT e) {
201: //thrown if timeout is expired
202: profile = null;
203: use_ssl = false;
204: ssl_port = -1;
205: throw e;
206: }
207:
208: }
209:
210: if (retries < 0) {
211: profile = null;
212: use_ssl = false;
213: ssl_port = -1;
214: throw new org.omg.CORBA.TRANSIENT(
215: "Retries exceeded, couldn't reconnect to "
216: + connection_info);
217: }
218: }
219: }
220:
221: private IIOPLoopback getLocalLoopback() {
222: final IIOPProfile iiopProfile = (IIOPProfile) profile;
223: final List addressList = new ArrayList();
224: addressList.add(iiopProfile.getAddress());
225: addressList.addAll(iiopProfile.getAlternateAddresses());
226:
227: final Iterator addressIterator = addressList.iterator();
228: final IIOPLoopbackRegistry registry = IIOPLoopbackRegistry
229: .getRegistry();
230:
231: while (addressIterator.hasNext()) {
232: final IIOPAddress address = (IIOPAddress) addressIterator
233: .next();
234: final IIOPLoopback loopback = registry.getLoopback(address);
235: if (loopback != null) {
236: return loopback;
237: }
238: }
239:
240: return null;
241: }
242:
243: /**
244: * Tries to create a socket connection to any of the addresses in
245: * the target profile, starting with the primary IIOP address,
246: * and then any alternate IIOP addresses that have been specified.
247: */
248: private void createSocket(long time_out) throws IOException {
249: List addressList = new ArrayList();
250: addressList.add(((IIOPProfile) profile).getAddress());
251: addressList.addAll(((IIOPProfile) profile)
252: .getAlternateAddresses());
253:
254: Iterator addressIterator = addressList.iterator();
255:
256: exception = null;
257: socket = null;
258: while (socket == null && addressIterator.hasNext()) {
259: try {
260: IIOPAddress address = (IIOPAddress) addressIterator
261: .next();
262:
263: final SocketFactory factory = (use_ssl) ? getSSLSocketFactory()
264: : getSocketFactory();
265:
266: final String ipAddress = address.getIP();
267: final int port = (use_ssl) ? ssl_port : address
268: .getPort();
269: if (ipAddress.indexOf(':') == -1) {
270: connection_info = ipAddress + ":" + port;
271: } else {
272: connection_info = "[" + ipAddress + "]:" + port;
273: }
274:
275: if (logger.isDebugEnabled()) {
276: logger.debug("Trying to connect to "
277: + connection_info + " with timeout="
278: + time_out
279: + (use_ssl ? " using SSL." : "."));
280: }
281: exception = null;
282:
283: if (time_out > 0) {
284: final int truncatedTimeout = (int) time_out;
285: if (truncatedTimeout != time_out) {
286: logger
287: .warn("timeout might be changed due to conversion from long to int. old value: "
288: + time_out
289: + " new value: "
290: + truncatedTimeout);
291: }
292: socket = factory.createSocket(ipAddress, port,
293: truncatedTimeout);
294: } else {
295: //no timeout --> may hang forever!
296: socket = factory.createSocket(ipAddress, port);
297: }
298: } catch (Exception e) {
299: exception = e;
300: } finally {
301: if (socket != null
302: && connectionListener.isListenerEnabled()) {
303: connectionListener
304: .connectionOpened(new TCPConnectionEvent(
305: this , socket.getInetAddress()
306: .toString(), socket
307: .getPort(), socket
308: .getLocalPort(),
309: getLocalhost()));
310: }
311: }
312: }
313:
314: if (exception != null) {
315: if (exception instanceof SocketTimeoutException) {
316: throw new TIMEOUT("connection timeout of " + timeout
317: + " milliseconds expired: " + exception);
318: } else if (exception instanceof IOException) {
319: throw (IOException) exception;
320: } else {
321: //not expected, because all used methods just throw IOExceptions or TIMEOUT
322: //but... never say never ;o)
323: throw new IOException("Unexpected exception occured: "
324: + exception.toString());
325: }
326: }
327: }
328:
329: public synchronized void close() {
330: if (!connected) {
331: return;
332: }
333:
334: try {
335: if (socket != null) {
336: socket.close();
337: }
338:
339: //this will cause exceptions when trying to read from
340: //the streams. Better than "nulling" them.
341: if (in_stream != null) {
342: in_stream.close();
343: }
344: if (out_stream != null) {
345: out_stream.close();
346: }
347:
348: //for testing purposes
349: --openTransports;
350:
351: if (logger.isInfoEnabled()) {
352: logger.info("Client-side TCP transport to "
353: + connection_info + " closed.");
354: }
355:
356: connected = false;
357: } catch (IOException ex) {
358: if (logger.isDebugEnabled()) {
359: logger.debug("Exception when closing the socket", ex);
360: }
361:
362: throw to_COMM_FAILURE(ex, socket);
363: } finally {
364: if (socket != null
365: && connectionListener.isListenerEnabled()) {
366: connectionListener
367: .connectionClosed(new TCPConnectionEvent(this ,
368: socket.getInetAddress().toString(),
369: socket.getPort(),
370: socket.getLocalPort(), getLocalhost()));
371: }
372: }
373: }
374:
375: /**
376: * Check if this client should use SSL when connecting to
377: * the server described by the 'profile'. The result
378: * is stored in the private fields use_ssl and ssl_port.
379: */
380: private void checkSSL() {
381: // Check if SSL profile
382: if (((IIOPProfile) profile).getSSL() == null) {
383: return;
384: }
385:
386: CompoundSecMechList sas;
387: try {
388: sas = (CompoundSecMechList) ((IIOPProfile) profile)
389: .getComponent(TAG_CSI_SEC_MECH_LIST.value,
390: CompoundSecMechListHelper.class);
391: } catch (Exception ex) {
392: logger.info("Not able to process security mech. component");
393: return;
394: }
395:
396: TLS_SEC_TRANS tls = null;
397: if (sas != null
398: && sas.mechanism_list[0].transport_mech.tag == TAG_TLS_SEC_TRANS.value) {
399: try {
400: byte[] tagData = sas.mechanism_list[0].transport_mech.component_data;
401: final CDRInputStream in = new CDRInputStream(
402: (org.omg.CORBA.ORB) null, tagData);
403: try {
404: in.openEncapsulatedArray();
405: tls = TLS_SEC_TRANSHelper.read(in);
406: } finally {
407: in.close();
408: }
409: } catch (Exception e) {
410: logger.warn("Error parsing TLS_SEC_TRANS: " + e);
411: }
412: }
413:
414: SSL ssl = (SSL) ((IIOPProfile) profile).getComponent(
415: TAG_SSL_SEC_TRANS.value, SSLHelper.class);
416: //if( sas != null &&
417: // ssl != null )
418: //{
419: // ssl.target_requires |= sas.mechanism_list[0].target_requires;
420: //}
421:
422: // SSL usage is decided the following way: At least one side
423: // must require it. Therefore, we first check if it is
424: // supported by both sides, and then if it is required by at
425: // least one side. The distinction between
426: // EstablishTrustInTarget and EstablishTrustInClient is
427: // handled at the socket factory layer.
428:
429: //the following is used as a bit mask to check, if any of
430: //these options are set
431: int minimum_options = Integrity.value | Confidentiality.value
432: | DetectReplay.value | DetectMisordering.value
433: | EstablishTrustInTarget.value
434: | EstablishTrustInClient.value;
435:
436: int client_required = 0;
437: int client_supported = 0;
438:
439: //only read in the properties if ssl is really supported.
440: if (doSupportSSL) {
441: client_required = configuration.getAttributeAsInteger(
442: "jacorb.security.ssl.client.required_options", 16);
443: client_supported = configuration.getAttributeAsInteger(
444: "jacorb.security.ssl.client.supported_options", 16);
445: }
446:
447: if (tls != null && // server knows about ssl...
448: ((tls.target_supports & minimum_options) != 0) && //...and "really" supports it
449: doSupportSSL && //client knows about ssl...
450: ((client_supported & minimum_options) != 0) && //...and "really" supports it
451: (((tls.target_requires & minimum_options) != 0) || //server ...
452: ((client_required & minimum_options) != 0))) //...or client require it
453: {
454: use_ssl = true;
455: ssl_port = tls.addresses[0].port;
456: if (ssl_port < 0) {
457: ssl_port += 65536;
458: }
459:
460: if (logger.isDebugEnabled()) {
461: logger.debug("Selecting TLS for connection");
462: }
463: } else if (ssl != null && // server knows about ssl...
464: ((ssl.target_supports & minimum_options) != 0) && //...and "really" supports it
465: doSupportSSL && //client knows about ssl...
466: ((client_supported & minimum_options) != 0) && //...and "really" supports it
467: (((ssl.target_requires & minimum_options) != 0) || //server ...
468: ((client_required & minimum_options) != 0))) //...or client require it
469: {
470: use_ssl = true;
471: ssl_port = ssl.port;
472: if (ssl_port < 0) {
473: ssl_port += 65536;
474: }
475:
476: if (logger.isDebugEnabled()) {
477: logger.debug("Selecting SSL for connection");
478: }
479: }
480: //prevent client policy violation, i.e. opening plain TCP
481: //connections when SSL is required
482: else if ( // server doesn't know ssl...
483: doSupportSSL && //client knows about ssl...
484: ((client_required & minimum_options) != 0)) //...and requires it
485: {
486: throw new org.omg.CORBA.NO_PERMISSION(
487: "Client-side policy requires SSL/TLS, but server doesn't support it");
488: } else {
489: use_ssl = false;
490: ssl_port = -1;
491: }
492: }
493:
494: private SocketFactory getSocketFactory() {
495: return transportManager.getSocketFactoryManager()
496: .getSocketFactory();
497: }
498:
499: private SocketFactory getSSLSocketFactory() {
500: return transportManager.getSocketFactoryManager()
501: .getSSLSocketFactory();
502: }
503: }
|