View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.jndi;
18  
19  
20  import java.io.IOException;
21  import java.io.FileFilter;
22  import java.io.File;
23  import java.net.InetSocketAddress;
24  import java.util.Hashtable;
25  import java.util.Iterator;
26  
27  import javax.naming.NamingException;
28  import javax.naming.Context;
29  import javax.naming.directory.DirContext;
30  import javax.naming.directory.Attributes;
31  import javax.naming.directory.BasicAttributes;
32  
33  import org.apache.kerberos.kdc.KdcConfiguration;
34  import org.apache.kerberos.kdc.KerberosServer;
35  import org.apache.kerberos.store.JndiPrincipalStoreImpl;
36  import org.apache.kerberos.store.PrincipalStore;
37  import org.apache.ldap.common.exception.LdapConfigurationException;
38  import org.apache.ldap.server.DirectoryService;
39  import org.apache.ldap.server.configuration.ServerStartupConfiguration;
40  import org.apache.ldap.server.protocol.ExtendedOperationHandler;
41  import org.apache.ldap.server.protocol.LdapProtocolProvider;
42  import org.apache.mina.common.TransportType;
43  import org.apache.mina.registry.Service;
44  import org.apache.mina.registry.ServiceRegistry;
45  import org.apache.ntp.NtpServer;
46  import org.apache.ntp.NtpConfiguration;
47  import org.apache.protocol.common.LoadStrategy;
48  import org.apache.protocol.common.store.LdifFileLoader;
49  import org.apache.changepw.ChangePasswordServer;
50  import org.apache.changepw.ChangePasswordConfiguration;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  
55  /***
56   * Adds additional bootstrapping for server socket listeners when firing
57   * up the server.
58   *
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
60   * @version $Rev: 329711 $
61   * @see javax.naming.spi.InitialContextFactory
62   */
63  public class ServerContextFactory extends CoreContextFactory
64  {
65      private static final Logger log = LoggerFactory.getLogger( ServerContextFactory.class.getName() );
66      private static final String LDIF_FILES_DN = "ou=loadedLdifFiles,ou=configuration,ou=system";
67  
68      private static Service ldapService;
69      private static KerberosServer kdcServer;
70      private static ChangePasswordServer changePasswordServer;
71      private static NtpServer ntpServer;
72      private static ServiceRegistry minaRegistry;
73  
74  
75      protected ServiceRegistry getMinaRegistry()
76      {
77          return minaRegistry;
78      }
79  
80  
81      public void afterShutdown( DirectoryService service )
82      {
83          if ( minaRegistry != null )
84          {
85              if ( ldapService != null )
86              {
87                  minaRegistry.unbind( ldapService );
88                  if ( log.isInfoEnabled() )
89                  {
90                      log.info( "Unbind of LDAP Service complete: " + ldapService );
91                  }
92                  ldapService = null;
93              }
94  
95              if ( kdcServer != null )
96              {
97                  kdcServer.destroy();
98                  if ( log.isInfoEnabled() )
99                  {
100                     log.info( "Unbind of KRB5 Service complete: " + kdcServer );
101                 }
102                 kdcServer = null;
103             }
104 
105             if ( changePasswordServer != null )
106             {
107                 changePasswordServer.destroy();
108                 if ( log.isInfoEnabled() )
109                 {
110                     log.info( "Unbind of Change Password Service complete: " + changePasswordServer );
111                 }
112                 changePasswordServer = null;
113             }
114 
115             if ( ntpServer != null )
116             {
117                 ntpServer.destroy();
118                 if ( log.isInfoEnabled() )
119                 {
120                     log.info( "Unbind of NTP Service complete: " + ntpServer );
121                 }
122                 ntpServer = null;
123             }
124         }
125     }
126 
127 
128     public void afterStartup( DirectoryService service ) throws NamingException
129     {
130         ServerStartupConfiguration cfg =
131             ( ServerStartupConfiguration ) service.getConfiguration().getStartupConfiguration();
132         Hashtable env = service.getConfiguration().getEnvironment();
133 
134         loadLdifs( service );
135 
136         if ( cfg.isEnableNetworking() )
137         {
138             setupRegistry( cfg );
139             startLdapProtocol( cfg, env );
140 
141             if ( cfg.isEnableKerberos() )
142             {
143                 try
144                 {
145                     KdcConfiguration kdcConfiguration = new KdcConfiguration( env, LoadStrategy.PROPS );
146                     PrincipalStore kdcStore = new JndiPrincipalStoreImpl( kdcConfiguration, this );
147                     kdcServer = new KerberosServer( kdcConfiguration, minaRegistry, kdcStore );
148                 }
149                 catch ( Throwable t )
150                 {
151                     log.error( "Failed to start the Kerberos service", t );
152                 }
153             }
154 
155             if ( cfg.isEnableChangePassword() )
156             {
157                 try
158                 {
159                     ChangePasswordConfiguration changePasswordConfiguration = new ChangePasswordConfiguration( env, LoadStrategy.PROPS );
160                     PrincipalStore store = new JndiPrincipalStoreImpl( changePasswordConfiguration, this );
161                     changePasswordServer = new ChangePasswordServer( changePasswordConfiguration, minaRegistry, store );
162                 }
163                 catch ( Throwable t )
164                 {
165                     log.error( "Failed to start the Change Password service", t );
166                 }
167             }
168 
169             if ( cfg.isEnableNtp() )
170             {
171                 try
172                 {
173                     NtpConfiguration ntpConfig = new NtpConfiguration( env, LoadStrategy.PROPS );
174                     ntpServer = new NtpServer( ntpConfig, minaRegistry );
175                 }
176                 catch ( Throwable t )
177                 {
178                     log.error( "Failed to start the NTP service", t );
179                 }
180             }
181         }
182     }
183 
184 
185     private void ensureLdifFileBase( DirContext root ) throws NamingException
186     {
187         Attributes entry = new BasicAttributes( "ou", "loadedLdifFiles", true );
188         entry.put( "objectClass", "top" );
189         entry.get( "objectClass" ).add( "organizationalUnit" );
190         try
191         {
192             root.createSubcontext( LDIF_FILES_DN, entry );
193             log.info( "Creating " + LDIF_FILES_DN );
194         }
195         catch( NamingException e ) { log.info( LDIF_FILES_DN + " exists" );}
196     }
197 
198 
199     private final static String WINDOWSFILE_ATTR = "windowsFilePath";
200     private final static String UNIXFILE_ATTR = "unixFilePath";
201     private final static String WINDOWSFILE_OC = "windowsFile";
202     private final static String UNIXFILE_OC = "unixFile";
203     private void addFileEntry( DirContext root, File ldif ) throws NamingException
204     {
205         String rdnAttr = File.pathSeparatorChar == '//' ? WINDOWSFILE_ATTR : UNIXFILE_ATTR;
206         String oc = File.pathSeparatorChar == '//' ? WINDOWSFILE_OC : UNIXFILE_OC;
207         StringBuffer buf = new StringBuffer();
208         buf.append( rdnAttr );
209         buf.append( "=" );
210         buf.append( getCanonical( ldif ) );
211         buf.append( "," );
212         buf.append( LDIF_FILES_DN );
213 
214         Attributes entry = new BasicAttributes( rdnAttr, getCanonical( ldif ), true );
215         entry.put( "objectClass", "top" );
216         entry.get( "objectClass" ).add( oc );
217         root.createSubcontext( buf.toString(), entry );
218     }
219 
220 
221     private Attributes getLdifFileEntry( DirContext root, File ldif ) throws NamingException
222     {
223         String rdnAttr = File.pathSeparatorChar == '//' ? WINDOWSFILE_ATTR : UNIXFILE_ATTR;
224         StringBuffer buf = new StringBuffer();
225         buf.append( rdnAttr );
226         buf.append( "=" );
227         buf.append( getCanonical( ldif ) );
228         buf.append( "," );
229         buf.append( LDIF_FILES_DN );
230 
231         try
232         {
233             return root.getAttributes( buf.toString(), new String[]{ "createTimestamp" });
234         }
235         catch ( NamingException e )
236         {
237             return null;
238         }
239     }
240 
241 
242     private String getCanonical( File file ) throws NamingException
243     {
244         String canonical = null;
245         try
246         {
247             canonical = file.getCanonicalPath();
248         }
249         catch (IOException e)
250         {
251             log.error( "could not get canonical path", e );
252             return null;
253         }
254         return canonical;
255     }
256 
257 
258     private void loadLdifs( DirectoryService service ) throws NamingException
259     {
260         ServerStartupConfiguration cfg =
261             ( ServerStartupConfiguration ) service.getConfiguration().getStartupConfiguration();
262 
263         // log and bail if property not set
264         if ( cfg.getLdifDirectory() == null )
265         {
266             log.info( "LDIF load directory not specified.  No LDIF files will be loaded." );
267             return;
268         }
269 
270         // log and bail if LDIF directory does not exists
271         if ( !cfg.getLdifDirectory().exists() )
272         {
273             log.warn( "LDIF load directory '" + getCanonical( cfg.getLdifDirectory() )
274                     + "' does not exist.  No LDIF files will be loaded.");
275             return;
276         }
277 
278         // get an initial context to the rootDSE for creating the LDIF entries
279         Hashtable env = ( Hashtable ) service.getConfiguration().getEnvironment().clone();
280         env.put( Context.PROVIDER_URL, "" );
281         DirContext root = ( DirContext ) this.getInitialContext( env );
282 
283         // make sure the configuration area for loaded ldif files is present
284         ensureLdifFileBase( root );
285 
286         // if ldif directory is a file try to load it
287         if ( !cfg.getLdifDirectory().isDirectory() )
288         {
289             log.info( "LDIF load directory '" + getCanonical( cfg.getLdifDirectory() )
290                     + "' is a file.  Will attempt to load as LDIF." );
291             Attributes fileEntry = getLdifFileEntry( root, cfg.getLdifDirectory() );
292             if ( fileEntry != null )
293             {
294                 String time = ( String ) fileEntry.get( "createTimestamp" ).get();
295                 log.info( "Load of LDIF file '" + getCanonical( cfg.getLdifDirectory() )
296                         + "' skipped.  It has already been loaded on " + time + "." );
297                 return;
298             }
299             LdifFileLoader loader = new LdifFileLoader( root, cfg.getLdifDirectory(), cfg.getLdifFilters() );
300             loader.execute();
301 
302             addFileEntry( root, cfg.getLdifDirectory() );
303             return;
304         }
305 
306         // get all the ldif files within the directory (should be sorted alphabetically)
307         File[] ldifFiles = cfg.getLdifDirectory().listFiles( new FileFilter()
308         {
309             public boolean accept( File pathname )
310             {
311                 boolean isLdif = pathname.getName().toLowerCase().endsWith( ".ldif" );
312                 return pathname.isFile() && pathname.canRead() && isLdif;
313             }
314         });
315 
316         // log and bail if we could not find any LDIF files
317         if ( ldifFiles == null || ldifFiles.length == 0 )
318         {
319             log.warn( "LDIF load directory '" + getCanonical( cfg.getLdifDirectory() )
320                     + "' does not contain any LDIF files.  No LDIF files will be loaded.");
321             return;
322         }
323 
324         // load all the ldif files and load each one that is loaded
325         for ( int ii = 0; ii < ldifFiles.length; ii++ )
326         {
327             Attributes fileEntry = getLdifFileEntry( root, ldifFiles[ii] );
328             if ( fileEntry != null )
329             {
330                 String time = ( String ) fileEntry.get( "createTimestamp" ).get();
331                 log.info( "Load of LDIF file '" + getCanonical( ldifFiles[ii] )
332                         + "' skipped.  It has already been loaded on " + time + "." );
333                 continue;
334             }
335             LdifFileLoader loader = new LdifFileLoader( root, ldifFiles[ii], cfg.getLdifFilters() );
336             int count = loader.execute();
337             log.info( "Loaded " + count + " entries from LDIF file '" + getCanonical( ldifFiles[ii] ) + "'" );
338             if ( fileEntry == null )
339             {
340                 addFileEntry( root, ldifFiles[ii] );
341             }
342         }
343     }
344 
345 
346     /***
347      * Starts up the MINA registry so various protocol providers can be started.
348      */
349     private void setupRegistry( ServerStartupConfiguration cfg )
350     {
351         minaRegistry = cfg.getMinaServiceRegistry();
352     }
353 
354 
355     /***
356      * Starts up the LDAP protocol provider to service LDAP requests
357      *
358      * @throws NamingException if there are problems starting the LDAP provider
359      */
360     private void startLdapProtocol( ServerStartupConfiguration cfg, Hashtable env ) throws NamingException
361     {
362         int port = cfg.getLdapPort();
363         Service service = new Service( "ldap", TransportType.SOCKET, new InetSocketAddress( port ) );
364 
365         // Register all extended operation handlers.
366         LdapProtocolProvider protocolProvider = new LdapProtocolProvider( ( Hashtable ) env.clone() );
367         for( Iterator i = cfg.getExtendedOperationHandlers().iterator(); i.hasNext(); )
368         {
369             ExtendedOperationHandler h = ( ExtendedOperationHandler ) i.next();
370             protocolProvider.addExtendedOperationHandler( h );
371         }
372         
373         try
374         {
375             minaRegistry.bind( service, protocolProvider );
376             ldapService = service;
377             if ( log.isInfoEnabled() )
378             {
379                 log.info( "Successful bind of LDAP Service completed: " + ldapService );
380             }
381         }
382         catch ( IOException e )
383         {
384             String msg = "Failed to bind the LDAP protocol service to the service registry: " + service;
385             LdapConfigurationException lce = new LdapConfigurationException( msg );
386             lce.setRootCause( e );
387             log.error( msg, e );
388             throw lce;
389         }
390     }
391 }