1 /* 2 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.net.spi.nameservice.dns; 27 28 import java.lang.ref.SoftReference; 29 import java.net.InetAddress; 30 import java.net.UnknownHostException; 31 import javax.naming.*; 32 import javax.naming.directory.*; 33 import javax.naming.spi.NamingManager; 34 import java.util.*; 35 import sun.net.util.IPAddressUtil; 36 import sun.net.dns.ResolverConfiguration; 37 import sun.net.spi.nameservice.*; 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 41 /* 42 * A name service provider based on JNDI-DNS. 43 */ 44 45 public final class DNSNameService implements NameService { 46 47 // List of domains specified by property 48 private LinkedList<String> domainList = null; 49 50 // JNDI-DNS URL for name servers specified via property 51 private String nameProviderUrl = null; 52 53 // Per-thread soft cache of the last temporary context 54 private static ThreadLocal<SoftReference<ThreadContext>> contextRef = 55 new ThreadLocal<>(); 56 57 // Simple class to encapsulate the temporary context 58 private static class ThreadContext { 59 private DirContext dirCtxt; 60 private List<String> nsList; 61 62 public ThreadContext(DirContext dirCtxt, List<String> nsList) { 63 this.dirCtxt = dirCtxt; 64 this.nsList = nsList; 65 } 66 67 public DirContext dirContext() { 68 return dirCtxt; 69 } 70 71 public List<String> nameservers() { 72 return nsList; 73 } 74 } 75 76 // Returns a per-thread DirContext 77 private DirContext getTemporaryContext() throws NamingException { 78 SoftReference<ThreadContext> ref = contextRef.get(); 79 ThreadContext thrCtxt = null; 80 List<String> nsList = null; 81 82 // if no property specified we need to obtain the list of servers 83 // 84 if (nameProviderUrl == null) 85 nsList = ResolverConfiguration.open().nameservers(); 86 87 // if soft reference hasn't been gc'ed no property has been 88 // specified then we need to check if the DNS configuration 89 // has changed. 90 // 91 if ((ref != null) && ((thrCtxt = ref.get()) != null)) { 92 if (nameProviderUrl == null) { 93 if (!thrCtxt.nameservers().equals(nsList)) { 94 // DNS configuration has changed 95 thrCtxt = null; 96 } 97 } 98 } 99 100 // new thread context needs to be created 101 if (thrCtxt == null) { 102 final Hashtable<String,Object> env = new Hashtable<>(); 103 env.put("java.naming.factory.initial", 104 "com.sun.jndi.dns.DnsContextFactory"); 105 106 // If no nameservers property specified we create provider URL 107 // based on system configured name servers 108 // 109 String provUrl = nameProviderUrl; 110 if (provUrl == null) { 111 provUrl = createProviderURL(nsList); 112 if (provUrl.length() == 0) { 113 throw new RuntimeException("bad nameserver configuration"); 114 } 115 } 116 env.put("java.naming.provider.url", provUrl); 117 118 // Need to create directory context in privileged block 119 // as JNDI-DNS needs to resolve the name servers. 120 // 121 DirContext dirCtxt; 122 try { 123 dirCtxt = java.security.AccessController.doPrivileged( 124 new java.security.PrivilegedExceptionAction<DirContext>() { 125 public DirContext run() throws NamingException { 126 // Create the DNS context using NamingManager rather than using 127 // the initial context constructor. This avoids having the initial 128 // context constructor call itself. 129 Context ctx = NamingManager.getInitialContext(env); 130 if (!(ctx instanceof DirContext)) { 131 return null; // cannot create a DNS context 132 } 133 return (DirContext)ctx; 134 } 135 }); 136 } catch (java.security.PrivilegedActionException pae) { 137 throw (NamingException)pae.getException(); 138 } 139 140 // create new soft reference to our thread context 141 // 142 thrCtxt = new ThreadContext(dirCtxt, nsList); 143 contextRef.set(new SoftReference<ThreadContext>(thrCtxt)); 144 } 145 146 return thrCtxt.dirContext(); 147 } 148 149 /** 150 * Resolves the specified entry in DNS. 151 * 152 * Canonical name records are recursively resolved (to a maximum 153 * of 5 to avoid performance hit and potential CNAME loops). 154 * 155 * @param ctx JNDI directory context 156 * @param name name to resolve 157 * @param ids record types to search 158 * @param depth call depth - pass as 0. 159 * 160 * @return array list with results (will have at least on entry) 161 * 162 * @throws UnknownHostException if lookup fails or other error. 163 */ 164 private ArrayList<String> resolve(final DirContext ctx, final String name, 165 final String[] ids, int depth) 166 throws UnknownHostException 167 { 168 ArrayList<String> results = new ArrayList<>(); 169 Attributes attrs; 170 171 // do the query 172 try { 173 attrs = java.security.AccessController.doPrivileged( 174 new java.security.PrivilegedExceptionAction<Attributes>() { 175 public Attributes run() throws NamingException { 176 return ctx.getAttributes(name, ids); 177 } 178 }); 179 } catch (java.security.PrivilegedActionException pae) { 180 throw new UnknownHostException(pae.getException().getMessage()); 181 } 182 183 // non-requested type returned so enumeration is empty 184 NamingEnumeration<? extends Attribute> ne = attrs.getAll(); 185 if (!ne.hasMoreElements()) { 186 throw new UnknownHostException("DNS record not found"); 187 } 188 189 // iterate through the returned attributes 190 UnknownHostException uhe = null; 191 try { 192 while (ne.hasMoreElements()) { 193 Attribute attr = ne.next(); 194 String attrID = attr.getID(); 195 196 for (NamingEnumeration<?> e = attr.getAll(); e.hasMoreElements();) { 197 String addr = (String)e.next(); 198 199 // for canoncical name records do recursive lookup 200 // - also check for CNAME loops to avoid stack overflow 201 202 if (attrID.equals("CNAME")) { 203 if (depth > 4) { 204 throw new UnknownHostException(name + ": possible CNAME loop"); 205 } 206 try { 207 results.addAll(resolve(ctx, addr, ids, depth+1)); 208 } catch (UnknownHostException x) { 209 // canonical name can't be resolved. 210 if (uhe == null) 211 uhe = x; 212 } 213 } else { 214 results.add(addr); 215 } 216 } 217 } 218 } catch (NamingException nx) { 219 throw new UnknownHostException(nx.getMessage()); 220 } 221 222 // pending exception as canonical name could not be resolved. 223 if (results.isEmpty() && uhe != null) { 224 throw uhe; 225 } 226 227 return results; 228 } 229 230 public DNSNameService() throws Exception { 231 232 // default domain 233 String domain = AccessController.doPrivileged( 234 (PrivilegedAction<String>) () -> System.getProperty("sun.net.spi.nameservice.domain")); 235 if (domain != null && domain.length() > 0) { 236 domainList = new LinkedList<String>(); 237 domainList.add(domain); 238 } 239 240 // name servers 241 String nameservers = AccessController.doPrivileged( 242 (PrivilegedAction<String>) () -> System.getProperty("sun.net.spi.nameservice.nameservers")); 243 if (nameservers != null && nameservers.length() > 0) { 244 nameProviderUrl = createProviderURL(nameservers); 245 if (nameProviderUrl.length() == 0) { 246 throw new RuntimeException("malformed nameservers property"); 247 } 248 249 } else { 250 251 // no property specified so check host DNS resolver configured 252 // with at least one nameserver in dotted notation. 253 // 254 List<String> nsList = ResolverConfiguration.open().nameservers(); 255 if (nsList.isEmpty()) { 256 throw new RuntimeException("no nameservers provided"); 257 } 258 boolean found = false; 259 for (String addr: nsList) { 260 if (IPAddressUtil.isIPv4LiteralAddress(addr) || 261 IPAddressUtil.isIPv6LiteralAddress(addr)) { 262 found = true; 263 break; 264 } 265 } 266 if (!found) { 267 throw new RuntimeException("bad nameserver configuration"); 268 } 269 } 270 } 271 272 public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException { 273 274 // DNS records that we search for 275 String[] ids = {"A", "AAAA", "CNAME"}; 276 277 // first get directory context 278 DirContext ctx; 279 try { 280 ctx = getTemporaryContext(); 281 } catch (NamingException nx) { 282 throw new Error(nx); 283 } 284 285 ArrayList<String> results = null; 286 UnknownHostException uhe = null; 287 288 // If host already contains a domain name then just look it up 289 if (host.indexOf('.') >= 0) { 290 try { 291 results = resolve(ctx, host, ids, 0); 292 } catch (UnknownHostException x) { 293 uhe = x; 294 } 295 } 296 297 // Here we try to resolve the host using the domain suffix or 298 // the domain suffix search list. If the host cannot be resolved 299 // using the domain suffix then we attempt devolution of 300 // the suffix - eg: if we are searching for "foo" and our 301 // domain suffix is "eng.sun.com" we will try to resolve 302 // "foo.eng.sun.com" and "foo.sun.com". 303 // It's not normal to attempt devolation with domains on the 304 // domain suffix search list - however as ResolverConfiguration 305 // doesn't distinguish domain or search list in the list it 306 // returns we approximate by doing devolution on the domain 307 // suffix if the list has one entry. 308 309 if (results == null) { 310 List<String> searchList = null; 311 Iterator<String> i; 312 boolean usingSearchList = false; 313 314 if (domainList != null) { 315 i = domainList.iterator(); 316 } else { 317 searchList = ResolverConfiguration.open().searchlist(); 318 if (searchList.size() > 1) { 319 usingSearchList = true; 320 } 321 i = searchList.iterator(); 322 } 323 324 // iterator through each domain suffix 325 while (i.hasNext()) { 326 String parentDomain = i.next(); 327 int start = 0; 328 while ((start = parentDomain.indexOf('.')) != -1 329 && start < parentDomain.length() -1) { 330 try { 331 results = resolve(ctx, host+"."+parentDomain, ids, 0); 332 break; 333 } catch (UnknownHostException x) { 334 uhe = x; 335 if (usingSearchList) { 336 break; 337 } 338 339 // devolve 340 parentDomain = parentDomain.substring(start+1); 341 } 342 } 343 if (results != null) { 344 break; 345 } 346 } 347 } 348 349 // finally try the host if it doesn't have a domain name 350 if (results == null && (host.indexOf('.') < 0)) { 351 results = resolve(ctx, host, ids, 0); 352 } 353 354 // if not found then throw the (last) exception thrown. 355 if (results == null) { 356 assert uhe != null; 357 throw uhe; 358 } 359 360 /** 361 * Convert the array list into a byte aray list - this 362 * filters out any invalid IPv4/IPv6 addresses. 363 */ 364 assert results.size() > 0; 365 InetAddress[] addrs = new InetAddress[results.size()]; 366 int count = 0; 367 for (int i=0; i<results.size(); i++) { 368 String addrString = results.get(i); 369 byte addr[] = IPAddressUtil.textToNumericFormatV4(addrString); 370 if (addr == null) { 371 addr = IPAddressUtil.textToNumericFormatV6(addrString); 372 } 373 if (addr != null) { 374 addrs[count++] = InetAddress.getByAddress(host, addr); 375 } 376 } 377 378 /** 379 * If addresses are filtered then we need to resize the 380 * array. Additionally if all addresses are filtered then 381 * we throw an exception. 382 */ 383 if (count == 0) { 384 throw new UnknownHostException(host + ": no valid DNS records"); 385 } 386 if (count < results.size()) { 387 InetAddress[] tmp = new InetAddress[count]; 388 for (int i=0; i<count; i++) { 389 tmp[i] = addrs[i]; 390 } 391 addrs = tmp; 392 } 393 394 return addrs; 395 } 396 397 /** 398 * Reverse lookup code. I.E: find a host name from an IP address. 399 * IPv4 addresses are mapped in the IN-ADDR.ARPA. top domain, while 400 * IPv6 addresses can be in IP6.ARPA or IP6.INT. 401 * In both cases the address has to be converted into a dotted form. 402 */ 403 public String getHostByAddr(byte[] addr) throws UnknownHostException { 404 String host = null; 405 try { 406 String literalip = ""; 407 String[] ids = { "PTR" }; 408 DirContext ctx; 409 ArrayList<String> results = null; 410 try { 411 ctx = getTemporaryContext(); 412 } catch (NamingException nx) { 413 throw new Error(nx); 414 } 415 if (addr.length == 4) { // IPv4 Address 416 for (int i = addr.length-1; i >= 0; i--) { 417 literalip += (addr[i] & 0xff) +"."; 418 } 419 literalip += "IN-ADDR.ARPA."; 420 421 results = resolve(ctx, literalip, ids, 0); 422 host = results.get(0); 423 } else if (addr.length == 16) { // IPv6 Address 424 /** 425 * Because RFC 3152 changed the root domain name for reverse 426 * lookups from IP6.INT. to IP6.ARPA., we need to check 427 * both. I.E. first the new one, IP6.ARPA, then if it fails 428 * the older one, IP6.INT 429 */ 430 431 for (int i = addr.length-1; i >= 0; i--) { 432 literalip += Integer.toHexString((addr[i] & 0x0f)) +"." 433 +Integer.toHexString((addr[i] & 0xf0) >> 4) +"."; 434 } 435 String ip6lit = literalip + "IP6.ARPA."; 436 437 try { 438 results = resolve(ctx, ip6lit, ids, 0); 439 host = results.get(0); 440 } catch (UnknownHostException e) { 441 host = null; 442 } 443 if (host == null) { 444 // IP6.ARPA lookup failed, let's try the older IP6.INT 445 ip6lit = literalip + "IP6.INT."; 446 results = resolve(ctx, ip6lit, ids, 0); 447 host = results.get(0); 448 } 449 } 450 } catch (Exception e) { 451 throw new UnknownHostException(e.getMessage()); 452 } 453 // Either we couldn't find it or the address was neither IPv4 or IPv6 454 if (host == null) 455 throw new UnknownHostException(); 456 // remove trailing dot 457 if (host.endsWith(".")) { 458 host = host.substring(0, host.length() - 1); 459 } 460 return host; 461 } 462 463 464 // --------- 465 466 private static void appendIfLiteralAddress(String addr, StringBuilder sb) { 467 if (IPAddressUtil.isIPv4LiteralAddress(addr)) { 468 sb.append("dns://").append(addr).append(' '); 469 } else { 470 if (IPAddressUtil.isIPv6LiteralAddress(addr)) { 471 sb.append("dns://[").append(addr).append("] "); 472 } 473 } 474 } 475 476 /* 477 * @return String containing the JNDI-DNS provider URL 478 * corresponding to the supplied List of nameservers. 479 */ 480 private static String createProviderURL(List<String> nsList) { 481 StringBuilder sb = new StringBuilder(); 482 for (String s: nsList) { 483 appendIfLiteralAddress(s, sb); 484 } 485 return sb.toString(); 486 } 487 488 /* 489 * @return String containing the JNDI-DNS provider URL 490 * corresponding to the list of nameservers 491 * contained in the provided str. 492 */ 493 private static String createProviderURL(String str) { 494 StringBuilder sb = new StringBuilder(); 495 StringTokenizer st = new StringTokenizer(str, ","); 496 while (st.hasMoreTokens()) { 497 appendIfLiteralAddress(st.nextToken(), sb); 498 } 499 return sb.toString(); 500 } 501 }