Source for gnu.inet.ldap.LDAPConnection

   1: /*
   2:  * LDAPConnection.java
   3:  * Copyright (C) 2004 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20:  * 
  21:  * Linking this library statically or dynamically with other modules is
  22:  * making a combined work based on this library.  Thus, the terms and
  23:  * conditions of the GNU General Public License cover the whole
  24:  * combination.
  25:  *
  26:  * As a special exception, the copyright holders of this library give you
  27:  * permission to link this library with independent modules to produce an
  28:  * executable, regardless of the license terms of these independent
  29:  * modules, and to copy and distribute the resulting executable under
  30:  * terms of your choice, provided that you also meet, for each linked
  31:  * independent module, the terms and conditions of the license of that
  32:  * module.  An independent module is a module which is not derived from
  33:  * or based on this library.  If you modify this library, you may extend
  34:  * this exception to your version of the library, but you are not
  35:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.ldap;
  40: 
  41: import java.io.BufferedInputStream;
  42: import java.io.BufferedOutputStream;
  43: import java.io.InputStream;
  44: import java.io.IOException;
  45: import java.io.OutputStream;
  46: import java.util.ArrayList;
  47: import java.util.HashMap;
  48: import java.util.Iterator;
  49: import java.util.List;
  50: import java.util.Map;
  51: import java.util.Set;
  52: import java.util.TreeMap;
  53: import java.net.InetSocketAddress;
  54: import java.net.ProtocolException;
  55: import java.net.Socket;
  56: import java.net.SocketAddress;
  57: import java.util.ArrayList;
  58: import javax.naming.ldap.Control;
  59: 
  60: /**
  61:  * An LDAPv3 client.
  62:  * This client is still experimental, please contact
  63:  * <a href='mailto:dog@gnu.org'>Chris Burdess</a> if you want to help out
  64:  * with it.
  65:  *
  66:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  67:  */
  68: public class LDAPConnection
  69: {
  70: 
  71:   /**
  72:    * The default LDAP port.
  73:    */
  74:   public static final int DEFAULT_PORT = 389;
  75: 
  76:   public static final int SCOPE_BASE_OBJECT = 0;
  77:   public static final int SCOPE_SINGLE_LEVEL = 1;
  78:   public static final int SCOPE_WHOLE_SUBTREE = 2;
  79: 
  80:   /**
  81:    * Do not dereference aliases in searching or in locating the base object
  82:    * of the search.
  83:    */
  84:   public static final int DEREF_NEVER = 0;
  85: 
  86:   /**
  87:    * Dereference aliases in subordinates of the base object in searching,
  88:    * but not in locating the base object of the search.
  89:    */
  90:   public static final int DEREF_IN_SEARCHING = 1;
  91: 
  92:   /**
  93:    * Dereference aliases in locating the base object of the search, but not
  94:    * when searching subordinates of the base object.
  95:    */
  96:   public static final int DEREF_FINDING_BASE_OBJ = 2;
  97: 
  98:   /**
  99:    * Dereference aliases both in searching and in locating the base object
 100:    * of the search.
 101:    */
 102:   public static final int DEREF_ALWAYS = 3;
 103: 
 104:   private static final int SUCCESS = 0;
 105:   private static final int SASL_BIND_IN_PROGRESS = 14;
 106: 
 107:   private static final int MESSAGE = 0x30;
 108:   private static final int BIND_REQUEST = 0x60;
 109:   private static final int BIND_RESPONSE = 0x61;
 110:   private static final int UNBIND_REQUEST = 0x62;
 111:   private static final int SEARCH_REQUEST = 0x63;
 112:   private static final int SEARCH_RESULT = 0x64;
 113:   private static final int SEARCH_RESULT_DONE = 0x65;
 114:   private static final int MODIFY_REQUEST = 0x66;
 115:   private static final int MODIFY_RESPONSE = 0x67;
 116:   private static final int ADD_REQUEST = 0x68;
 117:   private static final int ADD_RESPONSE = 0x69;
 118:   private static final int DELETE_REQUEST = 0x6a;
 119:   private static final int DELETE_RESPONSE = 0x6b;
 120:   private static final int MODIFY_DN_REQUEST = 0x6c;
 121:   private static final int MODIFY_DN_RESPONSE = 0x6d;
 122:   private static final int SEARCH_REFERENCE = 0x73;
 123: 
 124:   protected String host;
 125:   protected int port;
 126:   protected int version; // 2 or 3
 127:   
 128:   private Socket socket;
 129:   private InputStream in;
 130:   private OutputStream out;
 131:   
 132:   private int messageId;
 133:   private Map asyncResponses;
 134: 
 135:   /**
 136:    * Creates a new LDAP connection to the specified host, using the default
 137:    * LDAP port.
 138:    * @param host the host
 139:    */
 140:   public LDAPConnection(String host)
 141:     throws IOException
 142:   {
 143:     this(host, DEFAULT_PORT, 0, 0);
 144:   }
 145: 
 146:   /**
 147:    * Creates a new LDAP connection to the specified host and port.
 148:    * @param host the host
 149:    * @param port the port
 150:    */
 151:   public LDAPConnection(String host, int port)
 152:     throws IOException
 153:   {
 154:     this(host, port, 0, 0);
 155:   }
 156: 
 157:   /**
 158:    * Creates a new LDAP connection to the specified host, port, and timeouts.
 159:    * @param host the host
 160:    * @param port the port
 161:    * @param connectionTimeout the connection timeout in ms
 162:    * @param timeout the socket I/O timeout in ms
 163:    */
 164:   public LDAPConnection(String host, int port,
 165:                         int connectionTimeout, int timeout)
 166:     throws IOException
 167:   {
 168:     this.host = host;
 169:     if (port < 0)
 170:       {
 171:         port = DEFAULT_PORT;
 172:       }
 173:     this.port = port;
 174:     messageId = 0;
 175:     asyncResponses = new HashMap();
 176:     version = 3;
 177: 
 178:     // Connect
 179:     socket = new Socket();
 180:     SocketAddress address = new InetSocketAddress(host, port);
 181:     if (connectionTimeout > 0)
 182:       {
 183:         socket.connect(address, connectionTimeout);
 184:       }
 185:     else
 186:       {
 187:         socket.connect(address);
 188:       }
 189:     in = new BufferedInputStream(socket.getInputStream());
 190:     out = new BufferedOutputStream(socket.getOutputStream());
 191:   }
 192: 
 193:   /**
 194:    * Sets the version of LDAP to use.
 195:    * This implementation supports versions 2 and 3.
 196:    * @param version the LDAP version
 197:    */
 198:   public void setVersion(int version)
 199:   {
 200:     if (version < 2 || version > 3)
 201:       {
 202:         throw new IllegalArgumentException(Integer.toString(version));
 203:       }
 204:     this.version = version;
 205:   }
 206: 
 207:   /**
 208:    * Initiates a bind operation to authenticate the client to the server.
 209:    * @param name the LDAP DN to authenticate to, or <code>null</code> for
 210:    * anonymous binding
 211:    * @param mechanism the SASL mechanism to use, or <code>null</code> for
 212:    * simple authentication
 213:    * @param credentials the security credentials to use
 214:    * @return the LDAP result
 215:    */
 216:   public LDAPResult bind(String name, String mechanism,
 217:                          byte[] credentials, Control[] controls)
 218:     throws IOException
 219:   {
 220:     int id = messageId++;
 221:     boolean utf8 = (version == 3);
 222:     BEREncoder bind = new BEREncoder(utf8);
 223:     if (mechanism == null)
 224:       {
 225:         bind.append(version);
 226:         bind.append(name);
 227:         if (credentials != null)
 228:           {
 229:             bind.append(credentials);
 230:           }
 231:       }
 232:     else
 233:       {
 234:         bind.append(version);
 235:         bind.append(name);
 236:         // SASL credentials
 237:         BEREncoder saslCredentials = new BEREncoder(utf8);
 238:         saslCredentials.append(mechanism);
 239:         if (credentials != null)
 240:           {
 241:             saslCredentials.append(credentials);
 242:           }
 243:         bind.append(saslCredentials.toByteArray(), BERConstants.SEQUENCE);
 244:       }
 245:     // Request controls
 246:     BEREncoder ctls = new BEREncoder(utf8);
 247:     if (controls != null)
 248:       {
 249:         for (int i = 0; i < controls.length; i++)
 250:           {
 251:             ctls.append(controlSequence(controls[i], utf8),
 252:                         BERConstants.SEQUENCE);
 253:           }
 254:       }
 255:     bind.append(ctls.toByteArray(), BERConstants.CONTEXT);
 256:     // Write request
 257:     write(id, BIND_REQUEST, bind.toByteArray());
 258:     // Read response
 259:     BERDecoder response = read(id);
 260:     BERDecoder resultSequence = response.parseSequence(BIND_RESPONSE);
 261:     LDAPResult result = parseResult(resultSequence);
 262:     if (resultSequence.available())
 263:       {
 264:         byte[] serverCreds = resultSequence.parseOctetString();
 265:         // TODO
 266:       }
 267:     // TODO response controls
 268:     return result;
 269:   }
 270: 
 271:   /**
 272:    * Issues an unbind request. This indicates to the server that the client
 273:    * has no more requests to issue and will terminate the connection. After
 274:    * invoking this method, no further methods may be invoked.
 275:    */
 276:   public void unbind()
 277:     throws IOException
 278:   {
 279:     int id = messageId++;
 280:     boolean utf8 = (version == 3);
 281:     BEREncoder unbind = new BEREncoder(utf8);
 282:     unbind.appendNull();
 283:     write(id, UNBIND_REQUEST, unbind.toByteArray());
 284:     // Close socket
 285:     socket.close();
 286:   }
 287: 
 288:   /**
 289:    * Issues a search request.
 290:    * @param name the LDAP DN that is the base object entry relative to which
 291:    * the search is to be performed
 292:    * @param scope the search scope, one of the SCOPE_* constants
 293:    * @param derefAliases whether to dereference aliases, one of the DEREF_*
 294:    * constants
 295:    * @param sizeLimit the maximum number of entries to return, or 0 for no
 296:    * restriction
 297:    * @param timeLimit the maximum time in seconds permitted for the search,
 298:    * or 0 for no restriction
 299:    * @param typesOnly whether to return only attribute types(true) or both
 300:    * attribute types and values(false)
 301:    * @param filter the search filter in RFC2254 format
 302:    * @param attributes the IDs of the attributes to return
 303:    * @param controls the request controls
 304:    * @param handler the result handler to receive notification of results
 305:    * @return the LDAP result
 306:    */
 307:   public LDAPResult search(String name, int scope, int derefAliases,
 308:                            int sizeLimit, int timeLimit,
 309:                            boolean typesOnly, String filter,
 310:                            String[] attributes, Control[] controls,
 311:                            ResultHandler handler)
 312:     throws IOException
 313:   {
 314:     if (filter == null || filter.length() == 0)
 315:       {
 316:         filter = "(objectClass=*)";
 317:       }
 318:     int id = messageId++;
 319:     boolean utf8 = (version == 3);
 320:     BEREncoder search = new BEREncoder(utf8);
 321:     search.append(name);
 322:     search.append(scope, BERConstants.ENUMERATED);
 323:     search.append(derefAliases, BERConstants.ENUMERATED);
 324:     search.append(sizeLimit);
 325:     search.append(timeLimit);
 326:     search.append(typesOnly);
 327:     search.appendFilter(filter);
 328:     BEREncoder attributeSequence = new BEREncoder(utf8);
 329:     if (attributes != null)
 330:       {
 331:         for (int i = 0; i < attributes.length; i++)
 332:           {
 333:             attributeSequence.append(attributes[i]);
 334:           }
 335:       }
 336:     search.append(attributeSequence.toByteArray(), BERConstants.SEQUENCE);
 337:     // Request controls
 338:     BEREncoder ctls = new BEREncoder(utf8);
 339:     if (controls != null)
 340:       {
 341:         for (int i = 0; i < controls.length; i++)
 342:           {
 343:             ctls.append(controlSequence(controls[i], utf8),
 344:                         BERConstants.SEQUENCE);
 345:           }
 346:       }
 347:     search.append(ctls.toByteArray(), BERConstants.SEQUENCE);
 348:     // Write request
 349:     write(id, SEARCH_REQUEST, search.toByteArray());
 350:     do
 351:       {
 352:         BERDecoder response = read(id);
 353:         int code = response.parseType();
 354:         switch (code)
 355:           {
 356:           case SEARCH_RESULT:
 357:             BERDecoder entry = response.parseSequence(code);
 358:             String objectName = entry.parseString();
 359:             BERDecoder attributeSeq = entry.parseSequence(0x30);
 360:             Map attrs = new TreeMap();
 361:             while (attributeSeq.available())
 362:               {
 363:                 BERDecoder attribute = attributeSeq.parseSequence(0x30);
 364:                 String type = attribute.parseString();
 365:                 BERDecoder values = attribute.parseSet(0x31);
 366:                 List acc = new ArrayList();
 367:                 while (values.available())
 368:                   {
 369:                     int valueType = values.parseType();
 370:                     switch (valueType)
 371:                       {
 372:                       case BERConstants.BOOLEAN:
 373:                         acc.add(Boolean.valueOf(values.parseBoolean()));
 374:                         break;
 375:                       case BERConstants.INTEGER:
 376:                       case BERConstants.ENUMERATED:
 377:                         acc.add(new Integer(values.parseInt()));
 378:                         break;
 379:                         // TODO float
 380:                       case BERConstants.UTF8_STRING:
 381:                         acc.add(values.parseString());
 382:                         break;
 383:                       case BERConstants.OCTET_STRING:
 384:                         acc.add(values.parseOctetString());
 385:                         break;
 386:                       }
 387:                   }
 388:                 attrs.put(type, acc);
 389:               }
 390:             handler.searchResultEntry(objectName, attrs);
 391:             break;
 392:           case SEARCH_REFERENCE:
 393:             List acc = new ArrayList();
 394:             BERDecoder urls = response.parseSequence(code);
 395:             while (urls.available())
 396:               {
 397:                 acc.add(urls.parseString());
 398:               }
 399:             handler.searchResultReference(acc);
 400:             break;
 401:           case SEARCH_RESULT_DONE:
 402:             return parseResult(response.parseSequence(code));
 403:           default:
 404:             throw new ProtocolException("Unexpected response: " + code);
 405:           }
 406:       }
 407:     while (true);
 408:   }
 409: 
 410:   /**
 411:    * Issues a modify request.
 412:    * @param name the LDAP DN of the object to be modified(alias
 413:    * dereferencing will not be performed)
 414:    * @param modifications a sequence of modifications to be executed
 415:    * to be executed
 416:    * @see Modification
 417:    */
 418:   public LDAPResult modify(String name, final Modification[] modifications)
 419:     throws IOException
 420:   {
 421:     int id = messageId++;
 422:     boolean utf8 = (version == 3);
 423:     BEREncoder modify = new BEREncoder(utf8);
 424:     modify.append(name);
 425:     BEREncoder modSeq = new BEREncoder(utf8);
 426:     for (int i = 0; i < modifications.length; i++)
 427:       {
 428:         BEREncoder mod = new BEREncoder(utf8);
 429:         mod.append(modifications[i].operation);
 430:         BEREncoder typeAndValues = new BEREncoder(utf8);
 431:         typeAndValues.append(modifications[i].type);
 432:         BEREncoder values = new BEREncoder(utf8);
 433:         appendValues(values, modifications[i].values);
 434:         typeAndValues.append(values.toByteArray(), BERConstants.SET);
 435:         mod.append(typeAndValues.toByteArray(), BERConstants.SEQUENCE);
 436:         modSeq.append(mod.toByteArray(), BERConstants.SEQUENCE);
 437:       }
 438:     modify.append(modSeq.toByteArray(), BERConstants.SEQUENCE);
 439:     // Write request
 440:     write(id, MODIFY_REQUEST, modify.toByteArray());
 441:     // Read response
 442:     BERDecoder response = read(id);
 443:     BERDecoder resultSequence = response.parseSequence(MODIFY_RESPONSE);
 444:     LDAPResult result = parseResult(resultSequence);
 445:     return result;
 446:   }
 447: 
 448:   /**
 449:    * Requests the addition of a new entry into the directory.
 450:    * @param name the LDAP DN of the new entry
 451:    * @param attributes a sequence of attributes to assign to the new entry
 452:    */
 453:   public LDAPResult add(String name, AttributeValues[] attributes)
 454:     throws IOException
 455:   {
 456:     int id = messageId++;
 457:     boolean utf8 = (version == 3);
 458:     BEREncoder add = new BEREncoder(utf8);
 459:     add.append(name);
 460:     BEREncoder attrSeq = new BEREncoder(utf8);
 461:     for (int i = 0; i < attributes.length; i++)
 462:       {
 463:         BEREncoder attr = new BEREncoder(utf8);
 464:         attr.append(attributes[i].type);
 465:         BEREncoder values = new BEREncoder(utf8);
 466:         appendValues(values, attributes[i].values);
 467:         attr.append(values.toByteArray(), BERConstants.SET);
 468:         attrSeq.append(attr.toByteArray(), BERConstants.SEQUENCE);
 469:       }
 470:     add.append(attrSeq.toByteArray(), BERConstants.SEQUENCE);
 471:     // Write request
 472:     write(id, ADD_REQUEST, add.toByteArray());
 473:     // Read response
 474:     BERDecoder response = read(id);
 475:     BERDecoder resultSequence = response.parseSequence(ADD_RESPONSE);
 476:     LDAPResult result = parseResult(resultSequence);
 477:     return result;
 478:   }
 479: 
 480:   /**
 481:    * Requests the removal of an entry from the directory.
 482:    * @param name the LDAP DN of the entry to remove
 483:    */
 484:   public LDAPResult delete(String name)
 485:     throws IOException
 486:   {
 487:     int id = messageId++;
 488:     boolean utf8 = (version == 3);
 489:     BEREncoder del = new BEREncoder(utf8);
 490:     del.append(name);
 491:     // Write request
 492:     write(id, DELETE_REQUEST, del.toByteArray());
 493:     // Read response
 494:     BERDecoder response = read(id);
 495:     int code = response.parseType();
 496:     if (code != DELETE_RESPONSE)
 497:       {
 498:         throw new ProtocolException("Unexpected response type: " +
 499:                                     code);
 500:       }
 501:     BERDecoder resultSequence = response.parseSequence();
 502:     LDAPResult result = parseResult(resultSequence);
 503:     return result;
 504:   }
 505: 
 506:   /**
 507:    * Changes the leftmost(least significant) component of the name of an
 508:    * entry in the directory, or move a subtree of entries to a new location
 509:    * in the directory.
 510:    * @param name the LDAP DN of the entry to be changed
 511:    * @param newRDN the RDN that will form the leftmost component of the new
 512:    * name of the entry
 513:    * @param deleteOldRDN if false, the old RDN values will be retained as
 514:    * attributes of the entry, otherwise they are deleted from the entry
 515:    * @param newSuperior if non-null, the DN of the entry to become the
 516:    * immediate superior of the existing entry
 517:    */
 518:   public LDAPResult modifyDN(String name, String newRDN,
 519:                              boolean deleteOldRDN, String newSuperior)
 520:     throws IOException
 521:   {
 522:     int id = messageId++;
 523:     boolean utf8 = (version == 3);
 524:     BEREncoder modifyDN = new BEREncoder(utf8);
 525:     modifyDN.append(name);
 526:     modifyDN.append(newRDN);
 527:     modifyDN.append(deleteOldRDN);
 528:     if (newSuperior != null)
 529:       {
 530:         modifyDN.append(newSuperior);
 531:       }
 532:     // Write request
 533:     write(id, MODIFY_DN_REQUEST, modifyDN.toByteArray());
 534:     // Read response
 535:     BERDecoder response = read(id);
 536:     BERDecoder resultSequence = response.parseSequence(MODIFY_DN_RESPONSE);
 537:     LDAPResult result = parseResult(resultSequence);
 538:     return result;
 539:   }
 540: 
 541:   /* TODO Compare Operation */
 542: 
 543:   /* TODO Abandon Operation */
 544: 
 545:   /* TODO Extended Operation */
 546: 
 547: 
 548: 
 549:   /**
 550:    * Appends the specified set of values to the given encoder.
 551:    */
 552:   void appendValues(BEREncoder encoder, Set values)
 553:     throws BERException
 554:   {
 555:     if (values != null)
 556:       {
 557:         for (Iterator i = values.iterator(); i.hasNext(); )
 558:           {
 559:             Object value = i.next();
 560:             if (value == null)
 561:               {
 562:                 encoder.appendNull();
 563:               }
 564:             else if (value instanceof String)
 565:               {
 566:                 encoder.append((String) value);
 567:               }
 568:             else if (value instanceof Integer)
 569:               {
 570:                 encoder.append(((Integer) value).intValue());
 571:               }
 572:             else if (value instanceof Boolean)
 573:               {
 574:                 encoder.append(((Boolean) value).booleanValue());
 575:               }
 576:             else if (value instanceof byte[])
 577:               {
 578:                 encoder.append((byte[]) value);
 579:               }
 580:             // TODO float
 581:             else
 582:               {
 583:                 throw new ClassCastException(value.getClass().getName());
 584:               }
 585:           }
 586:       }
 587:   }
 588: 
 589:   /**
 590:    * Encode a control.
 591:    */
 592:   byte[] controlSequence(final Control control, boolean utf8)
 593:     throws IOException
 594:   {
 595:     BEREncoder encoder = new BEREncoder(utf8);
 596:     encoder.append(control.getID());
 597:     if (control.isCritical())
 598:       {
 599:         encoder.append(true);
 600:       }
 601:     return encoder.toByteArray();
 602:   }
 603: 
 604:   /**
 605:    * Parse a response into an LDAP result object.
 606:    */
 607:   LDAPResult parseResult(BERDecoder response)
 608:     throws IOException
 609:   {
 610:     int status = response.parseInt();
 611:     String matchingDN = response.parseString();
 612:     String errorMessage = response.parseString();
 613:     String[] referrals = null;
 614:     if (response.available())
 615:       {
 616:         int type = response.parseType();
 617:         if (type == BERConstants.SEQUENCE)
 618:           {
 619:             ArrayList list = new ArrayList();
 620:             BERDecoder sequence = response.parseSequence();
 621:             type = sequence.parseType();
 622:             while (type != -1)
 623:               {
 624:                 list.add(sequence.parseString());
 625:               }
 626:             referrals = new String[list.size()];
 627:             list.toArray(referrals);
 628:           }
 629:       }
 630:     return new LDAPResult(status, matchingDN, errorMessage, referrals);
 631:   }
 632: 
 633:   /**
 634:    * Write a request.
 635:    * @param id the message ID
 636:    * @param code the operation code
 637:    * @param request the request body
 638:    */
 639:   void write(int id, int code, byte[] request)
 640:     throws IOException
 641:   {
 642:     boolean utf8 = (version == 3);
 643:     BEREncoder envelope = new BEREncoder(utf8);
 644:     envelope.append(id);
 645:     envelope.append(request, code);
 646:     BEREncoder message = new BEREncoder(utf8);
 647:     message.append(envelope.toByteArray(), MESSAGE);
 648:     byte[] toSend = message.toByteArray();
 649:     // Write to socket
 650:     out.write(toSend);
 651:     out.flush();
 652:   }
 653: 
 654:   /**
 655:    * Read a response associated with the given message ID.
 656:    * @param id the message ID
 657:    * @return a BERDecoder for the content of the message
 658:    */
 659:   BERDecoder read(int id)
 660:     throws IOException
 661:   {
 662:     // Check for an already received async response
 663:     Integer key = new Integer(id);
 664:     List responses = (List) asyncResponses.get(key);
 665:     if (responses != null)
 666:       {
 667:         BERDecoder response = (BERDecoder) responses.remove(0);
 668:         if (responses.size() == 0)
 669:           {
 670:             asyncResponses.remove(key);
 671:           }
 672:         return response;
 673:       }
 674:     do
 675:       {
 676:         // Read LDAP message
 677:         byte[] bytes = readMessage();
 678:         boolean utf8 = (version == 3);
 679:         BERDecoder message = new BERDecoder(bytes, utf8);
 680:         message = message.parseSequence(MESSAGE);
 681:         // Check message ID
 682:         int msgId = message.parseInt();
 683:         if (msgId == id)
 684:           {
 685:             return message;
 686:           }
 687:         else
 688:           {
 689:             // Store this message for later processing
 690:             key = new Integer(msgId);
 691:             responses = (List) asyncResponses.get(key);
 692:             if (responses == null)
 693:               {
 694:                 responses = new ArrayList();
 695:                 asyncResponses.put(key, responses);
 696:               }
 697:             responses.add(message);
 698:           }
 699:       }
 700:     while (true);
 701:   }
 702: 
 703:   /**
 704:    * Read an LDAP message.
 705:    */
 706:   byte[] readMessage()
 707:     throws IOException
 708:   {
 709:     // Peek at the length part of the BER encoding to determine the length
 710:     // of the message
 711:     // TODO normalize this with functionality in BERDecoder
 712:     byte[] header = new byte[6];
 713:     int offset = 0;
 714:     header[offset++] = (byte) readByte(); // type
 715:     int len = readByte(); // length 0
 716:     header[offset++] = (byte) len;
 717:     if ((len & 0x80) != 0)
 718:       {
 719:         int lsize = len - 0x80;
 720:         if (lsize > 4)
 721:           {
 722:             throw new BERException("Data too long: " + lsize);
 723:           }
 724:         len = 0;
 725:         for (int i = 0; i < lsize; i++)
 726:           {
 727:             int c = readByte();
 728:             header[offset++] = (byte) c;
 729:             len = (len << 8) + c;
 730:           }
 731:       }
 732:     // Allocate message array
 733:     byte[] message = new byte[offset + len];
 734:     System.arraycopy(header, 0, message, 0, offset);
 735:     if (len == 0)
 736:       {
 737:         return message;
 738:       }
 739:     header = null;
 740:     // Read message content
 741:     do
 742:       {
 743:         int l = in.read(message, offset, len);
 744:         if (l == -1)
 745:           {
 746:             throw new IOException("EOF");
 747:           }
 748:         offset += l;
 749:         len -= l;
 750:       }
 751:     while (len > 0);
 752:     return message;
 753:   }
 754: 
 755:   /**
 756:    * Read a single byte.
 757:    */
 758:   int readByte()
 759:     throws IOException
 760:   {
 761:     int ret = in.read();
 762:     if (ret == -1)
 763:       {
 764:         throw new IOException("EOF");
 765:       }
 766:     return ret & 0xff;
 767:   }
 768:   
 769: }