1 /* 2 * Copyright (c) 2000, 2013, 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 com.sun.jndi.dns; 27 28 29 import java.util.Enumeration; 30 import java.util.Hashtable; 31 32 import javax.naming.*; 33 import javax.naming.directory.*; 34 import javax.naming.spi.DirectoryManager; 35 36 import com.sun.jndi.toolkit.ctx.*; 37 38 39 /** 40 * A DnsContext is a directory context representing a DNS node. 41 * 42 * @author Scott Seligman 43 */ 44 45 46 public class DnsContext extends ComponentDirContext { 47 48 DnsName domain; // fully-qualified domain name of this context, 49 // with a root (empty) label at position 0 50 Hashtable<Object,Object> environment; 51 private boolean envShared; // true if environment is possibly shared 52 // and so must be copied on write 53 private boolean parentIsDns; // was this DnsContext created by 54 // another? see composeName() 55 private String[] servers; 56 private Resolver resolver; 57 58 private boolean authoritative; // must all responses be authoritative? 59 private boolean recursion; // request recursion on queries? 60 private int timeout; // initial timeout on UDP queries in ms 61 private int retries; // number of UDP retries 62 63 static final NameParser nameParser = new DnsNameParser(); 64 65 // Timeouts for UDP queries use exponential backoff: each retry 66 // is for twice as long as the last. The following constants set 67 // the defaults for the initial timeout (in ms) and the number of 68 // retries, and name the environment properties used to override 69 // these defaults. 70 private static final int DEFAULT_INIT_TIMEOUT = 1000; 71 private static final int DEFAULT_RETRIES = 4; 72 private static final String INIT_TIMEOUT = 73 "com.sun.jndi.dns.timeout.initial"; 74 private static final String RETRIES = "com.sun.jndi.dns.timeout.retries"; 75 76 // The resource record type and class to use for lookups, and the 77 // property used to modify them 78 private CT lookupCT; 79 private static final String LOOKUP_ATTR = "com.sun.jndi.dns.lookup.attr"; 80 81 // Property used to disallow recursion on queries 82 private static final String RECURSION = "com.sun.jndi.dns.recursion"; 83 84 // ANY == ResourceRecord.QCLASS_STAR == ResourceRecord.QTYPE_STAR 85 private static final int ANY = ResourceRecord.QTYPE_STAR; 86 87 // The zone tree used for list operations 88 private static final ZoneNode zoneTree = new ZoneNode(null); 89 90 91 /** 92 * Returns a DNS context for a given domain and servers. 93 * Each server is of the form "server[:port]". 94 * IPv6 literal host names include delimiting brackets. 95 * There must be at least one server. 96 * The environment must not be null; it is cloned before being stored. 97 */ 98 @SuppressWarnings("unchecked") 99 public DnsContext(String domain, String[] servers, Hashtable<?,?> environment) 100 throws NamingException { 101 102 this.domain = new DnsName(domain.endsWith(".") 103 ? domain 104 : domain + "."); 105 this.servers = (servers == null) ? null : servers.clone(); 106 this.environment = (Hashtable<Object,Object>) environment.clone(); 107 envShared = false; 108 parentIsDns = false; 109 resolver = null; 110 111 initFromEnvironment(); 112 } 113 114 /* 115 * Returns a clone of a DNS context, just like DnsContext(DnsContext) 116 * but with a different domain name and with parentIsDns set to true. 117 */ 118 DnsContext(DnsContext ctx, DnsName domain) { 119 this(ctx); 120 this.domain = domain; 121 parentIsDns = true; 122 } 123 124 /* 125 * Returns a clone of a DNS context. The context's modifiable 126 * private state is independent of the original's (so closing one 127 * context, for example, won't close the other). The two contexts 128 * share {@code environment}, but it's copy-on-write so there's 129 * no conflict. 130 */ 131 private DnsContext(DnsContext ctx) { 132 environment = ctx.environment; // shared environment, copy-on-write 133 envShared = ctx.envShared = true; 134 parentIsDns = ctx.parentIsDns; 135 domain = ctx.domain; 136 servers = ctx.servers; // shared servers, no write operation 137 resolver = ctx.resolver; 138 authoritative = ctx.authoritative; 139 recursion = ctx.recursion; 140 timeout = ctx.timeout; 141 retries = ctx.retries; 142 lookupCT = ctx.lookupCT; 143 } 144 145 public void close() { 146 if (resolver != null) { 147 resolver.close(); 148 resolver = null; 149 } 150 } 151 152 153 //---------- Environment operations 154 155 /* 156 * Override default with a noncloning version. 157 */ 158 protected Hashtable<?,?> p_getEnvironment() { 159 return environment; 160 } 161 162 public Hashtable<?,?> getEnvironment() throws NamingException { 163 return (Hashtable<?,?>) environment.clone(); 164 } 165 166 @SuppressWarnings("unchecked") 167 public Object addToEnvironment(String propName, Object propVal) 168 throws NamingException { 169 170 if (propName.equals(LOOKUP_ATTR)) { 171 lookupCT = getLookupCT((String) propVal); 172 } else if (propName.equals(Context.AUTHORITATIVE)) { 173 authoritative = "true".equalsIgnoreCase((String) propVal); 174 } else if (propName.equals(RECURSION)) { 175 recursion = "true".equalsIgnoreCase((String) propVal); 176 } else if (propName.equals(INIT_TIMEOUT)) { 177 int val = Integer.parseInt((String) propVal); 178 if (timeout != val) { 179 timeout = val; 180 resolver = null; 181 } 182 } else if (propName.equals(RETRIES)) { 183 int val = Integer.parseInt((String) propVal); 184 if (retries != val) { 185 retries = val; 186 resolver = null; 187 } 188 } 189 190 if (!envShared) { 191 return environment.put(propName, propVal); 192 } else if (environment.get(propName) != propVal) { 193 // copy on write 194 environment = (Hashtable<Object,Object>) environment.clone(); 195 envShared = false; 196 return environment.put(propName, propVal); 197 } else { 198 return propVal; 199 } 200 } 201 202 @SuppressWarnings("unchecked") 203 public Object removeFromEnvironment(String propName) 204 throws NamingException { 205 206 if (propName.equals(LOOKUP_ATTR)) { 207 lookupCT = getLookupCT(null); 208 } else if (propName.equals(Context.AUTHORITATIVE)) { 209 authoritative = false; 210 } else if (propName.equals(RECURSION)) { 211 recursion = true; 212 } else if (propName.equals(INIT_TIMEOUT)) { 213 if (timeout != DEFAULT_INIT_TIMEOUT) { 214 timeout = DEFAULT_INIT_TIMEOUT; 215 resolver = null; 216 } 217 } else if (propName.equals(RETRIES)) { 218 if (retries != DEFAULT_RETRIES) { 219 retries = DEFAULT_RETRIES; 220 resolver = null; 221 } 222 } 223 224 if (!envShared) { 225 return environment.remove(propName); 226 } else if (environment.get(propName) != null) { 227 // copy-on-write 228 environment = (Hashtable<Object,Object>) environment.clone(); 229 envShared = false; 230 return environment.remove(propName); 231 } else { 232 return null; 233 } 234 } 235 236 /* 237 * Update PROVIDER_URL property. Call this only when environment 238 * is not being shared. 239 */ 240 void setProviderUrl(String url) { 241 // assert !envShared; 242 environment.put(Context.PROVIDER_URL, url); 243 } 244 245 /* 246 * Read environment properties and set parameters. 247 */ 248 private void initFromEnvironment() 249 throws InvalidAttributeIdentifierException { 250 251 lookupCT = getLookupCT((String) environment.get(LOOKUP_ATTR)); 252 authoritative = "true".equalsIgnoreCase((String) 253 environment.get(Context.AUTHORITATIVE)); 254 String val = (String) environment.get(RECURSION); 255 recursion = ((val == null) || 256 "true".equalsIgnoreCase(val)); 257 val = (String) environment.get(INIT_TIMEOUT); 258 timeout = (val == null) 259 ? DEFAULT_INIT_TIMEOUT 260 : Integer.parseInt(val); 261 val = (String) environment.get(RETRIES); 262 retries = (val == null) 263 ? DEFAULT_RETRIES 264 : Integer.parseInt(val); 265 } 266 267 private CT getLookupCT(String attrId) 268 throws InvalidAttributeIdentifierException { 269 return (attrId == null) 270 ? new CT(ResourceRecord.CLASS_INTERNET, ResourceRecord.TYPE_TXT) 271 : fromAttrId(attrId); 272 } 273 274 275 //---------- Naming operations 276 277 public Object c_lookup(Name name, Continuation cont) 278 throws NamingException { 279 280 cont.setSuccess(); 281 if (name.isEmpty()) { 282 DnsContext ctx = new DnsContext(this); 283 ctx.resolver = new Resolver(servers, timeout, retries); 284 // clone for parallelism 285 return ctx; 286 } 287 try { 288 DnsName fqdn = fullyQualify(name); 289 ResourceRecords rrs = 290 getResolver().query(fqdn, lookupCT.rrclass, lookupCT.rrtype, 291 recursion, authoritative); 292 Attributes attrs = rrsToAttrs(rrs, null); 293 DnsContext ctx = new DnsContext(this, fqdn); 294 return DirectoryManager.getObjectInstance(ctx, name, this, 295 environment, attrs); 296 } catch (NamingException e) { 297 cont.setError(this, name); 298 throw cont.fillInException(e); 299 } catch (Exception e) { 300 cont.setError(this, name); 301 NamingException ne = new NamingException( 302 "Problem generating object using object factory"); 303 ne.setRootCause(e); 304 throw cont.fillInException(ne); 305 } 306 } 307 308 public Object c_lookupLink(Name name, Continuation cont) 309 throws NamingException { 310 return c_lookup(name, cont); 311 } 312 313 public NamingEnumeration<NameClassPair> c_list(Name name, Continuation cont) 314 throws NamingException { 315 cont.setSuccess(); 316 try { 317 DnsName fqdn = fullyQualify(name); 318 NameNode nnode = getNameNode(fqdn); 319 DnsContext ctx = new DnsContext(this, fqdn); 320 return new NameClassPairEnumeration(ctx, nnode.getChildren()); 321 322 } catch (NamingException e) { 323 cont.setError(this, name); 324 throw cont.fillInException(e); 325 } 326 } 327 328 public NamingEnumeration<Binding> c_listBindings(Name name, Continuation cont) 329 throws NamingException { 330 cont.setSuccess(); 331 try { 332 DnsName fqdn = fullyQualify(name); 333 NameNode nnode = getNameNode(fqdn); 334 DnsContext ctx = new DnsContext(this, fqdn); 335 return new BindingEnumeration(ctx, nnode.getChildren()); 336 337 } catch (NamingException e) { 338 cont.setError(this, name); 339 throw cont.fillInException(e); 340 } 341 } 342 343 public void c_bind(Name name, Object obj, Continuation cont) 344 throws NamingException { 345 cont.setError(this, name); 346 throw cont.fillInException( 347 new OperationNotSupportedException()); 348 } 349 350 public void c_rebind(Name name, Object obj, Continuation cont) 351 throws NamingException { 352 cont.setError(this, name); 353 throw cont.fillInException( 354 new OperationNotSupportedException()); 355 } 356 357 public void c_unbind(Name name, Continuation cont) 358 throws NamingException { 359 cont.setError(this, name); 360 throw cont.fillInException( 361 new OperationNotSupportedException()); 362 } 363 364 public void c_rename(Name oldname, Name newname, Continuation cont) 365 throws NamingException { 366 cont.setError(this, oldname); 367 throw cont.fillInException( 368 new OperationNotSupportedException()); 369 } 370 371 public Context c_createSubcontext(Name name, Continuation cont) 372 throws NamingException { 373 cont.setError(this, name); 374 throw cont.fillInException( 375 new OperationNotSupportedException()); 376 } 377 378 public void c_destroySubcontext(Name name, Continuation cont) 379 throws NamingException { 380 cont.setError(this, name); 381 throw cont.fillInException( 382 new OperationNotSupportedException()); 383 } 384 385 public NameParser c_getNameParser(Name name, Continuation cont) 386 throws NamingException { 387 cont.setSuccess(); 388 return nameParser; 389 } 390 391 392 //---------- Directory operations 393 394 public void c_bind(Name name, 395 Object obj, 396 Attributes attrs, 397 Continuation cont) 398 throws NamingException { 399 cont.setError(this, name); 400 throw cont.fillInException( 401 new OperationNotSupportedException()); 402 } 403 404 public void c_rebind(Name name, 405 Object obj, 406 Attributes attrs, 407 Continuation cont) 408 throws NamingException { 409 cont.setError(this, name); 410 throw cont.fillInException( 411 new OperationNotSupportedException()); 412 } 413 414 public DirContext c_createSubcontext(Name name, 415 Attributes attrs, 416 Continuation cont) 417 throws NamingException { 418 cont.setError(this, name); 419 throw cont.fillInException( 420 new OperationNotSupportedException()); 421 } 422 423 public Attributes c_getAttributes(Name name, 424 String[] attrIds, 425 Continuation cont) 426 throws NamingException { 427 428 cont.setSuccess(); 429 try { 430 DnsName fqdn = fullyQualify(name); 431 CT[] cts = attrIdsToClassesAndTypes(attrIds); 432 CT ct = getClassAndTypeToQuery(cts); 433 ResourceRecords rrs = 434 getResolver().query(fqdn, ct.rrclass, ct.rrtype, 435 recursion, authoritative); 436 return rrsToAttrs(rrs, cts); 437 438 } catch (NamingException e) { 439 cont.setError(this, name); 440 throw cont.fillInException(e); 441 } 442 } 443 444 public void c_modifyAttributes(Name name, 445 int mod_op, 446 Attributes attrs, 447 Continuation cont) 448 throws NamingException { 449 cont.setError(this, name); 450 throw cont.fillInException( 451 new OperationNotSupportedException()); 452 } 453 454 public void c_modifyAttributes(Name name, 455 ModificationItem[] mods, 456 Continuation cont) 457 throws NamingException { 458 cont.setError(this, name); 459 throw cont.fillInException( 460 new OperationNotSupportedException()); 461 } 462 463 public NamingEnumeration<SearchResult> c_search(Name name, 464 Attributes matchingAttributes, 465 String[] attributesToReturn, 466 Continuation cont) 467 throws NamingException { 468 throw new OperationNotSupportedException(); 469 } 470 471 public NamingEnumeration<SearchResult> c_search(Name name, 472 String filter, 473 SearchControls cons, 474 Continuation cont) 475 throws NamingException { 476 throw new OperationNotSupportedException(); 477 } 478 479 public NamingEnumeration<SearchResult> c_search(Name name, 480 String filterExpr, 481 Object[] filterArgs, 482 SearchControls cons, 483 Continuation cont) 484 throws NamingException { 485 throw new OperationNotSupportedException(); 486 } 487 488 public DirContext c_getSchema(Name name, Continuation cont) 489 throws NamingException { 490 cont.setError(this, name); 491 throw cont.fillInException( 492 new OperationNotSupportedException()); 493 } 494 495 public DirContext c_getSchemaClassDefinition(Name name, Continuation cont) 496 throws NamingException { 497 cont.setError(this, name); 498 throw cont.fillInException( 499 new OperationNotSupportedException()); 500 } 501 502 503 //---------- Name-related operations 504 505 public String getNameInNamespace() { 506 return domain.toString(); 507 } 508 509 public Name composeName(Name name, Name prefix) throws NamingException { 510 Name result; 511 512 // Any name that's not a CompositeName is assumed to be a DNS 513 // compound name. Convert each to a DnsName for syntax checking. 514 if (!(prefix instanceof DnsName || prefix instanceof CompositeName)) { 515 prefix = (new DnsName()).addAll(prefix); 516 } 517 if (!(name instanceof DnsName || name instanceof CompositeName)) { 518 name = (new DnsName()).addAll(name); 519 } 520 521 // Each of prefix and name is now either a DnsName or a CompositeName. 522 523 // If we have two DnsNames, simply join them together. 524 if ((prefix instanceof DnsName) && (name instanceof DnsName)) { 525 result = (DnsName) (prefix.clone()); 526 result.addAll(name); 527 return new CompositeName().add(result.toString()); 528 } 529 530 // Wrap compound names in composite names. 531 Name prefixC = (prefix instanceof CompositeName) 532 ? prefix 533 : new CompositeName().add(prefix.toString()); 534 Name nameC = (name instanceof CompositeName) 535 ? name 536 : new CompositeName().add(name.toString()); 537 int prefixLast = prefixC.size() - 1; 538 539 // Let toolkit do the work at namespace boundaries. 540 if (nameC.isEmpty() || nameC.get(0).equals("") || 541 prefixC.isEmpty() || prefixC.get(prefixLast).equals("")) { 542 return super.composeName(nameC, prefixC); 543 } 544 545 result = (prefix == prefixC) 546 ? (CompositeName) prefixC.clone() 547 : prefixC; // prefixC is already a clone 548 result.addAll(nameC); 549 550 if (parentIsDns) { 551 DnsName dnsComp = (prefix instanceof DnsName) 552 ? (DnsName) prefix.clone() 553 : new DnsName(prefixC.get(prefixLast)); 554 dnsComp.addAll((name instanceof DnsName) 555 ? name 556 : new DnsName(nameC.get(0))); 557 result.remove(prefixLast + 1); 558 result.remove(prefixLast); 559 result.add(prefixLast, dnsComp.toString()); 560 } 561 return result; 562 } 563 564 565 //---------- Helper methods 566 567 /* 568 * Resolver is not created until needed, to allow time for updates 569 * to the environment. 570 */ 571 private synchronized Resolver getResolver() throws NamingException { 572 if (resolver == null) { 573 resolver = new Resolver(servers, timeout, retries); 574 } 575 return resolver; 576 } 577 578 /* 579 * Returns the fully-qualified domain name of a name given 580 * relative to this context. Result includes a root label (an 581 * empty component at position 0). 582 */ 583 DnsName fullyQualify(Name name) throws NamingException { 584 if (name.isEmpty()) { 585 return domain; 586 } 587 DnsName dnsName = (name instanceof CompositeName) 588 ? new DnsName(name.get(0)) // parse name 589 : (DnsName) (new DnsName()).addAll(name); // clone & check syntax 590 591 if (dnsName.hasRootLabel()) { 592 // Be overly generous and allow root label if we're in root domain. 593 if (domain.size() == 1) { 594 return dnsName; 595 } else { 596 throw new InvalidNameException( 597 "DNS name " + dnsName + " not relative to " + domain); 598 } 599 } 600 return (DnsName) dnsName.addAll(0, domain); 601 } 602 603 /* 604 * Converts resource records to an attribute set. Only resource 605 * records in the answer section are used, and only those that 606 * match the classes and types in cts (see classAndTypeMatch() 607 * for matching rules). 608 */ 609 private static Attributes rrsToAttrs(ResourceRecords rrs, CT[] cts) { 610 611 BasicAttributes attrs = new BasicAttributes(true); 612 613 for (int i = 0; i < rrs.answer.size(); i++) { 614 ResourceRecord rr = rrs.answer.elementAt(i); 615 int rrtype = rr.getType(); 616 int rrclass = rr.getRrclass(); 617 618 if (!classAndTypeMatch(rrclass, rrtype, cts)) { 619 continue; 620 } 621 622 String attrId = toAttrId(rrclass, rrtype); 623 Attribute attr = attrs.get(attrId); 624 if (attr == null) { 625 attr = new BasicAttribute(attrId); 626 attrs.put(attr); 627 } 628 attr.add(rr.getRdata()); 629 } 630 return attrs; 631 } 632 633 /* 634 * Returns true if rrclass and rrtype match some element of cts. 635 * A match occurs if corresponding classes and types are equal, 636 * or if the array value is ANY. If cts is null, then any class 637 * and type match. 638 */ 639 private static boolean classAndTypeMatch(int rrclass, int rrtype, 640 CT[] cts) { 641 if (cts == null) { 642 return true; 643 } 644 for (int i = 0; i < cts.length; i++) { 645 CT ct = cts[i]; 646 boolean classMatch = (ct.rrclass == ANY) || 647 (ct.rrclass == rrclass); 648 boolean typeMatch = (ct.rrtype == ANY) || 649 (ct.rrtype == rrtype); 650 if (classMatch && typeMatch) { 651 return true; 652 } 653 } 654 return false; 655 } 656 657 /* 658 * Returns the attribute ID for a resource record given its class 659 * and type. If the record is in the internet class, the 660 * corresponding attribute ID is the record's type name (or the 661 * integer type value if the name is not known). If the record is 662 * not in the internet class, the class name (or integer class 663 * value) is prepended to the attribute ID, separated by a space. 664 * 665 * A class or type value of ANY represents an indeterminate class 666 * or type, and is represented within the attribute ID by "*". 667 * For example, the attribute ID "IN *" represents 668 * any type in the internet class, and "* NS" represents an NS 669 * record of any class. 670 */ 671 private static String toAttrId(int rrclass, int rrtype) { 672 String attrId = ResourceRecord.getTypeName(rrtype); 673 if (rrclass != ResourceRecord.CLASS_INTERNET) { 674 attrId = ResourceRecord.getRrclassName(rrclass) + " " + attrId; 675 } 676 return attrId; 677 } 678 679 /* 680 * Returns the class and type values corresponding to an attribute 681 * ID. An indeterminate class or type is represented by ANY. See 682 * toAttrId() for the format of attribute IDs. 683 * 684 * @throws InvalidAttributeIdentifierException 685 * if class or type is unknown 686 */ 687 private static CT fromAttrId(String attrId) 688 throws InvalidAttributeIdentifierException { 689 690 if (attrId.equals("")) { 691 throw new InvalidAttributeIdentifierException( 692 "Attribute ID cannot be empty"); 693 } 694 int rrclass; 695 int rrtype; 696 int space = attrId.indexOf(' '); 697 698 // class 699 if (space < 0) { 700 rrclass = ResourceRecord.CLASS_INTERNET; 701 } else { 702 String className = attrId.substring(0, space); 703 rrclass = ResourceRecord.getRrclass(className); 704 if (rrclass < 0) { 705 throw new InvalidAttributeIdentifierException( 706 "Unknown resource record class '" + className + '\''); 707 } 708 } 709 710 // type 711 String typeName = attrId.substring(space + 1); 712 rrtype = ResourceRecord.getType(typeName); 713 if (rrtype < 0) { 714 throw new InvalidAttributeIdentifierException( 715 "Unknown resource record type '" + typeName + '\''); 716 } 717 718 return new CT(rrclass, rrtype); 719 } 720 721 /* 722 * Returns an array of the classes and types corresponding to a 723 * set of attribute IDs. See toAttrId() for the format of 724 * attribute IDs, and classAndTypeMatch() for the format of the 725 * array returned. 726 */ 727 private static CT[] attrIdsToClassesAndTypes(String[] attrIds) 728 throws InvalidAttributeIdentifierException { 729 if (attrIds == null) { 730 return null; 731 } 732 CT[] cts = new CT[attrIds.length]; 733 734 for (int i = 0; i < attrIds.length; i++) { 735 cts[i] = fromAttrId(attrIds[i]); 736 } 737 return cts; 738 } 739 740 /* 741 * Returns the most restrictive resource record class and type 742 * that may be used to query for records matching cts. 743 * See classAndTypeMatch() for matching rules. 744 */ 745 private static CT getClassAndTypeToQuery(CT[] cts) { 746 int rrclass; 747 int rrtype; 748 749 if (cts == null) { 750 // Query all records. 751 rrclass = ANY; 752 rrtype = ANY; 753 } else if (cts.length == 0) { 754 // No records are requested, but we need to ask for something. 755 rrclass = ResourceRecord.CLASS_INTERNET; 756 rrtype = ANY; 757 } else { 758 rrclass = cts[0].rrclass; 759 rrtype = cts[0].rrtype; 760 for (int i = 1; i < cts.length; i++) { 761 if (rrclass != cts[i].rrclass) { 762 rrclass = ANY; 763 } 764 if (rrtype != cts[i].rrtype) { 765 rrtype = ANY; 766 } 767 } 768 } 769 return new CT(rrclass, rrtype); 770 } 771 772 773 //---------- Support for list operations 774 775 /* 776 * Synchronization notes: 777 * 778 * Any access to zoneTree that walks the tree, whether it modifies 779 * the tree or not, is synchronized on zoneTree. 780 * [%%% Note: a read/write lock would allow increased concurrency.] 781 * The depth of a ZoneNode can thereafter be accessed without 782 * further synchronization. Access to other fields and methods 783 * should be synchronized on the node itself. 784 * 785 * A zone's contents is a NameNode tree that, once created, is never 786 * modified. The only synchronization needed is to ensure that it 787 * gets flushed into shared memory after being created, which is 788 * accomplished by ZoneNode.populate(). The contents are accessed 789 * via a soft reference, so a ZoneNode may be seen to be populated 790 * one moment and unpopulated the next. 791 */ 792 793 /* 794 * Returns the node in the zone tree corresponding to a 795 * fully-qualified domain name. If the desired portion of the 796 * tree has not yet been populated or has been outdated, a zone 797 * transfer is done to populate the tree. 798 */ 799 private NameNode getNameNode(DnsName fqdn) throws NamingException { 800 dprint("getNameNode(" + fqdn + ")"); 801 802 // Find deepest related zone in zone tree. 803 ZoneNode znode; 804 DnsName zone; 805 synchronized (zoneTree) { 806 znode = zoneTree.getDeepestPopulated(fqdn); 807 } 808 dprint("Deepest related zone in zone tree: " + 809 ((znode != null) ? znode.getLabel() : "[none]")); 810 811 NameNode topOfZone; 812 NameNode nnode; 813 814 if (znode != null) { 815 synchronized (znode) { 816 topOfZone = znode.getContents(); 817 } 818 // If fqdn is in znode's zone, is not at a zone cut, and 819 // is current, we're done. 820 if (topOfZone != null) { 821 nnode = topOfZone.get(fqdn, znode.depth() + 1); // +1 for root 822 823 if ((nnode != null) && !nnode.isZoneCut()) { 824 dprint("Found node " + fqdn + " in zone tree"); 825 zone = (DnsName) 826 fqdn.getPrefix(znode.depth() + 1); // +1 for root 827 boolean current = isZoneCurrent(znode, zone); 828 boolean restart = false; 829 830 synchronized (znode) { 831 if (topOfZone != znode.getContents()) { 832 // Zone was modified while we were examining it. 833 // All bets are off. 834 restart = true; 835 } else if (!current) { 836 znode.depopulate(); 837 } else { 838 return nnode; // cache hit! 839 } 840 } 841 dprint("Zone not current; discarding node"); 842 if (restart) { 843 return getNameNode(fqdn); 844 } 845 } 846 } 847 } 848 849 // Cache miss... do it the expensive way. 850 dprint("Adding node " + fqdn + " to zone tree"); 851 852 // Find fqdn's zone and add it to the tree. 853 zone = getResolver().findZoneName(fqdn, ResourceRecord.CLASS_INTERNET, 854 recursion); 855 dprint("Node's zone is " + zone); 856 synchronized (zoneTree) { 857 znode = (ZoneNode) zoneTree.add(zone, 1); // "1" to skip root 858 } 859 860 // If znode is now populated we know -- because the first half of 861 // getNodeName() didn't find it -- that it was populated by another 862 // thread during this method call. Assume then that it's current. 863 864 synchronized (znode) { 865 topOfZone = znode.isPopulated() 866 ? znode.getContents() 867 : populateZone(znode, zone); 868 } 869 // Desired node should now be in znode's populated zone. Find it. 870 nnode = topOfZone.get(fqdn, zone.size()); 871 if (nnode == null) { 872 throw new ConfigurationException( 873 "DNS error: node not found in its own zone"); 874 } 875 dprint("Found node in newly-populated zone"); 876 return nnode; 877 } 878 879 /* 880 * Does a zone transfer to [re]populate a zone in the zone tree. 881 * Returns the zone's new contents. 882 */ 883 private NameNode populateZone(ZoneNode znode, DnsName zone) 884 throws NamingException { 885 dprint("Populating zone " + zone); 886 // assert Thread.holdsLock(znode); 887 ResourceRecords rrs = 888 getResolver().queryZone(zone, 889 ResourceRecord.CLASS_INTERNET, recursion); 890 dprint("zone xfer complete: " + rrs.answer.size() + " records"); 891 return znode.populate(zone, rrs); 892 } 893 894 /* 895 * Determine if a ZoneNode's data is current. 896 * We base this on a comparison between the cached serial 897 * number and the latest SOA record. 898 * 899 * If there is no SOA record, znode is not (or is no longer) a zone: 900 * depopulate znode and return false. 901 * 902 * Since this method may perform a network operation, it is best 903 * to call it with znode unlocked. Caller must then note that the 904 * result may be outdated by the time this method returns. 905 */ 906 private boolean isZoneCurrent(ZoneNode znode, DnsName zone) 907 throws NamingException { 908 // former version: return !znode.isExpired(); 909 910 if (!znode.isPopulated()) { 911 return false; 912 } 913 ResourceRecord soa = 914 getResolver().findSoa(zone, ResourceRecord.CLASS_INTERNET, 915 recursion); 916 synchronized (znode) { 917 if (soa == null) { 918 znode.depopulate(); 919 } 920 return (znode.isPopulated() && 921 znode.compareSerialNumberTo(soa) >= 0); 922 } 923 } 924 925 926 //---------- Debugging 927 928 private static final boolean debug = false; 929 930 private static final void dprint(String msg) { 931 if (debug) { 932 System.err.println("** " + msg); 933 } 934 } 935 } 936 937 938 //---------- 939 940 /* 941 * A pairing of a resource record class and a resource record type. 942 * A value of ANY in either field represents an indeterminate value. 943 */ 944 class CT { 945 int rrclass; 946 int rrtype; 947 948 CT(int rrclass, int rrtype) { 949 this.rrclass = rrclass; 950 this.rrtype = rrtype; 951 } 952 } 953 954 955 //---------- 956 957 /* 958 * Common base class for NameClassPairEnumeration and BindingEnumeration. 959 */ 960 abstract class BaseNameClassPairEnumeration<T> implements NamingEnumeration<T> { 961 962 protected Enumeration<NameNode> nodes; // nodes to be enumerated, or null if none 963 protected DnsContext ctx; // context being enumerated 964 965 BaseNameClassPairEnumeration(DnsContext ctx, Hashtable<String,NameNode> nodes) { 966 this.ctx = ctx; 967 this.nodes = (nodes != null) 968 ? nodes.elements() 969 : null; 970 } 971 972 /* 973 * ctx will be set to null when no longer needed by the enumeration. 974 */ 975 public final void close() { 976 nodes = null; 977 ctx = null; 978 } 979 980 public final boolean hasMore() { 981 boolean more = ((nodes != null) && nodes.hasMoreElements()); 982 if (!more) { 983 close(); 984 } 985 return more; 986 } 987 988 public final boolean hasMoreElements() { 989 return hasMore(); 990 } 991 992 abstract public T next() throws NamingException; 993 994 public final T nextElement() { 995 try { 996 return next(); 997 } catch (NamingException e) { 998 java.util.NoSuchElementException nsee = 999 new java.util.NoSuchElementException(); 1000 nsee.initCause(e); 1001 throw nsee; 1002 } 1003 } 1004 } 1005 1006 /* 1007 * An enumeration of name/classname pairs. 1008 * 1009 * Nodes that have children or that are zone cuts are returned with 1010 * classname DirContext. Other nodes are returned with classname 1011 * Object even though they are DirContexts as well, since this might 1012 * make the namespace easier to browse. 1013 */ 1014 final class NameClassPairEnumeration 1015 extends BaseNameClassPairEnumeration<NameClassPair> 1016 implements NamingEnumeration<NameClassPair> { 1017 1018 NameClassPairEnumeration(DnsContext ctx, Hashtable<String,NameNode> nodes) { 1019 super(ctx, nodes); 1020 } 1021 1022 @Override 1023 public NameClassPair next() throws NamingException { 1024 if (!hasMore()) { 1025 throw new java.util.NoSuchElementException(); 1026 } 1027 NameNode nnode = nodes.nextElement(); 1028 String className = (nnode.isZoneCut() || 1029 (nnode.getChildren() != null)) 1030 ? "javax.naming.directory.DirContext" 1031 : "java.lang.Object"; 1032 1033 String label = nnode.getLabel(); 1034 Name compName = (new DnsName()).add(label); 1035 Name cname = (new CompositeName()).add(compName.toString()); 1036 1037 NameClassPair ncp = new NameClassPair(cname.toString(), className); 1038 ncp.setNameInNamespace(ctx.fullyQualify(cname).toString()); 1039 return ncp; 1040 } 1041 } 1042 1043 /* 1044 * An enumeration of Bindings. 1045 */ 1046 final class BindingEnumeration extends BaseNameClassPairEnumeration<Binding> 1047 implements NamingEnumeration<Binding> { 1048 1049 BindingEnumeration(DnsContext ctx, Hashtable<String,NameNode> nodes) { 1050 super(ctx, nodes); 1051 } 1052 1053 // Finalizer not needed since it's safe to leave ctx unclosed. 1054 // protected void finalize() { 1055 // close(); 1056 // } 1057 1058 @Override 1059 public Binding next() throws NamingException { 1060 if (!hasMore()) { 1061 throw (new java.util.NoSuchElementException()); 1062 } 1063 NameNode nnode = nodes.nextElement(); 1064 1065 String label = nnode.getLabel(); 1066 Name compName = (new DnsName()).add(label); 1067 String compNameStr = compName.toString(); 1068 Name cname = (new CompositeName()).add(compNameStr); 1069 String cnameStr = cname.toString(); 1070 1071 DnsName fqdn = ctx.fullyQualify(compName); 1072 1073 // Clone ctx to create the child context. 1074 DnsContext child = new DnsContext(ctx, fqdn); 1075 1076 try { 1077 Object obj = DirectoryManager.getObjectInstance( 1078 child, cname, ctx, child.environment, null); 1079 Binding binding = new Binding(cnameStr, obj); 1080 binding.setNameInNamespace(ctx.fullyQualify(cname).toString()); 1081 return binding; 1082 } catch (Exception e) { 1083 NamingException ne = new NamingException( 1084 "Problem generating object using object factory"); 1085 ne.setRootCause(e); 1086 throw ne; 1087 } 1088 } 1089 }