-TLOCAL_EXP
-TLOCAL_STATE
-TLONG_NAME_MASK
--TMAC_EXP
+-TMAC_EXP_CONTEXT
+-TMAC_EXP_OP_INFO
-TMAC_HEAD
-TMAC_PARSE
-TMAIL_PRINT
global/deliver_request.h, global/mail_params.h, global/sent.c,
*qmgr/qmgr.c, *qmgr/qmgr_active.c, *qmgr/qmgr_message.c.
+20140908-14
+
+ Feature: for the first time in 17 years, support for
+ ${name?if-nonempty:if-empty} macro expressions, and for
+ logical expressions ${logical-expr?if-true:if-false}. In
+ preparation for configurable message headers and logging.
+ Files: util/mac_expand.c.
+
+20140914
+
+ Bugfix (introduced: 19971026): a zero precision value in
+ %.*s and $.<digits>s was implemented as if no precision
+ value was specified, i.e. print the entire string. This was
+ not harmful, it just looked weird. File: util/vbuf_print.c.
+
+20120917
+
+ Feature: RFC 7372 enhanced status code for unknown SMTP
+ client hostnames. File: smtpd/smtpd_check.c
+
+ Bugfix: the accept() calls in test progams escaped attention
+ when Postfix 2.2 was ported to IPv6. Problem found by Mark
+ Martinec. Files: smtpstone/smtp-sink.c, smtpstone/qmqp-sink.c.
+
+20140918
+
+ Cleanup: log a warning when the cleanup server detects too
+ many hops. smtpd(8) does not log any of the CLEANUP_STAT_XXX
+ results. The pickup server logs some because there is no
+ client to send the problem description to. This logic of
+ who logs what needs to be revisited. File:
+ cleanup/cleanup_message.c.
+
+20140919
+
+ Usability: randmap and pipemap syntax, for example,
+ pipemap:{type_1:name_1, ..., type_n:name_n}. This required
+ small updates to code that parses input into lookup table
+ names. Files: global/data_redirect.c, global/maps.c,
+ global/server_acl.c, postconf/postconf.c, postconf/postconf_dbms.c,
+ postconf/test58.ref, proto/DATABASE_README.html,
+ proxymap/proxymap.c, smtpd/smtpd_check.c, util/argv.h,
+ util/balpar.c, util/dict_pipe.c, util/dict_random.c,
+ util/match_list.c, util/mystrtok.c, util/argv_splitq.c,
+ util/stringops.h.
+
+ Cleanup: added PRINTFLIKE() to enable missing format string
+ checks. Files: bounce/bounce_template.h, global/memcache_proto.h,
+ global/dict_memcache, postconf/postconf.h, util/dict.h,
+ util/msg.h.
+
+20140920
+
+ Bugfix (introduced: 20080212): incorrect client name in
+ reject messages from check_reverse_client_hostname_access
+ and check_reverse_client_hostname_{a,mx,ns}_access. They
+ replied with the verified client name, instead of the name
+ that was rejected. Problem reported by Reindl Harald. File:
+ smtpd/smtpd_check.c.
+
+20140921
+
+ Cleanup: postconf code to determine the default mydomain
+ value had not evolved since 1997, while the rest of Postfix
+ changed in 2000. File: postconf/postconf-dbms.c.
format is described in pcre_table(5). The lookup table name as used in
"pcre:table" is the name of the regular expression file.
p\bpi\bip\bpe\bem\bma\bap\bp (read-only)
- A pipeline of lookup tables. Example: "p\bpi\bip\bpe\bem\bma\bap\bp:\b:!type1:name1! ...
- !typen:namen". Each "pipemap:" query is given to the first table. Each
+ A pipeline of lookup tables. Example: "pipemap:{type1:name1, ...,
+ typen:namen}". Each "pipemap:" query is given to the first table. Each
lookup result becomes the query for the next table in the pipeline, and
the last table produces the final result. When any table lookup
- produces no result, the pipeline produces no result. The first ASCII
- character after "pipemap:" will be used as the separator between the
- lookup tables that follow (do not use space, ",", ":" or non-ASCII).
+ produces no result, the pipeline produces no result. The first and last
+ characters of the "pipemap:" table name must be "{" and "}". Within
+ these, individual maps are separated with comma or whitespace.
p\bpg\bgs\bsq\bql\bl (read-only)
PostgreSQL database client. Configuration details are given in
pgsql_table(5).
Postfix proxymap(8) client for shared access to Postfix databases. The
lookup table name syntax is "proxy:type:table".
r\bra\ban\bnd\bdm\bma\bap\bp (read-only)
- An in-memory table that performs random selection. Example: "r\bra\ban\bnd\bdm\bma\bap\bp:\b:
- !result1! ... !resultn". Each table query returns a random choice from
- the specified results. The first ASCII character after "randmap:" will
- be used as the separator between the results that follow (do not use
- space, ",", ":" or non-ASCII).
+ An in-memory table that performs random selection. Example: "randmap:
+ {result1. ..., resultn}". Each table query returns a random choice from
+ the specified results. The first and last characters of the "randmap:
+ " table name must be "{" and "}". Within these, individual maps are
+ separated with comma or whitespace.
r\bre\beg\bge\bex\bxp\bp (read-only)
A lookup table based on regular expressions. The file format is
described in regexp_table(5). The lookup table name as used in "regexp:
(mantools/srctoman - makedefs | nroff -man | less) with information
about build options that are not described in the INSTALL instructions.
+Major changes with snapshot 20140921
+====================================
+
+In preparation for configurable mail headers and logging, new main.cf
+support for if-then-else expressions:
+
+ ${name?{text1}:{text2}}
+
+and for logical expressions:
+
+ ${{text1}=={text2}?{text3}:{text4}}
+ ${{text1}!={text2}?{text3}:{text4}}
+
+Whitespace before and after {text} is ignored. This can help to
+make complex expressions more readable. See the postconf(5) manpage
+for further details.
+
+The syntax of pipemap and randmap has improved. Postfix now uses
+pipemap:{map1, ..., mapN} and randmap:{result1, ..., resultN}.
+The old syntax was just too ugly.
+
+It is expected that usability can be improved elsewhere in Postfix
+in a similar manner. For example,
+
+- Milter clients and policy clients with non-default settings:
+ smtpd_milters = {inet:host:port, timeout=xxx, default_action=yyy}, ...
+
+- Parameter overrides in master.cf with commas and spaces:
+ -o { parameter = value ... }
+
Major changes with snapshot 20140801
====================================
Things to do after the stable release:
+ The pickup daemon logs warnings only when the cleanup daemon
+ dit not provide a "reason" attribute. Is this logic right?
+
+ Make the "relayed after delay" notification conditional on
+ the presence of the DSN_NOTIFY_DELAY flag.
+
up-convert myhostname to UTF-8 in MIME boundary strings?
+ Update postconf to recursively parse legacy-style mapnames
+ in random:, pipe:, and other multimaps.
+
+ Introduce constants to replace all the ad-hoc ", \t\r\n"
+ etc. for tokenization. That will have to go into an "util"
+ file because match_strings(3), dict_pipe(3) and dict_random(3)
+ depend on these definitions.
+
+ Make sure that proxy: can handle random:, pipe:, and other
+ multimaps.
+
+ Add a switch to consider postscreen deep protocol tests as
+ "completed" when receiving "RSET" after "RCPT TO" and the
+ session has passed all tests up to that point. RSET becomes
+ like QUIT except perhaps that it does not hang up.
+
+ apipe: map, splits results into address lists and performs
+ lookups for the invidual addresses, converting back and
+ forth between external and internal forms.
+
+ union: map, concatenates results, default separator is ','.
+
+ Include <3htPpS5B6bzbcpM@spike.porcupine.org> example with
+ filter policies for different mail streams. Correction:
+ filter should be content_filter. Posted Wed, 10 Sep 2014
+ 09:53:52 -0400 (EDT).
+
Clarify that receive_override_options should not be used
with smtpd_proxy_filter.
<dt> <b>pipemap</b> (read-only) </dt>
<dd> A pipeline of lookup tables. Example:
-"<b><a href="DATABASE_README.html#types">pipemap</a>:</b><i>!type<sub>1</sub>:name<sub>1</sub>! ...
-!type<sub>n</sub>:name<sub>n</sub></i>". Each "<a href="DATABASE_README.html#types">pipemap</a>:" query is
+"<a href="DATABASE_README.html#types">pipemap</a>:{<i>type<sub>1</sub>:name<sub>1</sub>, ...,
+type<sub>n</sub>:name<sub>n</sub></i>}". Each "<a href="DATABASE_README.html#types">pipemap</a>:" query is
given to the first table. Each lookup result becomes the query for
the next table in the pipeline, and the last table produces the
final result. When any table lookup produces no result, the pipeline
-produces no result. The first ASCII character after "<a href="DATABASE_README.html#types">pipemap</a>:"
-will be used as the separator between the lookup tables that follow
-(do not use space, ",", ":" or non-ASCII). </dd>
+produces no result. The first and last characters of the "<a href="DATABASE_README.html#types">pipemap</a>:"
+table name must be "{" and "}". Within these, individual maps are
+separated with comma or whitespace. </dd>
<dt> <b>pgsql</b> (read-only) </dt>
<dt> <b>randmap</b> (read-only) </dt>
<dd> An in-memory table that performs random selection. Example:
-"<b><a href="DATABASE_README.html#types">randmap</a>:</b><i>!result<sub>1</sub>! ... !result<sub>n</sub></i>".
+"<a href="DATABASE_README.html#types">randmap</a>:{<i>result<sub>1</sub>. ..., result<sub>n</sub></i>}".
Each table query returns a random choice from the specified results.
-The first ASCII character after "<a href="DATABASE_README.html#types">randmap</a>:" will be used as the
-separator between the results that follow (do not use space, ",",
-":" or non-ASCII). </dd>
+The first and last characters of the "<a href="DATABASE_README.html#types">randmap</a>:" table name must be
+"{" and "}". Within these, individual maps are separated with comma
+or whitespace. </dd>
<dt> <b>regexp</b> (read-only) </dt>
<b>postconf -a</b>|<b>-A</b>|<b>-l</b>|<b>-m</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>]
<b>DESCRIPTION</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-
+ 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). It can also change <a href="postconf.5.html"><b>main.cf</b></a> configura-
+ names (Postfix 2.9 and later). It can also change <a href="postconf.5.html"><b>main.cf</b></a> configura‐
tion parameter values, or display other configuration information about
the Postfix mail system.
Options:
<b>-a</b> List the available SASL server plug-in types. The SASL plug-in
- type is selected with the <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b> configuration parame-
+ type is selected with the <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b> configuration parame‐
ter by specifying one of the names listed below.
<b>cyrus</b> This server plug-in is available when Postfix is built
This feature is available with Postfix 2.3 and later.
<b>-A</b> List the available SASL client plug-in types. The SASL 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> con-
+ 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> con‐
figuration parameters by specifying one of the names listed
below.
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, replacing $<b>name</b> expres-
+ Display the message text that appears at the beginning of deliv‐
+ ery status notification (DSN) messages, replacing $<b>name</b> expres‐
sions with actual values as described in <a href="bounce.5.html"><b>bounce</b>(5)</a>.
To override the built-in templates, specify a template file name
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-
+ <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-
+ <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>
+ 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 specied 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-
+ or more service fields with new values as specied 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-
+ "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 specied with "<i>ser-</i>
+ update one or more service parameter settings (-o parame‐
+ ter=value settings) with new values as specied 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
and all fields), formatted as one "<i>service/type/field=value</i>" 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.
+ Specify one or more "<i>service/type/field</i>" instances on the <b>post</b>‐\b‐
+ <b>conf</b>(1) command line to limit the output to fields of interest.
Trailing parameter name or service type fields that are omitted
will be handled as "*" wildcard fields.
<b>-h</b> Show parameter or attribute values without the "<i>name</i> = " label
that normally precedes the value.
- <b>-l</b> List the names of all supported mailbox locking methods. Post-
+ <b>-l</b> List the names of all supported mailbox locking methods. Post‐
fix supports the following methods:
<b>flock</b> A kernel-based advisory locking method for local files
<b>dotlock</b>
An application-level locking method. An application locks
- a file named <i>filename</i> by creating a file named <i>file-</i>
+ a file named <i>filename</i> by creating a file named <i>file‐</i>
<i>name</i><b>.lock</b>. The application is expected to remove its own
lock file, as well as stale lock files that were left
behind after abnormal program termination.
<b>-m</b> List the names of all supported lookup table types. In Postfix
configuration files, lookup tables are specified as <i>type</i><b>:</b><i>name</i>,
- where <i>type</i> is one of the types listed below. The table <i>name</i> syn-
- tax depends on the lookup table type as described in the <a href="DATABASE_README.html">DATA</a>-
- <a href="DATABASE_README.html">BASE_README</a> document.
+ where <i>type</i> is one of the types listed below. The table <i>name</i> syn‐
+ tax depends on the lookup table type as described in the DATA‐
+ <a href="BASE_README.html">BASE_README</a> document.
<b>btree</b> A sorted, balanced tree structure. Available on systems
with support for Berkeley DB databases.
- <b>cdb</b> A read-optimized structure with no support for incremen-
+ <b>cdb</b> A read-optimized structure with no support for incremen‐
tal updates. Available on systems with support for CDB
databases.
Domain Routing (CIDR) patterns. This is described in
<a href="cidr_table.5.html"><b>cidr_table</b>(5)</a>.
- <b>dbm</b> An indexed file type based on hashing. Available on sys-
+ <b>dbm</b> An indexed file type based on hashing. Available on sys‐
tems with support for DBM databases.
<b>environ</b>
The UNIX process environment array. The lookup key is the
- variable name. Originally implemented for testing, some-
+ variable name. Originally implemented for testing, some‐
one may find this useful someday.
- <b>fail</b> A table that reliably fails all requests. The lookup ta-
- ble name is used for logging. This table exists to sim-
+ <b>fail</b> A table that reliably fails all requests. The lookup ta‐
+ ble name is used for logging. This table exists to sim‐
plify Postfix error tests.
- <b>hash</b> An indexed file type based on hashing. Available on sys-
+ <b>hash</b> An indexed file type based on hashing. Available on sys‐
tems with support for Berkeley DB databases.
<b>internal</b>
when a process terminates.
<b>lmdb</b> OpenLDAP LMDB database (a memory-mapped, persistent
- file). Available on systems with support for LMDB data-
+ file). Available on systems with support for LMDB data‐
bases. This is described in <a href="lmdb_table.5.html"><b>lmdb_table</b>(5)</a>.
<b>ldap</b> (read-only)
LDAP database client. This is described in <a href="ldap_table.5.html"><b>ldap_table</b>(5)</a>.
<b>memcache</b>
- Memcache database client. This is described in <a href="memcache_table.5.html"><b>mem-</b></a>
- <a href="memcache_table.5.html"><b>cache_table</b>(5)</a>.
+ Memcache database client. This is described in <b>mem</b>‐\b‐
+ <b>cache_table</b>(5).
<b>mysql</b> (read-only)
MySQL database client. Available on systems with support
- for MySQL databases. This is described in <a href="mysql_table.5.html"><b>mysql_ta-</b></a>
- <a href="mysql_table.5.html"><b>ble</b>(5)</a>.
+ for MySQL databases. This is described in <b>mysql_ta</b>‐\b‐
+ <b>ble</b>(5).
<b>pcre</b> (read-only)
- A lookup table based on Perl Compatible Regular Expres-
+ A lookup table based on Perl Compatible Regular Expres‐
sions. The file format is described in <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
<b>pgsql</b> (read-only)
<a href="pgsql_table.5.html"><b>pgsql_table</b>(5)</a>.
<b>pipemap</b> (read-only)
- A pipeline of lookup tables. Example:
- "<b><a href="DATABASE_README.html#types">pipemap</a>:</b><i>!type</i><b>_</b><i>1:name</i><b>_</b><i>1! ... !type</i><b>_</b><i>n:name</i><b>_</b><i>n</i>". Each
- "<a href="DATABASE_README.html#types">pipemap</a>:" query is given to the first table. Each
+ A lookup table that constructs a pipeline of tables.
+ Example: "<b><a href="DATABASE_README.html#types">pipemap</a>:{</b><i>type</i><b>_</b><i>1:name</i><b>_</b><i>1, ..., type</i><b>_</b><i>n:name</i><b>_</b><i>n</i><b>}</b>".
+ Each "<a href="DATABASE_README.html#types">pipemap</a>:" query is given to the first table. Each
lookup result becomes the query for the next table in the
pipeline, and the last table produces the final result.
When any table lookup produces no result, the pipeline
- produces no result. The first ASCII character after
- "<a href="DATABASE_README.html#types">pipemap</a>:" will be used as the separator between the
- lookup tables that follow (do not use space, ",", ":" or
- non-ASCII).
+ produces no result. The first and last characters of the
+ "<a href="DATABASE_README.html#types">pipemap</a>:" table name must be "<b>{</b>" and "<b>}</b>". Within these,
+ individual maps are separated with comma or whitespace.
- <b>proxy</b> Postfix <a href="proxymap.8.html"><b>proxymap</b>(8)</a> client for shared access to Postfix
+ <b>proxy</b> Postfix <a href="proxymap.8.html"><b>proxymap</b>(8)</a> client for shared access to Postfix
databases. The table name syntax is <i>type</i><b>:</b><i>name</i>.
<b>randmap</b> (read-only)
- An in-memory table that performs random selection. Exam-
- ple: "<b><a href="DATABASE_README.html#types">randmap</a>:</b><i>!result</i><b>_</b><i>1! ... !result</i><b>_</b><i>n</i>". Each table query
- returns a random choice from the specified results. The
- first ASCII character after "<a href="DATABASE_README.html#types">randmap</a>:" will be used as
- the separator between the results that follow (do not use
- space, ",", ":" or non-ASCII).
+ An in-memory table that performs random selection. Exam‐
+ ple: "<b><a href="DATABASE_README.html#types">randmap</a>:{</b><i>result</i><b>_</b><i>1, ..., result</i><b>_</b><i>n</i><b>}</b>". Each table
+ query returns a random choice from the specified results.
+ The first and last characters of the "<a href="DATABASE_README.html#types">randmap</a>:" table
+ name must be "<b>{</b>" and "<b>}</b>". Within these, individual maps
+ are separated with comma or whitespace.
<b>regexp</b> (read-only)
- A lookup table based on regular expressions. The file
+ A lookup table based on regular expressions. The file
format is described in <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a>.
- <b>sdbm</b> An indexed file type based on hashing. Available on sys-
+ <b>sdbm</b> An indexed file type based on hashing. Available on sys‐
tems with support for SDBM databases.
<b>socketmap</b> (read-only)
- Sendmail-style socketmap client. The table name is
- <b>inet</b>:<i>host</i>:<i>port</i>:<i>name</i> for a TCP/IP server, or <b>unix</b>:<i>path-</i>
- <i>name</i>:<i>name</i> for a UNIX-domain server. This is described in
+ Sendmail-style socketmap client. The table name is
+ <b>inet</b>:<i>host</i>:<i>port</i>:<i>name</i> for a TCP/IP server, or <b>unix</b>:<i>path‐</i>
+ <i>name</i>:<i>name</i> for a UNIX-domain server. This is described in
<a href="socketmap_table.5.html"><b>socketmap_table</b>(5)</a>.
<b>sqlite</b> (read-only)
SQLite database. This is described in <a href="sqlite_table.5.html"><b>sqlite_table</b>(5)</a>.
<b>static</b> (read-only)
- A table that always returns its name as lookup result.
- For example, <b><a href="DATABASE_README.html#types">static</a>:foobar</b> always returns the string <b>foo-</b>
+ A table that always returns its name as lookup result.
+ For example, <b><a href="DATABASE_README.html#types">static</a>:foobar</b> always returns the string <b>foo</b>‐\b‐
<b>bar</b> as lookup result.
<b>tcp</b> (read-only)
TCP/IP client. The protocol is described in <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>.
<b>texthash</b> (read-only)
- Produces similar results as <a href="DATABASE_README.html#types">hash</a>: files, except that you
- don't need to run the <a href="postmap.1.html"><b>postmap</b>(1)</a> command before you can
- use the file, and that it does not detect changes after
+ Produces similar results as <a href="DATABASE_README.html#types">hash</a>: files, except that you
+ don't need to run the <a href="postmap.1.html"><b>postmap</b>(1)</a> command before you can
+ use the file, and that it does not detect changes after
the file is read.
<b>unix</b> (read-only)
- A limited view of the UNIX authentication database. The
+ A limited view of the UNIX authentication database. The
following tables are implemented:
<b>unix:passwd.byname</b>
- The table is the UNIX password database. The key
- is a login name. The result is a password file
+ The table is the UNIX password database. The key
+ is a login name. The result is a password file
entry in <b>passwd</b>(5) format.
<b>unix:group.byname</b>
The table is the UNIX group database. The key is a
- group name. The result is a group file entry in
+ group name. The result is a group file entry in
<b>group</b>(5) format.
- Other table types may exist depending on how Postfix was built.
+ Other table types may exist depending on how Postfix was built.
- <b>-M</b> Show <a href="master.5.html"><b>master.cf</b></a> file contents instead of <a href="postconf.5.html"><b>main.cf</b></a> file contents.
+ <b>-M</b> Show <a href="master.5.html"><b>master.cf</b></a> file contents instead of <a href="postconf.5.html"><b>main.cf</b></a> file contents.
Specify <b>-Mf</b> to fold long lines for human readability.
- Specify zero or more arguments, each with a <i>service-name</i> or <i>ser-</i>
- <i>vice-name/service-type</i> pair, where <i>service-name</i> is the first
- field of a <a href="master.5.html">master.cf</a> entry and <i>service-type</i> is one of (<b>inet</b>,
+ Specify zero or more arguments, each with a <i>service-name</i> or <i>ser‐</i>
+ <i>vice-name/service-type</i> pair, where <i>service-name</i> is the first
+ field of a <a href="master.5.html">master.cf</a> entry and <i>service-type</i> is one of (<b>inet</b>,
<b>unix</b>, <b>fifo</b>, or <b>pass</b>).
- If <i>service-name</i> or <i>service-name/service-type</i> is specified, only
- the matching <a href="master.5.html">master.cf</a> entries will be output. For example,
- "<b>postconf -Mf smtp</b>" will output all services named "smtp", and
- "<b>postconf -Mf smtp/inet</b>" will output only the smtp service that
- listens on the network. Trailing service type fields that are
+ If <i>service-name</i> or <i>service-name/service-type</i> is specified, only
+ the matching <a href="master.5.html">master.cf</a> entries will be output. For example,
+ "<b>postconf -Mf smtp</b>" will output all services named "smtp", and
+ "<b>postconf -Mf smtp/inet</b>" will output only the smtp service that
+ listens on the network. Trailing service type fields that are
omitted will be handled as "*" wildcard fields.
This feature is available with Postfix 2.9 and later. The syntax
- was changed from "<i>name.type</i>" to "<i>name/type</i>", and "*" wildcard
+ was changed from "<i>name.type</i>" to "<i>name/type</i>", and "*" wildcard
support was added with Postfix 2.11.
<b>-n</b> Show only configuration parameters that have explicit <i>name=value</i>
- settings in <a href="postconf.5.html"><b>main.cf</b></a>. Specify <b>-nf</b> to fold long lines for human
+ settings in <a href="postconf.5.html"><b>main.cf</b></a>. Specify <b>-nf</b> to fold long lines for human
readability (Postfix 2.9 and later).
<b>-o</b> <i>name=value</i>
This feature is available with Postfix 2.11 and later.
- <b>-P</b> Show <a href="master.5.html"><b>master.cf</b></a> service parameter settings (by default all ser-
- vices and all parameters). formatted as one "<i>ser-</i>
- <i>vice/type/parameter=value</i>" per line. Specify <b>-Pf</b> to fold long
+ <b>-P</b> Show <a href="master.5.html"><b>master.cf</b></a> service parameter settings (by default all ser‐
+ vices and all parameters). formatted as one "<i>ser‐</i>
+ <i>vice/type/parameter=value</i>" per line. Specify <b>-Pf</b> to fold long
lines.
- Specify one or more "<i>service/type/parameter</i>" instances on the
- <a href="postconf.1.html"><b>postconf</b>(1)</a> command line to limit the output to parameters of
- interest. Trailing parameter name or service type fields that
+ Specify one or more "<i>service/type/parameter</i>" instances on the
+ <a href="postconf.1.html"><b>postconf</b>(1)</a> command line to limit the output to parameters 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>-t</b> [<i>template</i><b>_</b><i>file</i>]
- Display the templates for text that appears at the beginning of
- delivery status notification (DSN) messages, without expanding
+ Display the templates for text that appears at the beginning of
+ delivery status notification (DSN) messages, without expanding
$<b>name</b> expressions.
To override the built-in templates, specify a template file name
- at the end of the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line, or specify a file
+ at the end of the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line, or specify a file
name in <a href="postconf.5.html"><b>main.cf</b></a> with the <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a></b> parameter.
- To force selection of the built-in templates, specify an empty
- template file name on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line (in shell
+ To force selection of the built-in templates, specify an empty
+ template file name on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line (in shell
language: "").
This feature is available with Postfix 2.3 and later.
- <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>-x</b> Expand <i>$name</i> in <a href="postconf.5.html"><b>main.cf</b></a> or <a href="master.5.html"><b>master.cf</b></a> parameter values. The
+ <b>-x</b> Expand <i>$name</i> in <a href="postconf.5.html"><b>main.cf</b></a> or <a href="master.5.html"><b>master.cf</b></a> parameter values. The
expansion is recursive.
This feature is available with Postfix 2.10 and later.
- <b>-X</b> Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and remove the parameters
- named on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line. Specify a list of param-
+ <b>-X</b> Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and remove the parameters
+ named on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line. Specify a list of param‐
eter names, not "<i>name=value</i>" pairs.
- With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and remove one
- or more service entries as specified with "<i>service/type</i>" on the
+ With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and remove one
+ or more service entries as specified with "<i>service/type</i>" on the
<a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
- With <b>-P</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and remove one
+ With <b>-P</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and remove one
or more service parameter settings (-o parameter=value settings)
- as specied with "<i>service/type/parameter</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a> com-
+ as specied with "<i>service/type/parameter</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a> com‐
mand line.
In all cases the file is copied to a temporary file then renamed
into place. Specify quotes to protect special characters on the
<a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
- There is no <a href="postconf.1.html"><b>postconf</b>(1)</a> command to perform the reverse opera-
+ There is no <a href="postconf.1.html"><b>postconf</b>(1)</a> command to perform the reverse opera‐
tion.
- This feature is available with Postfix 2.10 and later. Support
+ This feature is available with Postfix 2.10 and later. Support
for -M and -P was added with Postfix 2.11.
- <b>-#</b> Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and comment out the parame-
- ters named on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line, so that those param-
- eters revert to their default values. Specify a list of parame-
+ <b>-#</b> Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and comment out the parame‐
+ ters named on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line, so that those param‐
+ eters revert to their default values. Specify a list of parame‐
ter names, not "<i>name=value</i>" pairs.
- With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and comment out
- one or more service entries as specified with "<i>service/type</i>" on
+ With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and comment out
+ one or more service entries as specified with "<i>service/type</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 on the
<a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
- There is no <a href="postconf.1.html"><b>postconf</b>(1)</a> command to perform the reverse opera-
+ There is no <a href="postconf.1.html"><b>postconf</b>(1)</a> command to perform the reverse opera‐
tion.
- This feature is available with Postfix 2.6 and later. Support
+ This feature is available with Postfix 2.6 and later. Support
for -M was added with Postfix 2.11.
<b>DIAGNOSTICS</b>
Directory with Postfix configuration files.
<b>CONFIGURATION PARAMETERS</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#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#bounce_template_file">bounce_template_file</a> (empty)</b>
- Pathname of a configuration file with bounce message templates.
+ Pathname of a configuration file with bounce message templates.
<b>FILES</b>
/etc/postfix/<a href="postconf.5.html">main.cf</a>, Postfix configuration parameters
<ul>
-<li> <p> The expressions "$name", "${name}" or "$(name)" are
-recursively replaced by the value of the named parameter. </p>
+<li> <p> The expressions "$name" and "${name}" are recursively
+replaced with the value of the named parameter, except where noted.
+An undefined parameter value is replaced with the empty value. </p>
-<li> <p> The expression "${name?value}" expands to "value" when
-"$name" is non-empty. This form is supported with Postfix version
-2.2 and later. </p>
+<li> <p> The expressions "${name?value}" and "${name?{value}}" are
+replaced with "value" when "$name" is non-empty. These forms are
+supported with Postfix versions ≥ 2.2 and ≥ 2.12, respectively.
+</p>
-<li> <p> The expression "${name:value}" expands to "value" when
-"$name" is empty. This form is supported with Postfix version 2.2
-and later. </p>
+<li> <p> The expressions "${name:value}" and "${name?{value}}" are
+replaced with "value" when "$name" is empty. These forms are supported
+with Postfix versions ≥ 2.2 and ≥ 2.12, respectively. </p>
+
+<li> <p> The expression "${name?{value1}:{value2}}" is replaced
+with "value1" when "$name" is non-empty, and with "value2" when
+"$name" is empty. The "{}" is required for "value1", optional for
+"value2". This form is supported with Postfix versions ≥ 2.12.
+</p>
+
+<li> <p> Instead of a parameter name, the first item inside "${...}"
+may be a logical expression of the form: "{value3} == {value4}"
+(equality) or "{value3} != {value4}" (inequality). This form is
+supported with Postfix versions ≥ 2.12. </p>
+
+<li> <p> Each "value" is subject to recursive named parameter and
+logical expression evaluation, except where noted. </p>
+
+<li> <p> Whitespace before or after each "{value}" is ignored. </p>
<li> <p> Specify "$$" to produce a single "$" character. </p>
+<li> <p> The legacy form "$(...)" is equivalent to the preferred
+form "${...}". </p>
+
</ul>
<li> <p> When the same parameter is defined multiple times, only
<b>DESCRIPTION</b>
The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server provides read-only or read-write table lookup
- service to Postfix processes. These services are implemented with dis-
+ service to Postfix processes. These services are implemented with dis‐
tinct service names: <b>proxymap</b> and <b>proxywrite</b>, respectively. The purpose
of these services is:
- <b>o</b> To overcome chroot restrictions. For example, a chrooted SMTP
+ · To overcome chroot restrictions. For example, a chrooted SMTP
server needs access to the system passwd file in order to reject
mail for non-existent local addresses, but it is not practical
to maintain a copy of the passwd file in the chroot jail. The
<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
<a href="proxymap.8.html">proxy</a>:unix:passwd.byname $<a href="postconf.5.html#alias_maps">alias_maps</a>
- <b>o</b> To consolidate the number of open lookup tables by sharing one
+ · To consolidate the number of open lookup tables by sharing one
open table among multiple processes. For example, making mysql
connections from every Postfix daemon process results in "too
many connections" errors. The solution:
The total number of connections is limited by the number of
proxymap server processes.
- <b>o</b> To provide single-updater functionality for lookup tables that
+ · To provide single-updater functionality for lookup tables that
do not reliably support multiple writers (i.e. all file-based
tables).
<b>open</b> <i>maptype:mapname flags</i>
Open the table with type <i>maptype</i> and name <i>mapname</i>, as controlled
by <i>flags</i>. The reply includes the <i>maptype</i> dependent flags (to
- distinguish a fixed string table from a regular expression ta-
+ distinguish a fixed string table from a regular expression ta‐
ble).
<b>lookup</b> <i>maptype:mapname flags key</i>
The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server opens only tables that are approved via the
<b><a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a></b> or <b><a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a></b> configuration parameters, does not
talk to users, and can run at fixed low privilege, chrooted or not.
- However, running the proxymap server chrooted severely limits usabil-
+ However, running the proxymap server chrooted severely limits usabil‐
ity, because it can open only chrooted tables.
The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server is not a trusted daemon process, and must not be
the table directly. This allows the same <a href="postconf.5.html">main.cf</a> setting to be used by
sensitive and non-sensitive processes.
- Postfix-writable data files should be stored under a dedicated direc-
+ Postfix-writable data files should be stored under a dedicated direc‐
tory that is writable only by the Postfix mail system, such as the
Postfix-owned <b><a href="postconf.5.html#data_directory">data_directory</a></b>.
The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> read-write service does not explicitly close lookup
tables (even if it did, this could not be relied on, because the
- process may be terminated between table updates). The read-write ser-
+ process may be terminated between table updates). The read-write ser‐
vice should therefore not be used with tables that leave persistent
storage in an inconsistent state between updates (for example, CDB).
Tables that support "sync on update" should be safe (for example,
more details including examples.
<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#data_directory">data_directory</a> (see 'postconf -d' output)</b>
<b>DESCRIPTION</b>
<b>qmqp-sink</b> listens on the named host (or address) and port. It receives
- messages from the network and throws them away. The purpose is to mea-
+ messages from the network and throws them away. The purpose is to mea‐
sure QMQP client performance, not protocol compliance. Connections can
be accepted on IPv4 or IPv6 endpoints, or on UNIX-domain sockets. IPv4
and IPv6 are the default. This program is the complement of the <a href="qmqp-source.1.html"><b>qmqp-</b></a>
<a href="qmqp-source.1.html"><b>source</b>(1)</a> program.
- Note: this is an unsupported test program. No attempt is made to main-
+ Note: this is an unsupported test program. No attempt is made to main‐
tain compatibility between successive versions.
Arguments:
<b>-c</b> Display a running counter that is updated whenever a delivery is
completed.
- <b>-v</b> Increase verbosity. Specify <b>-v -v</b> to see some of the QMQP con-
+ <b>-v</b> Increase verbosity. Specify <b>-v -v</b> to see some of the QMQP con‐
versation.
<b>-x</b> <i>time</i>
SMTP messages from the network and throws them away. The purpose is to
measure client performance, not protocol compliance.
- <b>smtp-sink</b> may also be configured to capture each mail delivery transac-
+ <b>smtp-sink</b> may also be configured to capture each mail delivery transac‐
tion to file. Since disk latencies are large compared to network
delays, this mode of operation can reduce the maximal performance by
several orders of magnitude.
domain sockets. IPv4 and IPv6 are the default. This program is the
complement of the <a href="smtp-source.1.html"><b>smtp-source</b>(1)</a> program.
- Note: this is an unsupported test program. No attempt is made to main-
+ Note: this is an unsupported test program. No attempt is made to main‐
tain compatibility between successive versions.
Arguments:
<b>-a</b> Do not announce SASL authentication support.
<b>-A</b> <i>delay</i>
- Wait <i>delay</i> seconds after responding to DATA, then abort prema-
+ Wait <i>delay</i> seconds after responding to DATA, then abort prema‐
turely with a 550 reply status. Do not read further input from
the client; this is an attempt to block the client before it
sends ".". Specify a zero delay value to abort immediately.
Use <i>hard-bounce-reply</i> for hard reject responses. The default
reply is "500 5.3.0 Error: command failed".
- <b>-c</b> Display running counters that are updated whenever an SMTP ses-
+ <b>-c</b> Display running counters that are updated whenever an SMTP ses‐
sion ends, a QUIT command is executed, or when "." is received.
<b>-C</b> Disable XCLIENT support.
is created by expanding the <i>dump-template</i> via strftime(3) and
appending a pseudo-random hexadecimal number (example:
"%Y%m%d%H/%M." expands into "2006081203/05.809a62e3"). If the
- template contains "/" characters, missing directories are cre-
+ template contains "/" characters, missing directories are cre‐
ated automatically. The message dump format is described below.
Note: this option keeps one capture file open for every mail
<b>-m</b> <i>count</i> (default: 256)
An upper bound on the maximal number of simultaneous connections
- that <b>smtp-sink</b> will handle. This prevents the process from run-
+ that <b>smtp-sink</b> will handle. This prevents the process from run‐
ning out of file descriptors. Excess connections will stay
queued in the TCP/IP stack.
CISCO PIX system. Implies <b>-e</b>.
<b>-q</b> <i>command,command,...</i>
- Disconnect (without replying) after receiving one of the speci-
+ Disconnect (without replying) after receiving one of the speci‐
fied commands.
Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT,
from the shell. Command names are case-insensitive.
<b>-Q</b> <i>command,command,...</i>
- Send a 421 reply and disconnect after receiving one of the spec-
+ Send a 421 reply and disconnect after receiving one of the spec‐
ified commands.
Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT,
An optional string that is prepended to each message that is
written to a dump file (see the dump file format description
below). The following C escape sequences are supported: \a
- (bell), \b (backslace), \f (formfeed), \n (newline), \r (car-
+ (bell), \b (backslace), \f (formfeed), \n (newline), \r (car‐
riage return), \t (horizontal tab), \v (vertical tab), \<i>ddd</i> (up
to three octal digits) and \\ (the backslash character).
window scaling implementations, specify a value > 0 and < 65536.
<b>-u</b> <i>username</i>
- Switch to the specified user privileges after opening the net-
+ Switch to the specified user privileges after opening the net‐
work socket and optionally changing the process root directory.
This option is required when the process runs with super-user
privileges. See also the <b>-R</b> option.
Each dumped message contains a sequence of text lines, terminated with
the newline character. The sequence of information is as follows:
- <b>o</b> The optional string specified with the <b>-S</b> option.
+ · The optional string specified with the <b>-S</b> option.
- <b>o</b> The <b>smtp-sink</b> generated headers as documented below.
+ · The <b>smtp-sink</b> generated headers as documented below.
- <b>o</b> The message header and body as received from the SMTP client.
+ · The message header and body as received from the SMTP client.
- <b>o</b> An empty line.
+ · An empty line.
The format of the <b>smtp-sink</b> generated headers is as follows:
<b>X-Helo-Args:</b> <i>text</i>
The arguments of the last HELO or EHLO command before this mail
delivery transaction. This record is present only if the client
- sent a recognizable HELO or EHLO command before the DATA com-
+ sent a recognizable HELO or EHLO command before the DATA com‐
mand.
<b>X-Mail-Args:</b> <i>text</i>
- The arguments of the MAIL command that started this mail deliv-
+ The arguments of the MAIL command that started this mail deliv‐
ery transaction. This record is present exactly once.
<b>X-Rcpt-Args:</b> <i>text</i>
are in the order as sent by the client.
<b>Received:</b> <i>text</i>
- A message header for compatibility with mail processing soft-
- ware. This three-line header marks the end of the headers pro-
+ A message header for compatibility with mail processing soft‐
+ ware. This three-line header marks the end of the headers pro‐
vided by <b>smtp-sink</b>, and is formatted as follows:
<b>from</b> <i>helo</i> <b>([</b><i>addr</i><b>])</b>
<b>by</b> <i>host</i> <b>(smtp-sink) with</b> <i>proto</i> <b>id</b> <i>random</i><b>;</b>
The hostname specified with the <b>-h</b> option, the client
- protocol (see <b>X-Client-Proto</b> above), and the pseudo-ran-
+ protocol (see <b>X-Client-Proto</b> above), and the pseudo-ran‐
dom portion of the per-message capture file name.
<i>time-stamp</i>
PostgreSQL database client. This is described in
\fBpgsql_table\fR(5).
.IP "\fBpipemap\fR (read-only)"
-A pipeline of lookup tables. Example:
-"\fBpipemap:\fI!type_1:name_1! ... !type_n:name_n\fR".
+A lookup table that constructs a pipeline of tables. Example:
+"\fBpipemap:{\fItype_1:name_1, ..., type_n:name_n\fB}\fR".
Each "pipemap:" query is given to the first table. Each
lookup result becomes the query for the next table in the
pipeline, and the last table produces the final result.
When any table lookup produces no result, the pipeline
-produces no result. The first ASCII character after "pipemap:"
-will be used as the separator between the lookup tables
-that follow (do not use space, ",", ":" or non-ASCII).
+produces no result. The first and last characters of the
+"pipemap:" table name must be "\fB{\fR" and "\fB}\fR".
+Within these, individual maps are separated with comma or
+whitespace.
.IP "\fBproxy\fR"
Postfix \fBproxymap\fR(8) client for shared access to Postfix
databases. The table name syntax is \fItype\fB:\fIname\fR.
.IP "\fBrandmap\fR (read-only)"
An in-memory table that performs random selection. Example:
-"\fBrandmap:\fI!result_1! ... !result_n\fR". Each table query
+"\fBrandmap:{\fIresult_1, ..., result_n\fB}\fR". Each table query
returns a random choice from the specified results. The first
-ASCII character after "randmap:" will be used as the separator
-between the results that follow (do not use space, ",", ":"
-or non-ASCII).
+and last characters of the "randmap:" table name must be
+"\fB{\fR" and "\fB}\fR". Within these, individual maps are
+separated with comma or whitespace.
.IP "\fBregexp\fR (read-only)"
A lookup table based on regular expressions. The file format
is described in \fBregexp_table\fR(5).
A parameter value may refer to other parameters.
.RS
.IP \(bu
-The expressions "$name", "${name}" or "$(name)" are
-recursively replaced by the value of the named parameter.
+The expressions "$name" and "${name}" are recursively replaced with
+the value of the named parameter. An undefined parameter value is
+replaced with the empty value.
.IP \(bu
-The expression "${name?value}" expands to "value" when
-"$name" is non-empty. This form is supported with Postfix
-version 2.2 and later.
+The expressions "${name?value}" and "${name?{value}}" are replaced
+with "value" when "$name" is non-empty. These forms are supported
+with Postfix versions >= 2.2 and >= 2.12, respectively.
.IP \(bu
-The expression "${name:value}" expands to "value" when
-"$name" is empty. This form is supported with Postfix
-version 2.2 and later.
+The expressions "${name:value}" and "${name:{value}}" are replaced
+with "value" when "$name" is empty. These forms are supported with
+Postfix versions >= 2.2 and >= 2.12, respectively.
+.IP \(bu
+The expression "${name?{value1}:{value2}}" is replaced with "value1"
+when "$name" is non-empty, and with "value2" when "$name" is empty.
+The "{}" is required for "value1", optional for "value2". This form
+is supported with Postfix versions >= 2.12.
+.IP \(bu
+Instead of a parameter name, the first item inside "${...}" may be
+a logical expression of the form: "{value3} == {value4}" (equality)
+or "{value3} != {value4}" (inequality). This form is supported
+with Postfix versions >= 2.12.
+.IP \(bu
+Each "value" is subject to recursive named parameter and logical
+expression evaluation, except where noted.
+.IP \(bu
+Whitespace before or after each "{value}" is ignored.
.IP \(bu
Specify "$$" to produce a single "$" character.
+.IP \(bu
+The legacy form "$(...)" is equivalent to the preferred form "${...}".
.RE
.IP \(bu
When the same parameter is defined multiple times, only the last
<dt> <b>pipemap</b> (read-only) </dt>
<dd> A pipeline of lookup tables. Example:
-"<b>pipemap:</b><i>!type<sub>1</sub>:name<sub>1</sub>! ...
-!type<sub>n</sub>:name<sub>n</sub></i>". Each "pipemap:" query is
+"pipemap:{<i>type<sub>1</sub>:name<sub>1</sub>, ...,
+type<sub>n</sub>:name<sub>n</sub></i>}". Each "pipemap:" query is
given to the first table. Each lookup result becomes the query for
the next table in the pipeline, and the last table produces the
final result. When any table lookup produces no result, the pipeline
-produces no result. The first ASCII character after "pipemap:"
-will be used as the separator between the lookup tables that follow
-(do not use space, ",", ":" or non-ASCII). </dd>
+produces no result. The first and last characters of the "pipemap:"
+table name must be "{" and "}". Within these, individual maps are
+separated with comma or whitespace. </dd>
<dt> <b>pgsql</b> (read-only) </dt>
<dt> <b>randmap</b> (read-only) </dt>
<dd> An in-memory table that performs random selection. Example:
-"<b>randmap:</b><i>!result<sub>1</sub>! ... !result<sub>n</sub></i>".
+"randmap:{<i>result<sub>1</sub>. ..., result<sub>n</sub></i>}".
Each table query returns a random choice from the specified results.
-The first ASCII character after "randmap:" will be used as the
-separator between the results that follow (do not use space, ",",
-":" or non-ASCII). </dd>
+The first and last characters of the "randmap:" table name must be
+"{" and "}". Within these, individual maps are separated with comma
+or whitespace. </dd>
<dt> <b>regexp</b> (read-only) </dt>
<ul>
-<li> <p> The expressions "$name", "${name}" or "$(name)" are
-recursively replaced by the value of the named parameter. </p>
+<li> <p> The expressions "$name" and "${name}" are recursively
+replaced with the value of the named parameter, except where noted.
+An undefined parameter value is replaced with the empty value. </p>
-<li> <p> The expression "${name?value}" expands to "value" when
-"$name" is non-empty. This form is supported with Postfix version
-2.2 and later. </p>
+<li> <p> The expressions "${name?value}" and "${name?{value}}" are
+replaced with "value" when "$name" is non-empty. These forms are
+supported with Postfix versions ≥ 2.2 and ≥ 2.12, respectively.
+</p>
-<li> <p> The expression "${name:value}" expands to "value" when
-"$name" is empty. This form is supported with Postfix version 2.2
-and later. </p>
+<li> <p> The expressions "${name:value}" and "${name?{value}}" are
+replaced with "value" when "$name" is empty. These forms are supported
+with Postfix versions ≥ 2.2 and ≥ 2.12, respectively. </p>
+
+<li> <p> The expression "${name?{value1}:{value2}}" is replaced
+with "value1" when "$name" is non-empty, and with "value2" when
+"$name" is empty. The "{}" is required for "value1", optional for
+"value2". This form is supported with Postfix versions ≥ 2.12.
+</p>
+
+<li> <p> Instead of a parameter name, the first item inside "${...}"
+may be a logical expression of the form: "{value3} == {value4}"
+(equality) or "{value3} != {value4}" (inequality). This form is
+supported with Postfix versions ≥ 2.12. </p>
+
+<li> <p> Each "value" is subject to recursive named parameter and
+logical expression evaluation, except where noted. </p>
+
+<li> <p> Whitespace before or after each "{value}" is ignored. </p>
<li> <p> Specify "$$" to produce a single "$" character. </p>
+<li> <p> The legacy form "$(...)" is equivalent to the preferred
+form "${...}". </p>
+
</ul>
<li> <p> When the same parameter is defined multiple times, only
A parameter value may refer to other parameters.
.RS
.IP \(bu
-The expressions "$name", "${name}" or "$(name)" are
-recursively replaced by the value of the named parameter.
+The expressions "$name" and "${name}" are recursively replaced with
+the value of the named parameter. An undefined parameter value is
+replaced with the empty value.
.IP \(bu
-The expression "${name?value}" expands to "value" when
-"$name" is non-empty. This form is supported with Postfix
-version 2.2 and later.
+The expressions "${name?value}" and "${name?{value}}" are replaced
+with "value" when "$name" is non-empty. These forms are supported
+with Postfix versions >= 2.2 and >= 2.12, respectively.
.IP \(bu
-The expression "${name:value}" expands to "value" when
-"$name" is empty. This form is supported with Postfix
-version 2.2 and later.
+The expressions "${name:value}" and "${name:{value}}" are replaced
+with "value" when "$name" is empty. These forms are supported with
+Postfix versions >= 2.2 and >= 2.12, respectively.
+.IP \(bu
+The expression "${name?{value1}:{value2}}" is replaced with "value1"
+when "$name" is non-empty, and with "value2" when "$name" is empty.
+The "{}" is required for "value1", optional for "value2". This form
+is supported with Postfix versions >= 2.12.
+.IP \(bu
+Instead of a parameter name, the first item inside "${...}" may be
+a logical expression of the form: "{value3} == {value4}" (equality)
+or "{value3} != {value4}" (inequality). This form is supported
+with Postfix versions >= 2.12.
+.IP \(bu
+Each "value" is subject to recursive named parameter and logical
+expression evaluation, except where noted.
+.IP \(bu
+Whitespace before or after each "{value}" is ignored.
.IP \(bu
Specify "$$" to produce a single "$" character.
+.IP \(bu
+The legacy form "$(...)" is equivalent to the preferred form "${...}".
.RE
.IP \(bu
When the same parameter is defined multiple times, only the last
#define bounce_template_encoding(t) ((t)->mime_encoding)
#define bounce_template_charset(t) ((t)->mime_charset)
-typedef int (*BOUNCE_XP_PRN_FN) (VSTREAM *, const char *, ...);
+typedef int PRINTFLIKE(2, 3) (*BOUNCE_XP_PRN_FN) (VSTREAM *, const char *,...);
typedef int (*BOUNCE_XP_PUT_FN) (VSTREAM *, const char *);
extern BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *);
if (hdr_opts->type == HDR_RESENT_MESSAGE_ID)
msg_info("%s: resent-message-id=%s", state->queue_id, hdrval);
if (hdr_opts->type == HDR_RECEIVED)
- if (++state->hop_count >= var_hopcount_limit)
+ if (++state->hop_count >= var_hopcount_limit) {
+ msg_warn("%s: message rejected: hopcount exceeded",
+ state->queue_id);
state->errs |= CLEANUP_STAT_HOPS;
+ }
if (CLEANUP_OUT_OK(state)) {
if (hdr_opts->flags & HDR_OPT_RR)
state->resent = "Resent-";
vstream_fflush(VSTREAM_OUT);
continue;
}
- target = mystrtok(&bufp, " \t");
+ target = mystrtokq(&bufp, " \t");
junk = mystrtok(&bufp, " \t");
if (strcmp(cmd, "file") == 0 && target && !junk) {
data_redirect_file(result, target);
{
VSTREAM *fp;
int count;
- int data_len = strlen(value);
+ size_t data_len = strlen(value);
/*
* Return a permanent error if we can't store this data. This results in
if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) {
break;
} else if (memcache_printf(fp, "set %s %d %d %ld",
- STR(dict_mc->key_buf), dict_mc->mc_flags, ttl, data_len) < 0
+ STR(dict_mc->key_buf), dict_mc->mc_flags,
+ ttl, (long) data_len) < 0
|| memcache_fwrite(fp, value, strlen(value)) < 0
|| memcache_get(fp, dict_mc->clnt_buf,
dict_mc->max_line) < 0) {
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20140907"
+#define MAIL_RELEASE_DATE "20140921"
#define MAIL_VERSION_NUMBER "2.12"
#ifdef SNAPSHOT
char *temp;
char *bufp;
static char sep[] = " \t,\r\n";
+ static char parens[] = "{}";
MAPS *maps;
char *map_type_name;
VSTRING *map_type_name_flags;
#define OPEN_FLAGS O_RDONLY
- while ((map_type_name = mystrtok(&bufp, sep)) != 0) {
+ while ((map_type_name = mystrtokq(&bufp, sep, parens)) != 0) {
vstring_sprintf(map_type_name_flags, "%s(%o,%s)",
map_type_name, OPEN_FLAGS,
dict_flags_str(dict_flags));
*/
extern int memcache_get(VSTREAM *, VSTRING *, ssize_t);
extern int memcache_vprintf(VSTREAM *, const char *, va_list);
-extern int memcache_printf(VSTREAM *, const char *fmt,...);
+extern int PRINTFLIKE(2, 3) memcache_printf(VSTREAM *, const char *fmt,...);
extern int memcache_fread(VSTREAM *, VSTRING *, ssize_t);
extern int memcache_fwrite(VSTREAM *, const char *, ssize_t);
* chroot jail, while access lists are evaluated after entering the
* chroot jail.
*/
- while ((acl = mystrtok(&bp, SERVER_ACL_SEPARATORS)) != 0) {
+ while ((acl = mystrtokq(&bp, SERVER_ACL_SEPARATORS, "{}")) != 0) {
if (strchr(acl, ':') != 0) {
if (strchr(origin, ':') != 0) {
msg_warn("table %s: lookup result \"%s\" is not allowed"
test22 test23 test24 test25 test26 test27 test28 test29 test30 test4b \
test31 test32 test33 test34 test35 test36 test37 test39 test40 test41 \
test42 test43 test44 test45 test46 test47 test48 test49 test50 test51 \
- test52 test53 test54 test55 test56
+ test52 test53 test54 test55 test56 test57 test58
root_tests:
touch main.cf master.cf
echo smtpd_restriction_classes = foo bar >> main.cf
echo foo = yes >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test1.tmp 2>&1
diff test1.ref test1.tmp
rm -f main.cf master.cf test1.tmp
touch main.cf master.cf
echo restriction_classes = foo bar >> main.cf
echo foo = yes >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test2.tmp 2>&1
diff test2.ref test2.tmp
rm -f main.cf master.cf test2.tmp
echo foo = yes >> main.cf
echo 'bar = $$foo' >> main.cf
echo 'always_bcc = $$bar' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test3.tmp 2>&1
diff test3.ref test3.tmp
rm -f main.cf master.cf test3.tmp
echo 'bar = $$foo' >> main.cf
echo smtpd unix - n n - 0 smtpd >> master.cf
echo ' -o always_bcc=$$bar' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test4.tmp 2>&1
diff test4.ref test4.tmp
rm -f main.cf master.cf test4.tmp
echo smtpd1 unix - n n - 0 smtpd >> master.cf
echo ' -o foo=xxx -o bar=yyy -o baz=zzz' >> master.cf
echo '#smtpd2 unix - n n - 0 smtpd' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test4b.tmp 2>&1
diff test4b.ref test4b.tmp
rm -f main.cf master.cf test4b.tmp
touch main.cf master.cf
echo smtpd unix - n n - 0 smtpd >> master.cf
echo ' -o bar=yes -o always_bcc=$$bar -o' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test5.tmp 2>&1
diff test5.ref test5.tmp
rm -f main.cf master.cf test5.tmp
rm -f main.cf master.cf
touch main.cf master.cf
echo whatevershebrings unix - n n - 0 pipe >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . 2>&1 | grep whatevershebrings >test6.tmp
diff test6.ref test6.tmp
rm -f main.cf master.cf test6.tmp
rm -f main.cf master.cf
touch main.cf master.cf
echo whatevershebrings unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . 2>&1 | grep whatevershebrings >test7.tmp
diff test7.ref test7.tmp
rm -f main.cf master.cf test7.tmp
touch main.cf master.cf
echo whatevershebrings inet - n n - 0 spawn >> master.cf
echo whatevershebrings_time_limit=1 >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . 2>&1 | grep whatevershebrings >test8.tmp
diff test8.ref test8.tmp
rm -f main.cf master.cf test8.tmp
touch main.cf master.cf
echo foo inet - n n - 0 spawn >> master.cf
echo bar unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -M '*'/inet >test9.tmp 2>&1
diff test9.ref test9.tmp
rm -f main.cf master.cf test9.tmp
touch main.cf master.cf
echo foo inet - n n - 0 spawn >> master.cf
echo bar unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -M bar/inet foo/unix >test10.tmp 2>&1
diff test10.ref test10.tmp
rm -f main.cf master.cf test10.tmp
touch main.cf master.cf
echo foo inet - n n - 0 spawn >> master.cf
echo bar unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -M >test11.tmp 2>&1
diff test11.ref test11.tmp
rm -f main.cf master.cf test11.tmp
echo ' -o always_bcc=$$bar -o' >> master.cf
echo foo inet - n n - 0 spawn >> master.cf
echo ' -o always_bcc=$$bar -o' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -M >test12.tmp 2>&1
diff test12.ref test12.tmp
rm -f main.cf master.cf test12.tmp
echo baz=xx >> main.cf
echo foo inet - n n - 0 spawn >> master.cf
echo ' -o smtpd_restriction_classes=bar' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test13.tmp 2>&1
diff test13.ref test13.tmp
rm -f main.cf master.cf test13.tmp
echo smtpd_restriction_classes=bar >> main.cf
echo foo inet - n n - 0 spawn >> master.cf
echo ' -o bar=yes -o baz=xx' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test14.tmp 2>&1
diff test14.ref test14.tmp
rm -f main.cf master.cf test14.tmp
echo baz=yy >> main.cf
echo foo inet - n n - 0 spawn >> master.cf
echo ' -o bar=yes -o always_bcc=$$bar$$baz' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test15.tmp 2>&1
diff test15.ref test15.tmp
rm -f main.cf master.cf test15.tmp
test16: $(PROG) test16.ref
rm -f main.cf master.cf
- touch main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test16.tmp 2>&1
diff test16.ref test16.tmp
rm -f main.cf master.cf test16.tmp
test17: $(PROG) test17.ref
rm -f main.cf master.cf
- touch main.cf
+ touch -t 197101010000 main.cf
-./$(PROG) -Mc . >test17.tmp 2>&1; exit 0
diff test17.ref test17.tmp
rm -f main.cf master.cf test17.tmp
touch main.cf master.cf
echo virtual_maps=xxx >> main.cf
echo smtpd_client_connection_limit_exceptions=yyy >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test18.tmp 2>&1
diff test18.ref test18.tmp
rm -f main.cf master.cf test18.tmp
touch main.cf master.cf
echo forward_path='$$'aaaa >> main.cf
echo default_rbl_reply='$$'bbbb >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test19.tmp 2>&1
diff test19.ref test19.tmp
rm -f main.cf master.cf test19.tmp
touch main.cf master.cf
echo foo inet - n n - 0 spawn >> master.cf
echo ' -o always_bcc=$$bar$$baz' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc . >test20.tmp 2>&1
diff test20.ref test20.tmp
rm -f main.cf master.cf test20.tmp
touch main.cf master.cf
echo forward_path = xxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxx \
xxxxxxxxxxxxx xxxxxxxxxxxxxx >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nfc . >test21.tmp 2>&1
diff test21.ref test21.tmp
rm -f main.cf master.cf test21.tmp
rm -f main.cf master.cf
touch main.cf master.cf
echo whatevershebrings unix - n n - 0 smtp >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . 2>&1 | grep whatevershebrings >test22.tmp
diff test22.ref test22.tmp
rm -f main.cf master.cf test22.tmp
echo name = value >> main.cf
echo whatevershebrings unix - n n - 0 smtp >> master.cf
echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -nC builtin >test23.tmp 2>&1
diff test23.ref test23.tmp
rm -f main.cf master.cf test23.tmp
echo name = value >> main.cf
echo whatevershebrings unix - n n - 0 smtp >> master.cf
echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -nC user >test24.tmp 2>&1
diff test24.ref test24.tmp
rm -f main.cf master.cf test24.tmp
echo name = value >> main.cf
echo whatevershebrings unix - n n - 0 smtp >> master.cf
echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -C service 2>&1 | grep whatevershebrings >test25.tmp
diff test25.ref test25.tmp
rm -f main.cf master.cf test25.tmp
echo name = value >> main.cf
echo whatevershebrings unix - n n - 0 smtp >> master.cf
echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . -C all >test26.tmp 2>&1
diff test26.ref test26.tmp
rm -f main.cf master.cf test26.tmp
echo name = value >> main.cf
echo whatevershebrings unix - n n - 0 smtp >> master.cf
echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -c . -C all 2>&1 | grep whatevershebrings >test27.tmp
diff test27.ref test27.tmp
rm -f main.cf master.cf test27.tmp
echo ' -o body_checks=$$db:zz' >> master.cf
echo 'zz_domain = whatever' >> main.cf
echo 'aa_domain = whatever' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test28.tmp 2>&1
diff test28.ref test28.tmp
rm -f main.cf master.cf test28.tmp
echo 'memcachexx = proxy:memcache:memcachefoo' >> main.cf
echo 'memcachefoo_domain = bar' >> main.cf
echo 'memcachefoo_domainx = bar' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test29.tmp 2>&1
diff test29.ref test29.tmp
rm -f main.cf master.cf test29.tmp
echo ' -o bodyx_checks=$$p2' >> master.cf
echo ' -oheader_checks=$$p3' >> master.cf
echo ' -oheaderx_checks=$$p4' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nc . >test30.tmp 2>&1
diff test30.ref test30.tmp
rm -f main.cf master.cf test30.tmp
touch main.cf master.cf
echo 'smtpd_helo_restrictions=whatever' >> main.cf
echo 'smtpd_sender_restrictions=$$smtpd_helo_restrictions' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nxc . >test31.tmp 2>&1
diff test31.ref test31.tmp
rm -f main.cf master.cf test31.tmp
rm -f main.cf master.cf
touch main.cf master.cf
echo 'relay_domains=whatever' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -xc . fast_flush_domains >test32.tmp 2>&1
diff test32.ref test32.tmp
rm -f main.cf master.cf test32.tmp
touch main.cf master.cf
echo 'mydestination=whatever' >> main.cf
echo 'always_bcc=$$relay_domains' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -xc . always_bcc >test33.tmp 2>&1
diff test33.ref test33.tmp
rm -f main.cf master.cf test33.tmp
echo 'mydestination=whatever' >> main.cf
echo 'process_name=xxx' >> main.cf
echo 'process_id=yyy' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -xc . mydestination process_name >test34.tmp 2>&1
diff test34.ref test34.tmp
rm -f main.cf master.cf test34.tmp
echo ' -o body_checks=whatever' >> master.cf
echo ' -o process_name=aaa' >> master.cf
echo ' -o process_id=bbb' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -xc . process_name >test35.tmp 2>&1
diff test35.ref test35.tmp
rm -f main.cf master.cf test35.tmp
touch main.cf master.cf
echo 'mydestination=$$virtual_mapx' >> main.cf
echo 'virtual_alias_maps=$$virtual_maps' >> main.cf
+ touch -t 197101010000 main.cf
./$(PROG) -nxc . >test36.tmp 2>&1
diff test36.ref test36.tmp
rm -f main.cf master.cf test36.tmp
echo ' -o mydestination=$$xxx' >> master.cf
echo ' -o always_bcc=$$aaa' >> master.cf
echo ' -o aaa=ccc' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfxc . >test37.tmp 2>&1
diff test37.ref test37.tmp
rm -f main.cf master.cf test37.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc . '*'/unix >test39.tmp 2>&1
diff test39.ref test39.tmp
rm -f main.cf master.cf test39.tmp
echo ' -voaaa=bbb' >> master.cf
echo ' -vo ccc=$$aaa' >> master.cf
echo ' -v -oddd=$$ccc' >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfxc . '*'/unix >test40.tmp 2>&1
diff test40.ref test40.tmp
rm -f main.cf master.cf test40.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar unix - n n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Pc . bar/unix/xxx=yyy bar/unix/aaa=bbb >test41.tmp 2>&1
./$(PROG) -Mfc. >>test41.tmp 2>&1
./$(PROG) -Pc . bar/unix/xxx=YYY bar/unix/aaa=BBB >>test41.tmp 2>&1
echo foo unix - n n - 0 other >> master.cf
echo bar unix - n n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Pc . bar/unix/xxx=yyy bar/unix/aaa=bbb >test42.tmp 2>&1
./$(PROG) -Mfc. >>test42.tmp 2>&1
./$(PROG) -Pc . >>test42.tmp 2>&1
echo foo unix - n n - 0 other >> master.cf
echo bar unix - n n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Fc . bar/unix/chroot=y bar/unix/command='aa -stuffobb=cc dd' >test43.tmp 2>&1
./$(PROG) -Mfc. >>test43.tmp 2>&1
diff test43.ref test43.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar unix - n n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mc . bar/unix='xx inet - n n - 0 aa -stuffobb=cc dd' >test44.tmp 2>&1
./$(PROG) -Mfc. >>test44.tmp 2>&1
diff test44.ref test44.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar xxxx - n n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc. >test45.tmp 2>&1 || true
diff test45.ref test45.tmp
rm -f main.cf master.cf test45.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet X n n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc. >test46.tmp 2>&1 || true
diff test46.ref test46.tmp
rm -f main.cf master.cf test46.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - X n - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc. >test47.tmp 2>&1 || true
diff test47.ref test47.tmp
rm -f main.cf master.cf test47.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n X - 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc. >test48.tmp 2>&1 || true
diff test48.ref test48.tmp
rm -f main.cf master.cf test48.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n n X 0 other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc. >test49.tmp 2>&1 || true
diff test49.ref test49.tmp
rm -f main.cf master.cf test49.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n n - X other >> master.cf
echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc. >test50.tmp 2>&1 || true
diff test50.ref test50.tmp
rm -f main.cf master.cf test50.tmp
echo foo unix - n n -? 0 other >> master.cf
echo bar inet - n n X? 0 other >> master.cf
echo baz unix - n n 0? 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -Mfc. >test51.tmp 2>&1 || true
diff test51.ref test51.tmp
rm -f main.cf master.cf test51.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n n 0 0 other >> master.cf
echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -MXc. bar/inet foo/unix xxx/yyy
./$(PROG) -Mfc. >test52.tmp 2>&1 || true
diff test52.ref test52.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n n 0 0 other >> master.cf
echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -M#c. bar/inet xxx/yyy
diff test53.ref master.cf
rm -f main.cf master.cf test53.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n n 0 0 other >> master.cf
echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -M#c. bar/inet foo/unix
diff test54.ref master.cf
rm -f main.cf master.cf test54.tmp
echo foo unix - n n - 0 other >> master.cf
echo bar inet - n n 0 0 other >> master.cf
echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -M#c. bar/inet baz/unix
diff test55.ref master.cf
rm -f main.cf master.cf test55.tmp
echo " -o first" >> master.cf
echo " -o second" >> master.cf
echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
./$(PROG) -M#c. bar/inet xxx/yyy
diff test56.ref master.cf
rm -f main.cf master.cf test56.tmp
+# Many more tests in util/mac_expand.in.
+
+test57: $(PROG) test57.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'x = $${{1} == {2}?{error}:x-value}' >> main.cf
+ echo 'y = y-value' >> main.cf
+ echo 'bar = $${x?{$$y}:$$z}' >> main.cf
+ echo 'baz = $${x?{$$z}:$$y}' >> main.cf
+ echo 'foo = $$bar$$baz' >> main.cf
+ echo 't1 = Postfix 2.11 $${{$${x?bug:x}} == {bug}?in}compatible' >> main.cf
+ echo 't2 = $$t1' >> main.cf
+ touch -t 197101010000 main.cf
+ ./$(PROG) -nxc. >test57.tmp 2>&1
+ diff test57.ref test57.tmp
+ rm -f main.cf master.cf test57.tmp
+
+test58: $(PROG) test58.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'mydestination = foo bar pipemap:{ldap:xxx, memcache:yy} randmap:{xx' >> main.cf
+ echo 'xxx_domain = foo' >> main.cf
+ echo 'xxx_bogus = foo' >> main.cf
+ echo 'yy_backup = bbb' >> main.cf
+ echo 'yy_bogus = bbb' >> main.cf
+ touch -t 197101010000 main.cf
+ $(SHLIB_ENV) ./postconf -nc. >test58.tmp 2>&1 || true
+ diff test58.ref test58.tmp
+ rm -f main.cf master.cf test58.tmp
+
printfck: $(OBJS) $(PROG)
rm -rf printfck
mkdir printfck
postconf_dbms.o: ../../include/mac_expand.h
postconf_dbms.o: ../../include/mac_parse.h
postconf_dbms.o: ../../include/mail_conf.h
+postconf_dbms.o: ../../include/mail_params.h
+postconf_dbms.o: ../../include/msg.h
postconf_dbms.o: ../../include/myflock.h
postconf_dbms.o: ../../include/name_code.h
postconf_dbms.o: ../../include/split_at.h
/* PostgreSQL database client. This is described in
/* \fBpgsql_table\fR(5).
/* .IP "\fBpipemap\fR (read-only)"
-/* A pipeline of lookup tables. Example:
-/* "\fBpipemap:\fI!type_1:name_1! ... !type_n:name_n\fR".
+/* A lookup table that constructs a pipeline of tables. Example:
+/* "\fBpipemap:{\fItype_1:name_1, ..., type_n:name_n\fB}\fR".
/* Each "pipemap:" query is given to the first table. Each
/* lookup result becomes the query for the next table in the
/* pipeline, and the last table produces the final result.
/* When any table lookup produces no result, the pipeline
-/* produces no result. The first ASCII character after "pipemap:"
-/* will be used as the separator between the lookup tables
-/* that follow (do not use space, ",", ":" or non-ASCII).
+/* produces no result. The first and last characters of the
+/* "pipemap:" table name must be "\fB{\fR" and "\fB}\fR".
+/* Within these, individual maps are separated with comma or
+/* whitespace.
/* .IP "\fBproxy\fR"
/* Postfix \fBproxymap\fR(8) client for shared access to Postfix
/* databases. The table name syntax is \fItype\fB:\fIname\fR.
/* .IP "\fBrandmap\fR (read-only)"
/* An in-memory table that performs random selection. Example:
-/* "\fBrandmap:\fI!result_1! ... !result_n\fR". Each table query
+/* "\fBrandmap:{\fIresult_1, ..., result_n\fB}\fR". Each table query
/* returns a random choice from the specified results. The first
-/* ASCII character after "randmap:" will be used as the separator
-/* between the results that follow (do not use space, ",", ":"
-/* or non-ASCII).
+/* and last characters of the "randmap:" table name must be
+/* "\fB{\fR" and "\fB}\fR". Within these, individual maps are
+/* separated with comma or whitespace.
/* .IP "\fBregexp\fR (read-only)"
/* A lookup table based on regular expressions. The file format
/* is described in \fBregexp_table\fR(5).
/*
* postconf_print.c.
*/
-extern void pcf_print_line(VSTREAM *, int, const char *,...);
+extern void PRINTFLIKE(3, 4) pcf_print_line(VSTREAM *, int, const char *,...);
/*
* postconf_unused.c.
return (domain);
/*
- * Use the hostname when it is not a FQDN ("foo"), or when the hostname
- * actually is a domain name ("foo.com").
+ * Use a default domain when the hostname is not a FQDN ("foo").
*/
if (var_myhostname == 0)
pcf_get_myhostname();
- if ((dot = strchr(var_myhostname, '.')) == 0 || strchr(dot + 1, '.') == 0)
+ if ((dot = strchr(var_myhostname, '.')) == 0)
return (domain = DEF_MYDOMAIN);
return (domain = mystrdup(dot + 1));
}
#include <split_at.h>
#include <mac_expand.h>
#include <dict.h>
+#include <msg.h>
/* Global library. */
#include <mail_conf.h>
+#include <mail_params.h>
#include <dict_proxy.h>
#include <dict_ldap.h>
#include <dict_mysql.h>
0,
};
-/* pcf_register_dbms_parameters - look for database_type:prefix_name */
+ /*
+ * Pseudo-databases that wrap around other databases.
+ */
+static const char *pcf_multi_dbms_names[] = {
+ "pipemap", "addr_pipemap", "unionmap", 0,
+};
-void pcf_register_dbms_parameters(const char *param_value,
+/* pcf_register_dbms_helper - parse one possible database type:name */
+
+static void pcf_register_dbms_helper(char *str_value,
const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
PCF_MASTER_ENT *local_scope)
{
const PCF_DBMS_INFO *dp;
- char *bufp;
+ size_t len;
char *db_type;
char *prefix;
- static VSTRING *buffer = 0;
static VSTRING *candidate = 0;
const char **cpp;
/*
- * XXX This does not examine both sides of conditional macro expansion,
- * and may expand the "wrong" conditional macros. This is the best we can
- * do for legacy database configuration support.
- */
- if (buffer == 0)
- buffer = vstring_alloc(100);
- bufp = pcf_expand_parameter_value(buffer, PCF_SHOW_EVAL, param_value,
- local_scope);
-
- /*
- * Naive parsing. We don't really know if the parameter specifies free
- * text or a list of databases.
+ * Naive parsing. We don't really know if this substring specifies a
+ * database or some other text.
*/
- while ((db_type = mystrtok(&bufp, " ,\t\r\n")) != 0) {
+ while ((db_type = mystrtokq(&str_value, " ,\t\r\n", "{}")) != 0) {
/*
* Skip over "proxy:" maptypes, to emulate the proxymap(8) server's
* local or global namespace.
*/
if (prefix != 0 && *prefix != '/' && *prefix != '.') {
- for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
- if (strcmp(db_type, dp->db_type) == 0) {
- for (cpp = dp->db_suffixes; *cpp; cpp++) {
- vstring_sprintf(candidate ? candidate :
- (candidate = vstring_alloc(30)),
- "%s_%s", prefix, *cpp);
- flag_parameter(STR(candidate),
+ if (*prefix == '{') {
+ if ((len = balpar(prefix, "{}")) > 0) {
+ prefix[len - 1] = 0;
+ for (cpp = pcf_multi_dbms_names; *cpp; cpp++) {
+ if (strcmp(db_type, *cpp) == 0) {
+ pcf_register_dbms_helper(prefix + 1, flag_parameter,
+ local_scope);
+ break;
+ }
+ }
+ } else {
+ if (local_scope)
+ msg_warn("%s:%s: missing '}' in parameter value: \"%s:%s\"",
+ MASTER_CONF_FILE, local_scope->name_space);
+ else
+ msg_warn("%s: missing '}' in parameter value: \"%s:%s\"",
+ MAIN_CONF_FILE, db_type, prefix);
+ }
+ } else {
+ for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
+ if (strcmp(db_type, dp->db_type) == 0) {
+ for (cpp = dp->db_suffixes; *cpp; cpp++) {
+ vstring_sprintf(candidate ? candidate :
+ (candidate = vstring_alloc(30)),
+ "%s_%s", prefix, *cpp);
+ flag_parameter(STR(candidate),
PCF_PARAM_FLAG_DBMS | PCF_PARAM_FLAG_USER,
- local_scope);
+ local_scope);
+ }
+ break;
}
- break;
}
}
}
}
}
+/* pcf_register_dbms_parameters - look for database_type:prefix_name */
+
+void pcf_register_dbms_parameters(const char *param_value,
+ const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
+ PCF_MASTER_ENT *local_scope)
+{
+ char *bufp;
+ static VSTRING *buffer = 0;
+
+ /*
+ * XXX This does not examine both sides of conditional macro expansion,
+ * and may expand the "wrong" conditional macros. This is the best we can
+ * do for legacy database configuration support.
+ */
+ if (buffer == 0)
+ buffer = vstring_alloc(100);
+ bufp = pcf_expand_parameter_value(buffer, PCF_SHOW_EVAL, param_value,
+ local_scope);
+ pcf_register_dbms_helper(bufp, flag_parameter, local_scope);
+}
+
#endif
--- /dev/null
+./postconf: warning: ./main.cf: undefined parameter: z
+./postconf: warning: ./main.cf: undefined parameter: z
+bar = y-value
+baz =
+config_directory = .
+t1 = Postfix 2.11 compatible
+x = x-value
+y = y-value
+./postconf: warning: ./main.cf: unused parameter: t2=$t1
+./postconf: warning: ./main.cf: unused parameter: foo=$bar$baz
--- /dev/null
+./postconf: warning: main.cf: missing '}' in parameter value: "randmap:{xx"
+config_directory = .
+mydestination = foo bar pipemap:{ldap:xxx, memcache:yy} randmap:{xx
+xxx_domain = foo
+yy_backup = bbb
+./postconf: warning: ./main.cf: unused parameter: yy_bogus=bbb
+./postconf: warning: ./main.cf: unused parameter: xxx_bogus=foo
static void post_jail_init(char *service_name, char **unused_argv)
{
const char *sep = ", \t\r\n";
+ const char *parens = "{}";
char *saved_filter;
char *bp;
char *type_name;
saved_filter = bp = mystrdup(proxy_writer ? var_proxy_write_maps :
var_proxy_read_maps);
proxy_auth_maps = htable_create(13);
- while ((type_name = mystrtok(&bp, sep)) != 0) {
+ while ((type_name = mystrtokq(&bp, sep, parens)) != 0) {
if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
continue;
do {
#define SMTPD_CHECK_PARSE_MAPS (1<<1)
#define SMTPD_CHECK_PARSE_ALL (~0)
- while ((name = mystrtok(&bp, RESTRICTION_SEPARATORS)) != 0) {
+ while ((name = mystrtokq(&bp, RESTRICTION_SEPARATORS, "{}")) != 0) {
argv_add(argv, name, (char *) 0);
if ((flags & SMTPD_CHECK_PARSE_POLICY)
&& last && strcasecmp(last, CHECK_POLICY_SERVICE) == 0)
if (msg_verbose)
msg_info("%s: %s %s", myname, state->name, state->addr);
+ /* RFC 7372: Email Authentication Status Codes. */
if (state->name_status != SMTPD_PEER_CODE_OK)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
state->name_status >= SMTPD_PEER_CODE_PERM ?
- var_unk_client_code : 450, "4.7.1",
+ var_unk_client_code : 450, "4.7.25",
"Client host rejected: cannot find your hostname, [%s]",
state->addr));
return (SMTPD_CHECK_DUNNO);
*/
#define ADDROF(x) ((char *) &(x))
- restrictions = argv_split(value, RESTRICTION_SEPARATORS);
+ restrictions = argv_splitq(value, RESTRICTION_SEPARATORS, "{}");
memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf));
status = setjmp(smtpd_check_buf);
if (status != 0) {
SMTPD_NAME_CLIENT, def_acl);
} else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_ACL, &cpp)) {
status = check_namadr_access(state, *cpp, state->reverse_name, state->addr,
- FULL, &found, state->namaddr,
+ FULL, &found, state->reverse_name,
SMTPD_NAME_REV_CLIENT, def_acl);
forbid_whitelist(state, name, status, state->reverse_name);
} else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) {
} else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_NS_ACL, &cpp)) {
if (strcasecmp(state->reverse_name, "unknown") != 0) {
status = check_server_access(state, *cpp, state->reverse_name,
- T_NS, state->namaddr,
+ T_NS, state->reverse_name,
SMTPD_NAME_REV_CLIENT, def_acl);
forbid_whitelist(state, name, status, state->reverse_name);
}
} else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_MX_ACL, &cpp)) {
if (strcasecmp(state->reverse_name, "unknown") != 0) {
status = check_server_access(state, *cpp, state->reverse_name,
- T_MX, state->namaddr,
+ T_MX, state->reverse_name,
SMTPD_NAME_REV_CLIENT, def_acl);
forbid_whitelist(state, name, status, state->reverse_name);
}
} else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_A_ACL, &cpp)) {
if (strcasecmp(state->reverse_name, "unknown") != 0) {
status = check_server_access(state, *cpp, state->reverse_name,
- T_A, state->namaddr,
+ T_A, state->reverse_name,
SMTPD_NAME_REV_CLIENT, def_acl);
forbid_whitelist(state, name, status, state->reverse_name);
}
static void connect_event(int unused_event, char *context)
{
int sock = CAST_CHAR_PTR_TO_INT(context);
- struct sockaddr sa;
- SOCKADDR_SIZE len = sizeof(sa);
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE len = sizeof(ss);
+ struct sockaddr *sa = (struct sockaddr *) &ss;
SINK_STATE *state;
int fd;
- if ((fd = accept(sock, &sa, &len)) >= 0) {
+ if ((fd = accept(sock, sa, &len)) >= 0) {
if (msg_verbose)
msg_info("connect (%s)",
#ifdef AF_LOCAL
- sa.sa_family == AF_LOCAL ? "AF_LOCAL" :
+ sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
#else
- sa.sa_family == AF_UNIX ? "AF_UNIX" :
+ sa->sa_family == AF_UNIX ? "AF_UNIX" :
#endif
- sa.sa_family == AF_INET ? "AF_INET" :
+ sa->sa_family == AF_INET ? "AF_INET" :
#ifdef AF_INET6
- sa.sa_family == AF_INET6 ? "AF_INET6" :
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
"unknown protocol family");
non_blocking(fd, NON_BLOCKING);
static void connect_event(int unused_event, char *unused_context)
{
- struct sockaddr sa;
- SOCKADDR_SIZE len = sizeof(sa);
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE len = sizeof(ss);
+ struct sockaddr *sa = (struct sockaddr *) &ss;
SINK_STATE *state;
int fd;
- if ((fd = sane_accept(sock, &sa, &len)) >= 0) {
+ if ((fd = sane_accept(sock, sa, &len)) >= 0) {
/* Safety: limit the number of open sockets and capture files. */
if (++client_count == max_client_count)
event_disable_readwrite(sock);
state = (SINK_STATE *) mymalloc(sizeof(*state));
- if (strchr((char *) proto_info->sa_family_list, sa.sa_family))
- SOCKADDR_TO_HOSTADDR(&sa, len, &state->client_addr,
- (MAI_SERVPORT_STR *) 0, sa.sa_family);
+ if (strchr((char *) proto_info->sa_family_list, sa->sa_family))
+ SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr,
+ (MAI_SERVPORT_STR *) 0, sa->sa_family);
else
strncpy(state->client_addr.buf, "local", sizeof("local"));
if (msg_verbose)
msg_info("connect (%s %s)",
#ifdef AF_LOCAL
- sa.sa_family == AF_LOCAL ? "AF_LOCAL" :
+ sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
#else
- sa.sa_family == AF_UNIX ? "AF_UNIX" :
+ sa->sa_family == AF_UNIX ? "AF_UNIX" :
#endif
- sa.sa_family == AF_INET ? "AF_INET" :
+ sa->sa_family == AF_INET ? "AF_INET" :
#ifdef AF_INET6
- sa.sa_family == AF_INET6 ? "AF_INET6" :
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
"unknown protocol family",
state->client_addr.buf);
state->delayed_args = 0;
/* Initialize file capture attributes. */
#ifdef AF_INET6
- if (sa.sa_family == AF_INET6)
+ if (sa->sa_family == AF_INET6)
state->addr_prefix = "ipv6:";
else
#endif
dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \
dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \
poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \
- valid_utf8_hostname.c midna.c
+ valid_utf8_hostname.c midna.c argv_splitq.c balpar.c
OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \
dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \
poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \
- valid_utf8_hostname.o midna.o
+ valid_utf8_hostname.o midna.o argv_splitq.o balpar.o
# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
# When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
# otherwise it sets the PLUGIN_* macros.
argv_split.o: sys_defs.h
argv_split.o: vbuf.h
argv_split.o: vstring.h
+argv_splitq.o: argv.h
+argv_splitq.o: argv_splitq.c
+argv_splitq.o: msg.h
+argv_splitq.o: mymalloc.h
+argv_splitq.o: stringops.h
+argv_splitq.o: sys_defs.h
+argv_splitq.o: vbuf.h
+argv_splitq.o: vstring.h
attr_clnt.o: attr.h
attr_clnt.o: attr_clnt.c
attr_clnt.o: attr_clnt.h
auto_clnt.o: sys_defs.h
auto_clnt.o: vbuf.h
auto_clnt.o: vstream.h
+balpar.o: balpar.c
+balpar.o: stringops.h
+balpar.o: sys_defs.h
+balpar.o: vbuf.h
+balpar.o: vstring.h
base32_code.o: base32_code.c
base32_code.o: base32_code.h
base32_code.o: msg.h
dict_random.o: myflock.h
dict_random.o: mymalloc.h
dict_random.o: myrand.h
+dict_random.o: stringops.h
dict_random.o: sys_defs.h
dict_random.o: vbuf.h
dict_random.o: vstream.h
extern ARGV *argv_split_count(const char *, const char *, ssize_t);
extern ARGV *argv_split_append(ARGV *, const char *, const char *);
+extern ARGV *argv_splitq(const char *, const char *, const char *);
+extern ARGV *argv_splitq_count(const char *, const char *, const char *, ssize_t);
+extern ARGV *argv_splitq_append(ARGV *, const char *, const char *, const char *);
+
#define ARGV_FAKE_BEGIN(fake_argv, arg) { \
ARGV fake_argv; \
char *__fake_argv_args__[2]; \
--- /dev/null
+/*++
+/* NAME
+/* argv_splitq 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_splitq(string, delim, parens)
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/*
+/* ARGV *argv_splitq_count(string, delim, parens, count)
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/* ssize_t count;
+/*
+/* ARGV *argv_splitq_append(argv, string, delim, parens)
+/* ARGV *argv;
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/* DESCRIPTION
+/* argv_splitq() breaks up \fIstring\fR into tokens according
+/* to the delimiters specified in \fIdelim\fR, while avoiding
+/* splitting text between matching parentheses. The result is
+/* a null-terminated string array.
+/*
+/* argv_splitq_count() is like argv_splitq() but stops splitting
+/* input after at most \fIcount\fR -1 times and leaves the
+/* remainder, if any, in the last array element. It is an error
+/* to specify a count < 1.
+/*
+/* argv_splitq_append() performs the same operation as argv_splitq(),
+/* but appends the result to an existing string array.
+/* SEE ALSO
+/* mystrtokq(), safe string splitter.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "stringops.h"
+#include "argv.h"
+#include "msg.h"
+
+/* argv_splitq - split string into token array */
+
+ARGV *argv_splitq(const char *string, const char *delim, const char *parens)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_splitq_count - split string into token array */
+
+ARGV *argv_splitq_count(const char *string, const char *delim,
+ const char *parens, ssize_t count)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ if (count < 1)
+ msg_panic("argv_splitq_count: bad count: %ld", (long) count);
+ while (count-- > 1 && (arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ if (*bp)
+ bp += strspn(bp, delim);
+ if (*bp)
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_splitq_append - split string into token array, append to array */
+
+ARGV *argv_splitq_append(ARGV *argvp, const char *string, const char *delim,
+ const char *parens)
+{
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
--- /dev/null
+/*++
+/* NAME
+/* balpar 3
+/* SUMMARY
+/* determine length of string in parentheses
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* size_t balpar(string, parens)
+/* const char *string;
+/* const char *parens;
+/* DESCRIPTION
+/* balpar() determines the length of a string enclosed in
+/* the specified parentheses, zero in case of error.
+/* SEE ALSO
+/* A balpar() routine appears in Brian W. Kernighan, P.J. Plauger:
+/* "Software Tools", Addison-Wesley 1976. This function is different.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <stringops.h>
+
+/* balpar - return length of {text} */
+
+size_t balpar(const char *string, const char *parens)
+{
+ const char *cp;
+ int level;
+ int ch;
+
+ if (*string != parens[0])
+ return (0);
+ for (level = 1, cp = string + 1; (ch = *cp) != 0; cp++) {
+ if (ch == parens[1]) {
+ if (--level == 0)
+ return (cp - string + 1);
+ } else if (ch == parens[0]) {
+ level++;
+ }
+ }
+ return (0);
+}
* functionality.
*/
extern int dict_allow_surrogate;
-extern DICT *dict_surrogate(const char *, const char *, int, int, const char *,...);
+extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, int, const char *,...);
/*
* This name is reserved for matchlist error handling.
DICT_TYPE_SOCKMAP, dict_sockmap_open,
DICT_TYPE_FAIL, dict_fail_open,
DICT_TYPE_PIPE, dict_pipe_open,
-#ifdef DICT_TYPE_PIPE_LEGACY
- DICT_TYPE_PIPE_LEGACY, dict_pipe_open,
-#endif
DICT_TYPE_RANDOM, dict_random_open,
-#ifdef DICT_TYPE_RANDOM_LEGACY
- DICT_TYPE_RANDOM_LEGACY, dict_random_open,
-#endif
#ifndef USE_DYNAMIC_MAPS
#ifdef HAS_PCRE
DICT_TYPE_PCRE, dict_pcre_open,
#ifdef TEST
+DEFINE_DICT_LMDB_MAP_SIZE;
+DEFINE_DICT_DB_CACHE_SIZE;
+
/*
* Proof-of-concept test program.
*/
/* int dict_flags;
/* DESCRIPTION
/* dict_pipe_open() opens a pipeline of one or more tables.
-/* Example: "\fBpipemap:\fI!type_1:name_1! ... !type_n:name_n\fR".
+/* Example: "\fBpipemap:{\fItype_1:name_1, ... ,type_n:name_n\fR}".
/*
/* Each "pipemap:" query is given to the first table. Each
/* lookup result becomes the query for the next table in the
/* When any table lookup produces no result, the pipeline
/* produces no result.
/*
-/* The first ASCII character after "pipemap:" will be used as
-/* the separator between the lookup tables that follow (do not
-/* use space, ",", ":" or non-ASCII).
+/* The first and last characters of the "pipemap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
/*
/* The open_flags and dict_flags arguments are passed on to
/* the underlying dictionaries.
DICT *dict;
int match_flags = 0;
struct DICT_OWNER aggr_owner;
- char delim[2];
+ size_t len;
/*
* Clarity first. Let the optimizer worry about redundant code.
open_flags, dict_flags,
"%s:%s map requires O_RDONLY access mode",
DICT_TYPE_PIPE, name));
- if (name[0] == ':')
- DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
- open_flags, dict_flags,
- "invalid list delimiter \"%c\" in \"%s:%s\"",
- name[0], DICT_TYPE_PIPE, name));
-
/*
- * Split the table name on the user-specified delimiter.
+ * Split the table name into its constituent parts.
*/
- delim[0] = name[0]; /* XXX ASCII delimiter */
- delim[1] = 0;
- saved_name = mystrdup(name + 1); /* XXX ASCII delimiter */
- if (*saved_name == 0)
+ if ((len = balpar(name, "{}")) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0)
DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
open_flags, dict_flags,
"bad syntax: \"%s:%s\"; "
- "need \"%s:%stype:name%s...\"",
+ "need \"%s:{type:name...}\"",
DICT_TYPE_PIPE, name,
- DICT_TYPE_PIPE, delim, delim));
+ DICT_TYPE_PIPE));
/*
* The least-trusted table in the pipeline determines the over-all trust
* level. The first table determines the pattern-matching flags.
*/
DICT_OWNER_AGGREGATE_INIT(aggr_owner);
- argv = argv_split(saved_name, delim);
+ argv = argv_splitq(saved_name, ", \t\r\n", "{}");
for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
if (msg_verbose)
msg_info("%s: %s", myname, dict_type_name);
DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
open_flags, dict_flags,
"bad syntax: \"%s:%s\"; "
- "need \"%s:%stype:name%s...\"",
+ "need \"%s:{type:name...}\"",
DICT_TYPE_PIPE, name,
- DICT_TYPE_PIPE, delim, delim));
+ DICT_TYPE_PIPE));
if ((dict = dict_handle(dict_type_name)) == 0)
dict = dict_open(dict_type_name, open_flags, dict_flags);
dict_register(dict_type_name, dict);
*/
#define DICT_TYPE_PIPE "pipemap"
-#ifdef SNAPSHOT
-#define DICT_TYPE_PIPE_LEGACY "pipeline"
-#endif
-
extern DICT *dict_pipe_open(const char *, int, int);
/* LICENSE
/* int dict_flags;
/* DESCRIPTION
/* dict_random_open() opens an in-memory, read-only, table.
-/* Example: "\fBrandmap:\fI!result_1! ... !result_n\fR".
+/* Example: "\fBrandmap:{\fIresult_1, ... ,result_n}\fR".
/*
/* Each table query returns a random choice from the specified
/* results. Other table access methods are not supported.
/*
-/* The ASCII character after "randmap:" will be used as the
-/* separator between the results that follow (do not use space,
-/* ",", ":" or non-ASCII).
+/* The first and last characters of the "randmap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
/* SEE ALSO
/* dict(3) generic dictionary manager
/* LICENSE
#include <msg.h>
#include <mymalloc.h>
#include <myrand.h>
+#include <stringops.h>
#include <dict_random.h>
/* Application-specific. */
{
DICT_RANDOM *dict_random;
char *saved_name = 0;
- char delim[2];
+ size_t len;
/*
* Clarity first. Let the optimizer worry about redundant code.
DICT_TYPE_RANDOM, name));
/*
- * Split the name on the user-specified delimiter.
+ * Split the name name into its constituent parts.
*/
- delim[0] = name[0]; /* XXX ASCII delimiter */
- delim[1] = 0;
- saved_name = mystrdup(name + 1); /* XXX ASCII delimiter */
- if (*saved_name == 0)
+ if ((len = balpar(name, "{}")) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0)
DICT_RANDOM_RETURN(dict_surrogate(DICT_TYPE_RANDOM, name,
open_flags, dict_flags,
- "bad syntax: \"%s:%s\"; need \"%s:%svalue%s...\"",
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
DICT_TYPE_RANDOM, name,
- DICT_TYPE_RANDOM, delim, delim));
+ DICT_TYPE_RANDOM));
/*
* Bundle up the result.
dict_random->dict.lookup = dict_random_lookup;
dict_random->dict.close = dict_random_close;
dict_random->dict.flags = dict_flags | DICT_FLAG_PATTERN;
- dict_random->replies = argv_split(saved_name, delim);
+ dict_random->replies = argv_splitq(saved_name, ", \t\r\n", "{}");
dict_random->dict.owner.status = DICT_OWNER_TRUSTED;
dict_random->dict.owner.uid = 0;
- DICT_RANDOM_RETURN(DICT_DEBUG(&dict_random->dict));
+ DICT_RANDOM_RETURN(DICT_DEBUG (&dict_random->dict));
}
*/
#define DICT_TYPE_RANDOM "randmap"
-#ifdef SNAPSHOT
-#define DICT_TYPE_RANDOM_LEGACY "random"
-#endif
-
extern DICT *dict_random_open(const char *, int, int);
/* LICENSE
#include <dict_lmdb.h>
#include <dict_db.h>
-DEFINE_DICT_LMDB_MAP_SIZE;
-DEFINE_DICT_DB_CACHE_SIZE;
-
static NORETURN usage(char *myname)
{
msg_fatal("usage: %s type:file read|write|create [flags...]", myname);
/* const char *lookup(const char *key, int mode, char *context)
/* char *context;
/* DESCRIPTION
-/* This module implements parameter-less macro expansions, both
-/* conditional and unconditional, and both recursive and non-recursive.
+/* This module implements parameter-less named attribute
+/* expansions, both conditional and unconditional. As of Postfix
+/* 2.12 this code supports logical expression evaluation.
/*
/* In this text, an attribute is considered "undefined" when its value
/* is a null pointer. Otherwise, the attribute is considered "defined"
/* and is expected to have as value a null-terminated string.
/*
-/* The following expansions are implemented:
-/* .IP "$name, ${name}, $(name)"
-/* Unconditional expansion. If the named attribute value is non-empty, the
-/* expansion is the value of the named attribute, optionally subjected
-/* to further $name expansions. Otherwise, the expansion is empty.
-/* .IP "${name?text}, $(name?text)"
-/* Conditional expansion. If the named attribute value is non-empty, the
-/* expansion is the given text, subjected to another iteration of
-/* $name expansion. Otherwise, the expansion is empty.
-/* .IP "${name:text}, $(name:text)"
-/* Conditional expansion. If the attribute value is empty or undefined,
-/* the expansion is the given text, subjected to another iteration
-/* of $name expansion. Otherwise, the expansion is empty.
-/* .PP
+/* In the text below, the legacy form $(...) is equivalent to
+/* ${...}. The legacy form $(...) may eventually disappear
+/* from documentation.
+/*
+/* The following substitutions are supported:
+/* .IP "$name, ${name}"
+/* Unconditional attribute-based substition. The result is the
+/* named attribute value (empty if the attribute is not defined)
+/* after optional further named attribute substitution.
+/* .IP "${name?text}, ${name?{text}}"
+/* Conditional attribute-based substition. If the named attribute
+/* value is non-empty, the result is the given text, after
+/* named attribute expansion and logical expression evaluation.
+/* Otherwise, the result is empty. Whitespace before or after
+/* {text} is ignored.
+/* .IP "${name:text}, ${name:{text}}"
+/* Conditional attribute-based substition. If the attribute
+/* value is empty or undefined, the expansion is the given
+/* text, after named attribute expansion and logical expression
+/* evaluation. Otherwise, the result is empty. Whitespace
+/* before or after {text} is ignored.
+/* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}"
+/* Conditional attribute-based substition. If the named attribute
+/* value is non-empty, the result is text1. Otherwise, the
+/* result is text2. In both cases the result is subject to
+/* named attribute expansion and logical expression evaluation.
+/* Whitespace before or after {text1} or {text2} is ignored.
+/* .IP "${{text1} == ${text2} ? {text3} : {text4}}"
+/* .IP "${{text1} != ${text2} ? {text3} : {text4}}"
+/* Logical expression-based substition. First, the content
+/* of {text1} and ${text2} is subjected to named attribute and
+/* logical expression-based substitution. Next, the logical
+/* expression is evaluated. If it evaluates to "true", the
+/* result is the content of {text3}, otherwise it is the content
+/* of {text4}, after named attribute and logical expression-based
+/* substitution.
+/*
/* Arguments:
/* .IP result
-/* Storage for the result of expansion. The result is truncated
-/* upon entry.
+/* Storage for the result of expansion. By default, the result
+/* is truncated upon entry.
/* .IP pattern
/* The string to be expanded.
/* .IP flags
/* Bit-wise OR of zero or more of the following:
/* .RS
/* .IP MAC_EXP_FLAG_RECURSE
-/* Expand macros in lookup results. This should never be done with
-/* data whose origin is untrusted.
+/* Expand attributes in lookup results. This should never be
+/* done with data whose origin is untrusted.
/* .IP MAC_EXP_FLAG_APPEND
/* Append text to the result buffer without truncating it.
/* .IP MAC_EXP_FLAG_SCAN
-/* Invoke the call-back function for each macro name in the input
-/* string, including macro names in the values of conditional
-/* expressions. Do not expand macros, and do not write to the
-/* result argument.
+/* Scan the input for named attributes, including named
+/* attributes in all conditional result values. Do not expand
+/* named attributes, and do not truncate or write to the result
+/* argument.
/* .IP MAC_EXP_FLAG_PRINTABLE
/* Use the printable() function instead of \fIfilter\fR.
/* .PP
/* Caller context that is passed on to the attribute lookup routine.
/* DIAGNOSTICS
/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable
-/* macro nesting.
+/* recursion depth.
/*
/* The result value is the binary OR of zero or more of the following:
/* .IP MAC_PARSE_ERROR
-/* A syntax error was found in \fBpattern\fR, or some macro had
+/* A syntax error was found in \fBpattern\fR, or some attribute had
/* an unreasonable nesting depth.
/* .IP MAC_PARSE_UNDEF
-/* A macro was expanded but its value not defined.
+/* An attribute was expanded but its value was not defined.
/* SEE ALSO
/* mac_parse(3) locate macro references in string.
/* LICENSE
#include <vstring.h>
#include <mymalloc.h>
#include <stringops.h>
+#include <name_code.h>
#include <mac_parse.h>
#include <mac_expand.h>
char *context; /* caller context */
int status; /* findings */
int level; /* nesting level */
-} MAC_EXP;
+} MAC_EXP_CONTEXT;
+
+ /*
+ * Support for logical expressions.
+ *
+ * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the
+ * result respectively when the parameter value is non-empty, or when the
+ * parameter value is undefined or empty; support for the ternary ?:
+ * operator was anticipated, but not implemented for 10 years.
+ *
+ * To make ${logical-expr?result} and ${logical-expr:result} work as expected
+ * without breaking the way that ? and : work, logical expressions evaluate
+ * to a non-empty or empty value. It does not matter what non-empty value we
+ * use for TRUE. However we must not use the undefined (null pointer) value
+ * for FALSE - that would raise the MAC_PARSE_UNDEF flag.
+ *
+ * The value of a logical expression can be exposed with ${logical-expr}, i.e.
+ * a logical expression that is not followed by ? or : conditional
+ * expansion.
+ */
+#define MAC_EXP_BVAL_TRUE "true"
+#define MAC_EXP_BVAL_FALSE ""
+
+ /*
+ * Relational operator. For now, we test only for (in)equality.
+ */
+#define MAC_EXP_OP_STR_EQ "=="
+#define MAC_EXP_OP_STR_NE "!="
+#define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \
+ "\" or \"" MAC_EXP_OP_STR_NE "\""
+
+#define MAC_EXP_OP_TOK_NONE 0
+#define MAC_EXP_OP_TOK_EQ 1
+#define MAC_EXP_OP_TOK_NE 2
+
+static const NAME_CODE mac_exp_op_table[] =
+{
+ MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ,
+ MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE,
+ 0, MAC_EXP_OP_TOK_NONE,
+};
+
+ /*
+ * The whitespace separator set.
+ */
+#define MAC_EXP_WHITESPACE " \t\r\n"
+
+/* mac_exp_parse_error - report parse error, set error flag, return status */
+
+static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+ return (mc->status |= MAC_PARSE_ERROR);
+};
+
+/* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */
+
+#define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \
+ return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \
+ } while (0)
+
+ /*
+ * Postfix 2.12 introduces support for {text} operands. Only with these do
+ * we support the ternary ?: operator and logical operators.
+ *
+ * We cannot support operators in random text, because that would break Postfix
+ * 2.11 compatibility. For example, with the expression "${name?value}", the
+ * value is random text that may contain ':', '?', '{' and '}' characters.
+ * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates
+ * to "??foo:{b}ar" or empty. There are explicit tests in this directory and
+ * the postconf directory to ensure that Postfix 2.11 compatibility is
+ * maintained.
+ *
+ * Ideally, future Postfix configurations enclose random text operands inside
+ * {} braces. These allow whitespace around operands, which improves
+ * readability.
+ */
+
+/* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */
+
+#define MAC_EXP_FIND_LEFT_CURLY(len, cp) \
+ ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \
+ (cp += len) : 0)
+
+/* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */
+
+static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp)
+{
+ char *payload;
+ char *cp;
+ int level;
+ int ch;
+
+ /*
+ * Extract the payload and balance the {}. The caller is expected to skip
+ * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) {
+ if ((ch = *cp) == 0) {
+ mac_exp_parse_error(mc, "unbalanced {} in attribute expression: "
+ "\"%s\"",
+ *bp);
+ return (0);
+ } else if (ch == '{') {
+ level++;
+ } else if (ch == '}') {
+ if (--level <= 0)
+ break;
+ }
+ }
+ *cp++ = 0;
+
+ /*
+ * Skip trailing whitespace after }.
+ */
+ *bp = cp + strspn(cp, MAC_EXP_WHITESPACE);
+ return (payload);
+}
+
+/* mac_exp_parse_logical - parse logical expression, advance read ptr */
+
+static int mac_exp_parse_logical(MAC_EXP_CONTEXT *mc, const char **lookup,
+ char **bp)
+{
+ const char myname[] = "mac_exp_parse_logical";
+ char *cp = *bp;
+ VSTRING *left_op_buf;
+ VSTRING *rite_op_buf;
+ const char *left_op_strval;
+ const char *rite_op_strval;
+ char *op_strval;
+ size_t op_len;
+ int op_tokval;
+ int op_result;
+ size_t tmp_len;
+
+ /*
+ * Left operand. The caller is expected to skip leading whitespace before
+ * the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Operator. Todo: regexp operator.
+ */
+ op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */
+ op_strval = mystrndup(cp, op_len);
+ op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval);
+ myfree(op_strval);
+ if (op_tokval == MAC_EXP_OP_TOK_NONE)
+ MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"",
+ MAC_EXP_OP_STR_ANY, left_op_strval, cp);
+ cp += op_len;
+
+ /*
+ * Right operand. Todo: syntax may depend on operator.
+ */
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0)
+ MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: "
+ "\"...{%s} %.*s>>>%.20s\"",
+ left_op_strval, (int) op_len, op_strval, cp);
+ if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Evaluate the logical expression. Todo: regexp support.
+ */
+ mc->status |=
+ mac_expand(left_op_buf = vstring_alloc(100), left_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ mc->status |=
+ mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ op_result =
+ strcmp(vstring_str(left_op_buf), vstring_str(rite_op_buf));
+ vstring_free(left_op_buf);
+ vstring_free(rite_op_buf);
+ if (mc->status & MAC_PARSE_ERROR)
+ return (mc->status);
+
+ /*
+ * Here, we fake up a non-empty or empty parameter value lookup result,
+ * for compatibility with the historical code that looks named parameter
+ * values.
+ */
+ switch (op_tokval) {
+ case MAC_EXP_OP_TOK_EQ:
+ *lookup = op_result == 0 ?
+ MAC_EXP_BVAL_TRUE : MAC_EXP_BVAL_FALSE;
+ break;
+ case MAC_EXP_OP_TOK_NE:
+ *lookup = op_result != 0 ?
+ MAC_EXP_BVAL_TRUE : MAC_EXP_BVAL_FALSE;
+ break;
+ default:
+ msg_panic("%s: unknown macro operator code %d",
+ myname, op_tokval);
+ }
+ *bp = cp;
+ return (0);
+}
/* mac_expand_callback - callback for mac_parse */
static int mac_expand_callback(int type, VSTRING *buf, char *ptr)
{
- MAC_EXP *mc = (MAC_EXP *) ptr;
+ const char myname[] = "mac_expand_callback";
+ MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr;
int lookup_mode;
- const char *text;
+ const char *lookup;
char *cp;
int ch;
- ssize_t len;
+ ssize_t res_len;
+ ssize_t tmp_len;
+ const char *res_iftrue;
+ const char *res_iffalse;
/*
* Sanity check.
*/
- if (mc->level++ > 100) {
- msg_warn("unreasonable macro call nesting: \"%s\"", vstring_str(buf));
- mc->status |= MAC_PARSE_ERROR;
- }
+ if (mc->level++ > 100)
+ mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"",
+ vstring_str(buf));
if (mc->status & MAC_PARSE_ERROR)
return (mc->status);
/*
- * $Name etc. reference.
- *
- * In order to support expansion of lookup results, we must save the lookup
- * result. We use the input buffer since it will not be needed anymore.
+ * Named parameter or logical expression. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
*/
if (type == MAC_PARSE_EXPR) {
+ cp = vstring_str(buf);
+
/*
- * Look for the ? or : delimiter. In case of a syntax error, return
- * without doing damage, and issue a warning instead.
+ * Logical expression. If recursion is disabled, perform only one
+ * level of $name expansion.
*/
- for (cp = vstring_str(buf); /* void */ ; cp++) {
- if ((ch = *cp) == 0) {
- lookup_mode = MAC_EXP_MODE_USE;
- break;
- }
- if (ch == '?' || ch == ':') {
- *cp++ = 0;
- lookup_mode = MAC_EXP_MODE_TEST;
- break;
- }
- if (!ISALNUM(ch) && ch != '_') {
- msg_warn("macro name syntax error: \"%s\"", vstring_str(buf));
- mc->status |= MAC_PARSE_ERROR;
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if (mac_exp_parse_logical(mc, &lookup, &cp) != 0)
return (mc->status);
+
+ /*
+ * Look for the ? or : operator.
+ */
+ if ((ch = *cp) != 0) {
+ if (ch != '?' && ch != ':')
+ MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: "
+ "\"...}>>>%.20s\"", cp);
+ cp++;
}
}
/*
- * Look up the named parameter.
+ * Named parameter.
*/
- text = mc->lookup(vstring_str(buf), lookup_mode, mc->context);
+ else {
+
+ /*
+ * Look for the ? or : operator. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
+ */
+ for ( /* void */ ; /* void */ ; cp++) {
+ if ((ch = *cp) == 0) {
+ lookup_mode = MAC_EXP_MODE_USE;
+ break;
+ }
+ if (ch == '?' || ch == ':') {
+ *cp++ = 0;
+ lookup_mode = MAC_EXP_MODE_TEST;
+ break;
+ }
+ if (!ISALNUM(ch) && ch != '_') {
+ MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: "
+ "\"...%.*s>>>%.20s\"",
+ (int) (cp - vstring_str(buf)),
+ vstring_str(buf), cp);
+ }
+ }
+
+ /*
+ * Look up the named parameter. Todo: allow the lookup function
+ * to specify if the result is safe for $name expanson.
+ */
+ lookup = mc->lookup(vstring_str(buf), lookup_mode, mc->context);
+ }
/*
- * Perform the requested substitution.
+ * Return the requested result. After parsing the result operand
+ * following ?, we fall through to parse the result operand following
+ * :. This is necessary with the ternary ?: operator: first, with
+ * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(),
+ * and second, to find garbage after any result operand. Without
+ * MAC_EXP_FLAG_SCAN the content of only one of the ?: result
+ * operands will be parsed with mac_parse(); syntax errors in the
+ * other operand will be missed.
*/
switch (ch) {
case '?':
- if ((text != 0 && *text != 0) || (mc->flags & MAC_EXP_FLAG_SCAN))
- mac_parse(cp, mac_expand_callback, (char *) mc);
- break;
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iftrue = cp;
+ cp = ""; /* no left-over text */
+ }
+ if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iftrue, mac_expand_callback,
+ (char *) mc);
+ if (*cp == 0) /* end of input, OK */
+ break;
+ if (*cp != ':') /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "\":\" expected at: "
+ "\"...%s}>>>%.20s\"", res_iftrue, cp);
+ cp += 1;
+ /* FALLTHROUGH: do not remove, see comment above. */
case ':':
- if (text == 0 || *text == 0 || (mc->flags & MAC_EXP_FLAG_SCAN))
- mac_parse(cp, mac_expand_callback, (char *) mc);
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iffalse = cp;
+ cp = ""; /* no left-over text */
+ }
+ if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iffalse, mac_expand_callback,
+ (char *) mc);
+ if (*cp != 0) /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "unexpected input at: "
+ "\"...%s}>>>%.20s\"", res_iffalse, cp);
break;
- default:
- if (text == 0) {
+ case 0:
+ if (lookup == 0) {
mc->status |= MAC_PARSE_UNDEF;
- } else if (*text == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) {
+ } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) {
/* void */ ;
} else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
- vstring_strcpy(buf, text);
- mac_parse(vstring_str(buf), mac_expand_callback, (char *) mc);
+ vstring_strcpy(buf, lookup);
+ mc->status |= mac_parse(vstring_str(buf), mac_expand_callback,
+ (char *) mc);
} else {
- len = VSTRING_LEN(mc->result);
- vstring_strcat(mc->result, text);
+ res_len = VSTRING_LEN(mc->result);
+ vstring_strcat(mc->result, lookup);
if (mc->flags & MAC_EXP_FLAG_PRINTABLE) {
- printable(vstring_str(mc->result) + len, '_');
+ printable(vstring_str(mc->result) + res_len, '_');
} else if (mc->filter) {
- cp = vstring_str(mc->result) + len;
+ cp = vstring_str(mc->result) + res_len;
while (*(cp += strspn(cp, mc->filter)))
*cp++ = '_';
}
}
break;
+ default:
+ msg_panic("%s: unknown operator code %d", myname, ch);
}
}
const char *filter,
MAC_EXP_LOOKUP_FN lookup, char *context)
{
- MAC_EXP mc;
+ MAC_EXP_CONTEXT mc;
int status;
/*
name1 = name1-value
-name2 =
-${name1?name 1 defined, |$name1|$name2|}
-${name1:name 1 undefined, |$name1|$name2|}
-${name2?name 2 defined, |$name1|$name2|}
-${name2:name 2 undefined, |$name1|$name2|}
-|$name1|$name2|
$(name1
$(name )
+${${name1} != {}?name 1 defined, |$name1|$name2|}
+${ ${name1} != {}?name 1 defined, |$name1|$name2|}
+${ ${name1} ?name 1 defined, |$name1|$name2|}
+${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} }
+${x{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+${{$name1}x?{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}x{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}?x{name 1 defined, |$name1|$name2|}}
+${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}x}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}}
+${{text}}
+${{text}?{non-empty}:{empty}}
+${{text} = {}}
+${{${ name1}} == {}}
+${name1?{${ name1}}:{${name2}}}
+${name2?{${ name1}}:{${name2}}}
+${name2?{${name1}}:{${ name2}}}
+${name2:{${name1}}:{${name2}}}
+${name2?{${name1}}?{${name2}}}
+${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{xx}==(yy)?{oops}:{phew}}
-name2 = name2-value
+name1 = name1-value
${name1?name 1 defined, |$name1|$name2|}
${name1:name 1 undefined, |$name1|$name2|}
${name2?name 2 defined, |$name1|$name2|}
${name2:name 2 undefined, |$name1|$name2|}
|$name1|$name2|
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} == {}?{name 1 undefined, |$name1|$name2|}}
+${{$name1} == {}:{name 1 defined, |$name1|$name2|}}
+${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|}
+${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|}
+${{$name1} != {}}
+${{$name1} == {}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}}
+${{$name2} != {}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} == {}?{name 2 undefined, |$name1|$name2|}}
+${{$name2} == {}:{name 2 defined, |$name1|$name2|}}
+${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|}
+${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|}
+${{$name2} != {}}
+${{$name2} == {}}
<< name1 = name1-value
-<< name2 =
<<
-<< ${name1?name 1 defined, |$name1|$name2|}
-stat=2 result=name 1 defined, |name1-value||
-<< ${name1:name 1 undefined, |$name1|$name2|}
-stat=0 result=
-<< ${name2?name 2 defined, |$name1|$name2|}
-stat=0 result=
-<< ${name2:name 2 undefined, |$name1|$name2|}
-stat=2 result=name 2 undefined, |name1-value||
-<< |$name1|$name2|
-stat=2 result=|name1-value||
<< $(name1
unknown: warning: truncated macro reference: "$(name1"
stat=1 result=
<< $(name )
-unknown: warning: macro name syntax error: "name "
+unknown: warning: attribute name syntax error at: "...name>>> "
+stat=1 result=
+<< ${${name1} != {}?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "...>>>${name1} != {}?name "
+stat=1 result=
+<< ${ ${name1} != {}?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "...>>> ${name1} != {}?name"
+stat=1 result=
+<< ${ ${name1} ?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "...>>> ${name1} ?name 1 de"
+stat=1 result=
+<< ${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} }
+unknown: warning: "==" or "!=" expected at: "...$name1}>>>? {name 1 defined, |"
+stat=1 result=
+<< ${x{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+unknown: warning: attribute name syntax error at: "...x>>>{$name1} != {}?{name"
+stat=1 result=
+<< ${{$name1}x?{name 1 defined, |$name1|$name2|}}
+unknown: warning: "==" or "!=" expected at: "...$name1}>>>x?{name 1 defined, |"
+stat=1 result=
+<< ${{$name1} != {}x{name 1 defined, |$name1|$name2|}}
+unknown: warning: "?" or ":" expected at: "...}>>>x{name 1 defined, |$"
+stat=1 result=
+<< ${{$name1} != {}?x{name 1 defined, |$name1|$name2|}}
+stat=2 result=x{name 1 defined, |name1-value||}
+<< ${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x}
+unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x"
+stat=3 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}}
+unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x:{name 1 undefined,"
+stat=3 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}}
+stat=2 result=x{name 2 undefined, |name1-value||}
+<< ${{text}}
+unknown: warning: "==" or "!=" expected at: "...text}>>>"
+stat=1 result=
+<< ${{text}?{non-empty}:{empty}}
+unknown: warning: "==" or "!=" expected at: "...text}>>>?{non-empty}:{empty}"
+stat=1 result=
+<< ${{text} = {}}
+unknown: warning: "==" or "!=" expected at: "...text}>>>= {}"
+stat=1 result=
+<< ${{${ name1}} == {}}
+unknown: warning: attribute name syntax error at: "...>>> name1"
+stat=1 result=
+<< ${name1?{${ name1}}:{${name2}}}
+unknown: warning: attribute name syntax error at: "...>>> name1"
+stat=1 result=
+<< ${name2?{${ name1}}:{${name2}}}
+stat=2 result=
+<< ${name2?{${name1}}:{${ name2}}}
+unknown: warning: attribute name syntax error at: "...>>> name2"
+stat=1 result=
+<< ${name2:{${name1}}:{${name2}}}
+unknown: warning: unexpected input at: "...${name1}}>>>:{${name2}}"
+stat=1 result=name1-value
+<< ${name2?{${name1}}?{${name2}}}
+unknown: warning: ":" expected at: "...${name1}}>>>?{${name2}}"
+stat=1 result=
+<< ${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{xx}==(yy)?{oops}:{phew}}
+unknown: warning: "{expression}" expected at: "...{xx} ??>>>(yy)?{oops}:{phew}"
stat=1 result=
<<
-<< name2 = name2-value
+<< name1 = name1-value
<<
<< ${name1?name 1 defined, |$name1|$name2|}
-stat=0 result=
+stat=2 result=name 1 defined, |name1-value||
<< ${name1:name 1 undefined, |$name1|$name2|}
-stat=2 result=name 1 undefined, ||name2-value|
+stat=0 result=
<< ${name2?name 2 defined, |$name1|$name2|}
-stat=2 result=name 2 defined, ||name2-value|
-<< ${name2:name 2 undefined, |$name1|$name2|}
stat=0 result=
+<< ${name2:name 2 undefined, |$name1|$name2|}
+stat=2 result=name 2 undefined, |name1-value||
<< |$name1|$name2|
-stat=2 result=||name2-value|
+stat=2 result=|name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}:{name 1 undefined, |$name1|$name2|}}
+stat=0 result=
+<< ${{$name1} == {}?{name 1 undefined, |$name1|$name2|}}
+stat=0 result=
+<< ${{$name1} == {}:{name 1 defined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}}
+stat=0 result=true
+<< ${{$name1} == {}}
+stat=0 result=
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}}
+stat=2 result=
+<< ${{$name2} != {}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} == {}?{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} == {}:{name 2 defined, |$name1|$name2|}}
+stat=2 result=
+<< ${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|}
+stat=2 result= name 2 undefined, |name1-value||
+<< ${{$name2} != {}}
+stat=2 result=
+<< ${{$name2} == {}}
+stat=2 result=true
* /filename contents are expanded in-line. To support !/filename we
* prepend the negation operator to each item from the file.
*/
- while ((start = mystrtok(&bp, delim)) != 0) {
+ while ((start = mystrtokq(&bp, delim, "{}")) != 0) {
if (*start == '#') {
msg_warn("%s: comment at end of line is not supported: %s %s",
myname, start, bp);
extern MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN);
extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int,
- void (*log_fn) (const char *,...),
+ void PRINTFLIKE(1, 2) (*log_fn) (const char *,...),
const char *,...);
/* LICENSE
/* char *mystrtok(bufp, delimiters)
/* char **bufp;
/* const char *delimiters;
+/*
+/* char *mystrtokq(bufp, delimiters, parens)
+/* char **bufp;
+/* const char *delimiters;
+/* const char *parens;
/* DESCRIPTION
/* mystrtok() splits a buffer on the specified \fIdelimiters\fR.
/* Tokens are delimited by runs of delimiters, so this routine
/* cannot return zero-length tokens.
/*
+/* mystrtokq() is like mystrtok() but will not split text
+/* between balanced parentheses. \fIparens\fR specifies the
+/* opening and closing parenthesis (one of each). The set of
+/* \fIparens\fR must be distinct from the set of \fIdelimiters\fR.
+/*
/* The \fIbufp\fR argument specifies the start of the search; it
/* is updated with each call. The input is destroyed.
/*
return (start);
}
+/* mystrtokq - safe tokenizer with quoting support */
+
+char *mystrtokq(char **src, const char *sep, const char *parens)
+{
+ char *start = *src;
+ static char *cp;
+ int ch;
+ int level;
+
+ /*
+ * Skip over leading delimiters.
+ */
+ start += strspn(start, sep);
+ if (*start == 0) {
+ *src = start;
+ return (0);
+ }
+
+ /*
+ * Parse out the next token.
+ */
+ for (level = 0, cp = start; (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ch == parens[0]) {
+ level++;
+ } else if (level > 0 && ch == parens[1]) {
+ level--;
+ } else if (level == 0 && strchr(sep, ch) != 0) {
+ *cp++ = 0;
+ break;
+ }
+ }
+ *src = cp;
+ return (start);
+}
+
#ifdef TEST
/*
char *start;
char *str;
- while (vstring_fgets(vp, VSTREAM_IN)) {
+ while (vstring_fgets(vp, VSTREAM_IN) && VSTRING_LEN(vp) > 0) {
start = vstring_str(vp);
- while ((str = mystrtok(&start, " \t\r\n")) != 0)
- vstream_printf(">%s<\n", str);
+ if (strchr(start, '{') == 0) {
+ while ((str = mystrtok(&start, " \t\r\n")) != 0)
+ vstream_printf(">%s<\n", str);
+ } else {
+ while ((str = mystrtokq(&start, " \t\r\n", "{}")) != 0)
+ vstream_printf(">%s<\n", str);
+ }
vstream_fflush(VSTREAM_OUT);
}
vstring_free(vp);
extern char *trimblanks(char *, int);
extern char *concatenate(const char *,...);
extern char *mystrtok(char **, const char *);
+extern char *mystrtokq(char **, const char *, const char *);
extern char *translit(char *, const char *, const char *);
#ifndef HAVE_BASENAME
#define basename postfix_basename
extern int allascii(const char *);
extern const char *split_nameval(char *, char **, char **);
extern int valid_utf8_string(const char *, ssize_t);
+extern size_t balpar(const char *, const char *);
/* LICENSE
/* .ad
* strings, since we are ging to let sprintf() do the hard work.
* In regular expression notation, we recognize:
*
- * %-?0?([0-9]+|\*)?\.?([0-9]+|\*)?l?[a-zA-Z]
+ * %-?0?([0-9]+|\*)?\.(?[0-9]+|\*)?l?[a-zA-Z]
*
* which includes some combinations that do not make sense. Garbage
* in, garbage out.
msg_warn("%s: bad width %d in %.50s", myname, width, format);
width = 0;
}
- if (*cp == '.') /* width/precision separator */
+ if (*cp == '.') { /* width/precision separator */
VSTRING_ADDCH(fmt, *cp++);
- if (*cp == '*') { /* dynamic precision */
- prec = va_arg(ap, int);
- VSTRING_ADDNUM(fmt, prec);
- cp++;
- } else { /* hard-coded precision */
- for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
- prec = prec * 10 + ch - '0';
- VSTRING_ADDCH(fmt, ch);
+ if (*cp == '*') { /* dynamic precision */
+ prec = va_arg(ap, int);
+ VSTRING_ADDNUM(fmt, prec);
+ cp++;
+ } else { /* hard-coded precision */
+ for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
+ prec = prec * 10 + ch - '0';
+ VSTRING_ADDCH(fmt, ch);
+ }
}
- }
- if (prec < 0) {
- msg_warn("%s: bad precision %d in %.50s", myname, prec, format);
- prec = 0;
+ if (prec < 0) {
+ msg_warn("%s: bad precision %d in %.50s", myname, prec, format);
+ prec = -1;
+ }
+ } else {
+ prec = -1;
}
if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
VSTRING_ADDCH(fmt, *cp++);
switch (*cp) {
case 's': /* string-valued argument */
s = va_arg(ap, char *);
- if (prec > 0 || (width > 0 && width > strlen(s))) {
+ if (prec >= 0 || (width > 0 && width > strlen(s))) {
if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE))
return (bp);
sprintf((char *) bp->ptr, vstring_str(fmt), s);