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