1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.catalina.core;
19
20
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.apache.catalina.Lifecycle;
27 import org.apache.catalina.LifecycleEvent;
28 import org.apache.catalina.LifecycleListener;
29 import org.apache.juli.logging.Log;
30 import org.apache.juli.logging.LogFactory;
31 import org.apache.tomcat.jni.Library;
32 import org.apache.tomcat.jni.LibraryNotFoundError;
33 import org.apache.tomcat.jni.SSL;
34 import org.apache.tomcat.util.ExceptionUtils;
35 import org.apache.tomcat.util.res.StringManager;
36
37
38
39 /**
40  * Implementation of <code>LifecycleListener</code> that will init and
41  * and destroy APR.
42  *
43  * @since 4.1
44  */

45 public class AprLifecycleListener
46     implements LifecycleListener {
47
48     private static final Log log = LogFactory.getLog(AprLifecycleListener.class);
49     private static boolean instanceCreated = false;
50     /**
51      * Info messages during init() are cached until Lifecycle.BEFORE_INIT_EVENT
52      * so that, in normal (non-error) cases, init() related log messages appear
53      * at the expected point in the lifecycle.
54      */

55     private static final List<String> initInfoLogMessages = new ArrayList<>(3);
56
57     /**
58      * The string manager for this package.
59      */

60     protected static final StringManager sm =
61         StringManager.getManager(Constants.Package);
62
63
64     // ---------------------------------------------- Constants
65
66
67     protected static final int TCN_REQUIRED_MAJOR = 1;
68     protected static final int TCN_REQUIRED_MINOR = 2;
69     protected static final int TCN_REQUIRED_PATCH = 14;
70     protected static final int TCN_RECOMMENDED_MINOR = 2;
71     protected static final int TCN_RECOMMENDED_PV = 23;
72
73
74     // ---------------------------------------------- Properties
75     protected static String SSLEngine = "on"//default on
76     protected static String FIPSMode = "off"// default off, valid only when SSLEngine="on"
77     protected static String SSLRandomSeed = "builtin";
78     protected static boolean sslInitialized = false;
79     protected static boolean aprInitialized = false;
80     protected static boolean aprAvailable = false;
81     protected static boolean useAprConnector = false;
82     protected static boolean useOpenSSL = true;
83     protected static boolean fipsModeActive = false;
84
85     /**
86      * The "FIPS mode" level that we use as the argument to OpenSSL method
87      * <code>FIPS_mode_set()</code> to enable FIPS mode and that we expect as
88      * the return value of <code>FIPS_mode()</code> when FIPS mode is enabled.
89      * <p>
90      * In the future the OpenSSL library might grow support for different
91      * non-zero "FIPS" modes that specify different allowed subsets of ciphers
92      * or whatever, but nowadays only "1" is the supported value.
93      * </p>
94      * @see <a href="http://wiki.openssl.org/index.php/FIPS_mode_set%28%29">OpenSSL method FIPS_mode_set()</a>
95      * @see <a href="http://wiki.openssl.org/index.php/FIPS_mode%28%29">OpenSSL method FIPS_mode()</a>
96      */

97     private static final int FIPS_ON = 1;
98
99     private static final int FIPS_OFF = 0;
100
101     protected static final Object lock = new Object();
102
103     public static boolean isAprAvailable() {
104         //https://bz.apache.org/bugzilla/show_bug.cgi?id=48613
105         if (instanceCreated) {
106             synchronized (lock) {
107                 init();
108             }
109         }
110         return aprAvailable;
111     }
112
113     public AprLifecycleListener() {
114         instanceCreated = true;
115     }
116
117     // ---------------------------------------------- LifecycleListener Methods
118
119     /**
120      * Primary entry point for startup and shutdown events.
121      *
122      * @param event The event that has occurred
123      */

124     @Override
125     public void lifecycleEvent(LifecycleEvent event) {
126
127         if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
128             synchronized (lock) {
129                 init();
130                 for (String msg : initInfoLogMessages) {
131                     log.info(msg);
132                 }
133                 initInfoLogMessages.clear();
134                 if (aprAvailable) {
135                     try {
136                         initializeSSL();
137                     } catch (Throwable t) {
138                         t = ExceptionUtils.unwrapInvocationTargetException(t);
139                         ExceptionUtils.handleThrowable(t);
140                         log.error(sm.getString("aprListener.sslInit"), t);
141                     }
142                 }
143                 // Failure to initialize FIPS mode is fatal
144                 if (!(null == FIPSMode || "off".equalsIgnoreCase(FIPSMode)) && !isFIPSModeActive()) {
145                     String errorMessage = sm.getString("aprListener.initializeFIPSFailed");
146                     Error e = new Error(errorMessage);
147                     // Log here, because thrown error might be not logged
148                     log.fatal(errorMessage, e);
149                     throw e;
150                 }
151             }
152         } else if (Lifecycle.AFTER_DESTROY_EVENT.equals(event.getType())) {
153             synchronized (lock) {
154                 if (!aprAvailable) {
155                     return;
156                 }
157                 try {
158                     terminateAPR();
159                 } catch (Throwable t) {
160                     t = ExceptionUtils.unwrapInvocationTargetException(t);
161                     ExceptionUtils.handleThrowable(t);
162                     log.info(sm.getString("aprListener.aprDestroy"));
163                 }
164             }
165         }
166
167     }
168
169     private static void terminateAPR()
170         throws ClassNotFoundException, NoSuchMethodException,
171                IllegalAccessException, InvocationTargetException
172     {
173         String methodName = "terminate";
174         Method method = Class.forName("org.apache.tomcat.jni.Library")
175             .getMethod(methodName, (Class [])null);
176         method.invoke(null, (Object []) null);
177         aprAvailable = false;
178         aprInitialized = false;
179         sslInitialized = false// Well we cleaned the pool in terminate.
180         fipsModeActive = false;
181     }
182
183     private static void init()
184     {
185         int major = 0;
186         int minor = 0;
187         int patch = 0;
188         int apver = 0;
189         int rqver = TCN_REQUIRED_MAJOR * 1000 + TCN_REQUIRED_MINOR * 100 + TCN_REQUIRED_PATCH;
190         int rcver = TCN_REQUIRED_MAJOR * 1000 + TCN_RECOMMENDED_MINOR * 100 + TCN_RECOMMENDED_PV;
191
192         if (aprInitialized) {
193             return;
194         }
195         aprInitialized = true;
196
197         try {
198             Library.initialize(null);
199             major = Library.TCN_MAJOR_VERSION;
200             minor = Library.TCN_MINOR_VERSION;
201             patch = Library.TCN_PATCH_VERSION;
202             apver = major * 1000 + minor * 100 + patch;
203         } catch (LibraryNotFoundError lnfe) {
204             // Library not on path
205             if (log.isDebugEnabled()) {
206                 log.debug(sm.getString("aprListener.aprInitDebug",
207                         lnfe.getLibraryNames(), System.getProperty("java.library.path"),
208                         lnfe.getMessage()), lnfe);
209             }
210             initInfoLogMessages.add(sm.getString("aprListener.aprInit",
211                     System.getProperty("java.library.path")));
212             return;
213         } catch (Throwable t) {
214             // Library present but failed to load
215             t = ExceptionUtils.unwrapInvocationTargetException(t);
216             ExceptionUtils.handleThrowable(t);
217             log.warn(sm.getString("aprListener.aprInitError", t.getMessage()), t);
218             return;
219         }
220         if (apver < rqver) {
221             log.error(sm.getString("aprListener.tcnInvalid", major + "."
222                     + minor + "." + patch,
223                     TCN_REQUIRED_MAJOR + "." +
224                     TCN_REQUIRED_MINOR + "." +
225                     TCN_REQUIRED_PATCH));
226             try {
227                 // Terminate the APR in case the version
228                 // is below required.
229                 terminateAPR();
230             } catch (Throwable t) {
231                 t = ExceptionUtils.unwrapInvocationTargetException(t);
232                 ExceptionUtils.handleThrowable(t);
233             }
234             return;
235         }
236         if (apver < rcver) {
237             initInfoLogMessages.add(sm.getString("aprListener.tcnVersion",
238                     major + "." + minor + "." + patch,
239                     TCN_REQUIRED_MAJOR + "." +
240                     TCN_RECOMMENDED_MINOR + "." +
241                     TCN_RECOMMENDED_PV));
242         }
243
244         initInfoLogMessages.add(sm.getString("aprListener.tcnValid",
245                 major + "." + minor + "." + patch,
246                 Library.APR_MAJOR_VERSION + "." +
247                 Library.APR_MINOR_VERSION + "." +
248                 Library.APR_PATCH_VERSION));
249
250         // Log APR flags
251         initInfoLogMessages.add(sm.getString("aprListener.flags",
252                 Boolean.valueOf(Library.APR_HAVE_IPV6),
253                 Boolean.valueOf(Library.APR_HAS_SENDFILE),
254                 Boolean.valueOf(Library.APR_HAS_SO_ACCEPTFILTER),
255                 Boolean.valueOf(Library.APR_HAS_RANDOM)));
256
257         initInfoLogMessages.add(sm.getString("aprListener.config",
258                 Boolean.valueOf(useAprConnector),
259                 Boolean.valueOf(useOpenSSL)));
260
261         aprAvailable = true;
262     }
263
264     private static void initializeSSL() throws Exception {
265
266         if ("off".equalsIgnoreCase(SSLEngine)) {
267             return;
268         }
269         if (sslInitialized) {
270              //only once per VM
271             return;
272         }
273
274         sslInitialized = true;
275
276         String methodName = "randSet";
277         Class<?> paramTypes[] = new Class[1];
278         paramTypes[0] = String.class;
279         Object paramValues[] = new Object[1];
280         paramValues[0] = SSLRandomSeed;
281         Class<?> clazz = Class.forName("org.apache.tomcat.jni.SSL");
282         Method method = clazz.getMethod(methodName, paramTypes);
283         method.invoke(null, paramValues);
284
285
286         methodName = "initialize";
287         paramValues[0] = "on".equalsIgnoreCase(SSLEngine)?null:SSLEngine;
288         method = clazz.getMethod(methodName, paramTypes);
289         method.invoke(null, paramValues);
290
291         if (!(null == FIPSMode || "off".equalsIgnoreCase(FIPSMode))) {
292
293             fipsModeActive = false;
294
295             final boolean enterFipsMode;
296             int fipsModeState = SSL.fipsModeGet();
297
298             if(log.isDebugEnabled()) {
299                 log.debug(sm.getString("aprListener.currentFIPSMode",
300                                        Integer.valueOf(fipsModeState)));
301             }
302
303             if ("on".equalsIgnoreCase(FIPSMode)) {
304                 if (fipsModeState == FIPS_ON) {
305                     log.info(sm.getString("aprListener.skipFIPSInitialization"));
306                     fipsModeActive = true;
307                     enterFipsMode = false;
308                 } else {
309                     enterFipsMode = true;
310                 }
311             } else if ("require".equalsIgnoreCase(FIPSMode)) {
312                 if (fipsModeState == FIPS_ON) {
313                     fipsModeActive = true;
314                     enterFipsMode = false;
315                 } else {
316                     throw new IllegalStateException(
317                             sm.getString("aprListener.requireNotInFIPSMode"));
318                 }
319             } else if ("enter".equalsIgnoreCase(FIPSMode)) {
320                 if (fipsModeState == FIPS_OFF) {
321                     enterFipsMode = true;
322                 } else {
323                     throw new IllegalStateException(sm.getString(
324                             "aprListener.enterAlreadyInFIPSMode",
325                             Integer.valueOf(fipsModeState)));
326                 }
327             } else {
328                 throw new IllegalArgumentException(sm.getString(
329                         "aprListener.wrongFIPSMode", FIPSMode));
330             }
331
332             if (enterFipsMode) {
333                 log.info(sm.getString("aprListener.initializingFIPS"));
334
335                 fipsModeState = SSL.fipsModeSet(FIPS_ON);
336                 if (fipsModeState != FIPS_ON) {
337                     // This case should be handled by the native method,
338                     // but we'll make absolutely sure, here.
339                     String message = sm.getString("aprListener.initializeFIPSFailed");
340                     log.error(message);
341                     throw new IllegalStateException(message);
342                 }
343
344                 fipsModeActive = true;
345                 log.info(sm.getString("aprListener.initializeFIPSSuccess"));
346             }
347         }
348
349         log.info(sm.getString("aprListener.initializedOpenSSL", SSL.versionString()));
350     }
351
352     public String getSSLEngine() {
353         return SSLEngine;
354     }
355
356     public void setSSLEngine(String SSLEngine) {
357         if (!SSLEngine.equals(AprLifecycleListener.SSLEngine)) {
358             // Ensure that the SSLEngine is consistent with that used for SSL init
359             if (sslInitialized) {
360                 throw new IllegalStateException(
361                         sm.getString("aprListener.tooLateForSSLEngine"));
362             }
363
364             AprLifecycleListener.SSLEngine = SSLEngine;
365         }
366     }
367
368     public String getSSLRandomSeed() {
369         return SSLRandomSeed;
370     }
371
372     public void setSSLRandomSeed(String SSLRandomSeed) {
373         if (!SSLRandomSeed.equals(AprLifecycleListener.SSLRandomSeed)) {
374             // Ensure that the random seed is consistent with that used for SSL init
375             if (sslInitialized) {
376                 throw new IllegalStateException(
377                         sm.getString("aprListener.tooLateForSSLRandomSeed"));
378             }
379
380             AprLifecycleListener.SSLRandomSeed = SSLRandomSeed;
381         }
382     }
383
384     public String getFIPSMode() {
385         return FIPSMode;
386     }
387
388     public void setFIPSMode(String FIPSMode) {
389         if (!FIPSMode.equals(AprLifecycleListener.FIPSMode)) {
390             // Ensure that the FIPS mode is consistent with that used for SSL init
391             if (sslInitialized) {
392                 throw new IllegalStateException(
393                         sm.getString("aprListener.tooLateForFIPSMode"));
394             }
395
396             AprLifecycleListener.FIPSMode = FIPSMode;
397         }
398     }
399
400     public boolean isFIPSModeActive() {
401         return fipsModeActive;
402     }
403
404     public void setUseAprConnector(boolean useAprConnector) {
405         if (useAprConnector != AprLifecycleListener.useAprConnector) {
406             AprLifecycleListener.useAprConnector = useAprConnector;
407         }
408     }
409
410     public static boolean getUseAprConnector() {
411         return useAprConnector;
412     }
413
414     public void setUseOpenSSL(boolean useOpenSSL) {
415         if (useOpenSSL != AprLifecycleListener.useOpenSSL) {
416             AprLifecycleListener.useOpenSSL = useOpenSSL;
417         }
418     }
419
420     public static boolean getUseOpenSSL() {
421         return useOpenSSL;
422     }
423
424     public static boolean isInstanceCreated() {
425         return instanceCreated;
426     }
427
428 }
429