]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.11-20251122
authorWietse Z Venema <wietse@porcupine.org>
Sat, 22 Nov 2025 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Sun, 23 Nov 2025 01:49:31 +0000 (12:49 +1100)
31 files changed:
postfix/HISTORY
postfix/html/postalias.1.html
postfix/html/postconf.1.html
postfix/html/postmap.1.html
postfix/man/man1/postalias.1
postfix/man/man1/postconf.1
postfix/man/man1/postmap.1
postfix/proto/stop.double-history
postfix/proto/stop.spell-cc
postfix/src/global/mail_version.h
postfix/src/postalias/Makefile.in
postfix/src/postalias/postalias.c
postfix/src/postconf/Makefile.in
postfix/src/postconf/postconf.c
postfix/src/postconf/postconf.h
postfix/src/postconf/postconf_builtin.c
postfix/src/postconf/postconf_edit.c
postfix/src/postconf/postconf_main.c
postfix/src/postconf/postconf_master.c
postfix/src/postconf/test80.ref [new file with mode: 0644]
postfix/src/postconf/test81.ref [new file with mode: 0644]
postfix/src/postconf/test82.ref [new file with mode: 0644]
postfix/src/postconf/test83.ref [new file with mode: 0644]
postfix/src/postconf/test84.ref [new file with mode: 0644]
postfix/src/postconf/test85.ref [new file with mode: 0644]
postfix/src/postconf/test86.ref [new file with mode: 0644]
postfix/src/postconf/test87.ref [new file with mode: 0644]
postfix/src/postmap/Makefile.in
postfix/src/postmap/postmap.c
postfix/src/util/quote_for_json.c
postfix/src/util/stringops.h

index 9d0223734c5c2236da56e250421a15ea98a90c54..21f71bbfd6ecf82e8dbd40bb94f0a38fb43cb140 100644 (file)
@@ -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.
index ed2fcbc9e632a4d9cc7a98c465616e55db1b67cb..b6ba86167ab76af0dd88cc004d16c4fc1313ad54 100644 (file)
@@ -11,7 +11,7 @@ POSTALIAS(1)                                                      POSTALIAS(1)
        postalias - Postfix alias database maintenance
 
 <b><a name="synopsis">SYNOPSIS</a></b>
