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:
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
-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
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.
<b>Iterator mode:</b>
- <b>postmulti -l</b> [<b>-aRv</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>]
+ <b>postmulti -l</b> [<b>-ajRv</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>]
<b>postmulti -p</b> [<b>-av</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>] <i>postfix-command...</i>
This option cannot be used with <b>-p</b>.
<b>List mode</b>
+ <b>-j</b> Produce JSON output. See JSON OBJECT FORMAT below.
+
<b>-l</b> List Postfix instances with their instance name, instance group
name, enable/disable status and configuration directory.
<b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
options make the software increasingly verbose.
+<b><a name="json_object_format">JSON OBJECT FORMAT</a></b>
+ The output consists of a sequence of lines. Each line contains one JSON
+ object that represents settings in a corresponding instance's <a href="postconf.5.html">main.cf</a>
+ 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.
+
+ <b>name</b> The value of the corresponding <b><a href="postconf.5.html#multi_instance_name">multi_instance_name</a></b> parameter, or
+ "<b>-</b>" if no name is specified.
+
+ <b>group</b> The value of the corresponding <b><a href="postconf.5.html#multi_instance_group">multi_instance_group</a></b> parameter,
+ or "<b>-</b>" if no group is specified.
+
+ <b>enabled</b>
+ Either "<b>y</b>" or "<b>n</b>", depending on whether the corresponding
+ <b><a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a></b> parameter value is "<b>yes</b>" or "<b>no</b>".
+
+ Note: this reports "<b>y</b>" for a primary instance, when
+ multi-instance support is not enabled.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a></b>
+ The value of the corresponding <b><a href="postconf.5.html#config_directory">config_directory</a></b> parameter.
+
<b><a name="environment">ENVIRONMENT</a></b>
The <a href="postmulti.1.html"><b>postmulti</b>(1)</a> command exports the following environment variables
before executing the requested <i>command</i> for a given instance:
.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]
.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.
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
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
+joqvx
fhHjoqvx
joqvx
+ajRv
Natalenko
nocertmatch
pgnd
+jl
* 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
SHELL = /bin/sh
-SRCS = postmulti.c
+SRCS = postmulti.c fake_strcmp.c
OBJS = postmulti.o
HDRS =
TESTSRC =
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
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:
cp $(PROG) ../../bin
clean:
- rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -f *.o *core $(PROG) $(TESTPROG) $(LIB_SO) junk
tidy: clean
--- /dev/null
+/* System library. */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+/* Utility library. */
+#include <msg.h>
+
+/* Global library. */
+#include <mail_params.h>
+
+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);
+}
/* .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]
/* 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.
/* 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
#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,
*/
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");
* 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]);
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");
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.