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;
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.directory.Attribute;
26  import javax.naming.directory.Attributes;
27  
28  import org.apache.ldap.common.exception.LdapAuthenticationNotSupportedException;
29  import org.apache.ldap.common.exception.LdapConfigurationException;
30  import org.apache.ldap.common.exception.LdapNoPermissionException;
31  import org.apache.ldap.common.message.LockableAttributeImpl;
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.authz.AuthorizationService;
39  import org.apache.ldap.server.configuration.Configuration;
40  import org.apache.ldap.server.configuration.ConfigurationException;
41  import org.apache.ldap.server.configuration.StartupConfiguration;
42  import org.apache.ldap.server.interceptor.InterceptorChain;
43  import org.apache.ldap.server.jndi.DeadContext;
44  import org.apache.ldap.server.jndi.ServerLdapContext;
45  import org.apache.ldap.server.partition.DefaultDirectoryPartitionNexus;
46  import org.apache.ldap.server.partition.DirectoryPartitionNexus;
47  import org.apache.ldap.server.schema.AttributeTypeRegistry;
48  import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
49  import org.apache.ldap.server.schema.GlobalRegistries;
50  import org.apache.ldap.server.schema.bootstrap.BootstrapRegistries;
51  import org.apache.ldap.server.schema.bootstrap.BootstrapSchemaLoader;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  
56  /***
57   * Default implementation of {@link DirectoryService}.
58   * 
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
60   * @version $Rev: 328323 $
61   */
62  class DefaultDirectoryService extends DirectoryService
63  {
64      private static final Logger log = LoggerFactory.getLogger( DefaultDirectoryService.class );
65  
66      private final String instanceId;
67  
68      private final DirectoryServiceConfiguration configuration = new DefaultDirectoryServiceConfiguration( this );
69  
70      private DirectoryServiceListener serviceListener;
71      
72      /*** the initial context environment that fired up the backend subsystem */
73      private Hashtable environment;
74      
75      /*** the configuration */
76      private StartupConfiguration startupConfiguration;
77  
78      /*** the registries for system schema objects */
79      private GlobalRegistries globalRegistries;
80  
81      /*** the root nexus */
82      private DefaultDirectoryPartitionNexus partitionNexus;
83  
84      /*** whether or not server is started for the first time */
85      private boolean firstStart;
86  
87      /*** The interceptor (or interceptor chain) for this service */
88      private InterceptorChain interceptorChain;
89      
90      /*** whether or not this instance has been shutdown */
91      private boolean started = false;
92  
93  
94      // ------------------------------------------------------------------------
95      // Constructor
96      // ------------------------------------------------------------------------
97  
98      /***
99       * Creates a new instance.
100      */
101     public DefaultDirectoryService( String instanceId )
102     {
103         if( instanceId == null )
104         {
105             throw new NullPointerException( "instanceId" );
106         }
107         
108         this.instanceId = instanceId;
109         
110         // Register shutdown hook.
111         Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() {
112             public void run()
113             {
114                 try
115                 {
116                     shutdown();
117                 }
118                 catch( NamingException e )
119                 {
120                     log.warn(
121                             "Failed to shut down the directory service: " +
122                             DefaultDirectoryService.this.instanceId, e );
123                 }
124             }
125         }, "ApacheDS Shutdown Hook (" + instanceId + ')' ) );
126     }
127 
128     // ------------------------------------------------------------------------
129     // BackendSubsystem Interface Method Implemetations
130     // ------------------------------------------------------------------------
131 
132     public Context getJndiContext( String rootDN ) throws NamingException
133     {
134         return this.getJndiContext( null, null, "none", rootDN );
135     }
136 
137     public synchronized Context getJndiContext( String principal, byte[] credential, String authentication, String rootDN ) throws NamingException
138     {
139         checkSecuritySettings( principal, credential, authentication );
140         
141         if ( !started )
142         {
143             return new DeadContext();
144         }
145         
146         Hashtable environment = getEnvironment();
147         environment.remove( Context.SECURITY_PRINCIPAL );
148         environment.remove( Context.SECURITY_CREDENTIALS );
149         environment.remove( Context.SECURITY_AUTHENTICATION );
150         
151         if( principal != null )
152         {
153             environment.put( Context.SECURITY_PRINCIPAL, principal );
154         }
155         
156         if( credential != null )
157         {
158             environment.put( Context.SECURITY_CREDENTIALS, credential );
159         }
160         
161         if( authentication != null )
162         {
163             environment.put( Context.SECURITY_AUTHENTICATION, authentication );
164         }
165         
166         if( rootDN == null )
167         {
168             rootDN = "";
169         }
170         environment.put( Context.PROVIDER_URL, rootDN );
171 
172         return new ServerLdapContext( this, environment );
173     }
174 
175     public synchronized void startup( DirectoryServiceListener listener, Hashtable env ) throws NamingException
176     {
177         Hashtable envCopy = ( Hashtable ) env.clone();
178 
179         if( started )
180         {
181             return;
182         }
183 
184         StartupConfiguration cfg = ( StartupConfiguration ) Configuration.toConfiguration( env );
185         envCopy.put( Context.PROVIDER_URL, "" );
186         
187         try
188         {
189             cfg.validate();
190         }
191         catch( ConfigurationException e )
192         {
193             NamingException ne = new LdapConfigurationException( "Invalid configuration." );
194             ne.initCause( e );
195             throw ne;
196         }
197 
198         this.environment = envCopy;
199         this.startupConfiguration = cfg;
200         
201         listener.beforeStartup( this );
202 
203         initialize();
204         firstStart = createBootstrapEntries();
205         showSecurityWarnings();
206         createTestEntries();
207         this.serviceListener = listener;
208         started = true;
209         listener.afterStartup( this );
210     }
211 
212     public synchronized void sync() throws NamingException
213     {
214         if ( !started )
215         {
216             return;
217         }
218 
219         serviceListener.beforeSync( this );
220         try
221         {
222             this.partitionNexus.sync();
223         }
224         finally
225         {
226             serviceListener.afterSync( this );
227         }
228     }
229 
230 
231     public synchronized void shutdown() throws NamingException
232     {
233         if ( !started )
234         {
235             return;
236         }
237 
238         serviceListener.beforeShutdown( this );
239         try
240         {
241             this.partitionNexus.sync();
242             this.partitionNexus.destroy();
243             this.interceptorChain.destroy();
244             this.started = false;
245         }
246         finally
247         {
248             environment = null;
249             interceptorChain = null;
250             startupConfiguration = null;
251             serviceListener.afterShutdown( this );
252         }
253     }
254     
255     public String getInstanceId()
256     {
257         return instanceId;
258     }
259     
260     public DirectoryServiceConfiguration getConfiguration()
261     {
262         return configuration;
263     }
264     
265     
266     public Hashtable getEnvironment()
267     {
268         return ( Hashtable ) environment.clone();
269     }
270     
271     public DirectoryServiceListener getServiceListener()
272     {
273         return serviceListener;
274     }
275     
276     public StartupConfiguration getStartupConfiguration()
277     {
278         return startupConfiguration;
279     }
280     
281     public GlobalRegistries getGlobalRegistries()
282     {
283         return globalRegistries;
284     }
285 
286     public DirectoryPartitionNexus getPartitionNexus()
287     {
288         return partitionNexus;
289     }
290     
291     public InterceptorChain getInterceptorChain()
292     {
293         return interceptorChain;
294     }
295     
296     public boolean isFirstStart()
297     {
298         return firstStart;
299     }
300     
301     public boolean isStarted()
302     {
303         return started;
304     }
305     
306     /***
307      * Checks to make sure security environment parameters are set correctly.
308      *
309      * @throws javax.naming.NamingException if the security settings are not correctly configured.
310      */
311     private void checkSecuritySettings( String principal, byte[] credential, String authentication ) throws NamingException
312     {
313         if( authentication == null )
314         {
315             authentication = "";
316         }
317         
318         /*
319          * If bind is simple make sure we have the credentials and the
320          * principal name set within the environment, otherwise complain
321          */
322         if ( "simple".equalsIgnoreCase( authentication ) )
323         {
324             if ( credential == null )
325             {
326                 throw new LdapConfigurationException( "missing required "
327                         + Context.SECURITY_CREDENTIALS + " property for simple authentication" );
328             }
329 
330             if ( principal == null )
331             {
332                 throw new LdapConfigurationException( "missing required "
333                         + Context.SECURITY_PRINCIPAL + " property for simple authentication" );
334             }
335         }
336         /*
337          * If bind is none make sure credentials and the principal
338          * name are NOT set within the environment, otherwise complain
339          */
340         else if ( "none".equalsIgnoreCase( authentication ) )
341         {
342             if ( credential != null )
343             {
344                 throw new LdapConfigurationException( "ambiguous bind "
345                         + "settings encountered where bind is anonymous yet "
346                         + Context.SECURITY_CREDENTIALS + " property is set" );
347             }
348             if ( principal != null )
349             {
350                 throw new LdapConfigurationException( "ambiguous bind "
351                         + "settings encountered where bind is anonymous yet "
352                         + Context.SECURITY_PRINCIPAL + " property is set" );
353             }
354             
355             if( !startupConfiguration.isAllowAnonymousAccess() )
356             {
357                 throw new LdapNoPermissionException( "Anonymous access disabled." );
358             }
359         }
360         else
361         {
362             /*
363              * If bind is anything other than simple or none we need to
364              * complain because SASL is not a supported auth method yet
365              */
366             throw new LdapAuthenticationNotSupportedException( "Unknown authentication type: '" + authentication + "'", ResultCodeEnum.AUTHMETHODNOTSUPPORTED );
367         }
368     }
369 
370 
371     /***
372      * Returns true if we had to create the bootstrap entries on the first
373      * start of the server.  Otherwise if all entries exist, meaning none
374      * had to be created, then we are not starting for the first time.
375      *
376      * @throws javax.naming.NamingException
377      */
378     private boolean createBootstrapEntries() throws NamingException
379     {
380         boolean firstStart = false;
381 
382         // -------------------------------------------------------------------
383         // create admin entry
384         // -------------------------------------------------------------------
385 
386         /*
387          * If the admin entry is there, then the database was already created
388          */
389         if ( !partitionNexus.hasEntry( DirectoryPartitionNexus.getAdminName() ) )
390         {
391             firstStart = true;
392 
393             Attributes attributes = new LockableAttributesImpl();
394             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
395             objectClass.add( "top" );
396             objectClass.add( "person" );
397             objectClass.add( "organizationalPerson" );
398             objectClass.add( "inetOrgPerson" );
399             attributes.put( objectClass );
400 
401             attributes.put( "uid", DirectoryPartitionNexus.ADMIN_UID );
402             attributes.put( "userPassword", DirectoryPartitionNexus.ADMIN_PASSWORD );
403             attributes.put( "displayName", "Directory Superuser" );
404             attributes.put( "cn", "system administrator" );
405             attributes.put( "sn", "administrator" );
406             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
407             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
408             attributes.put( "displayName", "Directory Superuser" );
409             
410             partitionNexus.add( DirectoryPartitionNexus.ADMIN_PRINCIPAL, DirectoryPartitionNexus.getAdminName(), attributes );
411         }
412 
413         // -------------------------------------------------------------------
414         // create system users area
415         // -------------------------------------------------------------------
416 
417         if ( !partitionNexus.hasEntry( new LdapName( "ou=users,ou=system" ) ) )
418         {
419             firstStart = true;
420 
421             Attributes attributes = new LockableAttributesImpl();
422             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
423             objectClass.add( "top" );
424             objectClass.add( "organizationalUnit" );
425             attributes.put( objectClass );
426 
427             attributes.put( "ou", "users" );
428             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
429             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
430 
431             partitionNexus.add( "ou=users,ou=system", new LdapName( "ou=users,ou=system" ), attributes );
432         }
433 
434         // -------------------------------------------------------------------
435         // create system groups area
436         // -------------------------------------------------------------------
437 
438         if ( !partitionNexus.hasEntry( new LdapName( "ou=groups,ou=system" ) ) )
439         {
440             firstStart = true;
441 
442             Attributes attributes = new LockableAttributesImpl();
443             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
444             objectClass.add( "top" );
445             objectClass.add( "organizationalUnit" );
446             attributes.put( objectClass );
447 
448             attributes.put( "ou", "groups" );
449             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
450             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
451 
452             partitionNexus.add( "ou=groups,ou=system", new LdapName( "ou=groups,ou=system" ), attributes );
453         }
454 
455         // -------------------------------------------------------------------
456         // create administrator group
457         // -------------------------------------------------------------------
458 
459         String upName = "cn=Administrators,ou=groups,ou=system";
460         Name normName = new LdapName( "cn=administrators,ou=groups,ou=system" );
461         if ( !partitionNexus.hasEntry( normName ) )
462         {
463             firstStart = true;
464 
465             Attributes attributes = new LockableAttributesImpl();
466             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
467             objectClass.add( "top" );
468             objectClass.add( "groupOfUniqueNames" );
469             attributes.put( objectClass );
470             attributes.put( "cn", "Administrators" );
471             attributes.put( "uniqueMember", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
472             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
473             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
474 
475             partitionNexus.add( upName, normName, attributes );
476             AuthorizationService authzSrvc = ( AuthorizationService ) interceptorChain.get( "authorizationService" );
477             authzSrvc.cacheNewGroup( upName, normName, attributes );
478         }
479 
480         // -------------------------------------------------------------------
481         // create system configuration area
482         // -------------------------------------------------------------------
483 
484         if ( !partitionNexus.hasEntry( new LdapName( "ou=configuration,ou=system" ) ) )
485         {
486             firstStart = true;
487 
488             Attributes attributes = new LockableAttributesImpl();
489             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
490             objectClass.add( "top" );
491             objectClass.add( "organizationalUnit" );
492             attributes.put( objectClass );
493 
494             attributes.put( "ou", "configuration" );
495             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
496             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
497 
498             partitionNexus.add( "ou=configuration,ou=system", new LdapName( "ou=configuration,ou=system" ), attributes );
499         }
500 
501         // -------------------------------------------------------------------
502         // create system configuration area for partition information
503         // -------------------------------------------------------------------
504 
505         if ( !partitionNexus.hasEntry( new LdapName( "ou=partitions,ou=configuration,ou=system" ) ) )
506         {
507             firstStart = true;
508 
509             Attributes attributes = new LockableAttributesImpl();
510             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
511             objectClass.add( "top" );
512             objectClass.add( "organizationalUnit" );
513             attributes.put( objectClass );
514 
515             attributes.put( "ou", "partitions" );
516             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
517             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
518 
519             partitionNexus.add( "ou=partitions,ou=configuration,ou=system",
520                     new LdapName( "ou=partitions,ou=configuration,ou=system" ), attributes );
521         }
522 
523         // -------------------------------------------------------------------
524         // create system configuration area for services
525         // -------------------------------------------------------------------
526 
527         if ( !partitionNexus.hasEntry( new LdapName( "ou=services,ou=configuration,ou=system" ) ) )
528         {
529             firstStart = true;
530 
531             Attributes attributes = new LockableAttributesImpl();
532             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
533             objectClass.add( "top" );
534             objectClass.add( "organizationalUnit" );
535             attributes.put( objectClass );
536 
537             attributes.put( "ou", "services" );
538             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
539             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
540 
541             partitionNexus.add( "ou=services,ou=configuration,ou=system",
542                     new LdapName( "ou=services,ou=configuration,ou=system" ), attributes );
543         }
544 
545         // -------------------------------------------------------------------
546         // create system configuration area for interceptors
547         // -------------------------------------------------------------------
548 
549         if ( !partitionNexus.hasEntry( new LdapName( "ou=interceptors,ou=configuration,ou=system" ) ) )
550         {
551             firstStart = true;
552 
553             Attributes attributes = new LockableAttributesImpl();
554             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
555             objectClass.add( "top" );
556             objectClass.add( "organizationalUnit" );
557             attributes.put( objectClass );
558 
559             attributes.put( "ou", "interceptors" );
560             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
561             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
562 
563             partitionNexus.add( "ou=interceptors,ou=configuration,ou=system",
564                     new LdapName( "ou=interceptors,ou=configuration,ou=system" ), attributes );
565         }
566 
567         // -------------------------------------------------------------------
568         // create system preferences area
569         // -------------------------------------------------------------------
570 
571         if ( !partitionNexus.hasEntry( new LdapName( "prefNodeName=sysPrefRoot,ou=system" ) ) )
572         {
573             firstStart = true;
574 
575             Attributes attributes = new LockableAttributesImpl();
576             Attribute objectClass = new LockableAttributeImpl( "objectClass" );
577             objectClass.add( "top" );
578             objectClass.add( "organizationalUnit" );
579             attributes.put( objectClass );
580 
581             attributes.put( "objectClass", "extensibleObject" );
582             attributes.put( "prefNodeName", "sysPrefRoot" );
583             attributes.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
584             attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
585 
586             LdapName dn = new LdapName( "prefNodeName=sysPrefRoot,ou=system" );
587 
588             partitionNexus.add( "prefNodeName=sysPrefRoot,ou=system", dn, attributes );
589         }
590 
591         return firstStart;
592     }
593     
594     /***
595      * Displays security warning messages if any possible secutiry issue is found.
596      */
597     private void showSecurityWarnings() throws NamingException
598     {
599         // Warn if the default password is not changed.
600         boolean needToChangeAdminPassword = false;
601         
602         Attributes adminEntry = partitionNexus.lookup( new LdapName( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) );
603         Object userPassword = adminEntry.get( "userPassword" ).get();
604         if( userPassword instanceof byte[] )
605         {
606             needToChangeAdminPassword = DirectoryPartitionNexus.ADMIN_PASSWORD.equals( new String( ( byte[] ) userPassword ) );
607         }
608         else if ( userPassword.toString().equals( new String( DirectoryPartitionNexus.ADMIN_PASSWORD ) ) )
609         {
610             needToChangeAdminPassword = DirectoryPartitionNexus.ADMIN_PASSWORD.equals( userPassword.toString() );
611         }
612         
613         if( needToChangeAdminPassword )
614         {
615             log.warn(
616                     "You didn't change the admin password of directory service " +
617                     "instance '" + instanceId + "'.  " +
618                     "Please update the admin password as soon as possible " +
619                     "to prevent a possible security breach." );
620         }
621     }
622     
623     private void createTestEntries() throws NamingException
624     {
625         /*
626          * Unfortunately to test non-root user startup of the core and make sure
627          * all the appropriate functionality is there we need to load more user
628          * entries at startup due to a chicken and egg like problem.  The value
629          * of this property is a list of attributes to be added.
630          */
631         Iterator i = startupConfiguration.getTestEntries().iterator();
632         while( i.hasNext() )
633         {
634             Attributes entry = ( Attributes ) i.next();
635             entry.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL );
636             entry.put( "createTimestamp", DateUtils.getGeneralizedTime() );
637             
638             Attribute dn = ( Attribute ) entry.get( "dn" ).clone();
639             AttributeTypeRegistry registry = globalRegistries.getAttributeTypeRegistry();
640             NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( registry );
641             DnParser parser = new DnParser( ncn );
642             Name ndn = parser.parse( ( String ) dn.get() );
643             
644             partitionNexus.add( ( String ) dn.get(), ndn, entry );
645         }
646     }
647 
648     /***
649      * Kicks off the initialization of the entire system.
650      *
651      * @throws javax.naming.NamingException if there are problems along the way
652      */
653     private void initialize() throws NamingException
654     {
655         // --------------------------------------------------------------------
656         // Load the schema here and check that it is ok!
657         // --------------------------------------------------------------------
658 
659         BootstrapRegistries bootstrapRegistries = new BootstrapRegistries();
660 
661         BootstrapSchemaLoader loader = new BootstrapSchemaLoader();
662         loader.load( startupConfiguration.getBootstrapSchemas(), bootstrapRegistries );
663 
664         java.util.List errors = bootstrapRegistries.checkRefInteg();
665         if ( !errors.isEmpty() )
666         {
667             NamingException e = new NamingException();
668 
669             e.setRootCause( ( Throwable ) errors.get( 0 ) );
670 
671             throw e;
672         }
673 
674         globalRegistries = new GlobalRegistries( bootstrapRegistries );
675         
676         partitionNexus = new DefaultDirectoryPartitionNexus( new LockableAttributesImpl() );
677         partitionNexus.init( configuration, null );
678         
679         interceptorChain = new InterceptorChain();
680         interceptorChain.init( configuration );
681     }
682 }