]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
mod_log_config: Add regression for SNI logging (r1927034)
authorGiannis Christodoulou <ichristod@apache.org>
Thu, 4 Jun 2026 09:40:10 +0000 (09:40 +0000)
committerGiannis Christodoulou <ichristod@apache.org>
Thu, 4 Jun 2026 09:40:10 +0000 (09:40 +0000)
Add SSL test env and SNI logging validation.

Github: closes #621

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1934976 13f79535-47bb-0310-9956-ffa450edef68

test/modules/__init__.py [new file with mode: 0644]
test/modules/ssl/__init__.py [new file with mode: 0644]
test/modules/ssl/conftest.py [new file with mode: 0644]
test/modules/ssl/env.py [new file with mode: 0644]
test/modules/ssl/test_001_sni.py [new file with mode: 0644]
test/pyhttpd/conf.py
test/pyhttpd/env.py

diff --git a/test/modules/__init__.py b/test/modules/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/modules/ssl/__init__.py b/test/modules/ssl/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/modules/ssl/conftest.py b/test/modules/ssl/conftest.py
new file mode 100644 (file)
index 0000000..e6b836a
--- /dev/null
@@ -0,0 +1,40 @@
+import logging
+import os
+import pytest
+import sys
+
+from .env import SSLTestEnv
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+
+
+def pytest_report_header(config, start_path):
+    env = SSLTestEnv()
+    return f"mod_ssl [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
+
+
+@pytest.fixture(scope="package")
+def env(pytestconfig) -> SSLTestEnv:
+    level = logging.INFO
+    console = logging.StreamHandler()
+    console.setLevel(level)
+    console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
+    logging.getLogger('').addHandler(console)
+    logging.getLogger('').setLevel(level=level)
+    env = SSLTestEnv(pytestconfig=pytestconfig)
+    env.setup_httpd()
+    env.apache_access_log_clear()
+    env.httpd_error_log.clear_log()
+    return env
+
+
+@pytest.fixture(autouse=True, scope="package")
+def require_openssl(env):
+    if not env.has_tool("openssl"):
+        pytest.skip("openssl not installed")
+
+
+@pytest.fixture(autouse=True, scope="package")
+def _stop_package_scope(env):
+    yield
+    assert env.apache_stop() == 0
diff --git a/test/modules/ssl/env.py b/test/modules/ssl/env.py
new file mode 100644 (file)
index 0000000..a8a8848
--- /dev/null
@@ -0,0 +1,25 @@
+import inspect
+import logging
+import os
+
+from pyhttpd.env import HttpdTestEnv, HttpdTestSetup
+
+log = logging.getLogger(__name__)
+
+
+class SSLTestSetup(HttpdTestSetup):
+
+    def __init__(self, env: 'HttpdTestEnv'):
+        super().__init__(env=env)
+        self.add_source_dir(os.path.dirname(inspect.getfile(SSLTestSetup)))
+        self.add_modules(["ssl"])
+
+
+class SSLTestEnv(HttpdTestEnv):
+
+    def __init__(self, pytestconfig=None):
+        super().__init__(pytestconfig=pytestconfig)
+        self.add_httpd_log_modules(["http", "ssl", "core"])
+
+    def setup_httpd(self, setup: HttpdTestSetup = None):
+        super().setup_httpd(setup=SSLTestSetup(env=self))
\ No newline at end of file
diff --git a/test/modules/ssl/test_001_sni.py b/test/modules/ssl/test_001_sni.py
new file mode 100644 (file)
index 0000000..30da915
--- /dev/null
@@ -0,0 +1,38 @@
+import os
+import pytest
+
+from pyhttpd.conf import HttpdConf
+
+
+class TestSNI:
+    LOG_FILE = "test_sni.log"
+
+    @pytest.fixture(autouse=True, scope="class")
+    def _class_scope(self, env):
+        conf = HttpdConf(env, extras={
+            "base":
+                f'CustomLog logs/{self.LOG_FILE} "%{{SSL_TLS_SNI}}x"'
+        })
+        conf.add_vhost_test1()
+        conf.install()
+        assert env.apache_restart() == 0
+
+    # check sni logging characters
+    def test_ssl_001_01(self, env):
+        log_path = os.path.join(env.server_logs_dir, self.LOG_FILE)
+
+        open(log_path, 'w').close()
+        sni = "httpd\x01\n2024\".org"
+
+        r = env.run(args=[
+            'openssl', 's_client',
+            '-connect', f"localhost:{env.https_port}",
+            '-servername', sni
+        ], intext="GET / HTTP/1.1\n\n")
+        assert r.exit_code == 0
+
+        with open(log_path, 'rb') as f:
+            log_content = f.read()
+
+        assert sni.encode() not in log_content, \
+            f"found unescaped characters in {self.LOG_FILE}.log"
index e1c6bf5ee959972fb2da94e884dc15c2f063fd1a..fece62c71c38d4e28d975cba89762612331203ef 100644 (file)
@@ -69,7 +69,8 @@ class HttpdConf(object):
             # In fact it should go in the corresponding VirtualHost... Not sure how to do that.
             l = "SSLEngine On"
         else:
-            if line != "":
+            # conflict with the SSL_TLS_SNI
+            if line.lstrip().startswith("TLS"):
                 l = line.replace("TLS", "SSL")
             else:
                 l = line
@@ -175,7 +176,7 @@ class HttpdConf(object):
         if domains[0] in self._extras:
             self.add(self._extras[domains[0]])
         return self
-                  
+
     def end_vhost(self):
         self._indents -= 1
         self.add("</VirtualHost>")
@@ -196,7 +197,7 @@ class HttpdConf(object):
                 f"ProxyPassReverse /h2proxy/ https://{host}.{self.env.http_tld}:self.env.https_port/",
             ])
         return self
-    
+
     def add_vhost_test1(self, proxy_self=False, h2proxy_self=False):
         domain = f"test1.{self.env.http_tld}"
         self.start_vhost(domains=[domain, f"www1.{self.env.http_tld}"],
index a30a027fb935c105ba65f6a1534aa062c824324f..e3cf1e723177764d1c8a61f197c7657f026f4a8f 100644 (file)
@@ -225,6 +225,10 @@ class HttpdTestEnv:
 
     LIBEXEC_DIR = None
 
+    @staticmethod
+    def has_tool(name: str) -> bool:
+        return bool(shutil.which(name))
+
     @classmethod
     def has_python_package(cls, name: str) -> bool:
         if name in sys.modules: