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