This reuses the existing proxy object created by the dict_debug()
function.
---
 proto/DATABASE_README.html   | 13 +++++++
 src/postconf/postconf.c      |  9 +++++
 src/proxymap/Makefile.in     |  1 +
 src/proxymap/proxymap.c      |  2 ++
 src/util/Makefile.in         | 12 +++++--
 src/util/dict_debug.c        | 66 ++++++++++++++++++++++++++++++++----
 src/util/dict_debug.h        | 34 +++++++++++++++++++
 src/util/dict_debug_test.ref | 51 ++++++++++++++++++++++++++++
 src/util/dict_debug_test.sh  | 25 ++++++++++++++
 src/util/dict_open.c         |  2 ++
 10 files changed, 207 insertions(+), 8 deletions(-)
 create mode 100644 src/util/dict_debug.h
 create mode 100644 src/util/dict_debug_test.ref
 create mode 100755 src/util/dict_debug_test.sh

diff --git a/proto/DATABASE_README.html b/proto/DATABASE_README.html
index 42a54ac7c..4182bb8c8 100644
--- a/proto/DATABASE_README.html
+++ b/proto/DATABASE_README.html
@@ -293,6 +293,19 @@ databases are maintained by Postfix daemons. The lookup 
table name
 as used in "dbm:table" is the database file name without the ".dir"
 or ".pag" suffix.  </dd>
 
+<dt> <b>debug</b> </dt>
+
+<dd>
+<p> An adapter for another table that causes all accesses to be
+logged.  Example usage: "debug:hash:/etc/postfix/example".  The
+formats of the log messages are unspecified and subject to change.
+Warning: If a query or the underlying table contains sensitive
+information (such as a password), that information might be
+logged. </p>
+
+<p> This feature is available with Postfix 3.11 and later. </p>
+</dd>
+
 <dt> <b>environ</b> </dt>
 
 <dd> The UNIX process environment array. The lookup key is the
