View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.ldap;
21  
22  
23  import java.io.IOException;
24  import java.net.InetSocketAddress;
25  import java.security.KeyStore;
26  import java.security.Provider;
27  import java.security.Security;
28  import java.util.*;
29  
30  
31  import org.apache.directory.server.core.DirectoryService;
32  import org.apache.directory.server.core.partition.PartitionNexus;
33  import org.apache.directory.server.core.security.CoreKeyStoreSpi;
34  import org.apache.directory.server.ldap.handlers.LdapRequestHandler;
35  import org.apache.directory.server.ldap.handlers.AbandonHandler;
36  import org.apache.directory.server.ldap.handlers.AddHandler;
37  import org.apache.directory.server.ldap.handlers.BindHandler;
38  import org.apache.directory.server.ldap.handlers.CompareHandler;
39  import org.apache.directory.server.ldap.handlers.DeleteHandler;
40  import org.apache.directory.server.ldap.handlers.ExtendedHandler;
41  import org.apache.directory.server.ldap.handlers.ModifyDnHandler;
42  import org.apache.directory.server.ldap.handlers.ModifyHandler;
43  import org.apache.directory.server.ldap.handlers.SearchHandler;
44  import org.apache.directory.server.ldap.handlers.UnbindHandler;
45  import org.apache.directory.server.ldap.handlers.bind.*;
46  import org.apache.directory.server.ldap.handlers.ssl.LdapsInitializer;
47  import org.apache.directory.server.protocol.shared.DirectoryBackedService;
48  import org.apache.directory.shared.ldap.constants.SaslQoP;
49  import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
50  import org.apache.directory.shared.ldap.message.AbandonRequest;
51  import org.apache.directory.shared.ldap.message.AddRequest;
52  import org.apache.directory.shared.ldap.message.BindRequest;
53  import org.apache.directory.shared.ldap.message.CascadeControl;
54  import org.apache.directory.shared.ldap.message.CompareRequest;
55  import org.apache.directory.shared.ldap.message.DeleteRequest;
56  import org.apache.directory.shared.ldap.message.EntryChangeControl;
57  import org.apache.directory.shared.ldap.message.ExtendedRequest;
58  import org.apache.directory.shared.ldap.message.ManageDsaITControl;
59  import org.apache.directory.shared.ldap.message.ModifyDnRequest;
60  import org.apache.directory.shared.ldap.message.ModifyRequest;
61  import org.apache.directory.shared.ldap.message.PersistentSearchControl;
62  import org.apache.directory.shared.ldap.message.SearchRequest;
63  import org.apache.directory.shared.ldap.message.SubentriesControl;
64  import org.apache.directory.shared.ldap.message.UnbindRequest;
65  import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;
66  import org.apache.mina.common.DefaultIoFilterChainBuilder;
67  import org.apache.mina.common.IoFilterChainBuilder;
68  import org.apache.mina.common.IoHandler;
69  import org.apache.mina.common.IoSession;
70  import org.apache.mina.common.ThreadModel;
71  import org.apache.mina.common.WriteFuture;
72  import org.apache.mina.filter.codec.ProtocolCodecFactory;
73  import org.apache.mina.handler.demux.MessageHandler;
74  import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
75  import org.slf4j.Logger;
76  import org.slf4j.LoggerFactory;
77  
78  
79  /**
80   * An LDAP protocol provider implementation which dynamically associates
81   * handlers.
82   *
83   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
84   * @version $Rev: 688548 $
85   * @org.apache.xbean.XBean
86   */
87  public class LdapService extends DirectoryBackedService
88  {
89      /** Value (0) for configuration where size limit is unlimited. */
90      public static final int NO_SIZE_LIMIT = 0;
91  
92      /** Value (0) for configuration where time limit is unlimited. */
93      public static final int NO_TIME_LIMIT = 0;
94  
95      /** the constant service name of this ldap protocol provider **/
96      public static final String SERVICE_NAME = "ldap";
97  
98      
99      
100     private static final long serialVersionUID = 3757127143811666817L;
101 
102     /** logger for this class */
103     private static final Logger LOG = LoggerFactory.getLogger( LdapService.class.getName() );
104 
105     /** The default maximum size limit. */
106     private static final int MAX_SIZE_LIMIT_DEFAULT = 100;
107 
108     /** The default maximum time limit. */
109     private static final int MAX_TIME_LIMIT_DEFAULT = 10000;
110 
111     /** The default service pid. */
112     private static final String SERVICE_PID_DEFAULT = "org.apache.directory.server.ldap";
113 
114     /** The default service name. */
115     private static final String SERVICE_NAME_DEFAULT = "ApacheDS LDAP Service";
116 
117     /** The default IP port. */
118     private static final int IP_PORT_DEFAULT = 389;
119 
120     /** the session manager for this LdapService */
121     private LdapSessionManager ldapSessionManager = new LdapSessionManager();
122     
123     /** a set of supported controls */
124     private Set<String> supportedControls;
125 
126     /** 
127      * The maximum size limit. 
128      * @see {@link LdapService#MAX_SIZE_LIMIT_DEFAULT }
129      */
130     private int maxSizeLimit = MAX_SIZE_LIMIT_DEFAULT; 
131 
132     /** 
133      * The maximum time limit.
134      * @see {@link LdapService#MAX_TIME_LIMIT_DEFAULT }
135      */
136     private int maxTimeLimit = MAX_TIME_LIMIT_DEFAULT; 
137 
138     /** Whether LDAPS is enabled: disabled by default. */
139     private boolean enableLdaps;
140 
141     /** Whether to allow anonymous access: enabled by default. */
142     private boolean allowAnonymousAccess = true;
143 
144     /** The extended operation handlers. */
145     private final Collection<ExtendedOperationHandler> extendedOperationHandlers =
146         new ArrayList<ExtendedOperationHandler>();
147 
148     /** The supported authentication mechanisms. */
149     private Map<String, MechanismHandler> saslMechanismHandlers =
150         new HashMap<String, MechanismHandler>();
151 
152     /** The name of this host, validated during SASL negotiation. */
153     private String saslHost = "ldap.example.com";
154 
155     /** The service principal, used by GSSAPI. */
156     private String saslPrincipal = "ldap/ldap.example.com@EXAMPLE.COM";
157 
158     /** The quality of protection (QoP), used by DIGEST-MD5 and GSSAPI. */
159     private Set<String> saslQop;
160     private String      saslQopString;
161 
162     /** The list of realms serviced by this host. */
163     private List<String> saslRealms;
164 
165     private LdapRequestHandler<AbandonRequest> abandonHandler;
166     private LdapRequestHandler<AddRequest> addHandler;
167     private LdapRequestHandler<BindRequest> bindHandler;
168     private LdapRequestHandler<CompareRequest> compareHandler;
169     private LdapRequestHandler<DeleteRequest> deleteHandler;
170     private LdapRequestHandler<ExtendedRequest> extendedHandler;
171     private LdapRequestHandler<ModifyRequest> modifyHandler;
172     private LdapRequestHandler<ModifyDnRequest> modifyDnHandler;
173     private LdapRequestHandler<SearchRequest> searchHandler;
174     private LdapRequestHandler<UnbindRequest> unbindHandler;
175 
176 
177     /** the underlying provider codec factory */
178     private ProtocolCodecFactory codecFactory;
179 
180     /** the MINA protocol handler */
181     private final LdapProtocolHandler handler = new LdapProtocolHandler(this);
182 
183     /** tracks start state of the server */
184     private boolean started;
185 
186     /** 
187      * Whether or not confidentiality (TLS secured connection) is required: 
188      * disabled by default. 
189      */
190     private boolean confidentialityRequired;
191 
192 
193     /**
194      * Creates an LDAP protocol provider.
195      */
196     public LdapService()
197     {
198         super.setIpPort( IP_PORT_DEFAULT );
199         super.setEnabled( true );
200         super.setServiceId( SERVICE_PID_DEFAULT );
201         super.setServiceName( SERVICE_NAME_DEFAULT );
202 
203         saslQop = new HashSet<String>();
204         saslQop.add( SaslQoP.QOP_AUTH );
205         saslQop.add( SaslQoP.QOP_AUTH_INT );
206         saslQop.add( SaslQoP.QOP_AUTH_CONF );
207         saslQopString = SaslQoP.QOP_AUTH + ',' + SaslQoP.QOP_AUTH_INT + ',' + SaslQoP.QOP_AUTH_CONF;
208 
209         saslRealms = new ArrayList<String>();
210         saslRealms.add( "example.com" );
211 
212         this.supportedControls = new HashSet<String>();
213         this.supportedControls.add( PersistentSearchControl.CONTROL_OID );
214         this.supportedControls.add( EntryChangeControl.CONTROL_OID );
215         this.supportedControls.add( SubentriesControl.CONTROL_OID );
216         this.supportedControls.add( ManageDsaITControl.CONTROL_OID );
217         this.supportedControls.add( CascadeControl.CONTROL_OID );
218     }
219 
220 
221     /**
222      * Install the LDAP request handlers.
223      */
224     private void installDefaultHandlers()
225     {
226         if ( getAbandonHandler() == null )
227         {
228             setAbandonHandler( new AbandonHandler() );
229         }
230         
231         if ( getAddHandler() == null )
232         {
233             setAddHandler( new AddHandler() );
234         }
235         
236         if ( getBindHandler() == null )
237         {
238             BindHandler handler = new BindHandler();
239             handler.setSaslMechanismHandlers( saslMechanismHandlers );
240             setBindHandler( handler );
241         }
242         
243         if ( getCompareHandler() == null )
244         {
245             setCompareHandler( new CompareHandler() );
246         }
247         
248         if ( getDeleteHandler() == null )
249         {
250             setDeleteHandler( new DeleteHandler() );
251         }
252         
253         if ( getExtendedHandler() == null )
254         {
255             setExtendedHandler( new ExtendedHandler() );
256         }
257         
258         if ( getModifyHandler() == null )
259         {
260             setModifyHandler( new ModifyHandler() );
261         }
262         
263         if ( getModifyDnHandler() == null )
264         {
265             setModifyDnHandler( new ModifyDnHandler() );
266         }
267         
268         if ( getSearchHandler() == null )
269         {
270             setSearchHandler( new SearchHandler() );
271         }
272         
273         if ( getUnbindHandler() == null )
274         {
275             setUnbindHandler( new UnbindHandler() );
276         }
277     }
278 
279 
280     /**
281      * @throws IOException if we cannot bind to the specified port
282      * @throws NamingException if the LDAP server cannot be started
283      */
284     public void start() throws Exception
285     {
286         if ( ! isEnabled() )
287         {
288             return;
289         }
290 
291         IoFilterChainBuilder chain;
292         
293         if ( isEnableLdaps() )
294         {
295             Provider provider = Security.getProvider( "SUN" );
296             LOG.debug( "provider = {}", provider );
297             CoreKeyStoreSpi coreKeyStoreSpi = new CoreKeyStoreSpi( getDirectoryService() );
298             KeyStore keyStore = new KeyStore( coreKeyStoreSpi, provider, "JKS" ) {};
299             try
300             {
301                 keyStore.load( null, null );
302             }
303             catch ( Exception e )
304             {
305                 // nothing really happens with this keystore
306             }
307             chain = LdapsInitializer.init( keyStore );
308         }
309         else
310         {
311             chain = new DefaultIoFilterChainBuilder();
312         }
313 
314         /*
315          * The serveur is now initialized, we can
316          * install the default requests handlers, which need 
317          * access to the DirectoryServer instance.
318          */ 
319         installDefaultHandlers();      
320 
321         startLDAP0( getIpPort(), chain );
322         
323         started = true;
324     }
325 
326 
327     public void stop()
328     {
329         try
330         {
331             // we should unbind the service before we begin sending the notice
332             // of disconnect so new connections are not formed while we process
333             List<WriteFuture> writeFutures = new ArrayList<WriteFuture>();
334 
335             // If the socket has already been unbound as with a successful
336             // GracefulShutdownRequest then this will complain that the service
337             // is not bound - this is ok because the GracefulShutdown has already
338             // sent notices to to the existing active sessions
339             List<IoSession> sessions;
340 
341             try
342             {
343                 sessions = new ArrayList<IoSession>(
344                         getSocketAcceptor().getManagedSessions( new InetSocketAddress( getIpPort() ) ) );
345             }
346             catch ( IllegalArgumentException e )
347             {
348                 LOG.warn( "Seems like the LDAP service (" + getIpPort() + ") has already been unbound." );
349                 return;
350             }
351 
352             getSocketAcceptor().unbind( new InetSocketAddress( getIpPort() ) );
353 
354             if ( LOG.isInfoEnabled() )
355             {
356                 LOG.info( "Unbind of an LDAP service (" + getIpPort() + ") is complete." );
357                 LOG.info( "Sending notice of disconnect to existing clients sessions." );
358             }
359 
360             // Send Notification of Disconnection messages to all connected clients.
361             if ( sessions != null )
362             {
363                 for ( IoSession session:sessions )
364                 {
365                     writeFutures.add( session.write( NoticeOfDisconnect.UNAVAILABLE ) );
366                 }
367             }
368 
369             // And close the connections when the NoDs are sent.
370             Iterator<IoSession> sessionIt = sessions.iterator();
371 
372             for ( WriteFuture future:writeFutures )
373             {
374                 future.join( 1000 );
375                 sessionIt.next().close();
376             }
377         }
378         catch ( Exception e )
379         {
380             LOG.warn( "Failed to sent NoD.", e );
381         }
382     }
383 
384 
385     private void startLDAP0( int port, IoFilterChainBuilder chainBuilder )
386         throws Exception
387     {
388         PartitionNexus nexus = getDirectoryService().getPartitionNexus();
389 
390         for ( ExtendedOperationHandler h : extendedOperationHandlers )
391         {
392             LOG.info( "Added Extended Request Handler: " + h.getOid() );
393             h.setLdapServer( this );
394             nexus.registerSupportedExtensions( h.getExtensionOids() );
395         }
396 
397         nexus.registerSupportedSaslMechanisms( saslMechanismHandlers.keySet() );
398 
399         try
400         {
401             SocketAcceptorConfig acceptorCfg = new SocketAcceptorConfig();
402 
403             // Disable the disconnection of the clients on unbind
404             acceptorCfg.setDisconnectOnUnbind( false );
405             acceptorCfg.setReuseAddress( true );
406             acceptorCfg.setFilterChainBuilder( chainBuilder );
407             acceptorCfg.setThreadModel( ThreadModel.MANUAL );
408 
409             acceptorCfg.getSessionConfig().setTcpNoDelay( true );
410 
411             getSocketAcceptor().bind( new InetSocketAddress( port ), getHandler(), acceptorCfg );
412             started = true;
413 
414             if ( LOG.isInfoEnabled() )
415             {
416                 LOG.info( "Successful bind of an LDAP Service (" + port + ") is complete." );
417             }
418         }
419         catch ( IOException e )
420         {
421             String msg = "Failed to bind an LDAP service (" + port + ") to the service registry.";
422             LdapConfigurationException lce = new LdapConfigurationException( msg );
423             lce.setRootCause( e );
424             LOG.error( msg, e );
425             throw lce;
426         }
427     }
428 
429 
430     public String getName()
431     {
432         return SERVICE_NAME;
433     }
434 
435 
436     public IoHandler getHandler()
437     {
438         return handler;
439     }
440     
441     
442     public LdapSessionManager getLdapSessionManager()
443     {
444         return ldapSessionManager;
445     }
446     
447     
448     public ProtocolCodecFactory getProtocolCodecFactory()
449     {
450         return codecFactory;
451     }
452 
453     
454     // ------------------------------------------------------------------------
455     // Configuration Methods
456     // ------------------------------------------------------------------------
457 
458 
459     /**
460      * Registeres the specified {@link ExtendedOperationHandler} to this
461      * protocol provider to provide a specific LDAP extended operation.
462      *
463      * @param eoh an extended operation handler
464      * @throws NamingException on failure to add the handler
465      */
466     public void addExtendedOperationHandler( ExtendedOperationHandler eoh ) throws Exception
467     {
468         if ( started )
469         {
470             eoh.setLdapServer( this );
471             PartitionNexus nexus = getDirectoryService().getPartitionNexus();
472             nexus.registerSupportedExtensions( eoh.getExtensionOids() );
473         }
474         else
475         {
476             extendedOperationHandlers.add( eoh );
477         }
478     }
479 
480 
481     /**
482      * Deregisteres an {@link ExtendedOperationHandler} with the specified <tt>oid</tt>
483      * from this protocol provider.
484      *
485      * @param oid the numeric identifier for the extended operation associated with
486      * the handler to remove
487      */
488     public void removeExtendedOperationHandler( String oid )
489     {
490         // need to do something like this to make this work right
491         //            PartitionNexus nexus = getDirectoryService().getPartitionNexus();
492         //            nexus.unregisterSupportedExtensions( eoh.getExtensionOids() );
493 
494         ExtendedOperationHandler handler = null;
495         for ( ExtendedOperationHandler h : extendedOperationHandlers )
496         {
497             if ( h.getOid().equals( oid ) )
498             {
499                 handler = h;
500                 break;
501             }
502         }
503         extendedOperationHandlers.remove( handler );
504     }
505 
506 
507     /**
508      * Returns an {@link ExtendedOperationHandler} with the specified <tt>oid</tt>
509      * which is registered to this protocol provider.
510      *
511      * @param oid the oid of the extended request of associated with the extended
512      * request handler
513      * @return the exnteded operation handler
514      */
515     public ExtendedOperationHandler getExtendedOperationHandler( String oid )
516     {
517         for ( ExtendedOperationHandler h : extendedOperationHandlers )
518         {
519             if ( h.getOid().equals( oid ) )
520             {
521                 return h;
522             }
523         }
524 
525         return null;
526     }
527 
528 
529     /**
530      * Sets the mode for this LdapService to accept requests with or without a
531      * TLS secured connection via either StartTLS extended operations or using
532      * LDAPS.
533      * 
534      * @param confidentialityRequired true to require confidentiality
535      */
536     public void setConfidentialityRequired( boolean confidentialityRequired )
537     {
538         this.confidentialityRequired = confidentialityRequired;
539     }
540 
541 
542     /**
543      * Gets whether or not TLS secured connections are required to perform 
544      * operations on this LdapService.
545      * 
546      * @return true if TLS secured connections are required, false otherwise
547      */
548     public boolean isConfidentialityRequired()
549     {
550         return confidentialityRequired;
551     }
552 
553     
554     /**
555      * Returns <tt>true</tt> if LDAPS is enabled.
556      *
557      * @return True if LDAPS is enabled.
558      */
559     public boolean isEnableLdaps()
560     {
561         return enableLdaps;
562     }
563 
564 
565     /**
566      * Sets if LDAPS is enabled or not.
567      *
568      * @param enableLdaps Whether LDAPS is enabled.
569      */
570     public void setEnableLdaps( boolean enableLdaps )
571     {
572         this.enableLdaps = enableLdaps;
573     }
574 
575 
576     /**
577      * Returns <code>true</code> if anonymous access is allowed.
578      *
579      * @return True if anonymous access is allowed.
580      */
581     public boolean isAllowAnonymousAccess()
582     {
583         return allowAnonymousAccess;
584     }
585 
586 
587     /**
588      * Sets whether to allow anonymous access or not.
589      *
590      * @param enableAnonymousAccess Set <code>true</code> to allow anonymous access.
591      */
592     public void setAllowAnonymousAccess( boolean enableAnonymousAccess )
593     {
594         this.allowAnonymousAccess = enableAnonymousAccess;
595     }
596 
597 
598     /**
599      * Sets the maximum size limit in number of entries to return for search.
600      *
601      * @param maxSizeLimit the maximum number of entries to return for search
602      */
603     public void setMaxSizeLimit( int maxSizeLimit )
604     {
605         this.maxSizeLimit = maxSizeLimit;
606     }
607 
608 
609     /**
610      * Returns the maximum size limit in number of entries to return for search.
611      *
612      * @return The maximum size limit.
613      */
614     public int getMaxSizeLimit()
615     {
616         return maxSizeLimit;
617     }
618 
619 
620     /**
621      * Sets the maximum time limit in miliseconds to conduct a search.
622      *
623      * @param maxTimeLimit the maximum length of time in milliseconds for search
624      */
625     public void setMaxTimeLimit( int maxTimeLimit )
626     {
627         this.maxTimeLimit = maxTimeLimit;
628     }
629 
630 
631     /**
632      * Returns the maximum time limit in milliseonds to conduct a search.
633      *
634      * @return The maximum time limit in milliseconds for search
635      */
636     public int getMaxTimeLimit()
637     {
638         return maxTimeLimit;
639     }
640 
641 
642     /**
643      * Gets the {@link ExtendedOperationHandler}s.
644      *
645      * @return A collection of {@link ExtendedOperationHandler}s.
646      */
647     public Collection<ExtendedOperationHandler> getExtendedOperationHandlers()
648     {
649         return new ArrayList<ExtendedOperationHandler>( extendedOperationHandlers );
650     }
651 
652 
653     /**
654      * Sets the {@link ExtendedOperationHandler}s.
655      *
656      * @org.apache.xbean.Property nestedType="org.apache.directory.server.ldap.ExtendedOperationHandler"
657      *
658      * @param handlers A collection of {@link ExtendedOperationHandler}s.
659      */
660     public void setExtendedOperationHandlers( Collection<ExtendedOperationHandler> handlers )
661     {
662         this.extendedOperationHandlers.clear();
663         this.extendedOperationHandlers.addAll( handlers );
664     }
665 
666 
667     /**
668      * Returns the FQDN of this SASL host, validated during SASL negotiation.
669      *
670      * @return The FQDN of this SASL host, validated during SASL negotiation.
671      */
672     public String getSaslHost()
673     {
674         return saslHost;
675     }
676 
677 
678     /**
679      * Sets the FQDN of this SASL host, validated during SASL negotiation.
680      *
681      * @param saslHost The FQDN of this SASL host, validated during SASL negotiation.
682      */
683     public void setSaslHost( String saslHost )
684     {
685         this.saslHost = saslHost;
686     }
687 
688 
689     /**
690      * Returns the Kerberos principal name for this LDAP service, used by GSSAPI.
691      *
692      * @return The Kerberos principal name for this LDAP service, used by GSSAPI.
693      */
694     public String getSaslPrincipal()
695     {
696         return saslPrincipal;
697     }
698 
699 
700     /**
701      * Sets the Kerberos principal name for this LDAP service, used by GSSAPI.
702      *
703      * @param saslPrincipal The Kerberos principal name for this LDAP service, used by GSSAPI.
704      */
705     public void setSaslPrincipal( String saslPrincipal )
706     {
707         this.saslPrincipal = saslPrincipal;
708     }
709 
710 
711     /**
712      * Returns the quality-of-protection, used by DIGEST-MD5 and GSSAPI.
713      *
714      * @return The quality-of-protection, used by DIGEST-MD5 and GSSAPI.
715      */
716     public String getSaslQopString()
717     {
718         return saslQopString;
719     }
720 
721 
722     /**
723      * Returns the Set of quality-of-protection, used by DIGEST-MD5 and GSSAPI.
724      *
725      * @return The quality-of-protection, used by DIGEST-MD5 and GSSAPI.
726      */
727     public Set<String> getSaslQop()
728     {
729         return saslQop;
730     }
731 
732 
733     /**
734      * Sets the desired quality-of-protection, used by DIGEST-MD5 and GSSAPI.
735      * 
736      * We build a string from this list, where QoP are comma delimited 
737      *
738      * @org.apache.xbean.Property nestedType="java.lang.String"
739      *
740      * @param saslQop The desired quality-of-protection, used by DIGEST-MD5 and GSSAPI.
741      */
742     public void setSaslQop( Set<String> saslQop )
743     {
744         StringBuilder qopList = new StringBuilder();
745         boolean isFirst = true;
746 
747         for ( String qop:saslQop )
748         {
749             if ( isFirst )
750             {
751                 isFirst = false;
752             }
753             else
754             {
755                 qopList.append( ',' );
756             }
757             
758             qopList.append( qop );
759         }
760 
761         this.saslQopString = qopList.toString();
762         this.saslQop = saslQop;
763     }
764 
765 
766     /**
767      * Returns the realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
768      *
769      * @return The realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
770      */
771     public List<String> getSaslRealms()
772     {
773         return saslRealms;
774     }
775 
776 
777     /**
778      * Sets the realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
779      *
780      * @org.apache.xbean.Property nestedType="java.lang.String"
781      *
782      * @param saslRealms The realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
783      */
784     public void setSaslRealms( List<String> saslRealms )
785     {
786         this.saslRealms = saslRealms;
787     }
788 
789 
790     /**
791      * @org.apache.xbean.Map flat="true" dups="replace" keyName="mech-name"
792      */
793     public Map<String, MechanismHandler> getSaslMechanismHandlers()
794     {
795         return saslMechanismHandlers;
796     }
797 
798     public void setSaslMechanismHandlers( Map<String, MechanismHandler> saslMechanismHandlers )
799     {
800         this.saslMechanismHandlers = saslMechanismHandlers;
801     }
802 
803 
804     public MechanismHandler addSaslMechanismHandler( String mechanism, MechanismHandler handler )
805     {
806         return this.saslMechanismHandlers.put( mechanism, handler );
807     }
808 
809 
810     public MechanismHandler removeSaslMechanismHandler( String mechanism )
811     {
812         return this.saslMechanismHandlers.remove( mechanism );
813     }
814 
815 
816     public MechanismHandler getMechanismHandler( String mechanism )
817     {
818         return this.saslMechanismHandlers.get( mechanism );
819     }
820 
821 
822     public Set<String> getSupportedMechanisms()
823     {
824         return saslMechanismHandlers.keySet();
825     }
826 
827 
828     public void setDirectoryService( DirectoryService directoryService )
829     {
830         super.setDirectoryService( directoryService );
831         this.codecFactory = new LdapProtocolCodecFactory( directoryService );
832     }
833 
834 
835     public Set<String> getSupportedControls()
836     {
837         return supportedControls;
838     }
839 
840 
841     public void setSupportedControls( Set<String> supportedControls )
842     {
843         this.supportedControls = supportedControls;
844     }
845 
846 
847     public MessageHandler<AbandonRequest> getAbandonHandler()
848     {
849         return abandonHandler;
850     }
851 
852 
853     public void setAbandonHandler( LdapRequestHandler<AbandonRequest> abandonHandler )
854     {
855         this.handler.removeMessageHandler( AbandonRequest.class );
856         this.abandonHandler = abandonHandler;
857         this.abandonHandler.setLdapServer( this );
858         this.handler.addMessageHandler( AbandonRequest.class, this.abandonHandler );
859     }
860 
861 
862     public LdapRequestHandler<AddRequest> getAddHandler()
863     {
864         return addHandler;
865     }
866 
867 
868     public void setAddHandler( LdapRequestHandler<AddRequest> addHandler )
869     {
870         this.handler.removeMessageHandler( AddRequest.class );
871         this.addHandler = addHandler;
872         this.addHandler.setLdapServer( this );
873         this.handler.addMessageHandler( AddRequest.class, this.addHandler );
874     }
875 
876 
877     public LdapRequestHandler<BindRequest> getBindHandler()
878     {
879         return bindHandler;
880     }
881 
882 
883     public void setBindHandler( LdapRequestHandler<BindRequest> bindHandler )
884     {
885         this.handler.removeMessageHandler( BindRequest.class );
886         this.bindHandler = bindHandler;
887         this.bindHandler.setLdapServer( this );
888         this.handler.addMessageHandler( BindRequest.class, this.bindHandler );
889     }
890 
891 
892     public LdapRequestHandler<CompareRequest> getCompareHandler()
893     {
894         return compareHandler;
895     }
896 
897 
898     public void setCompareHandler( LdapRequestHandler<CompareRequest> compareHandler )
899     {
900         this.handler.removeMessageHandler( CompareRequest.class );
901         this.compareHandler = compareHandler;
902         this.compareHandler.setLdapServer( this );
903         this.handler.addMessageHandler( CompareRequest.class, this.compareHandler );
904     }
905 
906 
907     public LdapRequestHandler<DeleteRequest> getDeleteHandler()
908     {
909         return deleteHandler;
910     }
911 
912 
913     public void setDeleteHandler( LdapRequestHandler<DeleteRequest> deleteHandler )
914     {
915         this.handler.removeMessageHandler( DeleteRequest.class );
916         this.deleteHandler = deleteHandler;
917         this.deleteHandler.setLdapServer( this );
918         this.handler.addMessageHandler( DeleteRequest.class, this.deleteHandler );
919     }
920 
921 
922     public LdapRequestHandler<ExtendedRequest> getExtendedHandler()
923     {
924         return extendedHandler;
925     }
926 
927 
928     public void setExtendedHandler( LdapRequestHandler<ExtendedRequest> extendedHandler )
929     {
930         this.handler.removeMessageHandler( ExtendedRequest.class );
931         this.extendedHandler = extendedHandler;
932         this.extendedHandler.setLdapServer( this );
933         this.handler.addMessageHandler( ExtendedRequest.class, this.extendedHandler );
934     }
935 
936 
937     public LdapRequestHandler<ModifyRequest> getModifyHandler()
938     {
939         return modifyHandler;
940     }
941 
942 
943     public void setModifyHandler( LdapRequestHandler<ModifyRequest> modifyHandler )
944     {
945         this.handler.removeMessageHandler( ModifyRequest.class );
946         this.modifyHandler = modifyHandler;
947         this.modifyHandler.setLdapServer( this );
948         this.handler.addMessageHandler( ModifyRequest.class, this.modifyHandler );
949     }
950 
951 
952     public LdapRequestHandler<ModifyDnRequest> getModifyDnHandler()
953     {
954         return modifyDnHandler;
955     }
956 
957 
958     public void setModifyDnHandler( LdapRequestHandler<ModifyDnRequest> modifyDnHandler )
959     {
960         this.handler.removeMessageHandler( ModifyDnRequest.class );
961         this.modifyDnHandler = modifyDnHandler;
962         this.modifyDnHandler.setLdapServer( this );
963         this.handler.addMessageHandler( ModifyDnRequest.class, this.modifyDnHandler );
964     }
965 
966 
967     public LdapRequestHandler<SearchRequest> getSearchHandler()
968     {
969         return searchHandler;
970     }
971 
972 
973     public void setSearchHandler( LdapRequestHandler<SearchRequest> searchHandler )
974     {
975         this.handler.removeMessageHandler( SearchRequest.class );
976         this.searchHandler = searchHandler;
977         this.searchHandler.setLdapServer( this );
978         this.handler.addMessageHandler( SearchRequest.class, this.searchHandler );
979     }
980 
981 
982     public LdapRequestHandler<UnbindRequest> getUnbindHandler()
983     {
984         return unbindHandler;
985     }
986 
987 
988     public void setUnbindHandler( LdapRequestHandler<UnbindRequest> unbindHandler )
989     {
990         this.handler.removeMessageHandler( UnbindRequest.class );
991         this.unbindHandler = unbindHandler;
992         this.unbindHandler.setLdapServer( this );
993         this.handler.addMessageHandler( UnbindRequest.class, this.unbindHandler );
994     }
995 
996 
997     public boolean isStarted()
998     {
999         return started;
1000     }
1001 
1002 
1003     public void setStarted( boolean started )
1004     {
1005         this.started = started;
1006     }
1007 }