-       <b>postalias</b> [<b>-Nfinoprsuvw</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
+       <b>postalias</b> [<b>-Nfijnoprsuvw</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
                [<i>file</i><b>_</b><i>type</i>:]<i>file</i><b>_</b><i>name</i> ...
 
 <b><a name="description">DESCRIPTION</a></b>
@@ -62,52 +62,57 @@ POSTALIAS(1)                                                      POSTALIAS(1)
               truncate an existing database. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a>  creates
               a new database from the entries in <i>file</i><b>_</b><i>name</i>.
 
-       <b>-N</b>     Include  the  terminating  null character that terminates lookup
-              keys and values. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> does whatever  is  the
+       <b>-j</b>     JSON  output.  Format  the  output from <b>-q</b> and <b>-s</b> as one <b>{"</b><i>key</i><b>":</b>
+              <b>"</b><i>value</i><b>"}</b> object per line.
+
+              This feature is available in Postfix version 3.11 and later.
+
+       <b>-N</b>     Include the terminating null character  that  terminates  lookup
+              keys  and  values. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> does whatever is the
               default for the host operating system.
 
-       <b>-n</b>     Don't  include  the  terminating  null character that terminates
-              lookup keys and values. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a>  does  whatever
+       <b>-n</b>     Don't include the terminating  null  character  that  terminates
+              lookup  keys  and values. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> does whatever
               is the default for the host operating system.
 
-       <b>-o</b>     Do  not release root privileges when processing a non-root input
+       <b>-o</b>     Do not release root privileges when processing a non-root  input
               file. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> drops root privileges and runs as
               the source file owner instead.
 
-       <b>-p</b>     Do  not  inherit the file access permissions from the input file
-              when creating a new file.   Instead,  create  a  new  file  with
+       <b>-p</b>     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).
 
-       <b>-q</b> <i>key</i> Search  the  specified  maps  for  <i>key</i> and write the first value
-              found to the standard output stream. The  exit  status  is  zero
+       <b>-q</b> <i>key</i> Search the specified maps for <i>key</i>  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 <a href="aliases.5.html">aliases(5)</a> manual page.
 
-              If  a  key value of <b>-</b> is specified, the program reads key values
+              If a key value of <b>-</b> is specified, the program reads  key  values
               from the standard input stream and writes one line of <i>key: value</i>
               output for each key that was found. The exit status is zero when
               at least one of the requested keys was found.
 
-       <b>-r</b>     When updating a table, do not complain about attempts to  update
+       <b>-r</b>     When  updating a table, do not complain about attempts to update
               existing entries, and make those updates anyway.
 
        <b>-s</b>     Retrieve all database elements, and write one line of <i>key: value</i>
-              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.
 
-       <b>-u</b>     Disable  UTF-8 support. UTF-8 support is enabled by default when
-              "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes". It requires that keys  and  values  are
+       <b>-u</b>     Disable UTF-8 support. UTF-8 support is enabled by default  when
+              "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a>  =  yes".  It requires that keys and values are
               valid UTF-8 strings.
 
-       <b>-v</b>     Enable  verbose  logging  for  debugging  purposes.  Multiple <b>-v</b>
+       <b>-v</b>     Enable verbose  logging  for  debugging  purposes.  Multiple  <b>-v</b>
               options make the software increasingly verbose.
 
-       <b>-w</b>     When updating a table, do not complain about attempts to  update
+       <b>-w</b>     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
               "<b>postconf -m</b>" command.
 
-              The <a href="postalias.1.html"><b>postalias</b>(1)</a> command can query any supported file type,  but
+              The  <a href="postalias.1.html"><b>postalias</b>(1)</a> command can query any supported file type, but
               it can create only the following file types:
 
-              <b>btree</b>  The  output is a btree file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.  This is
+              <b>btree</b>  The output is a btree file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.  This  is
                      available on systems with support for <b>db</b> databases.
 
-              <b>cdb</b>    The output is one  file  named  <i>file</i><b>_</b><i>name</i><b>.cdb</b>.   This  is
+              <b>cdb</b>    The  output  is  one  file  named <i>file</i><b>_</b><i>name</i><b>.cdb</b>.  This is
                      available on systems with support for <b>cdb</b> databases.
 
               <b>dbm</b>    The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
                      <i>file</i><b>_</b><i>name</i><b>.dir</b>.  This is available on systems with support
                      for <b>dbm</b> databases.
 
-              <b>fail</b>   A  table that reliably fails all requests. The lookup ta-
-                     ble name is used for logging only. This table  exists  to
+              <b>fail</b>   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.
 
               <b>hash</b>   The output is a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.  This is
                      available on systems with support for <b>db</b> databases.
 
-              <b>lmdb</b>   The output is a btree-based file,  named  <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
-                     <b>lmdb</b>  supports concurrent writes and reads from different
+              <b>lmdb</b>   The  output  is a btree-based file, named <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
+                     <b>lmdb</b> supports concurrent writes and reads from  different
                      processes,  unlike  other  supported  file-based  tables.
-                     This  is available on systems with support for <b>lmdb</b> data-
+                     This is available on systems with support for <b>lmdb</b>  data-
                      bases.
 
               <b>sdbm</b>   The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
                      <i>file</i><b>_</b><i>name</i><b>.dir</b>.  This is available on systems with support
                      for <b>sdbm</b> databases.
 
-              When no <i>file</i><b>_</b><i>type</i> is specified, the software uses  the  database
-              type   specified  via  the  <b><a href="postconf.5.html#default_database_type">default_database_type</a></b>  configuration
-              parameter.  The default value for this parameter depends on  the
+              When  no  <i>file</i><b>_</b><i>type</i> is specified, the software uses the database
+              type  specified  via  the  <b><a href="postconf.5.html#default_database_type">default_database_type</a></b>   configuration
+              parameter.   The default value for this parameter depends on the
               host environment.
 
        <i>file</i><b>_</b><i>name</i>
@@ -156,11 +161,11 @@ POSTALIAS(1)                                                      POSTALIAS(1)
               base.
 
 <b><a name="diagnostics">DIAGNOSTICS</a></b>
-       Problems are logged to the standard error stream and to  <b>syslogd</b>(8)  or
-       <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.  No output means that no problems were detected. Duplicate
+       Problems  are  logged to the standard error stream and to <b>syslogd</b>(8) or
+       <a href="postlogd.8.html"><b>postlogd</b>(8)</a>. No output means that no problems were detected.  Duplicate
        entries are skipped and are flagged with a warning.
 
-       <a href="postalias.1.html"><b>postalias</b>(1)</a> terminates with  zero  exit  status  in  case  of  success
+       <a href="postalias.1.html"><b>postalias</b>(1)</a>  terminates  with  zero  exit  status  in  case of success
        (including  successful  "<b>postalias  -q</b>"  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.
 
 <b><a name="configuration_parameters">CONFIGURATION PARAMETERS</a></b>
-       The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to  this  pro-
+       The  following  <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
        gram.
 
-       The  text  below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+       The text below provides only a parameter summary. See  <a href="postconf.5.html"><b>postconf</b>(5)</a>  for
        more details including examples.
 
        <b><a href="postconf.5.html#alias_database">alias_database</a> (see 'postconf -d' output)</b>
-              The alias databases for <a href="local.8.html"><b>local</b>(8)</a> delivery that are updated  with
+              The  alias databases for <a href="local.8.html"><b>local</b>(8)</a> delivery that are updated with
               "<b>newaliases</b>" or with "<b>sendmail -bi</b>".
 
        <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
-              The  default  location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+              The default location of the Postfix <a href="postconf.5.html">main.cf</a> and  <a href="master.5.html">master.cf</a>  con-
               figuration files.
 
        <b><a href="postconf.5.html#berkeley_db_create_buffer_size">berkeley_db_create_buffer_size</a> (16777216)</b>
-              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.
 
        <b><a href="postconf.5.html#berkeley_db_read_buffer_size">berkeley_db_read_buffer_size</a> (131072)</b>
@@ -199,19 +204,19 @@ POSTALIAS(1)                                                      POSTALIAS(1)
               and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
 
        <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
-              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.
 
        <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
-              Enable  preliminary SMTPUTF8 support for the protocols described
+              Enable preliminary SMTPUTF8 support for the protocols  described
               in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>, <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a>, and <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a>.
 
        <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
               The syslog facility of Postfix logging.
 
        <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
-              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:
index 67d256e06c2b3bc64f80adc277a18cde33b861ac..9fe7e2704ab2aa7ed5f96ec90c853303fbd67df5 100644 (file)
@@ -13,7 +13,7 @@ POSTCONF(1)                                                        POSTCONF(1)
 <b><a name="synopsis">SYNOPSIS</a></b>
    <b>Managing <a href="postconf.5.html">main.cf</a>:</b>
 
-       <b>postconf</b> [<b>-dfhHnopqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-C</b> <i>class,...</i>] [<i>parameter ...</i>]
+       <b>postconf</b> [<b>-dfhHjnopqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-C</b> <i>class,...</i>] [<i>parameter ...</i>]
 
        <b>postconf</b> [<b>-epv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>parameter</i><b>=</b><i>value ...</i>
 
@@ -23,7 +23,7 @@ POSTCONF(1)                                                        POSTCONF(1)
 
    <b>Managing <a href="master.5.html">master.cf</a> service entries:</b>
 
-       <b>postconf -M</b> [<b>-foqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>] <i>...</i>]
+       <b>postconf -M</b> [<b>-joqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>] <i>...</i>]
 
        <b>postconf -M</b> [<b>-ev</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type</i><b>=</b><i>value ...</i>
 
@@ -33,13 +33,14 @@ POSTCONF(1)                                                        POSTCONF(1)
 
    <b>Managing <a href="master.5.html">master.cf</a> service fields:</b>
 
-       <b>postconf -F</b> [<b>-fhHoqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>[<b>/</b><i>field</i>]] <i>...</i>]
+       <b>postconf -F</b> [<b>-fhHjoqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>[<b>/</b><i>field</i>]] <i>...</i>]
 
        <b>postconf -F</b> [<b>-ev</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type</i><b>/</b><i>field</i><b>=</b><i>value ...</i>
 
    <b>Managing <a href="master.5.html">master.cf</a> service parameters:</b>
 
-       <b>postconf -P</b> [<b>-fhHoqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>[<b>/</b><i>parameter</i>]] <i>...</i>]
+       <b>postconf -P</b> [<b>-fhHjoqvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>[<b>/</b><i>parameter</i>]]
+       <i>...</i>]
 
        <b>postconf -P</b> [<b>-ev</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type</i><b>/</b><i>parameter</i><b>=</b><i>value ...</i>
 
@@ -61,46 +62,46 @@ POSTCONF(1)                                                        POSTCONF(1)
 
 <b><a name="description">DESCRIPTION</a></b>
        By default, the <a href="postconf.1.html"><b>postconf</b>(1)</a> command displays the values of <a href="postconf.5.html"><b>main.cf</b></a> con-
-       figuration  parameters,  and  warns  about possible mis-typed parameter
-       names (Postfix 2.9 and later).  The command  can  also  change  <a href="postconf.5.html"><b>main.cf</b></a>
+       figuration parameters, and warns  about  possible  mis-typed  parameter
+       names  (Postfix  2.9  and  later).  The command can also change <a href="postconf.5.html"><b>main.cf</b></a>
        configuration parameter values, or display other configuration informa-
        tion about the Postfix mail system.
 
        Options:
 
-       <b>-a</b>     List the available SASL  plug-in  types  for  the  Postfix  SMTP
-              server.  The  plug-in  type is selected with the <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b>
-              configuration parameter by specifying one of  the  names  listed
+       <b>-a</b>     List  the  available  SASL  plug-in  types  for the Postfix SMTP
+              server. The plug-in type is selected  with  the  <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b>
+              configuration  parameter  by  specifying one of the names listed
               below.
 
-              <b>cyrus</b>  This  server  plug-in  is available when Postfix is built
+              <b>cyrus</b>  This server plug-in is available when  Postfix  is  built
                      with Cyrus SASL support.
 
               <b>dovecot</b>
                      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.
 
-       <b>-A</b>     List the available SASL  plug-in  types  for  the  Postfix  SMTP
+       <b>-A</b>     List  the  available  SASL  plug-in  types  for the Postfix SMTP
               client.  The plug-in type is selected with the <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b> or
               <b><a href="postconf.5.html#lmtp_sasl_type">lmtp_sasl_type</a></b> configuration parameters by specifying one of the
               names listed below.
 
-              <b>cyrus</b>  This  client  plug-in  is available when Postfix is built
+              <b>cyrus</b>  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>-b</b> [<i>template</i><b>_</b><i>file</i>]
               Display the message text that appears at the beginning of deliv-
-              ery  status notification (DSN) messages, expanding $<b>name</b> expres-
+              ery status notification (DSN) messages, expanding $<b>name</b>  expres-
               sions with actual values as described in <a href="bounce.5.html"><b>bounce</b>(5)</a>.
 
-              To override the <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a></b> parameter setting,  specify
-              a  template  file  name  at the end of the "<b>postconf -b</b>" command
-              line. Specify an empty file name to display  built-in  templates
+              To  override the <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a></b> parameter setting, specify
+              a template file name at the end of  the  "<b>postconf  -b</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.
 
        <b>-C</b> <i>class,...</i>
-              When displaying <a href="postconf.5.html"><b>main.cf</b></a> parameters, select only parameters  from
+              When  displaying <a href="postconf.5.html"><b>main.cf</b></a> parameters, select only parameters from
               the specified class(es):
 
               <b>builtin</b>
@@ -128,37 +129,37 @@ POSTCONF(1)                                                        POSTCONF(1)
 
               This feature is available with Postfix 2.9 and later.
 
-       <b>-d</b>     Print <a href="postconf.5.html"><b>main.cf</b></a> default parameter settings instead of actual  set-
-              tings.   Specify  <b>-df</b>  to  fold long lines for human readability
+       <b>-d</b>     Print  <a href="postconf.5.html"><b>main.cf</b></a> default parameter settings instead of actual set-
+              tings.  Specify <b>-df</b> to fold long  lines  for  human  readability
               (Postfix 2.9 and later).
 
-       <b>-e</b>     Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and update  parameter  set-
-              tings  with  the  "<i>name=value</i>"  pairs on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command
+       <b>-e</b>     Edit  the  <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and update parameter set-
+              tings with the "<i>name=value</i>" pairs  on  the  <a href="postconf.1.html"><b>postconf</b>(1)</a>  command
               line.
 
-              With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and replace  one
-              or  more service entries with new values as specified with "<i>ser-</i>
+              With  <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and replace one
+              or more service entries with new values as specified with  "<i>ser-</i>
               <i>vice/type=value</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
 
-              With <b>-F</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and replace  one
-              or  more  service fields with new values as specified with "<i>ser-</i>
-              <i>vice/type/field=value</i>" on the  <a href="postconf.1.html"><b>postconf</b>(1)</a>  command  line.  Cur-
-              rently,  the  "command" field contains the command name and com-
+              With  <b>-F</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and replace one
+              or more service fields with new values as specified  with  "<i>ser-</i>
+              <i>vice/type/field=value</i>"  on  the  <a href="postconf.1.html"><b>postconf</b>(1)</a>  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 <b>-P</b>, edit the  <a href="master.5.html"><b>master.cf</b></a>  configuration  file,  and  add  or
-              update  one  or  more  service  parameter  settings  (-o parame-
-              ter=value settings) with new  values  as  specified  with  "<i>ser-</i>
+              With  <b>-P</b>,  edit  the  <a href="master.5.html"><b>master.cf</b></a>  configuration  file, and add or
+              update one  or  more  service  parameter  settings  (-o  parame-
+              ter=value  settings)  with  new  values  as specified with "<i>ser-</i>
               <i>vice/type/parameter=value</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a> 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 <a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
 
-              The  <b>-e</b>  option is no longer needed with Postfix version 2.8 and
-              later, as it is assumed whenever a value is specified (empty  or
+              The <b>-e</b> 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).
 
        <b>-f</b>     Fold long lines when printing <a href="postconf.5.html"><b>main.cf</b></a> or <a href="master.5.html"><b>master.cf</b></a> configuration
@@ -167,24 +168,29 @@ POSTCONF(1)                                                        POSTCONF(1)
               This feature is available with Postfix 2.9 and later.
 
        <b>-F</b>     Show <a href="master.5.html"><b>master.cf</b></a> per-entry field settings (by default all services
-              and  all  fields),  formatted as "<i>service/type/field=value</i>", one
+              and all fields), formatted  as  "<i>service/type/field=value</i>",  one
               per line. Specify <b>-Ff</b> to fold long lines.
 
