package org.bouncycastle.cert.dane.fetcher;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import org.bouncycastle.cert.dane.DANEEntry;
import org.bouncycastle.cert.dane.DANEEntryFetcher;
import org.bouncycastle.cert.dane.DANEEntryFetcherFactory;
import org.bouncycastle.cert.dane.DANEException;

/**
 * A DANE entry fetcher implemented using JNDI.
 */
public class JndiDANEFetcherFactory
    implements DANEEntryFetcherFactory
{
    private static final String DANE_TYPE = "65500";

    private List dnsServerList = new ArrayList();
    private boolean isAuthoritative;

    /**
     * Specify the dnsServer to use.
     *
     * @param dnsServer IP address/name of the dns server
     * @return the current factory.
     */
    public JndiDANEFetcherFactory usingDNSServer(String dnsServer)
    {
        this.dnsServerList.add(dnsServer);

        return this;
    }

    /**
     * Specify requests must be authoritative.
     *
     * @param isAuthoritative true if requests must be authoritative, false otherwise.
     * @return the current factory..
     */
    public JndiDANEFetcherFactory setAuthoritative(boolean isAuthoritative)
    {
        this.isAuthoritative = isAuthoritative;

        return this;
    }

    /**
     * Build an entry fetcher for the specified domain name.
     *
     * @param domainName the domain name of interest.
     * @return a resolver for fetching entry's associated with domainName.
     */
    public DANEEntryFetcher build(final String domainName)
    {
        final Hashtable env = new Hashtable();

        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
        env.put(Context.AUTHORITATIVE, isAuthoritative ? "true" : "false"); // JDK compatibility

        if (dnsServerList.size() > 0)
        {
            StringBuffer dnsServers = new StringBuffer();

            for (Iterator it = dnsServerList.iterator(); it.hasNext(); )
            {
                if (dnsServers.length() > 0)
                {
                    dnsServers.append(" ");
                }
                dnsServers.append("dns://" + it.next());
            }

            env.put(Context.PROVIDER_URL, dnsServers.toString());
        }

        return new DANEEntryFetcher()
        {
            public List getEntries()
                throws DANEException
            {
                List entries = new ArrayList();

                try
                {
                    DirContext ctx = new InitialDirContext(env);

                    NamingEnumeration bindings;
                    if (domainName.indexOf("_smimecert.") > 0)
                    {
                        bindings = ctx.listBindings(domainName);

                        // need to use fully qualified domain name if using named DNS server.
                        Attributes attrs = ctx.getAttributes(domainName, new String[]{DANE_TYPE});
                        Attribute smimeAttr = attrs.get(DANE_TYPE);

                        if (smimeAttr != null)
                        {
                            byte[] data = (byte[])attrs.get(DANE_TYPE).get();

                            if (DANEEntry.isValidCertificate(data))
                            {
                                try
                                {
                                    entries.add(new DANEEntry(domainName, data));
                                }
                                catch (IOException e)
                                {
                                    throw new DANEException("Exception parsing entry: " + e.getMessage(), e);
                                }
                            }
                        }
                    }
                    else
                    {
                        bindings = ctx.listBindings("_smimecert." + domainName);

                        while (bindings.hasMore())
                        {
                            Binding b = (Binding)bindings.next();

                            DirContext sc = (DirContext)b.getObject();

                            String name = sc.getNameInNamespace().substring(1, sc.getNameInNamespace().length() - 1);

                            // need to use fully qualified domain name if using named DNS server.
                            Attributes attrs = ctx.getAttributes(name, new String[]{DANE_TYPE});
                            Attribute smimeAttr = attrs.get(DANE_TYPE);

                            if (smimeAttr != null)
                            {
                                byte[] data = (byte[])attrs.get(DANE_TYPE).get();

                                if (DANEEntry.isValidCertificate(data))
                                {
                                    try
                                    {
                                        String fullName = sc.getNameInNamespace();

                                        entries.add(new DANEEntry(fullName.substring(1, fullName.length() - 1), data));
                                    }
                                    catch (IOException e)
                                    {
                                        throw new DANEException("Exception parsing entry: " + e.getMessage(), e);
                                    }
                                }
                            }
                        }
                    }
                    ;
                    return entries;
                }
                catch (NamingException e)
                {
                    throw new DANEException("Exception dealing with DNS: " + e.getMessage(), e);
                }
            }
        };
    }
}
