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