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 }