This is an automated email from the ASF dual-hosted git repository.

schultz pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 5f709da6b2693b3d7f3cf53a731eeac8984b5094
Author: Christopher Schultz <ch...@christopherschultz.net>
AuthorDate: Mon Mar 11 17:38:01 2024 -0400

    Add checking for the age of the Tomcat version running and warn if it's 
getting old.
---
 .../catalina/security/LocalStrings.properties      |  3 ++
 .../apache/catalina/security/SecurityListener.java | 63 ++++++++++++++++++++++
 java/org/apache/catalina/util/ServerInfo.java      | 18 +++++++
 webapps/docs/changelog.xml                         |  5 ++
 webapps/docs/config/listeners.xml                  |  5 ++
 5 files changed, 94 insertions(+)

diff --git a/java/org/apache/catalina/security/LocalStrings.properties 
b/java/org/apache/catalina/security/LocalStrings.properties
index 2af8b37d12..e356c44a27 100644
--- a/java/org/apache/catalina/security/LocalStrings.properties
+++ b/java/org/apache/catalina/security/LocalStrings.properties
@@ -18,6 +18,9 @@ SecurityListener.checkUmaskNone=No umask setting was found in 
system property [{
 SecurityListener.checkUmaskParseFail=Failed to parse value [{0}] as a valid 
umask.
 SecurityListener.checkUmaskSkip=Unable to determine umask. It appears Tomcat 
is running on Windows so skip the umask check.
 SecurityListener.checkUserWarning=Start attempted while running as user [{0}]. 
Running Tomcat as this user has been blocked by the Lifecycle listener 
org.apache.catalina.security.SecurityListener (usually configured in 
CATALINA_BASE/conf/server.xml)
+SecurityListener.buildDateAgeUnreadable=Unable to read configured 
buildDateWarningAgeDays [{0}], using default of [{1}] days.
+SecurityListener.buildDateUnreadable=Server build date [{0}] is unreadable as 
an ISO-8601 date.
+SecurityListener.buildDateIsOld=This version of Tomcat was built more than {0} 
days ago. You should consider upgrading to the current version.
 
 SecurityUtil.doAsPrivilege=An exception occurs when running the 
PrivilegedExceptionAction block.
 
diff --git a/java/org/apache/catalina/security/SecurityListener.java 
b/java/org/apache/catalina/security/SecurityListener.java
index 2371e30f7d..0fd20933b4 100644
--- a/java/org/apache/catalina/security/SecurityListener.java
+++ b/java/org/apache/catalina/security/SecurityListener.java
@@ -16,6 +16,10 @@
  */
 package org.apache.catalina.security;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
@@ -24,6 +28,7 @@ import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.Server;
+import org.apache.catalina.util.ServerInfo;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.StringUtils;
@@ -42,11 +47,18 @@ public class SecurityListener implements LifecycleListener {
 
     private static final String UMASK_FORMAT = "%04o";
 
+    private static final int DEFAULT_BUILD_DATE_WARNING_AGE_DAYS = 180;
+
     /**
      * The list of operating system users not permitted to run Tomcat.
      */
     private final Set<String> checkedOsUsers = new HashSet<>();
 
+    /**
+     * The number of days this Tomcat build can go without warning upon 
startup.
+     */
+    private int buildDateWarningAgeDays = DEFAULT_BUILD_DATE_WARNING_AGE_DAYS;
+
     /**
      * The minimum umask that must be configured for the operating system user 
running Tomcat. The umask is handled as
      * an octal.
@@ -126,6 +138,33 @@ public class SecurityListener implements LifecycleListener 
{
         return String.format(UMASK_FORMAT, minimumUmask);
     }
 
+    /**
+     * Sets the number of days that may pass between the build-date of this
+     * Tomcat instance before warnings are printed.
+     *
+     * @param ageDays The number of days a Tomcat build is allowed to age
+     *                before logging warnings.
+     */
+    public void setBuildDateWarningAgeDays(String ageDays) {
+        try {
+            buildDateWarningAgeDays = Integer.parseInt(ageDays);
+        } catch (NumberFormatException nfe) {
+            // Just use the default and warn the user
+            log.warn(sm.getString("SecurityListener.buildDateAgeUnreadable",
+                    ageDays, DEFAULT_BUILD_DATE_WARNING_AGE_DAYS));
+        }
+    }
+
+    /**
+     * Gets the number of days that may pass between the build-date of this
+     * Tomcat instance before warnings are printed.
+     *
+     * @return The number of days a Tomcat build is allowed to age
+     *         before logging warnings.
+     */
+    public int getBuildDateWarningAgeDays() {
+        return buildDateWarningAgeDays;
+    }
 
     /**
      * Execute the security checks. Each check should be in a separate method.
@@ -133,6 +172,7 @@ public class SecurityListener implements LifecycleListener {
     protected void doChecks() {
         checkOsUser();
         checkUmask();
+        checkServerBuildAge();
     }
 
 
@@ -179,4 +219,27 @@ public class SecurityListener implements LifecycleListener 
{
                     getMinimumUmask()));
         }
     }
+
+    protected void checkServerBuildAge() {
+        String buildDateString = ServerInfo.getServerBuiltISO();
+
+        if (null == buildDateString || buildDateString.length() < 1 || 
!Character.isDigit(buildDateString.charAt(0))) {
+            log.warn(sm.getString("SecurityListener.buildDateUnreadable", 
buildDateString));
+        } else {
+            try {
+                Date buildDate = new 
SimpleDateFormat("yyyy-MM-dd").parse(buildDateString);
+
+                int allowedAgeDays = getBuildDateWarningAgeDays();
+
+                Calendar old = Calendar.getInstance();
+                old.add(Calendar.DATE, -allowedAgeDays); // Subtract X days 
from today
+
+                if(buildDate.before(old.getTime())) {
+                    log.warn(sm.getString("SecurityListener.buildDateIsOld", 
allowedAgeDays));
+                }
+            } catch (ParseException pe) {
+                log.warn(sm.getString("SecurityListener.buildDateUnreadable", 
buildDateString));
+            }
+        }
+    }
 }
diff --git a/java/org/apache/catalina/util/ServerInfo.java 
b/java/org/apache/catalina/util/ServerInfo.java
index fbb171b532..d55bd8ed6d 100644
--- a/java/org/apache/catalina/util/ServerInfo.java
+++ b/java/org/apache/catalina/util/ServerInfo.java
@@ -45,6 +45,11 @@ public class ServerInfo {
      */
     private static final String serverBuilt;
 
+    /**
+     * The server built String, in ISO-8604 date format.
+     */
+    private static final String serverBuiltIso;
+
     /**
      * The server's version number String.
      */
@@ -54,6 +59,7 @@ public class ServerInfo {
 
         String info = null;
         String built = null;
+        String builtIso = null;
         String number = null;
 
         Properties props = new Properties();
@@ -62,6 +68,7 @@ public class ServerInfo {
             props.load(is);
             info = props.getProperty("server.info");
             built = props.getProperty("server.built");
+            builtIso = props.getProperty("server.built.iso");
             number = props.getProperty("server.number");
         } catch (Throwable t) {
             ExceptionUtils.handleThrowable(t);
@@ -72,12 +79,16 @@ public class ServerInfo {
         if (built == null || built.equals("@VERSION_BUILT@")) {
             built = "unknown";
         }
+        if (builtIso == null || builtIso.equals("@VERSION_BUILT_ISO@")) {
+            builtIso = "unknown";
+        }
         if (number == null || number.equals("@VERSION_NUMBER@")) {
             number = "9.0.x";
         }
 
         serverInfo = info;
         serverBuilt = built;
+        serverBuiltIso = builtIso;
         serverNumber = number;
     }
 
@@ -99,6 +110,13 @@ public class ServerInfo {
         return serverBuilt;
     }
 
+    /**
+     * @return the server built date for this version of Tomcat in ISO-8601 
date format.
+     */
+    public static String getServerBuiltISO() {
+        return serverBuiltIso;
+    }
+
     /**
      * @return the server's version number.
      */
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index f4c5e63ea5..c071e75c73 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -113,6 +113,11 @@
         error status code to the client when a permit cannot be acquired from
         the semaphore. (remm)
       </update>
+      <add>
+        Add checking of the "age" of the running Tomcat instance since its
+        build-date to the SecurityListener, and log a warning if the server
+        is old. (schultz)
+      </add>
     </changelog>
   </subsection>
 </section>
diff --git a/webapps/docs/config/listeners.xml 
b/webapps/docs/config/listeners.xml
index b6201c2463..ab6e3e9d5c 100644
--- a/webapps/docs/config/listeners.xml
+++ b/webapps/docs/config/listeners.xml
@@ -436,6 +436,11 @@
         is not performed on Windows platforms.</p>
       </attribute>
 
+      <attribute name="buildDateWarningAgeDays" required="false">
+        <p>The maximim number of days between the build-date of this instance
+        of Tomcat and its startup date can be before warnings will be logged.
+        If not specified, the default value of <b>180</b> is used.</p>
+      </attribute>
     </attributes>
 
   </subsection>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to