001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.directory.server.factory; 020 021 022import java.io.IOException; 023import java.lang.annotation.Annotation; 024import java.lang.reflect.Method; 025import java.net.ServerSocket; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.List; 029 030import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms; 031import org.apache.directory.api.util.Network; 032import org.apache.directory.api.util.Strings; 033import org.apache.directory.server.annotations.CreateChngPwdServer; 034import org.apache.directory.server.annotations.CreateConsumer; 035import org.apache.directory.server.annotations.CreateKdcServer; 036import org.apache.directory.server.annotations.CreateLdapServer; 037import org.apache.directory.server.annotations.CreateTransport; 038import org.apache.directory.server.annotations.SaslMechanism; 039import org.apache.directory.server.core.annotations.AnnotationUtils; 040import org.apache.directory.server.core.api.DirectoryService; 041import org.apache.directory.server.i18n.I18n; 042import org.apache.directory.server.kerberos.ChangePasswordConfig; 043import org.apache.directory.server.kerberos.KerberosConfig; 044import org.apache.directory.server.kerberos.changepwd.ChangePasswordServer; 045import org.apache.directory.server.kerberos.kdc.KdcServer; 046import org.apache.directory.server.ldap.ExtendedOperationHandler; 047import org.apache.directory.server.ldap.LdapServer; 048import org.apache.directory.server.ldap.handlers.sasl.MechanismHandler; 049import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler; 050import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmProvider; 051import org.apache.directory.server.ldap.replication.SyncReplConfiguration; 052import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumer; 053import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumerImpl; 054import org.apache.directory.server.protocol.shared.transport.TcpTransport; 055import org.apache.directory.server.protocol.shared.transport.Transport; 056import org.apache.directory.server.protocol.shared.transport.UdpTransport; 057import org.junit.runner.Description; 058 059 060/** 061 * 062 * Annotation processor for creating LDAP and Kerberos servers. 063 * 064 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 065 */ 066public final class ServerAnnotationProcessor 067{ 068 private ServerAnnotationProcessor() 069 { 070 } 071 072 073 private static void createTransports( LdapServer ldapServer, CreateTransport[] transportBuilders ) 074 { 075 if ( transportBuilders.length != 0 ) 076 { 077 for ( CreateTransport transportBuilder : transportBuilders ) 078 { 079 List< Transport > transports = createTransports( transportBuilder ); 080 081 for ( Transport t : transports ) 082 { 083 ldapServer.addTransports( t ); 084 } 085 } 086 } 087 else 088 { 089 // Create default LDAP and LDAPS transports 090 try 091 { 092 int port = getFreePort(); 093 Transport ldap = new TcpTransport( port ); 094 ldapServer.addTransports( ldap ); 095 } 096 catch ( IOException ioe ) 097 { 098 // Don't know what to do here... 099 } 100 101 try 102 { 103 int port = getFreePort(); 104 Transport ldaps = new TcpTransport( port ); 105 ldaps.setEnableSSL( true ); 106 ldapServer.addTransports( ldaps ); 107 } 108 catch ( IOException ioe ) 109 { 110 // Don't know what to do here... 111 } 112 } 113 } 114 115 116 /** 117 * Just gives an instance of {@link LdapServer} without starting it. 118 * For getting a running LdapServer instance see {@link #createLdapServer(CreateLdapServer, DirectoryService)} 119 * @see #createLdapServer(CreateLdapServer, DirectoryService) 120 * 121 * @param createLdapServer The LdapServer to create 122 * @param directoryService the directory service 123 * @return The created LdapServer 124 */ 125 public static LdapServer instantiateLdapServer( CreateLdapServer createLdapServer, DirectoryService directoryService ) 126 { 127 if ( createLdapServer != null ) 128 { 129 LdapServer ldapServer = new LdapServer(); 130 131 ldapServer.setServiceName( createLdapServer.name() ); 132 133 // Read the transports 134 createTransports( ldapServer, createLdapServer.transports() ); 135 136 // Associate the DS to this LdapServer 137 ldapServer.setDirectoryService( directoryService ); 138 139 // Propagate the anonymous flag to the DS 140 directoryService.setAllowAnonymousAccess( createLdapServer.allowAnonymousAccess() ); 141 142 ldapServer.setSaslHost( createLdapServer.saslHost() ); 143 144 ldapServer.setSaslPrincipal( createLdapServer.saslPrincipal() ); 145 146 if ( !Strings.isEmpty( createLdapServer.keyStore() ) ) 147 { 148 ldapServer.setKeystoreFile( createLdapServer.keyStore() ); 149 ldapServer.setCertificatePassword( createLdapServer.certificatePassword() ); 150 } 151 152 for ( Class<?> extOpClass : createLdapServer.extendedOpHandlers() ) 153 { 154 try 155 { 156 ExtendedOperationHandler extOpHandler = ( ExtendedOperationHandler ) extOpClass.newInstance(); 157 ldapServer.addExtendedOperationHandler( extOpHandler ); 158 } 159 catch ( Exception e ) 160 { 161 throw new RuntimeException( I18n.err( I18n.ERR_690, extOpClass.getName() ), e ); 162 } 163 } 164 165 for ( SaslMechanism saslMech : createLdapServer.saslMechanisms() ) 166 { 167 try 168 { 169 MechanismHandler handler = ( MechanismHandler ) saslMech.implClass().newInstance(); 170 ldapServer.addSaslMechanismHandler( saslMech.name(), handler ); 171 } 172 catch ( Exception e ) 173 { 174 throw new RuntimeException( 175 I18n.err( I18n.ERR_691, saslMech.name(), saslMech.implClass().getName() ), e ); 176 } 177 } 178 179 NtlmMechanismHandler ntlmHandler = ( NtlmMechanismHandler ) ldapServer.getSaslMechanismHandlers().get( 180 SupportedSaslMechanisms.NTLM ); 181 182 if ( ntlmHandler != null ) 183 { 184 Class<?> ntlmProviderClass = createLdapServer.ntlmProvider(); 185 // default value is a invalid Object.class 186 if ( ( ntlmProviderClass != null ) && ( ntlmProviderClass != Object.class ) ) 187 { 188 try 189 { 190 ntlmHandler.setNtlmProvider( ( NtlmProvider ) ntlmProviderClass.newInstance() ); 191 } 192 catch ( Exception e ) 193 { 194 throw new RuntimeException( I18n.err( I18n.ERR_692 ), e ); 195 } 196 } 197 } 198 199 List<String> realms = new ArrayList<String>(); 200 for ( String s : createLdapServer.saslRealms() ) 201 { 202 realms.add( s ); 203 } 204 205 ldapServer.setSaslRealms( realms ); 206 207 return ldapServer; 208 } 209 else 210 { 211 return null; 212 } 213 } 214 215 216 /** 217 * Returns an LdapServer instance and starts it before returning the instance, infering 218 * the configuration from the Stack trace 219 * 220 * @param directoryService the directory service 221 * @return a running LdapServer instance 222 * @throws ClassNotFoundException If the CreateLdapServer class cannot be loaded 223 */ 224 public static LdapServer getLdapServer( DirectoryService directoryService ) throws ClassNotFoundException 225 { 226 Object instance = AnnotationUtils.getInstance( CreateLdapServer.class ); 227 LdapServer ldapServer = null; 228 229 if ( instance != null ) 230 { 231 CreateLdapServer createLdapServer = ( CreateLdapServer ) instance; 232 233 ldapServer = createLdapServer( createLdapServer, directoryService ); 234 } 235 236 return ldapServer; 237 } 238 239 240 /** 241 * Create a replication consumer 242 */ 243 private static ReplicationConsumer createConsumer( CreateConsumer createConsumer ) 244 { 245 ReplicationConsumer consumer = new ReplicationConsumerImpl(); 246 247 SyncReplConfiguration config = new SyncReplConfiguration(); 248 249 String remoteHost = createConsumer.remoteHost(); 250 251 if ( Strings.isEmpty( remoteHost ) ) 252 { 253 remoteHost = Network.LOOPBACK_HOSTNAME; 254 } 255 256 config.setRemoteHost( remoteHost ); 257 config.setRemotePort( createConsumer.remotePort() ); 258 config.setReplUserDn( createConsumer.replUserDn() ); 259 config.setReplUserPassword( Strings.getBytesUtf8( createConsumer.replUserPassword() ) ); 260 config.setUseTls( createConsumer.useTls() ); 261 config.setBaseDn( createConsumer.baseDn() ); 262 config.setRefreshInterval( createConsumer.refreshInterval() ); 263 264 consumer.setConfig( config ); 265 266 return consumer; 267 } 268 269 270 /** 271 * creates an LdapServer and starts before returning the instance, infering 272 * the configuration from the Stack trace 273 * 274 * @return a running LdapServer instance 275 * @throws ClassNotFoundException If the CreateConsumer class cannot be loaded 276 */ 277 public static ReplicationConsumer createConsumer() throws ClassNotFoundException 278 { 279 Object instance = AnnotationUtils.getInstance( CreateConsumer.class ); 280 ReplicationConsumer consumer = null; 281 282 if ( instance != null ) 283 { 284 CreateConsumer createConsumer = ( CreateConsumer ) instance; 285 286 consumer = createConsumer( createConsumer ); 287 } 288 289 return consumer; 290 } 291 292 293 /** 294 * creates an LdapServer and starts before returning the instance 295 * 296 * @param createLdapServer the annotation containing the custom configuration 297 * @param directoryService the directory service 298 * @return a running LdapServer instance 299 */ 300 private static LdapServer createLdapServer( CreateLdapServer createLdapServer, DirectoryService directoryService ) 301 { 302 LdapServer ldapServer = instantiateLdapServer( createLdapServer, directoryService ); 303 304 if ( ldapServer == null ) 305 { 306 return null; 307 } 308 309 // Launch the server 310 try 311 { 312 ldapServer.start(); 313 } 314 catch ( Exception e ) 315 { 316 e.printStackTrace(); 317 } 318 319 return ldapServer; 320 } 321 322 323 /** 324 * Create a new instance of LdapServer 325 * 326 * @param description A description for the created LdapServer 327 * @param directoryService The associated DirectoryService 328 * @return An LdapServer instance 329 */ 330 public static LdapServer createLdapServer( Description description, DirectoryService directoryService ) 331 { 332 CreateLdapServer createLdapServer = description.getAnnotation( CreateLdapServer.class ); 333 334 // Ok, we have found a CreateLdapServer annotation. Process it now. 335 return createLdapServer( createLdapServer, directoryService ); 336 } 337 338 339 @SuppressWarnings("unchecked") 340 private static Annotation getAnnotation( Class annotationClass ) throws Exception 341 { 342 // Get the caller by inspecting the stackTrace 343 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 344 345 // In Java5 the 0th stacktrace element is: java.lang.Thread.dumpThreads(Native Method) 346 int index = stackTrace[0].getMethodName().equals( "dumpThreads" ) ? 4 : 3; 347 348 // Get the enclosing class 349 Class<?> classCaller = Class.forName( stackTrace[index].getClassName() ); 350 351 // Get the current method 352 String methodCaller = stackTrace[index].getMethodName(); 353 354 // Check if we have any annotation associated with the method 355 Method[] methods = classCaller.getMethods(); 356 357 for ( Method method : methods ) 358 { 359 if ( methodCaller.equals( method.getName() ) ) 360 { 361 Annotation annotation = method.getAnnotation( annotationClass ); 362 363 if ( annotation != null ) 364 { 365 return annotation; 366 } 367 } 368 } 369 370 // No : look at the class level 371 return classCaller.getAnnotation( annotationClass ); 372 } 373 374 375 public static KdcServer getKdcServer( DirectoryService directoryService, int startPort ) throws Exception 376 { 377 CreateKdcServer createKdcServer = ( CreateKdcServer ) getAnnotation( CreateKdcServer.class ); 378 return createKdcServer( createKdcServer, directoryService ); 379 } 380 381 382 private static KdcServer createKdcServer( CreateKdcServer createKdcServer, DirectoryService directoryService ) 383 { 384 if ( createKdcServer == null ) 385 { 386 return null; 387 } 388 389 KerberosConfig kdcConfig = new KerberosConfig(); 390 kdcConfig.setServicePrincipal( createKdcServer.kdcPrincipal() ); 391 kdcConfig.setPrimaryRealm( createKdcServer.primaryRealm() ); 392 kdcConfig.setMaximumTicketLifetime( createKdcServer.maxTicketLifetime() ); 393 kdcConfig.setMaximumRenewableLifetime( createKdcServer.maxRenewableLifetime() ); 394 395 KdcServer kdcServer = new KdcServer( kdcConfig ); 396 397 kdcServer.setSearchBaseDn( createKdcServer.searchBaseDn() ); 398 399 CreateTransport[] transportBuilders = createKdcServer.transports(); 400 401 if ( transportBuilders == null ) 402 { 403 // create only UDP transport if none specified 404 int port = 0; 405 try 406 { 407 port = getFreePort(); 408 } 409 catch ( IOException ioe ) 410 { 411 // Don't know what to do here... 412 } 413 UdpTransport defaultTransport = new UdpTransport( port ); 414 kdcServer.addTransports( defaultTransport ); 415 } 416 else if ( transportBuilders.length > 0 ) 417 { 418 for ( CreateTransport transportBuilder : transportBuilders ) 419 { 420 List< Transport > transports = createTransports( transportBuilder ); 421 for ( Transport t : transports ) 422 { 423 kdcServer.addTransports( t ); 424 } 425 } 426 } 427 428 CreateChngPwdServer[] createChngPwdServers = createKdcServer.chngPwdServer(); 429 430 if ( createChngPwdServers.length > 0 ) 431 { 432 433 CreateChngPwdServer createChngPwdServer = createChngPwdServers[0]; 434 ChangePasswordConfig config = new ChangePasswordConfig( kdcConfig ); 435 config.setServicePrincipal( createChngPwdServer.srvPrincipal() ); 436 437 ChangePasswordServer chngPwdServer = new ChangePasswordServer( config ); 438 439 for ( CreateTransport transportBuilder : createChngPwdServer.transports() ) 440 { 441 List< Transport > transports = createTransports( transportBuilder ); 442 for ( Transport t : transports ) 443 { 444 chngPwdServer.addTransports( t ); 445 } 446 } 447 448 chngPwdServer.setDirectoryService( directoryService ); 449 450 kdcServer.setChangePwdServer( chngPwdServer ); 451 } 452 453 kdcServer.setDirectoryService( directoryService ); 454 455 // Launch the server 456 try 457 { 458 kdcServer.start(); 459 } 460 catch ( Exception e ) 461 { 462 e.printStackTrace(); 463 } 464 465 return kdcServer; 466 } 467 468 469 private static List< Transport > createTransports( CreateTransport transportBuilder ) 470 { 471 String protocol = transportBuilder.protocol(); 472 int port = transportBuilder.port(); 473 int nbThreads = transportBuilder.nbThreads(); 474 int backlog = transportBuilder.backlog(); 475 String address = transportBuilder.address(); 476 477 if ( Strings.isEmpty( address ) ) 478 { 479 address = Network.LOOPBACK_HOSTNAME; 480 } 481 482 if ( port <= 0 ) 483 { 484 try 485 { 486 port = getFreePort(); 487 } 488 catch ( IOException ioe ) 489 { 490 // Don't know what to do here... 491 } 492 } 493 494 if ( protocol.equalsIgnoreCase( "TCP" ) || protocol.equalsIgnoreCase( "LDAP" ) ) 495 { 496 Transport tcp = new TcpTransport( address, port, nbThreads, backlog ); 497 return Collections.singletonList( tcp ); 498 } 499 else if ( protocol.equalsIgnoreCase( "LDAPS" ) ) 500 { 501 Transport tcp = new TcpTransport( address, port, nbThreads, backlog ); 502 tcp.setEnableSSL( true ); 503 return Collections.singletonList( tcp ); 504 } 505 else if ( protocol.equalsIgnoreCase( "UDP" ) ) 506 { 507 Transport udp = new UdpTransport( address, port ); 508 return Collections.singletonList( udp ); 509 } 510 else if ( protocol.equalsIgnoreCase( "KRB" ) || protocol.equalsIgnoreCase( "CPW" ) ) 511 { 512 Transport tcp = new TcpTransport( address, port, nbThreads, backlog ); 513 List< Transport > transports = new ArrayList< Transport >(); 514 transports.add( tcp ); 515 516 Transport udp = new UdpTransport( address, port ); 517 transports.add( udp ); 518 return transports; 519 } 520 521 throw new IllegalArgumentException( I18n.err( I18n.ERR_689, protocol ) ); 522 } 523 524 private static int getFreePort() throws IOException 525 { 526 ServerSocket ss = new ServerSocket( 0 ); 527 int port = ss.getLocalPort(); 528 ss.close(); 529 530 return port; 531 } 532 533 public static KdcServer getKdcServer( Description description, DirectoryService directoryService, int startPort ) 534 throws Exception 535 { 536 CreateKdcServer createLdapServer = description.getAnnotation( CreateKdcServer.class ); 537 538 return createKdcServer( createLdapServer, directoryService ); 539 } 540 541}