1 /*
   2  * Copyright (c) 1999, 2012, 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     protected void finalize() {
 478         if (debug > 0) System.err.println("LdapClient: finalize " + this);
 479         forceClose(pooled);
 480     }
 481 
 482     /*
 483      * Used by connection pooling to close physical connection.
 484      */
 485     synchronized public void closeConnection() {
 486         forceClose(false); // this is a pool callback so no need to clean pool
 487     }
 488 
 489     /**
 490      * Called by Connection.cleanup(). LdapClient should
 491      * notify any unsolicited listeners and removing itself from any pool.
 492      * This is almost like forceClose(), except it doesn't call
 493      * Connection.cleanup() (because this is called from cleanup()).
 494      */
 495     void processConnectionClosure() {
 496         // Notify listeners
 497         if (unsolicited.size() > 0) {
 498             String msg;
 499             if (conn != null) {
 500                 msg = conn.host + ":" + conn.port + " connection closed";
 501             } else {
 502                 msg = "Connection closed";
 503             }
 504             notifyUnsolicited(new CommunicationException(msg));
 505         }
 506 
 507         // Remove from pool
 508         if (pooled) {
 509             pcb.removePooledConnection(this);
 510         }
 511     }
 512 
 513     ////////////////////////////////////////////////////////////////////////////
 514     //
 515     // LDAP search. also includes methods to encode rfc 1558 compliant filters
 516     //
 517     ////////////////////////////////////////////////////////////////////////////
 518 
 519     static final int SCOPE_BASE_OBJECT = 0;
 520     static final int SCOPE_ONE_LEVEL = 1;
 521     static final int SCOPE_SUBTREE = 2;
 522 
 523     LdapResult search(String dn, int scope, int deref, int sizeLimit,
 524                       int timeLimit, boolean attrsOnly, String attrs[],
 525                       String filter, int batchSize, Control[] reqCtls,
 526                       Hashtable<String, Boolean> binaryAttrs,
 527                       boolean waitFirstReply, int replyQueueCapacity)
 528         throws IOException, NamingException {
 529 
 530         ensureOpen();
 531 
 532         LdapResult res = new LdapResult();
 533 
 534         BerEncoder ber = new BerEncoder();
 535         int curMsgId = conn.getMsgId();
 536 
 537             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 538                 ber.encodeInt(curMsgId);
 539                 ber.beginSeq(LDAP_REQ_SEARCH);
 540                     ber.encodeString(dn == null ? "" : dn, isLdapv3);
 541                     ber.encodeInt(scope, LBER_ENUMERATED);
 542                     ber.encodeInt(deref, LBER_ENUMERATED);
 543                     ber.encodeInt(sizeLimit);
 544                     ber.encodeInt(timeLimit);
 545                     ber.encodeBoolean(attrsOnly);
 546                     Filter.encodeFilterString(ber, filter, isLdapv3);
 547                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 548                         ber.encodeStringArray(attrs, isLdapv3);
 549                     ber.endSeq();
 550                 ber.endSeq();
 551                 if (isLdapv3) encodeControls(ber, reqCtls);
 552             ber.endSeq();
 553 
 554          LdapRequest req =
 555                 conn.writeRequest(ber, curMsgId, false, replyQueueCapacity);
 556 
 557          res.msgId = curMsgId;
 558          res.status = LdapClient.LDAP_SUCCESS; //optimistic
 559          if (waitFirstReply) {
 560              // get first reply
 561              res = getSearchReply(req, batchSize, res, binaryAttrs);
 562          }
 563          return res;
 564     }
 565 
 566     /*
 567      * Abandon the search operation and remove it from the message queue.
 568      */
 569     void clearSearchReply(LdapResult res, Control[] ctls) {
 570         if (res != null && conn != null) {
 571 
 572             // Only send an LDAP abandon operation when clearing the search
 573             // reply from a one-level or subtree search.
 574             LdapRequest req = conn.findRequest(res.msgId);
 575             if (req == null) {
 576                 return;
 577             }
 578 
 579             // OK if req got removed after check; double removal attempt
 580             // but otherwise no harm done
 581 
 582             // Send an LDAP abandon only if the search operation has not yet
 583             // completed.
 584             if (req.hasSearchCompleted()) {
 585                 conn.removeRequest(req);
 586             } else {
 587                 conn.abandonRequest(req, ctls);
 588             }
 589         }
 590     }
 591 
 592     /*
 593      * Retrieve the next batch of entries and/or referrals.
 594      */
 595     LdapResult getSearchReply(int batchSize, LdapResult res,
 596         Hashtable<String, Boolean> binaryAttrs) throws IOException, NamingException {
 597 
 598         ensureOpen();
 599 
 600         LdapRequest req;
 601 
 602         if ((req = conn.findRequest(res.msgId)) == null) {
 603             return null;
 604         }
 605 
 606         return getSearchReply(req, batchSize, res, binaryAttrs);
 607     }
 608 
 609     private LdapResult getSearchReply(LdapRequest req,
 610         int batchSize, LdapResult res, Hashtable<String, Boolean> binaryAttrs)
 611         throws IOException, NamingException {
 612 
 613         if (batchSize == 0)
 614             batchSize = Integer.MAX_VALUE;
 615 
 616         if (res.entries != null) {
 617             res.entries.setSize(0); // clear the (previous) set of entries
 618         } else {
 619             res.entries =
 620                 new Vector<>(batchSize == Integer.MAX_VALUE ? 32 : batchSize);
 621         }
 622 
 623         if (res.referrals != null) {
 624             res.referrals.setSize(0); // clear the (previous) set of referrals
 625         }
 626 
 627         BerDecoder replyBer;    // Decoder for response
 628         int seq;                // Request id
 629 
 630         Attributes lattrs;      // Attribute set read from response
 631         Attribute la;           // Attribute read from response
 632         String DN;              // DN read from response
 633         LdapEntry le;           // LDAP entry representing response
 634         int[] seqlen;           // Holder for response length
 635         int endseq;             // Position of end of response
 636 
 637         for (int i = 0; i < batchSize;) {
 638             replyBer = conn.readReply(req);
 639 
 640             //
 641             // process search reply
 642             //
 643             replyBer.parseSeq(null);                    // init seq
 644             replyBer.parseInt();                        // req id
 645             seq = replyBer.parseSeq(null);
 646 
 647             if (seq == LDAP_REP_SEARCH) {
 648 
 649                 // handle LDAPv3 search entries
 650                 lattrs = new BasicAttributes(caseIgnore);
 651                 DN = replyBer.parseString(isLdapv3);
 652                 le = new LdapEntry(DN, lattrs);
 653                 seqlen = new int[1];
 654 
 655                 replyBer.parseSeq(seqlen);
 656                 endseq = replyBer.getParsePosition() + seqlen[0];
 657                 while ((replyBer.getParsePosition() < endseq) &&
 658                     (replyBer.bytesLeft() > 0)) {
 659                     la = parseAttribute(replyBer, binaryAttrs);
 660                     lattrs.put(la);
 661                 }
 662                 le.respCtls = isLdapv3 ? parseControls(replyBer) : null;
 663 
 664                 res.entries.addElement(le);
 665                 i++;
 666 
 667             } else if ((seq == LDAP_REP_SEARCH_REF) && isLdapv3) {
 668 
 669                 // handle LDAPv3 search reference
 670                 Vector<String> URLs = new Vector<>(4);
 671 
 672                 // %%% Although not strictly correct, some LDAP servers
 673                 //     encode the SEQUENCE OF tag in the SearchResultRef
 674                 if (replyBer.peekByte() ==
 675                     (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {
 676                     replyBer.parseSeq(null);
 677                 }
 678 
 679                 while ((replyBer.bytesLeft() > 0) &&
 680                     (replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
 681 
 682                     URLs.addElement(replyBer.parseString(isLdapv3));
 683                 }
 684 
 685                 if (res.referrals == null) {
 686                     res.referrals = new Vector<>(4);
 687                 }
 688                 res.referrals.addElement(URLs);
 689                 res.resControls = isLdapv3 ? parseControls(replyBer) : null;
 690 
 691                 // Save referral and continue to get next search result
 692 
 693             } else if (seq == LDAP_REP_EXTENSION) {
 694 
 695                 parseExtResponse(replyBer, res); //%%% ignore for now
 696 
 697             } else if (seq == LDAP_REP_RESULT) {
 698 
 699                 parseResult(replyBer, res, isLdapv3);
 700                 res.resControls = isLdapv3 ? parseControls(replyBer) : null;
 701 
 702                 conn.removeRequest(req);
 703                 return res;     // Done with search
 704             }
 705         }
 706 
 707         return res;
 708     }
 709 
 710     private Attribute parseAttribute(BerDecoder ber,
 711                                      Hashtable<String, Boolean> binaryAttrs)
 712         throws IOException {
 713 
 714         int len[] = new int[1];
 715         int seq = ber.parseSeq(null);
 716         String attrid = ber.parseString(isLdapv3);
 717         boolean hasBinaryValues = isBinaryValued(attrid, binaryAttrs);
 718         Attribute la = new LdapAttribute(attrid);
 719 
 720         if ((seq = ber.parseSeq(len)) == LBER_SET) {
 721             int attrlen = len[0];
 722             while (ber.bytesLeft() > 0 && attrlen > 0) {
 723                 try {
 724                     attrlen -= parseAttributeValue(ber, la, hasBinaryValues);
 725                 } catch (IOException ex) {
 726                     ber.seek(attrlen);
 727                     break;
 728                 }
 729             }
 730         } else {
 731             // Skip the rest of the sequence because it is not what we want
 732             ber.seek(len[0]);
 733         }
 734         return la;
 735     }
 736 
 737     //
 738     // returns number of bytes that were parsed. Adds the values to attr
 739     //
 740     private int parseAttributeValue(BerDecoder ber, Attribute la,
 741         boolean hasBinaryValues) throws IOException {
 742 
 743         int len[] = new int[1];
 744 
 745         if (hasBinaryValues) {
 746             la.add(ber.parseOctetString(ber.peekByte(), len));
 747         } else {
 748             la.add(ber.parseStringWithTag(
 749                                     Ber.ASN_SIMPLE_STRING, isLdapv3, len));
 750         }
 751         return len[0];
 752     }
 753 
 754     private boolean isBinaryValued(String attrid,
 755                                    Hashtable<String, Boolean> binaryAttrs) {
 756         String id = attrid.toLowerCase(Locale.ENGLISH);
 757 
 758         return ((id.indexOf(";binary") != -1) ||
 759             defaultBinaryAttrs.containsKey(id) ||
 760             ((binaryAttrs != null) && (binaryAttrs.containsKey(id))));
 761     }
 762 
 763     // package entry point; used by Connection
 764     static void parseResult(BerDecoder replyBer, LdapResult res,
 765             boolean isLdapv3) throws IOException {
 766 
 767         res.status = replyBer.parseEnumeration();
 768         res.matchedDN = replyBer.parseString(isLdapv3);
 769         res.errorMessage = replyBer.parseString(isLdapv3);
 770 
 771         // handle LDAPv3 referrals (if present)
 772         if (isLdapv3 &&
 773             (replyBer.bytesLeft() > 0) &&
 774             (replyBer.peekByte() == LDAP_REP_REFERRAL)) {
 775 
 776             Vector<String> URLs = new Vector<>(4);
 777             int[] seqlen = new int[1];
 778 
 779             replyBer.parseSeq(seqlen);
 780             int endseq = replyBer.getParsePosition() + seqlen[0];
 781             while ((replyBer.getParsePosition() < endseq) &&
 782                 (replyBer.bytesLeft() > 0)) {
 783 
 784                 URLs.addElement(replyBer.parseString(isLdapv3));
 785             }
 786 
 787             if (res.referrals == null) {
 788                 res.referrals = new Vector<>(4);
 789             }
 790             res.referrals.addElement(URLs);
 791         }
 792     }
 793 
 794     // package entry point; used by Connection
 795     static Vector<Control> parseControls(BerDecoder replyBer) throws IOException {
 796 
 797         // handle LDAPv3 controls (if present)
 798         if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) {
 799             Vector<Control> ctls = new Vector<>(4);
 800             String controlOID;
 801             boolean criticality = false; // default
 802             byte[] controlValue = null;  // optional
 803             int[] seqlen = new int[1];
 804 
 805             replyBer.parseSeq(seqlen);
 806             int endseq = replyBer.getParsePosition() + seqlen[0];
 807             while ((replyBer.getParsePosition() < endseq) &&
 808                 (replyBer.bytesLeft() > 0)) {
 809 
 810                 replyBer.parseSeq(null);
 811                 controlOID = replyBer.parseString(true);
 812 
 813                 if ((replyBer.bytesLeft() > 0) &&
 814                     (replyBer.peekByte() == Ber.ASN_BOOLEAN)) {
 815                     criticality = replyBer.parseBoolean();
 816                 }
 817                 if ((replyBer.bytesLeft() > 0) &&
 818                     (replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
 819                     controlValue =
 820                         replyBer.parseOctetString(Ber.ASN_OCTET_STR, null);
 821                 }
 822                 if (controlOID != null) {
 823                     ctls.addElement(
 824                         new BasicControl(controlOID, criticality, controlValue));
 825                 }
 826             }
 827             return ctls;
 828         } else {
 829             return null;
 830         }
 831     }
 832 
 833     private void parseExtResponse(BerDecoder replyBer, LdapResult res)
 834         throws IOException {
 835 
 836         parseResult(replyBer, res, isLdapv3);
 837 
 838         if ((replyBer.bytesLeft() > 0) &&
 839             (replyBer.peekByte() == LDAP_REP_EXT_OID)) {
 840             res.extensionId =
 841                 replyBer.parseStringWithTag(LDAP_REP_EXT_OID, isLdapv3, null);
 842         }
 843         if ((replyBer.bytesLeft() > 0) &&
 844             (replyBer.peekByte() == LDAP_REP_EXT_VAL)) {
 845             res.extensionValue =
 846                 replyBer.parseOctetString(LDAP_REP_EXT_VAL, null);
 847         }
 848 
 849         res.resControls = parseControls(replyBer);
 850     }
 851 
 852     //
 853     // Encode LDAPv3 controls
 854     //
 855     static void encodeControls(BerEncoder ber, Control[] reqCtls)
 856         throws IOException {
 857 
 858         if ((reqCtls == null) || (reqCtls.length == 0)) {
 859             return;
 860         }
 861 
 862         byte[] controlVal;
 863 
 864         ber.beginSeq(LdapClient.LDAP_CONTROLS);
 865 
 866             for (int i = 0; i < reqCtls.length; i++) {
 867                 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 868                     ber.encodeString(reqCtls[i].getID(), true); // control OID
 869                     if (reqCtls[i].isCritical()) {
 870                         ber.encodeBoolean(true); // critical control
 871                     }
 872                     if ((controlVal = reqCtls[i].getEncodedValue()) != null) {
 873                         ber.encodeOctetString(controlVal, Ber.ASN_OCTET_STR);
 874                     }
 875                 ber.endSeq();
 876             }
 877         ber.endSeq();
 878     }
 879 
 880     /**
 881      * Reads the next reply corresponding to msgId, outstanding on requestBer.
 882      * Processes the result and any controls.
 883      */
 884     private LdapResult processReply(LdapRequest req,
 885         LdapResult res, int responseType) throws IOException, NamingException {
 886 
 887         BerDecoder rber = conn.readReply(req);
 888 
 889         rber.parseSeq(null);    // init seq
 890         rber.parseInt();        // msg id
 891         if (rber.parseByte() !=  responseType) {
 892             return res;
 893         }
 894 
 895         rber.parseLength();
 896         parseResult(rber, res, isLdapv3);
 897         res.resControls = isLdapv3 ? parseControls(rber) : null;
 898 
 899         conn.removeRequest(req);
 900 
 901         return res;     // Done with operation
 902     }
 903 
 904     ////////////////////////////////////////////////////////////////////////////
 905     //
 906     // LDAP modify:
 907     //  Modify the DN dn with the operations on attributes attrs.
 908     //  ie, operations[0] is the operation to be performed on
 909     //  attrs[0];
 910     //          dn - DN to modify
 911     //          operations - add, delete or replace
 912     //          attrs - array of Attribute
 913     //          reqCtls - array of request controls
 914     //
 915     ////////////////////////////////////////////////////////////////////////////
 916 
 917     static final int ADD = 0;
 918     static final int DELETE = 1;
 919     static final int REPLACE = 2;
 920 
 921     LdapResult modify(String dn, int operations[], Attribute attrs[],
 922                       Control[] reqCtls)
 923         throws IOException, NamingException {
 924 
 925         ensureOpen();
 926 
 927         LdapResult res = new LdapResult();
 928         res.status = LDAP_OPERATIONS_ERROR;
 929 
 930         if (dn == null || operations.length != attrs.length)
 931             return res;
 932 
 933         BerEncoder ber = new BerEncoder();
 934         int curMsgId = conn.getMsgId();
 935 
 936         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 937             ber.encodeInt(curMsgId);
 938             ber.beginSeq(LDAP_REQ_MODIFY);
 939                 ber.encodeString(dn, isLdapv3);
 940                 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 941                     for (int i = 0; i < operations.length; i++) {
 942                         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 943                             ber.encodeInt(operations[i], LBER_ENUMERATED);
 944 
 945                             // zero values is not permitted for the add op.
 946                             if ((operations[i] == ADD) && hasNoValue(attrs[i])) {
 947                                 throw new InvalidAttributeValueException(
 948                                     "'" + attrs[i].getID() + "' has no values.");
 949                             } else {
 950                                 encodeAttribute(ber, attrs[i]);
 951                             }
 952                         ber.endSeq();
 953                     }
 954                 ber.endSeq();
 955             ber.endSeq();
 956             if (isLdapv3) encodeControls(ber, reqCtls);
 957         ber.endSeq();
 958 
 959         LdapRequest req = conn.writeRequest(ber, curMsgId);
 960 
 961         return processReply(req, res, LDAP_REP_MODIFY);
 962     }
 963 
 964     private void encodeAttribute(BerEncoder ber, Attribute attr)
 965         throws IOException, NamingException {
 966 
 967         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 968             ber.encodeString(attr.getID(), isLdapv3);
 969             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR | 1);
 970                 NamingEnumeration<?> enum_ = attr.getAll();
 971                 Object val;
 972                 while (enum_.hasMore()) {
 973                     val = enum_.next();
 974                     if (val instanceof String) {
 975                         ber.encodeString((String)val, isLdapv3);
 976                     } else if (val instanceof byte[]) {
 977                         ber.encodeOctetString((byte[])val, Ber.ASN_OCTET_STR);
 978                     } else if (val == null) {
 979                         // no attribute value
 980                     } else {
 981                         throw new InvalidAttributeValueException(
 982                             "Malformed '" + attr.getID() + "' attribute value");
 983                     }
 984                 }
 985             ber.endSeq();
 986         ber.endSeq();
 987     }
 988 
 989     private static boolean hasNoValue(Attribute attr) throws NamingException {
 990         return attr.size() == 0 || (attr.size() == 1 && attr.get() == null);
 991     }
 992 
 993     ////////////////////////////////////////////////////////////////////////////
 994     //
 995     // LDAP add
 996     //          Adds entry to the Directory
 997     //
 998     ////////////////////////////////////////////////////////////////////////////
 999 
1000     LdapResult add(LdapEntry entry, Control[] reqCtls)
1001         throws IOException, NamingException {
1002 
1003         ensureOpen();
1004 
1005         LdapResult res = new LdapResult();
1006         res.status = LDAP_OPERATIONS_ERROR;
1007 
1008         if (entry == null || entry.DN == null)
1009             return res;
1010 
1011         BerEncoder ber = new BerEncoder();
1012         int curMsgId = conn.getMsgId();
1013         Attribute attr;
1014 
1015             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1016                 ber.encodeInt(curMsgId);
1017                 ber.beginSeq(LDAP_REQ_ADD);
1018                     ber.encodeString(entry.DN, isLdapv3);
1019                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1020                         NamingEnumeration<? extends Attribute> enum_ =
1021                                 entry.attributes.getAll();
1022                         while (enum_.hasMore()) {
1023                             attr = enum_.next();
1024 
1025                             // zero values is not permitted
1026                             if (hasNoValue(attr)) {
1027                                 throw new InvalidAttributeValueException(
1028                                     "'" + attr.getID() + "' has no values.");
1029                             } else {
1030                                 encodeAttribute(ber, attr);
1031                             }
1032                         }
1033                     ber.endSeq();
1034                 ber.endSeq();
1035                 if (isLdapv3) encodeControls(ber, reqCtls);
1036             ber.endSeq();
1037 
1038         LdapRequest req = conn.writeRequest(ber, curMsgId);
1039         return processReply(req, res, LDAP_REP_ADD);
1040     }
1041 
1042     ////////////////////////////////////////////////////////////////////////////
1043     //
1044     // LDAP delete
1045     //          deletes entry from the Directory
1046     //
1047     ////////////////////////////////////////////////////////////////////////////
1048 
1049     LdapResult delete(String DN, Control[] reqCtls)
1050         throws IOException, NamingException {
1051 
1052         ensureOpen();
1053 
1054         LdapResult res = new LdapResult();
1055         res.status = LDAP_OPERATIONS_ERROR;
1056 
1057         if (DN == null)
1058             return res;
1059 
1060         BerEncoder ber = new BerEncoder();
1061         int curMsgId = conn.getMsgId();
1062 
1063             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1064                 ber.encodeInt(curMsgId);
1065                 ber.encodeString(DN, LDAP_REQ_DELETE, isLdapv3);
1066                 if (isLdapv3) encodeControls(ber, reqCtls);
1067             ber.endSeq();
1068 
1069         LdapRequest req = conn.writeRequest(ber, curMsgId);
1070 
1071         return processReply(req, res, LDAP_REP_DELETE);
1072     }
1073 
1074     ////////////////////////////////////////////////////////////////////////////
1075     //
1076     // LDAP modrdn
1077     //  Changes the last element of DN to newrdn
1078     //          dn - DN to change
1079     //          newrdn - new RDN to rename to
1080     //          deleteoldrdn - boolean whether to delete old attrs or not
1081     //          newSuperior - new place to put the entry in the tree
1082     //                        (ignored if server is LDAPv2)
1083     //          reqCtls - array of request controls
1084     //
1085     ////////////////////////////////////////////////////////////////////////////
1086 
1087     LdapResult moddn(String DN, String newrdn, boolean deleteOldRdn,
1088                      String newSuperior, Control[] reqCtls)
1089         throws IOException, NamingException {
1090 
1091         ensureOpen();
1092 
1093         boolean changeSuperior = (newSuperior != null &&
1094                                   newSuperior.length() > 0);
1095 
1096         LdapResult res = new LdapResult();
1097         res.status = LDAP_OPERATIONS_ERROR;
1098 
1099         if (DN == null || newrdn == null)
1100             return res;
1101 
1102         BerEncoder ber = new BerEncoder();
1103         int curMsgId = conn.getMsgId();
1104 
1105             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1106                 ber.encodeInt(curMsgId);
1107                 ber.beginSeq(LDAP_REQ_MODRDN);
1108                     ber.encodeString(DN, isLdapv3);
1109                     ber.encodeString(newrdn, isLdapv3);
1110                     ber.encodeBoolean(deleteOldRdn);
1111                     if(isLdapv3 && changeSuperior) {
1112                         //System.err.println("changin superior");
1113                         ber.encodeString(newSuperior, LDAP_SUPERIOR_DN, isLdapv3);
1114                     }
1115                 ber.endSeq();
1116                 if (isLdapv3) encodeControls(ber, reqCtls);
1117             ber.endSeq();
1118 
1119 
1120         LdapRequest req = conn.writeRequest(ber, curMsgId);
1121 
1122         return processReply(req, res, LDAP_REP_MODRDN);
1123     }
1124 
1125     ////////////////////////////////////////////////////////////////////////////
1126     //
1127     // LDAP compare
1128     //  Compare attribute->value pairs in dn
1129     //
1130     ////////////////////////////////////////////////////////////////////////////
1131 
1132     LdapResult compare(String DN, String type, String value, Control[] reqCtls)
1133         throws IOException, NamingException {
1134 
1135         ensureOpen();
1136 
1137         LdapResult res = new LdapResult();
1138         res.status = LDAP_OPERATIONS_ERROR;
1139 
1140         if (DN == null || type == null || value == null)
1141             return res;
1142 
1143         BerEncoder ber = new BerEncoder();
1144         int curMsgId = conn.getMsgId();
1145 
1146             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1147                 ber.encodeInt(curMsgId);
1148                 ber.beginSeq(LDAP_REQ_COMPARE);
1149                     ber.encodeString(DN, isLdapv3);
1150                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1151                         ber.encodeString(type, isLdapv3);
1152 
1153                         // replace any escaped characters in the value
1154                         byte[] val = isLdapv3 ?
1155                             value.getBytes("UTF8") : value.getBytes("8859_1");
1156                         ber.encodeOctetString(
1157                             Filter.unescapeFilterValue(val, 0, val.length),
1158                             Ber.ASN_OCTET_STR);
1159 
1160                     ber.endSeq();
1161                 ber.endSeq();
1162                 if (isLdapv3) encodeControls(ber, reqCtls);
1163             ber.endSeq();
1164 
1165         LdapRequest req = conn.writeRequest(ber, curMsgId);
1166 
1167         return processReply(req, res, LDAP_REP_COMPARE);
1168     }
1169 
1170     ////////////////////////////////////////////////////////////////////////////
1171     //
1172     // LDAP extended operation
1173     //
1174     ////////////////////////////////////////////////////////////////////////////
1175 
1176     LdapResult extendedOp(String id, byte[] request, Control[] reqCtls,
1177         boolean pauseAfterReceipt) throws IOException, NamingException {
1178 
1179         ensureOpen();
1180 
1181         LdapResult res = new LdapResult();
1182         res.status = LDAP_OPERATIONS_ERROR;
1183 
1184         if (id == null)
1185             return res;
1186 
1187         BerEncoder ber = new BerEncoder();
1188         int curMsgId = conn.getMsgId();
1189 
1190             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1191                 ber.encodeInt(curMsgId);
1192                 ber.beginSeq(LDAP_REQ_EXTENSION);
1193                     ber.encodeString(id,
1194                         Ber.ASN_CONTEXT | 0, isLdapv3);//[0]
1195                     if (request != null) {
1196                         ber.encodeOctetString(request,
1197                             Ber.ASN_CONTEXT | 1);//[1]
1198                     }
1199                 ber.endSeq();
1200                 encodeControls(ber, reqCtls); // always v3
1201             ber.endSeq();
1202 
1203         LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);
1204 
1205         BerDecoder rber = conn.readReply(req);
1206 
1207         rber.parseSeq(null);    // init seq
1208         rber.parseInt();        // msg id
1209         if (rber.parseByte() !=  LDAP_REP_EXTENSION) {
1210             return res;
1211         }
1212 
1213         rber.parseLength();
1214         parseExtResponse(rber, res);
1215         conn.removeRequest(req);
1216 
1217         return res;     // Done with operation
1218     }
1219 
1220 
1221 
1222     ////////////////////////////////////////////////////////////////////////////
1223     //
1224     // Some BER definitions convenient for LDAP
1225     //
1226     ////////////////////////////////////////////////////////////////////////////
1227 
1228     static final int LDAP_VERSION3_VERSION2 = 32;
1229     static final int LDAP_VERSION2 = 0x02;
1230     static final int LDAP_VERSION3 = 0x03;              // LDAPv3
1231     static final int LDAP_VERSION = LDAP_VERSION3;
1232 
1233     static final int LDAP_REF_FOLLOW = 0x01;            // follow referrals
1234     static final int LDAP_REF_THROW = 0x02;             // throw referral ex.
1235     static final int LDAP_REF_IGNORE = 0x03;            // ignore referrals
1236 
1237     static final String LDAP_URL = "ldap://";           // LDAPv3
1238     static final String LDAPS_URL = "ldaps://";         // LDAPv3
1239 
1240     static final int LBER_BOOLEAN = 0x01;
1241     static final int LBER_INTEGER = 0x02;
1242     static final int LBER_BITSTRING = 0x03;
1243     static final int LBER_OCTETSTRING = 0x04;
1244     static final int LBER_NULL = 0x05;
1245     static final int LBER_ENUMERATED = 0x0a;
1246     static final int LBER_SEQUENCE = 0x30;
1247     static final int LBER_SET = 0x31;
1248 
1249     static final int LDAP_SUPERIOR_DN = 0x80;
1250 
1251     static final int LDAP_REQ_BIND = 0x60;      // app + constructed
1252     static final int LDAP_REQ_UNBIND = 0x42;    // app + primitive
1253     static final int LDAP_REQ_SEARCH = 0x63;    // app + constructed
1254     static final int LDAP_REQ_MODIFY = 0x66;    // app + constructed
1255     static final int LDAP_REQ_ADD = 0x68;       // app + constructed
1256     static final int LDAP_REQ_DELETE = 0x4a;    // app + primitive
1257     static final int LDAP_REQ_MODRDN = 0x6c;    // app + constructed
1258     static final int LDAP_REQ_COMPARE = 0x6e;   // app + constructed
1259     static final int LDAP_REQ_ABANDON = 0x50;   // app + primitive
1260     static final int LDAP_REQ_EXTENSION = 0x77; // app + constructed    (LDAPv3)
1261 
1262     static final int LDAP_REP_BIND = 0x61;      // app + constructed | 1
1263     static final int LDAP_REP_SEARCH = 0x64;    // app + constructed | 4
1264     static final int LDAP_REP_SEARCH_REF = 0x73;// app + constructed    (LDAPv3)
1265     static final int LDAP_REP_RESULT = 0x65;    // app + constructed | 5
1266     static final int LDAP_REP_MODIFY = 0x67;    // app + constructed | 7
1267     static final int LDAP_REP_ADD = 0x69;       // app + constructed | 9
1268     static final int LDAP_REP_DELETE = 0x6b;    // app + primitive | b
1269     static final int LDAP_REP_MODRDN = 0x6d;    // app + primitive | d
1270     static final int LDAP_REP_COMPARE = 0x6f;   // app + primitive | f
1271     static final int LDAP_REP_EXTENSION = 0x78; // app + constructed    (LDAPv3)
1272 
1273     static final int LDAP_REP_REFERRAL = 0xa3;  // ctx + constructed    (LDAPv3)
1274     static final int LDAP_REP_EXT_OID = 0x8a;   // ctx + primitive      (LDAPv3)
1275     static final int LDAP_REP_EXT_VAL = 0x8b;   // ctx + primitive      (LDAPv3)
1276 
1277     // LDAPv3 Controls
1278 
1279     static final int LDAP_CONTROLS = 0xa0;      // ctx + constructed    (LDAPv3)
1280     static final String LDAP_CONTROL_MANAGE_DSA_IT = "2.16.840.1.113730.3.4.2";
1281     static final String LDAP_CONTROL_PREFERRED_LANG = "1.3.6.1.4.1.1466.20035";
1282     static final String LDAP_CONTROL_PAGED_RESULTS = "1.2.840.113556.1.4.319";
1283     static final String LDAP_CONTROL_SERVER_SORT_REQ = "1.2.840.113556.1.4.473";
1284     static final String LDAP_CONTROL_SERVER_SORT_RES = "1.2.840.113556.1.4.474";
1285 
1286     ////////////////////////////////////////////////////////////////////////////
1287     //
1288     // return codes
1289     //
1290     ////////////////////////////////////////////////////////////////////////////
1291 
1292     static final int LDAP_SUCCESS = 0;
1293     static final int LDAP_OPERATIONS_ERROR = 1;
1294     static final int LDAP_PROTOCOL_ERROR = 2;
1295     static final int LDAP_TIME_LIMIT_EXCEEDED = 3;
1296     static final int LDAP_SIZE_LIMIT_EXCEEDED = 4;
1297     static final int LDAP_COMPARE_FALSE = 5;
1298     static final int LDAP_COMPARE_TRUE = 6;
1299     static final int LDAP_AUTH_METHOD_NOT_SUPPORTED = 7;
1300     static final int LDAP_STRONG_AUTH_REQUIRED = 8;
1301     static final int LDAP_PARTIAL_RESULTS = 9;                  // Slapd
1302     static final int LDAP_REFERRAL = 10;                        // LDAPv3
1303     static final int LDAP_ADMIN_LIMIT_EXCEEDED = 11;            // LDAPv3
1304     static final int LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12;  // LDAPv3
1305     static final int LDAP_CONFIDENTIALITY_REQUIRED = 13;        // LDAPv3
1306     static final int LDAP_SASL_BIND_IN_PROGRESS = 14;           // LDAPv3
1307     static final int LDAP_NO_SUCH_ATTRIBUTE = 16;
1308     static final int LDAP_UNDEFINED_ATTRIBUTE_TYPE = 17;
1309     static final int LDAP_INAPPROPRIATE_MATCHING = 18;
1310     static final int LDAP_CONSTRAINT_VIOLATION = 19;
1311     static final int LDAP_ATTRIBUTE_OR_VALUE_EXISTS = 20;
1312     static final int LDAP_INVALID_ATTRIBUTE_SYNTAX = 21;
1313     static final int LDAP_NO_SUCH_OBJECT = 32;
1314     static final int LDAP_ALIAS_PROBLEM = 33;
1315     static final int LDAP_INVALID_DN_SYNTAX = 34;
1316     static final int LDAP_IS_LEAF = 35;
1317     static final int LDAP_ALIAS_DEREFERENCING_PROBLEM = 36;
1318     static final int LDAP_INAPPROPRIATE_AUTHENTICATION = 48;
1319     static final int LDAP_INVALID_CREDENTIALS = 49;
1320     static final int LDAP_INSUFFICIENT_ACCESS_RIGHTS = 50;
1321     static final int LDAP_BUSY = 51;
1322     static final int LDAP_UNAVAILABLE = 52;
1323     static final int LDAP_UNWILLING_TO_PERFORM = 53;
1324     static final int LDAP_LOOP_DETECT = 54;
1325     static final int LDAP_NAMING_VIOLATION = 64;
1326     static final int LDAP_OBJECT_CLASS_VIOLATION = 65;
1327     static final int LDAP_NOT_ALLOWED_ON_NON_LEAF = 66;
1328     static final int LDAP_NOT_ALLOWED_ON_RDN = 67;
1329     static final int LDAP_ENTRY_ALREADY_EXISTS = 68;
1330     static final int LDAP_OBJECT_CLASS_MODS_PROHIBITED = 69;
1331     static final int LDAP_AFFECTS_MULTIPLE_DSAS = 71;           // LDAPv3
1332     static final int LDAP_OTHER = 80;
1333 
1334     static final String[] ldap_error_message = {
1335         "Success",                                      // 0
1336         "Operations Error",                             // 1
1337         "Protocol Error",                               // 2
1338         "Timelimit Exceeded",                           // 3
1339         "Sizelimit Exceeded",                           // 4
1340         "Compare False",                                // 5
1341         "Compare True",                                 // 6
1342         "Authentication Method Not Supported",          // 7
1343         "Strong Authentication Required",               // 8
1344         null,
1345         "Referral",                                     // 10
1346         "Administrative Limit Exceeded",                // 11
1347         "Unavailable Critical Extension",               // 12
1348         "Confidentiality Required",                     // 13
1349         "SASL Bind In Progress",                        // 14
1350         null,
1351         "No Such Attribute",                            // 16
1352         "Undefined Attribute Type",                     // 17
1353         "Inappropriate Matching",                       // 18
1354         "Constraint Violation",                         // 19
1355         "Attribute Or Value Exists",                    // 20
1356         "Invalid Attribute Syntax",                     // 21
1357         null,
1358         null,
1359         null,
1360         null,
1361         null,
1362         null,
1363         null,
1364         null,
1365         null,
1366         null,
1367         "No Such Object",                               // 32
1368         "Alias Problem",                                // 33
1369         "Invalid DN Syntax",                            // 34
1370         null,
1371         "Alias Dereferencing Problem",                  // 36
1372         null,
1373         null,
1374         null,
1375         null,
1376         null,
1377         null,
1378         null,
1379         null,
1380         null,
1381         null,
1382         null,
1383         "Inappropriate Authentication",                 // 48
1384         "Invalid Credentials",                          // 49
1385         "Insufficient Access Rights",                   // 50
1386         "Busy",                                         // 51
1387         "Unavailable",                                  // 52
1388         "Unwilling To Perform",                         // 53
1389         "Loop Detect",                                  // 54
1390         null,
1391         null,
1392         null,
1393         null,
1394         null,
1395         null,
1396         null,
1397         null,
1398         null,
1399         "Naming Violation",                             // 64
1400         "Object Class Violation",                       // 65
1401         "Not Allowed On Non-leaf",                      // 66
1402         "Not Allowed On RDN",                           // 67
1403         "Entry Already Exists",                         // 68
1404         "Object Class Modifications Prohibited",        // 69
1405         null,
1406         "Affects Multiple DSAs",                        // 71
1407         null,
1408         null,
1409         null,
1410         null,
1411         null,
1412         null,
1413         null,
1414         null,
1415         "Other",                                        // 80
1416         null,
1417         null,
1418         null,
1419         null,
1420         null,
1421         null,
1422         null,
1423         null,
1424         null,
1425         null
1426     };
1427 
1428 
1429     /*
1430      * Generate an error message from the LDAP error code and error diagnostic.
1431      * The message format is:
1432      *
1433      *     "[LDAP: error code <errorCode> - <errorMessage>]"
1434      *
1435      * where <errorCode> is a numeric error code
1436      * and <errorMessage> is a textual description of the error (if available)
1437      *
1438      */
1439     static String getErrorMessage(int errorCode, String errorMessage) {
1440 
1441         String message = "[LDAP: error code " + errorCode;
1442 
1443         if ((errorMessage != null) && (errorMessage.length() != 0)) {
1444 
1445             // append error message from the server
1446             message = message + " - " + errorMessage + "]";
1447 
1448         } else {
1449 
1450             // append built-in error message
1451             try {
1452                 if (ldap_error_message[errorCode] != null) {
1453                     message = message + " - " + ldap_error_message[errorCode] +
1454                                 "]";
1455                 }
1456             } catch (ArrayIndexOutOfBoundsException ex) {
1457                 message = message + "]";
1458             }
1459         }
1460         return message;
1461     }
1462 
1463 
1464     ////////////////////////////////////////////////////////////////////////////
1465     //
1466     // Unsolicited notification support.
1467     //
1468     // An LdapClient maintains a list of LdapCtx that have registered
1469     // for UnsolicitedNotifications. This is a list because a single
1470     // LdapClient might be shared among multiple contexts.
1471     //
1472     // When addUnsolicited() is invoked, the LdapCtx is added to the list.
1473     //
1474     // When Connection receives an unsolicited notification (msgid == 0),
1475     // it invokes LdapClient.processUnsolicited(). processUnsolicited()
1476     // parses the Extended Response. If there are registered listeners,
1477     // LdapClient creates an UnsolicitedNotification from the response
1478     // and informs each LdapCtx to fire an event for the notification.
1479     // If it is a DISCONNECT notification, the connection is closed and a
1480     // NamingExceptionEvent is fired to the listeners.
1481     //
1482     // When the connection is closed out-of-band like this, the next
1483     // time a method is invoked on LdapClient, an IOException is thrown.
1484     //
1485     // removeUnsolicited() is invoked to remove an LdapCtx from this client.
1486     //
1487     ////////////////////////////////////////////////////////////////////////////
1488     private Vector<LdapCtx> unsolicited = new Vector<>(3);
1489     void addUnsolicited(LdapCtx ctx) {
1490         if (debug > 0) {
1491             System.err.println("LdapClient.addUnsolicited" + ctx);
1492         }
1493         unsolicited.addElement(ctx);
1494     }
1495 
1496     void removeUnsolicited(LdapCtx ctx) {
1497         if (debug > 0) {
1498             System.err.println("LdapClient.removeUnsolicited" + ctx);
1499         }
1500         unsolicited.removeElement(ctx);
1501     }
1502 
1503     // NOTE: Cannot be synchronized because this is called asynchronously
1504     // by the reader thread in Connection. Instead, sync on 'unsolicited' Vector.
1505     void processUnsolicited(BerDecoder ber) {
1506         if (debug > 0) {
1507             System.err.println("LdapClient.processUnsolicited");
1508         }
1509         try {
1510             // Parse the response
1511             LdapResult res = new LdapResult();
1512 
1513             ber.parseSeq(null); // init seq
1514             ber.parseInt();             // msg id; should be 0; ignored
1515             if (ber.parseByte() != LDAP_REP_EXTENSION) {
1516                 throw new IOException(
1517                     "Unsolicited Notification must be an Extended Response");
1518             }
1519             ber.parseLength();
1520             parseExtResponse(ber, res);
1521 
1522             if (DISCONNECT_OID.equals(res.extensionId)) {
1523                 // force closing of connection
1524                 forceClose(pooled);
1525             }
1526 
1527             LdapCtx first = null;
1528             UnsolicitedNotification notice = null;
1529 
1530             synchronized (unsolicited) {
1531                 if (unsolicited.size() > 0) {
1532                     first = unsolicited.elementAt(0);
1533 
1534                     // Create an UnsolicitedNotification using the parsed data
1535                     // Need a 'ctx' object because we want to use the context's
1536                     // list of provider control factories.
1537                     notice = new UnsolicitedResponseImpl(
1538                         res.extensionId,
1539                         res.extensionValue,
1540                         res.referrals,
1541                         res.status,
1542                         res.errorMessage,
1543                         res.matchedDN,
1544                         (res.resControls != null) ?
1545                         first.convertControls(res.resControls) :
1546                         null);
1547                 }
1548             }
1549 
1550             if (notice != null) {
1551                 // Fire UnsolicitedNotification events to listeners
1552                 notifyUnsolicited(notice);
1553     
1554                 // If "disconnect" notification,
1555                 // notify unsolicited listeners via NamingException
1556                 if (DISCONNECT_OID.equals(res.extensionId)) {
1557                     notifyUnsolicited(
1558                         new CommunicationException("Connection closed"));
1559                 }
1560             }
1561         } catch (IOException e) {
1562             NamingException ne = new CommunicationException(
1563                 "Problem parsing unsolicited notification");
1564             ne.setRootCause(e);
1565 
1566             notifyUnsolicited(ne);
1567 
1568         } catch (NamingException e) {
1569             notifyUnsolicited(e);
1570         }
1571     }
1572 
1573 
1574     private void notifyUnsolicited(Object e) {
1575         Vector<LdapCtx> unsolicitedCopy;
1576         synchronized (unsolicited) {
1577             unsolicitedCopy = new Vector<>(unsolicited);
1578             if (e instanceof NamingException) {
1579                 unsolicited.setSize(0);  // no more listeners after exception
1580             }
1581         }
1582         for (int i = 0; i < unsolicitedCopy.size(); i++) {
1583             unsolicitedCopy.elementAt(i).fireUnsolicited(e);
1584         }
1585     }
1586 
1587     private void ensureOpen() throws IOException {
1588         if (conn == null || !conn.useable) {
1589             if (conn != null && conn.closureReason != null) {
1590                 throw conn.closureReason;
1591             } else {
1592                 throw new IOException("connection closed");
1593             }
1594         }
1595     }
1596 
1597     // package private (used by LdapCtx)
1598     static LdapClient getInstance(boolean usePool, String hostname, int port,
1599         String factory, int connectTimeout, int readTimeout, OutputStream trace,
1600         int version, String authMechanism, Control[] ctls, String protocol,
1601         String user, Object passwd, Hashtable<?,?> env) throws NamingException {
1602 
1603         if (usePool) {
1604             if (LdapPoolManager.isPoolingAllowed(factory, trace,
1605                     authMechanism, protocol, env)) {
1606                 LdapClient answer = LdapPoolManager.getLdapClient(
1607                         hostname, port, factory, connectTimeout, readTimeout,
1608                         trace, version, authMechanism, ctls, protocol, user,
1609                         passwd, env);
1610                 answer.referenceCount = 1;   // always one when starting out
1611                 return answer;
1612             }
1613         }
1614         return new LdapClient(hostname, port, factory, connectTimeout,
1615                                         readTimeout, trace, null);
1616     }
1617 }