From 6e47e461b81de2059800e6a1a3c77fc2f9a4d65d Mon Sep 17 00:00:00 2001 From: Wietse Z Venema Date: Mon, 24 Nov 2025 00:00:00 -0500 Subject: [PATCH] postfix-3.11-20251124 --- postfix/HISTORY | 17 +++++-- postfix/html/postmulti.1.html | 29 +++++++++++- postfix/man/man1/postmulti.1 | 31 +++++++++++- postfix/proto/stop.double-history | 1 + postfix/proto/stop.spell-cc | 1 + postfix/proto/stop.spell-history | 1 + postfix/src/global/mail_version.h | 2 +- postfix/src/postmulti/Makefile.in | 44 +++++++++++++++-- postfix/src/postmulti/fake_strcmp.c | 42 +++++++++++++++++ postfix/src/postmulti/postmulti.c | 73 +++++++++++++++++++++++++---- 10 files changed, 223 insertions(+), 18 deletions(-) create mode 100644 postfix/src/postmulti/fake_strcmp.c diff --git a/postfix/HISTORY b/postfix/HISTORY index 21f71bbfd..437e28770 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -29054,7 +29054,7 @@ Apologies for any names omitted. after an I/O error on an existing connection. Reported by Oleksandr Kozmenko. File: xsasl/xsasl_dovecot_server.c. -20240315 +20250315 Code health: two typos canceled each other's effect. Fix by Michael Tokarev. No change in compiler output. File: @@ -29807,7 +29807,7 @@ Apologies for any names omitted. for TLS feature status logging. Files: smtp/smtp_connect.c, proto/postconf.proto. -20240924 +20250924 TLSRPT Workaround: when policies[*].policy.policy-type is "no-policy-found", report the TLSRPT policy domain name as @@ -29947,7 +29947,7 @@ Apologies for any names omitted. -L ssl-debug" will decode TLS handshake messages. Viktor Dukhovni. File: posttls-finger/posttls-finger.c -20241031 +20251031 Bugfix (defect introduced: Postfix 3.10, date 20250117): support for "TLS-Required: no" broke client-side TLS wrappermode @@ -30070,6 +30070,15 @@ Apologies for any names omitted. 20251122 Feature: basic JSON output support with "postalias -j" and - "postmap -j". See respective manpages for details. Files: + "postmap -j". See respective manpages for details. Added + basic tests to verify the output format. Files: postmap/Makefile.in, postmap/postmap.c, postalias/Makefile.in, postalias/postalias.c. + +20251124 + + Feature: basic JSON output support with "postmulti -jl". + The schema is documented in the updated postmulti(1) manpage. + Added basic tests to verify the output formats for original + and JSON output. Files: postmulti/postmulti.c, + postmulti/fake_strcmp.c, postmulti/Makefile.in. diff --git a/postfix/html/postmulti.1.html b/postfix/html/postmulti.1.html index 6ae219568..47eec47cb 100644 --- a/postfix/html/postmulti.1.html +++ b/postfix/html/postmulti.1.html @@ -17,7 +17,7 @@ POSTMULTI(1) POSTMULTI(1) Iterator mode: - postmulti -l [-aRv] [-g group] [-i name] + postmulti -l [-ajRv] [-g group] [-i name] postmulti -p [-av] [-g group] [-i name] postfix-command... @@ -97,6 +97,8 @@ POSTMULTI(1) POSTMULTI(1) This option cannot be used with -p. List mode + -j Produce JSON output. See JSON OBJECT FORMAT below. + -l List Postfix instances with their instance name, instance group name, enable/disable status and configuration directory. @@ -301,6 +303,31 @@ POSTMULTI(1) POSTMULTI(1) -v Enable verbose logging for debugging purposes. Multiple -v options make the software increasingly verbose. +JSON OBJECT FORMAT + The output consists of a sequence of lines. Each line contains one JSON + object that represents settings in a corresponding instance's main.cf + file. + + Object members have string values unless indicated otherwise. Programs + should ignore members that are not listed here, as members may be added + over time. + + name The value of the corresponding multi_instance_name parameter, or + "-" if no name is specified. + + group The value of the corresponding multi_instance_group parameter, + or "-" if no group is specified. + + enabled + Either "y" or "n", depending on whether the corresponding + multi_instance_enable parameter value is "yes" or "no". + + Note: this reports "y" for a primary instance, when + multi-instance support is not enabled. + + config_directory + The value of the corresponding config_directory parameter. + ENVIRONMENT The postmulti(1) command exports the following environment variables before executing the requested command for a given instance: diff --git a/postfix/man/man1/postmulti.1 b/postfix/man/man1/postmulti.1 index 96c597e94..45273c804 100644 --- a/postfix/man/man1/postmulti.1 +++ b/postfix/man/man1/postmulti.1 @@ -17,7 +17,7 @@ Postfix multi\-instance manager .ti -4 \fBIterator mode:\fR -\fBpostmulti\fR \fB\-l\fR [\fB\-aRv\fR] [\fB\-g \fIgroup\fR] +\fBpostmulti\fR \fB\-l\fR [\fB\-ajRv\fR] [\fB\-g \fIgroup\fR] [\fB\-i \fIname\fR] \fBpostmulti\fR \fB\-p\fR [\fB\-av\fR] [\fB\-g \fIgroup\fR] @@ -119,6 +119,8 @@ This option cannot be used with \fB\-p\fR. .nf .ad .fi +.IP \fB\-j\fR +Produce JSON output. See JSON OBJECT FORMAT below. .IP \fB\-l\fR List Postfix instances with their instance name, instance group name, enable/disable status and configuration directory. @@ -355,6 +357,33 @@ started etc. with "postfix \-c config\-directory start". Enable verbose logging for debugging purposes. Multiple \fB\-v\fR options make the software increasingly verbose. .RE +.SH "JSON OBJECT FORMAT" +.na +.nf +.ad +.fi +The output consists of a sequence of lines. Each line contains +one JSON object that represents settings in a corresponding +instance's main.cf file. + +Object members have string values unless indicated otherwise. +Programs should ignore members that are not listed here, as +members may be added over time. +.IP \fBname\fR +The value of the corresponding \fBmulti_instance_name\fR +parameter, or "\fB\-\fR" if no name is specified. +.IP \fBgroup\fR +The value of the corresponding \fBmulti_instance_group\fR +parameter, or "\fB\-\fR" if no group is specified. +.IP \fBenabled\fR +Either "\fBy\fR" or "\fBn\fR", depending on whether the +corresponding \fBmulti_instance_enable\fR parameter value is +"\fByes\fR" or "\fBno\fR". +.sp +Note: this reports "\fBy\fR" for a primary instance, when +multi\-instance support is not enabled. +.IP \fBconfig_directory\fR +The value of the corresponding \fBconfig_directory\fR parameter. .SH "ENVIRONMENT" .na .nf diff --git a/postfix/proto/stop.double-history b/postfix/proto/stop.double-history index 3ad72a5f5..c882e8ed6 100644 --- a/postfix/proto/stop.double-history +++ b/postfix/proto/stop.double-history @@ -254,3 +254,4 @@ proto proto REQUIRETLS_README html global mail_params hc postmap Makefile in postmap postmap c postalias Makefile in postalias postalias c postconf postconf hc postconf postconf_main c + and JSON output Files postmulti postmulti c diff --git a/postfix/proto/stop.spell-cc b/postfix/proto/stop.spell-cc index 425dfb3f4..b4af84a6f 100644 --- a/postfix/proto/stop.spell-cc +++ b/postfix/proto/stop.spell-cc @@ -1889,3 +1889,4 @@ dfhHjnopqvx +joqvx fhHjoqvx joqvx +ajRv diff --git a/postfix/proto/stop.spell-history b/postfix/proto/stop.spell-history index 8713df0fb..1730b5d2c 100644 --- a/postfix/proto/stop.spell-history +++ b/postfix/proto/stop.spell-history @@ -117,3 +117,4 @@ YP Natalenko nocertmatch pgnd +jl diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 772baf79f..260b40159 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20251122" +#define MAIL_RELEASE_DATE "20251124" #define MAIL_VERSION_NUMBER "3.11" #ifdef SNAPSHOT diff --git a/postfix/src/postmulti/Makefile.in b/postfix/src/postmulti/Makefile.in index 61383109e..bb84a46d7 100644 --- a/postfix/src/postmulti/Makefile.in +++ b/postfix/src/postmulti/Makefile.in @@ -1,5 +1,5 @@ SHELL = /bin/sh -SRCS = postmulti.c +SRCS = postmulti.c fake_strcmp.c OBJS = postmulti.o HDRS = TESTSRC = @@ -11,6 +11,7 @@ TESTPROG= PROG = postmulti LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_SO = fake_strcmp.so .c.o:; $(CC) $(CFLAGS) -c $*.c @@ -24,7 +25,44 @@ Makefile: Makefile.in test: $(TESTPROG) -tests: +# Force strcmp(var_config_dir, DEF_CONFIG_DIR) to always succeeed. Outside +# tests, postmulti MUST abort when their values differ. +fake_strcmp.so: fake_strcmp.c + $(CC) $(CFLAGS) -fPIC -shared -o $@ fake_strcmp.c $(LIBS) + +tests: single_test json_tests + +# Single instance, original output format. +single_test: $(PROG) fake_strcmp.so + @echo; echo RUN single_test + rm -f main.cf single_test.tmp + echo config_directory = . >>main.cf + echo command_directory = ../../bin >> main.cf + echo daemon_directory = ../../libexec >> main.cf + echo meta_directory = . >> main.cf + echo shlib_directory = ../../lib >> main.cf + printf "%-15s %-15s %-9s %s\n" - - y . >single_test.tmp + $(SHLIB_ENV) ${VALGRIND} LD_PRELOAD=./fake_strcmp.so MAIL_CONFIG=. \ + ./$(PROG) -l | diff single_test.tmp - + rm -f main.cf single_test.tmp + @echo PASS single_test + +json_tests: json_single_test + +# Single instance, JSON output format. +json_single_test: $(PROG) fake_strcmp.so + @echo; echo RUN json_single_test + rm -f main.cf json_single_test.tmp + echo config_directory = . >>main.cf + echo command_directory = ../../bin >> main.cf + echo daemon_directory = ../../libexec >> main.cf + echo meta_directory = . >> main.cf + echo shlib_directory = ../../lib >> main.cf + echo "{{\"name\": \"-\"},{\"group\": \"-\"},{\"enabled\": \"y\"},{\"config_directory\": \".\"}}" >> json_single_test.tmp + $(SHLIB_ENV) ${VALGRIND} LD_PRELOAD=./fake_strcmp.so MAIL_CONFIG=. \ + ./$(PROG) -lj | diff json_single_test.tmp - + rm -f main.cf json_single_test.tmp + @echo PASS json_single_test root_tests: @@ -34,7 +72,7 @@ update: ../../bin/$(PROG) cp $(PROG) ../../bin clean: - rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -f *.o *core $(PROG) $(TESTPROG) $(LIB_SO) junk tidy: clean diff --git a/postfix/src/postmulti/fake_strcmp.c b/postfix/src/postmulti/fake_strcmp.c new file mode 100644 index 000000000..0f5da471e --- /dev/null +++ b/postfix/src/postmulti/fake_strcmp.c @@ -0,0 +1,42 @@ +/* System library. */ +#include +#include +#include +#include +#include + +/* Utility library. */ +#include + +/* Global library. */ +#include + +static int (*real_strcmp) (const char *, const char *); + +/* find_real_func - libc lookup */ + +static void *find_real_func(const char *name) +{ + void *sym; + + /* + * XXX Casting a data pointer into a function pointer is non-portable. + * Unfortunately, the dlfunc() function is available on FreeBSD but not + * on Linux or Solaris. This is a cosmetic issue except on systems with + * non-flat memory models. + */ + if ((sym = dlsym(RTLD_NEXT, name)) == 0) { + fprintf(stderr, "preload error for %s: %s\n", name, dlerror()); + exit(1); + } + return (sym); +} + +int strcmp(const char *s1, const char *s2) +{ + if (real_strcmp == 0) + real_strcmp = find_real_func("strcmp"); + if (real_strcmp(DEF_CONFIG_DIR, s2) == 0) + return (0); + return real_strcmp(s1, s2); +} diff --git a/postfix/src/postmulti/postmulti.c b/postfix/src/postmulti/postmulti.c index ccb85afa8..dddde2571 100644 --- a/postfix/src/postmulti/postmulti.c +++ b/postfix/src/postmulti/postmulti.c @@ -13,7 +13,7 @@ /* .ti -4 /* \fBIterator mode:\fR /* -/* \fBpostmulti\fR \fB-l\fR [\fB-aRv\fR] [\fB-g \fIgroup\fR] +/* \fBpostmulti\fR \fB-l\fR [\fB-ajRv\fR] [\fB-g \fIgroup\fR] /* [\fB-i \fIname\fR] /* /* \fBpostmulti\fR \fB-p\fR [\fB-av\fR] [\fB-g \fIgroup\fR] @@ -105,6 +105,8 @@ /* List mode /* .ad /* .fi +/* .IP \fB-j\fR +/* Produce JSON output. See JSON OBJECT FORMAT below. /* .IP \fB-l\fR /* List Postfix instances with their instance name, instance /* group name, enable/disable status and configuration directory. @@ -325,6 +327,31 @@ /* Enable verbose logging for debugging purposes. Multiple /* \fB-v\fR options make the software increasingly verbose. /* .RE +/* JSON OBJECT FORMAT +/* .ad +/* .fi +/* The output consists of a sequence of lines. Each line contains +/* one JSON object that represents settings in a corresponding +/* instance's main.cf file. +/* +/* Object members have string values unless indicated otherwise. +/* Programs should ignore members that are not listed here, as +/* members may be added over time. +/* .IP \fBname\fR +/* The value of the corresponding \fBmulti_instance_name\fR +/* parameter, or "\fB-\fR" if no name is specified. +/* .IP \fBgroup\fR +/* The value of the corresponding \fBmulti_instance_group\fR +/* parameter, or "\fB-\fR" if no group is specified. +/* .IP \fBenabled\fR +/* Either "\fBy\fR" or "\fBn\fR", depending on whether the +/* corresponding \fBmulti_instance_enable\fR parameter value is +/* "\fByes\fR" or "\fBno\fR". +/* .sp +/* Note: this reports "\fBy\fR" for a primary instance, when +/* multi-instance support is not enabled. +/* .IP \fBconfig_directory\fR +/* The value of the corresponding \fBconfig_directory\fR parameter. /* ENVIRONMENT /* .ad /* .fi @@ -657,6 +684,12 @@ static int match_instance_selection(INSTANCE *, INST_SELECTION *); #define INSTANCE_NAME(i) ((i)->name ? (i)->name : (i)->config_dir) #define STR(buf) vstring_str(buf) + /* + * JSON support. + */ +static int json_output; +static VSTRING *json_buf; + /* register_claim - register claim or bust */ static void register_claim(const char *instance_path, const char *param_name, @@ -1561,12 +1594,28 @@ static void list_instances(int iter_flags, INST_SELECTION *selection) */ FOREACH_ITERATOR_INSTANCE(iter_flags, entry) { ip = RING_TO_INSTANCE(entry); - if (match_instance_selection(ip, selection)) - vstream_printf("%-15s %-15s %-9s %s\n", - ip->name ? ip->name : "-", - ip->gname ? ip->gname : "-", - ip->enabled ? "y" : "n", - ip->config_dir); + if (match_instance_selection(ip, selection)) { + if (json_output == 0) { + vstream_printf("%-15s %-15s %-9s %s\n", + ip->name ? ip->name : "-", + ip->gname ? ip->gname : "-", + ip->enabled ? "y" : "n", + ip->config_dir); + } else { + vstream_printf("{{\"name\": \"%s\"},", + quote_for_json(json_buf, + ip->name ? ip->name : "-", -1)); + vstream_printf("{\"group\": \"%s\"},", + quote_for_json(json_buf, + ip->gname ? ip->gname : "-", 1)); + vstream_printf("{\"enabled\": \"%s\"},", + quote_for_json(json_buf, + ip->enabled ? "y" : "n", 1)); + vstream_printf("{\"config_directory\": \"%s\"}}\n", + quote_for_json(json_buf, + ip->config_dir, -1)); + } + } } if (vstream_fflush(VSTREAM_OUT)) msg_fatal("error writing output: %m"); @@ -1734,7 +1783,7 @@ int main(int argc, char **argv) * Parse switches. Move the above mail_conf_read() block after this loop, * if any command-line option can affect parameter processing. */ - while ((ch = GETOPT(argc, argv, "ae:g:i:G:I:lpRvx")) > 0) { + while ((ch = GETOPT(argc, argv, "ae:g:i:jG:I:lpRvx")) > 0) { switch (ch) { default: usage(argv[0]); @@ -1771,6 +1820,12 @@ int main(int argc, char **argv) selection.type = INST_SEL_NAME; selection.name = optarg; break; + case 'j': + if (json_output == 0) { + json_output = 1; + json_buf = vstring_alloc(100); + } + break; case 'G': if (assignment.gname != 0) msg_fatal("Specify at most one '-G' option"); @@ -1845,6 +1900,8 @@ int main(int argc, char **argv) msg_fatal("Parameter overrides not valid with '-e %s'", EDIT_CMD_STR(cmd_mode)); } + if (json_output && (cmd_mode & ITER_CMD_LIST) == 0) + msg_fatal("JSON output available only with '-l'"); /* * Sanity checks. -- 2.47.3