View Javadoc
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 }