-              Specify one or more "<i>service/type/field</i>" instances on the  <a href="postconf.1.html"><b>post-</b></a>
-              <a href="postconf.1.html"><b>conf</b>(1)</a>  command line to limit the output to fields of interest.
-              Trailing parameter name or service type fields that are  omitted
+              Specify  one or more "<i>service/type/field</i>" instances on the <a href="postconf.1.html"><b>post-</b></a>
+              <a href="postconf.1.html"><b>conf</b>(1)</a> 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.
 
-       <b>-h</b>     Show  parameter  or attribute values without the "<i>name</i> = " label
+       <b>-h</b>     Show parameter or attribute values without the "<i>name</i> =  "  label
               that normally precedes the value.
 
-       <b>-H</b>     Show parameter or attribute names without the "  =  <i>value</i>"  that
+       <b>-H</b>     Show  parameter  or  attribute names without the " = <i>value</i>" that
               normally follows the name.
 
               This feature is available with Postfix 3.1 and later.
 
+       <b>-j</b>     JSON output. Format <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a> settings as one <b>{"</b><i>key</i><b>":</b>
+              <b>"</b><i>value</i><b>"}</b> object per line.
+
+              This feature is available with Postfix 3.11 and later.
+
        <b>-l</b>     List  the names of all supported mailbox locking methods.  Post-
               fix supports the following methods:
 
