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