diff --git a/src/postconf/postconf.c b/src/postconf/postconf.c
index 74f13b2cd..a4d4c8490 100644
--- a/src/postconf/postconf.c
+++ b/src/postconf/postconf.c
@@ -252,6 +252,15 @@
 /* .IP \fBdbm\fR
 /*     An indexed file type based on hashing.  Available on systems
 /*     with support for DBM databases.
+/* .IP \fBdebug\fR
+/*     An adapter for another table that causes all accesses to be
+/*     logged.  Example usage: \fBdebug:hash:/etc/postfix/example\fR.
+/*     The formats of the log messages are unspecified and subject to
+/*     change.  Warning: If a query or the underlying table contains
+/*     sensitive information (such as a password), that information
+/*     might be logged.
+/*
+/*     This feature is available with Postfix 3.11 and later.
 /* .IP \fBenviron\fR
 /*     The UNIX process environment array. The lookup key is the
 /*     environment variable name; the table name is ignored.  Originally
diff --git a/src/proxymap/Makefile.in b/src/proxymap/Makefile.in
index efe6e0c87..29dec6a74 100644
--- a/src/proxymap/Makefile.in
+++ b/src/proxymap/Makefile.in
@@ -52,6 +52,7 @@ proxymap.o: ../../include/argv.h
 proxymap.o: ../../include/attr.h
 proxymap.o: ../../include/check_arg.h
 proxymap.o: ../../include/dict.h
+proxymap.o: ../../include/dict_debug.h
 proxymap.o: ../../include/dict_pipe.h
 proxymap.o: ../../include/dict_proxy.h
 proxymap.o: ../../include/dict_union.h
diff --git a/src/proxymap/proxymap.c b/src/proxymap/proxymap.c
index c0af411f6..9fc1159e8 100644
--- a/src/proxymap/proxymap.c
+++ b/src/proxymap/proxymap.c
@@ -232,6 +232,7 @@
 #include <htable.h>
 #include <stringops.h>
 #include <dict.h>
+#include <dict_debug.h>
 #include <dict_pipe.h>
 #include <dict_union.h>
 
@@ -307,6 +308,7 @@ static char *get_nested_dict_name(char *type_name)
     }      *prefix, prefixes[] = {
        DICT_TYPE_UNION ":", (sizeof(DICT_TYPE_UNION ":") - 1),
        DICT_TYPE_PIPE ":", (sizeof(DICT_TYPE_PIPE ":") - 1),
+       DICT_TYPE_DEBUG ":", (sizeof(DICT_TYPE_DEBUG ":") - 1),
     };
 
 #define COUNT_OF(x) (sizeof(x)/sizeof((x)[0]))
diff --git a/src/util/Makefile.in b/src/util/Makefile.in
index 7c99ae23a..b9d70dbd2 100644
--- a/src/util/Makefile.in
+++ b/src/util/Makefile.in
@@ -104,7 +104,8 @@ MAP_OBJ     = dict_pcre.o dict_cdb.o dict_lmdb.o 
dict_sdbm.o slmdb.o \
        mkmap_cdb.o mkmap_lmdb.o mkmap_sdbm.o 
 HDRS   = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
        chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \
-       dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \
+       dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_debug.h dict_env.h \
+       dict_ht.h \
        dict_lmdb.h dict_ni.h dict_nis.h dict_nisplus.h dict_pcre.h 
dict_regexp.h \
        dict_sdbm.h dict_static.h dict_tcp.h dict_unix.h dir_forest.h \
        events.h exec_command.h find_inet.h fsspace.h fullname.h \
@@ -658,7 +659,7 @@ dict_tests: all dict_test \
        dict_pipe_test dict_regexp_file_test dict_cidr_file_test \
        dict_static_file_test dict_random_test dict_random_file_test \
        dict_inline_file_test dict_stream_test dict_inline_regexp_test \
-       dict_inline_cidr_test
+       dict_inline_cidr_test dict_debug_test
 
 dict_pcre_tests: dict_pcre_test miss_endif_pcre_test dict_pcre_file_test \
        dict_inline_pcre_test
@@ -1113,6 +1114,11 @@ dict_inline_cidr_test: dict_open dict_inline_cidr.ref
        diff dict_inline_cidr.ref dict_inline_cidr.tmp
        rm -f dict_inline_cidr.tmp
 
+dict_debug_test: dict_open dict_debug_test.sh dict_debug_test.ref
+       $(SHLIB_ENV) ./dict_debug_test.sh >dict_debug_test.tmp 2>&1
+       diff dict_debug_test.ref dict_debug_test.tmp
+       rm -f dict_debug_test.tmp
+
 find_inet_test: find_inet find_inet.ref
        $(SHLIB_ENV) ${VALGRIND} ./find_inet >find_inet.tmp 2>&1
        diff find_inet.ref find_inet.tmp
@@ -1545,6 +1551,7 @@ dict_debug.o: argv.h
 dict_debug.o: check_arg.h
 dict_debug.o: dict.h
 dict_debug.o: dict_debug.c
+dict_debug.o: dict_debug.h
 dict_debug.o: msg.h
 dict_debug.o: myflock.h
 dict_debug.o: mymalloc.h
@@ -1672,6 +1679,7 @@ dict_open.o: dict_cdb.h
 dict_open.o: dict_cidr.h
 dict_open.o: dict_db.h
 dict_open.o: dict_dbm.h
+dict_open.o: dict_debug.h
 dict_open.o: dict_env.h
 dict_open.o: dict_fail.h
 dict_open.o: dict_ht.h
diff --git a/src/util/dict_debug.c b/src/util/dict_debug.c
index 46634d40c..1ea67240e 100644
--- a/src/util/dict_debug.c
+++ b/src/util/dict_debug.c
@@ -11,6 +11,13 @@
 /*
 /*     DICT    *DICT_DEBUG(dict_handle)
 /*     DICT    *dict_handle;
+/*
+/*     #include <dict_debug.h>
+/*
+/*     DICT    *dict_debug_open(name, open_flags, dict_flags);
+/*     const char *name;
+/*     int     open_flags;
+/*     int     dict_flags;
 /* DESCRIPTION
 /*     dict_debug() encapsulates the given dictionary object and returns
 /*     a proxy object that logs all access to the encapsulated object.
@@ -21,6 +28,12 @@
 /*     the object's debugging flag is not set, and that otherwise encapsulates
 /*     the object with dict_debug(). This macro simplifies usage by avoiding
 /*     clumsy expressions. The macro evaluates its argument multiple times.
+/*
+/*     dict_debug_open() is similar to dict_debug() except the underlying
+/*     table's handle is registered as \fIname\fR, which has the form
+/*     "\fItype\fB:\fIname\fR".  If no such registration exists yet, the
+/*     underlying table is first opened as if by a call to
+/*     dict_open(\fIname\fR, \fIopen_flags\fR, \fIdict_flags\fR).
 /* DIAGNOSTICS
 /*     Fatal errors: out of memory.
 /* LICENSE
@@ -32,6 +45,8 @@
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Richard Hansen <rhan...@rhansen.org> (dict_debug_open(), © 2025)
 /*--*/
 
 /* System libraries. */
@@ -43,12 +58,14 @@
 #include <msg.h>
 #include <mymalloc.h>
 #include <dict.h>
