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 }