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  import java.util.Hashtable;
20  import java.util.Iterator;
21  
22  import javax.naming.Context;
23  import javax.naming.Name;
24  import javax.naming.NamingException;
25  import javax.naming.NoPermissionException;
26  import javax.naming.directory.Attribute;
27  import javax.naming.directory.Attributes;
28  
29  import org.apache.ldap.common.exception.LdapAuthenticationNotSupportedException;
30  import org.apache.ldap.common.exception.LdapConfigurationException;
31  import org.apache.ldap.common.exception.LdapNoPermissionException;
32  import org.apache.ldap.common.message.LockableAttributesImpl;
33  import org.apache.ldap.common.message.ResultCodeEnum;
34  import org.apache.ldap.common.name.DnParser;
35  import org.apache.ldap.common.name.LdapName;
36  import org.apache.ldap.common.name.NameComponentNormalizer;
37  import org.apache.ldap.common.util.DateUtils;
38  import org.apache.ldap.server.configuration.Configuration;
39  import org.apache.ldap.server.configuration.ConfigurationException;
40  import org.apache.ldap.server.configuration.StartupConfiguration;
41  import org.apache.ldap.server.interceptor.InterceptorChain;
42  import org.apache.ldap.server.partition.ContextPartitionNexus;
43  import org.apache.ldap.server.partition.DefaultContextPartitionNexus;
44  import org.apache.ldap.server.schema.AttributeTypeRegistry;
45  import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
46  import org.apache.ldap.server.schema.GlobalRegistries;
47  import org.apache.ldap.server.schema.bootstrap.BootstrapRegistries;
48  import org.apache.ldap.server.schema.bootstrap.BootstrapSchemaLoader;
49  
50  
51  /***
52   * Default implementation of {@link ContextFactoryService}.
53   * 
54   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
55   * @version $Rev: 226451 $
56   */
57  class DefaultContextFactoryService extends ContextFactoryService
58  {
59      private final String instanceId;
60  
61      private final ContextFactoryConfiguration configuration = new DefaultContextFactoryConfiguration( this );
62  
63      private ContextFactoryServiceListener serviceListener;
64      
65      /*** the initial context environment that fired up the backend subsystem */
66      private Hashtable environment;
67      
68      /*** the configuration */
69      private StartupConfiguration startupConfiguration;
70  
71      /*** the registries for system schema objects */
72      private GlobalRegistries globalRegistries;
73  
74      /*** the root nexus */
75      private DefaultContextPartitionNexus partitionNexus;
76  
77      /*** whether or not server is started for the first time */
78      private boolean firstStart;
79  
80      /*** The interceptor (or interceptor chain) for this service */
81      private InterceptorChain interceptorChain;
82      
83      /*** whether or not this instance has been shutdown */
84      private boolean started = false;
85  
86  
87      // ------------------------------------------------------------------------
88      // Constructor
89      // ------------------------------------------------------------------------
90  
91      /***
92       * Creates a new instance.
93       */
94      public DefaultContextFactoryService( String instanceId )
95      {
96          if( instanceId == null )
97          {
98              throw new NullPointerException( "instanceId" );
99          }
100         
101         this.instanceId = instanceId;
102         
103         // Register shutdown hook.
104         Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() {
105             public void run()
106             {
107                 try
108                 {
109                     shutdown();
110                 }
111                 catch( NamingException e )
112                 {
113                     e.printStackTrace();
114                 }
115             }
116         }, "ApacheDS Shutdown Hook" ) );
117     }
118 
119     // ------------------------------------------------------------------------
120     // BackendSubsystem Interface Method Implemetations
121     // ------------------------------------------------------------------------
122 
123     public Context getJndiContext( String rootDN ) throws NamingException
124     {
125         return this.getJndiContext( null, null, "none", rootDN );
126     }
127 
128     public synchronized Context getJndiContext( String principal, byte[] credential, String authentication, String rootDN ) throws NamingException
129     {
130         checkSecuritySettings( principal, credential, authentication );
131         
132         if ( !started )
133         {
134             return new DeadContext();
135         }
136         
137         Hashtable environment = getEnvironment();
138         environment.remove( Context.SECURITY_PRINCIPAL );
139         environment.remove( Context.SECURITY_CREDENTIALS );
140         environment.remove( Context.SECURITY_AUTHENTICATION );
141         
142         if( principal != null )
143         {
144             environment.put( Context.SECURITY_PRINCIPAL, principal );
145         }
146         
147         if( credential != null )
148         {
149             environment.put( Context.SECURITY_CREDENTIALS, credential );
150         }
151         
152         if( authentication != null )
153         {
154             environment.put( Context.SECURITY_AUTHENTICATION, authentication );
155         }
156         
157         if( rootDN == null )
158         {
159             rootDN = "";
160         }
161         environment.put( Context.PROVIDER_URL, rootDN );
162 
163         return new ServerLdapContext( this, environment );
164     }
165 
166     public synchronized void startup( ContextFactoryServiceListener listener, Hashtable env ) throws NamingException
167     {
168         Hashtable envCopy = ( Hashtable ) env.clone();
169 
170         if( started )
171         {
172             return;
173         }
174 
175         StartupConfiguration cfg = ( StartupConfiguration ) Configuration.toConfiguration( env );
176         envCopy.put( Context.PROVIDER_URL, "" );
177         
178         try
179         {
180             cfg.validate();
181         }
182         catch( ConfigurationException e )
183         {
184             NamingException ne = new LdapConfigurationException( "Invalid configuration." );
185             ne.initCause( e );
186             throw ne;
187         }
188 
189         this.environment = envCopy;
190         this.startupConfiguration = cfg;
191         
192         listener.beforeStartup( this );
193         try
194         {
195             initialize();
196             firstStart = createBootstrapEntries();
197             createTestEntries();
198             this.serviceListener = listener;
199             started = true;
200         }
201         finally
202         {
203             listener.afterStartup( this );
204         }
205     }
206 
207     public synchronized void sync() throws NamingException
208     {
209         if ( !started )
210         {
211             return;
212         }
213 
214         serviceListener.beforeSync( this );
215         try
216         {
217             this.partitionNexus.sync();
218         }
219         finally
220         {
221             serviceListener.afterSync( this );
222         }
223     }
224 
225 
226     public synchronized void shutdown() throws NamingException
227     {
228         if ( !started )
229         {
230             return;
231         }
232 
233         serviceListener.beforeShutdown( this );
234         try
235         {
236             this.partitionNexus.sync();
237             this.partitionNexus.destroy();
238             this.interceptorChain.destroy();
239             this.started = false;
240         }
241         finally
242         {
243             environment = null;
244             interceptorChain = null;
245             startupConfiguration = null;
246             serviceListener.afterShutdown( this );
247         }
248     }
249     
250     public String getInstanceId()
251     {
252         return instanceId;
253     }
254     
255     public ContextFactoryConfiguration getConfiguration()
256     {
257         return configuration;
258     }
259     
260     
261     public Hashtable getEnvironment()
262     {
263         return ( Hashtable ) environment.clone();
264     }
265     
266     public ContextFactoryServiceListener getServiceListener()
267     {
268         return serviceListener;
269     }
270     
271     public StartupConfiguration getStartupConfiguration()
272     {
273         return startupConfiguration;
274     }
275     
276     public GlobalRegistries getGlobalRegistries()
277     {
278         return globalRegistries;
279     }
280 
281     public ContextPartitionNexus getPartitionNexus()
282     {
283         return partitionNexus;
284     }
285     
286     public InterceptorChain getInterceptorChain()
287     {
288         return interceptorChain;
289     }
290     
291     public boolean isFirstStart()
292     {
293         return firstStart;
294     }
295     
296     public boolean isStarted()
297     {
298         return started;
299     }
300     
301     /***
302      * Checks to make sure security environment parameters are set correctly.
303      *
304      * @throws javax.naming.NamingException if the security settings are not correctly configured.
305      */
306     private void checkSecuritySettings( String principal, byte[] credential, String authentication ) throws NamingException
307     {
308         if( authentication == null )
309         {
310             authentication = "";
311         }
312         
313         /*
314          * If bind is simple make sure we have the credentials and the
315          * principal name set within the environment, otherwise complain
316          */
317         if ( "simple".equalsIgnoreCase( authentication ) )
318         {
319             if ( credential == null )
320             {
321                 throw new LdapConfigurationException( "missing required "
322                         + Context.SECURITY_CREDENTIALS + " property for simple authentication" );
323             }
324 
325             if ( principal == null )
326             {
327                 throw new LdapConfigurationException( "missing required "
328                         + Context.SECURITY_PRINCIPAL + " property for simple authentication" );
329             }
330         }
331         /*
332          * If bind is none make sure credentials and the principal
333          * name are NOT set within the environment, otherwise complain
334          */
335         else if ( "none".equalsIgnoreCase( authentication ) )
336         {
337             if ( credential != null )
338             {
339                 throw new LdapConfigurationException( "ambiguous bind "
340                         + "settings encountered where bind is anonymous yet "
341                         + Context.SECURITY_CREDENTIALS + " property is set" );
342             }
343             if ( principal != null )
344             {
345                 throw new LdapConfigurationException( "ambiguous bind "
346                         + "settings encountered where bind is anonymous yet "
347                         + Context.SECURITY_PRINCIPAL + " property is set" );
348             }
349             
350             if( !startupConfiguration.isAllowAnonymousAccess() )
351             {
352                 throw new LdapNoPermissionException( "Anonymous access disabled." );
353             }
354         }
355         else
356         {
357             /*
358              * If bind is anything other than simple or none we need to
359              * complain because SASL is not a supported auth method yet
360              */
361             throw new LdapAuthenticationNotSupportedException( "Unknown authentication type: '" + authentication + "'", ResultCodeEnum.AUTHMETHODNOTSUPPORTED );
362         }
363     }
364 
365 
366     /***
367      * Returns true if we had to create the bootstrap entries on the first
368      * start of the server.  Otherwise if all entries exist, meaning none
369      * had to be created, then we are not starting for the first time.
370      *
371      * @throws javax.naming.NamingException
372      */
373     private boolean createBootstrapEntries() throws NamingException
374     {
375         boolean firstStart = false;
376 
377         // -------------------------------------------------------------------
378         // create admin entry
379         // -------------------------------------------------------------------
380 
381         /*
382          * If the admin entry is there, then the database was already created
383          */
384         if ( !partitionNexus.hasEntry( ContextPartitionNexus.getAdminName() ) )
385         {
386             checkPermissionToCreateBootstrapEntries();
387             firstStart = true;
388 
389             Attributes attributes = new LockableAttributesImpl();
390             attributes.put( "objectClass", "top" );
391             attributes.put( "objectClass", "person" );
392             attributes.put( "objectClass", "organizationalPerson" );
393             attributes.put( "objectClass", "inetOrgPerson" );
394             attributes.put( "uid", ContextPartitionNexus.ADMIN_UID );
395             attributes.put( "userPassword", environment.get( Context.SECURITY_CREDENTIALS ) );
396             attributes.put( "displayName", "Directory Superuser" );
397             attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
398             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
399             attributes.put( "displayName", "Directory Superuser" );
400             
401             partitionNexus.add( ContextPartitionNexus.ADMIN_PRINCIPAL, ContextPartitionNexus.getAdminName(), attributes );
402         }
403 
404         // -------------------------------------------------------------------
405         // create system users area
406         // -------------------------------------------------------------------
407 
408         if ( !partitionNexus.hasEntry( new LdapName( "ou=users,ou=system" ) ) )
409         {
410             firstStart = true;
411             checkPermissionToCreateBootstrapEntries();
412 
413             Attributes attributes = new LockableAttributesImpl();
414             attributes.put( "objectClass", "top" );
415             attributes.put( "objectClass", "organizationalUnit" );
416             attributes.put( "ou", "users" );
417             attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
418             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
419 
420             partitionNexus.add( "ou=users,ou=system", new LdapName( "ou=users,ou=system" ), attributes );
421         }
422 
423         // -------------------------------------------------------------------
424         // create system groups area
425         // -------------------------------------------------------------------
426 
427         if ( !partitionNexus.hasEntry( new LdapName( "ou=groups,ou=system" ) ) )
428         {
429             firstStart = true;
430             checkPermissionToCreateBootstrapEntries();
431 
432             Attributes attributes = new LockableAttributesImpl();
433             attributes.put( "objectClass", "top" );
434             attributes.put( "objectClass", "organizationalUnit" );
435             attributes.put( "ou", "groups" );
436             attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
437             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
438 
439             partitionNexus.add( "ou=groups,ou=system", new LdapName( "ou=groups,ou=system" ), attributes );
440         }
441 
442         // -------------------------------------------------------------------
443         // create system preferences area
444         // -------------------------------------------------------------------
445 
446         if ( !partitionNexus.hasEntry( new LdapName( "prefNodeName=sysPrefRoot,ou=system" ) ) )
447         {
448             firstStart = true;
449             checkPermissionToCreateBootstrapEntries();
450 
451             Attributes attributes = new LockableAttributesImpl();
452             attributes.put( "objectClass", "top" );
453             attributes.put( "objectClass", "prefNode" );
454             attributes.put( "objectClass", "extensibleObject" );
455             attributes.put( "prefNodeName", "sysPrefRoot" );
456             attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
457             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
458 
459             LdapName dn = new LdapName( "prefNodeName=sysPrefRoot,ou=system" );
460 
461             partitionNexus.add( "prefNodeName=sysPrefRoot,ou=system", dn, attributes );
462         }
463 
464         return firstStart;
465     }
466     
467     private void checkPermissionToCreateBootstrapEntries() throws NamingException
468     {
469         String principal = ( String ) environment.get( Context.SECURITY_PRINCIPAL );
470         if( principal == null || !ContextPartitionNexus.ADMIN_PRINCIPAL.equals( principal ) )
471         {
472             throw new NoPermissionException(
473                     "Only '" + ContextPartitionNexus.ADMIN_PRINCIPAL + "' can initiate the first run." );
474         }
475     }
476 
477 
478 
479     private void createTestEntries() throws NamingException
480     {
481         /*
482          * Unfortunately to test non-root user startup of the core and make sure
483          * all the appropriate functionality is there we need to load more user
484          * entries at startup due to a chicken and egg like problem.  The value
485          * of this property is a list of attributes to be added.
486          */
487         Iterator i = startupConfiguration.getTestEntries().iterator();
488         while( i.hasNext() )
489         {
490             Attributes entry = ( Attributes ) i.next();
491             entry.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
492             entry.put( "createTimestamp", DateUtils.getGeneralizedTime() );
493             
494             Attribute dn = ( Attribute ) entry.get( "dn" ).clone();
495             AttributeTypeRegistry registry = globalRegistries.getAttributeTypeRegistry();
496             NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( registry );
497             DnParser parser = new DnParser( ncn );
498             Name ndn = parser.parse( ( String ) dn.get() );
499             
500             partitionNexus.add( ( String ) dn.get(), ndn, entry );
501         }
502     }
503 
504     /***
505      * Kicks off the initialization of the entire system.
506      *
507      * @throws javax.naming.NamingException if there are problems along the way
508      */
509     private void initialize() throws NamingException
510     {
511         // --------------------------------------------------------------------
512         // Load the schema here and check that it is ok!
513         // --------------------------------------------------------------------
514 
515         BootstrapRegistries bootstrapRegistries = new BootstrapRegistries();
516 
517         BootstrapSchemaLoader loader = new BootstrapSchemaLoader();
518         loader.load( startupConfiguration.getBootstrapSchemas(), bootstrapRegistries );
519 
520         java.util.List errors = bootstrapRegistries.checkRefInteg();
521         if ( !errors.isEmpty() )
522         {
523             NamingException e = new NamingException();
524 
525             e.setRootCause( ( Throwable ) errors.get( 0 ) );
526 
527             throw e;
528         }
529 
530         globalRegistries = new GlobalRegistries( bootstrapRegistries );
531         
532         partitionNexus = new DefaultContextPartitionNexus( new LockableAttributesImpl() );
533         partitionNexus.init( configuration, null );
534         
535         interceptorChain = new InterceptorChain();
536         interceptorChain.init( configuration );
537     }
538 }