1 /*
   2  * Copyright (c) 1999, 2018, 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 java.io.*;
  29 import java.util.Locale;
  30 import java.util.Vector;
  31 import java.util.Hashtable;
  32 
  33 import javax.naming.*;
  34 import javax.naming.directory.*;
  35 import javax.naming.ldap.*;
  36 
  37 import com.sun.jndi.ldap.pool.PooledConnection;
  38 import com.sun.jndi.ldap.pool.PoolCallback;
  39 import com.sun.jndi.ldap.sasl.LdapSasl;
  40 import com.sun.jndi.ldap.sasl.SaslInputStream;
  41 
  42 /**
  43  * LDAP (RFC-1777) and LDAPv3 (RFC-2251) compliant client
  44  *
  45  * This class represents a connection to an LDAP client.
  46  * Callers interact with this class at an LDAP operation level.
  47  * That is, the caller invokes a method to do a SEARCH or MODRDN
  48  * operation and gets back the result.
  49  * The caller uses the constructor to create a connection to the server.
  50  * It then needs to use authenticate() to perform an LDAP BIND.
  51  * Note that for v3, BIND is optional so authenticate() might not
  52  * actually send a BIND. authenticate() can be used later on to issue
  53  * a BIND, for example, for a v3 client that wants to change the connection's
  54  * credentials.
  55  *<p>
  56  * Multiple LdapCtx might share the same LdapClient. For example, contexts
  57  * derived from the same initial context would share the same LdapClient
  58  * until changes to a context's properties necessitates its own LdapClient.
  59  * LdapClient methods that access shared data are thread-safe (i.e., caller
  60  * does not have to sync).
  61  *<p>
  62  * Fields:
  63  *   isLdapv3 - no sync; initialized and updated within sync authenticate();
  64  *       always updated when connection is "quiet" and not shared;
  65  *       read access from outside LdapClient not sync
  66  *   referenceCount - sync within LdapClient; exception is forceClose() which
  67  *       is used by Connection thread to close connection upon receiving
  68  *       an Unsolicited Notification.
  69  *       access from outside LdapClient must sync;
  70  *   conn - no sync; Connection takes care of its own sync
  71  *   unsolicited - sync Vector; multiple operations sync'ed
  72  *
  73  * @author Vincent Ryan
  74  * @author Jagane Sundar
  75  * @author Rosanna Lee
  76  */
  77 
  78 public final class LdapClient implements PooledConnection {
  79     // ---------------------- Constants ----------------------------------
  80     private static final int debug = 0;
  81     static final boolean caseIgnore = true;
  82 
  83     // Default list of binary attributes
  84     private static final Hashtable<String, Boolean> defaultBinaryAttrs =
  85             new Hashtable<>(23,0.75f);
  86     static {
  87         defaultBinaryAttrs.put("userpassword", Boolean.TRUE);      //2.5.4.35
  88         defaultBinaryAttrs.put("javaserializeddata", Boolean.TRUE);
  89                                                 //1.3.6.1.4.1.42.2.27.4.1.8
  90         defaultBinaryAttrs.put("javaserializedobject", Boolean.TRUE);
  91                                                 // 1.3.6.1.4.1.42.2.27.4.1.2
  92         defaultBinaryAttrs.put("jpegphoto", Boolean.TRUE);
  93                                                 //0.9.2342.19200300.100.1.60
  94         defaultBinaryAttrs.put("audio", Boolean.TRUE);  //0.9.2342.19200300.100.1.55
  95         defaultBinaryAttrs.put("thumbnailphoto", Boolean.TRUE);
  96                                                 //1.3.6.1.4.1.1466.101.120.35
  97         defaultBinaryAttrs.put("thumbnaillogo", Boolean.TRUE);
  98                                                 //1.3.6.1.4.1.1466.101.120.36
  99         defaultBinaryAttrs.put("usercertificate", Boolean.TRUE);     //2.5.4.36
 100         defaultBinaryAttrs.put("cacertificate", Boolean.TRUE);       //2.5.4.37
 101         defaultBinaryAttrs.put("certificaterevocationlist", Boolean.TRUE);
 102                                                 //2.5.4.39
 103         defaultBinaryAttrs.put("authorityrevocationlist", Boolean.TRUE); //2.5.4.38
 104         defaultBinaryAttrs.put("crosscertificatepair", Boolean.TRUE);    //2.5.4.40
 105         defaultBinaryAttrs.put("photo", Boolean.TRUE);   //0.9.2342.19200300.100.1.7
 106         defaultBinaryAttrs.put("personalsignature", Boolean.TRUE);
 107                                                 //0.9.2342.19200300.100.1.53
 108         defaultBinaryAttrs.put("x500uniqueidentifier", Boolean.TRUE); //2.5.4.45
 109     }
 110 
 111     private static final String DISCONNECT_OID = "1.3.6.1.4.1.1466.20036";
 112 
 113 
 114     // ----------------------- instance fields ------------------------
 115     boolean isLdapv3;         // Used by LdapCtx
 116     int referenceCount = 1;   // Used by LdapCtx for check for sharing
 117 
 118     Connection conn;  // Connection to server; has reader thread
 119                       // used by LdapCtx for StartTLS
 120 
 121     final private PoolCallback pcb;
 122     final private boolean pooled;
 123     private boolean authenticateCalled = false;
 124 
 125     ////////////////////////////////////////////////////////////////////////////
 126     //
 127     // constructor: Create an authenticated connection to server
 128     //
 129     ////////////////////////////////////////////////////////////////////////////
 130 
 131     LdapClient(String host, int port, String socketFactory,
 132         int connectTimeout, int readTimeout, OutputStream trace, PoolCallback pcb)
 133         throws NamingException {
 134 
 135         if (debug > 0)
 136             System.err.println("LdapClient: constructor called " + host + ":" + port );
 137         conn = new Connection(this, host, port, socketFactory, connectTimeout, readTimeout,
 138             trace);
 139 
 140         this.pcb = pcb;
 141         pooled = (pcb != null);
 142     }
 143 
 144     synchronized boolean authenticateCalled() {
 145         return authenticateCalled;
 146     }
 147 
 148     synchronized LdapResult
 149     authenticate(boolean initial, String name, Object pw, int version,
 150         String authMechanism, Control[] ctls,  Hashtable<?,?> env)
 151         throws NamingException {
 152 
 153         int readTimeout = conn.readTimeout;
 154         conn.readTimeout = conn.connectTimeout;
 155         LdapResult res = null;
 156 
 157         try {
 158             authenticateCalled = true;
 159 
 160             try {
 161                 ensureOpen();
 162             } catch (IOException e) {
 163                 NamingException ne = new CommunicationException();
 164                 ne.setRootCause(e);
 165                 throw ne;
 166             }
 167 
 168             switch (version) {
 169             case LDAP_VERSION3_VERSION2:
 170             case LDAP_VERSION3:
 171                 isLdapv3 = true;
 172                 break;
 173             case LDAP_VERSION2:
 174                 isLdapv3 = false;
 175                 break;
 176             default:
 177                 throw new CommunicationException("Protocol version " + version +
 178                     " not supported");
 179             }
 180 
 181             if (authMechanism.equalsIgnoreCase("none") ||
 182                 authMechanism.equalsIgnoreCase("anonymous")) {
 183 
 184                 // Perform LDAP bind if we are reauthenticating, using LDAPv2,
 185                 // supporting failover to LDAPv2, or controls have been supplied.
 186                 if (!initial ||
 187                     (version == LDAP_VERSION2) ||
 188                     (version == LDAP_VERSION3_VERSION2) ||
 189                     ((ctls != null) && (ctls.length > 0))) {
 190                     try {
 191                         // anonymous bind; update name/pw for LDAPv2 retry
 192                         res = ldapBind(name=null, (byte[])(pw=null), ctls, null,
 193                             false);
 194                         if (res.status == LdapClient.LDAP_SUCCESS) {
 195                             conn.setBound();
 196                         }
 197                     } catch (IOException e) {
 198                         NamingException ne =
 199                             new CommunicationException("anonymous bind failed: " +
 200                             conn.host + ":" + conn.port);
 201                         ne.setRootCause(e);
 202                         throw ne;
 203                     }
 204                 } else {
 205                     // Skip LDAP bind for LDAPv3 anonymous bind
 206                     res = new LdapResult();
 207                     res.status = LdapClient.LDAP_SUCCESS;
 208                 }
 209             } else if (authMechanism.equalsIgnoreCase("simple")) {
 210                 // simple authentication
 211                 byte[] encodedPw = null;
 212                 try {
 213                     encodedPw = encodePassword(pw, isLdapv3);
 214                     res = ldapBind(name, encodedPw, ctls, null, false);
 215                     if (res.status == LdapClient.LDAP_SUCCESS) {
 216                         conn.setBound();
 217                     }
 218                 } catch (IOException e) {
 219                     NamingException ne =
 220                         new CommunicationException("simple bind failed: " +
 221                             conn.host + ":" + conn.port);
 222                     ne.setRootCause(e);
 223                     throw ne;
 224                 } finally {
 225                     // If pw was copied to a new array, clear that array as
 226                     // a security precaution.
 227                     if (encodedPw != pw && encodedPw != null) {
 228                         for (int i = 0; i < encodedPw.length; i++) {
 229                             encodedPw[i] = 0;
 230                         }
 231                     }
 232                 }
 233             } else if (isLdapv3) {
 234                 // SASL authentication
 235                 try {
 236                     res = LdapSasl.saslBind(this, conn, conn.host, name, pw,
 237                         authMechanism, env, ctls);
 238                     if (res.status == LdapClient.LDAP_SUCCESS) {
 239                         conn.setBound();
 240                     }
 241                 } catch (IOException e) {
 242                     NamingException ne =
 243                         new CommunicationException("SASL bind failed: " +
 244                         conn.host + ":" + conn.port);
 245                     ne.setRootCause(e);
 246                     throw ne;
 247                 }
 248             } else {
 249                 throw new AuthenticationNotSupportedException(authMechanism);
 250             }
 251 
 252             //
 253             // re-try login using v2 if failing over
 254             //
 255             if (initial &&
 256                 (res.status == LdapClient.LDAP_PROTOCOL_ERROR) &&
 257                 (version == LdapClient.LDAP_VERSION3_VERSION2) &&
 258                 (authMechanism.equalsIgnoreCase("none") ||
 259                     authMechanism.equalsIgnoreCase("anonymous") ||
 260                     authMechanism.equalsIgnoreCase("simple"))) {
 261 
 262                 byte[] encodedPw = null;
 263                 try {
 264                     isLdapv3 = false;
 265                     encodedPw = encodePassword(pw, false);
 266                     res = ldapBind(name, encodedPw, ctls, null, false);
 267                     if (res.status == LdapClient.LDAP_SUCCESS) {
 268                         conn.setBound();
 269                     }
 270                 } catch (IOException e) {
 271                     NamingException ne =
 272                         new CommunicationException(authMechanism + ":" +
 273                             conn.host +     ":" + conn.port);
 274                     ne.setRootCause(e);
 275                     throw ne;
 276                 } finally {
 277                     // If pw was copied to a new array, clear that array as
 278                     // a security precaution.
 279                     if (encodedPw != pw && encodedPw != null) {
 280                         for (int i = 0; i < encodedPw.length; i++) {
 281                             encodedPw[i] = 0;
 282                         }
 283                     }
 284                 }
 285             }
 286 
 287             // principal name not found
 288             // (map NameNotFoundException to AuthenticationException)
 289             // %%% This is a workaround for Netscape servers returning
 290             // %%% no such object when the principal name is not found
 291             // %%% Note that when this workaround is applied, it does not allow
 292             // %%% response controls to be recorded by the calling context
 293             if (res.status == LdapClient.LDAP_NO_SUCH_OBJECT) {
 294                 throw new AuthenticationException(
 295                     getErrorMessage(res.status, res.errorMessage));
 296             }
 297             conn.setV3(isLdapv3);
 298             return res;
 299         } finally {
 300             conn.readTimeout = readTimeout;
 301         }
 302     }
 303 
 304     /**
 305      * Sends an LDAP Bind request.
 306      * Cannot be private; called by LdapSasl
 307      * @param dn The possibly null DN to use in the BIND request. null if anonymous.
 308      * @param toServer The possibly null array of bytes to send to the server.
 309      * @param auth The authentication mechanism
 310      *
 311      */
 312     synchronized public LdapResult ldapBind(String dn, byte[]toServer,
 313         Control[] bindCtls, String auth, boolean pauseAfterReceipt)
 314         throws java.io.IOException, NamingException {
 315 
 316         ensureOpen();
 317 
 318         // flush outstanding requests
 319         conn.abandonOutstandingReqs(null);
 320 
 321         BerEncoder ber = new BerEncoder();
 322         int curMsgId = conn.getMsgId();
 323         LdapResult res = new LdapResult();
 324         res.status = LDAP_OPERATIONS_ERROR;
 325 
 326         //
 327         // build the bind request.
 328         //
 329         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 330             ber.encodeInt(curMsgId);
 331             ber.beginSeq(LdapClient.LDAP_REQ_BIND);
 332                 ber.encodeInt(isLdapv3 ? LDAP_VERSION3 : LDAP_VERSION2);
 333                 ber.encodeString(dn, isLdapv3);
 334 
 335                 // if authentication mechanism specified, it is SASL
 336                 if (auth != null) {
 337                     ber.beginSeq(Ber.ASN_CONTEXT | Ber.ASN_CONSTRUCTOR | 3);
 338                         ber.encodeString(auth, isLdapv3);    // SASL mechanism
 339                         if (toServer != null) {
 340                             ber.encodeOctetString(toServer,
 341                                 Ber.ASN_OCTET_STR);
 342                         }
 343                     ber.endSeq();
 344                 } else {
 345                     if (toServer != null) {
 346                         ber.encodeOctetString(toServer, Ber.ASN_CONTEXT);
 347                     } else {
 348                         ber.encodeOctetString(null, Ber.ASN_CONTEXT, 0, 0);
 349                     }
 350                 }
 351             ber.endSeq();
 352 
 353             // Encode controls
 354             if (isLdapv3) {
 355                 encodeControls(ber, bindCtls);
 356             }
 357         ber.endSeq();
 358 
 359         LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);
 360         if (toServer != null) {
 361             ber.reset();        // clear internally-stored password
 362         }
 363 
 364         // Read reply
 365         BerDecoder rber = conn.readReply(req);
 366 
 367         rber.parseSeq(null);    // init seq
 368         rber.parseInt();        // msg id
 369         if (rber.parseByte() !=  LDAP_REP_BIND) {
 370             return res;
 371         }
 372 
 373         rber.parseLength();
 374         parseResult(rber, res, isLdapv3);
 375 
 376         // handle server's credentials (if present)
 377         if (isLdapv3 &&
 378             (rber.bytesLeft() > 0) &&
 379             (rber.peekByte() == (Ber.ASN_CONTEXT | 7))) {
 380             res.serverCreds = rber.parseOctetString((Ber.ASN_CONTEXT | 7), null);
 381         }
 382 
 383         res.resControls = isLdapv3 ? parseControls(rber) : null;
 384 
 385         conn.removeRequest(req);
 386         return res;
 387     }
 388 
 389     /**
 390      * Determines whether SASL encryption/integrity is in progress.
 391      * This check is made prior to reauthentication. You cannot reauthenticate
 392      * over an encrypted/integrity-protected SASL channel. You must
 393      * close the channel and open a new one.
 394      */
 395     boolean usingSaslStreams() {
 396         return (conn.inStream instanceof SaslInputStream);
 397     }
 398 
 399     synchronized void incRefCount() {
 400         ++referenceCount;
 401         if (debug > 1) {
 402             System.err.println("LdapClient.incRefCount: " + referenceCount + " " + this);
 403         }
 404 
 405     }
 406 
 407     /**
 408      * Returns the encoded password.
 409      */
 410     private static byte[] encodePassword(Object pw, boolean v3) throws IOException {
 411 
 412         if (pw instanceof char[]) {
 413             pw = new String((char[])pw);
 414         }
 415 
 416         if (pw instanceof String) {
 417             if (v3) {
 418                 return ((String)pw).getBytes("UTF8");
 419             } else {
 420                 return ((String)pw).getBytes("8859_1");
 421             }
 422         } else {
 423             return (byte[])pw;
 424         }
 425     }
 426 
 427     synchronized void close(Control[] reqCtls, boolean hardClose) {
 428         --referenceCount;
 429 
 430         if (debug > 1) {
 431             System.err.println("LdapClient: " + this);
 432             System.err.println("LdapClient: close() called: " + referenceCount);
 433             (new Throwable()).printStackTrace();
 434         }
 435 
 436         if (referenceCount <= 0 && conn != null) {
 437             if (debug > 0) System.err.println("LdapClient: closed connection " + this);
 438             if (!pooled) {
 439                 // Not being pooled; continue with closing
 440                 conn.cleanup(reqCtls, false);
 441                 //conn = null;
 442             } else {
 443                 // Pooled
 444 
 445                 // Is this a real close or a request to return conn to pool
 446                 if (hardClose) {
 447                     conn.cleanup(reqCtls, false);
 448                     //conn = null;
 449                     pcb.removePooledConnection(this);
 450                 } else {
 451                     pcb.releasePooledConnection(this);
 452                 }
 453             }
 454         }
 455     }
 456 
 457     // NOTE: Should NOT be synchronized otherwise won't be able to close
 458     private void forceClose(boolean cleanPool) {
 459         referenceCount = 0; // force closing of connection
 460 
 461         if (debug > 1) {
 462             System.err.println("LdapClient: forceClose() of " + this);
 463         }
 464 
 465         if (conn != null) {
 466             if (debug > 0) System.err.println(
 467                 "LdapClient: forced close of connection " + this);
 468             conn.cleanup(null, false);
 469             //conn = null;
 470 
 471             if (cleanPool) {
 472                 pcb.removePooledConnection(this);
 473             }
 474         }
 475     }
 476 
 477     @SuppressWarnings("deprecation")
 478     protected void finalize() {
 479         if (debug > 0) System.err.println("LdapClient: finalize " + this);
 480         forceClose(pooled);
 481     }
 482 
 483     /*
 484      * Used by connection pooling to close physical connection.
 485      */
 486     synchronized public void closeConnection() {
 487         forceClose(false); // this is a pool callback so no need to clean pool
 488     }
 489 
 490     /**
 491      * Called by Connection.cleanup(). LdapClient should
 492      * notify any unsolicited listeners and removing itself from any pool.
 493      * This is almost like forceClose(), except it doesn't call
 494      * Connection.cleanup() (because this is called from cleanup()).
 495      */
 496     void processConnectionClosure() {
 497         // Notify listeners
 498         if (unsolicited.size() > 0) {
 499             String msg;
 500             if (conn != null) {
 501                 msg = conn.host + ":" + conn.port + " connection closed";
 502             } else {
 503                 msg = "Connection closed";
 504             }
 505             notifyUnsolicited(new CommunicationException(msg));
 506         }
 507 
 508         // Remove from pool
 509         if (pooled) {
 510             pcb.removePooledConnection(this);
 511         }
 512     }
 513 
 514     ////////////////////////////////////////////////////////////////////////////
 515     //
 516     // LDAP search. also includes methods to encode rfc 1558 compliant filters
 517     //
 518     ////////////////////////////////////////////////////////////////////////////
 519 
 520     static final int SCOPE_BASE_OBJECT = 0;
 521     static final int SCOPE_ONE_LEVEL = 1;
 522     static final int SCOPE_SUBTREE = 2;
 523 
 524     LdapResult search(String dn, int scope, int deref, int sizeLimit,
 525                       int timeLimit, boolean attrsOnly, String attrs[],
 526                       String filter, int batchSize, Control[] reqCtls,
 527                       Hashtable<String, Boolean> binaryAttrs,
 528                       boolean waitFirstReply, int replyQueueCapacity)
 529         throws IOException, NamingException {
 530 
 531         ensureOpen();
 532 
 533         LdapResult res = new LdapResult();
 534 
 535         BerEncoder ber = new BerEncoder();
 536         int curMsgId = conn.getMsgId();
 537 
 538             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 539                 ber.encodeInt(curMsgId);
 540                 ber.beginSeq(LDAP_REQ_SEARCH);
 541                     ber.encodeString(dn == null ? "" : dn, isLdapv3);
 542                     ber.encodeInt(scope, LBER_ENUMERATED);
 543                     ber.encodeInt(deref, LBER_ENUMERATED);
 544                     ber.encodeInt(sizeLimit);
 545                     ber.encodeInt(timeLimit);
 546                     ber.encodeBoolean(attrsOnly);
 547                     Filter.encodeFilterString(ber, filter, isLdapv3);
 548                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 549                         ber.encodeStringArray(attrs, isLdapv3);
 550                     ber.endSeq();
 551                 ber.endSeq();
 552                 if (isLdapv3) encodeControls(ber, reqCtls);
 553             ber.endSeq();
 554 
 555          LdapRequest req =
 556                 conn.writeRequest(ber, curMsgId, false, replyQueueCapacity);
 557 
 558          res.msgId = curMsgId;
 559          res.status = LdapClient.LDAP_SUCCESS; //optimistic
 560          if (waitFirstReply) {
 561              // get first reply
 562              res = getSearchReply(req, batchSize, res, binaryAttrs);
 563          }
 564          return res;
 565     }
 566 
 567     /*
 568      * Abandon the search operation and remove it from the message queue.
 569      */
 570     void clearSearchReply(LdapResult res, Control[] ctls) {
 571         if (res != null && conn != null) {
 572 
 573             // Only send an LDAP abandon operation when clearing the search
 574             // reply from a one-level or subtree search.
 575             LdapRequest req = conn.findRequest(res.msgId);
 576             if (req == null) {
 577                 return;
 578             }
 579 
 580             // OK if req got removed after check; double removal attempt
 581             // but otherwise no harm done
 582 
 583             // Send an LDAP abandon only if the search operation has not yet
 584             // completed.
 585             if (req.hasSearchCompleted()) {
 586                 conn.removeRequest(req);
 587             } else {
 588                 conn.abandonRequest(req, ctls);
 589             }
 590         }
 591     }
 592 
 593     /*
 594      * Retrieve the next batch of entries and/or referrals.
 595      */
 596     LdapResult getSearchReply(int batchSize, LdapResult res,
 597         Hashtable<String, Boolean> binaryAttrs) throws IOException, NamingException {
 598 
 599         ensureOpen();
 600 
 601         LdapRequest req;
 602 
 603         if ((req = conn.findRequest(res.msgId)) == null) {
 604             return null;
 605         }
 606 
 607         return getSearchReply(req, batchSize, res, binaryAttrs);
 608     }
 609 
 610     private LdapResult getSearchReply(LdapRequest req,
 611         int batchSize, LdapResult res, Hashtable<String, Boolean> binaryAttrs)
 612         throws IOException, NamingException {
 613 
 614         if (batchSize == 0)
 615             batchSize = Integer.MAX_VALUE;
 616 
 617         if (res.entries != null) {
 618             res.entries.setSize(0); // clear the (previous) set of entries
 619         } else {
 620             res.entries =
 621                 new Vector<>(batchSize == Integer.MAX_VALUE ? 32 : batchSize);
 622         }
 623 
 624         if (res.referrals != null) {
 625             res.referrals.setSize(0); // clear the (previous) set of referrals
 626         }
 627 
 628         BerDecoder replyBer;    // Decoder for response
 629         int seq;                // Request id
 630 
 631         Attributes lattrs;      // Attribute set read from response
 632         Attribute la;           // Attribute read from response
 633         String DN;              // DN read from response
 634         LdapEntry le;           // LDAP entry representing response
 635         int[] seqlen;           // Holder for response length
 636         int endseq;             // Position of end of response
 637 
 638         for (int i = 0; i < batchSize;) {
 639             replyBer = conn.readReply(req);
 640 
 641             //
 642             // process search reply
 643             //
 644             replyBer.parseSeq(null);                    // init seq
 645             replyBer.parseInt();                        // req id
 646             seq = replyBer.parseSeq(null);
 647 
 648             if (seq == LDAP_REP_SEARCH) {
 649 
 650                 // handle LDAPv3 search entries
 651                 lattrs = new BasicAttributes(caseIgnore);
 652                 DN = replyBer.parseString(isLdapv3);
 653                 le = new LdapEntry(DN, lattrs);
 654                 seqlen = new int[1];
 655 
 656                 replyBer.parseSeq(seqlen);
 657                 endseq = replyBer.getParsePosition() + seqlen[0];
 658                 while ((replyBer.getParsePosition() < endseq) &&
 659                     (replyBer.bytesLeft() > 0)) {
 660                     la = parseAttribute(replyBer, binaryAttrs);
 661                     lattrs.put(la);
 662                 }
 663                 le.respCtls = isLdapv3 ? parseControls(replyBer) : null;
 664 
 665                 res.entries.addElement(le);
 666                 i++;
 667 
 668             } else if ((seq == LDAP_REP_SEARCH_REF) && isLdapv3) {
 669 
 670                 // handle LDAPv3 search reference
 671                 Vector<String> URLs = new Vector<>(4);
 672 
 673                 // %%% Although not strictly correct, some LDAP servers
 674                 //     encode the SEQUENCE OF tag in the SearchResultRef
 675                 if (replyBer.peekByte() ==
 676                     (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {
 677                     replyBer.parseSeq(null);
 678                 }
 679 
 680                 while ((replyBer.bytesLeft() > 0) &&
 681                     (replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
 682 
 683                     URLs.addElement(replyBer.parseString(isLdapv3));
 684                 }
 685 
 686                 if (res.referrals == null) {
 687                     res.referrals = new Vector<>(4);
 688                 }
 689                 res.referrals.addElement(URLs);
 690                 res.resControls = isLdapv3 ? parseControls(replyBer) : null;
 691 
 692                 // Save referral and continue to get next search result
 693 
 694             } else if (seq == LDAP_REP_EXTENSION) {
 695 
 696                 parseExtResponse(replyBer, res); //%%% ignore for now
 697 
 698             } else if (seq == LDAP_REP_RESULT) {
 699 
 700                 parseResult(replyBer, res, isLdapv3);
 701                 res.resControls = isLdapv3 ? parseControls(replyBer) : null;
 702 
 703                 conn.removeRequest(req);
 704                 return res;     // Done with search
 705             }
 706         }
 707 
 708         return res;
 709     }
 710 
 711     private Attribute parseAttribute(BerDecoder ber,
 712                                      Hashtable<String, Boolean> binaryAttrs)
 713         throws IOException {
 714 
 715         int len[] = new int[1];
 716         int seq = ber.parseSeq(null);
 717         String attrid = ber.parseString(isLdapv3);
 718         boolean hasBinaryValues = isBinaryValued(attrid, binaryAttrs);
 719         Attribute la = new LdapAttribute(attrid);
 720 
 721         if ((seq = ber.parseSeq(len)) == LBER_SET) {
 722             int attrlen = len[0];
 723             while (ber.bytesLeft() > 0 && attrlen > 0) {
 724                 try {
 725                     attrlen -= parseAttributeValue(ber, la, hasBinaryValues);
 726                 } catch (IOException ex) {
 727                     ber.seek(attrlen);
 728                     break;
 729                 }
 730             }
 731         } else {
 732             // Skip the rest of the sequence because it is not what we want
 733             ber.seek(len[0]);
 734         }
 735         return la;
 736     }
 737 
 738     //
 739     // returns number of bytes that were parsed. Adds the values to attr
 740     //
 741     private int parseAttributeValue(BerDecoder ber, Attribute la,
 742         boolean hasBinaryValues) throws IOException {
 743 
 744         int len[] = new int[1];
 745 
 746         if (hasBinaryValues) {
 747             la.add(ber.parseOctetString(ber.peekByte(), len));
 748         } else {
 749             la.add(ber.parseStringWithTag(
 750                                     Ber.ASN_SIMPLE_STRING, isLdapv3, len));
 751         }
 752         return len[0];
 753     }
 754 
 755     private boolean isBinaryValued(String attrid,
 756                                    Hashtable<String, Boolean> binaryAttrs) {
 757         String id = attrid.toLowerCase(Locale.ENGLISH);
 758 
 759         return ((id.indexOf(";binary") != -1) ||
 760             defaultBinaryAttrs.containsKey(id) ||
 761             ((binaryAttrs != null) && (binaryAttrs.containsKey(id))));
 762     }
 763 
 764     // package entry point; used by Connection
 765     static void parseResult(BerDecoder replyBer, LdapResult res,
 766             boolean isLdapv3) throws IOException {
 767 
 768         res.status = replyBer.parseEnumeration();
 769         res.matchedDN = replyBer.parseString(isLdapv3);
 770         res.errorMessage = replyBer.parseString(isLdapv3);
 771 
 772         // handle LDAPv3 referrals (if present)
 773         if (isLdapv3 &&
 774             (replyBer.bytesLeft() > 0) &&
 775             (replyBer.peekByte() == LDAP_REP_REFERRAL)) {
 776 
 777             Vector<String> URLs = new Vector<>(4);
 778             int[] seqlen = new int[1];
 779 
 780             replyBer.parseSeq(seqlen);
 781             int endseq = replyBer.getParsePosition() + seqlen[0];
 782             while ((replyBer.getParsePosition() < endseq) &&
 783                 (replyBer.bytesLeft() > 0)) {
 784 
 785                 URLs.addElement(replyBer.parseString(isLdapv3));
 786             }
 787 
 788             if (res.referrals == null) {
 789                 res.referrals = new Vector<>(4);
 790             }
 791             res.referrals.addElement(URLs);
 792         }
 793     }
 794 
 795     // package entry point; used by Connection
 796     static Vector<Control> parseControls(BerDecoder replyBer) throws IOException {
 797 
 798         // handle LDAPv3 controls (if present)
 799         if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) {
 800             Vector<Control> ctls = new Vector<>(4);
 801             String controlOID;
 802             boolean criticality = false; // default
 803             byte[] controlValue = null;  // optional
 804             int[] seqlen = new int[1];
 805 
 806             replyBer.parseSeq(seqlen);
 807             int endseq = replyBer.getParsePosition() + seqlen[0];
 808             while ((replyBer.getParsePosition() < endseq) &&
 809                 (replyBer.bytesLeft() > 0)) {
 810 
 811                 replyBer.parseSeq(null);
 812                 controlOID = replyBer.parseString(true);
 813 
 814                 if ((replyBer.bytesLeft() > 0) &&
 815                     (replyBer.peekByte() == Ber.ASN_BOOLEAN)) {
 816                     criticality = replyBer.parseBoolean();
 817                 }
 818                 if ((replyBer.bytesLeft() > 0) &&
 819                     (replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
 820                     controlValue =
 821                         replyBer.parseOctetString(Ber.ASN_OCTET_STR, null);
 822                 }
 823                 if (controlOID != null) {
 824                     ctls.addElement(
 825                         new BasicControl(controlOID, criticality, controlValue));
 826                 }
 827             }
 828             return ctls;
 829         } else {
 830             return null;
 831         }
 832     }
 833 
 834     private void parseExtResponse(BerDecoder replyBer, LdapResult res)
 835         throws IOException {
 836 
 837         parseResult(replyBer, res, isLdapv3);
 838 
 839         if ((replyBer.bytesLeft() > 0) &&
 840             (replyBer.peekByte() == LDAP_REP_EXT_OID)) {
 841             res.extensionId =
 842                 replyBer.parseStringWithTag(LDAP_REP_EXT_OID, isLdapv3, null);
 843         }
 844         if ((replyBer.bytesLeft() > 0) &&
 845             (replyBer.peekByte() == LDAP_REP_EXT_VAL)) {
 846             res.extensionValue =
 847                 replyBer.parseOctetString(LDAP_REP_EXT_VAL, null);
 848         }
 849 
 850         res.resControls = parseControls(replyBer);
 851     }
 852 
 853     //
 854     // Encode LDAPv3 controls
 855     //
 856     static void encodeControls(BerEncoder ber, Control[] reqCtls)
 857         throws IOException {
 858 
 859         if ((reqCtls == null) || (reqCtls.length == 0)) {
 860             return;
 861         }
 862 
 863         byte[] controlVal;
 864 
 865         ber.beginSeq(LdapClient.LDAP_CONTROLS);
 866 
 867             for (int i = 0; i < reqCtls.length; i++) {
 868                 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 869                     ber.encodeString(reqCtls[i].getID(), true); // control OID
 870                     if (reqCtls[i].isCritical()) {
 871                         ber.encodeBoolean(true); // critical control
 872                     }
 873                     if ((controlVal = reqCtls[i].getEncodedValue()) != null) {
 874                         ber.encodeOctetString(controlVal, Ber.ASN_OCTET_STR);
 875                     }
 876                 ber.endSeq();
 877             }
 878         ber.endSeq();
 879     }
 880 
 881     /**
 882      * Reads the next reply corresponding to msgId, outstanding on requestBer.
 883      * Processes the result and any controls.
 884      */
 885     private LdapResult processReply(LdapRequest req,
 886         LdapResult res, int responseType) throws IOException, NamingException {
 887 
 888         BerDecoder rber = conn.readReply(req);
 889 
 890         rber.parseSeq(null);    // init seq
 891         rber.parseInt();        // msg id
 892         if (rber.parseByte() !=  responseType) {
 893             return res;
 894         }
 895 
 896         rber.parseLength();
 897         parseResult(rber, res, isLdapv3);
 898         res.resControls = isLdapv3 ? parseControls(rber) : null;
 899 
 900         conn.removeRequest(req);
 901 
 902         return res;     // Done with operation
 903     }
 904 
 905     ////////////////////////////////////////////////////////////////////////////
 906     //
 907     // LDAP modify:
 908     //  Modify the DN dn with the operations on attributes attrs.
 909     //  ie, operations[0] is the operation to be performed on
 910     //  attrs[0];
 911     //          dn - DN to modify
 912     //          operations - add, delete or replace
 913     //          attrs - array of Attribute
 914     //          reqCtls - array of request controls
 915     //
 916     ////////////////////////////////////////////////////////////////////////////
 917 
 918     static final int ADD = 0;
 919     static final int DELETE = 1;
 920     static final int REPLACE = 2;
 921 
 922     LdapResult modify(String dn, int operations[], Attribute attrs[],
 923                       Control[] reqCtls)
 924         throws IOException, NamingException {
 925 
 926         ensureOpen();
 927 
 928         LdapResult res = new LdapResult();
 929         res.status = LDAP_OPERATIONS_ERROR;
 930 
 931         if (dn == null || operations.length != attrs.length)
 932             return res;
 933 
 934         BerEncoder ber = new BerEncoder();
 935         int curMsgId = conn.getMsgId();
 936 
 937         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 938             ber.encodeInt(curMsgId);
 939             ber.beginSeq(LDAP_REQ_MODIFY);
 940                 ber.encodeString(dn, isLdapv3);
 941                 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 942                     for (int i = 0; i < operations.length; i++) {
 943                         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 944                             ber.encodeInt(operations[i], LBER_ENUMERATED);
 945 
 946                             // zero values is not permitted for the add op.
 947                             if ((operations[i] == ADD) && hasNoValue(attrs[i])) {
 948                                 throw new InvalidAttributeValueException(
 949                                     "'" + attrs[i].getID() + "' has no values.");
 950                             } else {
 951                                 encodeAttribute(ber, attrs[i]);
 952                             }
 953                         ber.endSeq();
 954                     }
 955                 ber.endSeq();
 956             ber.endSeq();
 957             if (isLdapv3) encodeControls(ber, reqCtls);
 958         ber.endSeq();
 959 
 960         LdapRequest req = conn.writeRequest(ber, curMsgId);
 961 
 962         return processReply(req, res, LDAP_REP_MODIFY);
 963     }
 964 
 965     private void encodeAttribute(BerEncoder ber, Attribute attr)
 966         throws IOException, NamingException {
 967 
 968         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 969             ber.encodeString(attr.getID(), isLdapv3);
 970             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR | 1);
 971                 NamingEnumeration<?> enum_ = attr.getAll();
 972                 Object val;
 973                 while (enum_.hasMore()) {
 974                     val = enum_.next();
 975                     if (val instanceof String) {
 976                         ber.encodeString((String)val, isLdapv3);
 977                     } else if (val instanceof byte[]) {
 978                         ber.encodeOctetString((byte[])val, Ber.ASN_OCTET_STR);
 979                     } else if (val == null) {
 980                         // no attribute value
 981                     } else {
 982                         throw new InvalidAttributeValueException(
 983                             "Malformed '" + attr.getID() + "' attribute value");
 984                     }
 985                 }
 986             ber.endSeq();
 987         ber.endSeq();
 988     }
 989 
 990     private static boolean hasNoValue(Attribute attr) throws NamingException {
 991         return attr.size() == 0 || (attr.size() == 1 && attr.get() == null);
 992     }
 993 
 994     ////////////////////////////////////////////////////////////////////////////
 995     //
 996     // LDAP add
 997     //          Adds entry to the Directory
 998     //
 999     ////////////////////////////////////////////////////////////////////////////
1000 
1001     LdapResult add(LdapEntry entry, Control[] reqCtls)
1002         throws IOException, NamingException {
1003 
1004         ensureOpen();
1005 
1006         LdapResult res = new LdapResult();
1007         res.status = LDAP_OPERATIONS_ERROR;
1008 
1009         if (entry == null || entry.DN == null)
1010             return res;
1011 
1012         BerEncoder ber = new BerEncoder();
1013         int curMsgId = conn.getMsgId();
1014         Attribute attr;
1015 
1016             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1017                 ber.encodeInt(curMsgId);
1018                 ber.beginSeq(LDAP_REQ_ADD);
1019                     ber.encodeString(entry.DN, isLdapv3);
1020                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1021                         NamingEnumeration<? extends Attribute> enum_ =
1022                                 entry.attributes.getAll();
1023                         while (enum_.hasMore()) {
1024                             attr = enum_.next();
1025 
1026                             // zero values is not permitted
1027                             if (hasNoValue(attr)) {
1028                                 throw new InvalidAttributeValueException(
1029                                     "'" + attr.getID() + "' has no values.");
1030                             } else {
1031                                 encodeAttribute(ber, attr);
1032                             }
1033                         }
1034                     ber.endSeq();
1035                 ber.endSeq();
1036                 if (isLdapv3) encodeControls(ber, reqCtls);
1037             ber.endSeq();
1038 
1039         LdapRequest req = conn.writeRequest(ber, curMsgId);
1040         return processReply(req, res, LDAP_REP_ADD);
1041     }
1042 
1043     ////////////////////////////////////////////////////////////////////////////
1044     //
1045     // LDAP delete
1046     //          deletes entry from the Directory
1047     //
1048     ////////////////////////////////////////////////////////////////////////////
1049 
1050     LdapResult delete(String DN, Control[] reqCtls)
1051         throws IOException, NamingException {
1052 
1053         ensureOpen();
1054 
1055         LdapResult res = new LdapResult();
1056         res.status = LDAP_OPERATIONS_ERROR;
1057 
1058         if (DN == null)
1059             return res;
1060 
1061         BerEncoder ber = new BerEncoder();
1062         int curMsgId = conn.getMsgId();
1063 
1064             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1065                 ber.encodeInt(curMsgId);
1066                 ber.encodeString(DN, LDAP_REQ_DELETE, isLdapv3);
1067                 if (isLdapv3) encodeControls(ber, reqCtls);
1068             ber.endSeq();
1069 
1070         LdapRequest req = conn.writeRequest(ber, curMsgId);
1071 
1072         return processReply(req, res, LDAP_REP_DELETE);
1073     }
1074 
1075     ////////////////////////////////////////////////////////////////////////////
1076     //
1077     // LDAP modrdn
1078     //  Changes the last element of DN to newrdn
1079     //          dn - DN to change
1080     //          newrdn - new RDN to rename to
1081     //          deleteoldrdn - boolean whether to delete old attrs or not
1082     //          newSuperior - new place to put the entry in the tree
1083     //                        (ignored if server is LDAPv2)
1084     //          reqCtls - array of request controls
1085     //
1086     ////////////////////////////////////////////////////////////////////////////
1087 
1088     LdapResult moddn(String DN, String newrdn, boolean deleteOldRdn,
1089                      String newSuperior, Control[] reqCtls)
1090         throws IOException, NamingException {
1091 
1092         ensureOpen();
1093 
1094         boolean changeSuperior = (newSuperior != null &&
1095                                   newSuperior.length() > 0);
1096 
1097         LdapResult res = new LdapResult();
1098         res.status = LDAP_OPERATIONS_ERROR;
1099 
1100         if (DN == null || newrdn == null)
1101             return res;
1102 
1103         BerEncoder ber = new BerEncoder();
1104         int curMsgId = conn.getMsgId();
1105 
1106             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1107                 ber.encodeInt(curMsgId);
1108                 ber.beginSeq(LDAP_REQ_MODRDN);
1109                     ber.encodeString(DN, isLdapv3);
1110                     ber.encodeString(newrdn, isLdapv3);
1111                     ber.encodeBoolean(deleteOldRdn);
1112                     if(isLdapv3 && changeSuperior) {
1113                         //System.err.println("changin superior");
1114                         ber.encodeString(newSuperior, LDAP_SUPERIOR_DN, isLdapv3);
1115                     }
1116                 ber.endSeq();
1117                 if (isLdapv3) encodeControls(ber, reqCtls);
1118             ber.endSeq();
1119 
1120 
1121         LdapRequest req = conn.writeRequest(ber, curMsgId);
1122 
1123         return processReply(req, res, LDAP_REP_MODRDN);
1124     }
1125 
1126     ////////////////////////////////////////////////////////////////////////////
1127     //
1128     // LDAP compare
1129     //  Compare attribute->value pairs in dn
1130     //
1131     ////////////////////////////////////////////////////////////////////////////
1132 
1133     LdapResult compare(String DN, String type, String value, Control[] reqCtls)
1134         throws IOException, NamingException {
1135 
1136         ensureOpen();
1137 
1138         LdapResult res = new LdapResult();
1139         res.status = LDAP_OPERATIONS_ERROR;
1140 
1141         if (DN == null || type == null || value == null)
1142             return res;
1143 
1144         BerEncoder ber = new BerEncoder();
1145         int curMsgId = conn.getMsgId();
1146 
1147             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1148                 ber.encodeInt(curMsgId);
1149                 ber.beginSeq(LDAP_REQ_COMPARE);
1150                     ber.encodeString(DN, isLdapv3);
1151                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1152                         ber.encodeString(type, isLdapv3);
1153 
1154                         // replace any escaped characters in the value
1155                         byte[] val = isLdapv3 ?
1156                             value.getBytes("UTF8") : value.getBytes("8859_1");
1157                         ber.encodeOctetString(
1158                             Filter.unescapeFilterValue(val, 0, val.length),
1159                             Ber.ASN_OCTET_STR);
1160 
1161                     ber.endSeq();
1162                 ber.endSeq();
1163                 if (isLdapv3) encodeControls(ber, reqCtls);
1164             ber.endSeq();
1165 
1166         LdapRequest req = conn.writeRequest(ber, curMsgId);
1167 
1168         return processReply(req, res, LDAP_REP_COMPARE);
1169     }
1170 
1171     ////////////////////////////////////////////////////////////////////////////
1172     //
1173     // LDAP extended operation
1174     //
1175     ////////////////////////////////////////////////////////////////////////////
1176 
1177     LdapResult extendedOp(String id, byte[] request, Control[] reqCtls,
1178         boolean pauseAfterReceipt) throws IOException, NamingException {
1179 
1180         ensureOpen();
1181 
1182         LdapResult res = new LdapResult();
1183         res.status = LDAP_OPERATIONS_ERROR;
1184 
1185         if (id == null)
1186             return res;
1187 
1188         BerEncoder ber = new BerEncoder();
1189         int curMsgId = conn.getMsgId();
1190 
1191             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1192                 ber.encodeInt(curMsgId);
1193                 ber.beginSeq(LDAP_REQ_EXTENSION);
1194                     ber.encodeString(id,
1195                         Ber.ASN_CONTEXT | 0, isLdapv3);//[0]
1196                     if (request != null) {
1197                         ber.encodeOctetString(request,
1198                             Ber.ASN_CONTEXT | 1);//[1]
1199                     }
1200                 ber.endSeq();
1201                 encodeControls(ber, reqCtls); // always v3
1202             ber.endSeq();
1203 
1204         LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);
1205 
1206         BerDecoder rber = conn.readReply(req);
1207 
1208         rber.parseSeq(null);    // init seq
1209         rber.parseInt();        // msg id
1210         if (rber.parseByte() !=  LDAP_REP_EXTENSION) {
1211             return res;
1212         }
1213 
1214         rber.parseLength();
1215         parseExtResponse(rber, res);
1216         conn.removeRequest(req);
1217 
1218         return res;     // Done with operation
1219     }
1220 
1221 
1222 
1223     ////////////////////////////////////////////////////////////////////////////
1224     //
1225     // Some BER definitions convenient for LDAP
1226     //
1227     ////////////////////////////////////////////////////////////////////////////
1228 
1229     static final int LDAP_VERSION3_VERSION2 = 32;
1230     static final int LDAP_VERSION2 = 0x02;
1231     static final int LDAP_VERSION3 = 0x03;              // LDAPv3
1232     static final int LDAP_VERSION = LDAP_VERSION3;
1233 
1234     static final int LDAP_REF_FOLLOW = 0x01;            // follow referrals
1235     static final int LDAP_REF_THROW = 0x02;             // throw referral ex.
1236     static final int LDAP_REF_IGNORE = 0x03;            // ignore referrals
1237     static final int LDAP_REF_FOLLOW_SCHEME = 0x04;     // follow referrals of the same scheme
1238 
1239     static final String LDAP_URL = "ldap://";           // LDAPv3
1240     static final String LDAPS_URL = "ldaps://";         // LDAPv3
1241 
1242     static final int LBER_BOOLEAN = 0x01;
1243     static final int LBER_INTEGER = 0x02;
1244     static final int LBER_BITSTRING = 0x03;
1245     static final int LBER_OCTETSTRING = 0x04;
1246     static final int LBER_NULL = 0x05;
1247     static final int LBER_ENUMERATED = 0x0a;
1248     static final int LBER_SEQUENCE = 0x30;
1249     static final int LBER_SET = 0x31;
1250 
1251     static final int LDAP_SUPERIOR_DN = 0x80;
1252 
1253     static final int LDAP_REQ_BIND = 0x60;      // app + constructed
1254     static final int LDAP_REQ_UNBIND = 0x42;    // app + primitive
1255     static final int LDAP_REQ_SEARCH = 0x63;    // app + constructed
1256     static final int LDAP_REQ_MODIFY = 0x66;    // app + constructed
1257     static final int LDAP_REQ_ADD = 0x68;       // app + constructed
1258     static final int LDAP_REQ_DELETE = 0x4a;    // app + primitive
1259     static final int LDAP_REQ_MODRDN = 0x6c;    // app + constructed
1260     static final int LDAP_REQ_COMPARE = 0x6e;   // app + constructed
1261     static final int LDAP_REQ_ABANDON = 0x50;   // app + primitive
1262     static final int LDAP_REQ_EXTENSION = 0x77; // app + constructed    (LDAPv3)
1263 
1264     static final int LDAP_REP_BIND = 0x61;      // app + constructed | 1
1265     static final int LDAP_REP_SEARCH = 0x64;    // app + constructed | 4
1266     static final int LDAP_REP_SEARCH_REF = 0x73;// app + constructed    (LDAPv3)
1267     static final int LDAP_REP_RESULT = 0x65;    // app + constructed | 5
1268     static final int LDAP_REP_MODIFY = 0x67;    // app + constructed | 7
1269     static final int LDAP_REP_ADD = 0x69;       // app + constructed | 9
1270     static final int LDAP_REP_DELETE = 0x6b;    // app + primitive | b
1271     static final int LDAP_REP_MODRDN = 0x6d;    // app + primitive | d
1272     static final int LDAP_REP_COMPARE = 0x6f;   // app + primitive | f
1273     static final int LDAP_REP_EXTENSION = 0x78; // app + constructed    (LDAPv3)
1274 
1275     static final int LDAP_REP_REFERRAL = 0xa3;  // ctx + constructed    (LDAPv3)
1276     static final int LDAP_REP_EXT_OID = 0x8a;   // ctx + primitive      (LDAPv3)
1277     static final int LDAP_REP_EXT_VAL = 0x8b;   // ctx + primitive      (LDAPv3)
1278 
1279     // LDAPv3 Controls
1280 
1281     static final int LDAP_CONTROLS = 0xa0;      // ctx + constructed    (LDAPv3)
1282     static final String LDAP_CONTROL_MANAGE_DSA_IT = "2.16.840.1.113730.3.4.2";
1283     static final String LDAP_CONTROL_PREFERRED_LANG = "1.3.6.1.4.1.1466.20035";
1284     static final String LDAP_CONTROL_PAGED_RESULTS = "1.2.840.113556.1.4.319";
1285     static final String LDAP_CONTROL_SERVER_SORT_REQ = "1.2.840.113556.1.4.473";
1286     static final String LDAP_CONTROL_SERVER_SORT_RES = "1.2.840.113556.1.4.474";
1287 
1288     ////////////////////////////////////////////////////////////////////////////
1289     //
1290     // return codes
1291     //
1292     ////////////////////////////////////////////////////////////////////////////
1293 
1294     static final int LDAP_SUCCESS = 0;
1295     static final int LDAP_OPERATIONS_ERROR = 1;
1296     static final int LDAP_PROTOCOL_ERROR = 2;
1297     static final int LDAP_TIME_LIMIT_EXCEEDED = 3;
1298     static final int LDAP_SIZE_LIMIT_EXCEEDED = 4;
1299     static final int LDAP_COMPARE_FALSE = 5;
1300     static final int LDAP_COMPARE_TRUE = 6;
1301     static final int LDAP_AUTH_METHOD_NOT_SUPPORTED = 7;
1302     static final int LDAP_STRONG_AUTH_REQUIRED = 8;
1303     static final int LDAP_PARTIAL_RESULTS = 9;                  // Slapd
1304     static final int LDAP_REFERRAL = 10;                        // LDAPv3
1305     static final int LDAP_ADMIN_LIMIT_EXCEEDED = 11;            // LDAPv3
1306     static final int LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12;  // LDAPv3
1307     static final int LDAP_CONFIDENTIALITY_REQUIRED = 13;        // LDAPv3
1308     static final int LDAP_SASL_BIND_IN_PROGRESS = 14;           // LDAPv3
1309     static final int LDAP_NO_SUCH_ATTRIBUTE = 16;
1310     static final int LDAP_UNDEFINED_ATTRIBUTE_TYPE = 17;
1311     static final int LDAP_INAPPROPRIATE_MATCHING = 18;
1312     static final int LDAP_CONSTRAINT_VIOLATION = 19;
1313     static final int LDAP_ATTRIBUTE_OR_VALUE_EXISTS = 20;
1314     static final int LDAP_INVALID_ATTRIBUTE_SYNTAX = 21;
1315     static final int LDAP_NO_SUCH_OBJECT = 32;
1316     static final int LDAP_ALIAS_PROBLEM = 33;
1317     static final int LDAP_INVALID_DN_SYNTAX = 34;
1318     static final int LDAP_IS_LEAF = 35;
1319     static final int LDAP_ALIAS_DEREFERENCING_PROBLEM = 36;
1320     static final int LDAP_INAPPROPRIATE_AUTHENTICATION = 48;
1321     static final int LDAP_INVALID_CREDENTIALS = 49;
1322     static final int LDAP_INSUFFICIENT_ACCESS_RIGHTS = 50;
1323     static final int LDAP_BUSY = 51;
1324     static final int LDAP_UNAVAILABLE = 52;
1325     static final int LDAP_UNWILLING_TO_PERFORM = 53;
1326     static final int LDAP_LOOP_DETECT = 54;
1327     static final int LDAP_NAMING_VIOLATION = 64;
1328     static final int LDAP_OBJECT_CLASS_VIOLATION = 65;
1329     static final int LDAP_NOT_ALLOWED_ON_NON_LEAF = 66;
1330     static final int LDAP_NOT_ALLOWED_ON_RDN = 67;
1331     static final int LDAP_ENTRY_ALREADY_EXISTS = 68;
1332     static final int LDAP_OBJECT_CLASS_MODS_PROHIBITED = 69;
1333     static final int LDAP_AFFECTS_MULTIPLE_DSAS = 71;           // LDAPv3
1334     static final int LDAP_OTHER = 80;
1335 
1336     static final String[] ldap_error_message = {
1337         "Success",                                      // 0
1338         "Operations Error",                             // 1
1339         "Protocol Error",                               // 2
1340         "Timelimit Exceeded",                           // 3
1341         "Sizelimit Exceeded",                           // 4
1342         "Compare False",                                // 5
1343         "Compare True",                                 // 6
1344         "Authentication Method Not Supported",          // 7
1345         "Strong Authentication Required",               // 8
1346         null,
1347         "Referral",                                     // 10
1348         "Administrative Limit Exceeded",                // 11
1349         "Unavailable Critical Extension",               // 12
1350         "Confidentiality Required",                     // 13
1351         "SASL Bind In Progress",                        // 14
1352         null,
1353         "No Such Attribute",                            // 16
1354         "Undefined Attribute Type",                     // 17
1355         "Inappropriate Matching",                       // 18
1356         "Constraint Violation",                         // 19
1357         "Attribute Or Value Exists",                    // 20
1358         "Invalid Attribute Syntax",                     // 21
1359         null,
1360         null,
1361         null,
1362         null,
1363         null,
1364         null,
1365         null,
1366         null,
1367         null,
1368         null,
1369         "No Such Object",                               // 32
1370         "Alias Problem",                                // 33
1371         "Invalid DN Syntax",                            // 34
1372         null,
1373         "Alias Dereferencing Problem",                  // 36
1374         null,
1375         null,
1376         null,
1377         null,
1378         null,
1379         null,
1380         null,
1381         null,
1382         null,
1383         null,
1384         null,
1385         "Inappropriate Authentication",                 // 48
1386         "Invalid Credentials",                          // 49
1387         "Insufficient Access Rights",                   // 50
1388         "Busy",                                         // 51
1389         "Unavailable",                                  // 52
1390         "Unwilling To Perform",                         // 53
1391         "Loop Detect",                                  // 54
1392         null,
1393         null,
1394         null,
1395         null,
1396         null,
1397         null,
1398         null,
1399         null,
1400         null,
1401         "Naming Violation",                             // 64
1402         "Object Class Violation",                       // 65
1403         "Not Allowed On Non-leaf",                      // 66
1404         "Not Allowed On RDN",                           // 67
1405         "Entry Already Exists",                         // 68
1406         "Object Class Modifications Prohibited",        // 69
1407         null,
1408         "Affects Multiple DSAs",                        // 71
1409         null,
1410         null,
1411         null,
1412         null,
1413         null,
1414         null,
1415         null,
1416         null,
1417         "Other",                                        // 80
1418         null,
1419         null,
1420         null,
1421         null,
1422         null,
1423         null,
1424         null,
1425         null,
1426         null,
1427         null
1428     };
1429 
1430 
1431     /*
1432      * Generate an error message from the LDAP error code and error diagnostic.
1433      * The message format is:
1434      *
1435      *     "[LDAP: error code <errorCode> - <errorMessage>]"
1436      *
1437      * where <errorCode> is a numeric error code
1438      * and <errorMessage> is a textual description of the error (if available)
1439      *
1440      */
1441     static String getErrorMessage(int errorCode, String errorMessage) {
1442 
1443         String message = "[LDAP: error code " + errorCode;
1444 
1445         if ((errorMessage != null) && (errorMessage.length() != 0)) {
1446 
1447             // append error message from the server
1448             message = message + " - " + errorMessage + "]";
1449 
1450         } else {
1451 
1452             // append built-in error message
1453             try {
1454                 if (ldap_error_message[errorCode] != null) {
1455                     message = message + " - " + ldap_error_message[errorCode] +
1456                                 "]";
1457                 }
1458             } catch (ArrayIndexOutOfBoundsException ex) {
1459                 message = message + "]";
1460             }
1461         }
1462         return message;
1463     }
1464 
1465 
1466     ////////////////////////////////////////////////////////////////////////////
1467     //
1468     // Unsolicited notification support.
1469     //
1470     // An LdapClient maintains a list of LdapCtx that have registered
1471     // for UnsolicitedNotifications. This is a list because a single
1472     // LdapClient might be shared among multiple contexts.
1473     //
1474     // When addUnsolicited() is invoked, the LdapCtx is added to the list.
1475     //
1476     // When Connection receives an unsolicited notification (msgid == 0),
1477     // it invokes LdapClient.processUnsolicited(). processUnsolicited()
1478     // parses the Extended Response. If there are registered listeners,
1479     // LdapClient creates an UnsolicitedNotification from the response
1480     // and informs each LdapCtx to fire an event for the notification.
1481     // If it is a DISCONNECT notification, the connection is closed and a
1482     // NamingExceptionEvent is fired to the listeners.
1483     //
1484     // When the connection is closed out-of-band like this, the next
1485     // time a method is invoked on LdapClient, an IOException is thrown.
1486     //
1487     // removeUnsolicited() is invoked to remove an LdapCtx from this client.
1488     //
1489     ////////////////////////////////////////////////////////////////////////////
1490     private Vector<LdapCtx> unsolicited = new Vector<>(3);
1491     void addUnsolicited(LdapCtx ctx) {
1492         if (debug > 0) {
1493             System.err.println("LdapClient.addUnsolicited" + ctx);
1494         }
1495         unsolicited.addElement(ctx);
1496     }
1497 
1498     void removeUnsolicited(LdapCtx ctx) {
1499         if (debug > 0) {
1500             System.err.println("LdapClient.removeUnsolicited" + ctx);
1501         }
1502             unsolicited.removeElement(ctx);
1503         }
1504 
1505     // NOTE: Cannot be synchronized because this is called asynchronously
1506     // by the reader thread in Connection. Instead, sync on 'unsolicited' Vector.
1507     void processUnsolicited(BerDecoder ber) {
1508         if (debug > 0) {
1509             System.err.println("LdapClient.processUnsolicited");
1510         }
1511         try {
1512             // Parse the response
1513             LdapResult res = new LdapResult();
1514 
1515             ber.parseSeq(null); // init seq
1516             ber.parseInt();             // msg id; should be 0; ignored
1517             if (ber.parseByte() != LDAP_REP_EXTENSION) {
1518                 throw new IOException(
1519                     "Unsolicited Notification must be an Extended Response");
1520             }
1521             ber.parseLength();
1522             parseExtResponse(ber, res);
1523 
1524             if (DISCONNECT_OID.equals(res.extensionId)) {
1525                 // force closing of connection
1526                 forceClose(pooled);
1527             }
1528 
1529             LdapCtx first = null;
1530             UnsolicitedNotification notice = null;
1531 
1532             synchronized (unsolicited) {
1533                 if (unsolicited.size() > 0) {
1534                     first = unsolicited.elementAt(0);
1535 
1536                     // Create an UnsolicitedNotification using the parsed data
1537                     // Need a 'ctx' object because we want to use the context's
1538                     // list of provider control factories.
1539                     notice = new UnsolicitedResponseImpl(
1540                         res.extensionId,
1541                         res.extensionValue,
1542                         res.referrals,
1543                         res.status,
1544                         res.errorMessage,
1545                         res.matchedDN,
1546                         (res.resControls != null) ?
1547                         first.convertControls(res.resControls) :
1548                         null);
1549                 }
1550             }
1551 
1552             if (notice != null) {
1553                 // Fire UnsolicitedNotification events to listeners
1554                 notifyUnsolicited(notice);
1555 
1556                 // If "disconnect" notification,
1557                 // notify unsolicited listeners via NamingException
1558                 if (DISCONNECT_OID.equals(res.extensionId)) {
1559                     notifyUnsolicited(
1560                         new CommunicationException("Connection closed"));
1561                 }
1562             }
1563         } catch (IOException e) {
1564             NamingException ne = new CommunicationException(
1565                 "Problem parsing unsolicited notification");
1566             ne.setRootCause(e);
1567 
1568             notifyUnsolicited(ne);
1569 
1570         } catch (NamingException e) {
1571             notifyUnsolicited(e);
1572         }
1573     }
1574 
1575 
1576     private void notifyUnsolicited(Object e) {
1577         Vector<LdapCtx> unsolicitedCopy;
1578         synchronized (unsolicited) {
1579             unsolicitedCopy = new Vector<>(unsolicited);
1580             if (e instanceof NamingException) {
1581                 unsolicited.setSize(0);  // no more listeners after exception
1582             }
1583         }
1584         for (int i = 0; i < unsolicitedCopy.size(); i++) {
1585             unsolicitedCopy.elementAt(i).fireUnsolicited(e);
1586         }
1587     }
1588 
1589     private void ensureOpen() throws IOException {
1590         if (conn == null || !conn.useable) {
1591             if (conn != null && conn.closureReason != null) {
1592                 throw conn.closureReason;
1593             } else {
1594                 throw new IOException("connection closed");
1595             }
1596         }
1597     }
1598 
1599     // package private (used by LdapCtx)
1600     static LdapClient getInstance(boolean usePool, String hostname, int port,
1601         String factory, int connectTimeout, int readTimeout, OutputStream trace,
1602         int version, String authMechanism, Control[] ctls, String protocol,
1603         String user, Object passwd, Hashtable<?,?> env) throws NamingException {
1604 
1605         if (usePool) {
1606             if (LdapPoolManager.isPoolingAllowed(factory, trace,
1607                     authMechanism, protocol, env)) {
1608                 LdapClient answer = LdapPoolManager.getLdapClient(
1609                         hostname, port, factory, connectTimeout, readTimeout,
1610                         trace, version, authMechanism, ctls, protocol, user,
1611                         passwd, env);
1612                 answer.referenceCount = 1;   // always one when starting out
1613                 return answer;
1614             }
1615         }
1616         return new LdapClient(hostname, port, factory, connectTimeout,
1617                                         readTimeout, trace, null);
1618     }
1619 }