diff --git a/.gitignore b/.gitignore index 18ea466..d088947 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /389-ds-base-*.tar.bz2 /jemalloc-*.tar.bz2 /libdb-5.3.28-59.tar.bz2 +/Cargo-3.1.3-1.lock +/vendor-3.1.3-1.tar.gz diff --git a/0003-Issue-6663-Fix-NULL-subsystem-crash-in-JSON-error-lo.patch b/0003-Issue-6663-Fix-NULL-subsystem-crash-in-JSON-error-lo.patch new file mode 100644 index 0000000..6281b86 --- /dev/null +++ b/0003-Issue-6663-Fix-NULL-subsystem-crash-in-JSON-error-lo.patch @@ -0,0 +1,380 @@ +From bd9ab54f64148d467e022c59ee8e5aed16f0c385 Mon Sep 17 00:00:00 2001 +From: Akshay Adhikari +Date: Mon, 28 Jul 2025 18:14:15 +0530 +Subject: [PATCH] Issue 6663 - Fix NULL subsystem crash in JSON error logging + (#6883) + +Description: Fixes crash in JSON error logging when subsystem is NULL. +Parametrized test case for better debugging. + +Relates: https://github.com/389ds/389-ds-base/issues/6663 + +Reviewed by: @mreynolds389 +--- + .../tests/suites/clu/dsconf_logging.py | 168 ------------------ + .../tests/suites/clu/dsconf_logging_test.py | 164 +++++++++++++++++ + ldap/servers/slapd/log.c | 2 +- + 3 files changed, 165 insertions(+), 169 deletions(-) + delete mode 100644 dirsrvtests/tests/suites/clu/dsconf_logging.py + create mode 100644 dirsrvtests/tests/suites/clu/dsconf_logging_test.py + +diff --git a/dirsrvtests/tests/suites/clu/dsconf_logging.py b/dirsrvtests/tests/suites/clu/dsconf_logging.py +deleted file mode 100644 +index 1c2f7fc2e..000000000 +--- a/dirsrvtests/tests/suites/clu/dsconf_logging.py ++++ /dev/null +@@ -1,168 +0,0 @@ +-# --- BEGIN COPYRIGHT BLOCK --- +-# Copyright (C) 2025 Red Hat, Inc. +-# All rights reserved. +-# +-# License: GPL (version 3 or any later version). +-# See LICENSE for details. +-# --- END COPYRIGHT BLOCK --- +-# +-import json +-import subprocess +-import logging +-import pytest +-from lib389._constants import DN_DM +-from lib389.topologies import topology_st as topo +- +-pytestmark = pytest.mark.tier1 +- +-log = logging.getLogger(__name__) +- +-SETTINGS = [ +- ('logging-enabled', None), +- ('logging-disabled', None), +- ('mode', '700'), +- ('compress-enabled', None), +- ('compress-disabled', None), +- ('buffering-enabled', None), +- ('buffering-disabled', None), +- ('max-logs', '4'), +- ('max-logsize', '7'), +- ('rotation-interval', '2'), +- ('rotation-interval-unit', 'week'), +- ('rotation-tod-enabled', None), +- ('rotation-tod-disabled', None), +- ('rotation-tod-hour', '12'), +- ('rotation-tod-minute', '20'), +- ('deletion-interval', '3'), +- ('deletion-interval-unit', 'day'), +- ('max-disk-space', '20'), +- ('free-disk-space', '2'), +-] +- +-DEFAULT_TIME_FORMAT = "%FT%TZ" +- +- +-def execute_dsconf_command(dsconf_cmd, subcommands): +- """Execute dsconf command and return output and return code""" +- +- cmdline = dsconf_cmd + subcommands +- proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE) +- out, _ = proc.communicate() +- return out.decode('utf-8'), proc.returncode +- +- +-def get_dsconf_base_cmd(topo): +- """Return base dsconf command list""" +- return ['/usr/sbin/dsconf', topo.standalone.serverid, +- '-j', '-D', DN_DM, '-w', 'password', 'logging'] +- +- +-def test_log_settings(topo): +- """Test each log setting can be set successfully +- +- :id: b800fd03-37f5-4e74-9af8-eeb07030eb52 +- :setup: Standalone DS instance +- :steps: +- 1. Test each log's settings +- :expectedresults: +- 1. Success +- """ +- +- dsconf_cmd = get_dsconf_base_cmd(topo) +- for log_type in ['access', 'audit', 'auditfail', 'error', 'security']: +- # Test "get" command +- output, rc = execute_dsconf_command(dsconf_cmd, [log_type, 'get']) +- assert rc == 0 +- json_result = json.loads(output) +- default_location = json_result['Log name and location'] +- +- # Log location +- output, rc = execute_dsconf_command(dsconf_cmd, [log_type, 'set', +- 'location', +- f'/tmp/{log_type}']) +- assert rc == 0 +- output, rc = execute_dsconf_command(dsconf_cmd, [log_type, 'set', +- 'location', +- default_location]) +- assert rc == 0 +- +- # Log levels +- if log_type == "access": +- # List levels +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'list-levels']) +- assert rc == 0 +- +- # Set levels +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', 'level', +- 'internal']) +- assert rc == 0 +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', 'level', +- 'internal', 'entry']) +- assert rc == 0 +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', 'level', +- 'internal', 'default']) +- assert rc == 0 +- +- if log_type == "error": +- # List levels +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'list-levels']) +- assert rc == 0 +- +- # Set levels +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', 'level', +- 'plugin', 'replication']) +- assert rc == 0 +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', 'level', +- 'default']) +- assert rc == 0 +- +- # Log formats +- if log_type in ["access", "audit", "error"]: +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', +- 'time-format', '%D']) +- assert rc == 0 +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', +- 'time-format', +- DEFAULT_TIME_FORMAT]) +- assert rc == 0 +- +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', +- 'log-format', +- 'json']) +- assert rc == 0 +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', +- 'log-format', +- 'default']) +- assert rc == 0 +- +- # Audit log display attrs +- if log_type == "audit": +- output, rc = execute_dsconf_command(dsconf_cmd, +- [log_type, 'set', +- 'display-attrs', 'cn']) +- assert rc == 0 +- +- # Common settings +- for attr, value in SETTINGS: +- if log_type == "auditfail" and attr.startswith("buffer"): +- # auditfail doesn't have a buffering settings +- continue +- +- if value is None: +- output, rc = execute_dsconf_command(dsconf_cmd, [log_type, +- 'set', attr]) +- else: +- output, rc = execute_dsconf_command(dsconf_cmd, [log_type, +- 'set', attr, value]) +- assert rc == 0 +diff --git a/dirsrvtests/tests/suites/clu/dsconf_logging_test.py b/dirsrvtests/tests/suites/clu/dsconf_logging_test.py +new file mode 100644 +index 000000000..ca3f71997 +--- /dev/null ++++ b/dirsrvtests/tests/suites/clu/dsconf_logging_test.py +@@ -0,0 +1,164 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2025 Red Hat, Inc. ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++# ++import json ++import subprocess ++import logging ++import pytest ++from lib389._constants import DN_DM ++from lib389.topologies import topology_st as topo ++ ++pytestmark = pytest.mark.tier1 ++ ++log = logging.getLogger(__name__) ++ ++SETTINGS = [ ++ ('logging-enabled', None), ++ ('logging-disabled', None), ++ ('mode', '700'), ++ ('compress-enabled', None), ++ ('compress-disabled', None), ++ ('buffering-enabled', None), ++ ('buffering-disabled', None), ++ ('max-logs', '4'), ++ ('max-logsize', '7'), ++ ('rotation-interval', '2'), ++ ('rotation-interval-unit', 'week'), ++ ('rotation-tod-enabled', None), ++ ('rotation-tod-disabled', None), ++ ('rotation-tod-hour', '12'), ++ ('rotation-tod-minute', '20'), ++ ('deletion-interval', '3'), ++ ('deletion-interval-unit', 'day'), ++ ('max-disk-space', '20'), ++ ('free-disk-space', '2'), ++] ++ ++DEFAULT_TIME_FORMAT = "%FT%TZ" ++ ++ ++def execute_dsconf_command(dsconf_cmd, subcommands): ++ """Execute dsconf command and return output and return code""" ++ ++ cmdline = dsconf_cmd + subcommands ++ proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ++ out, err = proc.communicate() ++ ++ if proc.returncode != 0 and err: ++ log.error(f"Command failed: {' '.join(cmdline)}") ++ log.error(f"Stderr: {err.decode('utf-8')}") ++ ++ return out.decode('utf-8'), proc.returncode ++ ++ ++def get_dsconf_base_cmd(topo): ++ """Return base dsconf command list""" ++ return ['/usr/sbin/dsconf', topo.standalone.serverid, ++ '-j', '-D', DN_DM, '-w', 'password', 'logging'] ++ ++ ++@pytest.mark.parametrize("log_type", ['access', 'audit', 'auditfail', 'error', 'security']) ++def test_log_settings(topo, log_type): ++ """Test each log setting can be set successfully ++ ++ :id: b800fd03-37f5-4e74-9af8-eeb07030eb52 ++ :setup: Standalone DS instance ++ :steps: ++ 1. Test each log's settings ++ :expectedresults: ++ 1. Success ++ """ ++ ++ dsconf_cmd = get_dsconf_base_cmd(topo) ++ ++ output, rc = execute_dsconf_command(dsconf_cmd, [log_type, 'get']) ++ assert rc == 0 ++ json_result = json.loads(output) ++ default_location = json_result['Log name and location'] ++ ++ output, rc = execute_dsconf_command(dsconf_cmd, [log_type, 'set', ++ 'location', ++ f'/tmp/{log_type}']) ++ assert rc == 0 ++ output, rc = execute_dsconf_command(dsconf_cmd, [log_type, 'set', ++ 'location', ++ default_location]) ++ assert rc == 0 ++ ++ if log_type == "access": ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'list-levels']) ++ assert rc == 0 ++ ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', 'level', ++ 'internal']) ++ assert rc == 0 ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', 'level', ++ 'internal', 'entry']) ++ assert rc == 0 ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', 'level', ++ 'internal', 'default']) ++ assert rc == 0 ++ ++ if log_type == "error": ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'list-levels']) ++ assert rc == 0 ++ ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', 'level', ++ 'plugin', 'replication']) ++ assert rc == 0 ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', 'level', ++ 'default']) ++ assert rc == 0 ++ ++ if log_type in ["access", "audit", "error"]: ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', ++ 'time-format', '%D']) ++ assert rc == 0 ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', ++ 'time-format', ++ DEFAULT_TIME_FORMAT]) ++ assert rc == 0 ++ ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', ++ 'log-format', ++ 'json']) ++ assert rc == 0, f"Failed to set {log_type} log-format to json: {output}" ++ ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', ++ 'log-format', ++ 'default']) ++ assert rc == 0, f"Failed to set {log_type} log-format to default: {output}" ++ ++ if log_type == "audit": ++ output, rc = execute_dsconf_command(dsconf_cmd, ++ [log_type, 'set', ++ 'display-attrs', 'cn']) ++ assert rc == 0 ++ ++ for attr, value in SETTINGS: ++ if log_type == "auditfail" and attr.startswith("buffer"): ++ continue ++ ++ if value is None: ++ output, rc = execute_dsconf_command(dsconf_cmd, [log_type, ++ 'set', attr]) ++ else: ++ output, rc = execute_dsconf_command(dsconf_cmd, [log_type, ++ 'set', attr, value]) ++ assert rc == 0 +diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c +index 06dae4d0b..e859682fe 100644 +--- a/ldap/servers/slapd/log.c ++++ b/ldap/servers/slapd/log.c +@@ -2937,7 +2937,7 @@ vslapd_log_error( + json_obj = json_object_new_object(); + json_object_object_add(json_obj, "local_time", json_object_new_string(local_time)); + json_object_object_add(json_obj, "severity", json_object_new_string(get_log_sev_name(sev_level, sev_name))); +- json_object_object_add(json_obj, "subsystem", json_object_new_string(subsystem)); ++ json_object_object_add(json_obj, "subsystem", json_object_new_string(subsystem ? subsystem : "")); + json_object_object_add(json_obj, "msg", json_object_new_string(vbuf)); + + PR_snprintf(buffer, sizeof(buffer), "%s\n", +-- +2.49.0 + diff --git a/389-ds-base.spec b/389-ds-base.spec index 4e08ffa..d2f7131 100644 --- a/389-ds-base.spec +++ b/389-ds-base.spec @@ -290,8 +290,12 @@ Source4: 389-ds-base.sysusers Source5: https://fedorapeople.org/groups/389ds/libdb-5.3.28-59.tar.bz2 %endif +Source6: vendor-%{version}-1.tar.gz +Source7: Cargo-%{version}-1.lock + Patch: 0001-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch Patch: 0002-Issue-6852-Move-ds-CLI-tools-back-to-sbin.patch +Patch: 0003-Issue-6663-Fix-NULL-subsystem-crash-in-JSON-error-lo.patch %description 389 Directory Server is an LDAPv3 compliant server. The base package includes @@ -423,6 +427,9 @@ cd src/lib389 %prep %autosetup -p1 -n %{name}-%{version} +rm -rf vendor +tar xzf %{SOURCE6} +cp %{SOURCE7} src/Cargo.lock %if %{with bundle_jemalloc} %setup -q -n %{name}-%{version} -T -D -b 3 diff --git a/sources b/sources index 0e6a87a..519bf20 100644 --- a/sources +++ b/sources @@ -1,3 +1,5 @@ SHA512 (jemalloc-5.3.0.tar.bz2) = 22907bb052096e2caffb6e4e23548aecc5cc9283dce476896a2b1127eee64170e3562fa2e7db9571298814a7a2c7df6e8d1fbe152bd3f3b0c1abec22a2de34b1 SHA512 (libdb-5.3.28-59.tar.bz2) = 731a434fa2e6487ebb05c458b0437456eb9f7991284beb08cb3e21931e23bdeddddbc95bfabe3a2f9f029fe69cd33a2d4f0f5ce6a9811e9c3b940cb6fde4bf79 SHA512 (389-ds-base-3.1.3.tar.bz2) = bd15c29dba5209ed828a2534e51fd000fdd5d32862fd07ea73339e73489b3c79f1991c91592c75dbb67384c696a03c82378f156bbea594e2e17421c95ca4c6be +SHA512 (Cargo-3.1.3-1.lock) = ea6db252e49de8aa2fe165f5cc773dc2eb227100d56953a36ca062680a3fc54870a961b05aaac1f7a761c69f3685cc8a7be474ac92377a1219c293fd1117f491 +SHA512 (vendor-3.1.3-1.tar.gz) = bf7f775da482a0164b5192e60cc335f32c65edf120ab94336835d98b2ea769eb116c808d06376e8ececb96e617194ec3febebf375821657e3d4751d9d8a0cf3c