Skip to content

Commit 6c55a57

Browse files
committed
Enhance version.sh and version.bat output to display APR, OpenSSL, and third-party library versions
1 parent b7d5660 commit 6c55a57

File tree

8 files changed

+706
-2
lines changed

8 files changed

+706
-2
lines changed

bin/catalina.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ set CATALINA_OPTS=
305305
goto execCmd
306306

307307
:doVersion
308-
%_EXECJAVA% %JAVA_OPTS% -classpath "%CATALINA_HOME%\lib\catalina.jar" org.apache.catalina.util.ServerInfo
308+
%_EXECJAVA% %JAVA_OPTS% -classpath "%CATALINA_HOME%\bin\tomcat-juli.jar;%CATALINA_HOME%\lib\*" -Dcatalina.home="%CATALINA_HOME%" -Dcatalina.base="%CATALINA_BASE%" org.apache.catalina.util.ServerInfo
309309
goto end
310310

311311

bin/catalina.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,9 @@ elif [ "$1" = "configtest" ] ; then
564564
elif [ "$1" = "version" ] ; then
565565

566566
eval "\"$_RUNJAVA\"" "$JAVA_OPTS" \
567-
-classpath "\"$CATALINA_HOME/lib/catalina.jar\"" \
567+
-classpath "\"$CATALINA_HOME/bin/tomcat-juli.jar:$CATALINA_HOME/lib/*\"" \
568+
-Dcatalina.home="\"$CATALINA_HOME\"" \
569+
-Dcatalina.base="\"$CATALINA_BASE\"" \
568570
org.apache.catalina.util.ServerInfo
569571

570572
else

java/org/apache/catalina/core/AprLifecycleListener.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,96 @@ public static boolean isAprAvailable() {
119119
return org.apache.tomcat.jni.AprStatus.isAprAvailable();
120120
}
121121

122+
/**
123+
* Helper method to safely get a version string from APR/TCN.
124+
* Checks APR availability and handles exceptions.
125+
*
126+
* @param versionSupplier supplier that returns the version string
127+
* @return the version string, or null if APR is not available or an error occurs
128+
*/
129+
private static String getVersionString(java.util.function.Supplier<String> versionSupplier) {
130+
if (!isAprAvailable()) {
131+
return null;
132+
}
133+
134+
try {
135+
return versionSupplier.get();
136+
} catch (Exception e) {
137+
return null;
138+
}
139+
}
140+
141+
/**
142+
* Get the installed Tomcat Native version string, if available.
143+
*
144+
* @return the version string, or null if APR is not available
145+
*/
146+
public static String getInstalledTcnVersion() {
147+
return getVersionString(org.apache.tomcat.jni.Library::versionString);
148+
}
149+
150+
/**
151+
* Get the installed APR version string, if available.
152+
*
153+
* @return the APR version string, or null if APR is not available
154+
*/
155+
public static String getInstalledAprVersion() {
156+
return getVersionString(org.apache.tomcat.jni.Library::aprVersionString);
157+
}
158+
159+
/**
160+
* Get the installed OpenSSL version string (via APR), if available.
161+
*
162+
* @return the OpenSSL version string, or null if not available
163+
*/
164+
public static String getInstalledOpenSslVersion() {
165+
return getVersionString(org.apache.tomcat.jni.SSL::versionString);
166+
}
167+
168+
/**
169+
* Helper method to convert version components to a comparable integer.
170+
*
171+
* @param major major version number
172+
* @param minor minor version number
173+
* @param patch patch version number
174+
*
175+
* @return comparable version integer
176+
*/
177+
private static int versionToInt(int major, int minor, int patch) {
178+
return major * 1000 + minor * 100 + patch;
179+
}
180+
181+
/**
182+
* Get a warning message if the installed Tomcat Native version is older than recommended.
183+
* This performs the same version check used during Tomcat startup.
184+
*
185+
* @return a warning message if the installed version is outdated, or null if the version
186+
* is acceptable or APR is not available
187+
*/
188+
public static String getTcnVersionWarning() {
189+
if (!isAprAvailable()) {
190+
return null;
191+
}
192+
193+
try {
194+
int installedVersion = versionToInt(
195+
org.apache.tomcat.jni.Library.TCN_MAJOR_VERSION,
196+
org.apache.tomcat.jni.Library.TCN_MINOR_VERSION,
197+
org.apache.tomcat.jni.Library.TCN_PATCH_VERSION);
198+
int recommendedVersion = versionToInt(
199+
TCN_RECOMMENDED_MAJOR,
200+
TCN_RECOMMENDED_MINOR,
201+
TCN_RECOMMENDED_PV);
202+
if (installedVersion < recommendedVersion) {
203+
return "WARNING: Tomcat recommends a minimum version of " +
204+
TCN_RECOMMENDED_MAJOR + "." + TCN_RECOMMENDED_MINOR + "." + TCN_RECOMMENDED_PV;
205+
}
206+
return null;
207+
} catch (Exception e) {
208+
return null;
209+
}
210+
}
211+
122212
public AprLifecycleListener() {
123213
org.apache.tomcat.jni.AprStatus.setInstanceCreated(true);
124214
}

