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         synchronized (unsolicited) {
 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 
 509         // Remove from pool
 510         if (pooled) {
 511             pcb.removePooledConnection(this);
 512         }
 513     }
 514 
 515     ////////////////////////////////////////////////////////////////////////////
 516     //
 517     // LDAP search. also includes methods to encode rfc 1558 compliant filters
 518     //
 519     ////////////////////////////////////////////////////////////////////////////
 520 
 521     static final int SCOPE_BASE_OBJECT = 0;
 522     static final int SCOPE_ONE_LEVEL = 1;
 523     static final int SCOPE_SUBTREE = 2;
 524 
 525     LdapResult search(String dn, int scope, int deref, int sizeLimit,
 526                       int timeLimit, boolean attrsOnly, String attrs[],
 527                       String filter, int batchSize, Control[] reqCtls,
 528                       Hashtable<String, Boolean> binaryAttrs,
 529                       boolean waitFirstReply, int replyQueueCapacity)
 530         throws IOException, NamingException {
 531 
 532         ensureOpen();
 533 
 534         LdapResult res = new LdapResult();
 535 
 536         BerEncoder ber = new BerEncoder();
 537         int curMsgId = conn.getMsgId();
 538 
 539             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 540                 ber.encodeInt(curMsgId);
 541                 ber.beginSeq(LDAP_REQ_SEARCH);
 542                     ber.encodeString(dn == null ? "" : dn, isLdapv3);
 543                     ber.encodeInt(scope, LBER_ENUMERATED);
 544                     ber.encodeInt(deref, LBER_ENUMERATED);
 545                     ber.encodeInt(sizeLimit);
 546                     ber.encodeInt(timeLimit);
 547                     ber.encodeBoolean(attrsOnly);
 548                     Filter.encodeFilterString(ber, filter, isLdapv3);
 549                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 550                         ber.encodeStringArray(attrs, isLdapv3);
 551                     ber.endSeq();
 552                 ber.endSeq();
 553                 if (isLdapv3) encodeControls(ber, reqCtls);
 554             ber.endSeq();
 555 
 556          LdapRequest req =
 557                 conn.writeRequest(ber, curMsgId, false, replyQueueCapacity);
 558 
 559          res.msgId = curMsgId;
 560          res.status = LdapClient.LDAP_SUCCESS; //optimistic
 561          if (waitFirstReply) {
 562              // get first reply
 563              res = getSearchReply(req, batchSize, res, binaryAttrs);
 564          }
 565          return res;
 566     }
 567 
 568     /*
 569      * Abandon the search operation and remove it from the message queue.
 570      */
 571     void clearSearchReply(LdapResult res, Control[] ctls) {
 572         if (res != null && conn != null) {
 573 
 574             // Only send an LDAP abandon operation when clearing the search
 575             // reply from a one-level or subtree search.
 576             LdapRequest req = conn.findRequest(res.msgId);
 577             if (req == null) {
 578                 return;
 579             }
 580 
 581             // OK if req got removed after check; double removal attempt
 582             // but otherwise no harm done
 583 
 584             // Send an LDAP abandon only if the search operation has not yet
 585             // completed.
 586             if (req.hasSearchCompleted()) {
 587                 conn.removeRequest(req);
 588             } else {
 589                 conn.abandonRequest(req, ctls);
 590             }
 591         }
 592     }
 593 
 594     /*
 595      * Retrieve the next batch of entries and/or referrals.
 596      */
 597     LdapResult getSearchReply(int batchSize, LdapResult res,
 598         Hashtable<String, Boolean> binaryAttrs) throws IOException, NamingException {
 599 
 600         ensureOpen();
 601 
 602         LdapRequest req;
 603 
 604         if ((req = conn.findRequest(res.msgId)) == null) {
 605             return null;
 606         }
 607 
 608         return getSearchReply(req, batchSize, res, binaryAttrs);
 609     }
 610 
 611     private LdapResult getSearchReply(LdapRequest req,
 612         int batchSize, LdapResult res, Hashtable<String, Boolean> binaryAttrs)
 613         throws IOException, NamingException {
 614 
 615         if (batchSize == 0)
 616             batchSize = Integer.MAX_VALUE;
 617 
 618         if (res.entries != null) {
 619             res.entries.setSize(0); // clear the (previous) set of entries
 620         } else {
 621             res.entries =
 622                 new Vector<>(batchSize == Integer.MAX_VALUE ? 32 : batchSize);
 623         }
 624 
 625         if (res.referrals != null) {
 626             res.referrals.setSize(0); // clear the (previous) set of referrals
 627         }
 628 
 629         BerDecoder replyBer;    // Decoder for response
 630         int seq;                // Request id
 631 
 632         Attributes lattrs;      // Attribute set read from response
 633         Attribute la;           // Attribute read from response
 634         String DN;              // DN read from response
 635         LdapEntry le;           // LDAP entry representing response
 636         int[] seqlen;           // Holder for response length
 637         int endseq;             // Position of end of response
 638 
 639         for (int i = 0; i < batchSize;) {
 640             replyBer = conn.readReply(req);
 641 
 642             //
 643             // process search reply
 644             //
 645             replyBer.parseSeq(null);                    // init seq
 646             replyBer.parseInt();                        // req id
 647             seq = replyBer.parseSeq(null);
 648 
 649             if (seq == LDAP_REP_SEARCH) {
 650 
 651                 // handle LDAPv3 search entries
 652                 lattrs = new BasicAttributes(caseIgnore);
 653                 DN = replyBer.parseString(isLdapv3);
 654                 le = new LdapEntry(DN, lattrs);
 655                 seqlen = new int[1];
 656 
 657                 replyBer.parseSeq(seqlen);
 658                 endseq = replyBer.getParsePosition() + seqlen[0];
 659                 while ((replyBer.getParsePosition() < endseq) &&
 660                     (replyBer.bytesLeft() > 0)) {
 661                     la = parseAttribute(replyBer, binaryAttrs);
 662                     lattrs.put(la);
 663                 }
 664                 le.respCtls = isLdapv3 ? parseControls(replyBer) : null;
 665 
 666                 res.entries.addElement(le);
 667                 i++;
 668 
 669             } else if ((seq == LDAP_REP_SEARCH_REF) && isLdapv3) {
 670 
 671                 // handle LDAPv3 search reference
 672                 Vector<String> URLs = new Vector<>(4);
 673 
 674                 // %%% Although not strictly correct, some LDAP servers
 675                 //     encode the SEQUENCE OF tag in the SearchResultRef
 676                 if (replyBer.peekByte() ==
 677                     (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {
 678                     replyBer.parseSeq(null);
 679                 }
 680 
 681                 while ((replyBer.bytesLeft() > 0) &&
 682                     (replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
 683 
 684                     URLs.addElement(replyBer.parseString(isLdapv3));
 685                 }
 686 
 687                 if (res.referrals == null) {
 688                     res.referrals = new Vector<>(4);
 689                 }
 690                 res.referrals.addElement(URLs);
 691                 res.resControls = isLdapv3 ? parseControls(replyBer) : null;
 692 
 693                 // Save referral and continue to get next search result
 694 
 695             } else if (seq == LDAP_REP_EXTENSION) {
 696 
 697                 parseExtResponse(replyBer, res); //%%% ignore for now
 698 
 699             } else if (seq == LDAP_REP_RESULT) {
 700 
 701                 parseResult(replyBer, res, isLdapv3);
 702                 res.resControls = isLdapv3 ? parseControls(replyBer) : null;
 703 
 704                 conn.removeRequest(req);
 705                 return res;     // Done with search
 706             }
 707         }
 708 
 709         return res;
 710     }
 711 
 712     private Attribute parseAttribute(BerDecoder ber,
 713                                      Hashtable<String, Boolean> binaryAttrs)
 714         throws IOException {
 715 
 716         int len[] = new int[1];
 717         int seq = ber.parseSeq(null);
 718         String attrid = ber.parseString(isLdapv3);
 719         boolean hasBinaryValues = isBinaryValued(attrid, binaryAttrs);
 720         Attribute la = new LdapAttribute(attrid);
 721 
 722         if ((seq = ber.parseSeq(len)) == LBER_SET) {
 723             int attrlen = len[0];
 724             while (ber.bytesLeft() > 0 && attrlen > 0) {
 725                 try {
 726                     attrlen -= parseAttributeValue(ber, la, hasBinaryValues);
 727                 } catch (IOException ex) {
 728                     ber.seek(attrlen);
 729                     break;
 730                 }
 731             }
 732         } else {
 733             // Skip the rest of the sequence because it is not what we want
 734             ber.seek(len[0]);
 735         }
 736         return la;
 737     }
 738 
 739     //
 740     // returns number of bytes that were parsed. Adds the values to attr
 741     //
 742     private int parseAttributeValue(BerDecoder ber, Attribute la,
 743         boolean hasBinaryValues) throws IOException {
 744 
 745         int len[] = new int[1];
 746 
 747         if (hasBinaryValues) {
 748             la.add(ber.parseOctetString(ber.peekByte(), len));
 749         } else {
 750             la.add(ber.parseStringWithTag(
 751                                     Ber.ASN_SIMPLE_STRING, isLdapv3, len));
 752         }
 753         return len[0];
 754     }
 755 
 756     private boolean isBinaryValued(String attrid,
 757                                    Hashtable<String, Boolean> binaryAttrs) {
 758         String id = attrid.toLowerCase(Locale.ENGLISH);
 759 
 760         return ((id.indexOf(";binary") != -1) ||
 761             defaultBinaryAttrs.containsKey(id) ||
 762             ((binaryAttrs != null) && (binaryAttrs.containsKey(id))));
 763     }
 764 
 765     // package entry point; used by Connection
 766     static void parseResult(BerDecoder replyBer, LdapResult res,
 767             boolean isLdapv3) throws IOException {
 768 
 769         res.status = replyBer.parseEnumeration();
 770         res.matchedDN = replyBer.parseString(isLdapv3);
 771         res.errorMessage = replyBer.parseString(isLdapv3);
 772 
 773         // handle LDAPv3 referrals (if present)
 774         if (isLdapv3 &&
 775             (replyBer.bytesLeft() > 0) &&
 776             (replyBer.peekByte() == LDAP_REP_REFERRAL)) {
 777 
 778             Vector<String> URLs = new Vector<>(4);
 779             int[] seqlen = new int[1];
 780 
 781             replyBer.parseSeq(seqlen);
 782             int endseq = replyBer.getParsePosition() + seqlen[0];
 783             while ((replyBer.getParsePosition() < endseq) &&
 784                 (replyBer.bytesLeft() > 0)) {
 785 
 786                 URLs.addElement(replyBer.parseString(isLdapv3));
 787             }
 788 
 789             if (res.referrals == null) {
 790                 res.referrals = new Vector<>(4);
 791             }
 792             res.referrals.addElement(URLs);
 793         }
 794     }
 795 
 796     // package entry point; used by Connection
 797     static Vector<Control> parseControls(BerDecoder replyBer) throws IOException {
 798 
 799         // handle LDAPv3 controls (if present)
 800         if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) {
 801             Vector<Control> ctls = new Vector<>(4);
 802             String controlOID;
 803             boolean criticality = false; // default
 804             byte[] controlValue = null;  // optional
 805             int[] seqlen = new int[1];
 806 
 807             replyBer.parseSeq(seqlen);
 808             int endseq = replyBer.getParsePosition() + seqlen[0];
 809             while ((replyBer.getParsePosition() < endseq) &&
 810                 (replyBer.bytesLeft() > 0)) {
 811 
 812                 replyBer.parseSeq(null);
 813                 controlOID = replyBer.parseString(true);
 814 
 815                 if ((replyBer.bytesLeft() > 0) &&
 816                     (replyBer.peekByte() == Ber.ASN_BOOLEAN)) {
 817                     criticality = replyBer.parseBoolean();
 818                 }
 819                 if ((replyBer.bytesLeft() > 0) &&
 820                     (replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
 821                     controlValue =
 822                         replyBer.parseOctetString(Ber.ASN_OCTET_STR, null);
 823                 }
 824                 if (controlOID != null) {
 825                     ctls.addElement(
 826                         new BasicControl(controlOID, criticality, controlValue));
 827                 }
 828             }
 829             return ctls;
 830         } else {
 831             return null;
 832         }
 833     }
 834 
 835     private void parseExtResponse(BerDecoder replyBer, LdapResult res)
 836         throws IOException {
 837 
 838         parseResult(replyBer, res, isLdapv3);
 839 
 840         if ((replyBer.bytesLeft() > 0) &&
 841             (replyBer.peekByte() == LDAP_REP_EXT_OID)) {
 842             res.extensionId =
 843                 replyBer.parseStringWithTag(LDAP_REP_EXT_OID, isLdapv3, null);
 844         }
 845         if ((replyBer.bytesLeft() > 0) &&
 846             (replyBer.peekByte() == LDAP_REP_EXT_VAL)) {
 847             res.extensionValue =
 848                 replyBer.parseOctetString(LDAP_REP_EXT_VAL, null);
 849         }
 850 
 851         res.resControls = parseControls(replyBer);
 852     }
 853 
 854     //
 855     // Encode LDAPv3 controls
 856     //
 857     static void encodeControls(BerEncoder ber, Control[] reqCtls)
 858         throws IOException {
 859 
 860         if ((reqCtls == null) || (reqCtls.length == 0)) {
 861             return;
 862         }
 863 
 864         byte[] controlVal;
 865 
 866         ber.beginSeq(LdapClient.LDAP_CONTROLS);
 867 
 868             for (int i = 0; i < reqCtls.length; i++) {
 869                 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 870                     ber.encodeString(reqCtls[i].getID(), true); // control OID
 871                     if (reqCtls[i].isCritical()) {
 872                         ber.encodeBoolean(true); // critical control
 873                     }
 874                     if ((controlVal = reqCtls[i].getEncodedValue()) != null) {
 875                         ber.encodeOctetString(controlVal, Ber.ASN_OCTET_STR);
 876                     }
 877                 ber.endSeq();
 878             }
 879         ber.endSeq();
 880     }
 881 
 882     /**
 883      * Reads the next reply corresponding to msgId, outstanding on requestBer.
 884      * Processes the result and any controls.
 885      */
 886     private LdapResult processReply(LdapRequest req,
 887         LdapResult res, int responseType) throws IOException, NamingException {
 888 
 889         BerDecoder rber = conn.readReply(req);
 890 
 891         rber.parseSeq(null);    // init seq
 892         rber.parseInt();        // msg id
 893         if (rber.parseByte() !=  responseType) {
 894             return res;
 895         }
 896 
 897         rber.parseLength();
 898         parseResult(rber, res, isLdapv3);
 899         res.resControls = isLdapv3 ? parseControls(rber) : null;
 900 
 901         conn.removeRequest(req);
 902 
 903         return res;     // Done with operation
 904     }
 905 
 906     ////////////////////////////////////////////////////////////////////////////
 907     //
 908     // LDAP modify:
 909     //  Modify the DN dn with the operations on attributes attrs.
 910     //  ie, operations[0] is the operation to be performed on
 911     //  attrs[0];
 912     //          dn - DN to modify
 913     //          operations - add, delete or replace
 914     //          attrs - array of Attribute
 915     //          reqCtls - array of request controls
 916     //
 917     ////////////////////////////////////////////////////////////////////////////
 918 
 919     static final int ADD = 0;
 920     static final int DELETE = 1;
 921     static final int REPLACE = 2;
 922 
 923     LdapResult modify(String dn, int operations[], Attribute attrs[],
 924                       Control[] reqCtls)
 925         throws IOException, NamingException {
 926 
 927         ensureOpen();
 928 
 929         LdapResult res = new LdapResult();
 930         res.status = LDAP_OPERATIONS_ERROR;
 931 
 932         if (dn == null || operations.length != attrs.length)
 933             return res;
 934 
 935         BerEncoder ber = new BerEncoder();
 936         int curMsgId = conn.getMsgId();
 937 
 938         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 939             ber.encodeInt(curMsgId);
 940             ber.beginSeq(LDAP_REQ_MODIFY);
 941                 ber.encodeString(dn, isLdapv3);
 942                 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 943                     for (int i = 0; i < operations.length; i++) {
 944                         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 945                             ber.encodeInt(operations[i], LBER_ENUMERATED);
 946 
 947                             // zero values is not permitted for the add op.
 948                             if ((operations[i] == ADD) && hasNoValue(attrs[i])) {
 949                                 throw new InvalidAttributeValueException(
 950                                     "'" + attrs[i].getID() + "' has no values.");
 951                             } else {
 952                                 encodeAttribute(ber, attrs[i]);
 953                             }
 954                         ber.endSeq();
 955                     }
 956                 ber.endSeq();
 957             ber.endSeq();
 958             if (isLdapv3) encodeControls(ber, reqCtls);
 959         ber.endSeq();
 960 
 961         LdapRequest req = conn.writeRequest(ber, curMsgId);
 962 
 963         return processReply(req, res, LDAP_REP_MODIFY);
 964     }
 965 
 966     private void encodeAttribute(BerEncoder ber, Attribute attr)
 967         throws IOException, NamingException {
 968 
 969         ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
 970             ber.encodeString(attr.getID(), isLdapv3);
 971             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR | 1);
 972                 NamingEnumeration<?> enum_ = attr.getAll();
 973                 Object val;
 974                 while (enum_.hasMore()) {
 975                     val = enum_.next();
 976                     if (val instanceof String) {
 977                         ber.encodeString((String)val, isLdapv3);
 978                     } else if (val instanceof byte[]) {
 979                         ber.encodeOctetString((byte[])val, Ber.ASN_OCTET_STR);
 980                     } else if (val == null) {
 981                         // no attribute value
 982                     } else {
 983                         throw new InvalidAttributeValueException(
 984                             "Malformed '" + attr.getID() + "' attribute value");
 985                     }
 986                 }
 987             ber.endSeq();
 988         ber.endSeq();
 989     }
 990 
 991     private static boolean hasNoValue(Attribute attr) throws NamingException {
 992         return attr.size() == 0 || (attr.size() == 1 && attr.get() == null);
 993     }
 994 
 995     ////////////////////////////////////////////////////////////////////////////
 996     //
 997     // LDAP add
 998     //          Adds entry to the Directory
 999     //
1000     ////////////////////////////////////////////////////////////////////////////
1001 
1002     LdapResult add(LdapEntry entry, Control[] reqCtls)
1003         throws IOException, NamingException {
1004 
1005         ensureOpen();
1006 
1007         LdapResult res = new LdapResult();
1008         res.status = LDAP_OPERATIONS_ERROR;
1009 
1010         if (entry == null || entry.DN == null)
1011             return res;
1012 
1013         BerEncoder ber = new BerEncoder();
1014         int curMsgId = conn.getMsgId();
1015         Attribute attr;
1016 
1017             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1018                 ber.encodeInt(curMsgId);
1019                 ber.beginSeq(LDAP_REQ_ADD);
1020                     ber.encodeString(entry.DN, isLdapv3);
1021                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1022                         NamingEnumeration<? extends Attribute> enum_ =
1023                                 entry.attributes.getAll();
1024                         while (enum_.hasMore()) {
1025                             attr = enum_.next();
1026 
1027                             // zero values is not permitted
1028                             if (hasNoValue(attr)) {
1029                                 throw new InvalidAttributeValueException(
1030                                     "'" + attr.getID() + "' has no values.");
1031                             } else {
1032                                 encodeAttribute(ber, attr);
1033                             }
1034                         }
1035                     ber.endSeq();
1036                 ber.endSeq();
1037                 if (isLdapv3) encodeControls(ber, reqCtls);
1038             ber.endSeq();
1039 
1040         LdapRequest req = conn.writeRequest(ber, curMsgId);
1041         return processReply(req, res, LDAP_REP_ADD);
1042     }
1043 
1044     ////////////////////////////////////////////////////////////////////////////
1045     //
1046     // LDAP delete
1047     //          deletes entry from the Directory
1048     //
1049     ////////////////////////////////////////////////////////////////////////////
1050 
1051     LdapResult delete(String DN, Control[] reqCtls)
1052         throws IOException, NamingException {
1053 
1054         ensureOpen();
1055 
1056         LdapResult res = new LdapResult();
1057         res.status = LDAP_OPERATIONS_ERROR;
1058 
1059         if (DN == null)
1060             return res;
1061 
1062         BerEncoder ber = new BerEncoder();
1063         int curMsgId = conn.getMsgId();
1064 
1065             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1066                 ber.encodeInt(curMsgId);
1067                 ber.encodeString(DN, LDAP_REQ_DELETE, isLdapv3);
1068                 if (isLdapv3) encodeControls(ber, reqCtls);
1069             ber.endSeq();
1070 
1071         LdapRequest req = conn.writeRequest(ber, curMsgId);
1072 
1073         return processReply(req, res, LDAP_REP_DELETE);
1074     }
1075 
1076     ////////////////////////////////////////////////////////////////////////////
1077     //
1078     // LDAP modrdn
1079     //  Changes the last element of DN to newrdn
1080     //          dn - DN to change
1081     //          newrdn - new RDN to rename to
1082     //          deleteoldrdn - boolean whether to delete old attrs or not
1083     //          newSuperior - new place to put the entry in the tree
1084     //                        (ignored if server is LDAPv2)
1085     //          reqCtls - array of request controls
1086     //
1087     ////////////////////////////////////////////////////////////////////////////
1088 
1089     LdapResult moddn(String DN, String newrdn, boolean deleteOldRdn,
1090                      String newSuperior, Control[] reqCtls)
1091         throws IOException, NamingException {
1092 
1093         ensureOpen();
1094 
1095         boolean changeSuperior = (newSuperior != null &&
1096                                   newSuperior.length() > 0);
1097 
1098         LdapResult res = new LdapResult();
1099         res.status = LDAP_OPERATIONS_ERROR;
1100 
1101         if (DN == null || newrdn == null)
1102             return res;
1103 
1104         BerEncoder ber = new BerEncoder();
1105         int curMsgId = conn.getMsgId();
1106 
1107             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1108                 ber.encodeInt(curMsgId);
1109                 ber.beginSeq(LDAP_REQ_MODRDN);
1110                     ber.encodeString(DN, isLdapv3);
1111                     ber.encodeString(newrdn, isLdapv3);
1112                     ber.encodeBoolean(deleteOldRdn);
1113                     if(isLdapv3 && changeSuperior) {
1114                         //System.err.println("changin superior");
1115                         ber.encodeString(newSuperior, LDAP_SUPERIOR_DN, isLdapv3);
1116                     }
1117                 ber.endSeq();
1118                 if (isLdapv3) encodeControls(ber, reqCtls);
1119             ber.endSeq();
1120 
1121 
1122         LdapRequest req = conn.writeRequest(ber, curMsgId);
1123 
1124         return processReply(req, res, LDAP_REP_MODRDN);
1125     }
1126 
1127     ////////////////////////////////////////////////////////////////////////////
1128     //
1129     // LDAP compare
1130     //  Compare attribute->value pairs in dn
1131     //
1132     ////////////////////////////////////////////////////////////////////////////
1133 
1134     LdapResult compare(String DN, String type, String value, Control[] reqCtls)
1135         throws IOException, NamingException {
1136 
1137         ensureOpen();
1138 
1139         LdapResult res = new LdapResult();
1140         res.status = LDAP_OPERATIONS_ERROR;
1141 
1142         if (DN == null || type == null || value == null)
1143             return res;
1144 
1145         BerEncoder ber = new BerEncoder();
1146         int curMsgId = conn.getMsgId();
1147 
1148             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1149                 ber.encodeInt(curMsgId);
1150                 ber.beginSeq(LDAP_REQ_COMPARE);
1151                     ber.encodeString(DN, isLdapv3);
1152                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1153                         ber.encodeString(type, isLdapv3);
1154 
1155                         // replace any escaped characters in the value
1156                         byte[] val = isLdapv3 ?
1157                             value.getBytes("UTF8") : value.getBytes("8859_1");
1158                         ber.encodeOctetString(
1159                             Filter.unescapeFilterValue(val, 0, val.length),
1160                             Ber.ASN_OCTET_STR);
1161 
1162                     ber.endSeq();
1163                 ber.endSeq();
1164                 if (isLdapv3) encodeControls(ber, reqCtls);
1165             ber.endSeq();
1166 
1167         LdapRequest req = conn.writeRequest(ber, curMsgId);
1168 
1169         return processReply(req, res, LDAP_REP_COMPARE);
1170     }
1171 
1172     ////////////////////////////////////////////////////////////////////////////
1173     //
1174     // LDAP extended operation
1175     //
1176     ////////////////////////////////////////////////////////////////////////////
1177 
1178     LdapResult extendedOp(String id, byte[] request, Control[] reqCtls,
1179         boolean pauseAfterReceipt) throws IOException, NamingException {
1180 
1181         ensureOpen();
1182 
1183         LdapResult res = new LdapResult();
1184         res.status = LDAP_OPERATIONS_ERROR;
1185 
1186         if (id == null)
1187             return res;
1188 
1189         BerEncoder ber = new BerEncoder();
1190         int curMsgId = conn.getMsgId();
1191 
1192             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1193                 ber.encodeInt(curMsgId);
1194                 ber.beginSeq(LDAP_REQ_EXTENSION);
1195                     ber.encodeString(id,
1196                         Ber.ASN_CONTEXT | 0, isLdapv3);//[0]
1197                     if (request != null) {
1198                         ber.encodeOctetString(request,
1199                             Ber.ASN_CONTEXT | 1);//[1]
1200                     }
1201                 ber.endSeq();
1202                 encodeControls(ber, reqCtls); // always v3
1203             ber.endSeq();
1204 
1205         LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);
1206 
1207         BerDecoder rber = conn.readReply(req);
1208 
1209         rber.parseSeq(null);    // init seq
1210         rber.parseInt();        // msg id
1211         if (rber.parseByte() !=  LDAP_REP_EXTENSION) {
1212             return res;
1213         }
1214 
1215         rber.parseLength();
1216         parseExtResponse(rber, res);
1217         conn.removeRequest(req);
1218 
1219         return res;     // Done with operation
1220     }
1221 
1222 
1223 
1224     ////////////////////////////////////////////////////////////////////////////
1225     //
1226     // Some BER definitions convenient for LDAP
1227     //
1228     ////////////////////////////////////////////////////////////////////////////
1229 
1230     static final int LDAP_VERSION3_VERSION2 = 32;
1231     static final int LDAP_VERSION2 = 0x02;
1232     static final int LDAP_VERSION3 = 0x03;              // LDAPv3
1233     static final int LDAP_VERSION = LDAP_VERSION3;
1234 
1235     static final int LDAP_REF_FOLLOW = 0x01;            // follow referrals
1236     static final int LDAP_REF_THROW = 0x02;             // throw referral ex.
1237     static final int LDAP_REF_IGNORE = 0x03;            // ignore referrals
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         synchronized (unsolicited) {
1503             if (unsolicited.size() == 0) {
1504                 return;
1505             }
1506             unsolicited.removeElement(ctx);
1507         }
1508     }
1509 
1510     // NOTE: Cannot be synchronized because this is called asynchronously
1511     // by the reader thread in Connection. Instead, sync on 'unsolicited' Vector.
1512     void processUnsolicited(BerDecoder ber) {
1513         if (debug > 0) {
1514             System.err.println("LdapClient.processUnsolicited");
1515         }
1516         synchronized (unsolicited) {
1517             try {
1518                 // Parse the response
1519                 LdapResult res = new LdapResult();
1520 
1521                 ber.parseSeq(null); // init seq
1522                 ber.parseInt();             // msg id; should be 0; ignored
1523                 if (ber.parseByte() != LDAP_REP_EXTENSION) {
1524                     throw new IOException(
1525                         "Unsolicited Notification must be an Extended Response");
1526                 }
1527                 ber.parseLength();
1528                 parseExtResponse(ber, res);
1529 
1530                 if (DISCONNECT_OID.equals(res.extensionId)) {
1531                     // force closing of connection
1532                     forceClose(pooled);
1533                 }
1534 
1535                 if (unsolicited.size() > 0) {
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                     UnsolicitedNotification 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                         unsolicited.elementAt(0).convertControls(res.resControls) :
1548                         null);
1549 
1550                     // Fire UnsolicitedNotification events to listeners
1551                     notifyUnsolicited(notice);
1552 
1553                     // If "disconnect" notification,
1554                     // notify unsolicited listeners via NamingException
1555                     if (DISCONNECT_OID.equals(res.extensionId)) {
1556                         notifyUnsolicited(
1557                             new CommunicationException("Connection closed"));
1558                     }
1559                 }
1560             } catch (IOException e) {
1561                 if (unsolicited.size() == 0)
1562                     return;  // no one registered; ignore
1563 
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 
1577     private void notifyUnsolicited(Object e) {
1578         for (int i = 0; i < unsolicited.size(); i++) {
1579             unsolicited.elementAt(i).fireUnsolicited(e);
1580         }
1581         if (e instanceof NamingException) {
1582             unsolicited.setSize(0);  // no more listeners after exception
1583         }
1584     }
1585 
1586     private void ensureOpen() throws IOException {
1587         if (conn == null || !conn.useable) {
1588             if (conn != null && conn.closureReason != null) {
1589                 throw conn.closureReason;
1590             } else {
1591                 throw new IOException("connection closed");
1592             }
1593         }
1594     }
1595 
1596     // package private (used by LdapCtx)
1597     static LdapClient getInstance(boolean usePool, String hostname, int port,
1598         String factory, int connectTimeout, int readTimeout, OutputStream trace,
1599         int version, String authMechanism, Control[] ctls, String protocol,
1600         String user, Object passwd, Hashtable<?,?> env) throws NamingException {
1601 
1602         if (usePool) {
1603             if (LdapPoolManager.isPoolingAllowed(factory, trace,
1604                     authMechanism, protocol, env)) {
1605                 LdapClient answer = LdapPoolManager.getLdapClient(
1606                         hostname, port, factory, connectTimeout, readTimeout,
1607                         trace, version, authMechanism, ctls, protocol, user,
1608                         passwd, env);
1609                 answer.referenceCount = 1;   // always one when starting out
1610                 return answer;
1611             }
1612         }
1613         return new LdapClient(hostname, port, factory, connectTimeout,
1614                                         readTimeout, trace, null);
1615     }
1616 }