jdk/src/share/classes/sun/net/spi/nameservice/dns/DNSNameService.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/net/spi/nameservice/dns/DNSNameService.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,503 @@
+/*
+ * Copyright 2000-2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.net.spi.nameservice.dns;
+
+import java.lang.ref.SoftReference;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import javax.naming.*;
+import javax.naming.directory.*;
+import javax.naming.spi.NamingManager;
+import java.util.*;
+import sun.net.util.IPAddressUtil;
+import sun.net.dns.ResolverConfiguration;
+import sun.net.spi.nameservice.*;
+import java.security.AccessController;
+import sun.security.action.*;
+
+/*
+ * A name service provider based on JNDI-DNS.
+ */
+
+public final class DNSNameService implements NameService {
+
+    // List of domains specified by property
+    private LinkedList domainList = null;
+
+    // JNDI-DNS URL for name servers specified via property
+    private String nameProviderUrl = null;
+
+    // Per-thread soft cache of the last temporary context
+    private static ThreadLocal contextRef = new ThreadLocal();
+
+    // Simple class to encapsulate the temporary context
+    private static class ThreadContext {
+        private DirContext dirCtxt;
+        private List nsList;
+
+        public ThreadContext(DirContext dirCtxt, List nsList) {
+            this.dirCtxt = dirCtxt;
+            this.nsList = nsList;
+        }
+
+        public DirContext dirContext() {
+            return dirCtxt;
+        }
+
+        public List nameservers() {
+            return nsList;
+        }
+    }
+
+    // Returns a per-thread DirContext
+    private DirContext getTemporaryContext() throws NamingException {
+        SoftReference ref = (SoftReference)contextRef.get();
+        ThreadContext thrCtxt = null;
+        List nsList = null;
+
+        // if no property specified we need to obtain the list of servers
+        //
+        if (nameProviderUrl == null)
+            nsList = ResolverConfiguration.open().nameservers();
+
+        // if soft reference hasn't been gc'ed no property has been
+        // specified then we need to check if the DNS configuration
+        // has changed.
+        //
+        if ((ref != null) && ((thrCtxt = (ThreadContext)ref.get()) != null)) {
+            if (nameProviderUrl == null) {
+                if (!thrCtxt.nameservers().equals(nsList)) {
+                    // DNS configuration has changed
+                    thrCtxt = null;
+                }
+            }
+        }
+
+        // new thread context needs to be created
+        if (thrCtxt == null) {
+            final Hashtable<String,Object> env = new Hashtable<String,Object>();
+            env.put("java.naming.factory.initial",
+                    "com.sun.jndi.dns.DnsContextFactory");
+
+            // If no nameservers property specified we create provider URL
+            // based on system configured name servers
+            //
+            String provUrl = nameProviderUrl;
+            if (provUrl == null) {
+                provUrl = createProviderURL(nsList);
+                if (provUrl.length() == 0) {
+                    throw new RuntimeException("bad nameserver configuration");
+                }
+            }
+            env.put("java.naming.provider.url", provUrl);
+
+            // Need to create directory context in privileged block
+            // as JNDI-DNS needs to resolve the name servers.
+            //
+            DirContext dirCtxt;
+            try {
+                dirCtxt = (DirContext)
+                    java.security.AccessController.doPrivileged(
+                        new java.security.PrivilegedExceptionAction() {
+                            public Object run() throws NamingException {
+                                // Create the DNS context using NamingManager rather than using
+                                // the initial context constructor. This avoids having the initial
+                                // context constructor call itself.
+                                Context ctx = NamingManager.getInitialContext(env);
+                                if (!(ctx instanceof DirContext)) {
+                                    return null; // cannot create a DNS context
+                                }
+                                return ctx;
+                            }
+                    });
+            } catch (java.security.PrivilegedActionException pae) {
+                throw (NamingException)pae.getException();
+            }
+
+            // create new soft reference to our thread context
+            //
+            thrCtxt = new ThreadContext(dirCtxt, nsList);
+            contextRef.set(new SoftReference(thrCtxt));
+        }
+
+        return thrCtxt.dirContext();
+    }
+
+    /**
+     * Resolves the specified entry in DNS.
+     *
+     * Canonical name records are recursively resolved (to a maximum
+     * of 5 to avoid performance hit and potential CNAME loops).
+     *
+     * @param   ctx     JNDI directory context
+     * @param   name    name to resolve
+     * @param   ids     record types to search
+     * @param   depth   call depth - pass as 0.
+     *
+     * @return  array list with results (will have at least on entry)
+     *
+     * @throws  UnknownHostException if lookup fails or other error.
+     */
+    private ArrayList resolve(final DirContext ctx, final String name, final String[] ids,
+                              int depth) throws UnknownHostException
+    {
+        ArrayList results = new ArrayList();
+        Attributes attrs;
+
+        // do the query
+        try {
+            attrs = (Attributes)
+                java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedExceptionAction() {
+                        public Object run() throws NamingException {
+                            return ctx.getAttributes(name, ids);
+                        }
+                });
+        } catch (java.security.PrivilegedActionException pae) {
+            throw new UnknownHostException(pae.getException().getMessage());
+        }
+
+        // non-requested type returned so enumeration is empty
+        NamingEnumeration ne = attrs.getAll();
+        if (!ne.hasMoreElements()) {
+            throw new UnknownHostException("DNS record not found");
+        }
+
+        // iterate through the returned attributes
+        UnknownHostException uhe = null;
+        try {
+            while (ne.hasMoreElements()) {
+                Attribute attr = (Attribute)ne.next();
+                String attrID = attr.getID();
+
+                for (NamingEnumeration e = attr.getAll(); e.hasMoreElements();) {
+                    String addr = (String)e.next();
+
+                    // for canoncical name records do recursive lookup
+                    // - also check for CNAME loops to avoid stack overflow
+
+                    if (attrID.equals("CNAME")) {
+                        if (depth > 4) {
+                            throw new UnknownHostException(name + ": possible CNAME loop");
+                        }
+                        try {
+                            results.addAll(resolve(ctx, addr, ids, depth+1));
+                        } catch (UnknownHostException x) {
+                            // canonical name can't be resolved.
+                            if (uhe == null)
+                                uhe = x;
+                        }
+                    } else {
+                        results.add(addr);
+                    }
+                }
+            }
+        } catch (NamingException nx) {
+            throw new UnknownHostException(nx.getMessage());
+        }
+
+        // pending exception as canonical name could not be resolved.
+        if (results.isEmpty() && uhe != null) {
+            throw uhe;
+        }
+
+        return results;
+    }
+
+    public DNSNameService() throws Exception {
+
+        // default domain
+        String domain = AccessController.doPrivileged(
+            new GetPropertyAction("sun.net.spi.nameservice.domain"));
+        if (domain != null && domain.length() > 0) {
+            domainList = new LinkedList();
+            domainList.add(domain);
+        }
+
+        // name servers
+        String nameservers = AccessController.doPrivileged(
+            new GetPropertyAction("sun.net.spi.nameservice.nameservers"));
+        if (nameservers != null && nameservers.length() > 0) {
+            nameProviderUrl = createProviderURL(nameservers);
+            if (nameProviderUrl.length() == 0) {
+                throw new RuntimeException("malformed nameservers property");
+            }
+
+        } else {
+
+            // no property specified so check host DNS resolver configured
+            // with at least one nameserver in dotted notation.
+            //
+            List nsList = ResolverConfiguration.open().nameservers();
+            if (nsList.size() == 0)
+                throw new RuntimeException("no nameservers provided");
+            boolean found = false;
+            Iterator i = nsList.iterator();
+            while (i.hasNext()) {
+                String addr = (String)i.next();
+                if (IPAddressUtil.isIPv4LiteralAddress(addr) ||
+                    IPAddressUtil.isIPv6LiteralAddress(addr)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new RuntimeException("bad nameserver configuration");
+            }
+        }
+    }
+
+    public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException {
+
+        // DNS records that we search for
+        String[] ids = {"A", "AAAA", "CNAME"};
+
+        // first get directory context
+        DirContext ctx;
+        try {
+            ctx = getTemporaryContext();
+        } catch (NamingException nx) {
+            throw new Error(nx);
+        }
+
+        ArrayList results = null;
+        UnknownHostException uhe = null;
+
+        // If host already contains a domain name then just look it up
+        if (host.indexOf('.') >= 0) {
+            try {
+                results = resolve(ctx, host, ids, 0);
+            } catch (UnknownHostException x) {
+                uhe = x;
+            }
+        }
+
+        // Here we try to resolve the host using the domain suffix or
+        // the domain suffix search list. If the host cannot be resolved
+        // using the domain suffix then we attempt devolution of
+        // the suffix - eg: if we are searching for "foo" and our
+        // domain suffix is "eng.sun.com" we will try to resolve
+        // "foo.eng.sun.com" and "foo.sun.com".
+        // It's not normal to attempt devolation with domains on the
+        // domain suffix search list - however as ResolverConfiguration
+        // doesn't distinguish domain or search list in the list it
+        // returns we approximate by doing devolution on the domain
+        // suffix if the list has one entry.
+
+        if (results == null) {
+            List searchList = null;
+            Iterator i;
+            boolean usingSearchList = false;
+
+            if (domainList != null) {
+                i = domainList.iterator();
+            } else {
+                searchList = ResolverConfiguration.open().searchlist();
+                if (searchList.size() > 1) {
+                    usingSearchList = true;
+                }
+                i = searchList.iterator();
+            }
+
+            // iterator through each domain suffix
+            while (i.hasNext()) {
+                String parentDomain = (String)i.next();
+                int start = 0;
+                while ((start = parentDomain.indexOf(".")) != -1
+                       && start < parentDomain.length() -1) {
+                    try {
+                        results = resolve(ctx, host+"."+parentDomain, ids, 0);
+                        break;
+                    } catch (UnknownHostException x) {
+                        uhe = x;
+                        if (usingSearchList) {
+                            break;
+                        }
+
+                        // devolve
+                        parentDomain = parentDomain.substring(start+1);
+                    }
+                }
+                if (results != null) {
+                    break;
+                }
+            }
+        }
+
+        // finally try the host if it doesn't have a domain name
+        if (results == null && (host.indexOf('.') < 0)) {
+            results = resolve(ctx, host, ids, 0);
+        }
+
+        // if not found then throw the (last) exception thrown.
+        if (results == null) {
+            assert uhe != null;
+            throw uhe;
+        }
+
+        /**
+         * Convert the array list into a byte aray list - this
+         * filters out any invalid IPv4/IPv6 addresses.
+         */
+        assert results.size() > 0;
+        InetAddress[] addrs = new InetAddress[results.size()];
+        int count = 0;
+        for (int i=0; i<results.size(); i++) {
+            String addrString = (String)results.get(i);
+            byte addr[] = IPAddressUtil.textToNumericFormatV4(addrString);
+            if (addr == null) {
+                addr = IPAddressUtil.textToNumericFormatV6(addrString);
+            }
+            if (addr != null) {
+                addrs[count++] = InetAddress.getByAddress(host, addr);
+            }
+        }
+
+        /**
+         * If addresses are filtered then we need to resize the
+         * array. Additionally if all addresses are filtered then
+         * we throw an exception.
+         */
+        if (count == 0) {
+            throw new UnknownHostException(host + ": no valid DNS records");
+        }
+        if (count < results.size()) {
+            InetAddress[] tmp = new InetAddress[count];
+            for (int i=0; i<count; i++) {
+                tmp[i] = addrs[i];
+            }
+            addrs = tmp;
+        }
+
+        return addrs;
+    }
+
+    /**
+     * Reverse lookup code. I.E: find a host name from an IP address.
+     * IPv4 addresses are mapped in the IN-ADDR.ARPA. top domain, while
+     * IPv6 addresses can be in IP6.ARPA or IP6.INT.
+     * In both cases the address has to be converted into a dotted form.
+     */
+    public String getHostByAddr(byte[] addr) throws UnknownHostException {
+        String host = null;
+        try {
+            String literalip = "";
+            String[] ids = { "PTR" };
+            DirContext ctx;
+            ArrayList results = null;
+            try {
+                ctx = getTemporaryContext();
+            } catch (NamingException nx) {
+                throw new Error(nx);
+            }
+            if (addr.length == 4) { // IPv4 Address
+                for (int i = addr.length-1; i >= 0; i--) {
+                    literalip += (addr[i] & 0xff) +".";
+                }
+                literalip += "IN-ADDR.ARPA.";
+
+                results = resolve(ctx, literalip, ids, 0);
+                host = (String)results.get(0);
+            } else if (addr.length == 16) { // IPv6 Address
+                /**
+                 * Because RFC 3152 changed the root domain name for reverse
+                 * lookups from IP6.INT. to IP6.ARPA., we need to check
+                 * both. I.E. first the new one, IP6.ARPA, then if it fails
+                 * the older one, IP6.INT
+                 */
+
+                for (int i = addr.length-1; i >= 0; i--) {
+                    literalip += Integer.toHexString((addr[i] & 0x0f)) +"."
+                        +Integer.toHexString((addr[i] & 0xf0) >> 4) +".";
+                }
+                String ip6lit = literalip + "IP6.ARPA.";
+
+                try {
+                    results = resolve(ctx, ip6lit, ids, 0);
+                    host = (String)results.get(0);
+                } catch (UnknownHostException e) {
+                    host = null;
+                }
+                if (host == null) {
+                    // IP6.ARPA lookup failed, let's try the older IP6.INT
+                    ip6lit = literalip + "IP6.INT.";
+                    results = resolve(ctx, ip6lit, ids, 0);
+                    host = (String)results.get(0);
+                }
+            }
+        } catch (Exception e) {
+            throw new UnknownHostException(e.getMessage());
+        }
+        // Either we couldn't find it or the address was neither IPv4 or IPv6
+        if (host == null)
+            throw new UnknownHostException();
+        // remove trailing dot
+        if (host.endsWith(".")) {
+            host = host.substring(0, host.length() - 1);
+        }
+        return host;
+    }
+
+
+    // ---------
+
+    private static void appendIfLiteralAddress(String addr, StringBuffer sb) {
+        if (IPAddressUtil.isIPv4LiteralAddress(addr)) {
+            sb.append("dns://" + addr + " ");
+        } else {
+            if (IPAddressUtil.isIPv6LiteralAddress(addr)) {
+                sb.append("dns://[" + addr + "] ");
+            }
+        }
+    }
+
+    /*
+     * @return String containing the JNDI-DNS provider URL
+     *         corresponding to the supplied List of nameservers.
+     */
+    private static String createProviderURL(List nsList) {
+        Iterator i = nsList.iterator();
+        StringBuffer sb = new StringBuffer();
+        while (i.hasNext()) {
+            appendIfLiteralAddress((String)i.next(), sb);
+        }
+        return sb.toString();
+    }
+
+    /*
+     * @return String containing the JNDI-DNS provider URL
+     *         corresponding to the list of nameservers
+     *         contained in the provided str.
+     */
+    private static String createProviderURL(String str) {
+        StringBuffer sb = new StringBuffer();
+        StringTokenizer st = new StringTokenizer(str, ",");
+        while (st.hasMoreTokens()) {
+            appendIfLiteralAddress(st.nextToken(), sb);
+        }
+        return sb.toString();
+    }
+}