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 * 019 */ 020 021package org.apache.directory.server.integration.http; 022 023 024import java.io.ByteArrayInputStream; 025import java.io.File; 026import java.io.FilenameFilter; 027import java.io.InputStream; 028import java.io.OutputStream; 029import java.nio.file.Files; 030import java.nio.file.Paths; 031import java.security.KeyPair; 032import java.security.KeyStore; 033import java.security.cert.Certificate; 034import java.security.cert.X509Certificate; 035import java.util.Set; 036import java.util.UUID; 037 038import org.apache.directory.api.ldap.model.constants.SchemaConstants; 039import org.apache.directory.api.ldap.model.entry.Entry; 040import org.apache.directory.api.ldap.model.name.Dn; 041import org.apache.directory.server.bridge.http.HttpDirectoryService; 042import org.apache.directory.server.constants.ServerDNConstants; 043import org.apache.directory.server.core.api.DirectoryService; 044import org.apache.directory.server.core.security.TlsKeyGenerator; 045import org.apache.directory.server.i18n.I18n; 046import org.apache.directory.server.protocol.shared.transport.TcpTransport; 047import org.bouncycastle.jce.provider.X509CertParser; 048import org.eclipse.jetty.server.Handler; 049import org.eclipse.jetty.server.HttpConfiguration; 050import org.eclipse.jetty.server.HttpConnectionFactory; 051import org.eclipse.jetty.server.SecureRequestCustomizer; 052import org.eclipse.jetty.server.Server; 053import org.eclipse.jetty.server.ServerConnector; 054import org.eclipse.jetty.server.SslConnectionFactory; 055import org.eclipse.jetty.server.handler.ContextHandler; 056import org.eclipse.jetty.server.handler.HandlerList; 057import org.eclipse.jetty.util.ssl.SslContextFactory; 058import org.eclipse.jetty.webapp.WebAppContext; 059import org.eclipse.jetty.xml.XmlConfiguration; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063 064/** 065 * Class to start the jetty http server 066 * 067 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 068 */ 069public class HttpServer 070{ 071 072 /** the jetty http server instance */ 073 private Server jetty; 074 075 /** jetty config file */ 076 private String confFile; 077 078 /** a collection to hold the configured web applications */ 079 private Set<WebApp> webApps; 080 081 /** Transport for http */ 082 private TcpTransport httpTransport = null; 083 084 /** Transport for https */ 085 private TcpTransport httpsTransport = null; 086 087 /** protocol identifier for http */ 088 public static final String HTTP_TRANSPORT_ID = "http"; 089 090 /** protocol identifier for https */ 091 public static final String HTTPS_TRANSPORT_ID = "https"; 092 093 /** an internal flag to check the server configuration */ 094 private boolean configured = false; 095 096 private static final Logger LOG = LoggerFactory.getLogger( HttpServer.class ); 097 098 private DirectoryService dirService; 099 100 101 public HttpServer() 102 { 103 } 104 105 106 /** 107 * starts the jetty http server 108 * 109 * @param dirService The DirectoryService instance 110 * @throws Exception If Jetty can't be started 111 */ 112 public void start( DirectoryService dirService ) throws Exception 113 { 114 this.dirService = dirService; 115 116 XmlConfiguration jettyConf = null; 117 118 if ( confFile != null ) 119 { 120 InputStream input = Files.newInputStream( Paths.get( confFile ) ); 121 jettyConf = new XmlConfiguration( input ); 122 123 LOG.info( "configuring jetty http server from the configuration file {}", confFile ); 124 125 try 126 { 127 jetty = new Server(); 128 jettyConf.configure( jetty ); 129 configured = true; 130 } 131 catch ( Exception e ) 132 { 133 LOG.error( I18n.err( I18n.ERR_120 ) ); 134 throw e; 135 } 136 } 137 else 138 { 139 LOG.info( "No configuration file set, looking for web apps" ); 140 configureServerThroughCode(); 141 } 142 143 if ( configured ) 144 { 145 Handler[] handlers = jetty.getHandlers(); 146 147 for ( Handler h : handlers ) 148 { 149 if ( h instanceof ContextHandler ) 150 { 151 ContextHandler ch = ( ContextHandler ) h; 152 ch.setAttribute( HttpDirectoryService.KEY, new HttpDirectoryService( dirService ) ); 153 } 154 } 155 156 LOG.info( "starting jetty http server" ); 157 jetty.start(); 158 } 159 else 160 { 161 jetty = null; 162 LOG.warn( "Error while configuring the http server, skipping the http server startup" ); 163 } 164 } 165 166 167 /* 168 * configure the jetty server programmatically without using any configuration file 169 */ 170 private void configureServerThroughCode() 171 { 172 try 173 { 174 jetty = new Server(); 175 176 if ( httpTransport != null ) 177 { 178 ServerConnector httpConnector = new ServerConnector( jetty ); 179 httpConnector.setPort( httpTransport.getPort() ); 180 httpConnector.setHost( httpTransport.getAddress() ); 181 jetty.addConnector( httpConnector ); 182 } 183 184 if ( httpsTransport != null ) 185 { 186 // load the admin entry to get the private key and certificate 187 Dn adminDn = dirService.getDnFactory().create( ServerDNConstants.ADMIN_SYSTEM_DN ); 188 Entry adminEntry = dirService.getAdminSession().lookup( adminDn, SchemaConstants.ALL_USER_ATTRIBUTES, 189 SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ); 190 191 File confDir = dirService.getInstanceLayout().getConfDirectory(); 192 File ksFile = new File( confDir, "httpserver.generated.ks" ); 193 194 String password = UUID.randomUUID().toString(); 195 196 KeyStore ks = KeyStore.getInstance( KeyStore.getDefaultType() ); 197 ks.load( null, null ); 198 199 X509CertParser parser = new X509CertParser(); 200 201 parser.engineInit( new ByteArrayInputStream( adminEntry.get( TlsKeyGenerator.USER_CERTIFICATE_AT ) 202 .getBytes() ) ); 203 204 X509Certificate cert = ( X509Certificate ) parser.engineRead(); 205 206 ks.setCertificateEntry( "cert", cert ); 207 208 KeyPair keyPair = TlsKeyGenerator.getKeyPair( adminEntry ); 209 ks.setKeyEntry( "privatekey", keyPair.getPrivate(), password.toCharArray(), new Certificate[] 210 { cert } ); 211 212 try ( OutputStream stream = Files.newOutputStream( ksFile.toPath() ) ) 213 { 214 ks.store( stream, password.toCharArray() ); 215 } 216 217 SslContextFactory sslContextFactory = new SslContextFactory(); 218 sslContextFactory.setKeyStoreType( KeyStore.getDefaultType() ); 219 sslContextFactory.setKeyStorePath( ksFile.getAbsolutePath() ); 220 sslContextFactory.setKeyStorePassword( password ); 221 sslContextFactory.setKeyManagerPassword( password ); 222 223 HttpConfiguration httpsConfiguration = new HttpConfiguration(); 224 httpsConfiguration.setSecureScheme( HTTPS_TRANSPORT_ID ); 225 httpsConfiguration.setSecurePort( httpsTransport.getPort() ); 226 httpsConfiguration.addCustomizer( new SecureRequestCustomizer() ); 227 228 ServerConnector httpsConnector = new ServerConnector( jetty, new SslConnectionFactory( sslContextFactory, "http/1.1" ), new HttpConnectionFactory( httpsConfiguration ) ); 229 httpsConnector.setPort( httpsTransport.getPort() ); 230 httpsConnector.setHost( httpsTransport.getAddress() ); 231 232 jetty.addConnector( httpsConnector ); 233 } 234 235 HandlerList handlers = new HandlerList(); 236 for ( WebApp w : webApps ) 237 { 238 WebAppContext webapp = new WebAppContext(); 239 webapp.setWar( w.getWarFile() ); 240 webapp.setContextPath( w.getContextPath() ); 241 handlers.addHandler( webapp ); 242 243 webapp.setParentLoaderPriority( true ); 244 } 245 246 // add web apps from the webapps directory inside directory service's working directory 247 // the exploded or archived wars 248 File webAppDir = new File( dirService.getInstanceLayout().getInstanceDirectory(), "webapps" ); 249 250 FilenameFilter webAppFilter = new FilenameFilter() 251 { 252 253 public boolean accept( File dir, String name ) 254 { 255 return name.endsWith( ".war" ); 256 } 257 }; 258 259 if ( webAppDir.exists() ) 260 { 261 File[] appList = webAppDir.listFiles( webAppFilter ); 262 for ( File app : appList ) 263 { 264 WebAppContext webapp = new WebAppContext(); 265 webapp.setWar( app.getAbsolutePath() ); 266 String ctxName = app.getName(); 267 int pos = ctxName.indexOf( '.' ); 268 if ( pos > 0 ) 269 { 270 ctxName = ctxName.substring( 0, pos ); 271 } 272 273 webapp.setContextPath( "/" + ctxName ); 274 handlers.addHandler( webapp ); 275 276 webapp.setParentLoaderPriority( true ); 277 } 278 } 279 280 jetty.setHandler( handlers ); 281 282 configured = true; 283 } 284 catch ( Exception e ) 285 { 286 LOG.error( I18n.err( I18n.ERR_121 ), e ); 287 } 288 289 } 290 291 292 /** 293 * stops the jetty http server 294 * 295 * @throws Exception If Jetty can't be stopped 296 */ 297 public void stop() throws Exception 298 { 299 if ( jetty != null && jetty.isStarted() ) 300 { 301 LOG.info( "stopping jetty http server" ); 302 jetty.stop(); 303 } 304 } 305 306 307 public void setConfFile( String confFile ) 308 { 309 this.confFile = confFile; 310 } 311 312 313 public Set<WebApp> getWebApps() 314 { 315 return webApps; 316 } 317 318 319 public void setWebApps( Set<WebApp> webapps ) 320 { 321 this.webApps = webapps; 322 } 323 324 325 public TcpTransport getHttpTransport() 326 { 327 return httpTransport; 328 } 329 330 331 public void setHttpTransport( TcpTransport httpTransport ) 332 { 333 this.httpTransport = httpTransport; 334 } 335 336 337 public TcpTransport getHttpsTransport() 338 { 339 return httpsTransport; 340 } 341 342 343 public void setHttpsTransport( TcpTransport httpsTransport ) 344 { 345 this.httpsTransport = httpsTransport; 346 } 347 348}