From 0bba6eeda457cc6fafd5f01ff7c1edf73e022fa1 Mon Sep 17 00:00:00 2001 From: Wietse Z Venema Date: Sat, 22 Nov 2025 00:00:00 -0500 Subject: [PATCH] postfix-3.11-20251122 --- postfix/HISTORY | 21 +++ postfix/html/postalias.1.html | 93 +++++------ postfix/html/postconf.1.html | 86 +++++----- postfix/html/postmap.1.html | 101 ++++++------ postfix/man/man1/postalias.1 | 7 +- postfix/man/man1/postconf.1 | 13 +- postfix/man/man1/postmap.1 | 7 +- postfix/proto/stop.double-history | 3 + postfix/proto/stop.spell-cc | 7 + postfix/src/global/mail_version.h | 2 +- postfix/src/postalias/Makefile.in | 37 ++++- postfix/src/postalias/postalias.c | 46 +++++- postfix/src/postconf/Makefile.in | 125 ++++++++++++++- postfix/src/postconf/postconf.c | 33 ++-- postfix/src/postconf/postconf.h | 3 + postfix/src/postconf/postconf_builtin.c | 8 +- postfix/src/postconf/postconf_edit.c | 6 +- postfix/src/postconf/postconf_main.c | 12 +- postfix/src/postconf/postconf_master.c | 198 +++++++++++++++++++++++- postfix/src/postconf/test80.ref | 3 + postfix/src/postconf/test81.ref | 3 + postfix/src/postconf/test82.ref | 2 + postfix/src/postconf/test83.ref | 1 + postfix/src/postconf/test84.ref | 16 ++ postfix/src/postconf/test85.ref | 8 + postfix/src/postconf/test86.ref | 3 + postfix/src/postconf/test87.ref | 2 + postfix/src/postmap/Makefile.in | 59 ++++++- postfix/src/postmap/postmap.c | 54 ++++++- postfix/src/util/quote_for_json.c | 113 +++++++++++--- postfix/src/util/stringops.h | 1 + 31 files changed, 878 insertions(+), 195 deletions(-) create mode 100644 postfix/src/postconf/test80.ref create mode 100644 postfix/src/postconf/test81.ref create mode 100644 postfix/src/postconf/test82.ref create mode 100644 postfix/src/postconf/test83.ref create mode 100644 postfix/src/postconf/test84.ref create mode 100644 postfix/src/postconf/test85.ref create mode 100644 postfix/src/postconf/test86.ref create mode 100644 postfix/src/postconf/test87.ref diff --git a/postfix/HISTORY b/postfix/HISTORY index 9d0223734..21f71bbfd 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -30052,3 +30052,24 @@ Apologies for any names omitted. Miscellaneous documentation fixes. Files: proto/postconf.proto, mantools/postconf2html. + +20251120 + + Bugfix (defect introduced: Postfix 2.9, date: 20120307): + segfault with duplicate parameter name in "postconf -X" or + "postconf -#'. File: postconf/postconf_edit.c. + +20251121 + + Feature: postconf '-j' option for JSON output. Files: + postconf/postconf.[hc], postconf/postconf_main.c, + postconf/postconf_json.c, postconf/test8[0-7].ref, + postconf/Makefile.in, util/quote_for_json.c, + util/stringops.h. + +20251122 + + Feature: basic JSON output support with "postalias -j" and + "postmap -j". See respective manpages for details. Files: + postmap/Makefile.in, postmap/postmap.c, postalias/Makefile.in, + postalias/postalias.c. diff --git a/postfix/html/postalias.1.html b/postfix/html/postalias.1.html index ed2fcbc9e..b6ba86167 100644 --- a/postfix/html/postalias.1.html +++ b/postfix/html/postalias.1.html @@ -11,7 +11,7 @@ POSTALIAS(1) POSTALIAS(1) postalias - Postfix alias database maintenance SYNOPSIS - postalias [-Nfinoprsuvw] [-c config_dir] [-d key] [-q key] + postalias [-Nfijnoprsuvw] [-c config_dir] [-d key] [-q key] [file_type:]file_name ... DESCRIPTION @@ -62,52 +62,57 @@ POSTALIAS(1) POSTALIAS(1) truncate an existing database. By default, postalias(1) creates a new database from the entries in file_name. - -N Include the terminating null character that terminates lookup - keys and values. By default, postalias(1) does whatever is the + -j JSON output. Format the output from -q and -s as one {"key": + "value"} object per line. + + This feature is available in Postfix version 3.11 and later. + + -N Include the terminating null character that terminates lookup + keys and values. By default, postalias(1) does whatever is the default for the host operating system. - -n Don't include the terminating null character that terminates - lookup keys and values. By default, postalias(1) does whatever + -n Don't include the terminating null character that terminates + lookup keys and values. By default, postalias(1) does whatever is the default for the host operating system. - -o Do not release root privileges when processing a non-root input + -o Do not release root privileges when processing a non-root input file. By default, postalias(1) drops root privileges and runs as the source file owner instead. - -p Do not inherit the file access permissions from the input file - when creating a new file. Instead, create a new file with + -p Do not inherit the file access permissions from the input file + when creating a new file. Instead, create a new file with default access permissions (mode 0644). - -q key Search the specified maps for key and write the first value - found to the standard output stream. The exit status is zero + -q key Search the specified maps for key and write the first value + found to the standard output stream. The exit status is zero when the requested information was found. - Note: this performs a single query with the key as specified, - and does not make iterative queries with substrings of the key + Note: this performs a single query with the key as specified, + and does not make iterative queries with substrings of the key as described in the aliases(5) manual page. - If a key value of - is specified, the program reads key values + If a key value of - is specified, the program reads key values from the standard input stream and writes one line of key: value output for each key that was found. The exit status is zero when at least one of the requested keys was found. - -r When updating a table, do not complain about attempts to update + -r When updating a table, do not complain about attempts to update existing entries, and make those updates anyway. -s Retrieve all database elements, and write one line of key: value - output for each element. The elements are printed in database - order, which is not necessarily the same as the original input - order. This feature is available in Postfix version 2.2 and + output for each element. The elements are printed in database + order, which is not necessarily the same as the original input + order. This feature is available in Postfix version 2.2 and later, and is not available for all database types. - -u Disable UTF-8 support. UTF-8 support is enabled by default when - "smtputf8_enable = yes". It requires that keys and values are + -u Disable UTF-8 support. UTF-8 support is enabled by default when + "smtputf8_enable = yes". It requires that keys and values are valid UTF-8 strings. - -v Enable verbose logging for debugging purposes. Multiple -v + -v Enable verbose logging for debugging purposes. Multiple -v options make the software increasingly verbose. - -w When updating a table, do not complain about attempts to update + -w When updating a table, do not complain about attempts to update existing entries, and ignore those attempts. Arguments: @@ -116,39 +121,39 @@ POSTALIAS(1) POSTALIAS(1) The database type. To find out what types are supported, use the "postconf -m" command. - The postalias(1) command can query any supported file type, but + The postalias(1) command can query any supported file type, but it can create only the following file types: - btree The output is a btree file, named file_name.db. This is + btree The output is a btree file, named file_name.db. This is available on systems with support for db databases. - cdb The output is one file named file_name.cdb. This is + cdb The output is one file named file_name.cdb. This is available on systems with support for cdb databases. dbm The output consists of two files, named file_name.pag and file_name.dir. This is available on systems with support for dbm databases. - fail A table that reliably fails all requests. The lookup ta- - ble name is used for logging only. This table exists to + fail A table that reliably fails all requests. The lookup ta- + ble name is used for logging only. This table exists to simplify Postfix error tests. hash The output is a hashed file, named file_name.db. This is available on systems with support for db databases. - lmdb The output is a btree-based file, named file_name.lmdb. - lmdb supports concurrent writes and reads from different + lmdb The output is a btree-based file, named file_name.lmdb. + lmdb supports concurrent writes and reads from different processes, unlike other supported file-based tables. - This is available on systems with support for lmdb data- + This is available on systems with support for lmdb data- bases. sdbm The output consists of two files, named file_name.pag and file_name.dir. This is available on systems with support for sdbm databases. - When no file_type is specified, the software uses the database - type specified via the default_database_type configuration - parameter. The default value for this parameter depends on the + When no file_type is specified, the software uses the database + type specified via the default_database_type configuration + parameter. The default value for this parameter depends on the host environment. file_name @@ -156,11 +161,11 @@ POSTALIAS(1) POSTALIAS(1) base. DIAGNOSTICS - Problems are logged to the standard error stream and to syslogd(8) or - postlogd(8). No output means that no problems were detected. Duplicate + Problems are logged to the standard error stream and to syslogd(8) or + postlogd(8). No output means that no problems were detected. Duplicate entries are skipped and are flagged with a warning. - postalias(1) terminates with zero exit status in case of success + postalias(1) terminates with zero exit status in case of success (including successful "postalias -q" lookup) and terminates with non-zero exit status in case of failure. @@ -172,22 +177,22 @@ POSTALIAS(1) POSTALIAS(1) Enable verbose logging for debugging purposes. CONFIGURATION PARAMETERS - The following main.cf parameters are especially relevant to this pro- + The following main.cf parameters are especially relevant to this pro- gram. - The text below provides only a parameter summary. See postconf(5) for + The text below provides only a parameter summary. See postconf(5) for more details including examples. alias_database (see 'postconf -d' output) - The alias databases for local(8) delivery that are updated with + The alias databases for local(8) delivery that are updated with "newaliases" or with "sendmail -bi". config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. berkeley_db_create_buffer_size (16777216) - The per-table I/O buffer size for programs that create Berkeley + The per-table I/O buffer size for programs that create Berkeley DB hash or btree tables. berkeley_db_read_buffer_size (131072) @@ -199,19 +204,19 @@ POSTALIAS(1) POSTALIAS(1) and postmap(1) commands. import_environment (see 'postconf -d' output) - The list of environment variables that a privileged Postfix - process will import from a non-Postfix parent process, or + The list of environment variables that a privileged Postfix + process will import from a non-Postfix parent process, or name=value environment overrides. smtputf8_enable (yes) - Enable preliminary SMTPUTF8 support for the protocols described + Enable preliminary SMTPUTF8 support for the protocols described in RFC 6531, RFC 6532, and RFC 6533. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - A prefix that is prepended to the process name in syslog + A prefix that is prepended to the process name in syslog records, so that, for example, "smtpd" becomes "prefix/smtpd". Available in Postfix 2.11 and later: diff --git a/postfix/html/postconf.1.html b/postfix/html/postconf.1.html index 67d256e06..9fe7e2704 100644 --- a/postfix/html/postconf.1.html +++ b/postfix/html/postconf.1.html @@ -13,7 +13,7 @@ POSTCONF(1) POSTCONF(1) SYNOPSIS Managing main.cf: - postconf [-dfhHnopqvx] [-c config_dir] [-C class,...] [parameter ...] + postconf [-dfhHjnopqvx] [-c config_dir] [-C class,...] [parameter ...] postconf [-epv] [-c config_dir] parameter=value ... @@ -23,7 +23,7 @@ POSTCONF(1) POSTCONF(1) Managing master.cf service entries: - postconf -M [-foqvx] [-c config_dir] [service[/type] ...] + postconf -M [-joqvx] [-c config_dir] [service[/type] ...] postconf -M [-ev] [-c config_dir] service/type=value ... @@ -33,13 +33,14 @@ POSTCONF(1) POSTCONF(1) Managing master.cf service fields: - postconf -F [-fhHoqvx] [-c config_dir] [service[/type[/field]] ...] + postconf -F [-fhHjoqvx] [-c config_dir] [service[/type[/field]] ...] postconf -F [-ev] [-c config_dir] service/type/field=value ... Managing master.cf service parameters: - postconf -P [-fhHoqvx] [-c config_dir] [service[/type[/parameter]] ...] + postconf -P [-fhHjoqvx] [-c config_dir] [service[/type[/parameter]] + ...] postconf -P [-ev] [-c config_dir] service/type/parameter=value ... @@ -61,46 +62,46 @@ POSTCONF(1) POSTCONF(1) DESCRIPTION By default, the postconf(1) command displays the values of main.cf con- - figuration parameters, and warns about possible mis-typed parameter - names (Postfix 2.9 and later). The command can also change main.cf + figuration parameters, and warns about possible mis-typed parameter + names (Postfix 2.9 and later). The command can also change main.cf configuration parameter values, or display other configuration informa- tion about the Postfix mail system. Options: - -a List the available SASL plug-in types for the Postfix SMTP - server. The plug-in type is selected with the smtpd_sasl_type - configuration parameter by specifying one of the names listed + -a List the available SASL plug-in types for the Postfix SMTP + server. The plug-in type is selected with the smtpd_sasl_type + configuration parameter by specifying one of the names listed below. - cyrus This server plug-in is available when Postfix is built + cyrus This server plug-in is available when Postfix is built with Cyrus SASL support. dovecot This server plug-in uses the Dovecot authentication - server, and is available when Postfix is built with any + server, and is available when Postfix is built with any form of SASL support. This feature is available with Postfix 2.3 and later. - -A List the available SASL plug-in types for the Postfix SMTP + -A List the available SASL plug-in types for the Postfix SMTP client. The plug-in type is selected with the smtp_sasl_type or lmtp_sasl_type configuration parameters by specifying one of the names listed below. - cyrus This client plug-in is available when Postfix is built + cyrus This client plug-in is available when Postfix is built with Cyrus SASL support. This feature is available with Postfix 2.3 and later. -b [template_file] Display the message text that appears at the beginning of deliv- - ery status notification (DSN) messages, expanding $name expres- + ery status notification (DSN) messages, expanding $name expres- sions with actual values as described in bounce(5). - To override the bounce_template_file parameter setting, specify - a template file name at the end of the "postconf -b" command - line. Specify an empty file name to display built-in templates + To override the bounce_template_file parameter setting, specify + a template file name at the end of the "postconf -b" command + line. Specify an empty file name to display built-in templates (in shell language: ""). This feature is available with Postfix 2.3 and later. @@ -110,7 +111,7 @@ POSTCONF(1) POSTCONF(1) of the default configuration directory. -C class,... - When displaying main.cf parameters, select only parameters from + When displaying main.cf parameters, select only parameters from the specified class(es): builtin @@ -128,37 +129,37 @@ POSTCONF(1) POSTCONF(1) This feature is available with Postfix 2.9 and later. - -d Print main.cf default parameter settings instead of actual set- - tings. Specify -df to fold long lines for human readability + -d Print main.cf default parameter settings instead of actual set- + tings. Specify -df to fold long lines for human readability (Postfix 2.9 and later). - -e Edit the main.cf configuration file, and update parameter set- - tings with the "name=value" pairs on the postconf(1) command + -e Edit the main.cf configuration file, and update parameter set- + tings with the "name=value" pairs on the postconf(1) command line. - With -M, edit the master.cf configuration file, and replace one - or more service entries with new values as specified with "ser- + With -M, edit the master.cf configuration file, and replace one + or more service entries with new values as specified with "ser- vice/type=value" on the postconf(1) command line. - With -F, edit the master.cf configuration file, and replace one - or more service fields with new values as specified with "ser- - vice/type/field=value" on the postconf(1) command line. Cur- - rently, the "command" field contains the command name and com- + With -F, edit the master.cf configuration file, and replace one + or more service fields with new values as specified with "ser- + vice/type/field=value" on the postconf(1) command line. Cur- + rently, the "command" field contains the command name and com- mand arguments. This may change in the near future, so that the "command" field contains only the command name, and a new "argu- ments" pseudofield contains the command arguments. - With -P, edit the master.cf configuration file, and add or - update one or more service parameter settings (-o parame- - ter=value settings) with new values as specified with "ser- + With -P, edit the master.cf configuration file, and add or + update one or more service parameter settings (-o parame- + ter=value settings) with new values as specified with "ser- vice/type/parameter=value" on the postconf(1) command line. In all cases the file is copied to a temporary file then renamed - into place. Specify quotes to protect special characters and + into place. Specify quotes to protect special characters and whitespace on the postconf(1) command line. - The -e option is no longer needed with Postfix version 2.8 and - later, as it is assumed whenever a value is specified (empty or + The -e option is no longer needed with Postfix version 2.8 and + later, as it is assumed whenever a value is specified (empty or non-empty). -f Fold long lines when printing main.cf or master.cf configuration @@ -167,24 +168,29 @@ POSTCONF(1) POSTCONF(1) This feature is available with Postfix 2.9 and later. -F Show master.cf per-entry field settings (by default all services - and all fields), formatted as "service/type/field=value", one + and all fields), formatted as "service/type/field=value", one per line. Specify -Ff to fold long lines. - Specify one or more "service/type/field" instances on the post- - conf(1) command line to limit the output to fields of interest. - Trailing parameter name or service type fields that are omitted + Specify one or more "service/type/field" instances on the post- + conf(1) command line to limit the output to fields of interest. + Trailing parameter name or service type fields that are omitted will be handled as "*" wildcard fields. This feature is available with Postfix 2.11 and later. - -h Show parameter or attribute values without the "name = " label + -h Show parameter or attribute values without the "name = " label that normally precedes the value. - -H Show parameter or attribute names without the " = value" that + -H Show parameter or attribute names without the " = value" that normally follows the name. This feature is available with Postfix 3.1 and later. + -j JSON output. Format main.cf or master.cf settings as one {"key": + "value"} object per line. + + This feature is available with Postfix 3.11 and later. + -l List the names of all supported mailbox locking methods. Post- fix supports the following methods: diff --git a/postfix/html/postmap.1.html b/postfix/html/postmap.1.html index a3c4bcf7b..ed0d8b28f 100644 --- a/postfix/html/postmap.1.html +++ b/postfix/html/postmap.1.html @@ -11,7 +11,7 @@ POSTMAP(1) POSTMAP(1) postmap - Postfix lookup table management SYNOPSIS - postmap [-bfFhimnNoprsuUvw] [-c config_dir] [-d key] [-q key] + postmap [-bfFhijmnNoprsuUvw] [-c config_dir] [-d key] [-q key] [file_type:]file_name ... DESCRIPTION @@ -130,62 +130,67 @@ POSTMAP(1) POSTMAP(1) truncate an existing database. By default, postmap(1) creates a new database from the entries in file_name. + -j JSON output. Format the output from -q and -s as one {"key": + "value"} object per line. + + This feature is available in Postfix version 3.11 and later. + -m Enable MIME parsing with "-b" and "-h". This feature is available in Postfix version 2.6 and later. - -N Include the terminating null character that terminates lookup - keys and values. By default, postmap(1) does whatever is the + -N Include the terminating null character that terminates lookup + keys and values. By default, postmap(1) does whatever is the default for the host operating system. - -n Don't include the terminating null character that terminates - lookup keys and values. By default, postmap(1) does whatever is + -n Don't include the terminating null character that terminates + lookup keys and values. By default, postmap(1) does whatever is the default for the host operating system. - -o Do not release root privileges when processing a non-root input - file. By default, postmap(1) drops root privileges and runs as + -o Do not release root privileges when processing a non-root input + file. By default, postmap(1) drops root privileges and runs as the source file owner instead. - -p Do not inherit the file access permissions from the input file - when creating a new file. Instead, create a new file with + -p Do not inherit the file access permissions from the input file + when creating a new file. Instead, create a new file with default access permissions (mode 0644). - -q key Search the specified maps for key and write the first value - found to the standard output stream. The exit status is zero + -q key Search the specified maps for key and write the first value + found to the standard output stream. The exit status is zero when the requested information was found. - Note: this performs a single query with the key as specified, - and does not make iterative queries with substrings of the key - as described for access(5), canonical(5), transport(5), vir- + Note: this performs a single query with the key as specified, + and does not make iterative queries with substrings of the key + as described for access(5), canonical(5), transport(5), vir- tual(5) and other Postfix table-driven features. - If a key value of - is specified, the program reads key values - from the standard input stream and writes one line of key value + If a key value of - is specified, the program reads key values + from the standard input stream and writes one line of key value output for each key that was found. The exit status is zero when at least one of the requested keys was found. - -r When updating a table, do not complain about attempts to update + -r When updating a table, do not complain about attempts to update existing entries, and make those updates anyway. - -s Retrieve all database elements, and write one line of key value - output for each element. The elements are printed in database - order, which is not necessarily the same as the original input + -s Retrieve all database elements, and write one line of key value + output for each element. The elements are printed in database + order, which is not necessarily the same as the original input order. - This feature is available in Postfix version 2.2 and later, and + This feature is available in Postfix version 2.2 and later, and is not available for all database types. - -u Disable UTF-8 support. UTF-8 support is enabled by default when - "smtputf8_enable = yes". It requires that keys and values are + -u Disable UTF-8 support. UTF-8 support is enabled by default when + "smtputf8_enable = yes". It requires that keys and values are valid UTF-8 strings. -U With "smtputf8_enable = yes", force UTF-8 syntax checks with the -b and -h options. - -v Enable verbose logging for debugging purposes. Multiple -v + -v Enable verbose logging for debugging purposes. Multiple -v options make the software increasingly verbose. - -w When updating a table, do not complain about attempts to update + -w When updating a table, do not complain about attempts to update existing entries, and ignore those attempts. Arguments: @@ -197,38 +202,38 @@ POSTMAP(1) POSTMAP(1) The postmap(1) command can query any supported file type, but it can create only the following file types: - btree The output file is a btree file, named file_name.db. - This is available on systems with support for db data- + btree The output file is a btree file, named file_name.db. + This is available on systems with support for db data- bases. - cdb The output consists of one file, named file_name.cdb. - This is available on systems with support for cdb data- + cdb The output consists of one file, named file_name.cdb. + This is available on systems with support for cdb data- bases. dbm The output consists of two files, named file_name.pag and file_name.dir. This is available on systems with support for dbm databases. - fail A table that reliably fails all requests. The lookup ta- - ble name is used for logging only. This table exists to + fail A table that reliably fails all requests. The lookup ta- + ble name is used for logging only. This table exists to simplify Postfix error tests. - hash The output file is a hashed file, named file_name.db. - This is available on systems with support for db data- + hash The output file is a hashed file, named file_name.db. + This is available on systems with support for db data- bases. - lmdb The output is a btree-based file, named file_name.lmdb. - lmdb supports concurrent writes and reads from different + lmdb The output is a btree-based file, named file_name.lmdb. + lmdb supports concurrent writes and reads from different processes, unlike other supported file-based tables. - This is available on systems with support for lmdb data- + This is available on systems with support for lmdb data- bases. sdbm The output consists of two files, named file_name.pag and file_name.dir. This is available on systems with support for sdbm databases. - When no file_type is specified, the software uses the database - type specified via the default_database_type configuration + When no file_type is specified, the software uses the database + type specified via the default_database_type configuration parameter. file_name @@ -236,12 +241,12 @@ POSTMAP(1) POSTMAP(1) base. DIAGNOSTICS - Problems are logged to the standard error stream and to syslogd(8) or + Problems are logged to the standard error stream and to syslogd(8) or postlogd(8). No output means that no problems were detected. Duplicate entries are skipped and are flagged with a warning. postmap(1) terminates with zero exit status in case of success (includ- - ing successful "postmap -q" lookup) and terminates with non-zero exit + ing successful "postmap -q" lookup) and terminates with non-zero exit status in case of failure. ENVIRONMENT @@ -252,12 +257,12 @@ POSTMAP(1) POSTMAP(1) Enable verbose logging for debugging purposes. CONFIGURATION PARAMETERS - The following main.cf parameters are especially relevant to this pro- - gram. The text below provides only a parameter summary. See post- + The following main.cf parameters are especially relevant to this pro- + gram. The text below provides only a parameter summary. See post- conf(5) for more details including examples. berkeley_db_create_buffer_size (16777216) - The per-table I/O buffer size for programs that create Berkeley + The per-table I/O buffer size for programs that create Berkeley DB hash or btree tables. berkeley_db_read_buffer_size (131072) @@ -265,7 +270,7 @@ POSTMAP(1) POSTMAP(1) hash or btree tables. config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. default_database_type (see 'postconf -d' output) @@ -273,19 +278,19 @@ POSTMAP(1) POSTMAP(1) and postmap(1) commands. import_environment (see 'postconf -d' output) - The list of environment variables that a privileged Postfix - process will import from a non-Postfix parent process, or + The list of environment variables that a privileged Postfix + process will import from a non-Postfix parent process, or name=value environment overrides. smtputf8_enable (yes) - Enable preliminary SMTPUTF8 support for the protocols described + Enable preliminary SMTPUTF8 support for the protocols described in RFC 6531, RFC 6532, and RFC 6533. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - A prefix that is prepended to the process name in syslog + A prefix that is prepended to the process name in syslog records, so that, for example, "smtpd" becomes "prefix/smtpd". Available in Postfix 2.11 and later: diff --git a/postfix/man/man1/postalias.1 b/postfix/man/man1/postalias.1 index 4c7f02bd1..10ee1fb51 100644 --- a/postfix/man/man1/postalias.1 +++ b/postfix/man/man1/postalias.1 @@ -9,7 +9,7 @@ Postfix alias database maintenance .na .nf .fi -\fBpostalias\fR [\fB\-Nfinoprsuvw\fR] [\fB\-c \fIconfig_dir\fR] +\fBpostalias\fR [\fB\-Nfijnoprsuvw\fR] [\fB\-c \fIconfig_dir\fR] [\fB\-d \fIkey\fR] [\fB\-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... .SH DESCRIPTION @@ -62,6 +62,11 @@ is controlled by appending a flag to a pattern. Incremental mode. Read entries from standard input and do not truncate an existing database. By default, \fBpostalias\fR(1) creates a new database from the entries in \fIfile_name\fR. +.IP \fB\-j\fR +JSON output. Format the output from \fB\-q\fR and \fB\-s\fR +as one \fB{"\fIkey\fB": "\fIvalue\fB"}\fR object per line. +.sp +This feature is available in Postfix version 3.11 and later. .IP \fB\-N\fR Include the terminating null character that terminates lookup keys and values. By default, \fBpostalias\fR(1) does whatever diff --git a/postfix/man/man1/postconf.1 b/postfix/man/man1/postconf.1 index 5b8ca5382..bcc3111a6 100644 --- a/postfix/man/man1/postconf.1 +++ b/postfix/man/man1/postconf.1 @@ -12,7 +12,7 @@ Postfix configuration utility .ti -4 \fBManaging main.cf:\fR -\fBpostconf\fR [\fB\-dfhHnopqvx\fR] [\fB\-c \fIconfig_dir\fR] +\fBpostconf\fR [\fB\-dfhHjnopqvx\fR] [\fB\-c \fIconfig_dir\fR] [\fB\-C \fIclass,...\fR] [\fIparameter ...\fR] \fBpostconf\fR [\fB\-epv\fR] [\fB\-c \fIconfig_dir\fR] @@ -27,7 +27,7 @@ Postfix configuration utility .ti -4 \fBManaging master.cf service entries:\fR -\fBpostconf\fR \fB\-M\fR [\fB\-foqvx\fR] [\fB\-c \fIconfig_dir\fR] +\fBpostconf\fR \fB\-M\fR [\fB\-joqvx\fR] [\fB\-c \fIconfig_dir\fR] [\fIservice\fR[\fB/\fItype\fR]\fI ...\fR] \fBpostconf\fR \fB\-M\fR [\fB\-ev\fR] [\fB\-c \fIconfig_dir\fR] @@ -42,7 +42,7 @@ Postfix configuration utility .ti -4 \fBManaging master.cf service fields:\fR -\fBpostconf\fR \fB\-F\fR [\fB\-fhHoqvx\fR] [\fB\-c \fIconfig_dir\fR] +\fBpostconf\fR \fB\-F\fR [\fB\-fhHjoqvx\fR] [\fB\-c \fIconfig_dir\fR] [\fIservice\fR[\fB/\fItype\fR[\fB/\fIfield\fR]]\fI ...\fR] \fBpostconf\fR \fB\-F\fR [\fB\-ev\fR] [\fB\-c \fIconfig_dir\fR] @@ -51,7 +51,7 @@ Postfix configuration utility .ti -4 \fBManaging master.cf service parameters:\fR -\fBpostconf\fR \fB\-P\fR [\fB\-fhHoqvx\fR] [\fB\-c \fIconfig_dir\fR] +\fBpostconf\fR \fB\-P\fR [\fB\-fhHjoqvx\fR] [\fB\-c \fIconfig_dir\fR] [\fIservice\fR[\fB/\fItype\fR[\fB/\fIparameter\fR]]\fI ...\fR] \fBpostconf\fR \fB\-P\fR [\fB\-ev\fR] [\fB\-c \fIconfig_dir\fR] @@ -216,6 +216,11 @@ Show parameter or attribute names without the " = \fIvalue\fR" that normally follows the name. This feature is available with Postfix 3.1 and later. +.IP \fB\-j\fR +JSON output. Format main.cf or master.cf settings as one +\fB{"\fIkey\fB": "\fIvalue\fB"}\fR object per line. + +This feature is available with Postfix 3.11 and later. .IP \fB\-l\fR List the names of all supported mailbox locking methods. Postfix supports the following methods: diff --git a/postfix/man/man1/postmap.1 b/postfix/man/man1/postmap.1 index 1cd785e05..3dd7a3864 100644 --- a/postfix/man/man1/postmap.1 +++ b/postfix/man/man1/postmap.1 @@ -9,7 +9,7 @@ Postfix lookup table management .na .nf .fi -\fBpostmap\fR [\fB\-bfFhimnNoprsuUvw\fR] [\fB\-c \fIconfig_dir\fR] +\fBpostmap\fR [\fB\-bfFhijmnNoprsuUvw\fR] [\fB\-c \fIconfig_dir\fR] [\fB\-d \fIkey\fR] [\fB\-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... .SH DESCRIPTION @@ -146,6 +146,11 @@ This feature is available in Postfix version 2.6 and later. Incremental mode. Read entries from standard input and do not truncate an existing database. By default, \fBpostmap\fR(1) creates a new database from the entries in \fBfile_name\fR. +.IP \fB\-j\fR +JSON output. Format the output from \fB\-q\fR and \fB\-s\fR +as one \fB{"\fIkey\fB": "\fIvalue\fB"}\fR object per line. +.sp +This feature is available in Postfix version 3.11 and later. .IP \fB\-m\fR Enable MIME parsing with "\fB\-b\fR" and "\fB\-h\fR". .sp diff --git a/postfix/proto/stop.double-history b/postfix/proto/stop.double-history index bb2e2418f..3ad72a5f5 100644 --- a/postfix/proto/stop.double-history +++ b/postfix/proto/stop.double-history @@ -251,3 +251,6 @@ proto proto REQUIRETLS_README html global mail_params hc smtpd smtpd c smtp smtp_connect c smtp smtp_proto c util mac_expand ref proto postconf proto proto Makefile in smtp lmtp_params c smtp smtp h smtp smtp_params c + postmap Makefile in postmap postmap c postalias Makefile in + postalias postalias c + postconf postconf hc postconf postconf_main c diff --git a/postfix/proto/stop.spell-cc b/postfix/proto/stop.spell-cc index 28311c39f..425dfb3f4 100644 --- a/postfix/proto/stop.spell-cc +++ b/postfix/proto/stop.spell-cc @@ -1882,3 +1882,10 @@ allalnumus Christophe Kalt stdlib +Nfijnoprsuvw +bfFhijmnNoprsuUvw +dfhHjnopqvx ++fhHjoqvx ++joqvx +fhHjoqvx +joqvx diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 0eb572e7d..772baf79f 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 "20251118" +#define MAIL_RELEASE_DATE "20251122" #define MAIL_VERSION_NUMBER "3.11" #ifdef SNAPSHOT diff --git a/postfix/src/postalias/Makefile.in b/postfix/src/postalias/Makefile.in index 751a55995..c21bdeea1 100644 --- a/postfix/src/postalias/Makefile.in +++ b/postfix/src/postalias/Makefile.in @@ -24,7 +24,7 @@ Makefile: Makefile.in update: ../../bin/$(PROG) -tests: test1 test2 fail_test mode_conflict_test +tests: test1 test2 fail_test mode_conflict_test json_tests root_tests: @@ -74,6 +74,41 @@ mode_conflict_test: $(PROG) mode_conflict_test.in mode_conflict_test.ref diff mode_conflict_test.ref mode_conflict_test.tmp rm -f mode_conflict_test.tmp main.cf +json_tests: json_query_test json_queries_test json_sequence_test + +json_query_test: $(PROG) + # Exercise code in postalias_query(). + @echo ; echo RUN json_query_test + echo '{"postmaster": "root"}' >json_query.tmp + $(SHLIB_ENV) ${VALGRIND} ./$(PROG) -jq postmaster \ + 'inline:{{postmaster = root}}' | diff json_query.tmp - + ($(SHLIB_ENV) ${VALGRIND} ./$(PROG) -jq other \ + 'inline:{{postmaster = root}}' || exit 0) | diff /dev/null - + rm -f json_query.tmp + @echo PASS json_query_test + +json_queries_test: $(PROG) + # Exercise json formatting in postalias_queries(). + @echo ; echo RUN json_queries_test + echo '{"postmaster": "root"}' >json_queries.tmp + (echo postmaster; echo other) | $(SHLIB_ENV) ${VALGRIND} \ + ./$(PROG) -jq - 'inline:{{postmaster = root}}' | \ + diff json_queries.tmp - + rm -f json_queries.tmp + @echo PASS json_queries_test + +json_sequence_test: $(PROG) + # Exercise json formatting in postalias_seq(). + @echo ; echo RUN json_sequence_test + rm -f json_sequence.tmp + (echo '{"postmaster": "root"}'; echo '{"root": "real-user"}') |\ + sort >json_sequence.tmp + $(SHLIB_ENV) ${VALGRIND} ./$(PROG) -js \ + 'inline:{{postmaster = root} {root=real-user}}' | sort | \ + diff json_sequence.tmp - + rm -f json_sequence.tmp + @echo PASS json_sequence_test + ../../bin/$(PROG): $(PROG) cp $(PROG) ../../bin diff --git a/postfix/src/postalias/postalias.c b/postfix/src/postalias/postalias.c index b3c9de87f..2ab13426a 100644 --- a/postfix/src/postalias/postalias.c +++ b/postfix/src/postalias/postalias.c @@ -5,7 +5,7 @@ /* Postfix alias database maintenance /* SYNOPSIS /* .fi -/* \fBpostalias\fR [\fB-Nfinoprsuvw\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostalias\fR [\fB-Nfijnoprsuvw\fR] [\fB-c \fIconfig_dir\fR] /* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] /* [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION @@ -56,6 +56,11 @@ /* Incremental mode. Read entries from standard input and do not /* truncate an existing database. By default, \fBpostalias\fR(1) creates /* a new database from the entries in \fIfile_name\fR. +/* .IP \fB-j\fR +/* JSON output. Format the output from \fB-q\fR and \fB-s\fR +/* as one \fB{"\fIkey\fB": "\fIvalue\fB"}\fR object per line. +/* .sp +/* This feature is available in Postfix version 3.11 and later. /* .IP \fB-N\fR /* Include the terminating null character that terminates lookup keys /* and values. By default, \fBpostalias\fR(1) does whatever @@ -287,6 +292,13 @@ #define POSTALIAS_FLAG_SAVE_PERM (1<<1) /* copy access permission * from source */ + /* + * Global state. + */ +int json_output; +VSTRING *json_key_buf; +VSTRING *json_val_buf; + /* postalias - create or update alias database */ static void postalias(char *map_type, char *path_name, int postalias_flags, @@ -553,7 +565,12 @@ static int postalias_queries(VSTREAM *in, char **maps, const int map_count, msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", dicts[n]->type, dicts[n]->name); } - vstream_printf("%s: %s\n", STR(keybuf), value); + if (json_output == 0) + vstream_printf("%s: %s\n", STR(keybuf), value); + else + vstream_printf("{\"%s\": \"%s\"}\n", + quote_for_json(json_key_buf, STR(keybuf), -1), + quote_for_json(json_val_buf, value, -1)); found = 1; break; } @@ -593,7 +610,12 @@ static int postalias_query(const char *map_type, const char *map_name, msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", map_type, map_name); } - vstream_printf("%s\n", value); + if (json_output == 0) + vstream_printf("%s\n", value); + else + vstream_printf("{\"%s\": \"%s\"}\n", + quote_for_json(json_key_buf, key, -1), + quote_for_json(json_val_buf, value, -1)); } if (dict->error) msg_fatal("table %s:%s: query error: %m", dict->type, dict->name); @@ -703,7 +725,12 @@ static void postalias_seq(const char *map_type, const char *map_name, msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", map_type, map_name); } - vstream_printf("%s: %s\n", key, value); + if (json_output == 0) + vstream_printf("%s: %s\n", key, value); + else + vstream_printf("{\"%s\": \"%s\"}\n", + quote_for_json(json_key_buf, key, -1), + quote_for_json(json_val_buf, value, -1)); } if (dict->error) msg_fatal("table %s:%s: sequence error: %m", dict->type, dict->name); @@ -783,7 +810,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "Nc:d:finopq:rsuvw")) > 0) { + while ((ch = GETOPT(argc, argv, "Nc:d:fijnopq:rsuvw")) > 0) { switch (ch) { default: usage(argv[0]); @@ -810,6 +837,13 @@ int main(int argc, char **argv) update = 1; open_flags &= ~O_TRUNC; break; + case 'j': + if (json_output == 0) { + json_output = 1; + json_key_buf = vstring_alloc(100); + json_val_buf = vstring_alloc(100); + } + break; case 'n': dict_flags |= DICT_FLAG_TRY0NULL; dict_flags &= ~DICT_FLAG_TRY1NULL; @@ -846,6 +880,8 @@ int main(int argc, char **argv) break; } } + if (json_output && !(sequence || query)) + msg_fatal("option -j requires -q or -s"); mail_conf_read(); /* Enforce consistent operation of different Postfix parts. */ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); diff --git a/postfix/src/postconf/Makefile.in b/postfix/src/postconf/Makefile.in index 63a0e5a96..75a6f428f 100644 --- a/postfix/src/postconf/Makefile.in +++ b/postfix/src/postconf/Makefile.in @@ -56,7 +56,9 @@ tests: test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 \ test42 test43 test44 test45 test46 test47 test48 test49 test50 test51 \ test52 test53 test54 test55 test56 test57 test58 test59 test60 test61 \ test62 test63 test64 test65 test66 test67 test68 test69 test70 test71 \ - test72 test73 test74 test75 test76 test78 test79 + test72 test73 test74 test75 test76 test78 test79 json_tests + +json_tests: test80 test81 test82 test83 test84 test85 test86 test87 root_tests: @@ -1127,10 +1129,131 @@ test79: $(PROG) test79.ref diff /dev/null test79.tmp rm -f main.cf master.cf test79.tmp +# JSON for main.cf parameters - no $name expansion +test80: $(PROG) test80.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc. -j >test80.tmp 2>&1 + diff test80.ref test80.tmp + rm -f main.cf master.cf test80.tmp + +# JSON for main.cf parameters - with $name expansion +test81: $(PROG) test81.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc. -jx >test81.tmp 2>&1 + diff test81.ref test81.tmp + rm -f main.cf master.cf test81.tmp + +# JSON for master.cf entries - no $name expansion +test82: $(PROG) test82.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o mydestination=$$whatever' >> master.cf + echo ' -o whatever=example.net' >> master.cf + echo 12345 inet n n n - - spawn >> master.cf + echo ' -o { command_time_limit = 2 3 }' >> master.cf + echo ' user=nobody argv=/bin/sh -c { sleep 1 }' >> master.cf + touch -t 197601010000 master.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -Mj >test82.tmp 2>&1 + diff test82.ref test82.tmp + rm -f main.cf master.cf test82.tmp + +# JSON for master.cf entries - with $name expansion +test83: $(PROG) test83.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o mydestination=$$whatever' >> master.cf + echo ' -o whatever=example.net' >> master.cf + touch -t 197601010000 master.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -Mjx >test83.tmp 2>&1 + diff test83.ref test83.tmp + rm -f main.cf master.cf test83.tmp + clean: rm -f *.o *core $(PROG) $(TESTPROG) junk $(MAKES) $(AUTOS) $(DUMMIES) \ $(TEST_TMP) $(DB_MAKES) +# JSON for master.cf fields - no $name expansion +test84: $(PROG) test84.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o mydestination=$$whatever' >> master.cf + echo ' -o whatever=example.net' >> master.cf + echo 12345 inet n n n - - spawn >> master.cf + echo ' -o { command_time_limit = 2 3 }' >> master.cf + echo ' user=nobody argv=/bin/sh -c { sleep 1 }' >> master.cf + touch -t 197601010000 master.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -Fj >test84.tmp 2>&1 + diff test84.ref test84.tmp + rm -f main.cf master.cf test84.tmp + +# JSON for master.cf fields - with $name expansion +test85: $(PROG) test85.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o mydestination=$$whatever' >> master.cf + echo ' -o whatever=example.net' >> master.cf + touch -t 197601010000 master.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -Fjx >test85.tmp 2>&1 + diff test85.ref test85.tmp + rm -f main.cf master.cf test85.tmp + +# JSON for master.cf parameters - no $name expansion +test86: $(PROG) test86.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o mydestination=$$whatever' >> master.cf + echo ' -o whatever=example.net' >> master.cf + echo 12345 inet n n n - - spawn >> master.cf + echo ' -o { command_time_limit = 2 3 }' >> master.cf + echo ' user=nobody argv=/bin/sh -c { sleep 1 }' >> master.cf + touch -t 197601010000 master.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -Pj >test86.tmp 2>&1 + diff test86.ref test86.tmp + rm -f main.cf master.cf test86.tmp + +# JSON for master.cf parameters - with $name expansion +test87: $(PROG) test87.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$whatever' >> main.cf + echo whatever=example.com >> main.cf + touch -t 197601010000 main.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o mydestination=$$whatever' >> master.cf + echo ' -o whatever=example.net' >> master.cf + touch -t 197601010000 master.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -Pjx >test87.tmp 2>&1 + diff test87.ref test87.tmp + rm -f main.cf master.cf test87.tmp + tidy: clean depend: $(MAKES) diff --git a/postfix/src/postconf/postconf.c b/postfix/src/postconf/postconf.c index d9e96831d..c53ca4a9a 100644 --- a/postfix/src/postconf/postconf.c +++ b/postfix/src/postconf/postconf.c @@ -8,7 +8,7 @@ /* .ti -4 /* \fBManaging main.cf:\fR /* -/* \fBpostconf\fR [\fB-dfhHnopqvx\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostconf\fR [\fB-dfhHjnopqvx\fR] [\fB-c \fIconfig_dir\fR] /* [\fB-C \fIclass,...\fR] [\fIparameter ...\fR] /* /* \fBpostconf\fR [\fB-epv\fR] [\fB-c \fIconfig_dir\fR] @@ -23,7 +23,7 @@ /* .ti -4 /* \fBManaging master.cf service entries:\fR /* -/* \fBpostconf\fR \fB-M\fR [\fB-foqvx\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostconf\fR \fB-M\fR [\fB-joqvx\fR] [\fB-c \fIconfig_dir\fR] /* [\fIservice\fR[\fB/\fItype\fR]\fI ...\fR] /* /* \fBpostconf\fR \fB-M\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR] @@ -38,7 +38,7 @@ /* .ti -4 /* \fBManaging master.cf service fields:\fR /* -/* \fBpostconf\fR \fB-F\fR [\fB-fhHoqvx\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostconf\fR \fB-F\fR [\fB-fhHjoqvx\fR] [\fB-c \fIconfig_dir\fR] /* [\fIservice\fR[\fB/\fItype\fR[\fB/\fIfield\fR]]\fI ...\fR] /* /* \fBpostconf\fR \fB-F\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR] @@ -47,7 +47,7 @@ /* .ti -4 /* \fBManaging master.cf service parameters:\fR /* -/* \fBpostconf\fR \fB-P\fR [\fB-fhHoqvx\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostconf\fR \fB-P\fR [\fB-fhHjoqvx\fR] [\fB-c \fIconfig_dir\fR] /* [\fIservice\fR[\fB/\fItype\fR[\fB/\fIparameter\fR]]\fI ...\fR] /* /* \fBpostconf\fR \fB-P\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR] @@ -210,6 +210,11 @@ /* that normally follows the name. /* /* This feature is available with Postfix 3.1 and later. +/* .IP \fB-j\fR +/* JSON output. Format main.cf or master.cf settings as one +/* \fB{"\fIkey\fB": "\fIvalue\fB"}\fR object per line. +/* +/* This feature is available with Postfix 3.11 and later. /* .IP \fB-l\fR /* List the names of all supported mailbox locking methods. /* Postfix supports the following methods: @@ -696,14 +701,19 @@ static const int pcf_compat_options[][2] = { {PCF_MAIN_PARAM, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT \ |PCF_FOLD_LINE | PCF_HIDE_NAME | PCF_PARAM_CLASS \ |PCF_SHOW_EVAL | PCF_SHOW_DEFS | PCF_SHOW_NONDEF \ - |PCF_MAIN_OVER | PCF_HIDE_VALUE)}, + |PCF_MAIN_OVER | PCF_HIDE_VALUE | PCF_SHOW_JSON)}, {PCF_MASTER_ENTRY, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT \ - |PCF_FOLD_LINE | PCF_MAIN_OVER | PCF_SHOW_EVAL)}, + |PCF_FOLD_LINE | PCF_MAIN_OVER | PCF_SHOW_EVAL \ + |PCF_SHOW_JSON)}, {PCF_MASTER_FLD, (PCF_EDIT_CONF | PCF_FOLD_LINE | PCF_HIDE_NAME \ - |PCF_MAIN_OVER | PCF_SHOW_EVAL | PCF_HIDE_VALUE)}, + |PCF_MAIN_OVER | PCF_SHOW_EVAL | PCF_HIDE_VALUE \ + |PCF_SHOW_JSON)}, {PCF_MASTER_PARAM, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_FOLD_LINE \ |PCF_HIDE_NAME | PCF_MAIN_OVER | PCF_SHOW_EVAL \ - |PCF_HIDE_VALUE)}, + |PCF_HIDE_VALUE | PCF_SHOW_JSON)}, + {PCF_SHOW_JSON, (PCF_MAIN_PARAM | PCF_MASTER_ENTRY | PCF_MASTER_FLD \ + |PCF_MASTER_PARAM | PCF_MAIN_OVER | PCF_SHOW_EVAL \ + |PCF_SHOW_NONDEF | PCF_SHOW_DEFS)}, /* Modifiers. */ {PCF_PARAM_CLASS, (PCF_MAIN_PARAM | PCF_SHOW_DEFS | PCF_SHOW_NONDEF)}, 0, @@ -723,6 +733,7 @@ static const NAME_MASK pcf_compat_names[] = { "-F", PCF_MASTER_FLD, "-h", PCF_HIDE_NAME, "-H", PCF_HIDE_VALUE, + "-j", PCF_SHOW_JSON, "-l", PCF_SHOW_LOCKS, "-m", PCF_SHOW_MAPS, "-M", PCF_MASTER_ENTRY, @@ -754,6 +765,7 @@ static void usage(const char *progname) " [-F (master.cf fields)]" " [-h (no names)]" " [-H (no values)]" + " [-j (streaming JSON) ]" " [-l (lock types)]" " [-m (map types)]" " [-M (master.cf)]" @@ -866,7 +878,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "aAbc:C:deEfFhHlmMno:pPqtT:vxX#")) > 0) { + while ((ch = GETOPT(argc, argv, "aAbc:C:deEfFhHjlmMno:pPqtT:vxX#")) > 0) { switch (ch) { case 'a': pcf_cmd_mode |= PCF_SHOW_SASL_SERV; @@ -910,6 +922,9 @@ int main(int argc, char **argv) case 'H': pcf_cmd_mode |= PCF_HIDE_VALUE; break; + case 'j': + pcf_cmd_mode |= PCF_SHOW_JSON; + break; case 'l': pcf_cmd_mode |= PCF_SHOW_LOCKS; break; diff --git a/postfix/src/postconf/postconf.h b/postfix/src/postconf/postconf.h index faeed0ded..9415f6f6c 100644 --- a/postfix/src/postconf/postconf.h +++ b/postfix/src/postconf/postconf.h @@ -47,6 +47,7 @@ #define PCF_HIDE_VALUE (1<<20) /* hide main.cf/master.cf =value */ #define PCF_SHOW_TLS (1<<21) /* TLS support introspection */ #define PCF_WARN_UNUSED_DEPRECATED (1<<22) /* As the name says */ +#define PCF_SHOW_JSON (1 << 23) /* JSON output */ #define PCF_DEF_MODE (PCF_WARN_UNUSED_DEPRECATED) @@ -69,6 +70,7 @@ typedef struct { #define PCF_PARAM_FLAG_LEGACY (1<<4) /* legacy parameter name */ #define PCF_PARAM_FLAG_READONLY (1<<5) /* legacy parameter name */ #define PCF_PARAM_FLAG_DBMS (1<<6) /* dbms-defined parameter name */ +#define PCF_PARAM_FLAG_NUMBER (1<<7) /* numeric value */ #define PCF_PARAM_MASK_CLASS \ (PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_SERVICE | PCF_PARAM_FLAG_USER) @@ -82,6 +84,7 @@ typedef struct { #define PCF_LEGACY_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_LEGACY) #define PCF_READONLY_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_READONLY) #define PCF_DBMS_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_DBMS) +#define PCF_NUMBER_PARAMETER(mode) ((node)->flags & PCF_PARAM_FLAG_NUMBER) /* Values for param_data. See postconf_node module for narrative text. */ #define PCF_PARAM_NO_DATA ((char *) 0) diff --git a/postfix/src/postconf/postconf_builtin.c b/postfix/src/postconf/postconf_builtin.c index e0ecc6efb..e0902862b 100644 --- a/postfix/src/postconf/postconf_builtin.c +++ b/postfix/src/postconf/postconf_builtin.c @@ -422,8 +422,8 @@ void pcf_register_builtin_parameters(const char *procname, pid_t pid) pcf_conv_bool_parameter); for (cit = pcf_int_table; cit->name; cit++) PCF_PARAM_TABLE_ENTER(pcf_param_table, cit->name, - PCF_PARAM_FLAG_BUILTIN, (void *) cit, - pcf_conv_int_parameter); + PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_NUMBER, + (void *) cit, pcf_conv_int_parameter); for (cst = pcf_str_table; cst->name; cst++) PCF_PARAM_TABLE_ENTER(pcf_param_table, cst->name, PCF_PARAM_FLAG_BUILTIN, (void *) cst, @@ -446,8 +446,8 @@ void pcf_register_builtin_parameters(const char *procname, pid_t pid) pcf_conv_nbool_parameter); for (lst = pcf_long_table; lst->name; lst++) PCF_PARAM_TABLE_ENTER(pcf_param_table, lst->name, - PCF_PARAM_FLAG_BUILTIN, (void *) lst, - pcf_conv_long_parameter); + PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_NUMBER, + (void *) lst, pcf_conv_long_parameter); /* * Register legacy parameters (used as a backwards-compatible migration diff --git a/postfix/src/postconf/postconf_edit.c b/postfix/src/postconf/postconf_edit.c index 642965817..2808290bf 100644 --- a/postfix/src/postconf/postconf_edit.c +++ b/postfix/src/postconf/postconf_edit.c @@ -209,8 +209,10 @@ void pcf_edit_main(int mode, int argc, char **argv) msg_panic("pcf_edit_main: unknown mode %d", mode); } if ((cvalue = htable_find(table, pattern)) != 0) { - msg_warn("ignoring earlier request: '%s = %s'", - pattern, cvalue->value); + if (edit_value && cvalue->value + && strcmp(edit_value, cvalue->value) != 0) + msg_warn("ignoring earlier request: '%s = %s'", + pattern, cvalue->value); htable_delete(table, pattern, myfree); } cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue)); diff --git a/postfix/src/postconf/postconf_main.c b/postfix/src/postconf/postconf_main.c index e58e2617b..73d523b7a 100644 --- a/postfix/src/postconf/postconf_main.c +++ b/postfix/src/postconf/postconf_main.c @@ -138,10 +138,13 @@ static void pcf_print_parameter(VSTREAM *fp, int mode, const char *name, PCF_PARAM_NODE *node) { static VSTRING *exp_buf = 0; + static VSTRING *json_buf = 0; const char *value; - if (exp_buf == 0) + if (exp_buf == 0 && (mode & PCF_SHOW_EVAL)) exp_buf = vstring_alloc(100); + if (json_buf == 0 && (mode & PCF_SHOW_JSON)) + json_buf = vstring_alloc(100); /* * Use the default or actual value. @@ -159,7 +162,12 @@ static void pcf_print_parameter(VSTREAM *fp, int mode, const char *name, if ((mode & PCF_SHOW_EVAL) != 0 && PCF_RAW_PARAMETER(node) == 0) value = pcf_expand_parameter_value(exp_buf, mode, value, (PCF_MASTER_ENT *) 0); - if ((mode & PCF_HIDE_NAME) == 0) { + if (mode & PCF_SHOW_JSON) { + vstream_fprintf(fp, "{\"%s\": ", + quote_for_json(json_buf, name, -1)); + vstream_fprintf(fp, "\"%s\"}\n", + quote_for_json(json_buf, value, -1)); + } else if ((mode & PCF_HIDE_NAME) == 0) { pcf_print_line(fp, mode, "%s = %s\n", name, value); } else { pcf_print_line(fp, mode, "%s\n", value); diff --git a/postfix/src/postconf/postconf_master.c b/postfix/src/postconf/postconf_master.c index bddb1af25..48061a106 100644 --- a/postfix/src/postconf/postconf_master.c +++ b/postfix/src/postconf/postconf_master.c @@ -198,6 +198,7 @@ static const char *pcf_valid_master_types[] = { static const char pcf_valid_bool_types[] = "yn-"; static VSTRING *pcf_exp_buf; +static VSTRING *pcf_json_buf; #define STR(x) vstring_str(x) @@ -464,6 +465,97 @@ void pcf_read_master(int fail_on_open_error) pcf_master_table[entry_count].argv = 0; } +/* pcf_print_master_entry_as_json - JSON formatter */ + +static void pcf_print_master_entry_as_json(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp) +{ + char **argv = masterp->argv->argv; + const char *arg; + const char *aval; + int field; + int in_daemon_options; + int need_parens; + + if (pcf_exp_buf == 0 && (mode & PCF_SHOW_EVAL)) + pcf_exp_buf = vstring_alloc(100); + if (pcf_json_buf == 0 && (mode & PCF_SHOW_JSON)) + pcf_json_buf = vstring_alloc(100); + + /* + * Output the namespace part first, so that we can reuse a buffer. + */ + vstream_fprintf(fp, "{\"%s\": ", + quote_for_json(pcf_json_buf, masterp->name_space, -1)); + + /* + * Show the standard fields with one-space column separation. + */ +#define APPEND_JSON_STR(s, l) quote_for_json_append(pcf_json_buf, (s), (l)) +#define APPEND_JSON_STR0(s) APPEND_JSON_STR((s), -1) +#define APPEND_JSON_CHAR(s) APPEND_JSON_STR((s), 1) + + VSTRING_RESET(pcf_json_buf); + for (field = 0; field < PCF_MASTER_MIN_FIELDS; field++) { + arg = argv[field]; + if (field > 0) + APPEND_JSON_CHAR(" "); + APPEND_JSON_STR0(arg); + } + + /* + * Format the daemon command-line options and non-option arguments. + */ + in_daemon_options = 1; + for ( /* void */ ; (arg = argv[field]) != 0; field++) { + aval = 0; + need_parens = 0; + if (in_daemon_options) { + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + in_daemon_options = 0; + } + + /* + * Special processing for options that require a value. + */ + else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv[field + 1]) != 0) { + + /* + * Optionally, expand $name in parameter value. + */ + if (strcmp(arg, "-o") == 0 + && (mode & PCF_SHOW_EVAL) != 0) + aval = pcf_expand_parameter_value(pcf_exp_buf, mode, + aval, masterp); + need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]; + } + } else { + need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; + } + APPEND_JSON_CHAR(" "); + if (in_daemon_options == 0 && need_parens) + APPEND_JSON_CHAR("{"); + APPEND_JSON_STR0(arg); + if (in_daemon_options == 0 && need_parens) + APPEND_JSON_CHAR("}"); + if (aval) { + APPEND_JSON_CHAR(" "); + if (need_parens) + APPEND_JSON_CHAR("{"); + APPEND_JSON_STR0(aval); + if (need_parens) + APPEND_JSON_CHAR("}"); + field += 1; + } + } + VSTRING_TERMINATE(pcf_json_buf); + vstream_fprintf(fp, "\"%s\"}\n", STR(pcf_json_buf)); + + if (msg_verbose) + vstream_fflush(fp); +} + /* pcf_print_master_entry - print one master line */ void pcf_print_master_entry(VSTREAM *fp, int mode, PCF_MASTER_ENT *masterp) @@ -487,6 +579,10 @@ void pcf_print_master_entry(VSTREAM *fp, int mode, PCF_MASTER_ENT *masterp) 57, /* command */ }; + if (mode & PCF_SHOW_JSON) { + pcf_print_master_entry_as_json(fp, mode, masterp); + return; + } #define ADD_TEXT(text, len) do { \ vstream_fputs(text, fp); line_len += len; } \ while (0) @@ -654,6 +750,91 @@ void pcf_show_master_entries(VSTREAM *fp, int mode, int argc, char **argv) } } +/* pcf_print_master_field_as_json - scaffolding for JSON */ + +static void pcf_print_master_field_as_json(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp, + int field) +{ + char **argv = masterp->argv->argv; + const char *arg; + const char *aval; + int in_daemon_options; + int need_parens; + + if (pcf_exp_buf == 0 && (mode & PCF_SHOW_EVAL) != 0) + pcf_exp_buf = vstring_alloc(100); + if (pcf_json_buf == 0 && (mode & PCF_SHOW_JSON) != 0) + pcf_json_buf = vstring_alloc(100); + + /* + * Output the name part first, so that we can reuse a buffer. + */ + vstream_fprintf(fp, "{\"%s\": ", + quote_for_json_var(pcf_json_buf, masterp->name_space, + PCF_NAMESP_SEP_STR, + pcf_str_field_pattern(field), + (const char *) 0)); + + /* + * Show the field value, or the first value in the case of a multi-column + * field. + */ + VSTRING_RESET(pcf_json_buf); + APPEND_JSON_STR0(argv[field]); + + /* + * Format the daemon command-line options and non-option arguments. Here, + * we have no data-dependent preference for column positions, but we do + * have argument grouping preferences. + */ + if (field == PCF_MASTER_FLD_CMD) { + in_daemon_options = 1; + for (field += 1; (arg = argv[field]) != 0; field++) { + aval = 0; + need_parens = 0; + if (in_daemon_options) { + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + in_daemon_options = 0; + } else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv[field + 1]) != 0) { + + /* + * Optionally, expand $name in parameter value. + */ + if (strcmp(arg, "-o") == 0 + && (mode & PCF_SHOW_EVAL) != 0) + aval = pcf_expand_parameter_value(pcf_exp_buf, mode, + aval, masterp); + need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]; + } + } else { + need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; + } + APPEND_JSON_CHAR(" "); + if (in_daemon_options == 0 && need_parens) + APPEND_JSON_CHAR("{"); + APPEND_JSON_STR0(arg); + if (in_daemon_options == 0 && need_parens) + APPEND_JSON_CHAR("}"); + if (aval) { + APPEND_JSON_CHAR(" "); + if (need_parens) + APPEND_JSON_CHAR("{"); + APPEND_JSON_STR0(aval); + if (need_parens) + APPEND_JSON_CHAR("}"); + field += 1; + } + } + } + VSTRING_TERMINATE(pcf_json_buf); + vstream_fprintf(fp, "\"%s\"}\n", STR(pcf_json_buf)); + + if (msg_verbose) + vstream_fflush(fp); +} + /* pcf_print_master_field - scaffolding */ static void pcf_print_master_field(VSTREAM *fp, int mode, @@ -668,6 +849,10 @@ static void pcf_print_master_field(VSTREAM *fp, int mode, int in_daemon_options; int need_parens; + if (mode & PCF_SHOW_JSON) { + pcf_print_master_field_as_json(fp, mode, masterp, field); + return; + } if (pcf_exp_buf == 0) pcf_exp_buf = vstring_alloc(100); @@ -882,8 +1067,10 @@ static void pcf_print_master_param(VSTREAM *fp, int mode, const char *param_name, const char *param_value) { - if (pcf_exp_buf == 0) + if (pcf_exp_buf == 0 && (mode & PCF_SHOW_EVAL)) pcf_exp_buf = vstring_alloc(100); + if (pcf_json_buf == 0 && (mode & PCF_SHOW_JSON)) + pcf_json_buf = vstring_alloc(100); if (mode & PCF_HIDE_VALUE) { pcf_print_line(fp, mode, "%s%c%s\n", @@ -893,7 +1080,14 @@ static void pcf_print_master_param(VSTREAM *fp, int mode, if ((mode & PCF_SHOW_EVAL) != 0) param_value = pcf_expand_parameter_value(pcf_exp_buf, mode, param_value, masterp); - if ((mode & PCF_HIDE_NAME) == 0) { + if (mode & PCF_SHOW_JSON) { + vstream_fprintf(fp, "{\"%s\": ", + quote_for_json_var(pcf_json_buf, masterp->name_space, + PCF_NAMESP_SEP_STR, param_name, + (const char *) 0)); + vstream_fprintf(fp, "\"%s\"}\n", + quote_for_json(pcf_json_buf, param_value, -1)); + } else if ((mode & PCF_HIDE_NAME) == 0) { pcf_print_line(fp, mode, "%s%c%s = %s\n", masterp->name_space, PCF_NAMESP_SEP_CH, param_name, param_value); diff --git a/postfix/src/postconf/test80.ref b/postfix/src/postconf/test80.ref new file mode 100644 index 000000000..34c5371a4 --- /dev/null +++ b/postfix/src/postconf/test80.ref @@ -0,0 +1,3 @@ +{"config_directory": "."} +{"mydestination": "$whatever"} +{"whatever": "example.com"} diff --git a/postfix/src/postconf/test81.ref b/postfix/src/postconf/test81.ref new file mode 100644 index 000000000..f8e4534cd --- /dev/null +++ b/postfix/src/postconf/test81.ref @@ -0,0 +1,3 @@ +{"config_directory": "."} +{"mydestination": "example.com"} +{"whatever": "example.com"} diff --git a/postfix/src/postconf/test82.ref b/postfix/src/postconf/test82.ref new file mode 100644 index 000000000..dd1ac515e --- /dev/null +++ b/postfix/src/postconf/test82.ref @@ -0,0 +1,2 @@ +{"foo/unix": "foo unix - n n - 0 other -o mydestination=$whatever -o whatever=example.net"} +{"12345/inet": "12345 inet n n n - - spawn -o {command_time_limit=2 3} user=nobody argv=/bin/sh -c {sleep 1}"} diff --git a/postfix/src/postconf/test83.ref b/postfix/src/postconf/test83.ref new file mode 100644 index 000000000..bfb1f01e6 --- /dev/null +++ b/postfix/src/postconf/test83.ref @@ -0,0 +1 @@ +{"foo/unix": "foo unix - n n - 0 other -o mydestination=example.net -o whatever=example.net"} diff --git a/postfix/src/postconf/test84.ref b/postfix/src/postconf/test84.ref new file mode 100644 index 000000000..6bac54d4f --- /dev/null +++ b/postfix/src/postconf/test84.ref @@ -0,0 +1,16 @@ +{"foo/unix/service": "foo"} +{"foo/unix/type": "unix"} +{"foo/unix/private": "-"} +{"foo/unix/unprivileged": "n"} +{"foo/unix/chroot": "n"} +{"foo/unix/wakeup": "-"} +{"foo/unix/process_limit": "0"} +{"foo/unix/command": "other -o mydestination=$whatever -o whatever=example.net"} +{"12345/inet/service": "12345"} +{"12345/inet/type": "inet"} +{"12345/inet/private": "n"} +{"12345/inet/unprivileged": "n"} +{"12345/inet/chroot": "n"} +{"12345/inet/wakeup": "-"} +{"12345/inet/process_limit": "-"} +{"12345/inet/command": "spawn -o {command_time_limit=2 3} user=nobody argv=/bin/sh -c {sleep 1}"} diff --git a/postfix/src/postconf/test85.ref b/postfix/src/postconf/test85.ref new file mode 100644 index 000000000..8ff85dd3e --- /dev/null +++ b/postfix/src/postconf/test85.ref @@ -0,0 +1,8 @@ +{"foo/unix/service": "foo"} +{"foo/unix/type": "unix"} +{"foo/unix/private": "-"} +{"foo/unix/unprivileged": "n"} +{"foo/unix/chroot": "n"} +{"foo/unix/wakeup": "-"} +{"foo/unix/process_limit": "0"} +{"foo/unix/command": "other -o mydestination=example.net -o whatever=example.net"} diff --git a/postfix/src/postconf/test86.ref b/postfix/src/postconf/test86.ref new file mode 100644 index 000000000..aa7a67fe0 --- /dev/null +++ b/postfix/src/postconf/test86.ref @@ -0,0 +1,3 @@ +{"foo/unix/mydestination": "$whatever"} +{"foo/unix/whatever": "example.net"} +{"12345/inet/command_time_limit": "2 3"} diff --git a/postfix/src/postconf/test87.ref b/postfix/src/postconf/test87.ref new file mode 100644 index 000000000..a0c11197a --- /dev/null +++ b/postfix/src/postconf/test87.ref @@ -0,0 +1,2 @@ +{"foo/unix/mydestination": "example.net"} +{"foo/unix/whatever": "example.net"} diff --git a/postfix/src/postmap/Makefile.in b/postfix/src/postmap/Makefile.in index b415d57b1..adb33f2c2 100644 --- a/postfix/src/postmap/Makefile.in +++ b/postfix/src/postmap/Makefile.in @@ -28,7 +28,8 @@ update: ../../bin/$(PROG) cp $(PROG) ../../bin tests: test1 test2 fail_test quote_test file_test lmdb_abb_test \ - lmdb_bulk_test lmdb_incr_test cdb_bulk_test mode_conflict_test + lmdb_bulk_test lmdb_incr_test cdb_bulk_test mode_conflict_test \ + json_tests root_tests: @@ -137,6 +138,62 @@ lmdb_incr_test: $(PROG) cmp lmdb_retry lmdb_retry.tmp rm -f lmdb_retry lmdb_retry.tmp lmdb_retry.lmdb main.cf +json_tests: json_query_test json_queries_test json_sequence_test \ + json_header_test json_body_test + +json_query_test: $(PROG) + # Exercise code in postmap_query(). + @echo ; echo RUN json_query_test + echo '{"foo": "fooval"}' >json_query.tmp + $(SHLIB_ENV) ${VALGRIND} ./$(PROG) -jq foo \ + 'inline:{{foo = fooval}}' | diff json_query.tmp - + ($(SHLIB_ENV) ${VALGRIND} ./$(PROG) -jq bar \ + 'inline:{{foo = fooval}}' || exit 0) | diff /dev/null - + rm -f json_query.tmp + @echo PASS json_query_test + +json_queries_test: $(PROG) + # Exercise json formatting in postmap_queries() with non-message input. + @echo ; echo RUN json_queries_test + echo '{"foo": "fooval"}' >json_queries.tmp + (echo foo; echo bar) | $(SHLIB_ENV) ${VALGRIND} \ + ./$(PROG) -jq - 'inline:{{foo = fooval}}' | \ + diff json_queries.tmp - + rm -f json_queries.tmp + @echo PASS json_queries_test + +json_sequence_test: $(PROG) + # Exercise json formatting in postmap_seq(). + @echo ; echo RUN json_sequence_test + rm -f json_sequence.tmp + (echo '{"bar": "barval"}'; echo '{"foo": "fooval"}') |\ + sort >json_sequence.tmp + $(SHLIB_ENV) ${VALGRIND} ./$(PROG) -js \ + 'inline:{{foo = fooval} {bar = barval}}' | sort | \ + diff json_sequence.tmp - + rm -f json_sequence.tmp + @echo PASS json_sequence_test + +json_header_test: $(PROG) + # Exercise json formatting in postmap_queries() in 'header' mode. + @echo ; echo RUN json_header_test + echo '{"Subject: test": "got subject"}' > json_header.tmp + (echo Subject: test; echo body) | $(SHLIB_ENV) ${VALGRIND} ./$(PROG) \ + -hjq - 'regexp:{{ /Subject/ got subject} { /./ got other }}' | \ + diff json_header.tmp - + rm -f json_header.tmp + @echo PASS json_header_test + +json_body_test: $(PROG) + # Exercise json formatting in postmap_queries() in 'body' mode. + @echo ; echo RUN json_body_test + echo '{"body": "got other"}' > json_body.tmp + (echo Subject: test; echo body) | $(SHLIB_ENV) ${VALGRIND} ./$(PROG) \ + -bjq - 'regexp:{{ /Subject/ got subject} { /./ got other }}' | \ + diff json_body.tmp - + rm -f json_body.tmp + @echo PASS json_body_test + clean: rm -f *.o *core $(PROG) $(TESTPROG) *.tmp junk *.db main.cf master.cf diff --git a/postfix/src/postmap/postmap.c b/postfix/src/postmap/postmap.c index 5ebc2e312..eaecf319b 100644 --- a/postfix/src/postmap/postmap.c +++ b/postfix/src/postmap/postmap.c @@ -5,7 +5,7 @@ /* Postfix lookup table management /* SYNOPSIS /* .fi -/* \fBpostmap\fR [\fB-bfFhimnNoprsuUvw\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostmap\fR [\fB-bfFhijmnNoprsuUvw\fR] [\fB-c \fIconfig_dir\fR] /* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] /* [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION @@ -136,6 +136,11 @@ /* Incremental mode. Read entries from standard input and do not /* truncate an existing database. By default, \fBpostmap\fR(1) creates /* a new database from the entries in \fBfile_name\fR. +/* .IP \fB-j\fR +/* JSON output. Format the output from \fB-q\fR and \fB-s\fR +/* as one \fB{"\fIkey\fB": "\fIvalue\fB"}\fR object per line. +/* .sp +/* This feature is available in Postfix version 3.11 and later. /* .IP \fB-m\fR /* Enable MIME parsing with "\fB-b\fR" and "\fB-h\fR". /* .sp @@ -386,6 +391,13 @@ typedef struct { int found; /* result */ } POSTMAP_KEY_STATE; + /* + * Global state. + */ +int json_output; +VSTRING *json_key_buf; +VSTRING *json_val_buf; + /* postmap - create or update mapping database */ static void postmap(char *map_type, char *path_name, int postmap_flags, @@ -615,7 +627,13 @@ static void postmap_body(void *ptr, int unused_rec_type, msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", dicts[n]->type, dicts[n]->name); } - vstream_printf("%s %s\n", keybuf, value); + if (json_output == 0) + vstream_printf("%s %s\n", keybuf, value); + else + vstream_printf("{\"%s\": \"%s\"}\n", + quote_for_json(json_key_buf, keybuf, -1), + quote_for_json(json_val_buf, value, -1)); + state->found = 1; break; } @@ -698,7 +716,12 @@ static int postmap_queries(VSTREAM *in, char **maps, const int map_count, msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", dicts[n]->type, dicts[n]->name); } - vstream_printf("%s %s\n", STR(keybuf), value); + if (json_output == 0) + vstream_printf("%s %s\n", STR(keybuf), value); + else + vstream_printf("{\"%s\": \"%s\"}\n", + quote_for_json(json_key_buf, STR(keybuf), -1), + quote_for_json(json_val_buf, value, -1)); found = 1; break; } @@ -794,7 +817,12 @@ static int postmap_query(const char *map_type, const char *map_name, msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", map_type, map_name); } - vstream_printf("%s\n", value); + if (json_output == 0) + vstream_printf("%s\n", value); + else + vstream_printf("{\"%s\": \"%s\"}\n", + quote_for_json(json_key_buf, key, -1), + quote_for_json(json_val_buf, value, -1)); } switch (dict->error) { case 0: @@ -926,7 +954,12 @@ static void postmap_seq(const char *map_type, const char *map_name, } value = STR(unb64); } - vstream_printf("%s %s\n", key, value); + if (json_output == 0) + vstream_printf("%s %s\n", key, value); + else + vstream_printf("{\"%s\": \"%s\"}\n", + quote_for_json(json_key_buf, key, -1), + quote_for_json(json_val_buf, value, -1)); } if (dict->error) msg_fatal("table %s:%s: sequence error: %m", dict->type, dict->name); @@ -1007,7 +1040,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "bc:d:fFhimnNopq:rsuUvw")) > 0) { + while ((ch = GETOPT(argc, argv, "bc:d:fFhijmnNopq:rsuUvw")) > 0) { switch (ch) { default: usage(argv[0]); @@ -1043,6 +1076,13 @@ int main(int argc, char **argv) update = 1; open_flags &= ~O_TRUNC; break; + case 'j': + if (json_output == 0) { + json_output = 1; + json_key_buf = vstring_alloc(100); + json_val_buf = vstring_alloc(100); + } + break; case 'm': postmap_flags |= POSTMAP_FLAG_MIME_KEY; break; @@ -1085,6 +1125,8 @@ int main(int argc, char **argv) break; } } + if (json_output && !(sequence || query)) + msg_fatal("option -j requires -q or -s"); mail_conf_read(); /* Enforce consistent operation of different Postfix parts. */ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); diff --git a/postfix/src/util/quote_for_json.c b/postfix/src/util/quote_for_json.c index f54af3fcc..81af57ac7 100644 --- a/postfix/src/util/quote_for_json.c +++ b/postfix/src/util/quote_for_json.c @@ -15,6 +15,10 @@ /* VSTRING *result, /* const char *in, /* ssize_t len) +/* +/* char *quote_for_json_var( +/* VSTRING *result, +/* const char *in) /* DESCRIPTION /* quote_for_json() takes well-formed UTF-8 encoded text, /* quotes that text compliant with RFC 4627, and returns a @@ -32,6 +36,9 @@ /* /* quote_for_json_append() appends the output to the result buffer. /* +/* quote_for_json_var() takes a null-terminated sequence of +/* null-terminated arguments and formats them with quote_for_json(). +* /* Arguments: /* .IP result /* Storage for the result, resized automatically. @@ -61,6 +68,7 @@ */ #include #include +#include #include /* @@ -135,6 +143,21 @@ char *quote_for_json(VSTRING *result, const char *text, ssize_t len) return (quote_for_json_append(result, text, len)); } + +/* quote_for_json_var - quote null-terminated list of null-terminated strings */ + +char *quote_for_json_var(VSTRING *result,...) +{ + VSTRING_RESET(result); + const char *in; + va_list ap; + + va_start(ap, result); + while ((in = va_arg(ap, const char *)) != 0) + quote_for_json_append(result, in, -1); + return (STR(result)); +} + #ifdef TEST /* @@ -145,38 +168,90 @@ char *quote_for_json(VSTRING *result, const char *text, ssize_t len) /* * Utility library. */ +#include #include #include typedef struct TEST_CASE { const char *label; /* identifies test case */ - char *(*fn) (VSTRING *, const char *, ssize_t); - const char *input; /* input string */ - ssize_t input_len; /* -1 or input length */ + int (*action) (const struct TEST_CASE *); + union { + struct { + char *(*fn) (VSTRING *, const char *, ssize_t); + const char *input; /* input string */ + ssize_t input_len; /* -1 or input length */ + } fixed; + struct { + char *(*fn) (VSTRING *,...); + const char *input; + } variadic; + } u; const char *exp_res; /* expected result */ } TEST_CASE; #define PASS (0) #define FAIL (1) +static VSTRING *res_buf; + +static int run_fixed_test(const TEST_CASE *tp) +{ + int test_fail = 0; + char *res; + + res = tp->u.fixed.fn(res_buf, tp->u.fixed.input, tp->u.fixed.input_len); + if (strcmp(res, tp->exp_res) != 0) { + msg_warn("test case '%s': got '%s', want '%s'", + tp->label, res, tp->exp_res); + test_fail = 1; + } + return (test_fail); +} + +static int run_variadic_test(const TEST_CASE *tp) +{ + int test_fail = 0; + char *res; + ARGV *argv = argv_split(tp->u.variadic.input, CHARS_SPACE); + + res = tp->u.variadic.fn(res_buf, argv->argv[0], argv->argv[1], + argv->argv[2], argv->argv[3]); + if (strcmp(res, tp->exp_res) != 0) { + msg_warn("test case '%s': got '%s', want '%s'", + tp->label, res, tp->exp_res); + test_fail = 1; + } + argv_free(argv); + return (test_fail); +} + /* * The test cases. */ static const TEST_CASE test_cases[] = { - {"ordinary ASCII text", quote_for_json, - " abcABC012.,[]{}/", -1, " abcABC012.,[]{}/", + {"ordinary ASCII text", run_fixed_test, + .u.fixed = {quote_for_json, + " abcABC012.,[]{}/", -1}, " abcABC012.,[]{}/", }, - {"quote_for_json_append", quote_for_json_append, - "foo", -1, " abcABC012.,[]{}/foo", + {"quote_for_json_append", run_fixed_test, + .u.fixed = {quote_for_json_append, + "foo", -1}, " abcABC012.,[]{}/foo", }, - {"common control characters", quote_for_json, - "\b\f\r\n\t", -1, "\\b\\f\\r\\n\\t", + {"common control characters", run_fixed_test, + .u.fixed = {quote_for_json, + "\b\f\r\n\t", -1}, "\\b\\f\\r\\n\\t", }, - {"uncommon control characters and DEL", quote_for_json, - "\0\01\037\040\176\177", 6, "\\u0000\\u0001\\u001F ~\\u007F", + {"uncommon control characters and DEL", run_fixed_test, + .u.fixed = {quote_for_json, + "\0\01\037\040\176\177", 6}, "\\u0000\\u0001\\u001F ~\\u007F", }, - {"malformed UTF-8", quote_for_json, - "\\*\\uasd\\u007F\x80", -1, "\\\\*\\\\uasd\\\\u007F\x80", + {"malformed UTF-8", run_fixed_test, + .u.fixed = {quote_for_json, + "\\*\\uasd\\u007F\x80", -1}, "\\\\*\\\\uasd\\\\u007F\x80", + }, + {"multiple input strings", run_variadic_test, + .u.variadic = {quote_for_json_var, "one - two"}, + "one-two", }, 0, }; @@ -186,32 +261,24 @@ int main(int argc, char **argv) const TEST_CASE *tp; int pass = 0; int fail = 0; - VSTRING *res_buf = vstring_alloc(100); msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR); + res_buf = vstring_alloc(100); for (tp = test_cases; tp->label != 0; tp++) { int test_fail = 0; - char *res; msg_info("RUN %s", tp->label); - res = tp->fn(res_buf, tp->input, tp->input_len); - if (strcmp(res, tp->exp_res) != 0) { - msg_warn("test case '%s': got '%s', want '%s'", - tp->label, res, tp->exp_res); - test_fail = 1; - } + test_fail = tp->action(tp); if (test_fail) { fail++; msg_info("FAIL %s", tp->label); - test_fail = 1; } else { msg_info("PASS %s", tp->label); pass++; } } msg_info("PASS=%d FAIL=%d", pass, fail); - vstring_free(res_buf); exit(fail != 0); } diff --git a/postfix/src/util/stringops.h b/postfix/src/util/stringops.h index 6b493151f..3cf2d117b 100644 --- a/postfix/src/util/stringops.h +++ b/postfix/src/util/stringops.h @@ -68,6 +68,7 @@ extern int strcasecmp_utf8x(int, const char *, const char *); extern int strncasecmp_utf8x(int, const char *, const char *, ssize_t); extern char *quote_for_json(VSTRING *, const char *, ssize_t); extern char *quote_for_json_append(VSTRING *, const char *, ssize_t); +extern char *quote_for_json_var(VSTRING *,...); extern const char *mystrerror(int); extern char *normalize_ws(char *); -- 2.47.3