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