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