index a3c4bcf7b0b9d2c47f709990b7425c0836a62f0d..ed0d8b28f6a4c70379be450a37059367f2461f2c 100644 (file)
@@ -11,7 +11,7 @@ POSTMAP(1)                                                          POSTMAP(1)
        postmap - Postfix lookup table management
 
 <b><a name="synopsis">SYNOPSIS</a></b>
-       <b>postmap</b> [<b>-bfFhimnNoprsuUvw</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
+       <b>postmap</b> [<b>-bfFhijmnNoprsuUvw</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
                [<i>file</i><b>_</b><i>type</i>:]<i>file</i><b>_</b><i>name</i> ...
 
 <b><a name="description">DESCRIPTION</a></b>
@@ -130,62 +130,67 @@ POSTMAP(1)                                                          POSTMAP(1)
               truncate an existing database. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> creates  a
               new database from the entries in <b>file_name</b>.
 
+       <b>-j</b>     JSON  output.  Format  the  output from <b>-q</b> and <b>-s</b> as one <b>{"</b><i>key</i><b>":</b>
+              <b>"</b><i>value</i><b>"}</b> object per line.
+
+              This feature is available in Postfix version 3.11 and later.
+
        <b>-m</b>     Enable MIME parsing with "<b>-b</b>" and "<b>-h</b>".
 
               This feature is available in Postfix version 2.6 and later.
 
-       <b>-N</b>     Include  the  terminating  null character that terminates lookup
-              keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a>  does  whatever  is  the
+       <b>-N</b>     Include the terminating null character  that  terminates  lookup
+              keys  and  values.  By  default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is the
               default for the host operating system.
 
-       <b>-n</b>     Don't  include  the  terminating  null character that terminates
-              lookup keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever  is
+       <b>-n</b>     Don't include the terminating  null  character  that  terminates
+              lookup  keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is
               the default for the host operating system.
 
-       <b>-o</b>     Do  not release root privileges when processing a non-root input
-              file. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> drops root privileges and  runs  as
+       <b>-o</b>     Do not release root privileges when processing a non-root  input
+              file.  By  default, <a href="postmap.1.html"><b>postmap</b>(1)</a> drops root privileges and runs as
               the source file owner instead.
 
-       <b>-p</b>     Do  not  inherit the file access permissions from the input file
-              when creating a new file.   Instead,  create  a  new  file  with
+       <b>-p</b>     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).
 
-       <b>-q</b> <i>key</i> Search  the  specified  maps  for  <i>key</i> and write the first value
-              found to the standard output stream. The  exit  status  is  zero
+       <b>-q</b> <i>key</i> Search the specified maps for <i>key</i>  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  <a href="access.5.html">access(5)</a>,  <a href="canonical.5.html">canonical(5)</a>, <a href="transport.5.html">transport(5)</a>, <a href="virtual.5.html">vir-</a>
+              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  <a href="access.5.html">access(5)</a>,  <a href="canonical.5.html">canonical(5)</a>,  <a href="transport.5.html">transport(5)</a>,  <a href="virtual.5.html">vir-</a>
               <a href="virtual.5.html">tual(5)</a> and other Postfix table-driven features.
 
-              If a key value of <b>-</b> is specified, the program reads  key  values
-              from  the standard input stream and writes one line of <i>key value</i>
+              If  a  key value of <b>-</b> is specified, the program reads key values
+              from the standard input stream and writes one line of <i>key  value</i>
               output for each key that was found. The exit status is zero when
               at least one of the requested keys was found.
 
-       <b>-r</b>     When  updating a table, do not complain about attempts to update
+       <b>-r</b>     When updating a table, do not complain about attempts to  update
               existing entries, and make those updates anyway.
 
-       <b>-s</b>     Retrieve all database elements, and write one line of <i>key  value</i>
-              output  for  each  element. The elements are printed in database
-              order, which is not necessarily the same as the  original  input
+       <b>-s</b>     Retrieve  all database elements, and write one line of <i>key value</i>
+              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.
 
-       <b>-u</b>     Disable UTF-8 support. UTF-8 support is enabled by default  when
-              "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a>  =  yes".  It requires that keys and values are
+       <b>-u</b>     Disable  UTF-8 support. UTF-8 support is enabled by default when
+              "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes". It requires that keys  and  values  are
               valid UTF-8 strings.
 
        <b>-U</b>     With "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", force UTF-8 syntax checks with the
               <b>-b</b> and <b>-h</b> options.
 
-       <b>-v</b>     Enable  verbose  logging  for  debugging  purposes.  Multiple <b>-v</b>
+       <b>-v</b>     Enable verbose  logging  for  debugging  purposes.  Multiple  <b>-v</b>
               options make the software increasingly verbose.
 
-       <b>-w</b>     When updating a table, do not complain about attempts to  update
+       <b>-w</b>     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 <a href="postmap.1.html"><b>postmap</b>(1)</a> command can query any supported file type, but it
               can create only the following file types:
 
-              <b>btree</b>  The  output  file  is  a  btree file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
-                     This is available on systems with support  for  <b>db</b>  data-
+              <b>btree</b>  The output file is  a  btree  file,  named  <i>file</i><b>_</b><i>name</i><b>.db</b>.
+                     This  is  available  on systems with support for <b>db</b> data-
                      bases.
 
-              <b>cdb</b>    The  output  consists  of  one file, named <i>file</i><b>_</b><i>name</i><b>.cdb</b>.
-                     This is available on systems with support for  <b>cdb</b>  data-
+              <b>cdb</b>    The output consists of  one  file,  named  <i>file</i><b>_</b><i>name</i><b>.cdb</b>.
+                     This  is  available on systems with support for <b>cdb</b> data-
                      bases.
 
               <b>dbm</b>    The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
                      <i>file</i><b>_</b><i>name</i><b>.dir</b>.  This is available on systems with support
                      for <b>dbm</b> databases.
 
-              <b>fail</b>   A  table that reliably fails all requests. The lookup ta-
-                     ble name is used for logging only. This table  exists  to
+              <b>fail</b>   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.
 
-              <b>hash</b>   The  output  file  is  a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
-                     This is available on systems with support  for  <b>db</b>  data-
+              <b>hash</b>   The output file is a  hashed  file,  named  <i>file</i><b>_</b><i>name</i><b>.db</b>.
+                     This  is  available  on systems with support for <b>db</b> data-
                      bases.
 
-              <b>lmdb</b>   The  output  is a btree-based file, named <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
-                     <b>lmdb</b> supports concurrent writes and reads from  different
+              <b>lmdb</b>   The output is a btree-based file,  named  <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
+                     <b>lmdb</b>  supports concurrent writes and reads from different
                      processes,  unlike  other  supported  file-based  tables.
-                     This is available on systems with support for <b>lmdb</b>  data-
+                     This  is available on systems with support for <b>lmdb</b> data-
                      bases.
 
               <b>sdbm</b>   The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
                      <i>file</i><b>_</b><i>name</i><b>.dir</b>.  This is available on systems with support
                      for <b>sdbm</b> databases.
 
-              When  no  <i>file</i><b>_</b><i>type</i> is specified, the software uses the database
-              type  specified  via  the  <b><a href="postconf.5.html#default_database_type">default_database_type</a></b>   configuration
+              When no <i>file</i><b>_</b><i>type</i> is specified, the software uses  the  database
+              type   specified  via  the  <b><a href="postconf.5.html#default_database_type">default_database_type</a></b>  configuration
               parameter.
 
        <i>file</i><b>_</b><i>name</i>
@@ -236,12 +241,12 @@ POSTMAP(1)                                                          POSTMAP(1)
               base.
 
 <b><a name="diagnostics">DIAGNOSTICS</a></b>
-       Problems are logged to the standard error stream and to  <b>syslogd</b>(8)  or
+       Problems  are  logged to the standard error stream and to <b>syslogd</b>(8) or
        <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.  No output means that no problems were detected. Duplicate
        entries are skipped and are flagged with a warning.
 
        <a href="postmap.1.html"><b>postmap</b>(1)</a> terminates with zero exit status in case of success (includ-
-       ing  successful  "<b>postmap -q</b>" lookup) and terminates with non-zero exit
+       ing successful "<b>postmap -q</b>" lookup) and terminates with  non-zero  exit
        status in case of failure.
 
 <b><a name="environment">ENVIRONMENT</a></b>