java/org/apache/catalina/core/OpenSSLLifecycleListener.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ public static boolean isAvailable() {
6767
return OpenSSLStatus.isAvailable();
6868
}
6969

70+
/**
71+
* Get the installed OpenSSL version string (via FFM), if available.
72+
*
73+
* @return the OpenSSL version string (e.g., "OpenSSL 3.2.6 30 Sep 2025"), or null if not available
74+
*/
75+
public static String getInstalledOpenSslVersion() {
76+
if (!isAvailable()) {
77+
return null;
78+
}
79+
80+
if (JreCompat.isJre22Available()) {
81+
try {
82+
Class<?> openSSLLibraryClass =
83+
Class.forName("org.apache.tomcat.util.net.openssl.panama.OpenSSLLibrary");
84+
return (String) openSSLLibraryClass.getMethod("getVersionString").invoke(null);
85+
} catch (Throwable t) {
86+
Throwable throwable = ExceptionUtils.unwrapInvocationTargetException(t);
87+
ExceptionUtils.handleThrowable(throwable);
88+
}
89+
}
90+
return null;
91+
}
92+
7093
public OpenSSLLifecycleListener() {
7194
OpenSSLStatus.setInstanceCreated(true);
7295
}

java/org/apache/catalina/util/ServerInfo.java

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@
1717
package org.apache.catalina.util;
1818

1919

20+
import java.io.File;
2021
import java.io.InputStream;
22+
import java.util.ArrayList;
23+
import java.util.List;
2124
import java.util.Properties;
25+
import java.util.jar.JarFile;
26+
import java.util.jar.Manifest;
2227

2328
import org.apache.tomcat.util.ExceptionUtils;
2429

@@ -121,6 +126,10 @@ public static String getServerNumber() {
121126
}
122127

