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