@@ -252,12 +257,12 @@ POSTMAP(1)                                                          POSTMAP(1)
               Enable verbose logging for debugging purposes.
 
 <b><a name="configuration_parameters">CONFIGURATION PARAMETERS</a></b>
-       The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to  this  pro-
-       gram.   The  text  below  provides  only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+       The  following  <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+       gram.  The text below provides only  a  parameter  summary.  See  <a href="postconf.5.html"><b>post-</b></a>
        <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
 
        <b><a href="postconf.5.html#berkeley_db_create_buffer_size">berkeley_db_create_buffer_size</a> (16777216)</b>
-              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.
 
        <b><a href="postconf.5.html#berkeley_db_read_buffer_size">berkeley_db_read_buffer_size</a> (131072)</b>
@@ -265,7 +270,7 @@ POSTMAP(1)                                                          POSTMAP(1)
               hash or btree tables.
 
        <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
-              The default location of the Postfix <a href="postconf.5.html">main.cf</a> and  <a href="master.5.html">master.cf</a>  con-
+              The  default  location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
               figuration files.
 
        <b><a href="postconf.5.html#default_database_type">default_database_type</a> (see 'postconf -d' output)</b>
@@ -273,19 +278,19 @@ POSTMAP(1)                                                          POSTMAP(1)
               and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
 
        <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
