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