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