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")
DnsContext(String domain, String[] servers, Hashtable<?,?> environment)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      */
DnsContext(DnsContext ctx, DnsName domain)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 <tt>environment</tt>, but it's copy-on-write so there's
129      * no conflict.
130      */
DnsContext(DnsContext ctx)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 
close()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      */
p_getEnvironment()158     protected Hashtable<?,?> p_getEnvironment() {
159         return environment;
160     }
161 
getEnvironment()162     public Hashtable<?,?> getEnvironment() throws NamingException {
163         return (Hashtable<?,?>) environment.clone();
164     }
165 
166     @SuppressWarnings("unchecked")
addToEnvironment(String propName, Object propVal)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")
removeFromEnvironment(String propName)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      */
setProviderUrl(String url)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      */
initFromEnvironment()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 
getLookupCT(String attrId)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 
c_lookup(Name name, Continuation cont)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 
c_lookupLink(Name name, Continuation cont)308     public Object c_lookupLink(Name name, Continuation cont)
309             throws NamingException {
310         return c_lookup(name, cont);
311     }
312 
c_list(Name name, Continuation cont)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 
c_listBindings(Name name, Continuation cont)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 
c_bind(Name name, Object obj, Continuation cont)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 
c_rebind(Name name, Object obj, Continuation cont)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 
c_unbind(Name name, Continuation cont)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 
c_rename(Name oldname, Name newname, Continuation cont)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 
c_createSubcontext(Name name, Continuation cont)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 
c_destroySubcontext(Name name, Continuation cont)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 
c_getNameParser(Name name, Continuation cont)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 
c_bind(Name name, Object obj, Attributes attrs, Continuation cont)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 
c_rebind(Name name, Object obj, Attributes attrs, Continuation cont)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 
c_createSubcontext(Name name, Attributes attrs, Continuation cont)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 
c_getAttributes(Name name, String[] attrIds, Continuation cont)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 
c_modifyAttributes(Name name, int mod_op, Attributes attrs, Continuation cont)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 
c_modifyAttributes(Name name, ModificationItem[] mods, Continuation cont)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 
c_search(Name name, Attributes matchingAttributes, String[] attributesToReturn, Continuation cont)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 
c_search(Name name, String filter, SearchControls cons, Continuation cont)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 
c_search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons, Continuation cont)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 
c_getSchema(Name name, Continuation cont)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 
c_getSchemaClassDefinition(Name name, Continuation cont)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 
getNameInNamespace()505     public String getNameInNamespace() {
506         return domain.toString();
507     }
508 
composeName(Name name, Name prefix)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      */
getResolver()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      */
fullyQualify(Name name)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      */
rrsToAttrs(ResourceRecords rrs, CT[] cts)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      */
classAndTypeMatch(int rrclass, int rrtype, CT[] cts)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      */
toAttrId(int rrclass, int rrtype)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      */
fromAttrId(String attrId)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      */
attrIdsToClassesAndTypes(String[] attrIds)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      */
getClassAndTypeToQuery(CT[] cts)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      */
getNameNode(DnsName fqdn)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      */
populateZone(ZoneNode znode, DnsName zone)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      */
isZoneCurrent(ZoneNode znode, DnsName zone)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 
dprint(String msg)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 
CT(int rrclass, int rrtype)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 
BaseNameClassPairEnumeration(DnsContext ctx, Hashtable<String,NameNode> nodes)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      */
close()975     public final void close() {
976         nodes = null;
977         ctx = null;
978     }
979 
hasMore()980     public final boolean hasMore() {
981         boolean more = ((nodes != null) && nodes.hasMoreElements());
982         if (!more) {
983             close();
984         }
985         return more;
986     }
987 
hasMoreElements()988     public final boolean hasMoreElements() {
989         return hasMore();
990     }
991 
next()992     abstract public T next() throws NamingException;
993 
nextElement()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 
NameClassPairEnumeration(DnsContext ctx, Hashtable<String,NameNode> nodes)1018     NameClassPairEnumeration(DnsContext ctx, Hashtable<String,NameNode> nodes) {
1019         super(ctx, nodes);
1020     }
1021 
1022     @Override
next()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 
BindingEnumeration(DnsContext ctx, Hashtable<String,NameNode> nodes)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
next()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 }
1090