View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  
21  package org.apache.directory.server.integration.http;
22  
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.File;
26  import java.io.FilenameFilter;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.nio.file.Files;
30  import java.nio.file.Paths;
31  import java.security.KeyPair;
32  import java.security.KeyStore;
33  import java.security.cert.Certificate;
34  import java.security.cert.X509Certificate;
35  import java.util.Set;
36  import java.util.UUID;
37  
38  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
39  import org.apache.directory.api.ldap.model.entry.Entry;
40  import org.apache.directory.api.ldap.model.name.Dn;
41  import org.apache.directory.server.bridge.http.HttpDirectoryService;
42  import org.apache.directory.server.constants.ServerDNConstants;
43  import org.apache.directory.server.core.api.DirectoryService;
44  import org.apache.directory.server.core.security.TlsKeyGenerator;
45  import org.apache.directory.server.i18n.I18n;
46  import org.apache.directory.server.protocol.shared.transport.TcpTransport;
47  import org.bouncycastle.jce.provider.X509CertParser;
48  import org.eclipse.jetty.server.Handler;
49  import org.eclipse.jetty.server.HttpConfiguration;
50  import org.eclipse.jetty.server.HttpConnectionFactory;
51  import org.eclipse.jetty.server.SecureRequestCustomizer;
52  import org.eclipse.jetty.server.Server;
53  import org.eclipse.jetty.server.ServerConnector;
54  import org.eclipse.jetty.server.SslConnectionFactory;
55  import org.eclipse.jetty.server.handler.ContextHandler;
56  import org.eclipse.jetty.server.handler.HandlerList;
57  import org.eclipse.jetty.util.ssl.SslContextFactory;
58  import org.eclipse.jetty.webapp.WebAppContext;
59  import org.eclipse.jetty.xml.XmlConfiguration;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  
64  /**
65   * Class to start the jetty http server
66   * 
67   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
68   */
69  public class HttpServer
70  {
71  
72      /** the jetty http server instance */
73      private Server jetty;
74  
75      /** jetty config file */
76      private String confFile;
77  
78      /** a collection to hold the configured web applications */
79      private Set<WebApp> webApps;
80  
81      /** Transport for http */
82      private TcpTransport httpTransport = null;
83  
84      /** Transport for https */
85      private TcpTransport httpsTransport = null;
86  
87      /** protocol identifier for http */
88      public static final String HTTP_TRANSPORT_ID = "http";
89  
90      /** protocol identifier for https */
91      public static final String HTTPS_TRANSPORT_ID = "https";
92  
93      /** an internal flag to check the server configuration */
94      private boolean configured = false;
95  
96      private static final Logger LOG = LoggerFactory.getLogger( HttpServer.class );
97  
98      private DirectoryService dirService;
99  
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 }