| /* |
| * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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<String> 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<SoftReference<ThreadContext>> contextRef = |
| new ThreadLocal<>(); |
| |
| // Simple class to encapsulate the temporary context |
| private static class ThreadContext { |
| private DirContext dirCtxt; |
| private List<String> nsList; |
| |
| public ThreadContext(DirContext dirCtxt, List<String> nsList) { |
| this.dirCtxt = dirCtxt; |
| this.nsList = nsList; |
| } |
| |
| public DirContext dirContext() { |
| return dirCtxt; |
| } |
| |
| public List<String> nameservers() { |
| return nsList; |
| } |
| } |
| |
| // Returns a per-thread DirContext |
| private DirContext getTemporaryContext() throws NamingException { |
| SoftReference<ThreadContext> ref = contextRef.get(); |
| ThreadContext thrCtxt = null; |
| List<String> 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 = 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<>(); |
| 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 = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedExceptionAction<DirContext>() { |
| public DirContext 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 (DirContext)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<String> resolve(final DirContext ctx, final String name, |
| final String[] ids, int depth) |
| throws UnknownHostException |
| { |
| ArrayList<String> results = new ArrayList<>(); |
| Attributes attrs; |
| |
| // do the query |
| try { |
| attrs = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedExceptionAction<Attributes>() { |
| public Attributes 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<? extends Attribute> 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 = 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<String> nsList = ResolverConfiguration.open().nameservers(); |
| if (nsList.isEmpty()) { |
| throw new RuntimeException("no nameservers provided"); |
| } |
| boolean found = false; |
| for (String addr: nsList) { |
| 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<String> searchList = null; |
| Iterator<String> 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 = 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<String> 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 = 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 = 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 = 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<String> nsList) { |
| StringBuffer sb = new StringBuffer(); |
| for (String s: nsList) { |
| appendIfLiteralAddress(s, 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(); |
| } |
| } |