123128
public static void main(String[] args) {
129+
// Suppress INFO logging from library initialization
130+
java.util.logging.Logger.getLogger("org.apache.tomcat.util.net.openssl.panama").setLevel(java.util.logging.Level.WARNING);
131+
java.util.logging.Logger.getLogger("org.apache.catalina.core").setLevel(java.util.logging.Level.WARNING);
132+
124133
System.out.println("Server version: " + getServerInfo());
125134
System.out.println("Server built: " + getServerBuilt());
126135
System.out.println("Server number: " + getServerNumber());
@@ -129,6 +138,175 @@ public static void main(String[] args) {
129138
System.out.println("Architecture: " + System.getProperty("os.arch"));
130139
System.out.println("JVM Version: " + System.getProperty("java.runtime.version"));
131140
System.out.println("JVM Vendor: " + System.getProperty("java.vm.vendor"));
141+
142+
// Get CATALINA_HOME for library scanning (already displayed in catalina script output preface)
143+
String catalinaHome = System.getProperty("catalina.home");
144+
145+
// Display APR/Tomcat Native information if available
146+
boolean aprLoaded = false;
147+
try {
148+
// Try to initialize APR by creating an instance and calling isAprAvailable()
149+
// Creating an instance sets the instance flag which allows initialization
150+
Class<?> aprLifecycleListenerClass = Class.forName("org.apache.catalina.core.AprLifecycleListener");
151+
aprLifecycleListenerClass.getConstructor().newInstance();
152+
Boolean aprAvailable = (Boolean) aprLifecycleListenerClass.getMethod("isAprAvailable").invoke(null);
153+
if (aprAvailable != null && aprAvailable.booleanValue()) {
154+
// APR is available, get version information using public methods
155+
String tcnVersion = (String) aprLifecycleListenerClass.getMethod("getInstalledTcnVersion").invoke(null);
156+
String aprVersion = (String) aprLifecycleListenerClass.getMethod("getInstalledAprVersion").invoke(null);
157+
158+
System.out.println("APR loaded: true");
159+
System.out.println("APR Version: " + aprVersion);
160+
System.out.println("Tomcat Native: " + tcnVersion);
161+
aprLoaded = true;
162+
163+
// Check if installed version is older than recommended
164+
try {
165+
String warning = (String) aprLifecycleListenerClass.getMethod("getTcnVersionWarning").invoke(null);
166+
167+
if (warning != null) {
168+
System.out.println(" " + warning);
169+
}
170+
} catch (Exception e) {
171+
// Failed to check version - ignore
172+
}
173+
174+
// Display OpenSSL version if available
175+
try {
176+
String openSSLVersion = (String) aprLifecycleListenerClass.getMethod("getInstalledOpenSslVersion").invoke(null);
177+
178+
if (openSSLVersion != null && !openSSLVersion.isEmpty()) {
179+
System.out.println("OpenSSL (APR): " + openSSLVersion);
180+
}
181+
} catch (Exception e) {
182+
// SSL not initialized or not available
183+
}
184+
}
185+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
186+
// APR/Tomcat Native classes not available on classpath
187+
} catch (Exception e) {
188+
// Error checking APR status
189+
}
190+
191+
if (!aprLoaded) {
192+
System.out.println("APR loaded: false");
193+
}
194+
195+
// Display FFM OpenSSL information if available
196+
try {
197+
// Try to initialize FFM OpenSSL by creating an instance and calling isAvailable()
198+
// Creating an instance sets the instance flag which allows initialization
199+
Class<?> openSSLLifecycleListenerClass = Class.forName("org.apache.catalina.core.OpenSSLLifecycleListener");
200+
openSSLLifecycleListenerClass.getConstructor().newInstance();
201+
Boolean ffmAvailable = (Boolean) openSSLLifecycleListenerClass.getMethod("isAvailable").invoke(null);
202+
203+
if (ffmAvailable != null && ffmAvailable.booleanValue()) {
204+
// FFM OpenSSL is available, get version information using public method
205+
String versionString = (String) openSSLLifecycleListenerClass.getMethod("getInstalledOpenSslVersion").invoke(null);
206+
207+
if (versionString != null && !versionString.isEmpty()) {
208+
System.out.println("OpenSSL (FFM): " + versionString);
209+
}
210+
}
211+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
212+
// FFM OpenSSL classes not available on classpath
213+
} catch (Exception e) {
214+
// Error checking FFM OpenSSL status
215+
}
216+
217+
// Display third-party libraries in CATALINA_HOME/lib
218+
if (catalinaHome != null) {
219+
File libDir = new File(catalinaHome, "lib");
220+
if (libDir.exists() && libDir.isDirectory()) {
221+
File[] allJars = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
222+
223+
if (allJars != null && allJars.length > 0) {
224+
// First pass: collect third-party JARs and find longest name
225+
List<File> thirdPartyJars = new ArrayList<>();
226+
int maxNameLength = 0;
227+
for (File jar : allJars) {
228+
if (!isTomcatCoreJar(jar)) {
229+
thirdPartyJars.add(jar);
230+
maxNameLength = Math.max(maxNameLength, jar.getName().length());
231+
}
232+
}
233+
234+
// Second pass: print with aligned formatting
235+
if (!thirdPartyJars.isEmpty()) {
236+
System.out.println();
237+
System.out.println("Third-party libraries:");
238+
for (File jar : thirdPartyJars) {
239+
String version = getJarVersion(jar);
240+
String jarName = jar.getName();
241+
// Colon right after name, then pad to align version numbers
242+
String nameWithColon = jarName + ":";
243+
String paddedName = String.format("%-" + (maxNameLength + 1) + "s", nameWithColon);
244+
if (version != null) {
245+
System.out.println(" " + paddedName + " " + version);
246+
} else {
247+
System.out.println(" " + paddedName + " (unknown)");
248+
}
249+
}
250+
}
251+
}
252+
}
253+
}
254+
}
255+
256+
private static boolean isTomcatCoreJar(File jarFile) {
257+
try (JarFile jar = new JarFile(jarFile)) {
258+
Manifest manifest = jar.getManifest();
259+
260+
if (manifest != null) {
261+
// Check Bundle-SymbolicName to identify Tomcat core JARs
262+
String bundleName = manifest.getMainAttributes().getValue("Bundle-SymbolicName");
263+
if (bundleName != null) {
264+
// Tomcat core JARs have Bundle-SymbolicName starting with org.apache.tomcat,
265+
// org.apache.catalina, or jakarta.
266+
if (bundleName.startsWith("org.apache.tomcat") ||
267+
bundleName.startsWith("org.apache.catalina") ||
268+
bundleName.startsWith("jakarta.")) {
269+
return true;
270+
}
271+
}
272+
273+
// Fallback: Check Implementation-Vendor and Implementation-Title
274+
String implVendor = manifest.getMainAttributes().getValue("Implementation-Vendor");
275+
String implTitle = manifest.getMainAttributes().getValue("Implementation-Title");
276+
277+
if ("Apache Software Foundation".equals(implVendor) && "Apache Tomcat".equals(implTitle)) {
278+
return true;
279+
}
280+
}
281+
} catch (Exception e) {
282+
// Ignore errors reading JAR manifest
283+
}
284+
285+
return false;
286+
}
287+
288+
private static String getJarVersion(File jarFile) {
289+
try (JarFile jar = new JarFile(jarFile)) {
290+
Manifest manifest = jar.getManifest();
291+
292+
if (manifest != null) {
293+
// Try different common version attributes
294+
String version = manifest.getMainAttributes().getValue("Bundle-Version");
295+
if (version == null) {
296+
version = manifest.getMainAttributes().getValue("Implementation-Version");
297+
}
298+
299+
if (version == null) {
300+
version = manifest.getMainAttributes().getValue("Specification-Version");
301+
}
302+
303+
return version;
304+
}
305+
} catch (Exception e) {
306+
// Ignore errors reading JAR manifest
307+
}
308+
309+
return null;
132310
}
133311

134312
}

java/org/apache/tomcat/util/net/openssl/panama/OpenSSLLibrary.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,18 @@ public static boolean isFIPSModeActive() {
425425
return fipsModeActive;
426426
}
427427

428+
public static String getVersionString() {
429+
if (!OpenSSLStatus.isAvailable()) {
430+
return null;
431+
}
432+
433+
try {
434+
return OpenSSL_version(0).getString(0);
435+
} catch (Exception e) {
436+
return null;
437+
}
438+
}
439+
428440
public static List<String> findCiphers(String ciphers) {
429441
ArrayList<String> ciphersList = new ArrayList<>();
430442
try (var localArena = Arena.ofConfined()) {

0 commit comments

Comments
 (0)