/*
* JacORB - a free Java ORB
*
* Copyright (C) 1997-2008 Gerald Brose.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.jacorb.orb.iiop;
import java.util.*;
import java.net.*;
import org.jacorb.config.*;
import org.slf4j.Logger;
import org.jacorb.orb.CDRInputStream;
import org.jacorb.orb.CDROutputStream;
import org.jacorb.orb.TaggedComponentList;
import org.jacorb.orb.etf.ProtocolAddressBase;
import org.omg.CSIIOP.CompoundSecMechList;
import org.omg.CSIIOP.CompoundSecMechListHelper;
import org.omg.CSIIOP.Confidentiality;
import org.omg.CSIIOP.DetectMisordering;
import org.omg.CSIIOP.DetectReplay;
import org.omg.CSIIOP.EstablishTrustInClient;
import org.omg.CSIIOP.EstablishTrustInTarget;
import org.omg.CSIIOP.Integrity;
import org.omg.CSIIOP.TAG_CSI_SEC_MECH_LIST;
import org.omg.CSIIOP.TAG_TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANSHelper;
import org.omg.ETF.Profile;
import org.omg.IOP.TAG_ALTERNATE_IIOP_ADDRESS;
import org.omg.IOP.TAG_INTERNET_IOP;
import org.omg.IOP.TaggedComponent;
import org.omg.SSLIOP.SSL;
import org.omg.SSLIOP.SSLHelper;
import org.omg.SSLIOP.TAG_SSL_SEC_TRANS;
/**
* @author Andre Spiegel
* @version $Id: IIOPProfile.java,v 1.34 2009-05-03 21:35:56 andre.spiegel Exp $
*/
public class IIOPProfile
extends org.jacorb.orb.etf.ProfileBase implements Cloneable
{
private IIOPAddress primaryAddress = null;
private Logger logger;
/** the following is used as a bit mask to check if any of these options are set */
private static final int MINIMUM_OPTIONS = Integrity.value | Confidentiality.value | DetectReplay.value |
DetectMisordering.value | EstablishTrustInTarget.value | EstablishTrustInClient.value;
public IIOPProfile()
{
super();
}
public IIOPProfile(byte[] data)
{
this();
initFromProfileData(data);
}
public IIOPProfile(IIOPAddress address, byte[] objectKey, int minor)
{
this();
this.version = new org.omg.GIOP.Version((byte)1,(byte)minor);
this.primaryAddress = address;
this.objectKey = objectKey;
this.components = new TaggedComponentList();
}
public IIOPProfile(IIOPAddress address, byte[] objectKey)
{
this(address, objectKey, 2);
}
/**
* Constructs an IIOPProfile from a corbaloc URL. Only to be used
* from the corbaloc parser.
*/
public IIOPProfile(String corbaloc)
{
this();
this.version = null;
this.primaryAddress = null;
this.objectKey = null;
this.components = null;
this.corbalocStr = corbaloc;
}
public void configure(Configuration config)
throws ConfigurationException
{
configuration = (org.jacorb.config.Configuration)config;
logger = configuration.getLogger("jacorb.iiop.profile");
if (primaryAddress != null)
{
primaryAddress.configure(config);
}
if (corbalocStr != null)
{
try
{
decode_corbaloc(corbalocStr);
}
catch(Exception e)
{
logger.debug("unable to decode_corbaloc", e);
}
}
addAlternateAddresses ((org.jacorb.config.Configuration)config);
}
/**
* An IPv6 corbaloc URL is of the format
* corbaloc:iiop:[fe80:5443::3333%3]:2809/my_object
* where the zone ID seperator is / or % depending on
* what the underlying OS supports.
*
* This preserves compatilibility with TAO, and falls in
* line with RFC 2732 and discussion on OMG news groups.
*/
private void decode_corbaloc(final String address)
{
String addr = address;
String host = "127.0.0.1"; //default to localhost
short port = 2809; // default IIOP port
int major = 1; // should be 1 by default. see 13.6.10.3
int minor = 0; // should be 0 by default. see 13.6.10.3
String errorstr =
"Illegal IIOP protocol format in object address format: " + addr;
int sep = addr.indexOf(':');
String protocol_identifier = "";
if( sep != 0)
{
protocol_identifier = addr.substring(0, sep);
}
if( sep + 1 == addr.length())
{
throw new IllegalArgumentException(errorstr);
}
addr = addr.substring(sep + 1);
// decode optional version number
sep = addr.indexOf( '@' );
if( sep > -1)
{
String ver_str = addr.substring(0,sep);
addr = addr.substring(sep+1);
sep = ver_str.indexOf('.');
if( sep != -1 )
{
try
{
major = Integer.parseInt(ver_str.substring(0,sep));
minor = Integer.parseInt(ver_str.substring(sep+1));
}
catch( NumberFormatException nfe )
{
throw new IllegalArgumentException(errorstr);
}
}
}
version = new org.omg.GIOP.Version((byte)major,(byte)minor);
int ipv6SeperatorStart = -1;
int ipv6SeperatorEnd = -1;
ipv6SeperatorStart = addr.indexOf('[');
if (ipv6SeperatorStart != -1)
{
ipv6SeperatorEnd = addr.indexOf(']');
if (ipv6SeperatorEnd == -1)
{
throw new IllegalArgumentException(errorstr);
}
}
sep = addr.indexOf(':');
if( sep != -1 )
{
if (ipv6SeperatorStart != -1) //IPv6
{
host=addr.substring(ipv6SeperatorStart + 1, ipv6SeperatorEnd);
if (addr.charAt(ipv6SeperatorEnd+1) == ':')
{
port=(short)Integer.parseInt(addr.substring(ipv6SeperatorEnd+2));
}
else
{
throw new IllegalArgumentException(errorstr);
}
}
else //IPv4 or hostname
{
try
{
port =(short)Integer.parseInt(addr.substring(sep+1));
host = addr.substring(0, sep);
}
catch( NumberFormatException ill )
{
throw new IllegalArgumentException(errorstr);
}
}
}
primaryAddress = new IIOPAddress(host,port);
try
{
primaryAddress.configure(configuration);
}
catch( ConfigurationException ce)
{
logger.warn("ConfigurationException", ce );
}
decode_extensions(protocol_identifier.toLowerCase());
}
private void decode_extensions(String ident)
{
this.components = new TaggedComponentList();
if (ident.equals("ssliop"))
{
SSL ssl = new SSL();
ssl.port = (short)primaryAddress.getPort();
String propname =
"jacorb.security.ssl.corbaloc_ssliop.supported_options";
ssl.target_supports = get_ssl_options(propname);
propname =
"jacorb.security.ssl.corbaloc_ssliop.required_options";
ssl.target_requires = get_ssl_options(propname);
//create the tagged component containing the ssl struct
final CDROutputStream out = new CDROutputStream();
try
{
out.beginEncapsulatedArray();
SSLHelper.write( out, ssl );
// TAG_SSL_SEC_TRANS must be disambiguated in case OpenORB-generated
// OMG classes are in the classpath.
components.addComponent
(new TaggedComponent( org.omg.SSLIOP.TAG_SSL_SEC_TRANS.value,
out.getBufferCopy() )
);
}
finally
{
out.close();
}
}
}
private short get_ssl_options(String propname)
{
//For the time being, we only use EstablishTrustInTarget,
//because we don't handle any of the other options anyway.
// So this makes a reasonable default.
short value =
(short)configuration.getAttributeAsInteger(propname,EstablishTrustInTarget.value);
return value;
}
/**
* Adds further addresses to this profile as TAG_ALTERNATE_IIOP_ADDRESS,
* if this has been configured in the Configuration.
*/
private void addAlternateAddresses (org.jacorb.config.Configuration config)
{
String value = config.getAttribute ("jacorb.iiop.alternate_addresses", null);
if (value == null)
{
return;
}
else if (value.trim().equals ("auto"))
{
addNetworkAddresses();
}
else
{
List addresses = Arrays.asList (value.split(","));
if (!addresses.isEmpty() && components == null)
{
components = new TaggedComponentList();
}
for (Iterator i = addresses.iterator(); i.hasNext();)
{
String addr = (String)i.next();
IIOPAddress iaddr = new IIOPAddress();
if (!iaddr.fromString (addr))
{
logger.warn ("could not decode " + addr +
" from jacorb.iiop.alternate_addresses");
continue;
}
else
{
components.addComponent (TAG_ALTERNATE_IIOP_ADDRESS.value,
iaddr.toCDR());
}
}
}
}
/**
* Adds all the network addresses of this machine to the profile
* as TAG_ALTERNATE_IIOP_ADDRESS. This excludes loopback addresses,
* and the address that is already used as the primary address.
* Also excluded are non-IPv4 addresses for the moment.
*/
private void addNetworkAddresses()
{
if (primaryAddress == null) return;
if (components == null) components = new TaggedComponentList();
try
{
for (Enumeration e = NetworkInterface.getNetworkInterfaces();
e.hasMoreElements();)
{
NetworkInterface ni = (NetworkInterface)e.nextElement();
for (Enumeration ee = ni.getInetAddresses();
ee.hasMoreElements();)
{
InetAddress addr = (InetAddress)ee.nextElement();
if (addr instanceof Inet4Address &&
!addr.isLoopbackAddress() &&
!addr.getHostAddress().equals (primaryAddress.getIP()))
{
IIOPAddress iaddr = new IIOPAddress();
iaddr.fromString (addr.toString().substring(1) + ":"
+ primaryAddress.getPort());
components.addComponent (TAG_ALTERNATE_IIOP_ADDRESS.value,
iaddr.toCDR());
}
}
}
}
catch (SocketException ex)
{
logger.warn ("could not get network interfaces, will not add addresses");
}
}
/**
* Writes the bytes that would make up the ETF::AddressProfile bytes (new spec)
* to a stream.
* <p>
* Writes GIOP version, host string, and port.
*/
public void writeAddressProfile(CDROutputStream addressProfileStream)
{
org.omg.GIOP.VersionHelper.write( addressProfileStream, version);
primaryAddress.write (addressProfileStream);
}
/**
* Reads the bytes that make up the ETF::AddressProfile bytes (new spec)
* from a stream.
* <p>
* Writes GIOP version, host string, and port.
*/
public void readAddressProfile(CDRInputStream addressProfileStream)
{
this.version = org.omg.GIOP.VersionHelper.read(addressProfileStream);
this.primaryAddress = IIOPAddress.read(addressProfileStream);
if (configuration != null)
{
try
{
primaryAddress.configure(configuration);
}
catch( ConfigurationException ce)
{
logger.warn("ConfigurationException", ce );
}
}
}
/**
* To improve the management of a large set of profile instances,
* the author may provide a hash function using the data in a Profile
* instance. The Profile shall always implement this function and either
* return a hash number, or 0 (zero) if no hashing is supported.
*/
public int hash()
{
return hashCode();
}
public Object clone() throws CloneNotSupportedException
{
IIOPProfile result = (IIOPProfile)super.clone(); // bitwise copy
result.primaryAddress = new IIOPAddress(primaryAddress.getHostname(),
primaryAddress.getPort());
if (configuration != null)
{
try
{
result.primaryAddress.configure(configuration);
}
catch( ConfigurationException ce)
{
logger.warn("ConfigurationException", ce );
}
}
result.version = new org.omg.GIOP.Version(this.version.major,
this.version.minor);
if (this.objectKey != null)
{
result.objectKey = new byte [this.objectKey.length];
System.arraycopy(this.objectKey, 0, result.objectKey, 0,
this.objectKey.length);
}
if (this.components != null)
{
result.components = (TaggedComponentList)this.components.clone();
}
return result;
}
/**
* This function shall determine if the passed profile, prof, is a match
* to this profile. The specifics of the match are left to the details
* of the underlying transport, however profiles shall be considered a
* match, if they would create connections that share the same attributes
* relevant to the transport setup. Among others, this could include
* address information (eg. host address) and transport layer
* characteristics (eg. encryption levels). If a match is found, it
* shall return true, or false otherwise.
*/
public boolean is_match(Profile prof)
{
if (prof == null)
{
return false;
}
if (prof instanceof IIOPProfile)
{
IIOPProfile other = (IIOPProfile)prof;
return
(
this.getSSLPort() == other.getSSLPort() &&
this.primaryAddress.equals (other.primaryAddress) &&
this.getAlternateAddresses().equals(other.getAlternateAddresses())
);
}
return false;
}
public int tag()
{
return TAG_INTERNET_IOP.value;
}
public ProtocolAddressBase getAddress()
{
return primaryAddress;
}
/**
* Replaces the host in this profile's primary address with newHost
* (if it is not null), and the port with newPort (if it is not -1).
*/
public void patchPrimaryAddress(ProtocolAddressBase replacement)
{
if (replacement instanceof IIOPAddress)
{
primaryAddress.replaceFrom((IIOPAddress)replacement);
}
}
public List getAlternateAddresses()
{
return components.getComponents(TAG_ALTERNATE_IIOP_ADDRESS.value,
IIOPAddress.class);
}
public SSL getSSL()
{
// TAG_SSL_SEC_TRANS must be disambiguated in case OpenORB-generated
// OMG classes are in the classpath.
return (SSL)components.getComponent( org.omg.SSLIOP.TAG_SSL_SEC_TRANS.value,
SSLHelper.class );
}
/**
* Returns the port on which SSL is available according to this profile,
* or -1 if SSL is not supported.
*/
public int getSSLPort()
{
TLS_SEC_TRANS tls = getTlsSpecFromCSIComponent();
if (tls != null && tls.addresses.length > 0)
{
return adjustedPortNum( tls.addresses[0].port );
}
else
{
SSL ssl = getSSL();
if (ssl != null)
{
return adjustedPortNum( ssl.port );
}
else
{
return -1;
}
}
}
/**
* Returns a copy of this profile that is compatible with GIOP 1.0.
*/
public IIOPProfile to_GIOP_1_0()
{
IIOPProfile result = new IIOPProfile(this.primaryAddress,
this.objectKey);
result.version.minor = 0;
return result;
}
public boolean equals(Object other)
{
return other instanceof Profile && this.is_match( (Profile) other );
}
public int hashCode()
{
return primaryAddress.hashCode();
}
public String toString()
{
return primaryAddress.toString();
}
/**
* Returns the SSL port which should be used to communicate with an IOR containing this profile.
* Returns -1 if SSL should not be used.
*
* SSL is enabled only if both client and target support it, and at least one requires it. Target options
* are determined by reading either the SSL_SEC_TRANS component or the CSI_SEC_MECH_LIST component, if it specifies
* transport-level security. If both are present, the latter is used.
*
* @param client_required the CSIv2 features required by the client.
* @param client_supported the CSIv2 features supported by the client.
* @return an ssl port number or -1, if none.
*/
int getSslPortIfSupported( int client_required, int client_supported )
{
TLS_SEC_TRANS tls = getTlsSpecFromCSIComponent();
SSL ssl = (SSL) getComponent( TAG_SSL_SEC_TRANS.value, SSLHelper.class );
if (tls != null && useSsl( client_supported, client_required, tls.target_supports, tls.target_requires ))
{
if (logger.isDebugEnabled())
{
logger.debug("Selecting TLS for connection");
}
return adjustedPortNum( tls.addresses[0].port );
}
else if (ssl != null && useSsl( client_supported, client_required, ssl.target_supports, ssl.target_requires ))
{
if (logger.isDebugEnabled())
{
logger.debug("Selecting SSL for connection");
}
return adjustedPortNum( ssl.port );
}
else if ((client_required & MINIMUM_OPTIONS) != 0)
{
throw new org.omg.CORBA.NO_PERMISSION( "Client-side policy requires SSL/TLS, but server doesn't support it" );
}
return -1;
}
private static int adjustedPortNum( short port )
{
return port < 0 ? port + 65536 : port;
}
private boolean useSsl( int clientSupports, int clientRequires, short targetSupports, short targetRequires)
{
return ((targetSupports & MINIMUM_OPTIONS) != 0) && // target supports ssl
((clientSupports & MINIMUM_OPTIONS) != 0) && // client supports ssl
( ((targetRequires & MINIMUM_OPTIONS) != 0) || ((clientRequires & MINIMUM_OPTIONS) != 0) ); // either side requires ssl
}
private TLS_SEC_TRANS getTlsSpecFromCSIComponent()
{
CompoundSecMechList sas = null;
try
{
sas = (CompoundSecMechList) getComponent( TAG_CSI_SEC_MECH_LIST.value, CompoundSecMechListHelper.class );
}
catch (Exception ex)
{
logger.info("Not able to process security mech. component");
}
TLS_SEC_TRANS tls = null;
if (sas != null && sas.mechanism_list[0].transport_mech.tag == TAG_TLS_SEC_TRANS.value)
{
try
{
byte[] tagData = sas.mechanism_list[0].transport_mech.component_data;
final CDRInputStream in = new CDRInputStream( null, tagData );
try
{
in.openEncapsulatedArray();
tls = TLS_SEC_TRANSHelper.read( in );
}
finally
{
in.close();
}
}
catch ( Exception e )
{
logger.warn("Error parsing TLS_SEC_TRANS: "+e);
}
}
return tls;
}
}