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}