1 /* 2 * Copyright (c) 1999, 2011, 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.ldap; 27 28 import javax.naming.*; 29 import javax.naming.directory.*; 30 import javax.naming.spi.*; 31 import javax.naming.event.*; 32 import javax.naming.ldap.*; 33 import javax.naming.ldap.LdapName; 34 import javax.naming.ldap.Rdn; 35 36 import java.util.Vector; 37 import java.util.Hashtable; 38 import java.util.List; 39 import java.util.StringTokenizer; 40 import java.util.Enumeration; 41 42 import java.io.IOException; 43 import java.io.OutputStream; 44 45 import com.sun.jndi.toolkit.ctx.*; 46 import com.sun.jndi.toolkit.dir.HierMemDirCtx; 47 import com.sun.jndi.toolkit.dir.SearchFilter; 48 import com.sun.jndi.ldap.ext.StartTlsResponseImpl; 49 50 /** 51 * The LDAP context implementation. 52 * 53 * Implementation is not thread-safe. Caller must sync as per JNDI spec. 54 * Members that are used directly or indirectly by internal worker threads 55 * (Connection, EventQueue, NamingEventNotifier) must be thread-safe. 56 * Connection - calls LdapClient.processUnsolicited(), which in turn calls 57 * LdapCtx.convertControls() and LdapCtx.fireUnsolicited(). 58 * convertControls() - no sync; reads envprops and 'this' 59 * fireUnsolicited() - sync on eventSupport for all references to 'unsolicited' 60 * (even those in other methods); don't sync on LdapCtx in case caller 61 * is already sync'ing on it - this would prevent Unsol events from firing 62 * and the Connection thread to block (thus preventing any other data 63 * from being read from the connection) 64 * References to 'eventSupport' need not be sync'ed because these 65 * methods can only be called after eventSupport has been set first 66 * (via addNamingListener()). 67 * EventQueue - no direct or indirect calls to LdapCtx 68 * NamingEventNotifier - calls newInstance() to get instance for run() to use; 69 * no sync needed for methods invoked on new instance; 70 * 71 * LdapAttribute links to LdapCtx in order to process getAttributeDefinition() 72 * and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(), 73 * which uses schemaTrees (a Hashtable - already sync). Potential conflict 74 * of duplicating construction of tree for same subschemasubentry 75 * but no inconsistency problems. 76 * 77 * NamingEnumerations link to LdapCtx for the following: 78 * 1. increment/decrement enum count so that ctx doesn't close the 79 * underlying connection 80 * 2. LdapClient handle to get next batch of results 81 * 3. Sets LdapCtx's response controls 82 * 4. Process return code 83 * 5. For narrowing response controls (using ctx's factories) 84 * Since processing of NamingEnumeration by client is treated the same as method 85 * invocation on LdapCtx, caller is responsible for locking. 86 * 87 * @author Vincent Ryan 88 * @author Rosanna Lee 89 */ 90 91 final public class LdapCtx extends ComponentDirContext 92 implements EventDirContext, LdapContext { 93 94 /* 95 * Used to store arguments to the search method. 96 */ 97 final static class SearchArgs { 98 Name name; 99 String filter; 100 SearchControls cons; 101 String[] reqAttrs; // those attributes originally requested 102 103 SearchArgs(Name name, String filter, SearchControls cons, String[] ra) { 104 this.name = name; 105 this.filter = filter; 106 this.cons = cons; 107 this.reqAttrs = ra; 108 } 109 } 110 111 private static final boolean debug = false; 112 113 private static final boolean HARD_CLOSE = true; 114 private static final boolean SOFT_CLOSE = false; 115 116 // ----------------- Constants ----------------- 117 118 public static final int DEFAULT_PORT = 389; 119 public static final int DEFAULT_SSL_PORT = 636; 120 public static final String DEFAULT_HOST = "localhost"; 121 122 private static final boolean DEFAULT_DELETE_RDN = true; 123 private static final boolean DEFAULT_TYPES_ONLY = false; 124 private static final int DEFAULT_DEREF_ALIASES = 3; // always deref 125 private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2; 126 private static final int DEFAULT_BATCH_SIZE = 1; 127 private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE; 128 private static final char DEFAULT_REF_SEPARATOR = '#'; 129 130 // Used by LdapPoolManager 131 static final String DEFAULT_SSL_FACTORY = 132 "javax.net.ssl.SSLSocketFactory"; // use Sun's SSL 133 private static final int DEFAULT_REFERRAL_LIMIT = 10; 134 private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037"; 135 136 // schema operational and user attributes 137 private static final String[] SCHEMA_ATTRIBUTES = 138 { "objectClasses", "attributeTypes", "matchingRules", "ldapSyntaxes" }; 139 140 // --------------- Environment property names ---------- 141 142 // LDAP protocol version: "2", "3" 143 private static final String VERSION = "java.naming.ldap.version"; 144 145 // Binary-valued attributes. Space separated string of attribute names. 146 private static final String BINARY_ATTRIBUTES = 147 "java.naming.ldap.attributes.binary"; 148 149 // Delete old RDN during modifyDN: "true", "false" 150 private static final String DELETE_RDN = "java.naming.ldap.deleteRDN"; 151 152 // De-reference aliases: "never", "searching", "finding", "always" 153 private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases"; 154 155 // Return only attribute types (no values) 156 private static final String TYPES_ONLY = "java.naming.ldap.typesOnly"; 157 158 // Separator character for encoding Reference's RefAddrs; default is '#' 159 private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator"; 160 161 // Socket factory 162 private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket"; 163 164 // Bind Controls (used by LdapReferralException) 165 static final String BIND_CONTROLS = "java.naming.ldap.control.connect"; 166 167 private static final String REFERRAL_LIMIT = 168 "java.naming.ldap.referral.limit"; 169 170 // trace BER (java.io.OutputStream) 171 private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber"; 172 173 // Get around Netscape Schema Bugs 174 private static final String NETSCAPE_SCHEMA_BUG = 175 "com.sun.jndi.ldap.netscape.schemaBugs"; 176 // deprecated 177 private static final String OLD_NETSCAPE_SCHEMA_BUG = 178 "com.sun.naming.netscape.schemaBugs"; // for backward compatability 179 180 // Timeout for socket connect 181 private static final String CONNECT_TIMEOUT = 182 "com.sun.jndi.ldap.connect.timeout"; 183 184 // Timeout for reading responses 185 private static final String READ_TIMEOUT = 186 "com.sun.jndi.ldap.read.timeout"; 187 188 // Environment property for connection pooling 189 private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool"; 190 191 // Environment property for the domain name (derived from this context's DN) 192 private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname"; 193 194 // Block until the first search reply is received 195 private static final String WAIT_FOR_REPLY = 196 "com.sun.jndi.ldap.search.waitForReply"; 197 198 // Size of the queue of unprocessed search replies 199 private static final String REPLY_QUEUE_SIZE = 200 "com.sun.jndi.ldap.search.replyQueueSize"; 201 202 // ----------------- Fields that don't change ----------------------- 203 private static final NameParser parser = new LdapNameParser(); 204 205 // controls that Provider needs 206 private static final ControlFactory myResponseControlFactory = 207 new DefaultResponseControlFactory(); 208 private static final Control manageReferralControl = 209 new ManageReferralControl(false); 210 211 private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx(); 212 static { 213 EMPTY_SCHEMA.setReadOnly( 214 new SchemaViolationException("Cannot update schema object")); 215 } 216 217 // ------------ Package private instance variables ---------------- 218 // Cannot be private; used by enums 219 220 // ------- Inherited by derived context instances 221 222 int port_number; // port number of server 223 String hostname = null; // host name of server (no brackets 224 // for IPv6 literals) 225 LdapClient clnt = null; // connection handle 226 Hashtable envprops = null; // environment properties of context 227 int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled 228 boolean hasLdapsScheme = false; // true if the context was created 229 // using an LDAPS URL. 230 231 // ------- Not inherited by derived context instances 232 233 String currentDN; // DN of this context 234 Name currentParsedDN; // DN of this context 235 Vector respCtls = null; // Response controls read 236 Control[] reqCtls = null; // Controls to be sent with each request 237 238 239 // ------------- Private instance variables ------------------------ 240 241 // ------- Inherited by derived context instances 242 243 private OutputStream trace = null; // output stream for BER debug output 244 private boolean netscapeSchemaBug = false; // workaround 245 private Control[] bindCtls = null; // Controls to be sent with LDAP "bind" 246 private int referralHopLimit = DEFAULT_REFERRAL_LIMIT; // max referral 247 private Hashtable schemaTrees = null; // schema root of this context 248 private int batchSize = DEFAULT_BATCH_SIZE; // batch size for search results 249 private boolean deleteRDN = DEFAULT_DELETE_RDN; // delete the old RDN when modifying DN 250 private boolean typesOnly = DEFAULT_TYPES_ONLY; // return attribute types (no values) 251 private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching 252 private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR; // encoding RefAddr 253 254 private Hashtable binaryAttrs = null; // attr values returned as byte[] 255 private int connectTimeout = -1; // no timeout value 256 private int readTimeout = -1; // no timeout value 257 private boolean waitForReply = true; // wait for search response 258 private int replyQueueSize = -1; // unlimited queue size 259 private boolean useSsl = false; // true if SSL protocol is active 260 private boolean useDefaultPortNumber = false; // no port number was supplied 261 262 // ------- Not inherited by derived context instances 263 264 // True if this context was created by another LdapCtx. 265 private boolean parentIsLdapCtx = false; // see composeName() 266 267 private int hopCount = 1; // current referral hop count 268 private String url = null; // URL of context; see getURL() 269 private EventSupport eventSupport; // Event support helper for this ctx 270 private boolean unsolicited = false; // if there unsolicited listeners 271 private boolean sharable = true; // can share connection with other ctx 272 273 // -------------- Constructors ----------------------------------- 274 275 public LdapCtx(String dn, String host, int port_number, Hashtable props, 276 boolean useSsl) throws NamingException { 277 278 this.useSsl = this.hasLdapsScheme = useSsl; 279 280 if (props != null) { 281 envprops = (Hashtable) props.clone(); 282 283 // SSL env prop overrides the useSsl argument 284 if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) { 285 this.useSsl = true; 286 } 287 288 // %%% These are only examined when the context is created 289 // %%% because they are only for debugging or workaround purposes. 290 trace = (OutputStream)envprops.get(TRACE_BER); 291 292 if (props.get(NETSCAPE_SCHEMA_BUG) != null || 293 props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) { 294 netscapeSchemaBug = true; 295 } 296 } 297 298 currentDN = (dn != null) ? dn : ""; 299 currentParsedDN = parser.parse(currentDN); 300 301 hostname = (host != null && host.length() > 0) ? host : DEFAULT_HOST; 302 if (hostname.charAt(0) == '[') { 303 hostname = hostname.substring(1, hostname.length() - 1); 304 } 305 306 if (port_number > 0) { 307 this.port_number = port_number; 308 } else { 309 this.port_number = this.useSsl ? DEFAULT_SSL_PORT : DEFAULT_PORT; 310 this.useDefaultPortNumber = true; 311 } 312 313 schemaTrees = new Hashtable(11, 0.75f); 314 initEnv(); 315 try { 316 connect(false); 317 } catch (NamingException e) { 318 try { 319 close(); 320 } catch (Exception e2) { 321 // Nothing 322 } 323 throw e; 324 } 325 } 326 327 LdapCtx(LdapCtx existing, String newDN) throws NamingException { 328 useSsl = existing.useSsl; 329 hasLdapsScheme = existing.hasLdapsScheme; 330 useDefaultPortNumber = existing.useDefaultPortNumber; 331 332 hostname = existing.hostname; 333 port_number = existing.port_number; 334 currentDN = newDN; 335 if (existing.currentDN == currentDN) { 336 currentParsedDN = existing.currentParsedDN; 337 } else { 338 currentParsedDN = parser.parse(currentDN); 339 } 340 341 envprops = existing.envprops; 342 schemaTrees = existing.schemaTrees; 343 344 clnt = existing.clnt; 345 clnt.incRefCount(); 346 347 parentIsLdapCtx = ((newDN == null || newDN.equals(existing.currentDN)) 348 ? existing.parentIsLdapCtx 349 : true); 350 351 // inherit these debugging/workaround flags 352 trace = existing.trace; 353 netscapeSchemaBug = existing.netscapeSchemaBug; 354 355 initEnv(); 356 } 357 358 public LdapContext newInstance(Control[] reqCtls) throws NamingException { 359 360 LdapContext clone = new LdapCtx(this, currentDN); 361 362 // Connection controls are inherited from environment 363 364 // Set clone's request controls 365 // setRequestControls() will clone reqCtls 366 clone.setRequestControls(reqCtls); 367 return clone; 368 } 369 370 // --------------- Namespace Updates --------------------- 371 // -- bind/rebind/unbind 372 // -- rename 373 // -- createSubcontext/destroySubcontext 374 375 protected void c_bind(Name name, Object obj, Continuation cont) 376 throws NamingException { 377 c_bind(name, obj, null, cont); 378 } 379 380 /* 381 * attrs == null 382 * if obj is DirContext, attrs = obj.getAttributes() 383 * if attrs == null && obj == null 384 * disallow (cannot determine objectclass to use) 385 * if obj == null 386 * just create entry using attrs 387 * else 388 * objAttrs = create attributes for representing obj 389 * attrs += objAttrs 390 * create entry using attrs 391 */ 392 protected void c_bind(Name name, Object obj, Attributes attrs, 393 Continuation cont) 394 throws NamingException { 395 396 cont.setError(this, name); 397 398 Attributes inputAttrs = attrs; // Attributes supplied by caller 399 try { 400 ensureOpen(); 401 402 if (obj == null) { 403 if (attrs == null) { 404 throw new IllegalArgumentException( 405 "cannot bind null object with no attributes"); 406 } 407 } else { 408 attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs, 409 false, name, this, envprops); // not cloned 410 } 411 412 String newDN = fullyQualifiedName(name); 413 attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs); 414 LdapEntry entry = new LdapEntry(newDN, attrs); 415 416 LdapResult answer = clnt.add(entry, reqCtls); 417 respCtls = answer.resControls; // retrieve response controls 418 419 if (answer.status != LdapClient.LDAP_SUCCESS) { 420 processReturnCode(answer, name); 421 } 422 423 } catch (LdapReferralException e) { 424 if (handleReferrals == LdapClient.LDAP_REF_THROW) 425 throw cont.fillInException(e); 426 427 // process the referrals sequentially 428 while (true) { 429 430 LdapReferralContext refCtx = 431 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 432 433 // repeat the original operation at the new context 434 try { 435 436 refCtx.bind(name, obj, inputAttrs); 437 return; 438 439 } catch (LdapReferralException re) { 440 e = re; 441 continue; 442 443 } finally { 444 // Make sure we close referral context 445 refCtx.close(); 446 } 447 } 448 449 } catch (IOException e) { 450 NamingException e2 = new CommunicationException(e.getMessage()); 451 e2.setRootCause(e); 452 throw cont.fillInException(e2); 453 454 } catch (NamingException e) { 455 throw cont.fillInException(e); 456 } 457 } 458 459 protected void c_rebind(Name name, Object obj, Continuation cont) 460 throws NamingException { 461 c_rebind(name, obj, null, cont); 462 } 463 464 465 /* 466 * attrs == null 467 * if obj is DirContext, attrs = obj.getAttributes(). 468 * if attrs == null 469 * leave any existing attributes alone 470 * (set attrs = {objectclass=top} if object doesn't exist) 471 * else 472 * replace all existing attributes with attrs 473 * if obj == null 474 * just create entry using attrs 475 * else 476 * objAttrs = create attributes for representing obj 477 * attrs += objAttrs 478 * create entry using attrs 479 */ 480 protected void c_rebind(Name name, Object obj, Attributes attrs, 481 Continuation cont) throws NamingException { 482 483 cont.setError(this, name); 484 485 Attributes inputAttrs = attrs; 486 487 try { 488 Attributes origAttrs = null; 489 490 // Check if name is bound 491 try { 492 origAttrs = c_getAttributes(name, null, cont); 493 } catch (NameNotFoundException e) {} 494 495 // Name not bound, just add it 496 if (origAttrs == null) { 497 c_bind(name, obj, attrs, cont); 498 return; 499 } 500 501 // there's an object there already, need to figure out 502 // what to do about its attributes 503 504 if (attrs == null && obj instanceof DirContext) { 505 attrs = ((DirContext)obj).getAttributes(""); 506 } 507 Attributes keepAttrs = (Attributes)origAttrs.clone(); 508 509 if (attrs == null) { 510 // we're not changing any attrs, leave old attributes alone 511 512 // Remove Java-related object classes from objectclass attribute 513 Attribute origObjectClass = 514 origAttrs.get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]); 515 516 if (origObjectClass != null) { 517 // clone so that keepAttrs is not affected 518 origObjectClass = (Attribute)origObjectClass.clone(); 519 for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) { 520 origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]); 521 origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES[i]); 522 } 523 // update; 524 origAttrs.put(origObjectClass); 525 } 526 527 // remove all Java-related attributes except objectclass 528 for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) { 529 origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]); 530 } 531 532 attrs = origAttrs; 533 } 534 if (obj != null) { 535 attrs = 536 Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs, 537 inputAttrs != attrs, name, this, envprops); 538 } 539 540 String newDN = fullyQualifiedName(name); 541 // remove entry 542 LdapResult answer = clnt.delete(newDN, reqCtls); 543 respCtls = answer.resControls; // retrieve response controls 544 545 if (answer.status != LdapClient.LDAP_SUCCESS) { 546 processReturnCode(answer, name); 547 return; 548 } 549 550 Exception addEx = null; 551 try { 552 attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs); 553 554 // add it back using updated attrs 555 LdapEntry entry = new LdapEntry(newDN, attrs); 556 answer = clnt.add(entry, reqCtls); 557 if (answer.resControls != null) { 558 respCtls = appendVector(respCtls, answer.resControls); 559 } 560 } catch (NamingException ae) { 561 addEx = ae; 562 } catch (IOException ae) { 563 addEx = ae; 564 } 565 566 if ((addEx != null && !(addEx instanceof LdapReferralException)) || 567 answer.status != LdapClient.LDAP_SUCCESS) { 568 // Attempt to restore old entry 569 LdapResult answer2 = 570 clnt.add(new LdapEntry(newDN, keepAttrs), reqCtls); 571 if (answer2.resControls != null) { 572 respCtls = appendVector(respCtls, answer2.resControls); 573 } 574 575 if (addEx == null) { 576 processReturnCode(answer, name); 577 } 578 } 579 580 // Rethrow exception 581 if (addEx instanceof NamingException) { 582 throw (NamingException)addEx; 583 } else if (addEx instanceof IOException) { 584 throw (IOException)addEx; 585 } 586 587 } catch (LdapReferralException e) { 588 if (handleReferrals == LdapClient.LDAP_REF_THROW) 589 throw cont.fillInException(e); 590 591 // process the referrals sequentially 592 while (true) { 593 594 LdapReferralContext refCtx = 595 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 596 597 // repeat the original operation at the new context 598 try { 599 600 refCtx.rebind(name, obj, inputAttrs); 601 return; 602 603 } catch (LdapReferralException re) { 604 e = re; 605 continue; 606 607 } finally { 608 // Make sure we close referral context 609 refCtx.close(); 610 } 611 } 612 613 } catch (IOException e) { 614 NamingException e2 = new CommunicationException(e.getMessage()); 615 e2.setRootCause(e); 616 throw cont.fillInException(e2); 617 618 } catch (NamingException e) { 619 throw cont.fillInException(e); 620 } 621 } 622 623 protected void c_unbind(Name name, Continuation cont) 624 throws NamingException { 625 cont.setError(this, name); 626 627 try { 628 ensureOpen(); 629 630 String fname = fullyQualifiedName(name); 631 LdapResult answer = clnt.delete(fname, reqCtls); 632 respCtls = answer.resControls; // retrieve response controls 633 634 adjustDeleteStatus(fname, answer); 635 636 if (answer.status != LdapClient.LDAP_SUCCESS) { 637 processReturnCode(answer, name); 638 } 639 640 } catch (LdapReferralException e) { 641 if (handleReferrals == LdapClient.LDAP_REF_THROW) 642 throw cont.fillInException(e); 643 644 // process the referrals sequentially 645 while (true) { 646 647 LdapReferralContext refCtx = 648 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 649 650 // repeat the original operation at the new context 651 try { 652 653 refCtx.unbind(name); 654 return; 655 656 } catch (LdapReferralException re) { 657 e = re; 658 continue; 659 660 } finally { 661 // Make sure we close referral context 662 refCtx.close(); 663 } 664 } 665 666 } catch (IOException e) { 667 NamingException e2 = new CommunicationException(e.getMessage()); 668 e2.setRootCause(e); 669 throw cont.fillInException(e2); 670 671 } catch (NamingException e) { 672 throw cont.fillInException(e); 673 } 674 } 675 676 protected void c_rename(Name oldName, Name newName, Continuation cont) 677 throws NamingException 678 { 679 Name oldParsed, newParsed; 680 Name oldParent, newParent; 681 String newRDN = null; 682 String newSuperior = null; 683 684 // assert (oldName instanceOf CompositeName); 685 686 cont.setError(this, oldName); 687 688 try { 689 ensureOpen(); 690 691 // permit oldName to be empty (for processing referral contexts) 692 if (oldName.isEmpty()) { 693 oldParent = parser.parse(""); 694 } else { 695 oldParsed = parser.parse(oldName.get(0)); // extract DN & parse 696 oldParent = oldParsed.getPrefix(oldParsed.size() - 1); 697 } 698 699 if (newName instanceof CompositeName) { 700 newParsed = parser.parse(newName.get(0)); // extract DN & parse 701 } else { 702 newParsed = newName; // CompoundName/LdapName is already parsed 703 } 704 newParent = newParsed.getPrefix(newParsed.size() - 1); 705 706 if(!oldParent.equals(newParent)) { 707 if (!clnt.isLdapv3) { 708 throw new InvalidNameException( 709 "LDAPv2 doesn't support changing " + 710 "the parent as a result of a rename"); 711 } else { 712 newSuperior = fullyQualifiedName(newParent.toString()); 713 } 714 } 715 716 newRDN = newParsed.get(newParsed.size() - 1); 717 718 LdapResult answer = clnt.moddn(fullyQualifiedName(oldName), 719 newRDN, 720 deleteRDN, 721 newSuperior, 722 reqCtls); 723 respCtls = answer.resControls; // retrieve response controls 724 725 if (answer.status != LdapClient.LDAP_SUCCESS) { 726 processReturnCode(answer, oldName); 727 } 728 729 } catch (LdapReferralException e) { 730 731 // Record the new RDN (for use after the referral is followed). 732 e.setNewRdn(newRDN); 733 734 // Cannot continue when a referral has been received and a 735 // newSuperior name was supplied (because the newSuperior is 736 // relative to a naming context BEFORE the referral is followed). 737 if (newSuperior != null) { 738 PartialResultException pre = new PartialResultException( 739 "Cannot continue referral processing when newSuperior is " + 740 "nonempty: " + newSuperior); 741 pre.setRootCause(cont.fillInException(e)); 742 throw cont.fillInException(pre); 743 } 744 745 if (handleReferrals == LdapClient.LDAP_REF_THROW) 746 throw cont.fillInException(e); 747 748 // process the referrals sequentially 749 while (true) { 750 751 LdapReferralContext refCtx = 752 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 753 754 // repeat the original operation at the new context 755 try { 756 757 refCtx.rename(oldName, newName); 758 return; 759 760 } catch (LdapReferralException re) { 761 e = re; 762 continue; 763 764 } finally { 765 // Make sure we close referral context 766 refCtx.close(); 767 } 768 } 769 770 } catch (IOException e) { 771 NamingException e2 = new CommunicationException(e.getMessage()); 772 e2.setRootCause(e); 773 throw cont.fillInException(e2); 774 775 } catch (NamingException e) { 776 throw cont.fillInException(e); 777 } 778 } 779 780 protected Context c_createSubcontext(Name name, Continuation cont) 781 throws NamingException { 782 return c_createSubcontext(name, null, cont); 783 } 784 785 protected DirContext c_createSubcontext(Name name, Attributes attrs, 786 Continuation cont) 787 throws NamingException { 788 cont.setError(this, name); 789 790 Attributes inputAttrs = attrs; 791 try { 792 ensureOpen(); 793 if (attrs == null) { 794 // add structural objectclass; name needs to have "cn" 795 Attribute oc = new BasicAttribute( 796 Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS], 797 Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]); 798 oc.add("top"); 799 attrs = new BasicAttributes(true); // case ignore 800 attrs.put(oc); 801 } 802 String newDN = fullyQualifiedName(name); 803 attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs); 804 805 LdapEntry entry = new LdapEntry(newDN, attrs); 806 807 LdapResult answer = clnt.add(entry, reqCtls); 808 respCtls = answer.resControls; // retrieve response controls 809 810 if (answer.status != LdapClient.LDAP_SUCCESS) { 811 processReturnCode(answer, name); 812 return null; 813 } 814 815 // creation successful, get back live object 816 return new LdapCtx(this, newDN); 817 818 } catch (LdapReferralException e) { 819 if (handleReferrals == LdapClient.LDAP_REF_THROW) 820 throw cont.fillInException(e); 821 822 // process the referrals sequentially 823 while (true) { 824 825 LdapReferralContext refCtx = 826 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 827 828 // repeat the original operation at the new context 829 try { 830 831 return refCtx.createSubcontext(name, inputAttrs); 832 833 } catch (LdapReferralException re) { 834 e = re; 835 continue; 836 837 } finally { 838 // Make sure we close referral context 839 refCtx.close(); 840 } 841 } 842 843 } catch (IOException e) { 844 NamingException e2 = new CommunicationException(e.getMessage()); 845 e2.setRootCause(e); 846 throw cont.fillInException(e2); 847 848 } catch (NamingException e) { 849 throw cont.fillInException(e); 850 } 851 } 852 853 protected void c_destroySubcontext(Name name, Continuation cont) 854 throws NamingException { 855 cont.setError(this, name); 856 857 try { 858 ensureOpen(); 859 860 String fname = fullyQualifiedName(name); 861 LdapResult answer = clnt.delete(fname, reqCtls); 862 respCtls = answer.resControls; // retrieve response controls 863 864 adjustDeleteStatus(fname, answer); 865 866 if (answer.status != LdapClient.LDAP_SUCCESS) { 867 processReturnCode(answer, name); 868 } 869 870 } catch (LdapReferralException e) { 871 if (handleReferrals == LdapClient.LDAP_REF_THROW) 872 throw cont.fillInException(e); 873 874 // process the referrals sequentially 875 while (true) { 876 877 LdapReferralContext refCtx = 878 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 879 880 // repeat the original operation at the new context 881 try { 882 883 refCtx.destroySubcontext(name); 884 return; 885 } catch (LdapReferralException re) { 886 e = re; 887 continue; 888 } finally { 889 // Make sure we close referral context 890 refCtx.close(); 891 } 892 } 893 } catch (IOException e) { 894 NamingException e2 = new CommunicationException(e.getMessage()); 895 e2.setRootCause(e); 896 throw cont.fillInException(e2); 897 } catch (NamingException e) { 898 throw cont.fillInException(e); 899 } 900 } 901 902 /** 903 * Adds attributes from RDN to attrs if not already present. 904 * Note that if attrs already contains an attribute by the same name, 905 * or if the distinguished name is empty, then leave attrs unchanged. 906 * 907 * @param dn The non-null DN of the entry to add 908 * @param attrs The non-null attributes of entry to add 909 * @param directUpdate Whether attrs can be updated directly 910 * @returns Non-null attributes with attributes from the RDN added 911 */ 912 private static Attributes addRdnAttributes(String dn, Attributes attrs, 913 boolean directUpdate) throws NamingException { 914 915 // Handle the empty name 916 if (dn.equals("")) { 917 return attrs; 918 } 919 920 // Parse string name into list of RDNs 921 //List<Rdn> rdnList = (new LdapName(dn)).rdns(); 922 List rdnList = (new LdapName(dn)).getRdns(); 923 924 // Get leaf RDN 925 //Rdn rdn = rdnList.get(rdnList.size() - 1); 926 Rdn rdn = (Rdn) rdnList.get(rdnList.size() - 1); 927 Attributes nameAttrs = rdn.toAttributes(); 928 929 // Add attributes of RDN to attrs if not already there 930 NamingEnumeration enum_ = nameAttrs.getAll(); 931 Attribute nameAttr; 932 while (enum_.hasMore()) { 933 nameAttr = (Attribute) enum_.next(); 934 935 // If attrs already has the attribute, don't change or add to it 936 if (attrs.get(nameAttr.getID()) == null) { 937 938 /** 939 * When attrs.isCaseIgnored() is false, attrs.get() will 940 * return null when the case mis-matches for otherwise 941 * equal attrIDs. 942 * As the attrIDs' case is irrelevant for LDAP, ignore 943 * the case of attrIDs even when attrs.isCaseIgnored() is 944 * false. This is done by explicitly comparing the elements in 945 * the enumeration of IDs with their case ignored. 946 */ 947 if (!attrs.isCaseIgnored() && 948 containsIgnoreCase(attrs.getIDs(), nameAttr.getID())) { 949 continue; 950 } 951 952 if (!directUpdate) { 953 attrs = (Attributes)attrs.clone(); 954 directUpdate = true; 955 } 956 attrs.put(nameAttr); 957 } 958 } 959 960 return attrs; 961 } 962 963 964 private static boolean containsIgnoreCase(NamingEnumeration enumStr, 965 String str) throws NamingException { 966 String strEntry; 967 968 while (enumStr.hasMore()) { 969 strEntry = (String) enumStr.next(); 970 if (strEntry.equalsIgnoreCase(str)) { 971 return true; 972 } 973 } 974 return false; 975 } 976 977 978 private void adjustDeleteStatus(String fname, LdapResult answer) { 979 if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT && 980 answer.matchedDN != null) { 981 try { 982 // %%% RL: are there any implications for referrals? 983 984 Name orig = parser.parse(fname); 985 Name matched = parser.parse(answer.matchedDN); 986 if ((orig.size() - matched.size()) == 1) 987 answer.status = LdapClient.LDAP_SUCCESS; 988 } catch (NamingException e) {} 989 } 990 } 991 992 /* 993 * Append the the second Vector onto the first Vector 994 * (v2 must be non-null) 995 */ 996 private static Vector appendVector(Vector v1, Vector v2) { 997 if (v1 == null) { 998 v1 = v2; 999 } else { 1000 for (int i = 0; i < v2.size(); i++) { 1001 v1.addElement(v2.elementAt(i)); 1002 } 1003 } 1004 return v1; 1005 } 1006 1007 // ------------- Lookups and Browsing ------------------------- 1008 // lookup/lookupLink 1009 // list/listBindings 1010 1011 protected Object c_lookupLink(Name name, Continuation cont) 1012 throws NamingException { 1013 return c_lookup(name, cont); 1014 } 1015 1016 protected Object c_lookup(Name name, Continuation cont) 1017 throws NamingException { 1018 cont.setError(this, name); 1019 Object obj = null; 1020 Attributes attrs; 1021 1022 try { 1023 SearchControls cons = new SearchControls(); 1024 cons.setSearchScope(SearchControls.OBJECT_SCOPE); 1025 cons.setReturningAttributes(null); // ask for all attributes 1026 cons.setReturningObjFlag(true); // need values to construct obj 1027 1028 LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true); 1029 respCtls = answer.resControls; // retrieve response controls 1030 1031 // should get back 1 SearchResponse and 1 SearchResult 1032 1033 if (answer.status != LdapClient.LDAP_SUCCESS) { 1034 processReturnCode(answer, name); 1035 } 1036 1037 if (answer.entries == null || answer.entries.size() != 1) { 1038 // found it but got no attributes 1039 attrs = new BasicAttributes(LdapClient.caseIgnore); 1040 } else { 1041 LdapEntry entry = (LdapEntry)answer.entries.elementAt(0); 1042 attrs = entry.attributes; 1043 1044 Vector entryCtls = entry.respCtls; // retrieve entry controls 1045 if (entryCtls != null) { 1046 appendVector(respCtls, entryCtls); // concatenate controls 1047 } 1048 } 1049 1050 if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) { 1051 // serialized object or object reference 1052 obj = Obj.decodeObject(attrs); 1053 } 1054 if (obj == null) { 1055 obj = new LdapCtx(this, fullyQualifiedName(name)); 1056 } 1057 } catch (LdapReferralException e) { 1058 if (handleReferrals == LdapClient.LDAP_REF_THROW) 1059 throw cont.fillInException(e); 1060 1061 // process the referrals sequentially 1062 while (true) { 1063 1064 LdapReferralContext refCtx = 1065 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 1066 // repeat the original operation at the new context 1067 try { 1068 1069 return refCtx.lookup(name); 1070 1071 } catch (LdapReferralException re) { 1072 e = re; 1073 continue; 1074 1075 } finally { 1076 // Make sure we close referral context 1077 refCtx.close(); 1078 } 1079 } 1080 1081 } catch (NamingException e) { 1082 throw cont.fillInException(e); 1083 } 1084 1085 try { 1086 return DirectoryManager.getObjectInstance(obj, name, 1087 this, envprops, attrs); 1088 1089 } catch (NamingException e) { 1090 throw cont.fillInException(e); 1091 1092 } catch (Exception e) { 1093 NamingException e2 = new NamingException( 1094 "problem generating object using object factory"); 1095 e2.setRootCause(e); 1096 throw cont.fillInException(e2); 1097 } 1098 } 1099 1100 protected NamingEnumeration c_list(Name name, Continuation cont) 1101 throws NamingException { 1102 SearchControls cons = new SearchControls(); 1103 String[] classAttrs = new String[2]; 1104 1105 classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]; 1106 classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]; 1107 cons.setReturningAttributes(classAttrs); 1108 1109 // set this flag to override the typesOnly flag 1110 cons.setReturningObjFlag(true); 1111 1112 cont.setError(this, name); 1113 1114 LdapResult answer = null; 1115 1116 try { 1117 answer = doSearch(name, "(objectClass=*)", cons, true, true); 1118 1119 // list result may contain continuation references 1120 if ((answer.status != LdapClient.LDAP_SUCCESS) || 1121 (answer.referrals != null)) { 1122 processReturnCode(answer, name); 1123 } 1124 1125 return new LdapNamingEnumeration(this, answer, name, cont); 1126 1127 } catch (LdapReferralException e) { 1128 if (handleReferrals == LdapClient.LDAP_REF_THROW) 1129 throw cont.fillInException(e); 1130 1131 // process the referrals sequentially 1132 while (true) { 1133 1134 LdapReferralContext refCtx = 1135 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 1136 1137 // repeat the original operation at the new context 1138 try { 1139 1140 return refCtx.list(name); 1141 1142 } catch (LdapReferralException re) { 1143 e = re; 1144 continue; 1145 1146 } finally { 1147 // Make sure we close referral context 1148 refCtx.close(); 1149 } 1150 } 1151 1152 } catch (LimitExceededException e) { 1153 LdapNamingEnumeration res = 1154 new LdapNamingEnumeration(this, answer, name, cont); 1155 1156 res.setNamingException( 1157 (LimitExceededException)cont.fillInException(e)); 1158 return res; 1159 1160 } catch (PartialResultException e) { 1161 LdapNamingEnumeration res = 1162 new LdapNamingEnumeration(this, answer, name, cont); 1163 1164 res.setNamingException( 1165 (PartialResultException)cont.fillInException(e)); 1166 return res; 1167 1168 } catch (NamingException e) { 1169 throw cont.fillInException(e); 1170 } 1171 } 1172 1173 protected NamingEnumeration c_listBindings(Name name, Continuation cont) 1174 throws NamingException { 1175 1176 SearchControls cons = new SearchControls(); 1177 cons.setReturningAttributes(null); // ask for all attributes 1178 cons.setReturningObjFlag(true); // need values to construct obj 1179 1180 cont.setError(this, name); 1181 1182 LdapResult answer = null; 1183 1184 try { 1185 answer = doSearch(name, "(objectClass=*)", cons, true, true); 1186 1187 // listBindings result may contain continuation references 1188 if ((answer.status != LdapClient.LDAP_SUCCESS) || 1189 (answer.referrals != null)) { 1190 processReturnCode(answer, name); 1191 } 1192 1193 return new LdapBindingEnumeration(this, answer, name, cont); 1194 1195 } catch (LdapReferralException e) { 1196 if (handleReferrals == LdapClient.LDAP_REF_THROW) 1197 throw cont.fillInException(e); 1198 1199 // process the referrals sequentially 1200 while (true) { 1201 1202 LdapReferralContext refCtx = 1203 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 1204 1205 // repeat the original operation at the new context 1206 try { 1207 1208 return refCtx.listBindings(name); 1209 1210 } catch (LdapReferralException re) { 1211 e = re; 1212 continue; 1213 1214 } finally { 1215 // Make sure we close referral context 1216 refCtx.close(); 1217 } 1218 } 1219 } catch (LimitExceededException e) { 1220 LdapBindingEnumeration res = 1221 new LdapBindingEnumeration(this, answer, name, cont); 1222 1223 res.setNamingException( 1224 (LimitExceededException)cont.fillInException(e)); 1225 return res; 1226 1227 } catch (PartialResultException e) { 1228 LdapBindingEnumeration res = 1229 new LdapBindingEnumeration(this, answer, name, cont); 1230 1231 res.setNamingException( 1232 (PartialResultException)cont.fillInException(e)); 1233 return res; 1234 1235 } catch (NamingException e) { 1236 throw cont.fillInException(e); 1237 } 1238 } 1239 1240 // --------------- Name-related Methods ----------------------- 1241 // -- getNameParser/getNameInNamespace/composeName 1242 1243 protected NameParser c_getNameParser(Name name, Continuation cont) 1244 throws NamingException 1245 { 1246 // ignore name, always return same parser 1247 cont.setSuccess(); 1248 return parser; 1249 } 1250 1251 public String getNameInNamespace() { 1252 return currentDN; 1253 } 1254 1255 public Name composeName(Name name, Name prefix) 1256 throws NamingException 1257 { 1258 Name result; 1259 1260 // Handle compound names. A pair of LdapNames is an easy case. 1261 if ((name instanceof LdapName) && (prefix instanceof LdapName)) { 1262 result = (Name)(prefix.clone()); 1263 result.addAll(name); 1264 return new CompositeName().add(result.toString()); 1265 } 1266 if (!(name instanceof CompositeName)) { 1267 name = new CompositeName().add(name.toString()); 1268 } 1269 if (!(prefix instanceof CompositeName)) { 1270 prefix = new CompositeName().add(prefix.toString()); 1271 } 1272 1273 int prefixLast = prefix.size() - 1; 1274 1275 if (name.isEmpty() || prefix.isEmpty() || 1276 name.get(0).equals("") || prefix.get(prefixLast).equals("")) { 1277 return super.composeName(name, prefix); 1278 } 1279 1280 result = (Name)(prefix.clone()); 1281 result.addAll(name); 1282 1283 if (parentIsLdapCtx) { 1284 String ldapComp = concatNames(result.get(prefixLast + 1), 1285 result.get(prefixLast)); 1286 result.remove(prefixLast + 1); 1287 result.remove(prefixLast); 1288 result.add(prefixLast, ldapComp); 1289 } 1290 return result; 1291 } 1292 1293 private String fullyQualifiedName(Name rel) { 1294 return rel.isEmpty() 1295 ? currentDN 1296 : fullyQualifiedName(rel.get(0)); 1297 } 1298 1299 private String fullyQualifiedName(String rel) { 1300 return (concatNames(rel, currentDN)); 1301 } 1302 1303 // used by LdapSearchEnumeration 1304 private static String concatNames(String lesser, String greater) { 1305 if (lesser == null || lesser.equals("")) { 1306 return greater; 1307 } else if (greater == null || greater.equals("")) { 1308 return lesser; 1309 } else { 1310 return (lesser + "," + greater); 1311 } 1312 } 1313 1314 // --------------- Reading and Updating Attributes 1315 // getAttributes/modifyAttributes 1316 1317 protected Attributes c_getAttributes(Name name, String[] attrIds, 1318 Continuation cont) 1319 throws NamingException { 1320 cont.setError(this, name); 1321 1322 SearchControls cons = new SearchControls(); 1323 cons.setSearchScope(SearchControls.OBJECT_SCOPE); 1324 cons.setReturningAttributes(attrIds); 1325 1326 try { 1327 LdapResult answer = 1328 doSearchOnce(name, "(objectClass=*)", cons, true); 1329 respCtls = answer.resControls; // retrieve response controls 1330 1331 if (answer.status != LdapClient.LDAP_SUCCESS) { 1332 processReturnCode(answer, name); 1333 } 1334 1335 if (answer.entries == null || answer.entries.size() != 1) { 1336 return new BasicAttributes(LdapClient.caseIgnore); 1337 } 1338 1339 // get attributes from result 1340 LdapEntry entry = (LdapEntry) answer.entries.elementAt(0); 1341 1342 Vector entryCtls = entry.respCtls; // retrieve entry controls 1343 if (entryCtls != null) { 1344 appendVector(respCtls, entryCtls); // concatenate controls 1345 } 1346 1347 // do this so attributes can find their schema 1348 setParents(entry.attributes, (Name) name.clone()); 1349 1350 return (entry.attributes); 1351 1352 } catch (LdapReferralException e) { 1353 if (handleReferrals == LdapClient.LDAP_REF_THROW) 1354 throw cont.fillInException(e); 1355 1356 // process the referrals sequentially 1357 while (true) { 1358 1359 LdapReferralContext refCtx = 1360 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 1361 1362 // repeat the original operation at the new context 1363 try { 1364 1365 return refCtx.getAttributes(name, attrIds); 1366 1367 } catch (LdapReferralException re) { 1368 e = re; 1369 continue; 1370 1371 } finally { 1372 // Make sure we close referral context 1373 refCtx.close(); 1374 } 1375 } 1376 1377 } catch (NamingException e) { 1378 throw cont.fillInException(e); 1379 } 1380 } 1381 1382 protected void c_modifyAttributes(Name name, int mod_op, Attributes attrs, 1383 Continuation cont) 1384 throws NamingException { 1385 1386 cont.setError(this, name); 1387 1388 try { 1389 ensureOpen(); 1390 1391 if (attrs == null || attrs.size() == 0) { 1392 return; // nothing to do 1393 } 1394 String newDN = fullyQualifiedName(name); 1395 int jmod_op = convertToLdapModCode(mod_op); 1396 1397 // construct mod list 1398 int[] jmods = new int[attrs.size()]; 1399 Attribute[] jattrs = new Attribute[attrs.size()]; 1400 1401 NamingEnumeration ae = attrs.getAll(); 1402 for(int i = 0; i < jmods.length && ae.hasMore(); i++) { 1403 jmods[i] = jmod_op; 1404 jattrs[i] = (Attribute)ae.next(); 1405 } 1406 1407 LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls); 1408 respCtls = answer.resControls; // retrieve response controls 1409 1410 if (answer.status != LdapClient.LDAP_SUCCESS) { 1411 processReturnCode(answer, name); 1412 return; 1413 } 1414 1415 } catch (LdapReferralException e) { 1416 if (handleReferrals == LdapClient.LDAP_REF_THROW) 1417 throw cont.fillInException(e); 1418 1419 // process the referrals sequentially 1420 while (true) { 1421 1422 LdapReferralContext refCtx = 1423 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 1424 1425 // repeat the original operation at the new context 1426 try { 1427 1428 refCtx.modifyAttributes(name, mod_op, attrs); 1429 return; 1430 1431 } catch (LdapReferralException re) { 1432 e = re; 1433 continue; 1434 1435 } finally { 1436 // Make sure we close referral context 1437 refCtx.close(); 1438 } 1439 } 1440 1441 } catch (IOException e) { 1442 NamingException e2 = new CommunicationException(e.getMessage()); 1443 e2.setRootCause(e); 1444 throw cont.fillInException(e2); 1445 1446 } catch (NamingException e) { 1447 throw cont.fillInException(e); 1448 } 1449 } 1450 1451 protected void c_modifyAttributes(Name name, ModificationItem[] mods, 1452 Continuation cont) 1453 throws NamingException { 1454 cont.setError(this, name); 1455 1456 try { 1457 ensureOpen(); 1458 1459 if (mods == null || mods.length == 0) { 1460 return; // nothing to do 1461 } 1462 String newDN = fullyQualifiedName(name); 1463 1464 // construct mod list 1465 int[] jmods = new int[mods.length]; 1466 Attribute[] jattrs = new Attribute[mods.length]; 1467 ModificationItem mod; 1468 for (int i = 0; i < jmods.length; i++) { 1469 mod = mods[i]; 1470 jmods[i] = convertToLdapModCode(mod.getModificationOp()); 1471 jattrs[i] = mod.getAttribute(); 1472 } 1473 1474 LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls); 1475 respCtls = answer.resControls; // retrieve response controls 1476 1477 if (answer.status != LdapClient.LDAP_SUCCESS) { 1478 processReturnCode(answer, name); 1479 } 1480 1481 } catch (LdapReferralException e) { 1482 if (handleReferrals == LdapClient.LDAP_REF_THROW) 1483 throw cont.fillInException(e); 1484 1485 // process the referrals sequentially 1486 while (true) { 1487 1488 LdapReferralContext refCtx = 1489 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 1490 1491 // repeat the original operation at the new context 1492 try { 1493 1494 refCtx.modifyAttributes(name, mods); 1495 return; 1496 1497 } catch (LdapReferralException re) { 1498 e = re; 1499 continue; 1500 1501 } finally { 1502 // Make sure we close referral context 1503 refCtx.close(); 1504 } 1505 } 1506 1507 } catch (IOException e) { 1508 NamingException e2 = new CommunicationException(e.getMessage()); 1509 e2.setRootCause(e); 1510 throw cont.fillInException(e2); 1511 1512 } catch (NamingException e) { 1513 throw cont.fillInException(e); 1514 } 1515 } 1516 1517 private static int convertToLdapModCode(int mod_op) { 1518 switch (mod_op) { 1519 case DirContext.ADD_ATTRIBUTE: 1520 return(LdapClient.ADD); 1521 1522 case DirContext.REPLACE_ATTRIBUTE: 1523 return (LdapClient.REPLACE); 1524 1525 case DirContext.REMOVE_ATTRIBUTE: 1526 return (LdapClient.DELETE); 1527 1528 default: 1529 throw new IllegalArgumentException("Invalid modification code"); 1530 } 1531 } 1532 1533 // ------------------- Schema ----------------------- 1534 1535 protected DirContext c_getSchema(Name name, Continuation cont) 1536 throws NamingException { 1537 cont.setError(this, name); 1538 try { 1539 return getSchemaTree(name); 1540 1541 } catch (NamingException e) { 1542 throw cont.fillInException(e); 1543 } 1544 } 1545 1546 protected DirContext c_getSchemaClassDefinition(Name name, 1547 Continuation cont) 1548 throws NamingException { 1549 cont.setError(this, name); 1550 1551 try { 1552 // retrieve the objectClass attribute from LDAP 1553 Attribute objectClassAttr = c_getAttributes(name, 1554 new String[]{"objectclass"}, cont).get("objectclass"); 1555 if (objectClassAttr == null || objectClassAttr.size() == 0) { 1556 return EMPTY_SCHEMA; 1557 } 1558 1559 // retrieve the root of the ObjectClass schema tree 1560 Context ocSchema = (Context) c_getSchema(name, cont).lookup( 1561 LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME); 1562 1563 // create a context to hold the schema objects representing the object 1564 // classes 1565 HierMemDirCtx objectClassCtx = new HierMemDirCtx(); 1566 DirContext objectClassDef; 1567 String objectClassName; 1568 for (Enumeration objectClasses = objectClassAttr.getAll(); 1569 objectClasses.hasMoreElements(); ) { 1570 objectClassName = (String)objectClasses.nextElement(); 1571 // %%% Should we fail if not found, or just continue? 1572 objectClassDef = (DirContext)ocSchema.lookup(objectClassName); 1573 objectClassCtx.bind(objectClassName, objectClassDef); 1574 } 1575 1576 // Make context read-only 1577 objectClassCtx.setReadOnly( 1578 new SchemaViolationException("Cannot update schema object")); 1579 return (DirContext)objectClassCtx; 1580 1581 } catch (NamingException e) { 1582 throw cont.fillInException(e); 1583 } 1584 } 1585 1586 /* 1587 * getSchemaTree first looks to see if we have already built a 1588 * schema tree for the given entry. If not, it builds a new one and 1589 * stores it in our private hash table 1590 */ 1591 private DirContext getSchemaTree(Name name) throws NamingException { 1592 String subschemasubentry = getSchemaEntry(name, true); 1593 1594 DirContext schemaTree = (DirContext)schemaTrees.get(subschemasubentry); 1595 1596 if(schemaTree==null) { 1597 if(debug){System.err.println("LdapCtx: building new schema tree " + this);} 1598 schemaTree = buildSchemaTree(subschemasubentry); 1599 schemaTrees.put(subschemasubentry, schemaTree); 1600 } 1601 1602 return schemaTree; 1603 } 1604 1605 /* 1606 * buildSchemaTree builds the schema tree corresponding to the 1607 * given subschemasubentree 1608 */ 1609 private DirContext buildSchemaTree(String subschemasubentry) 1610 throws NamingException { 1611 1612 // get the schema entry itself 1613 // DO ask for return object here because we need it to 1614 // create context. Since asking for all attrs, we won't 1615 // be transmitting any specific attrIDs (like Java-specific ones). 1616 SearchControls constraints = new 1617 SearchControls(SearchControls.OBJECT_SCOPE, 1618 0, 0, /* count and time limits */ 1619 SCHEMA_ATTRIBUTES /* return schema attrs */, 1620 true /* return obj */, 1621 false /*deref link */ ); 1622 1623 Name sse = (new CompositeName()).add(subschemasubentry); 1624 NamingEnumeration results = 1625 searchAux(sse, "(objectClass=subschema)", constraints, 1626 false, true, new Continuation()); 1627 1628 if(!results.hasMore()) { 1629 throw new OperationNotSupportedException( 1630 "Cannot get read subschemasubentry: " + subschemasubentry); 1631 } 1632 SearchResult result = (SearchResult)results.next(); 1633 results.close(); 1634 1635 Object obj = result.getObject(); 1636 if(!(obj instanceof LdapCtx)) { 1637 throw new NamingException( 1638 "Cannot get schema object as DirContext: " + subschemasubentry); 1639 } 1640 1641 return LdapSchemaCtx.createSchemaTree(envprops, subschemasubentry, 1642 (LdapCtx)obj /* schema entry */, 1643 result.getAttributes() /* schema attributes */, 1644 netscapeSchemaBug); 1645 } 1646 1647 /* 1648 * getSchemaEntree returns the DN of the subschemasubentree for the 1649 * given entree. It first looks to see if the given entry has 1650 * a subschema different from that of the root DIT (by looking for 1651 * a "subschemasubentry" attribute). If it doesn't find one, it returns 1652 * the one for the root of the DIT (by looking for the root's 1653 * "subschemasubentry" attribute). 1654 * 1655 * This function is called regardless of the server's version, since 1656 * an administrator may have setup the server to support client schema 1657 * queries. If this function trys a serarch on a v2 server that 1658 * doesn't support schema, one of these two things will happen: 1659 * 1) It will get an exception when querying the root DSE 1660 * 2) It will not find a subschemasubentry on the root DSE 1661 * If either of these things occur and the server is not v3, we 1662 * throw OperationNotSupported. 1663 * 1664 * the relative flag tells whether the given name is relative to this 1665 * context. 1666 */ 1667 private String getSchemaEntry(Name name, boolean relative) 1668 throws NamingException { 1669 1670 // Asks for operational attribute "subschemasubentry" 1671 SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE, 1672 0, 0, /* count and time limits */ 1673 new String[]{"subschemasubentry"} /* attr to return */, 1674 false /* returning obj */, 1675 false /* deref link */); 1676 1677 NamingEnumeration results; 1678 try { 1679 results = searchAux(name, "objectclass=*", constraints, relative, 1680 true, new Continuation()); 1681 1682 } catch (NamingException ne) { 1683 if (!clnt.isLdapv3 && currentDN.length() == 0 && name.isEmpty()) { 1684 // we got an error looking for a root entry on an ldapv2 1685 // server. The server must not support schema. 1686 throw new OperationNotSupportedException( 1687 "Cannot get schema information from server"); 1688 } else { 1689 throw ne; 1690 } 1691 } 1692 1693 if (!results.hasMoreElements()) { 1694 throw new ConfigurationException( 1695 "Requesting schema of nonexistent entry: " + name); 1696 } 1697 1698 SearchResult result = (SearchResult) results.next(); 1699 results.close(); 1700 1701 Attribute schemaEntryAttr = 1702 result.getAttributes().get("subschemasubentry"); 1703 //System.err.println("schema entry attrs: " + schemaEntryAttr); 1704 1705 if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) { 1706 if (currentDN.length() == 0 && name.isEmpty()) { 1707 // the server doesn't have a subschemasubentry in its root DSE. 1708 // therefore, it doesn't support schema. 1709 throw new OperationNotSupportedException( 1710 "Cannot read subschemasubentry of root DSE"); 1711 } else { 1712 return getSchemaEntry(new CompositeName(), false); 1713 } 1714 } 1715 1716 return (String)(schemaEntryAttr.get()); // return schema entry name 1717 } 1718 1719 // package-private; used by search enum. 1720 // Set attributes to point to this context in case some one 1721 // asked for their schema 1722 void setParents(Attributes attrs, Name name) throws NamingException { 1723 NamingEnumeration ae = attrs.getAll(); 1724 while(ae.hasMore()) { 1725 ((LdapAttribute) ae.next()).setParent(this, name); 1726 } 1727 } 1728 1729 /* 1730 * Returns the URL associated with this context; used by LdapAttribute 1731 * after deserialization to get pointer to this context. 1732 */ 1733 String getURL() { 1734 if (url == null) { 1735 url = LdapURL.toUrlString(hostname, port_number, currentDN, 1736 hasLdapsScheme); 1737 } 1738 1739 return url; 1740 } 1741 1742 // --------------------- Searches ----------------------------- 1743 protected NamingEnumeration c_search(Name name, 1744 Attributes matchingAttributes, 1745 Continuation cont) 1746 throws NamingException { 1747 return c_search(name, matchingAttributes, null, cont); 1748 } 1749 1750 protected NamingEnumeration c_search(Name name, 1751 Attributes matchingAttributes, 1752 String[] attributesToReturn, 1753 Continuation cont) 1754 throws NamingException { 1755 SearchControls cons = new SearchControls(); 1756 cons.setReturningAttributes(attributesToReturn); 1757 String filter; 1758 try { 1759 filter = SearchFilter.format(matchingAttributes); 1760 } catch (NamingException e) { 1761 cont.setError(this, name); 1762 throw cont.fillInException(e); 1763 } 1764 return c_search(name, filter, cons, cont); 1765 } 1766 1767 protected NamingEnumeration c_search(Name name, 1768 String filter, 1769 SearchControls cons, 1770 Continuation cont) 1771 throws NamingException { 1772 return searchAux(name, filter, cloneSearchControls(cons), true, 1773 waitForReply, cont); 1774 } 1775 1776 protected NamingEnumeration c_search(Name name, 1777 String filterExpr, 1778 Object[] filterArgs, 1779 SearchControls cons, 1780 Continuation cont) 1781 throws NamingException { 1782 String strfilter; 1783 try { 1784 strfilter = SearchFilter.format(filterExpr, filterArgs); 1785 } catch (NamingException e) { 1786 cont.setError(this, name); 1787 throw cont.fillInException(e); 1788 } 1789 return c_search(name, strfilter, cons, cont); 1790 } 1791 1792 // Used by NamingNotifier 1793 NamingEnumeration searchAux(Name name, 1794 String filter, 1795 SearchControls cons, 1796 boolean relative, 1797 boolean waitForReply, Continuation cont) throws NamingException { 1798 1799 LdapResult answer = null; 1800 String[] tokens = new String[2]; // stores ldap compare op. values 1801 String[] reqAttrs; // remember what was asked 1802 1803 if (cons == null) { 1804 cons = new SearchControls(); 1805 } 1806 reqAttrs = cons.getReturningAttributes(); 1807 1808 // if objects are requested then request the Java attributes too 1809 // so that the objects can be constructed 1810 if (cons.getReturningObjFlag()) { 1811 if (reqAttrs != null) { 1812 1813 // check for presence of "*" (user attributes wildcard) 1814 boolean hasWildcard = false; 1815 for (int i = reqAttrs.length - 1; i >= 0; i--) { 1816 if (reqAttrs[i].equals("*")) { 1817 hasWildcard = true; 1818 break; 1819 } 1820 } 1821 if (! hasWildcard) { 1822 String[] totalAttrs = 1823 new String[reqAttrs.length +Obj.JAVA_ATTRIBUTES.length]; 1824 System.arraycopy(reqAttrs, 0, totalAttrs, 0, 1825 reqAttrs.length); 1826 System.arraycopy(Obj.JAVA_ATTRIBUTES, 0, totalAttrs, 1827 reqAttrs.length, Obj.JAVA_ATTRIBUTES.length); 1828 1829 cons.setReturningAttributes(totalAttrs); 1830 } 1831 } 1832 } 1833 1834 LdapCtx.SearchArgs args = 1835 new LdapCtx.SearchArgs(name, filter, cons, reqAttrs); 1836 1837 cont.setError(this, name); 1838 try { 1839 // see if this can be done as a compare, otherwise do a search 1840 if (searchToCompare(filter, cons, tokens)){ 1841 //System.err.println("compare triggered"); 1842 answer = compare(name, tokens[0], tokens[1]); 1843 if (! (answer.compareToSearchResult(fullyQualifiedName(name)))){ 1844 processReturnCode(answer, name); 1845 } 1846 } else { 1847 answer = doSearch(name, filter, cons, relative, waitForReply); 1848 // search result may contain referrals 1849 processReturnCode(answer, name); 1850 } 1851 return new LdapSearchEnumeration(this, answer, 1852 fullyQualifiedName(name), args, cont); 1853 1854 } catch (LdapReferralException e) { 1855 if (handleReferrals == LdapClient.LDAP_REF_THROW) 1856 throw cont.fillInException(e); 1857 1858 // process the referrals sequentially 1859 while (true) { 1860 1861 LdapReferralContext refCtx = 1862 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 1863 1864 // repeat the original operation at the new context 1865 try { 1866 1867 return refCtx.search(name, filter, cons); 1868 1869 } catch (LdapReferralException re) { 1870 e = re; 1871 continue; 1872 1873 } finally { 1874 // Make sure we close referral context 1875 refCtx.close(); 1876 } 1877 } 1878 1879 } catch (LimitExceededException e) { 1880 LdapSearchEnumeration res = 1881 new LdapSearchEnumeration(this, answer, fullyQualifiedName(name), 1882 args, cont); 1883 res.setNamingException(e); 1884 return res; 1885 1886 } catch (PartialResultException e) { 1887 LdapSearchEnumeration res = 1888 new LdapSearchEnumeration(this, answer, fullyQualifiedName(name), 1889 args, cont); 1890 1891 res.setNamingException(e); 1892 return res; 1893 1894 } catch (IOException e) { 1895 NamingException e2 = new CommunicationException(e.getMessage()); 1896 e2.setRootCause(e); 1897 throw cont.fillInException(e2); 1898 1899 } catch (NamingException e) { 1900 throw cont.fillInException(e); 1901 } 1902 } 1903 1904 1905 LdapResult getSearchReply(LdapClient eClnt, LdapResult res) 1906 throws NamingException { 1907 // ensureOpen() won't work here because 1908 // session was associated with previous connection 1909 1910 // %%% RL: we can actually allow the enumeration to continue 1911 // using the old handle but other weird things might happen 1912 // when we hit a referral 1913 if (clnt != eClnt) { 1914 throw new CommunicationException( 1915 "Context's connection changed; unable to continue enumeration"); 1916 } 1917 1918 try { 1919 return eClnt.getSearchReply(batchSize, res, binaryAttrs); 1920 } catch (IOException e) { 1921 NamingException e2 = new CommunicationException(e.getMessage()); 1922 e2.setRootCause(e); 1923 throw e2; 1924 } 1925 } 1926 1927 // Perform a search. Expect 1 SearchResultEntry and the SearchResultDone. 1928 private LdapResult doSearchOnce(Name name, String filter, 1929 SearchControls cons, boolean relative) throws NamingException { 1930 1931 int savedBatchSize = batchSize; 1932 batchSize = 2; // 2 protocol elements 1933 1934 LdapResult answer = doSearch(name, filter, cons, relative, true); 1935 1936 batchSize = savedBatchSize; 1937 return answer; 1938 } 1939 1940 private LdapResult doSearch(Name name, String filter, SearchControls cons, 1941 boolean relative, boolean waitForReply) throws NamingException { 1942 ensureOpen(); 1943 try { 1944 int scope; 1945 1946 switch (cons.getSearchScope()) { 1947 case SearchControls.OBJECT_SCOPE: 1948 scope = LdapClient.SCOPE_BASE_OBJECT; 1949 break; 1950 default: 1951 case SearchControls.ONELEVEL_SCOPE: 1952 scope = LdapClient.SCOPE_ONE_LEVEL; 1953 break; 1954 case SearchControls.SUBTREE_SCOPE: 1955 scope = LdapClient.SCOPE_SUBTREE; 1956 break; 1957 } 1958 1959 // If cons.getReturningObjFlag() then caller should already 1960 // have make sure to request the appropriate attrs 1961 1962 String[] retattrs = cons.getReturningAttributes(); 1963 if (retattrs != null && retattrs.length == 0) { 1964 // Ldap treats null and empty array the same 1965 // need to replace with single element array 1966 retattrs = new String[1]; 1967 retattrs[0] = "1.1"; 1968 } 1969 1970 String nm = (relative 1971 ? fullyQualifiedName(name) 1972 : (name.isEmpty() 1973 ? "" 1974 : name.get(0))); 1975 1976 // JNDI unit is milliseconds, LDAP unit is seconds. 1977 // Zero means no limit. 1978 int msecLimit = cons.getTimeLimit(); 1979 int secLimit = 0; 1980 1981 if (msecLimit > 0) { 1982 secLimit = (msecLimit / 1000) + 1; 1983 } 1984 1985 LdapResult answer = 1986 clnt.search(nm, 1987 scope, 1988 derefAliases, 1989 (int)cons.getCountLimit(), 1990 secLimit, 1991 cons.getReturningObjFlag() ? false : typesOnly, 1992 retattrs, 1993 filter, 1994 batchSize, 1995 reqCtls, 1996 binaryAttrs, 1997 waitForReply, 1998 replyQueueSize); 1999 respCtls = answer.resControls; // retrieve response controls 2000 return answer; 2001 2002 } catch (IOException e) { 2003 NamingException e2 = new CommunicationException(e.getMessage()); 2004 e2.setRootCause(e); 2005 throw e2; 2006 } 2007 } 2008 2009 2010 /* 2011 * Certain simple JNDI searches are automatically converted to 2012 * LDAP compare operations by the LDAP service provider. A search 2013 * is converted to a compare iff: 2014 * 2015 * - the scope is set to OBJECT_SCOPE 2016 * - the filter string contains a simple assertion: "<type>=<value>" 2017 * - the returning attributes list is present but empty 2018 */ 2019 2020 // returns true if a search can be caried out as a compare, and sets 2021 // tokens[0] and tokens[1] to the type and value respectively. 2022 // e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz" 2023 // This function uses the documents JNDI Compare example as a model 2024 // for when to turn a search into a compare. 2025 2026 private static boolean searchToCompare( 2027 String filter, 2028 SearchControls cons, 2029 String tokens[]) { 2030 2031 // if scope is not object-scope, it's really a search 2032 if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) { 2033 return false; 2034 } 2035 2036 // if attributes are to be returned, it's really a search 2037 String[] attrs = cons.getReturningAttributes(); 2038 if (attrs == null || attrs.length != 0) { 2039 return false; 2040 } 2041 2042 // if the filter not a simple assertion, it's really a search 2043 if (! filterToAssertion(filter, tokens)) { 2044 return false; 2045 } 2046 2047 // it can be converted to a compare 2048 return true; 2049 } 2050 2051 // If the supplied filter is a simple assertion i.e. "<type>=<value>" 2052 // (enclosing parentheses are permitted) then 2053 // filterToAssertion will return true and pass the type and value as 2054 // the first and second elements of tokens respectively. 2055 // precondition: tokens[] must be initialized and be at least of size 2. 2056 2057 private static boolean filterToAssertion(String filter, String tokens[]) { 2058 2059 // find the left and right half of the assertion 2060 StringTokenizer assertionTokenizer = new StringTokenizer(filter, "="); 2061 2062 if (assertionTokenizer.countTokens() != 2) { 2063 return false; 2064 } 2065 2066 tokens[0] = assertionTokenizer.nextToken(); 2067 tokens[1] = assertionTokenizer.nextToken(); 2068 2069 // make sure the value does not contain a wildcard 2070 if (tokens[1].indexOf('*') != -1) { 2071 return false; 2072 } 2073 2074 // test for enclosing parenthesis 2075 boolean hasParens = false; 2076 int len = tokens[1].length(); 2077 2078 if ((tokens[0].charAt(0) == '(') && 2079 (tokens[1].charAt(len - 1) == ')')) { 2080 hasParens = true; 2081 2082 } else if ((tokens[0].charAt(0) == '(') || 2083 (tokens[1].charAt(len - 1) == ')')) { 2084 return false; // unbalanced 2085 } 2086 2087 // make sure the left and right half are not expresions themselves 2088 StringTokenizer illegalCharsTokenizer = 2089 new StringTokenizer(tokens[0], "()&|!=~><*", true); 2090 2091 if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) { 2092 return false; 2093 } 2094 2095 illegalCharsTokenizer = 2096 new StringTokenizer(tokens[1], "()&|!=~><*", true); 2097 2098 if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) { 2099 return false; 2100 } 2101 2102 // strip off enclosing parenthesis, if present 2103 if (hasParens) { 2104 tokens[0] = tokens[0].substring(1); 2105 tokens[1] = tokens[1].substring(0, len - 1); 2106 } 2107 2108 return true; 2109 } 2110 2111 private LdapResult compare(Name name, String type, String value) 2112 throws IOException, NamingException { 2113 2114 ensureOpen(); 2115 String nm = fullyQualifiedName(name); 2116 2117 LdapResult answer = clnt.compare(nm, type, value, reqCtls); 2118 respCtls = answer.resControls; // retrieve response controls 2119 2120 return answer; 2121 } 2122 2123 private static SearchControls cloneSearchControls(SearchControls cons) { 2124 if (cons == null) { 2125 return null; 2126 } 2127 String[] retAttrs = cons.getReturningAttributes(); 2128 if (retAttrs != null) { 2129 String[] attrs = new String[retAttrs.length]; 2130 System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length); 2131 retAttrs = attrs; 2132 } 2133 return new SearchControls(cons.getSearchScope(), 2134 cons.getCountLimit(), 2135 cons.getTimeLimit(), 2136 retAttrs, 2137 cons.getReturningObjFlag(), 2138 cons.getDerefLinkFlag()); 2139 } 2140 2141 // -------------- Environment Properties ------------------ 2142 2143 /** 2144 * Override with noncloning version. 2145 */ 2146 protected Hashtable p_getEnvironment() { 2147 return envprops; 2148 } 2149 2150 public Hashtable getEnvironment() throws NamingException { 2151 return (envprops == null 2152 ? new Hashtable(5, 0.75f) 2153 : (Hashtable)envprops.clone()); 2154 } 2155 2156 public Object removeFromEnvironment(String propName) 2157 throws NamingException { 2158 2159 // not there; just return 2160 if (envprops == null || envprops.get(propName) == null) { 2161 return null; 2162 } 2163 2164 if (propName.equals(REF_SEPARATOR)) { 2165 addrEncodingSeparator = DEFAULT_REF_SEPARATOR; 2166 } else if (propName.equals(TYPES_ONLY)) { 2167 typesOnly = DEFAULT_TYPES_ONLY; 2168 } else if (propName.equals(DELETE_RDN)) { 2169 deleteRDN = DEFAULT_DELETE_RDN; 2170 } else if (propName.equals(DEREF_ALIASES)) { 2171 derefAliases = DEFAULT_DEREF_ALIASES; 2172 } else if (propName.equals(Context.BATCHSIZE)) { 2173 batchSize = DEFAULT_BATCH_SIZE; 2174 } else if (propName.equals(REFERRAL_LIMIT)) { 2175 referralHopLimit = DEFAULT_REFERRAL_LIMIT; 2176 } else if (propName.equals(Context.REFERRAL)) { 2177 setReferralMode(null, true); 2178 } else if (propName.equals(BINARY_ATTRIBUTES)) { 2179 setBinaryAttributes(null); 2180 } else if (propName.equals(CONNECT_TIMEOUT)) { 2181 connectTimeout = -1; 2182 } else if (propName.equals(READ_TIMEOUT)) { 2183 readTimeout = -1; 2184 } else if (propName.equals(WAIT_FOR_REPLY)) { 2185 waitForReply = true; 2186 } else if (propName.equals(REPLY_QUEUE_SIZE)) { 2187 replyQueueSize = -1; 2188 2189 // The following properties affect the connection 2190 2191 } else if (propName.equals(Context.SECURITY_PROTOCOL)) { 2192 closeConnection(SOFT_CLOSE); 2193 // De-activate SSL and reset the context's url and port number 2194 if (useSsl && !hasLdapsScheme) { 2195 useSsl = false; 2196 url = null; 2197 if (useDefaultPortNumber) { 2198 port_number = DEFAULT_PORT; 2199 } 2200 } 2201 } else if (propName.equals(VERSION) || 2202 propName.equals(SOCKET_FACTORY)) { 2203 closeConnection(SOFT_CLOSE); 2204 } else if(propName.equals(Context.SECURITY_AUTHENTICATION) || 2205 propName.equals(Context.SECURITY_PRINCIPAL) || 2206 propName.equals(Context.SECURITY_CREDENTIALS)) { 2207 sharable = false; 2208 } 2209 2210 // Update environment; reconnection will use new props 2211 envprops = (Hashtable)envprops.clone(); 2212 return envprops.remove(propName); 2213 } 2214 2215 public Object addToEnvironment(String propName, Object propVal) 2216 throws NamingException { 2217 2218 // If adding null, call remove 2219 if (propVal == null) { 2220 return removeFromEnvironment(propName); 2221 } 2222 2223 if (propName.equals(REF_SEPARATOR)) { 2224 setRefSeparator((String)propVal); 2225 } else if (propName.equals(TYPES_ONLY)) { 2226 setTypesOnly((String)propVal); 2227 } else if (propName.equals(DELETE_RDN)) { 2228 setDeleteRDN((String)propVal); 2229 } else if (propName.equals(DEREF_ALIASES)) { 2230 setDerefAliases((String)propVal); 2231 } else if (propName.equals(Context.BATCHSIZE)) { 2232 setBatchSize((String)propVal); 2233 } else if (propName.equals(REFERRAL_LIMIT)) { 2234 setReferralLimit((String)propVal); 2235 } else if (propName.equals(Context.REFERRAL)) { 2236 setReferralMode((String)propVal, true); 2237 } else if (propName.equals(BINARY_ATTRIBUTES)) { 2238 setBinaryAttributes((String)propVal); 2239 } else if (propName.equals(CONNECT_TIMEOUT)) { 2240 setConnectTimeout((String)propVal); 2241 } else if (propName.equals(READ_TIMEOUT)) { 2242 setReadTimeout((String)propVal); 2243 } else if (propName.equals(WAIT_FOR_REPLY)) { 2244 setWaitForReply((String)propVal); 2245 } else if (propName.equals(REPLY_QUEUE_SIZE)) { 2246 setReplyQueueSize((String)propVal); 2247 2248 // The following properties affect the connection 2249 2250 } else if (propName.equals(Context.SECURITY_PROTOCOL)) { 2251 closeConnection(SOFT_CLOSE); 2252 // Activate SSL and reset the context's url and port number 2253 if ("ssl".equals(propVal)) { 2254 useSsl = true; 2255 url = null; 2256 if (useDefaultPortNumber) { 2257 port_number = DEFAULT_SSL_PORT; 2258 } 2259 } 2260 } else if (propName.equals(VERSION) || 2261 propName.equals(SOCKET_FACTORY)) { 2262 closeConnection(SOFT_CLOSE); 2263 } else if (propName.equals(Context.SECURITY_AUTHENTICATION) || 2264 propName.equals(Context.SECURITY_PRINCIPAL) || 2265 propName.equals(Context.SECURITY_CREDENTIALS)) { 2266 sharable = false; 2267 } 2268 2269 // Update environment; reconnection will use new props 2270 envprops = (envprops == null 2271 ? new Hashtable(5, 0.75f) 2272 : (Hashtable)envprops.clone()); 2273 return envprops.put(propName, propVal); 2274 } 2275 2276 /** 2277 * Sets the URL that created the context in the java.naming.provider.url 2278 * property. 2279 */ 2280 void setProviderUrl(String providerUrl) { // called by LdapCtxFactory 2281 if (envprops != null) { 2282 envprops.put(Context.PROVIDER_URL, providerUrl); 2283 } 2284 } 2285 2286 /** 2287 * Sets the domain name for the context in the com.sun.jndi.ldap.domainname 2288 * property. 2289 * Used for hostname verification by Start TLS 2290 */ 2291 void setDomainName(String domainName) { // called by LdapCtxFactory 2292 if (envprops != null) { 2293 envprops.put(DOMAIN_NAME, domainName); 2294 } 2295 } 2296 2297 private void initEnv() throws NamingException { 2298 if (envprops == null) { 2299 // Make sure that referrals are to their default 2300 setReferralMode(null, false); 2301 return; 2302 } 2303 2304 // Set batch size 2305 setBatchSize((String)envprops.get(Context.BATCHSIZE)); 2306 2307 // Set separator used for encoding RefAddr 2308 setRefSeparator((String)envprops.get(REF_SEPARATOR)); 2309 2310 // Set whether RDN is removed when renaming object 2311 setDeleteRDN((String)envprops.get(DELETE_RDN)); 2312 2313 // Set whether types are returned only 2314 setTypesOnly((String)envprops.get(TYPES_ONLY)); 2315 2316 // Set how aliases are dereferenced 2317 setDerefAliases((String)envprops.get(DEREF_ALIASES)); 2318 2319 // Set the limit on referral chains 2320 setReferralLimit((String)envprops.get(REFERRAL_LIMIT)); 2321 2322 setBinaryAttributes((String)envprops.get(BINARY_ATTRIBUTES)); 2323 2324 bindCtls = cloneControls((Control[]) envprops.get(BIND_CONTROLS)); 2325 2326 // set referral handling 2327 setReferralMode((String)envprops.get(Context.REFERRAL), false); 2328 2329 // Set the connect timeout 2330 setConnectTimeout((String)envprops.get(CONNECT_TIMEOUT)); 2331 2332 // Set the read timeout 2333 setReadTimeout((String)envprops.get(READ_TIMEOUT)); 2334 2335 // Set the flag that controls whether to block until the first reply 2336 // is received 2337 setWaitForReply((String)envprops.get(WAIT_FOR_REPLY)); 2338 2339 // Set the size of the queue of unprocessed search replies 2340 setReplyQueueSize((String)envprops.get(REPLY_QUEUE_SIZE)); 2341 2342 // When connection is created, it will use these and other 2343 // properties from the environment 2344 } 2345 2346 private void setDeleteRDN(String deleteRDNProp) { 2347 if ((deleteRDNProp != null) && 2348 (deleteRDNProp.equalsIgnoreCase("false"))) { 2349 deleteRDN = false; 2350 } else { 2351 deleteRDN = DEFAULT_DELETE_RDN; 2352 } 2353 } 2354 2355 private void setTypesOnly(String typesOnlyProp) { 2356 if ((typesOnlyProp != null) && 2357 (typesOnlyProp.equalsIgnoreCase("true"))) { 2358 typesOnly = true; 2359 } else { 2360 typesOnly = DEFAULT_TYPES_ONLY; 2361 } 2362 } 2363 2364 /** 2365 * Sets the batch size of this context; 2366 */ 2367 private void setBatchSize(String batchSizeProp) { 2368 // set batchsize 2369 if (batchSizeProp != null) { 2370 batchSize = Integer.parseInt(batchSizeProp); 2371 } else { 2372 batchSize = DEFAULT_BATCH_SIZE; 2373 } 2374 } 2375 2376 /** 2377 * Sets the referral mode of this context to 'follow', 'throw' or 'ignore'. 2378 * If referral mode is 'ignore' then activate the manageReferral control. 2379 */ 2380 private void setReferralMode(String ref, boolean update) { 2381 // First determine the referral mode 2382 if (ref != null) { 2383 if (ref.equals("follow")) { 2384 handleReferrals = LdapClient.LDAP_REF_FOLLOW; 2385 } else if (ref.equals("throw")) { 2386 handleReferrals = LdapClient.LDAP_REF_THROW; 2387 } else if (ref.equals("ignore")) { 2388 handleReferrals = LdapClient.LDAP_REF_IGNORE; 2389 } else { 2390 throw new IllegalArgumentException( 2391 "Illegal value for " + Context.REFERRAL + " property."); 2392 } 2393 } else { 2394 handleReferrals = DEFAULT_REFERRAL_MODE; 2395 } 2396 2397 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { 2398 // If ignoring referrals, add manageReferralControl 2399 reqCtls = addControl(reqCtls, manageReferralControl); 2400 2401 } else if (update) { 2402 2403 // If we're update an existing context, remove the control 2404 reqCtls = removeControl(reqCtls, manageReferralControl); 2405 2406 } // else, leave alone; need not update 2407 } 2408 2409 /** 2410 * Set whether aliases are derefereced during resolution and searches. 2411 */ 2412 private void setDerefAliases(String deref) { 2413 if (deref != null) { 2414 if (deref.equals("never")) { 2415 derefAliases = 0; // never de-reference aliases 2416 } else if (deref.equals("searching")) { 2417 derefAliases = 1; // de-reference aliases during searching 2418 } else if (deref.equals("finding")) { 2419 derefAliases = 2; // de-reference during name resolution 2420 } else if (deref.equals("always")) { 2421 derefAliases = 3; // always de-reference aliases 2422 } else { 2423 throw new IllegalArgumentException("Illegal value for " + 2424 DEREF_ALIASES + " property."); 2425 } 2426 } else { 2427 derefAliases = DEFAULT_DEREF_ALIASES; 2428 } 2429 } 2430 2431 private void setRefSeparator(String sepStr) throws NamingException { 2432 if (sepStr != null && sepStr.length() > 0) { 2433 addrEncodingSeparator = sepStr.charAt(0); 2434 } else { 2435 addrEncodingSeparator = DEFAULT_REF_SEPARATOR; 2436 } 2437 } 2438 2439 /** 2440 * Sets the limit on referral chains 2441 */ 2442 private void setReferralLimit(String referralLimitProp) { 2443 // set referral limit 2444 if (referralLimitProp != null) { 2445 referralHopLimit = Integer.parseInt(referralLimitProp); 2446 2447 // a zero setting indicates no limit 2448 if (referralHopLimit == 0) 2449 referralHopLimit = Integer.MAX_VALUE; 2450 } else { 2451 referralHopLimit = DEFAULT_REFERRAL_LIMIT; 2452 } 2453 } 2454 2455 // For counting referral hops 2456 void setHopCount(int hopCount) { 2457 this.hopCount = hopCount; 2458 } 2459 2460 /** 2461 * Sets the connect timeout value 2462 */ 2463 private void setConnectTimeout(String connectTimeoutProp) { 2464 if (connectTimeoutProp != null) { 2465 connectTimeout = Integer.parseInt(connectTimeoutProp); 2466 } else { 2467 connectTimeout = -1; 2468 } 2469 } 2470 2471 /** 2472 * Sets the size of the queue of unprocessed search replies 2473 */ 2474 private void setReplyQueueSize(String replyQueueSizeProp) { 2475 if (replyQueueSizeProp != null) { 2476 replyQueueSize = Integer.parseInt(replyQueueSizeProp); 2477 // disallow an empty queue 2478 if (replyQueueSize <= 0) { 2479 replyQueueSize = -1; // unlimited 2480 } 2481 } else { 2482 replyQueueSize = -1; // unlimited 2483 } 2484 } 2485 2486 /** 2487 * Sets the flag that controls whether to block until the first search 2488 * reply is received 2489 */ 2490 private void setWaitForReply(String waitForReplyProp) { 2491 if (waitForReplyProp != null && 2492 (waitForReplyProp.equalsIgnoreCase("false"))) { 2493 waitForReply = false; 2494 } else { 2495 waitForReply = true; 2496 } 2497 } 2498 2499 /** 2500 * Sets the read timeout value 2501 */ 2502 private void setReadTimeout(String readTimeoutProp) { 2503 if (readTimeoutProp != null) { 2504 readTimeout = Integer.parseInt(readTimeoutProp); 2505 } else { 2506 readTimeout = -1; 2507 } 2508 } 2509 2510 /* 2511 * Extract URLs from a string. The format of the string is: 2512 * 2513 * <urlstring > ::= "Referral:" <ldapurls> 2514 * <ldapurls> ::= <separator> <ldapurl> | <ldapurls> 2515 * <separator> ::= ASCII linefeed character (0x0a) 2516 * <ldapurl> ::= LDAP URL format (RFC 1959) 2517 */ 2518 private static Vector extractURLs(String refString) { 2519 2520 int separator = 0; 2521 int urlCount = 0; 2522 2523 // count the number of URLs 2524 while ((separator = refString.indexOf('\n', separator)) >= 0) { 2525 separator++; 2526 urlCount++; 2527 } 2528 2529 Vector referrals = new Vector(urlCount); 2530 int iURL; 2531 int i = 0; 2532 2533 separator = refString.indexOf('\n'); 2534 iURL = separator + 1; 2535 while ((separator = refString.indexOf('\n', iURL)) >= 0) { 2536 referrals.addElement(refString.substring(iURL, separator)); 2537 iURL = separator + 1; 2538 } 2539 referrals.addElement(refString.substring(iURL)); 2540 2541 return referrals; 2542 } 2543 2544 /* 2545 * Argument is a space-separated list of attribute IDs 2546 * Converts attribute IDs to lowercase before adding to built-in list. 2547 */ 2548 private void setBinaryAttributes(String attrIds) { 2549 if (attrIds == null) { 2550 binaryAttrs = null; 2551 } else { 2552 binaryAttrs = new Hashtable(11, 0.75f); 2553 StringTokenizer tokens = 2554 new StringTokenizer(attrIds.toLowerCase(), " "); 2555 2556 while (tokens.hasMoreTokens()) { 2557 binaryAttrs.put(tokens.nextToken(), Boolean.TRUE); 2558 } 2559 } 2560 } 2561 2562 // ----------------- Connection --------------------- 2563 2564 protected void finalize() { 2565 try { 2566 close(); 2567 } catch (NamingException e) { 2568 // ignore failures 2569 } 2570 } 2571 2572 synchronized public void close() throws NamingException { 2573 if (debug) { 2574 System.err.println("LdapCtx: close() called " + this); 2575 (new Throwable()).printStackTrace(); 2576 } 2577 2578 // Event (normal and unsolicited) 2579 if (eventSupport != null) { 2580 eventSupport.cleanup(); // idempotent 2581 removeUnsolicited(); 2582 } 2583 2584 // Enumerations that are keeping the connection alive 2585 if (enumCount > 0) { 2586 if (debug) 2587 System.err.println("LdapCtx: close deferred"); 2588 closeRequested = true; 2589 return; 2590 } 2591 closeConnection(SOFT_CLOSE); 2592 2593 // %%%: RL: There is no need to set these to null, as they're just 2594 // variables whose contents and references will automatically 2595 // be cleaned up when they're no longer referenced. 2596 // Also, setting these to null creates problems for the attribute 2597 // schema-related methods, which need these to work. 2598 /* 2599 schemaTrees = null; 2600 envprops = null; 2601 */ 2602 } 2603 2604 public void reconnect(Control[] connCtls) throws NamingException { 2605 // Update environment 2606 envprops = (envprops == null 2607 ? new Hashtable(5, 0.75f) 2608 : (Hashtable)envprops.clone()); 2609 2610 if (connCtls == null) { 2611 envprops.remove(BIND_CONTROLS); 2612 bindCtls = null; 2613 } else { 2614 envprops.put(BIND_CONTROLS, bindCtls = cloneControls(connCtls)); 2615 } 2616 2617 sharable = false; // can't share with existing contexts 2618 ensureOpen(); // open or reauthenticated 2619 } 2620 2621 private void ensureOpen() throws NamingException { 2622 ensureOpen(false); 2623 } 2624 2625 private void ensureOpen(boolean startTLS) throws NamingException { 2626 2627 try { 2628 if (clnt == null) { 2629 if (debug) { 2630 System.err.println("LdapCtx: Reconnecting " + this); 2631 } 2632 2633 // reset the cache before a new connection is established 2634 schemaTrees = new Hashtable(11, 0.75f); 2635 connect(startTLS); 2636 2637 } else if (!sharable || startTLS) { 2638 2639 synchronized (clnt) { 2640 if (!clnt.isLdapv3 2641 || clnt.referenceCount > 1 2642 || clnt.usingSaslStreams()) { 2643 closeConnection(SOFT_CLOSE); 2644 } 2645 } 2646 // reset the cache before a new connection is established 2647 schemaTrees = new Hashtable(11, 0.75f); 2648 connect(startTLS); 2649 } 2650 2651 } finally { 2652 sharable = true; // connection is now either new or single-use 2653 // OK for others to start sharing again 2654 } 2655 } 2656 2657 private void connect(boolean startTLS) throws NamingException { 2658 if (debug) { System.err.println("LdapCtx: Connecting " + this); } 2659 2660 String user = null; // authenticating user 2661 Object passwd = null; // password for authenticating user 2662 String secProtocol = null; // security protocol (e.g. "ssl") 2663 String socketFactory = null; // socket factory 2664 String authMechanism = null; // authentication mechanism 2665 String ver = null; 2666 int ldapVersion; // LDAP protocol version 2667 boolean usePool = false; // enable connection pooling 2668 2669 if (envprops != null) { 2670 user = (String)envprops.get(Context.SECURITY_PRINCIPAL); 2671 passwd = envprops.get(Context.SECURITY_CREDENTIALS); 2672 ver = (String)envprops.get(VERSION); 2673 secProtocol = 2674 useSsl ? "ssl" : (String)envprops.get(Context.SECURITY_PROTOCOL); 2675 socketFactory = (String)envprops.get(SOCKET_FACTORY); 2676 authMechanism = 2677 (String)envprops.get(Context.SECURITY_AUTHENTICATION); 2678 2679 usePool = "true".equalsIgnoreCase((String)envprops.get(ENABLE_POOL)); 2680 } 2681 2682 if (socketFactory == null) { 2683 socketFactory = 2684 "ssl".equals(secProtocol) ? DEFAULT_SSL_FACTORY : null; 2685 } 2686 2687 if (authMechanism == null) { 2688 authMechanism = (user == null) ? "none" : "simple"; 2689 } 2690 2691 try { 2692 boolean initial = (clnt == null); 2693 2694 if (initial) { 2695 ldapVersion = (ver != null) ? Integer.parseInt(ver) : 2696 DEFAULT_LDAP_VERSION; 2697 2698 clnt = LdapClient.getInstance( 2699 usePool, // Whether to use connection pooling 2700 2701 // Required for LdapClient constructor 2702 hostname, 2703 port_number, 2704 socketFactory, 2705 connectTimeout, 2706 readTimeout, 2707 trace, 2708 2709 // Required for basic client identity 2710 ldapVersion, 2711 authMechanism, 2712 bindCtls, 2713 secProtocol, 2714 2715 // Required for simple client identity 2716 user, 2717 passwd, 2718 2719 // Required for SASL client identity 2720 envprops); 2721 2722 2723 /** 2724 * Pooled connections are preauthenticated; 2725 * newly created ones are not. 2726 */ 2727 if (clnt.authenticateCalled()) { 2728 return; 2729 } 2730 2731 } else if (sharable && startTLS) { 2732 return; // no authentication required 2733 2734 } else { 2735 // reauthenticating over existing connection; 2736 // only v3 supports this 2737 ldapVersion = LdapClient.LDAP_VERSION3; 2738 } 2739 2740 LdapResult answer = clnt.authenticate(initial, 2741 user, passwd, ldapVersion, authMechanism, bindCtls, envprops); 2742 2743 respCtls = answer.resControls; // retrieve (bind) response controls 2744 2745 if (answer.status != LdapClient.LDAP_SUCCESS) { 2746 if (initial) { 2747 closeConnection(HARD_CLOSE); // hard close 2748 } 2749 processReturnCode(answer); 2750 } 2751 2752 } catch (LdapReferralException e) { 2753 if (handleReferrals == LdapClient.LDAP_REF_THROW) 2754 throw e; 2755 2756 String referral; 2757 LdapURL url; 2758 NamingException saved_ex = null; 2759 2760 // Process the referrals sequentially (top level) and 2761 // recursively (per referral) 2762 while (true) { 2763 2764 if ((referral = e.getNextReferral()) == null) { 2765 // No more referrals to follow 2766 2767 if (saved_ex != null) { 2768 throw (NamingException)(saved_ex.fillInStackTrace()); 2769 } else { 2770 // No saved exception, something must have gone wrong 2771 throw new NamingException( 2772 "Internal error processing referral during connection"); 2773 } 2774 } 2775 2776 // Use host/port number from referral 2777 url = new LdapURL(referral); 2778 hostname = url.getHost(); 2779 if ((hostname != null) && (hostname.charAt(0) == '[')) { 2780 hostname = hostname.substring(1, hostname.length() - 1); 2781 } 2782 port_number = url.getPort(); 2783 2784 // Try to connect again using new host/port number 2785 try { 2786 connect(startTLS); 2787 break; 2788 2789 } catch (NamingException ne) { 2790 saved_ex = ne; 2791 continue; // follow another referral 2792 } 2793 } 2794 } 2795 } 2796 2797 private void closeConnection(boolean hardclose) { 2798 removeUnsolicited(); // idempotent 2799 2800 if (clnt != null) { 2801 if (debug) { 2802 System.err.println("LdapCtx: calling clnt.close() " + this); 2803 } 2804 clnt.close(reqCtls, hardclose); 2805 clnt = null; 2806 } 2807 } 2808 2809 // Used by Enum classes to track whether it still needs context 2810 private int enumCount = 0; 2811 private boolean closeRequested = false; 2812 2813 synchronized void incEnumCount() { 2814 ++enumCount; 2815 if (debug) System.err.println("LdapCtx: " + this + " enum inc: " + enumCount); 2816 } 2817 2818 synchronized void decEnumCount() { 2819 --enumCount; 2820 if (debug) System.err.println("LdapCtx: " + this + " enum dec: " + enumCount); 2821 2822 if (enumCount == 0 && closeRequested) { 2823 try { 2824 close(); 2825 } catch (NamingException e) { 2826 // ignore failures 2827 } 2828 } 2829 } 2830 2831 2832 // ------------ Return code and Error messages ----------------------- 2833 2834 protected void processReturnCode(LdapResult answer) throws NamingException { 2835 processReturnCode(answer, null, this, null, envprops, null); 2836 } 2837 2838 void processReturnCode(LdapResult answer, Name remainName) 2839 throws NamingException { 2840 processReturnCode(answer, 2841 (new CompositeName()).add(currentDN), 2842 this, 2843 remainName, 2844 envprops, 2845 fullyQualifiedName(remainName)); 2846 } 2847 2848 protected void processReturnCode(LdapResult res, Name resolvedName, 2849 Object resolvedObj, Name remainName, Hashtable envprops, String fullDN) 2850 throws NamingException { 2851 2852 String msg = LdapClient.getErrorMessage(res.status, res.errorMessage); 2853 NamingException e; 2854 LdapReferralException r = null; 2855 2856 switch (res.status) { 2857 2858 case LdapClient.LDAP_SUCCESS: 2859 2860 // handle Search continuation references 2861 if (res.referrals != null) { 2862 2863 msg = "Unprocessed Continuation Reference(s)"; 2864 2865 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { 2866 e = new PartialResultException(msg); 2867 break; 2868 } 2869 2870 // handle multiple sets of URLs 2871 int contRefCount = res.referrals.size(); 2872 LdapReferralException head = null; 2873 LdapReferralException ptr = null; 2874 2875 msg = "Continuation Reference"; 2876 2877 // make a chain of LdapReferralExceptions 2878 for (int i = 0; i < contRefCount; i++) { 2879 2880 r = new LdapReferralException(resolvedName, resolvedObj, 2881 remainName, msg, envprops, fullDN, handleReferrals, 2882 reqCtls); 2883 r.setReferralInfo((Vector)res.referrals.elementAt(i), true); 2884 2885 if (hopCount > 1) { 2886 r.setHopCount(hopCount); 2887 } 2888 2889 if (head == null) { 2890 head = ptr = r; 2891 } else { 2892 ptr.nextReferralEx = r; // append ex. to end of chain 2893 ptr = r; 2894 } 2895 } 2896 res.referrals = null; // reset 2897 2898 if (res.refEx == null) { 2899 res.refEx = head; 2900 2901 } else { 2902 ptr = res.refEx; 2903 2904 while (ptr.nextReferralEx != null) { 2905 ptr = ptr.nextReferralEx; 2906 } 2907 ptr.nextReferralEx = head; 2908 } 2909 2910 // check the hop limit 2911 if (hopCount > referralHopLimit) { 2912 NamingException lee = 2913 new LimitExceededException("Referral limit exceeded"); 2914 lee.setRootCause(r); 2915 throw lee; 2916 } 2917 } 2918 return; 2919 2920 case LdapClient.LDAP_REFERRAL: 2921 2922 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { 2923 e = new PartialResultException(msg); 2924 break; 2925 } 2926 2927 r = new LdapReferralException(resolvedName, resolvedObj, remainName, 2928 msg, envprops, fullDN, handleReferrals, reqCtls); 2929 // only one set of URLs is present 2930 r.setReferralInfo((Vector)res.referrals.elementAt(0), false); 2931 2932 if (hopCount > 1) { 2933 r.setHopCount(hopCount); 2934 } 2935 2936 // check the hop limit 2937 if (hopCount > referralHopLimit) { 2938 NamingException lee = 2939 new LimitExceededException("Referral limit exceeded"); 2940 lee.setRootCause(r); 2941 e = lee; 2942 2943 } else { 2944 e = r; 2945 } 2946 break; 2947 2948 /* 2949 * Handle SLAPD-style referrals. 2950 * 2951 * Referrals received during name resolution should be followed 2952 * until one succeeds - the target entry is located. An exception 2953 * is thrown now to handle these. 2954 * 2955 * Referrals received during a search operation point to unexplored 2956 * parts of the directory and each should be followed. An exception 2957 * is thrown later (during results enumeration) to handle these. 2958 */ 2959 2960 case LdapClient.LDAP_PARTIAL_RESULTS: 2961 2962 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { 2963 e = new PartialResultException(msg); 2964 break; 2965 } 2966 2967 // extract SLAPD-style referrals from errorMessage 2968 if ((res.errorMessage != null) && (!res.errorMessage.equals(""))) { 2969 res.referrals = extractURLs(res.errorMessage); 2970 } else { 2971 e = new PartialResultException(msg); 2972 break; 2973 } 2974 2975 // build exception 2976 r = new LdapReferralException(resolvedName, 2977 resolvedObj, 2978 remainName, 2979 msg, 2980 envprops, 2981 fullDN, 2982 handleReferrals, 2983 reqCtls); 2984 2985 if (hopCount > 1) { 2986 r.setHopCount(hopCount); 2987 } 2988 /* 2989 * %%% 2990 * SLAPD-style referrals received during name resolution 2991 * cannot be distinguished from those received during a 2992 * search operation. Since both must be handled differently 2993 * the following rule is applied: 2994 * 2995 * If 1 referral and 0 entries is received then 2996 * assume name resolution has not yet completed. 2997 */ 2998 if (((res.entries == null) || (res.entries.size() == 0)) && 2999 (res.referrals.size() == 1)) { 3000 3001 r.setReferralInfo((Vector)res.referrals, false); 3002 3003 // check the hop limit 3004 if (hopCount > referralHopLimit) { 3005 NamingException lee = 3006 new LimitExceededException("Referral limit exceeded"); 3007 lee.setRootCause(r); 3008 e = lee; 3009 3010 } else { 3011 e = r; 3012 } 3013 3014 } else { 3015 r.setReferralInfo(res.referrals, true); 3016 res.refEx = r; 3017 return; 3018 } 3019 break; 3020 3021 case LdapClient.LDAP_INVALID_DN_SYNTAX: 3022 case LdapClient.LDAP_NAMING_VIOLATION: 3023 3024 if (remainName != null) { 3025 e = new 3026 InvalidNameException(remainName.toString() + ": " + msg); 3027 } else { 3028 e = new InvalidNameException(msg); 3029 } 3030 break; 3031 3032 default: 3033 e = mapErrorCode(res.status, res.errorMessage); 3034 break; 3035 } 3036 e.setResolvedName(resolvedName); 3037 e.setResolvedObj(resolvedObj); 3038 e.setRemainingName(remainName); 3039 throw e; 3040 } 3041 3042 /** 3043 * Maps an LDAP error code to an appropriate NamingException. 3044 * %%% public; used by controls 3045 * 3046 * @param errorCode numeric LDAP error code 3047 * @param errorMessage textual description of the LDAP error. May be null. 3048 * 3049 * @return A NamingException or null if the error code indicates success. 3050 */ 3051 public static NamingException mapErrorCode(int errorCode, 3052 String errorMessage) { 3053 3054 if (errorCode == LdapClient.LDAP_SUCCESS) 3055 return null; 3056 3057 NamingException e = null; 3058 String message = LdapClient.getErrorMessage(errorCode, errorMessage); 3059 3060 switch (errorCode) { 3061 3062 case LdapClient.LDAP_ALIAS_DEREFERENCING_PROBLEM: 3063 e = new NamingException(message); 3064 break; 3065 3066 case LdapClient.LDAP_ALIAS_PROBLEM: 3067 e = new NamingException(message); 3068 break; 3069 3070 case LdapClient.LDAP_ATTRIBUTE_OR_VALUE_EXISTS: 3071 e = new AttributeInUseException(message); 3072 break; 3073 3074 case LdapClient.LDAP_AUTH_METHOD_NOT_SUPPORTED: 3075 case LdapClient.LDAP_CONFIDENTIALITY_REQUIRED: 3076 case LdapClient.LDAP_STRONG_AUTH_REQUIRED: 3077 case LdapClient.LDAP_INAPPROPRIATE_AUTHENTICATION: 3078 e = new AuthenticationNotSupportedException(message); 3079 break; 3080 3081 case LdapClient.LDAP_ENTRY_ALREADY_EXISTS: 3082 e = new NameAlreadyBoundException(message); 3083 break; 3084 3085 case LdapClient.LDAP_INVALID_CREDENTIALS: 3086 case LdapClient.LDAP_SASL_BIND_IN_PROGRESS: 3087 e = new AuthenticationException(message); 3088 break; 3089 3090 case LdapClient.LDAP_INAPPROPRIATE_MATCHING: 3091 e = new InvalidSearchFilterException(message); 3092 break; 3093 3094 case LdapClient.LDAP_INSUFFICIENT_ACCESS_RIGHTS: 3095 e = new NoPermissionException(message); 3096 break; 3097 3098 case LdapClient.LDAP_INVALID_ATTRIBUTE_SYNTAX: 3099 case LdapClient.LDAP_CONSTRAINT_VIOLATION: 3100 e = new InvalidAttributeValueException(message); 3101 break; 3102 3103 case LdapClient.LDAP_LOOP_DETECT: 3104 e = new NamingException(message); 3105 break; 3106 3107 case LdapClient.LDAP_NO_SUCH_ATTRIBUTE: 3108 e = new NoSuchAttributeException(message); 3109 break; 3110 3111 case LdapClient.LDAP_NO_SUCH_OBJECT: 3112 e = new NameNotFoundException(message); 3113 break; 3114 3115 case LdapClient.LDAP_OBJECT_CLASS_MODS_PROHIBITED: 3116 case LdapClient.LDAP_OBJECT_CLASS_VIOLATION: 3117 case LdapClient.LDAP_NOT_ALLOWED_ON_RDN: 3118 e = new SchemaViolationException(message); 3119 break; 3120 3121 case LdapClient.LDAP_NOT_ALLOWED_ON_NON_LEAF: 3122 e = new ContextNotEmptyException(message); 3123 break; 3124 3125 case LdapClient.LDAP_OPERATIONS_ERROR: 3126 // %%% need new exception ? 3127 e = new NamingException(message); 3128 break; 3129 3130 case LdapClient.LDAP_OTHER: 3131 e = new NamingException(message); 3132 break; 3133 3134 case LdapClient.LDAP_PROTOCOL_ERROR: 3135 e = new CommunicationException(message); 3136 break; 3137 3138 case LdapClient.LDAP_SIZE_LIMIT_EXCEEDED: 3139 e = new SizeLimitExceededException(message); 3140 break; 3141 3142 case LdapClient.LDAP_TIME_LIMIT_EXCEEDED: 3143 e = new TimeLimitExceededException(message); 3144 break; 3145 3146 case LdapClient.LDAP_UNAVAILABLE_CRITICAL_EXTENSION: 3147 e = new OperationNotSupportedException(message); 3148 break; 3149 3150 case LdapClient.LDAP_UNAVAILABLE: 3151 case LdapClient.LDAP_BUSY: 3152 e = new ServiceUnavailableException(message); 3153 break; 3154 3155 case LdapClient.LDAP_UNDEFINED_ATTRIBUTE_TYPE: 3156 e = new InvalidAttributeIdentifierException(message); 3157 break; 3158 3159 case LdapClient.LDAP_UNWILLING_TO_PERFORM: 3160 e = new OperationNotSupportedException(message); 3161 break; 3162 3163 case LdapClient.LDAP_COMPARE_FALSE: 3164 case LdapClient.LDAP_COMPARE_TRUE: 3165 case LdapClient.LDAP_IS_LEAF: 3166 // these are really not exceptions and this code probably 3167 // never gets executed 3168 e = new NamingException(message); 3169 break; 3170 3171 case LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED: 3172 e = new LimitExceededException(message); 3173 break; 3174 3175 case LdapClient.LDAP_REFERRAL: 3176 e = new NamingException(message); 3177 break; 3178 3179 case LdapClient.LDAP_PARTIAL_RESULTS: 3180 e = new NamingException(message); 3181 break; 3182 3183 case LdapClient.LDAP_INVALID_DN_SYNTAX: 3184 case LdapClient.LDAP_NAMING_VIOLATION: 3185 e = new InvalidNameException(message); 3186 break; 3187 3188 default: 3189 e = new NamingException(message); 3190 break; 3191 } 3192 3193 return e; 3194 } 3195 3196 // ----------------- Extensions and Controls ------------------- 3197 3198 public ExtendedResponse extendedOperation(ExtendedRequest request) 3199 throws NamingException { 3200 3201 boolean startTLS = (request.getID().equals(STARTTLS_REQ_OID)); 3202 ensureOpen(startTLS); 3203 3204 try { 3205 3206 LdapResult answer = 3207 clnt.extendedOp(request.getID(), request.getEncodedValue(), 3208 reqCtls, startTLS); 3209 respCtls = answer.resControls; // retrieve response controls 3210 3211 if (answer.status != LdapClient.LDAP_SUCCESS) { 3212 processReturnCode(answer, new CompositeName()); 3213 } 3214 // %%% verify request.getID() == answer.extensionId 3215 3216 int len = (answer.extensionValue == null) ? 3217 0 : 3218 answer.extensionValue.length; 3219 3220 ExtendedResponse er = 3221 request.createExtendedResponse(answer.extensionId, 3222 answer.extensionValue, 0, len); 3223 3224 if (er instanceof StartTlsResponseImpl) { 3225 // Pass the connection handle to StartTlsResponseImpl 3226 String domainName = (String) 3227 (envprops != null ? envprops.get(DOMAIN_NAME) : null); 3228 ((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName); 3229 } 3230 return er; 3231 3232 } catch (LdapReferralException e) { 3233 3234 if (handleReferrals == LdapClient.LDAP_REF_THROW) 3235 throw e; 3236 3237 // process the referrals sequentially 3238 while (true) { 3239 3240 LdapReferralContext refCtx = 3241 (LdapReferralContext)e.getReferralContext(envprops, bindCtls); 3242 3243 // repeat the original operation at the new context 3244 try { 3245 3246 return refCtx.extendedOperation(request); 3247 3248 } catch (LdapReferralException re) { 3249 e = re; 3250 continue; 3251 3252 } finally { 3253 // Make sure we close referral context 3254 refCtx.close(); 3255 } 3256 } 3257 3258 } catch (IOException e) { 3259 NamingException e2 = new CommunicationException(e.getMessage()); 3260 e2.setRootCause(e); 3261 throw e2; 3262 } 3263 } 3264 3265 public void setRequestControls(Control[] reqCtls) throws NamingException { 3266 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { 3267 this.reqCtls = addControl(reqCtls, manageReferralControl); 3268 } else { 3269 this.reqCtls = cloneControls(reqCtls); 3270 } 3271 } 3272 3273 public Control[] getRequestControls() throws NamingException { 3274 return cloneControls(reqCtls); 3275 } 3276 3277 public Control[] getConnectControls() throws NamingException { 3278 return cloneControls(bindCtls); 3279 } 3280 3281 public Control[] getResponseControls() throws NamingException { 3282 return (respCtls != null)? convertControls(respCtls) : null; 3283 } 3284 3285 /** 3286 * Narrow controls using own default factory and ControlFactory. 3287 * @param ctls A non-null Vector 3288 */ 3289 Control[] convertControls(Vector ctls) throws NamingException { 3290 int count = ctls.size(); 3291 3292 if (count == 0) { 3293 return null; 3294 } 3295 3296 Control[] controls = new Control[count]; 3297 3298 for (int i = 0; i < count; i++) { 3299 // Try own factory first 3300 controls[i] = myResponseControlFactory.getControlInstance( 3301 (Control)ctls.elementAt(i)); 3302 3303 // Try assigned factories if own produced null 3304 if (controls[i] == null) { 3305 controls[i] = ControlFactory.getControlInstance( 3306 (Control)ctls.elementAt(i), this, envprops); 3307 } 3308 } 3309 return controls; 3310 } 3311 3312 private static Control[] addControl(Control[] prevCtls, Control addition) { 3313 if (prevCtls == null) { 3314 return new Control[]{addition}; 3315 } 3316 3317 // Find it 3318 int found = findControl(prevCtls, addition); 3319 if (found != -1) { 3320 return prevCtls; // no need to do it again 3321 } 3322 3323 Control[] newCtls = new Control[prevCtls.length+1]; 3324 System.arraycopy(prevCtls, 0, newCtls, 0, prevCtls.length); 3325 newCtls[prevCtls.length] = addition; 3326 return newCtls; 3327 } 3328 3329 private static int findControl(Control[] ctls, Control target) { 3330 for (int i = 0; i < ctls.length; i++) { 3331 if (ctls[i] == target) { 3332 return i; 3333 } 3334 } 3335 return -1; 3336 } 3337 3338 private static Control[] removeControl(Control[] prevCtls, Control target) { 3339 if (prevCtls == null) { 3340 return null; 3341 } 3342 3343 // Find it 3344 int found = findControl(prevCtls, target); 3345 if (found == -1) { 3346 return prevCtls; // not there 3347 } 3348 3349 // Remove it 3350 Control[] newCtls = new Control[prevCtls.length-1]; 3351 System.arraycopy(prevCtls, 0, newCtls, 0, found); 3352 System.arraycopy(prevCtls, found+1, newCtls, found, 3353 prevCtls.length-found-1); 3354 return newCtls; 3355 } 3356 3357 private static Control[] cloneControls(Control[] ctls) { 3358 if (ctls == null) { 3359 return null; 3360 } 3361 Control[] copiedCtls = new Control[ctls.length]; 3362 System.arraycopy(ctls, 0, copiedCtls, 0, ctls.length); 3363 return copiedCtls; 3364 } 3365 3366 // -------------------- Events ------------------------ 3367 /* 3368 * Access to eventSupport need not be synchronized even though the 3369 * Connection thread can access it asynchronously. It is 3370 * impossible for a race condition to occur because 3371 * eventSupport.addNamingListener() must have been called before 3372 * the Connection thread can call back to this ctx. 3373 */ 3374 public void addNamingListener(Name nm, int scope, NamingListener l) 3375 throws NamingException { 3376 addNamingListener(getTargetName(nm), scope, l); 3377 } 3378 3379 public void addNamingListener(String nm, int scope, NamingListener l) 3380 throws NamingException { 3381 if (eventSupport == null) 3382 eventSupport = new EventSupport(this); 3383 eventSupport.addNamingListener(getTargetName(new CompositeName(nm)), 3384 scope, l); 3385 3386 // If first time asking for unsol 3387 if (l instanceof UnsolicitedNotificationListener && !unsolicited) { 3388 addUnsolicited(); 3389 } 3390 } 3391 3392 public void removeNamingListener(NamingListener l) throws NamingException { 3393 if (eventSupport == null) 3394 return; // no activity before, so just return 3395 3396 eventSupport.removeNamingListener(l); 3397 3398 // If removing an Unsol listener and it is the last one, let clnt know 3399 if (l instanceof UnsolicitedNotificationListener && 3400 !eventSupport.hasUnsolicited()) { 3401 removeUnsolicited(); 3402 } 3403 } 3404 3405 public void addNamingListener(String nm, String filter, SearchControls ctls, 3406 NamingListener l) throws NamingException { 3407 if (eventSupport == null) 3408 eventSupport = new EventSupport(this); 3409 eventSupport.addNamingListener(getTargetName(new CompositeName(nm)), 3410 filter, cloneSearchControls(ctls), l); 3411 3412 // If first time asking for unsol 3413 if (l instanceof UnsolicitedNotificationListener && !unsolicited) { 3414 addUnsolicited(); 3415 } 3416 } 3417 3418 public void addNamingListener(Name nm, String filter, SearchControls ctls, 3419 NamingListener l) throws NamingException { 3420 addNamingListener(getTargetName(nm), filter, ctls, l); 3421 } 3422 3423 public void addNamingListener(Name nm, String filter, Object[] filterArgs, 3424 SearchControls ctls, NamingListener l) throws NamingException { 3425 addNamingListener(getTargetName(nm), filter, filterArgs, ctls, l); 3426 } 3427 3428 public void addNamingListener(String nm, String filterExpr, Object[] filterArgs, 3429 SearchControls ctls, NamingListener l) throws NamingException { 3430 String strfilter = SearchFilter.format(filterExpr, filterArgs); 3431 addNamingListener(getTargetName(new CompositeName(nm)), strfilter, ctls, l); 3432 } 3433 3434 public boolean targetMustExist() { 3435 return true; 3436 } 3437 3438 /** 3439 * Retrieves the target name for which the listener is registering. 3440 * If nm is a CompositeName, use its first and only component. It 3441 * cannot have more than one components because a target be outside of 3442 * this namespace. If nm is not a CompositeName, then treat it as a 3443 * compound name. 3444 * @param nm The non-null target name. 3445 */ 3446 private static String getTargetName(Name nm) throws NamingException { 3447 if (nm instanceof CompositeName) { 3448 if (nm.size() > 1) { 3449 throw new InvalidNameException( 3450 "Target cannot span multiple namespaces: " + nm); 3451 } else if (nm.size() == 0) { 3452 return ""; 3453 } else { 3454 return nm.get(0); 3455 } 3456 } else { 3457 // treat as compound name 3458 return nm.toString(); 3459 } 3460 } 3461 3462 // ------------------ Unsolicited Notification --------------- 3463 // package private methods for handling unsolicited notification 3464 3465 /** 3466 * Registers this context with the underlying LdapClient. 3467 * When the underlying LdapClient receives an unsolicited notification, 3468 * it will invoke LdapCtx.fireUnsolicited() so that this context 3469 * can (using EventSupport) notified any registered listeners. 3470 * This method is called by EventSupport when an unsolicited listener 3471 * first registers with this context (should be called just once). 3472 * @see #removeUnsolicited 3473 * @see #fireUnsolicited 3474 */ 3475 private void addUnsolicited() throws NamingException { 3476 if (debug) { 3477 System.out.println("LdapCtx.addUnsolicited: " + this); 3478 } 3479 3480 // addNamingListener must have created EventSupport already 3481 ensureOpen(); 3482 synchronized (eventSupport) { 3483 clnt.addUnsolicited(this); 3484 unsolicited = true; 3485 } 3486 } 3487 3488 /** 3489 * Removes this context from registering interest in unsolicited 3490 * notifications from the underlying LdapClient. This method is called 3491 * under any one of the following conditions: 3492 * <ul> 3493 * <li>All unsolicited listeners have been removed. (see removingNamingListener) 3494 * <li>This context is closed. 3495 * <li>This context's underlying LdapClient changes. 3496 *</ul> 3497 * After this method has been called, this context will not pass 3498 * on any events related to unsolicited notifications to EventSupport and 3499 * and its listeners. 3500 */ 3501 3502 private void removeUnsolicited() { 3503 if (debug) { 3504 System.out.println("LdapCtx.removeUnsolicited: " + unsolicited); 3505 } 3506 if (eventSupport == null) { 3507 return; 3508 } 3509 3510 // addNamingListener must have created EventSupport already 3511 synchronized(eventSupport) { 3512 if (unsolicited && clnt != null) { 3513 clnt.removeUnsolicited(this); 3514 } 3515 unsolicited = false; 3516 } 3517 } 3518 3519 /** 3520 * Uses EventSupport to fire an event related to an unsolicited notification. 3521 * Called by LdapClient when LdapClient receives an unsolicited notification. 3522 */ 3523 void fireUnsolicited(Object obj) { 3524 if (debug) { 3525 System.out.println("LdapCtx.fireUnsolicited: " + obj); 3526 } 3527 // addNamingListener must have created EventSupport already 3528 synchronized(eventSupport) { 3529 if (unsolicited) { 3530 eventSupport.fireUnsolicited(obj); 3531 3532 if (obj instanceof NamingException) { 3533 unsolicited = false; 3534 // No need to notify clnt because clnt is the 3535 // only one that can fire a NamingException to 3536 // unsol listeners and it will handle its own cleanup 3537 } 3538 } 3539 } 3540 } 3541 }