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 }