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