+#include "dict_debug.h"
 
 /* Application-specific. */
 
 typedef struct {
     DICT    dict;                      /* the proxy service */
     DICT   *real_dict;                 /* encapsulated object */
+    char   *reg;                       /* real_dict registration, or null */
 } DICT_DEBUG;
 
 /* dict_debug_lookup - log lookup operation */
@@ -62,7 +79,11 @@ static const char *dict_debug_lookup(DICT *dict, const char 
*key)
     real_dict->flags = dict->flags;
     result = dict_get(real_dict, key);
     dict->flags = real_dict->flags;
-    msg_info("%s:%s lookup: \"%s\" = \"%s\"", dict->type, dict->name, key,
+    msg_info("%s%s%s lookup: \"%s\" = \"%s\"",
+            dict_debug->reg ? dict_debug->reg : dict->type,
+            dict_debug->reg ? "" : ":",
+            dict_debug->reg ? "" : dict->name,
+            key,
             result ? result : real_dict->error ? "error" : "not_found");
     DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
 }
@@ -78,7 +99,10 @@ static int dict_debug_update(DICT *dict, const char *key, 
const char *value)
     real_dict->flags = dict->flags;
     result = dict_put(real_dict, key, value);
     dict->flags = real_dict->flags;
-    msg_info("%s:%s update: \"%s\" = \"%s\": %s", dict->type, dict->name,
+    msg_info("%s%s%s update: \"%s\" = \"%s\": %s",
+            dict_debug->reg ? dict_debug->reg : dict->type,
+            dict_debug->reg ? "" : ":",
+            dict_debug->reg ? "" : dict->name,
             key, value, result == 0 ? "success" : real_dict->error ?
             "error" : "failed");
     DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
@@ -95,7 +119,11 @@ static int dict_debug_delete(DICT *dict, const char *key)
     real_dict->flags = dict->flags;
     result = dict_del(real_dict, key);
     dict->flags = real_dict->flags;
-    msg_info("%s:%s delete: \"%s\": %s", dict->type, dict->name, key,
+    msg_info("%s%s%s delete: \"%s\": %s",
+            dict_debug->reg ? dict_debug->reg : dict->type,
+            dict_debug->reg ? "" : ":",
+            dict_debug->reg ? "" : dict->name,
+            key,
             result == 0 ? "success" : real_dict->error ?
             "error" : "failed");
     DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
@@ -114,10 +142,16 @@ static int dict_debug_sequence(DICT *dict, int function,
     result = dict_seq(real_dict, function, key, value);
     dict->flags = real_dict->flags;
     if (result == 0)
-       msg_info("%s:%s sequence: \"%s\" = \"%s\"", dict->type, dict->name,
+       msg_info("%s%s%s sequence: \"%s\" = \"%s\"",
+                dict_debug->reg ? dict_debug->reg : dict->type,
+                dict_debug->reg ? "" : ":",
+                dict_debug->reg ? "" : dict->name,
                 *key, *value);
     else
-       msg_info("%s:%s sequence: found EOF", dict->type, dict->name);
+       msg_info("%s%s%s sequence: found EOF",
+                dict_debug->reg ? dict_debug->reg : dict->type,
+                dict_debug->reg ? "" : ":",
+                dict_debug->reg ? "" : dict->name);
     DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
 }
 
@@ -127,7 +161,11 @@ static void dict_debug_close(DICT *dict)
 {
     DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
 
-    dict_close(dict_debug->real_dict);
+    if (dict_debug->reg != 0) {
+       dict_unregister(dict_debug->reg);
+       myfree(dict_debug->reg);
+    } else
+       dict_close(dict_debug->real_dict);
     dict_free(dict);
 }
 
@@ -146,5 +184,21 @@ DICT   *dict_debug(DICT *real_dict)
     dict_debug->dict.sequence = dict_debug_sequence;
     dict_debug->dict.close = dict_debug_close;
     dict_debug->real_dict = real_dict;
+    dict_debug->reg = 0;
     return (&dict_debug->dict);
 }
+
+DICT   *dict_debug_open(const char *name, int open_flags, int dict_flags)
+{
+    static const char myname[] = "dict_debug_open";
+    if (msg_verbose)
+       msg_info("%s: %s", myname, name);
+    DICT *real_dict = dict_handle(name);
+    if (real_dict == 0)
+       real_dict = dict_open(name, open_flags, dict_flags);
+    dict_register(name, real_dict);
+    DICT_DEBUG *dd = (DICT_DEBUG *) dict_debug(real_dict);
+    dd->reg = mystrdup(name);
+    dd->dict.owner = real_dict->owner;
+    return &dd->dict;
+}
diff --git a/src/util/dict_debug.h b/src/util/dict_debug.h
new file mode 100644
index 000000000..bbf56c6c6
--- /dev/null
+++ b/src/util/dict_debug.h
@@ -0,0 +1,34 @@
+#ifndef _DICT_DEBUG_H_INCLUDED_
+#define _DICT_DEBUG_H_INCLUDED_
+
+/*++
+/* NAME
+/*     dict_debug 3h
+/* SUMMARY
+/*     dictionary manager interface for "debug" tables
+/* SYNOPSIS
+/*     #include <dict_debug.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <dict.h>
+
+ /*
+  * External interface.
+  */
+#define DICT_TYPE_DEBUG "debug"
+
+extern DICT *dict_debug_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Richard Hansen <rhan...@rhansen.org> (© 2025)
+/*--*/
+
+#endif
diff --git a/src/util/dict_debug_test.ref b/src/util/dict_debug_test.ref
new file mode 100644
index 000000000..0c31d1ab1
--- /dev/null
+++ b/src/util/dict_debug_test.ref
@@ -0,0 +1,51 @@
++ ./dict_open debug: read
+./dict_open: fatal: open dictionary: expecting "type:name" form instead of 
"debug:"
++ ./dict_open debug:missing_colon_and_name read
+./dict_open: fatal: open dictionary: expecting "type:name" form instead of 
"missing_colon_and_name"
++ ./dict_open debug:static:{space in name} read
+owner=trusted (uid=2147483647)
+> get k
+./dict_open: static:{space in name} lookup: "k" = "space in name"
+k=space in name
++ ./dict_open debug:debug:static:value read
+owner=trusted (uid=2147483647)
+> get k
+./dict_open: static:value lookup: "k" = "value"
+./dict_open: debug:static:value lookup: "k" = "value"
+k=value
++ ./dict_open debug:internal:name write
+owner=trusted (uid=2147483647)
+> get k
+./dict_open: internal:name lookup: "k" = "not_found"
+k: not found
+> put k=v
+./dict_open: internal:name update: "k" = "v": success
+> get k
+./dict_open: internal:name lookup: "k" = "v"
+k=v
+> first
+./dict_open: internal:name sequence: "k" = "v"
+k=v
+> next
+./dict_open: internal:name sequence: found EOF
+not found
+> del k
+./dict_open: internal:name delete: "k": success
+k: deleted
+> get k
+./dict_open: internal:name lookup: "k" = "not_found"
+k: not found
+> del k
+./dict_open: internal:name delete: "k": failed
+k: not found
+> first
+./dict_open: internal:name sequence: found EOF
+not found
++ ./dict_open debug:fail:{oh no} read
+owner=trusted (uid=2147483647)
+> get k
+./dict_open: fail:{oh no} lookup: "k" = "error"
+k: error
+> first
+./dict_open: fail:{oh no} sequence: found EOF
+error
diff --git a/src/util/dict_debug_test.sh b/src/util/dict_debug_test.sh
new file mode 100755
index 000000000..a8c051d80
--- /dev/null
+++ b/src/util/dict_debug_test.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+set -ex
+! ${VALGRIND} ./dict_open 'debug:' read </dev/null || exit 1
+! ${VALGRIND} ./dict_open 'debug:missing_colon_and_name' read </dev/null || 
exit 1
+${VALGRIND} ./dict_open 'debug:static:{space in name}' read <<EOF
+get k
+EOF
+${VALGRIND} ./dict_open 'debug:debug:static:value' read <<EOF
+get k
+EOF
+${VALGRIND} ./dict_open 'debug:internal:name' write <<EOF
+get k
+put k=v
+get k
+first
+next
+del k
+get k
+del k
+first
+EOF
+${VALGRIND} ./dict_open 'debug:fail:{oh no}' read <<EOF
+get k
+first
+EOF
diff --git a/src/util/dict_open.c b/src/util/dict_open.c
index c3b90d497..bc42c06fc 100644
--- a/src/util/dict_open.c
+++ b/src/util/dict_open.c
@@ -337,6 +337,7 @@
 #include <msg.h>
 #include <dict.h>
 #include <dict_cdb.h>
+#include <dict_debug.h>
 #include <dict_env.h>
 #include <dict_unix.h>
 #include <dict_tcp.h>
@@ -415,6 +416,7 @@ static const DICT_OPEN_INFO dict_open_info[] = {
     DICT_TYPE_LMDB, dict_lmdb_open, mkmap_lmdb_open,
 #endif
 #endif                                 /* !USE_DYNAMIC_MAPS */
+    DICT_TYPE_DEBUG, dict_debug_open, 0,
     0,
 };
 
-- 
2.49.0

_______________________________________________
Postfix-devel mailing list -- postfix-devel@postfix.org
To unsubscribe send an email to postfix-devel-le...@postfix.org

Reply via email to