J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 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 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 | */ |
| 941 | class 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 | */ |
| 962 | class 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 | */ |
| 1030 | class 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 | } |