001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 028 package org.opends.admin.ads.util; 029 030 import java.io.IOException; 031 import java.net.ConnectException; 032 import java.net.URI; 033 import java.security.GeneralSecurityException; 034 import java.util.HashSet; 035 import java.util.Hashtable; 036 import java.util.Set; 037 import java.util.logging.Level; 038 import java.util.logging.Logger; 039 040 import javax.naming.CommunicationException; 041 import javax.naming.Context; 042 import javax.naming.NamingException; 043 import javax.naming.directory.Attribute; 044 import javax.naming.directory.Attributes; 045 import javax.naming.directory.SearchControls; 046 import javax.naming.directory.SearchResult; 047 import javax.naming.ldap.InitialLdapContext; 048 import javax.naming.ldap.StartTlsRequest; 049 import javax.naming.ldap.StartTlsResponse; 050 import javax.net.ssl.HostnameVerifier; 051 import javax.net.ssl.KeyManager; 052 import javax.net.ssl.SSLHandshakeException; 053 import javax.net.ssl.TrustManager; 054 055 /** 056 * Class providing some utilities to create LDAP connections using JNDI and 057 * to manage entries retrieved using JNDI. 058 * 059 */ 060 public class ConnectionUtils 061 { 062 private static final int DEFAULT_LDAP_CONNECT_TIMEOUT = 30000; 063 064 private static final String STARTTLS_PROPERTY = 065 "org.opends.connectionutils.isstarttls"; 066 067 static private final Logger LOG = 068 Logger.getLogger(ConnectionUtils.class.getName()); 069 070 /** 071 * Private constructor: this class cannot be instantiated. 072 */ 073 private ConnectionUtils() 074 { 075 } 076 077 /** 078 * Creates a clear LDAP connection and returns the corresponding LdapContext. 079 * This methods uses the specified parameters to create a JNDI environment 080 * hashtable and creates an InitialLdapContext instance. 081 * 082 * @param ldapURL 083 * the target LDAP URL 084 * @param dn 085 * passed as Context.SECURITY_PRINCIPAL if not null 086 * @param pwd 087 * passed as Context.SECURITY_CREDENTIALS if not null 088 * @param timeout 089 * passed as com.sun.jndi.ldap.connect.timeout if > 0 090 * @param env 091 * null or additional environment properties 092 * 093 * @throws NamingException 094 * the exception thrown when instantiating InitialLdapContext 095 * 096 * @return the created InitialLdapContext. 097 * @see javax.naming.Context 098 * @see javax.naming.ldap.InitialLdapContext 099 */ 100 public static InitialLdapContext createLdapContext(String ldapURL, String dn, 101 String pwd, int timeout, Hashtable<String, String> env) 102 throws NamingException 103 { 104 if (env != null) 105 { // We clone 'env' so that we can modify it freely 106 env = new Hashtable<String, String>(env); 107 } else 108 { 109 env = new Hashtable<String, String>(); 110 } 111 env.put(Context.INITIAL_CONTEXT_FACTORY, 112 "com.sun.jndi.ldap.LdapCtxFactory"); 113 env.put(Context.PROVIDER_URL, ldapURL); 114 if (timeout >= 1) 115 { 116 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(timeout)); 117 } 118 if (dn != null) 119 { 120 env.put(Context.SECURITY_PRINCIPAL, dn); 121 } 122 if (pwd != null) 123 { 124 env.put(Context.SECURITY_CREDENTIALS, pwd); 125 } 126 127 /* Contains the DirContext and the Exception if any */ 128 final Object[] pair = new Object[] 129 { null, null }; 130 final Hashtable fEnv = env; 131 Thread t = new Thread(new Runnable() 132 { 133 public void run() 134 { 135 try 136 { 137 pair[0] = new InitialLdapContext(fEnv, null); 138 139 } catch (NamingException ne) 140 { 141 pair[1] = ne; 142 143 } catch (Throwable t) 144 { 145 t.printStackTrace(); 146 pair[1] = t; 147 } 148 } 149 }); 150 return getInitialLdapContext(t, pair, timeout); 151 } 152 153 /** 154 * Creates an LDAPS connection and returns the corresponding LdapContext. 155 * This method uses the TrusteSocketFactory class so that the specified 156 * trust manager gets called during the SSL handshake. If trust manager is 157 * null, certificates are not verified during SSL handshake. 158 * 159 * @param ldapsURL the target *LDAPS* URL. 160 * @param dn passed as Context.SECURITY_PRINCIPAL if not null. 161 * @param pwd passed as Context.SECURITY_CREDENTIALS if not null. 162 * @param timeout passed as com.sun.jndi.ldap.connect.timeout if > 0. 163 * @param env null or additional environment properties. 164 * @param trustManager null or the trust manager to be invoked during SSL 165 * negociation. 166 * @param keyManager null or the key manager to be invoked during SSL 167 * negociation. 168 * @return the established connection with the given parameters. 169 * 170 * @throws NamingException the exception thrown when instantiating 171 * InitialLdapContext. 172 * 173 * @see javax.naming.Context 174 * @see javax.naming.ldap.InitialLdapContext 175 * @see TrustedSocketFactory 176 */ 177 public static InitialLdapContext createLdapsContext(String ldapsURL, 178 String dn, String pwd, int timeout, Hashtable<String, String> env, 179 TrustManager trustManager, KeyManager keyManager) throws NamingException { 180 if (env != null) 181 { // We clone 'env' so that we can modify it freely 182 env = new Hashtable<String, String>(env); 183 } else 184 { 185 env = new Hashtable<String, String>(); 186 } 187 env.put(Context.INITIAL_CONTEXT_FACTORY, 188 "com.sun.jndi.ldap.LdapCtxFactory"); 189 env.put(Context.PROVIDER_URL, ldapsURL); 190 env.put("java.naming.ldap.factory.socket", 191 org.opends.admin.ads.util.TrustedSocketFactory.class.getName()); 192 193 if (dn != null) 194 { 195 env.put(Context.SECURITY_PRINCIPAL, dn); 196 } 197 198 if (pwd != null) 199 { 200 env.put(Context.SECURITY_CREDENTIALS, pwd); 201 } 202 203 if (trustManager == null) 204 { 205 trustManager = new BlindTrustManager(); 206 } 207 208 /* Contains the DirContext and the Exception if any */ 209 final Object[] pair = new Object[] {null, null}; 210 final Hashtable fEnv = env; 211 final TrustManager fTrustManager = trustManager; 212 final KeyManager fKeyManager = keyManager; 213 214 Thread t = new Thread(new Runnable() { 215 public void run() { 216 try { 217 TrustedSocketFactory.setCurrentThreadTrustManager(fTrustManager, 218 fKeyManager); 219 pair[0] = new InitialLdapContext(fEnv, null); 220 221 } catch (NamingException ne) { 222 pair[1] = ne; 223 224 } catch (RuntimeException re) { 225 pair[1] = re; 226 } 227 } 228 }); 229 return getInitialLdapContext(t, pair, timeout); 230 } 231 232 /** 233 * Creates an LDAP+StartTLS connection and returns the corresponding 234 * LdapContext. 235 * This method first creates an LdapContext with anonymous bind. Then it 236 * requests a StartTlsRequest extended operation. The StartTlsResponse is 237 * setup with the specified hostname verifier. Negotiation is done using a 238 * TrustSocketFactory so that the specified TrustManager gets called during 239 * the SSL handshake. 240 * If trust manager is null, certificates are not checked during SSL 241 * handshake. 242 * 243 * @param ldapURL the target *LDAP* URL. 244 * @param dn passed as Context.SECURITY_PRINCIPAL if not null. 245 * @param pwd passed as Context.SECURITY_CREDENTIALS if not null. 246 * @param timeout passed as com.sun.jndi.ldap.connect.timeout if > 0. 247 * @param env null or additional environment properties. 248 * @param trustManager null or the trust manager to be invoked during SSL 249 * negociation. 250 * @param keyManager null or the key manager to be invoked during SSL 251 * negociation. 252 * @param verifier null or the hostname verifier to be setup in the 253 * StartTlsResponse. 254 * @return the established connection with the given parameters. 255 * 256 * @throws NamingException the exception thrown when instantiating 257 * InitialLdapContext. 258 * 259 * @see javax.naming.Context 260 * @see javax.naming.ldap.InitialLdapContext 261 * @see javax.naming.ldap.StartTlsRequest 262 * @see javax.naming.ldap.StartTlsResponse 263 * @see TrustedSocketFactory 264 */ 265 266 public static InitialLdapContext createStartTLSContext(String ldapURL, 267 String dn, String pwd, int timeout, Hashtable<String, String> env, 268 TrustManager trustManager, KeyManager keyManager, 269 HostnameVerifier verifier) 270 throws NamingException 271 { 272 if (trustManager == null) 273 { 274 trustManager = new BlindTrustManager(); 275 } 276 if (verifier == null) { 277 verifier = new BlindHostnameVerifier(); 278 } 279 280 if (env != null) 281 { // We clone 'env' to modify it freely 282 env = new Hashtable<String, String>(env); 283 } 284 else 285 { 286 env = new Hashtable<String, String>(); 287 } 288 env.put(Context.INITIAL_CONTEXT_FACTORY, 289 "com.sun.jndi.ldap.LdapCtxFactory"); 290 env.put(Context.PROVIDER_URL, ldapURL); 291 env.put(Context.SECURITY_AUTHENTICATION , "none"); 292 293 /* Contains the DirContext and the Exception if any */ 294 final Object[] pair = new Object[] {null, null}; 295 final Hashtable fEnv = env; 296 final String fDn = dn; 297 final String fPwd = pwd; 298 final TrustManager fTrustManager = trustManager; 299 final KeyManager fKeyManager = keyManager; 300 final HostnameVerifier fVerifier = verifier; 301 302 Thread t = new Thread(new Runnable() { 303 public void run() { 304 try { 305 StartTlsResponse tls; 306 307 InitialLdapContext result = new InitialLdapContext(fEnv, null); 308 309 tls = (StartTlsResponse) result.extendedOperation( 310 new StartTlsRequest()); 311 tls.setHostnameVerifier(fVerifier); 312 try 313 { 314 tls.negotiate(new TrustedSocketFactory(fTrustManager,fKeyManager)); 315 } 316 catch(IOException x) { 317 NamingException xx; 318 xx = new CommunicationException( 319 "Failed to negotiate Start TLS operation"); 320 xx.initCause(x); 321 result.close(); 322 throw xx; 323 } 324 325 result.addToEnvironment(STARTTLS_PROPERTY, "true"); 326 if (fDn != null) 327 { 328 329 result.addToEnvironment(Context.SECURITY_AUTHENTICATION , "simple"); 330 result.addToEnvironment(Context.SECURITY_PRINCIPAL, fDn); 331 if (fPwd != null) 332 { 333 result.addToEnvironment(Context.SECURITY_CREDENTIALS, fPwd); 334 } 335 result.reconnect(null); 336 } 337 pair[0] = result; 338 339 } catch (NamingException ne) 340 { 341 pair[1] = ne; 342 343 } catch (RuntimeException re) 344 { 345 pair[1] = re; 346 } 347 } 348 }); 349 return getInitialLdapContext(t, pair, timeout); 350 } 351 352 /** 353 * Returns the LDAP URL used in the provided InitialLdapContext. 354 * @param ctx the context to analyze. 355 * @return the LDAP URL used in the provided InitialLdapContext. 356 */ 357 public static String getLdapUrl(InitialLdapContext ctx) 358 { 359 String s = null; 360 try 361 { 362 s = (String)ctx.getEnvironment().get(Context.PROVIDER_URL); 363 } 364 catch (NamingException ne) 365 { 366 // This is really strange. Seems like a bug somewhere. 367 LOG.log(Level.WARNING, "Naming exception getting environment of "+ctx, 368 ne); 369 } 370 return s; 371 } 372 373 /** 374 * Returns the host name used in the provided InitialLdapContext. 375 * @param ctx the context to analyze. 376 * @return the host name used in the provided InitialLdapContext. 377 */ 378 public static String getHostName(InitialLdapContext ctx) 379 { 380 String s = null; 381 try 382 { 383 URI ldapURL = new URI(getLdapUrl(ctx)); 384 s = ldapURL.getHost(); 385 } 386 catch (Throwable t) 387 { 388 // This is really strange. Seems like a bug somewhere. 389 LOG.log(Level.WARNING, "Error getting host: "+t, t); 390 } 391 return s; 392 } 393 394 /** 395 * Returns the port number used in the provided InitialLdapContext. 396 * @param ctx the context to analyze. 397 * @return the port number used in the provided InitialLdapContext. 398 */ 399 public static int getPort(InitialLdapContext ctx) 400 { 401 int port = -1; 402 try 403 { 404 URI ldapURL = new URI(getLdapUrl(ctx)); 405 port = ldapURL.getPort(); 406 } 407 catch (Throwable t) 408 { 409 // This is really strange. Seems like a bug somewhere. 410 LOG.log(Level.WARNING, "Error getting port: "+t, t); 411 } 412 return port; 413 } 414 415 /** 416 * Returns the host port representation of the server to which this 417 * context is connected. 418 * @param ctx the context to analyze. 419 * @return the host port representation of the server to which this 420 * context is connected. 421 */ 422 public static String getHostPort(InitialLdapContext ctx) 423 { 424 return getHostName(ctx)+":"+getPort(ctx); 425 } 426 427 /** 428 * Returns the bind DN used in the provided InitialLdapContext. 429 * @param ctx the context to analyze. 430 * @return the bind DN used in the provided InitialLdapContext. 431 */ 432 public static String getBindDN(InitialLdapContext ctx) 433 { 434 String bindDN = null; 435 try 436 { 437 bindDN = (String)ctx.getEnvironment().get(Context.SECURITY_PRINCIPAL); 438 } 439 catch (NamingException ne) 440 { 441 // This is really strange. Seems like a bug somewhere. 442 LOG.log(Level.WARNING, "Naming exception getting environment of "+ctx, 443 ne); 444 } 445 return bindDN; 446 } 447 448 /** 449 * Returns the password used in the provided InitialLdapContext. 450 * @param ctx the context to analyze. 451 * @return the password used in the provided InitialLdapContext. 452 */ 453 public static String getBindPassword(InitialLdapContext ctx) 454 { 455 String bindPwd = null; 456 try 457 { 458 bindPwd = (String)ctx.getEnvironment().get(Context.SECURITY_CREDENTIALS); 459 } 460 catch (NamingException ne) 461 { 462 // This is really strange. Seems like a bug somewhere. 463 LOG.log(Level.WARNING, "Naming exception getting environment of "+ctx, 464 ne); 465 } 466 return bindPwd; 467 } 468 469 /** 470 * Tells whether we are using SSL in the provided InitialLdapContext. 471 * @param ctx the context to analyze. 472 * @return <CODE>true</CODE> if we are using SSL and <CODE>false</CODE> 473 * otherwise. 474 */ 475 public static boolean isSSL(InitialLdapContext ctx) 476 { 477 boolean isSSL = false; 478 String s = null; 479 try 480 { 481 s = getLdapUrl(ctx); 482 isSSL = s.toLowerCase().startsWith("ldaps"); 483 } 484 catch (Throwable t) 485 { 486 // This is really strange. Seems like a bug somewhere. 487 LOG.log(Level.WARNING, "Error getting if is SSL "+t, t); 488 } 489 return isSSL; 490 } 491 492 /** 493 * Tells whether we are using StartTLS in the provided InitialLdapContext. 494 * @param ctx the context to analyze. 495 * @return <CODE>true</CODE> if we are using StartTLS and <CODE>false</CODE> 496 * otherwise. 497 */ 498 public static boolean isStartTLS(InitialLdapContext ctx) 499 { 500 boolean isStartTLS = false; 501 try 502 { 503 isStartTLS = "true".equalsIgnoreCase((String)ctx.getEnvironment().get( 504 STARTTLS_PROPERTY)); 505 } 506 catch (NamingException ne) 507 { 508 // This is really strange. Seems like a bug somewhere. 509 LOG.log(Level.WARNING, "Naming exception getting environment of "+ctx, 510 ne); 511 } 512 return isStartTLS; 513 } 514 515 /** 516 * Method used to know if we can connect as administrator in a server with a 517 * given password and dn. 518 * @param ldapUrl the ldap URL of the server. 519 * @param dn the dn to be used. 520 * @param pwd the password to be used. 521 * @return <CODE>true</CODE> if we can connect and read the configuration and 522 * <CODE>false</CODE> otherwise. 523 */ 524 public static boolean canConnectAsAdministrativeUser(String ldapUrl, 525 String dn, String pwd) 526 { 527 boolean canConnectAsAdministrativeUser = false; 528 try 529 { 530 InitialLdapContext ctx; 531 if (ldapUrl.toLowerCase().startsWith("ldap:")) 532 { 533 ctx = createLdapContext(ldapUrl, dn, pwd, getDefaultLDAPTimeout(), 534 null); 535 } 536 else 537 { 538 ctx = createLdapsContext(ldapUrl, dn, pwd, getDefaultLDAPTimeout(), 539 null, null, null); 540 } 541 542 canConnectAsAdministrativeUser = connectedAsAdministrativeUser(ctx); 543 } catch (NamingException ne) 544 { 545 // Nothing to do. 546 } catch (Throwable t) 547 { 548 throw new IllegalStateException("Unexpected throwable.", t); 549 } 550 return canConnectAsAdministrativeUser; 551 } 552 553 /** 554 * Method used to know if we are connected as administrator in a server with a 555 * given InitialLdapContext. 556 * @param ctx the context. 557 * @return <CODE>true</CODE> if we are connected and read the configuration 558 * and <CODE>false</CODE> otherwise. 559 */ 560 public static boolean connectedAsAdministrativeUser(InitialLdapContext ctx) 561 { 562 boolean connectedAsAdministrativeUser = false; 563 try 564 { 565 /* 566 * Search for the config to check that it is the directory manager. 567 */ 568 SearchControls searchControls = new SearchControls(); 569 searchControls.setCountLimit(1); 570 searchControls.setSearchScope( 571 SearchControls. OBJECT_SCOPE); 572 searchControls.setReturningAttributes( 573 new String[] {"dn"}); 574 ctx.search("cn=config", "objectclass=*", searchControls); 575 576 connectedAsAdministrativeUser = true; 577 } catch (NamingException ne) 578 { 579 // Nothing to do. 580 } catch (Throwable t) 581 { 582 throw new IllegalStateException("Unexpected throwable.", t); 583 } 584 return connectedAsAdministrativeUser; 585 } 586 587 /** 588 * This is just a commodity method used to try to get an InitialLdapContext. 589 * @param t the Thread to be used to create the InitialLdapContext. 590 * @param pair an Object[] array that contains the InitialLdapContext and the 591 * Throwable if any occurred. 592 * @param timeout the timeout. If we do not get to create the connection 593 * before the timeout a CommunicationException will be thrown. 594 * @return the created InitialLdapContext 595 * @throws NamingException if something goes wrong during the creation. 596 */ 597 private static InitialLdapContext getInitialLdapContext(Thread t, 598 Object[] pair, int timeout) throws NamingException 599 { 600 try 601 { 602 if (timeout > 0) 603 { 604 t.start(); 605 t.join(timeout); 606 } else 607 { 608 t.run(); 609 } 610 611 } catch (InterruptedException x) 612 { 613 // This might happen for problems in sockets 614 // so it does not necessarily imply a bug 615 } 616 617 boolean throwException = false; 618 619 if ((timeout > 0) && t.isAlive()) 620 { 621 t.interrupt(); 622 try 623 { 624 t.join(2000); 625 } catch (InterruptedException x) 626 { 627 // This might happen for problems in sockets 628 // so it does not necessarily imply a bug 629 } 630 throwException = true; 631 } 632 633 if ((pair[0] == null) && (pair[1] == null)) 634 { 635 throwException = true; 636 } 637 638 if (throwException) 639 { 640 NamingException xx; 641 ConnectException x = new ConnectException("Connection timed out"); 642 xx = new CommunicationException("Connection timed out"); 643 xx.initCause(x); 644 throw xx; 645 } 646 647 if (pair[1] != null) 648 { 649 if (pair[1] instanceof NamingException) 650 { 651 throw (NamingException) pair[1]; 652 653 } else if (pair[1] instanceof RuntimeException) 654 { 655 throw (RuntimeException) pair[1]; 656 657 } else if (pair[1] instanceof Throwable) 658 { 659 throw new IllegalStateException("Unexpected throwable occurred", 660 (Throwable) pair[1]); 661 } 662 } 663 return (InitialLdapContext) pair[0]; 664 } 665 666 /** 667 * Returns the default LDAP timeout in milliseconds when we try to connect to 668 * a server. 669 * @return the default LDAP timeout in milliseconds when we try to connect to 670 * a server. 671 */ 672 public static int getDefaultLDAPTimeout() 673 { 674 return DEFAULT_LDAP_CONNECT_TIMEOUT; 675 } 676 677 /** 678 * Returns the String that can be used to represent a given host name in a 679 * LDAP URL. 680 * This method must be used when we have IPv6 addresses (the address in the 681 * LDAP URL must be enclosed with brackets). 682 * @param host the host name. 683 * @return the String that can be used to represent a given host name in a 684 * LDAP URL. 685 */ 686 public static String getHostNameForLdapUrl(String host) 687 { 688 if ((host != null) && host.indexOf(":") != -1) 689 { 690 // Assume an IPv6 address has been specified and adds the brackets 691 // for the URL. 692 host = host.trim(); 693 if (!host.startsWith("[")) 694 { 695 host = "["+host; 696 } 697 if (!host.endsWith("]")) 698 { 699 host = host + "]"; 700 } 701 } 702 return host; 703 } 704 705 /** 706 * Returns the LDAP URL for the provided parameters. 707 * @param host the host name. 708 * @param port the LDAP port. 709 * @param useSSL whether to use SSL or not. 710 * @return the LDAP URL for the provided parameters. 711 */ 712 public static String getLDAPUrl(String host, int port, boolean useSSL) 713 { 714 String ldapUrl; 715 host = getHostNameForLdapUrl(host); 716 if (useSSL) 717 { 718 ldapUrl = "ldaps://"+host+":"+port; 719 } 720 else 721 { 722 ldapUrl = "ldap://"+host+":"+port; 723 } 724 return ldapUrl; 725 } 726 727 /** 728 * Tells whether the provided Throwable was caused because of a problem with 729 * a certificate while trying to establish a connection. 730 * @param t the Throwable to analyze. 731 * @return <CODE>true</CODE> if the provided Throwable was caused because of a 732 * problem with a certificate while trying to establish a connection and 733 * <CODE>false</CODE> otherwise. 734 */ 735 public static boolean isCertificateException(Throwable t) 736 { 737 boolean returnValue = false; 738 739 while (!returnValue && (t != null)) 740 { 741 returnValue = (t instanceof SSLHandshakeException) || 742 (t instanceof GeneralSecurityException); 743 t = t.getCause(); 744 } 745 746 return returnValue; 747 } 748 749 /** 750 * Returns the String representation of the first value of an attribute in a 751 * LDAP entry. 752 * @param entry the entry. 753 * @param attrName the attribute name. 754 * @return the String representation of the first value of an attribute in a 755 * LDAP entry. 756 * @throws NamingException if there is an error processing the entry. 757 */ 758 static public String getFirstValue(SearchResult entry, String attrName) 759 throws NamingException 760 { 761 String v = null; 762 Attributes attrs = entry.getAttributes(); 763 if (attrs != null) 764 { 765 Attribute attr = attrs.get(attrName); 766 if ((attr != null) && (attr.size() > 0)) 767 { 768 Object o = attr.get(); 769 if (o instanceof String) 770 { 771 v = (String)o; 772 } 773 else 774 { 775 v = String.valueOf(o); 776 } 777 } 778 } 779 return v; 780 } 781 782 /** 783 * Returns a Set with the String representation of the values of an attribute 784 * in a LDAP entry. The returned Set will never be null. 785 * @param entry the entry. 786 * @param attrName the attribute name. 787 * @return a Set with the String representation of the values of an attribute 788 * in a LDAP entry. 789 * @throws NamingException if there is an error processing the entry. 790 */ 791 static public Set<String> getValues(SearchResult entry, String attrName) 792 throws NamingException 793 { 794 Set<String> values = new HashSet<String>(); 795 Attributes attrs = entry.getAttributes(); 796 if (attrs != null) 797 { 798 Attribute attr = attrs.get(attrName); 799 if (attr != null) 800 { 801 for (int i=0; i<attr.size(); i++) 802 { 803 values.add((String)attr.get(i)); 804 } 805 } 806 } 807 return values; 808 } 809 }