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