-              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.
 
        <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
-              Enable preliminary SMTPUTF8 support for the protocols  described
+              Enable  preliminary SMTPUTF8 support for the protocols described
               in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>, <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a>, and <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a>.
 
        <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
               The syslog facility of Postfix logging.
 
        <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
-              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:
index 4c7f02bd162e8fde5c8556e63ac13ef306cb4d1c..10ee1fb518e3592a3076eea0e721f9697d7f4cde 100644 (file)
@@ -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
index 5b8ca5382c067231224ea0a5c1b15b0355ef3ed5..bcc3111a6d79a34bb90090fe00ee38a612f8392a 100644 (file)
@@ -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:
index 1cd785e054f89056d66607319d00ef43c09cdc20..3dd7a386468cfe9c9e9fd3f4f6dc19b7771306b1 100644 (file)
@@ -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
index bb2e2418f69494d004331d969c88540b4e9f15e0..3ad72a5f501d08754c2a94d7d3130f36e51ab57b 100644 (file)
@@ -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 
index 28311c39f99ee5095022893425ebf7177b792429..425dfb3f43d1624f32ce91ae059daf704e21ee8f 100644 (file)
@@ -1882,3 +1882,10 @@ allalnumus
 Christophe
 Kalt
 stdlib
+Nfijnoprsuvw
+bfFhijmnNoprsuUvw
+dfhHjnopqvx
++fhHjoqvx
++joqvx
+fhHjoqvx
+joqvx
index 0eb572e7d8f9d22a99b7d71a5c9930cd64e46197..772baf79fd3a45d4ff4c52983ab3de700f7b4e57 100644 (file)
@@ -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
index 751a55995daa8bb8188501917f0ccef761ed15d9..c21bdeea18b1c4d69c895021c37012ff18a1a7f9 100644 (file)
@@ -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
 
