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