| /* |
| * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.jndi.dns; |
| |
| |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| |
| import javax.naming.*; |
| import javax.naming.directory.*; |
| import javax.naming.spi.DirectoryManager; |
| |
| import com.sun.jndi.toolkit.ctx.*; |
| |
| |
| /** |
| * A DnsContext is a directory context representing a DNS node. |
| * |
| * @author Scott Seligman |
| */ |
| |
| |
| public class DnsContext extends ComponentDirContext { |
| |
| DnsName domain; // fully-qualified domain name of this context, |
| // with a root (empty) label at position 0 |
| Hashtable environment; |
| private boolean envShared; // true if environment is possibly shared |
| // and so must be copied on write |
| private boolean parentIsDns; // was this DnsContext created by |
| // another? see composeName() |
| private String[] servers; |
| private Resolver resolver; |
| |
| private boolean authoritative; // must all responses be authoritative? |
| private boolean recursion; // request recursion on queries? |
| private int timeout; // initial timeout on UDP queries in ms |
| private int retries; // number of UDP retries |
| |
| static final NameParser nameParser = new DnsNameParser(); |
| |
| // Timeouts for UDP queries use exponential backoff: each retry |
| // is for twice as long as the last. The following constants set |
| // the defaults for the initial timeout (in ms) and the number of |
| // retries, and name the environment properties used to override |
| // these defaults. |
| private static final int DEFAULT_INIT_TIMEOUT = 1000; |
| private static final int DEFAULT_RETRIES = 4; |
| private static final String INIT_TIMEOUT = |
| "com.sun.jndi.dns.timeout.initial"; |
| private static final String RETRIES = "com.sun.jndi.dns.timeout.retries"; |
| |
| // The resource record type and class to use for lookups, and the |
| // property used to modify them |
| private CT lookupCT; |
| private static final String LOOKUP_ATTR = "com.sun.jndi.dns.lookup.attr"; |
| |
| // Property used to disallow recursion on queries |
| private static final String RECURSION = "com.sun.jndi.dns.recursion"; |
| |
| // ANY == ResourceRecord.QCLASS_STAR == ResourceRecord.QTYPE_STAR |
| private static final int ANY = ResourceRecord.QTYPE_STAR; |
| |
| // The zone tree used for list operations |
| private static final ZoneNode zoneTree = new ZoneNode(null); |
| |
| |
| /** |
| * Returns a DNS context for a given domain and servers. |
| * Each server is of the form "server[:port]". |
| * IPv6 literal host names include delimiting brackets. |
| * There must be at least one server. |
| * The environment must not be null; it is cloned before being stored. |
| */ |
| public DnsContext(String domain, String[] servers, Hashtable environment) |
| throws NamingException { |
| |
| this.domain = new DnsName(domain.endsWith(".") |
| ? domain |
| : domain + "."); |
| this.servers = servers; |
| this.environment = (Hashtable) environment.clone(); |
| envShared = false; |
| parentIsDns = false; |
| resolver = null; |
| |
| initFromEnvironment(); |
| } |
| |
| /* |
| * Returns a clone of a DNS context, just like DnsContext(DnsContext) |
| * but with a different domain name and with parentIsDns set to true. |
| */ |
| DnsContext(DnsContext ctx, DnsName domain) { |
| this(ctx); |
| this.domain = domain; |
| parentIsDns = true; |
| } |
| |
| /* |
| * Returns a clone of a DNS context. The context's modifiable |
| * private state is independent of the original's (so closing one |
| * context, for example, won't close the other). The two contexts |
| * share <tt>environment</tt>, but it's copy-on-write so there's |
| * no conflict. |
| */ |
| private DnsContext(DnsContext ctx) { |
| environment = ctx.environment; |
| envShared = ctx.envShared = true; |
| parentIsDns = ctx.parentIsDns; |
| domain = ctx.domain; |
| servers = ctx.servers; |
| resolver = ctx.resolver; |
| authoritative = ctx.authoritative; |
| recursion = ctx.recursion; |
| timeout = ctx.timeout; |
| retries = ctx.retries; |
| lookupCT = ctx.lookupCT; |
| } |
| |
| public void close() { |
| if (resolver != null) { |
| resolver.close(); |
| resolver = null; |
| } |
| } |
| |
| |
| //---------- Environment operations |
| |
| /* |
| * Override default with a noncloning version. |
| */ |
| protected Hashtable p_getEnvironment() { |
| return environment; |
| } |
| |
| public Hashtable getEnvironment() throws NamingException { |
| return (Hashtable) environment.clone(); |
| } |
| |
| public Object addToEnvironment(String propName, Object propVal) |
| throws NamingException { |
| |
| if (propName.equals(LOOKUP_ATTR)) { |
| lookupCT = getLookupCT((String) propVal); |
| } else if (propName.equals(Context.AUTHORITATIVE)) { |
| authoritative = "true".equalsIgnoreCase((String) propVal); |
| } else if (propName.equals(RECURSION)) { |
| recursion = "true".equalsIgnoreCase((String) propVal); |
| } else if (propName.equals(INIT_TIMEOUT)) { |
| int val = Integer.parseInt((String) propVal); |
| if (timeout != val) { |
| timeout = val; |
| resolver = null; |
| } |
| } else if (propName.equals(RETRIES)) { |
| int val = Integer.parseInt((String) propVal); |
| if (retries != val) { |
| retries = val; |
| resolver = null; |
| } |
| } |
| |
| if (!envShared) { |
| return environment.put(propName, propVal); |
| } else if (environment.get(propName) != propVal) { |
| // copy on write |
| environment = (Hashtable) environment.clone(); |
| envShared = false; |
| return environment.put(propName, propVal); |
| } else { |
| return propVal; |
| } |
| } |
| |
| public Object removeFromEnvironment(String propName) |
| throws NamingException { |
| |
| if (propName.equals(LOOKUP_ATTR)) { |
| lookupCT = getLookupCT(null); |
| } else if (propName.equals(Context.AUTHORITATIVE)) { |
| authoritative = false; |
| } else if (propName.equals(RECURSION)) { |
| recursion = true; |
| } else if (propName.equals(INIT_TIMEOUT)) { |
| if (timeout != DEFAULT_INIT_TIMEOUT) { |
| timeout = DEFAULT_INIT_TIMEOUT; |
| resolver = null; |
| } |
| } else if (propName.equals(RETRIES)) { |
| if (retries != DEFAULT_RETRIES) { |
| retries = DEFAULT_RETRIES; |
| resolver = null; |
| } |
| } |
| |
| if (!envShared) { |
| return environment.remove(propName); |
| } else if (environment.get(propName) != null) { |
| // copy-on-write |
| environment = (Hashtable) environment.clone(); |
| envShared = false; |
| return environment.remove(propName); |
| } else { |
| return null; |
| } |
| } |
| |
| /* |
| * Update PROVIDER_URL property. Call this only when environment |
| * is not being shared. |
| */ |
| void setProviderUrl(String url) { |
| // assert !envShared; |
| environment.put(Context.PROVIDER_URL, url); |
| } |
| |
| /* |
| * Read environment properties and set parameters. |
| */ |
| private void initFromEnvironment() |
| throws InvalidAttributeIdentifierException { |
| |
| lookupCT = getLookupCT((String) environment.get(LOOKUP_ATTR)); |
| authoritative = "true".equalsIgnoreCase((String) |
| environment.get(Context.AUTHORITATIVE)); |
| String val = (String) environment.get(RECURSION); |
| recursion = ((val == null) || |
| "true".equalsIgnoreCase(val)); |
| val = (String) environment.get(INIT_TIMEOUT); |
| timeout = (val == null) |
| ? DEFAULT_INIT_TIMEOUT |
| : Integer.parseInt(val); |
| val = (String) environment.get(RETRIES); |
| retries = (val == null) |
| ? DEFAULT_RETRIES |
| : Integer.parseInt(val); |
| } |
| |
| private CT getLookupCT(String attrId) |
| throws InvalidAttributeIdentifierException { |
| return (attrId == null) |
| ? new CT(ResourceRecord.CLASS_INTERNET, ResourceRecord.TYPE_TXT) |
| : fromAttrId(attrId); |
| } |
| |
| |
| //---------- Naming operations |
| |
| public Object c_lookup(Name name, Continuation cont) |
| throws NamingException { |
| |
| cont.setSuccess(); |
| if (name.isEmpty()) { |
| DnsContext ctx = new DnsContext(this); |
| ctx.resolver = new Resolver(servers, timeout, retries); |
| // clone for parallelism |
| return ctx; |
| } |
| try { |
| DnsName fqdn = fullyQualify(name); |
| ResourceRecords rrs = |
| getResolver().query(fqdn, lookupCT.rrclass, lookupCT.rrtype, |
| recursion, authoritative); |
| Attributes attrs = rrsToAttrs(rrs, null); |
| DnsContext ctx = new DnsContext(this, fqdn); |
| return DirectoryManager.getObjectInstance(ctx, name, this, |
| environment, attrs); |
| } catch (NamingException e) { |
| cont.setError(this, name); |
| throw cont.fillInException(e); |
| } catch (Exception e) { |
| cont.setError(this, name); |
| NamingException ne = new NamingException( |
| "Problem generating object using object factory"); |
| ne.setRootCause(e); |
| throw cont.fillInException(ne); |
| } |
| } |
| |
| public Object c_lookupLink(Name name, Continuation cont) |
| throws NamingException { |
| return c_lookup(name, cont); |
| } |
| |
| public NamingEnumeration c_list(Name name, Continuation cont) |
| throws NamingException { |
| cont.setSuccess(); |
| try { |
| DnsName fqdn = fullyQualify(name); |
| NameNode nnode = getNameNode(fqdn); |
| DnsContext ctx = new DnsContext(this, fqdn); |
| return new NameClassPairEnumeration(ctx, nnode.getChildren()); |
| |
| } catch (NamingException e) { |
| cont.setError(this, name); |
| throw cont.fillInException(e); |
| } |
| } |
| |
| public NamingEnumeration c_listBindings(Name name, Continuation cont) |
| throws NamingException { |
| cont.setSuccess(); |
| try { |
| DnsName fqdn = fullyQualify(name); |
| NameNode nnode = getNameNode(fqdn); |
| DnsContext ctx = new DnsContext(this, fqdn); |
| return new BindingEnumeration(ctx, nnode.getChildren()); |
| |
| } catch (NamingException e) { |
| cont.setError(this, name); |
| throw cont.fillInException(e); |
| } |
| } |
| |
| public void c_bind(Name name, Object obj, Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public void c_rebind(Name name, Object obj, Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public void c_unbind(Name name, Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public void c_rename(Name oldname, Name newname, Continuation cont) |
| throws NamingException { |
| cont.setError(this, oldname); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public Context c_createSubcontext(Name name, Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public void c_destroySubcontext(Name name, Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public NameParser c_getNameParser(Name name, Continuation cont) |
| throws NamingException { |
| cont.setSuccess(); |
| return nameParser; |
| } |
| |
| |
| //---------- Directory operations |
| |
| public void c_bind(Name name, |
| Object obj, |
| Attributes attrs, |
| Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public void c_rebind(Name name, |
| Object obj, |
| Attributes attrs, |
| Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public DirContext c_createSubcontext(Name name, |
| Attributes attrs, |
| Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public Attributes c_getAttributes(Name name, |
| String[] attrIds, |
| Continuation cont) |
| throws NamingException { |
| |
| cont.setSuccess(); |
| try { |
| DnsName fqdn = fullyQualify(name); |
| CT[] cts = attrIdsToClassesAndTypes(attrIds); |
| CT ct = getClassAndTypeToQuery(cts); |
| ResourceRecords rrs = |
| getResolver().query(fqdn, ct.rrclass, ct.rrtype, |
| recursion, authoritative); |
| return rrsToAttrs(rrs, cts); |
| |
| } catch (NamingException e) { |
| cont.setError(this, name); |
| throw cont.fillInException(e); |
| } |
| } |
| |
| public void c_modifyAttributes(Name name, |
| int mod_op, |
| Attributes attrs, |
| Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public void c_modifyAttributes(Name name, |
| ModificationItem[] mods, |
| Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public NamingEnumeration c_search(Name name, |
| Attributes matchingAttributes, |
| String[] attributesToReturn, |
| Continuation cont) |
| throws NamingException { |
| throw new OperationNotSupportedException(); |
| } |
| |
| public NamingEnumeration c_search(Name name, |
| String filter, |
| SearchControls cons, |
| Continuation cont) |
| throws NamingException { |
| throw new OperationNotSupportedException(); |
| } |
| |
| public NamingEnumeration c_search(Name name, |
| String filterExpr, |
| Object[] filterArgs, |
| SearchControls cons, |
| Continuation cont) |
| throws NamingException { |
| throw new OperationNotSupportedException(); |
| } |
| |
| public DirContext c_getSchema(Name name, Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| public DirContext c_getSchemaClassDefinition(Name name, Continuation cont) |
| throws NamingException { |
| cont.setError(this, name); |
| throw cont.fillInException( |
| new OperationNotSupportedException()); |
| } |
| |
| |
| //---------- Name-related operations |
| |
| public String getNameInNamespace() { |
| return domain.toString(); |
| } |
| |
| public Name composeName(Name name, Name prefix) throws NamingException { |
| Name result; |
| |
| // Any name that's not a CompositeName is assumed to be a DNS |
| // compound name. Convert each to a DnsName for syntax checking. |
| if (!(prefix instanceof DnsName || prefix instanceof CompositeName)) { |
| prefix = (new DnsName()).addAll(prefix); |
| } |
| if (!(name instanceof DnsName || name instanceof CompositeName)) { |
| name = (new DnsName()).addAll(name); |
| } |
| |
| // Each of prefix and name is now either a DnsName or a CompositeName. |
| |
| // If we have two DnsNames, simply join them together. |
| if ((prefix instanceof DnsName) && (name instanceof DnsName)) { |
| result = (DnsName) (prefix.clone()); |
| result.addAll(name); |
| return new CompositeName().add(result.toString()); |
| } |
| |
| // Wrap compound names in composite names. |
| Name prefixC = (prefix instanceof CompositeName) |
| ? prefix |
| : new CompositeName().add(prefix.toString()); |
| Name nameC = (name instanceof CompositeName) |
| ? name |
| : new CompositeName().add(name.toString()); |
| int prefixLast = prefixC.size() - 1; |
| |
| // Let toolkit do the work at namespace boundaries. |
| if (nameC.isEmpty() || nameC.get(0).equals("") || |
| prefixC.isEmpty() || prefixC.get(prefixLast).equals("")) { |
| return super.composeName(nameC, prefixC); |
| } |
| |
| result = (prefix == prefixC) |
| ? (CompositeName) prefixC.clone() |
| : prefixC; // prefixC is already a clone |
| result.addAll(nameC); |
| |
| if (parentIsDns) { |
| DnsName dnsComp = (prefix instanceof DnsName) |
| ? (DnsName) prefix.clone() |
| : new DnsName(prefixC.get(prefixLast)); |
| dnsComp.addAll((name instanceof DnsName) |
| ? name |
| : new DnsName(nameC.get(0))); |
| result.remove(prefixLast + 1); |
| result.remove(prefixLast); |
| result.add(prefixLast, dnsComp.toString()); |
| } |
| return result; |
| } |
| |
| |
| //---------- Helper methods |
| |
| /* |
| * Resolver is not created until needed, to allow time for updates |
| * to the environment. |
| */ |
| private synchronized Resolver getResolver() throws NamingException { |
| if (resolver == null) { |
| resolver = new Resolver(servers, timeout, retries); |
| } |
| return resolver; |
| } |
| |
| /* |
| * Returns the fully-qualified domain name of a name given |
| * relative to this context. Result includes a root label (an |
| * empty component at position 0). |
| */ |
| DnsName fullyQualify(Name name) throws NamingException { |
| if (name.isEmpty()) { |
| return domain; |
| } |
| DnsName dnsName = (name instanceof CompositeName) |
| ? new DnsName(name.get(0)) // parse name |
| : (DnsName) (new DnsName()).addAll(name); // clone & check syntax |
| |
| if (dnsName.hasRootLabel()) { |
| // Be overly generous and allow root label if we're in root domain. |
| if (domain.size() == 1) { |
| return dnsName; |
| } else { |
| throw new InvalidNameException( |
| "DNS name " + dnsName + " not relative to " + domain); |
| } |
| } |
| return (DnsName) dnsName.addAll(0, domain); |
| } |
| |
| /* |
| * Converts resource records to an attribute set. Only resource |
| * records in the answer section are used, and only those that |
| * match the classes and types in cts (see classAndTypeMatch() |
| * for matching rules). |
| */ |
| private static Attributes rrsToAttrs(ResourceRecords rrs, CT[] cts) { |
| |
| BasicAttributes attrs = new BasicAttributes(true); |
| |
| for (int i = 0; i < rrs.answer.size(); i++) { |
| ResourceRecord rr = (ResourceRecord) rrs.answer.elementAt(i); |
| int rrtype = rr.getType(); |
| int rrclass = rr.getRrclass(); |
| |
| if (!classAndTypeMatch(rrclass, rrtype, cts)) { |
| continue; |
| } |
| |
| String attrId = toAttrId(rrclass, rrtype); |
| Attribute attr = attrs.get(attrId); |
| if (attr == null) { |
| attr = new BasicAttribute(attrId); |
| attrs.put(attr); |
| } |
| attr.add(rr.getRdata()); |
| } |
| return attrs; |
| } |
| |
| /* |
| * Returns true if rrclass and rrtype match some element of cts. |
| * A match occurs if corresponding classes and types are equal, |
| * or if the array value is ANY. If cts is null, then any class |
| * and type match. |
| */ |
| private static boolean classAndTypeMatch(int rrclass, int rrtype, |
| CT[] cts) { |
| if (cts == null) { |
| return true; |
| } |
| for (int i = 0; i < cts.length; i++) { |
| CT ct = cts[i]; |
| boolean classMatch = (ct.rrclass == ANY) || |
| (ct.rrclass == rrclass); |
| boolean typeMatch = (ct.rrtype == ANY) || |
| (ct.rrtype == rrtype); |
| if (classMatch && typeMatch) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns the attribute ID for a resource record given its class |
| * and type. If the record is in the internet class, the |
| * corresponding attribute ID is the record's type name (or the |
| * integer type value if the name is not known). If the record is |
| * not in the internet class, the class name (or integer class |
| * value) is prepended to the attribute ID, separated by a space. |
| * |
| * A class or type value of ANY represents an indeterminate class |
| * or type, and is represented within the attribute ID by "*". |
| * For example, the attribute ID "IN *" represents |
| * any type in the internet class, and "* NS" represents an NS |
| * record of any class. |
| */ |
| private static String toAttrId(int rrclass, int rrtype) { |
| String attrId = ResourceRecord.getTypeName(rrtype); |
| if (rrclass != ResourceRecord.CLASS_INTERNET) { |
| attrId = ResourceRecord.getRrclassName(rrclass) + " " + attrId; |
| } |
| return attrId; |
| } |
| |
| /* |
| * Returns the class and type values corresponding to an attribute |
| * ID. An indeterminate class or type is represented by ANY. See |
| * toAttrId() for the format of attribute IDs. |
| * |
| * @throws InvalidAttributeIdentifierException |
| * if class or type is unknown |
| */ |
| private static CT fromAttrId(String attrId) |
| throws InvalidAttributeIdentifierException { |
| |
| if (attrId.equals("")) { |
| throw new InvalidAttributeIdentifierException( |
| "Attribute ID cannot be empty"); |
| } |
| int rrclass; |
| int rrtype; |
| int space = attrId.indexOf(' '); |
| |
| // class |
| if (space < 0) { |
| rrclass = ResourceRecord.CLASS_INTERNET; |
| } else { |
| String className = attrId.substring(0, space); |
| rrclass = ResourceRecord.getRrclass(className); |
| if (rrclass < 0) { |
| throw new InvalidAttributeIdentifierException( |
| "Unknown resource record class '" + className + '\''); |
| } |
| } |
| |
| // type |
| String typeName = attrId.substring(space + 1); |
| rrtype = ResourceRecord.getType(typeName); |
| if (rrtype < 0) { |
| throw new InvalidAttributeIdentifierException( |
| "Unknown resource record type '" + typeName + '\''); |
| } |
| |
| return new CT(rrclass, rrtype); |
| } |
| |
| /* |
| * Returns an array of the classes and types corresponding to a |
| * set of attribute IDs. See toAttrId() for the format of |
| * attribute IDs, and classAndTypeMatch() for the format of the |
| * array returned. |
| */ |
| private static CT[] attrIdsToClassesAndTypes(String[] attrIds) |
| throws InvalidAttributeIdentifierException { |
| if (attrIds == null) { |
| return null; |
| } |
| CT[] cts = new CT[attrIds.length]; |
| |
| for (int i = 0; i < attrIds.length; i++) { |
| cts[i] = fromAttrId(attrIds[i]); |
| } |
| return cts; |
| } |
| |
| /* |
| * Returns the most restrictive resource record class and type |
| * that may be used to query for records matching cts. |
| * See classAndTypeMatch() for matching rules. |
| */ |
| private static CT getClassAndTypeToQuery(CT[] cts) { |
| int rrclass; |
| int rrtype; |
| |
| if (cts == null) { |
| // Query all records. |
| rrclass = ANY; |
| rrtype = ANY; |
| } else if (cts.length == 0) { |
| // No records are requested, but we need to ask for something. |
| rrclass = ResourceRecord.CLASS_INTERNET; |
| rrtype = ANY; |
| } else { |
| rrclass = cts[0].rrclass; |
| rrtype = cts[0].rrtype; |
| for (int i = 1; i < cts.length; i++) { |
| if (rrclass != cts[i].rrclass) { |
| rrclass = ANY; |
| } |
| if (rrtype != cts[i].rrtype) { |
| rrtype = ANY; |
| } |
| } |
| } |
| return new CT(rrclass, rrtype); |
| } |
| |
| |
| //---------- Support for list operations |
| |
| /* |
| * Synchronization notes: |
| * |
| * Any access to zoneTree that walks the tree, whether it modifies |
| * the tree or not, is synchronized on zoneTree. |
| * [%%% Note: a read/write lock would allow increased concurrency.] |
| * The depth of a ZoneNode can thereafter be accessed without |
| * further synchronization. Access to other fields and methods |
| * should be synchronized on the node itself. |
| * |
| * A zone's contents is a NameNode tree that, once created, is never |
| * modified. The only synchronization needed is to ensure that it |
| * gets flushed into shared memory after being created, which is |
| * accomplished by ZoneNode.populate(). The contents are accessed |
| * via a soft reference, so a ZoneNode may be seen to be populated |
| * one moment and unpopulated the next. |
| */ |
| |
| /* |
| * Returns the node in the zone tree corresponding to a |
| * fully-qualified domain name. If the desired portion of the |
| * tree has not yet been populated or has been outdated, a zone |
| * transfer is done to populate the tree. |
| */ |
| private NameNode getNameNode(DnsName fqdn) throws NamingException { |
| dprint("getNameNode(" + fqdn + ")"); |
| |
| // Find deepest related zone in zone tree. |
| ZoneNode znode; |
| DnsName zone; |
| synchronized (zoneTree) { |
| znode = zoneTree.getDeepestPopulated(fqdn); |
| } |
| dprint("Deepest related zone in zone tree: " + |
| ((znode != null) ? znode.getLabel() : "[none]")); |
| |
| NameNode topOfZone; |
| NameNode nnode; |
| |
| if (znode != null) { |
| synchronized (znode) { |
| topOfZone = znode.getContents(); |
| } |
| // If fqdn is in znode's zone, is not at a zone cut, and |
| // is current, we're done. |
| if (topOfZone != null) { |
| nnode = topOfZone.get(fqdn, znode.depth() + 1); // +1 for root |
| |
| if ((nnode != null) && !nnode.isZoneCut()) { |
| dprint("Found node " + fqdn + " in zone tree"); |
| zone = (DnsName) |
| fqdn.getPrefix(znode.depth() + 1); // +1 for root |
| boolean current = isZoneCurrent(znode, zone); |
| boolean restart = false; |
| |
| synchronized (znode) { |
| if (topOfZone != znode.getContents()) { |
| // Zone was modified while we were examining it. |
| // All bets are off. |
| restart = true; |
| } else if (!current) { |
| znode.depopulate(); |
| } else { |
| return nnode; // cache hit! |
| } |
| } |
| dprint("Zone not current; discarding node"); |
| if (restart) { |
| return getNameNode(fqdn); |
| } |
| } |
| } |
| } |
| |
| // Cache miss... do it the expensive way. |
| dprint("Adding node " + fqdn + " to zone tree"); |
| |
| // Find fqdn's zone and add it to the tree. |
| zone = getResolver().findZoneName(fqdn, ResourceRecord.CLASS_INTERNET, |
| recursion); |
| dprint("Node's zone is " + zone); |
| synchronized (zoneTree) { |
| znode = (ZoneNode) zoneTree.add(zone, 1); // "1" to skip root |
| } |
| |
| // If znode is now populated we know -- because the first half of |
| // getNodeName() didn't find it -- that it was populated by another |
| // thread during this method call. Assume then that it's current. |
| |
| synchronized (znode) { |
| topOfZone = znode.isPopulated() |
| ? znode.getContents() |
| : populateZone(znode, zone); |
| } |
| // Desired node should now be in znode's populated zone. Find it. |
| nnode = topOfZone.get(fqdn, zone.size()); |
| if (nnode == null) { |
| throw new ConfigurationException( |
| "DNS error: node not found in its own zone"); |
| } |
| dprint("Found node in newly-populated zone"); |
| return nnode; |
| } |
| |
| /* |
| * Does a zone transfer to [re]populate a zone in the zone tree. |
| * Returns the zone's new contents. |
| */ |
| private NameNode populateZone(ZoneNode znode, DnsName zone) |
| throws NamingException { |
| dprint("Populating zone " + zone); |
| // assert Thread.holdsLock(znode); |
| ResourceRecords rrs = |
| getResolver().queryZone(zone, |
| ResourceRecord.CLASS_INTERNET, recursion); |
| dprint("zone xfer complete: " + rrs.answer.size() + " records"); |
| return znode.populate(zone, rrs); |
| } |
| |
| /* |
| * Determine if a ZoneNode's data is current. |
| * We base this on a comparison between the cached serial |
| * number and the latest SOA record. |
| * |
| * If there is no SOA record, znode is not (or is no longer) a zone: |
| * depopulate znode and return false. |
| * |
| * Since this method may perform a network operation, it is best |
| * to call it with znode unlocked. Caller must then note that the |
| * result may be outdated by the time this method returns. |
| */ |
| private boolean isZoneCurrent(ZoneNode znode, DnsName zone) |
| throws NamingException { |
| // former version: return !znode.isExpired(); |
| |
| if (!znode.isPopulated()) { |
| return false; |
| } |
| ResourceRecord soa = |
| getResolver().findSoa(zone, ResourceRecord.CLASS_INTERNET, |
| recursion); |
| synchronized (znode) { |
| if (soa == null) { |
| znode.depopulate(); |
| } |
| return (znode.isPopulated() && |
| znode.compareSerialNumberTo(soa) >= 0); |
| } |
| } |
| |
| |
| //---------- Debugging |
| |
| private static final boolean debug = false; |
| |
| private static final void dprint(String msg) { |
| if (debug) { |
| System.err.println("** " + msg); |
| } |
| } |
| } |
| |
| |
| //---------- |
| |
| /* |
| * A pairing of a resource record class and a resource record type. |
| * A value of ANY in either field represents an indeterminate value. |
| */ |
| class CT { |
| int rrclass; |
| int rrtype; |
| |
| CT(int rrclass, int rrtype) { |
| this.rrclass = rrclass; |
| this.rrtype = rrtype; |
| } |
| } |
| |
| |
| //---------- |
| |
| /* |
| * An enumeration of name/classname pairs. |
| * |
| * Nodes that have children or that are zone cuts are returned with |
| * classname DirContext. Other nodes are returned with classname |
| * Object even though they are DirContexts as well, since this might |
| * make the namespace easier to browse. |
| */ |
| class NameClassPairEnumeration implements NamingEnumeration { |
| |
| protected Enumeration nodes; // nodes to be enumerated, or null if none |
| protected DnsContext ctx; // context being enumerated |
| |
| NameClassPairEnumeration(DnsContext ctx, Hashtable nodes) { |
| this.ctx = ctx; |
| this.nodes = (nodes != null) |
| ? nodes.elements() |
| : null; |
| } |
| |
| /* |
| * ctx will be set to null when no longer needed by the enumeration. |
| */ |
| public void close() { |
| nodes = null; |
| ctx = null; |
| } |
| |
| public boolean hasMore() { |
| boolean more = ((nodes != null) && nodes.hasMoreElements()); |
| if (!more) { |
| close(); |
| } |
| return more; |
| } |
| |
| public Object next() throws NamingException { |
| if (!hasMore()) { |
| throw new java.util.NoSuchElementException(); |
| } |
| NameNode nnode = (NameNode) nodes.nextElement(); |
| String className = (nnode.isZoneCut() || |
| (nnode.getChildren() != null)) |
| ? "javax.naming.directory.DirContext" |
| : "java.lang.Object"; |
| |
| String label = nnode.getLabel(); |
| Name compName = (new DnsName()).add(label); |
| Name cname = (new CompositeName()).add(compName.toString()); |
| |
| NameClassPair ncp = new NameClassPair(cname.toString(), className); |
| ncp.setNameInNamespace(ctx.fullyQualify(cname).toString()); |
| return ncp; |
| } |
| |
| public boolean hasMoreElements() { |
| return hasMore(); |
| } |
| |
| public Object nextElement() { |
| try { |
| return next(); |
| } catch (NamingException e) { |
| throw (new java.util.NoSuchElementException( |
| "javax.naming.NamingException was thrown: " + |
| e.getMessage())); |
| } |
| } |
| } |
| |
| /* |
| * An enumeration of Bindings. |
| */ |
| class BindingEnumeration extends NameClassPairEnumeration { |
| |
| BindingEnumeration(DnsContext ctx, Hashtable nodes) { |
| super(ctx, nodes); |
| } |
| |
| // Finalizer not needed since it's safe to leave ctx unclosed. |
| // protected void finalize() { |
| // close(); |
| // } |
| |
| public Object next() throws NamingException { |
| if (!hasMore()) { |
| throw (new java.util.NoSuchElementException()); |
| } |
| NameNode nnode = (NameNode) nodes.nextElement(); |
| |
| String label = nnode.getLabel(); |
| Name compName = (new DnsName()).add(label); |
| String compNameStr = compName.toString(); |
| Name cname = (new CompositeName()).add(compNameStr); |
| String cnameStr = cname.toString(); |
| |
| DnsName fqdn = ctx.fullyQualify(compName); |
| |
| // Clone ctx to create the child context. |
| DnsContext child = new DnsContext(ctx, fqdn); |
| |
| try { |
| Object obj = DirectoryManager.getObjectInstance( |
| child, cname, ctx, child.environment, null); |
| Binding binding = new Binding(cnameStr, obj); |
| binding.setNameInNamespace(ctx.fullyQualify(cname).toString()); |
| return binding; |
| } catch (Exception e) { |
| NamingException ne = new NamingException( |
| "Problem generating object using object factory"); |
| ne.setRootCause(e); |
| throw ne; |
| } |
| } |
| } |