index b3c9de87f79dbd7380a94b082ad5e20eb2741b1e..2ab13426a972a35211392b078040fa8044c47256 100644 (file)
@@ -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
 /*     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
 #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);
index 63a0e5a96b4a067658ac74c1d8ebac167776806d..75a6f428f2a378c9de4855f73d2454236bb71298 100644 (file)
@@ -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)
index d9e96831dd4d4c8eede1522c29aa280ba8c9c1e2..c53ca4a9a310dddb074fb5728af2f9f029f2f400 100644 (file)
@@ -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]
 /*     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;
index faeed0ded7ba6f1317519a4be72dc9f8a21f1a4d..9415f6f6c98df6dee36356492d67d841c4224997 100644 (file)
@@ -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)
index e0ecc6efba931d6ecdac86bb9aa0a5be35bbcc09..e0902862b91a05b280def2a5b3304d1e6a97f244 100644 (file)
@@ -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
index 64296581715a4b9752d479e5c0d25303e307c7a2..2808290bf444685a416947d19d16cc14a88076f7 100644 (file)
@@ -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));
index e58e2617b45ba34092710acee79260c158486bbb..73d523b7a4642358e02a90b2e24917d182be6e28 100644 (file)
@@ -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);
index bddb1af25f8337781a29d9f9a9a04481c8d18fcf..48061a10637d6590c222726689a39152230d5120 100644 (file)
@@ -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 (file)
index 0000000..34c5371
--- /dev/null
@@ -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 (file)
index 0000000..f8e4534
--- /dev/null
@@ -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 (file)
index 0000000..dd1ac51
--- /dev/null
@@ -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 (file)
index 0000000..bfb1f01
--- /dev/null
@@ -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 (file)
index 0000000..6bac54d
--- /dev/null
@@ -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 (file)
index 0000000..8ff85dd
--- /dev/null
@@ -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 (file)
index 0000000..aa7a67f
--- /dev/null
@@ -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 (file)
index 0000000..a0c1119
--- /dev/null
@@ -0,0 +1,2 @@
+{"foo/unix/mydestination": "example.net"}
+{"foo/unix/whatever": "example.net"}
index b415d57b1f37862f3816d3c41262009c5ebd5886..adb33f2c2ac4d40907ec69f4208462d924509fb9 100644 (file)
@@ -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
 
index 5ebc2e3122ccbb07bc7673de07a8fd7ec01cc258..eaecf319bc2e123c30ab3e03ab8b3d6610d20cfe 100644 (file)
@@ -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
 /*     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);
index f54af3fcc651213c26183afef6862a24a9fd7685..81af57ac7dec8875ce69452b18d43ad4694650d4 100644 (file)
 /*     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 <sys_defs.h>
 #include <ctype.h>
+#include <stdarg.h>
 #include <string.h>
 
  /*
@@ -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 <argv.h>
 #include <msg.h>
 #include <msg_vstream.h>
 
 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);
 }
 
index 6b493151f3a5ed5800025b3b76c8e6e33f73cfe8..3cf2d117b13200094ff2281e88e8f98e2c5d866c 100644 (file)
@@ -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 *);