// **********************************************************************
//
// Copyright (c) 2003-2006 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

package IceSSL;

class TrustManager
{
    TrustManager(Ice.Communicator communicator)
    {
	assert communicator != null;
	_communicator = communicator;
	Ice.Properties properties = communicator.getProperties();
	_traceLevel = properties.getPropertyAsInt("IceSSL.Trace.Security");
	String key = null;
	try
	{
	    key = "IceSSL.TrustOnly";
	    _all = parse(properties.getProperty(key));
	    key = "IceSSL.TrustOnly.Client";
	    _client = parse(properties.getProperty(key));
	    key = "IceSSL.TrustOnly.Server";
	    _allServer = parse(properties.getProperty(key));
	    java.util.Map dict = properties.getPropertiesForPrefix("IceSSL.TrustOnly.Server.");
	    java.util.Iterator p = dict.entrySet().iterator();
	    while(p.hasNext())
	    {
		java.util.Map.Entry entry = (java.util.Map.Entry)p.next();
		key = (String)entry.getKey();
		String name = key.substring("IceSSL.TrustOnly.Server.".length());
		_server.put(name, parse((String)entry.getValue()));
	    }
	}
	catch(RFC2253.ParseException e)
	{
	    Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
	    ex.reason = "IceSSL: invalid property " + key  + ":\n" + e.reason;
	    throw ex;
	}
    }

    boolean
    verify(ConnectionInfo info)
    {
	java.util.List trustset = new java.util.LinkedList();
	if(!_all.isEmpty())
	{
	    trustset.add(_all);
	}

	if(info.incoming)
	{
	    if(!_allServer.isEmpty())
	    {
		trustset.add(_allServer);
	    }
	    if(info.adapterName.length() > 0)
	    {
		java.util.List p = (java.util.List)_server.get(info.adapterName);
		if(p != null)
		{
		    trustset.add(p);
		}
	    }
	}
	else
	{
	    if(!_client.isEmpty())
	    {
		trustset.add(_client);
	    }
	}

	//
	// If there is nothing to match against, then we accept the cert.
	//
	if(trustset.isEmpty())
	{
	    return true;
	}

	//
	// If there is no certificate then we match false.
	//
	if(info.certs.length != 0)
	{
	    javax.security.auth.x500.X500Principal subjectDN = (javax.security.auth.x500.X500Principal)
		((java.security.cert.X509Certificate)info.certs[0]).getSubjectX500Principal();
	    String subjectName = subjectDN.getName(javax.security.auth.x500.X500Principal.RFC2253);
	    assert subjectName != null;
	    try
	    {
		//
		// Decompose the subject DN into the RDNs.
		//
		if(_traceLevel > 0)
		{
		    _communicator.getLogger().trace("Security", "trust manager evaluating peer DN:\n" + subjectName);
		}
		java.util.List dn = RFC2253.parseStrict(subjectName);

		//
		// Try matching against everything in the trust set.
		//
		java.util.Iterator p = trustset.iterator();
		while(p.hasNext())
		{
		    if(match((java.util.List)p.next(), dn))
		    {
			return true;
		    }
		}
	    }
	    catch(RFC2253.ParseException e)
	    {
		_communicator.getLogger().warning(
		    "IceSSL: unable to parse certificate DN `" + subjectName + "'\nreason: " + e.reason);
	    }
	}

	return false;
    }

    private boolean
    match(java.util.List matchSet, java.util.List subject)
    {
	java.util.Iterator r = matchSet.iterator();
	while(r.hasNext())
	{
	    if(matchRDNs((java.util.List)r.next(), subject))
	    {
		return true;
	    }
	}
	return false;
    }

    private boolean
    matchRDNs(java.util.List match, java.util.List subject)
    {
	java.util.Iterator p = match.iterator();
	while(p.hasNext())
	{
	    RFC2253.RDNPair matchRDN = (RFC2253.RDNPair)p.next();
	    boolean found = false;
	    java.util.Iterator q = subject.iterator();
	    while(q.hasNext())
	    {
		RFC2253.RDNPair subjectRDN = (RFC2253.RDNPair)q.next();
		if(matchRDN.key.equals(subjectRDN.key))
		{
		    found = true;
		    if(!matchRDN.value.equals(subjectRDN.value))
		    {
			return false;
		    }
		}
	    }
	    if(!found)
	    {
		return false;
	    }
	}
	return true;
    }

    java.util.List
    parse(String value)
	throws RFC2253.ParseException
    {
	//
	// Java X500Principal.getName says:
	//
	// If "RFC2253" is specified as the format, this method emits
	// the attribute type keywords defined in RFC 2253 (CN, L, ST,
	// O, OU, C, STREET, DC, UID). Any other attribute type is
	// emitted as an OID. Under a strict reading, RFC 2253 only
	// specifies a UTF-8 string representation. The String
	// returned by this method is the Unicode string achieved by
	// decoding this UTF-8 representation.
	//
	// This means that things like emailAddress and such will be turned into
	// something like:
	//
	// 1.2.840.113549.1.9.1=#160e696e666f407a65726f632e636f6d
	//
	// The left hand side is the OID (see
	// http://www.columbia.edu/~ariel/ssleay/asn1-oids.html) for a
	// list. The right hand side is a BER encoding of the value.
	//
	// This means that the user input, unless it uses the
	// unfriendly OID format, will not directly match the
	// principal.
	// 
	// Two possible solutions:
	//
	// Have the RFC2253 parser convert anything that is not CN, L,
	// ST, O, OU, C, STREET, DC, UID into OID format, and have it
	// convert the values into a BER encoding.
	//
        // Send the user data through X500Principal to string form and
        // then through the RFC2253 encoder. This uses the
        // X500Principal to do the encoding for us.
	//
	// The latter is much simpler, however, it means we need to
	// send the data through the parser twice because we split the
	// DNs on ';' which cannot be blindly split because of quotes,
	// \ and such.
	//
	java.util.List l = RFC2253.parse(value);
	java.util.List result = new java.util.LinkedList();
	java.util.Iterator p = l.iterator();
	while(p.hasNext())
	{
	    java.util.List dn = (java.util.List)p.next();
	    String v = new String();
	    boolean first = true;
	    java.util.Iterator q = dn.iterator();
	    while(q.hasNext())
	    {
		if(!first)
		{
		    v += ",";
		}
		first = false;
		RFC2253.RDNPair pair = (RFC2253.RDNPair)q.next();
		v += pair.key;
		v += "=";
		v += pair.value;
	    }
	    javax.security.auth.x500.X500Principal princ = new javax.security.auth.x500.X500Principal(v);
	    String subjectName = princ.getName(javax.security.auth.x500.X500Principal.RFC2253);
	    result.add(RFC2253.parseStrict(subjectName));
	}
	return result;
    }

    private Ice.Communicator _communicator;
    private int _traceLevel;

    private java.util.List _all;
    private java.util.List _client;
    private java.util.List _allServer;
    private java.util.Map _server = new java.util.HashMap();
}
