Thienemann. See RELEASE_NOTES for caveats. Files:
proto/postconf.proto, bounce/bounce_notify_tester.c, many
test data files to exercise corner cases.
+
+20201220
+
+ Infrastructure: support to add custom comparison operators
+ for Postfix configuration files. This will be used to implement
+ custom comparison operators for compatibility_level values
+ that contain both the Postfix major and minor version and
+ maybe patchlevel. Files: util/alldig.c, util/stringops.h,
+ util/mac_expand.[hc] and test files.
+
+20210102
+
+ Infrastructure: support for the <=level, <level, and other
+ operators to compare compatibility levels. With the standard
+ <=, <, etc. operators, compatibility level 3.10 would be
+ less than 3.9 which is undesirable. Files: global/compat_level.[hc]
+ and test files.
+
+20210107
+
+ Documentation: added lmdb to the postmap/postalias pages.
+ Files: postmap/postmap.c, postalias/postalias.c.
+
+20210109
+
+ Feature: support for compatibility levels of the form
+ "major.minor.patch". Files: global/mail_params.[hc],
+ master/master_ent.c, postconf/postconf.c, postfix/postfix.c,
+ proto/COMPATIBILITY_README.html, proto/postconf.proto.
the expected client certificate or public key.
As long as the smtpd_tls_fingerprint_digest parameter is left at its implicit
-default value, and the compatibility_level setting is less than 3, Postfix logs
-a warning each time a client certificate or public key fingerprint is
+default value, and the compatibility_level setting is less than 3.6, Postfix
+logs a warning each time a client certificate or public key fingerprint is
(potentially) used for access control:
postfix/smtpd[27560]: using backwards-compatible default setting
As long as the smtp_tls_fingerprint_digest (or LMTP equivalent) parameter is
left at its implicit default value, and the compatibility_level setting is less
-than 3, Postfix logs a warning each time the "fingerprint" security level is
+than 3.6, Postfix logs a warning each time the "fingerprint" security level is
used to specify matching "md5" digests of trusted server certificates or public
keys:
Sites that don't care about backwards compatibility may set
"compatibility_level = 9999" at their own risk.
+Starting with Postfix version 3.6, the compatibility level in the above warning
+message is the Postfix version that introduced the last incompatible change.
+The level is formatted as major.minor.patch, where patch is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2.
+
+NOTE: this also introduces support for the "<level", "<=level", and other
+operators to compare compatibility levels. With the standard operators "<",
+"<=", etc., compatibility level "3.10" would be smaller than "3.9" which is
+undesirable.
+
The digest algorithm used to compute the client certificate fingerprints is
specified with the main.cf smtpd_tls_fingerprint_digest parameter. The default
-algorithm is s\bsh\bha\ba2\b25\b56\b6 with Postfix >= 3.6 and the c\bco\bom\bmp\bpa\bat\bti\bib\bbi\bil\bli\bit\bty\by_\b_l\ble\bev\bve\bel\bl set to 3 or
-higher. With Postfix <= 3.5, the default algorithm is m\bmd\bd5\b5. The best-practice
+algorithm is s\bsh\bha\ba2\b25\b56\b6 with Postfix >= 3.6 and the c\bco\bom\bmp\bpa\bat\bti\bib\bbi\bil\bli\bit\bty\by_\b_l\ble\bev\bve\bel\bl set to 3.6
+or higher. With Postfix <= 3.5, the default algorithm is m\bmd\bd5\b5. The best-practice
algorithm is now s\bsh\bha\ba2\b25\b56\b6. Recent advances in hash function cryptanalysis have
led to md5 and sha1 being deprecated in favor of sha256. However, as long as
there are no known "second pre-image" attacks against the older algorithms,
a delimiter as it occurs between each pair of fingerprint (hexadecimal) digits.
The default algorithm is s\bsh\bha\ba2\b25\b56\b6 with Postfix >= 3.6 and the c\bco\bom\bmp\bpa\bat\bti\bib\bbi\bil\bli\bit\bty\by_\b_l\ble\bev\bve\bel\bl
-set to 3 or higher; with Postfix <= 3.5, the default algorithm is m\bmd\bd5\b5. The
+set to 3.6 or higher; with Postfix <= 3.5, the default algorithm is m\bmd\bd5\b5. The
best-practice algorithm is now s\bsh\bha\ba2\b25\b56\b6. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of sha256.
However, as long as there are no known "second pre-image" attacks against the
the software under the license of their choice. Those who are more
comfortable with the IPL can continue with that license.
+Major changes with snapshot 20220109
+====================================
+
+Starting with Postfix version 3.6, the "latest" compatibility level
+is the Postfix version that introduced the last incompatible change.
+The level is formatted as 'major.minor.patch', where 'patch' is
+usually omitted and defaults to zero. Earlier compatibility levels
+are 0, 1 and 2.
+
+This also introduces support for the <=level, <level, and other
+operators to compare compatibility levels. With the standard <=,
+<, etc. operators, compatibility level 3.10 would be less than 3.9
+which is undesirable.
+
Major changes with snapshot 20201205
====================================
+[The patent discussed in this document expired in 2019, without a
+request for extension. The owner of that patent can no longer sue for
+infringement. However, other patents may make similar claims. The text
+below may serve as an example for dealing with them.]
+
1. Disclaimer: This text is not an authoritative statement. If
you are concerned about the implications of US patent 6,321,267,
then you should give this text to your own lawyer and get their
Then we can also consider to save input to dead.letter (drop
setgid privilege, use safe_open() to avoid clobbering files).
+ Consider removing compat_level_from_numbers() and aliases,
+ because they are no longer used anywhere.
+
Allow '}' at the beginning of a line. This would make
multi-line configuration settings easier to enter.
<p> As long as the <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> parameter is left at its
implicit default value, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is less than
-3, Postfix logs a warning each time a client certificate or public key
+3.6, Postfix logs a warning each time a client certificate or public key
fingerprint is (potentially) used for access control: </p>
<blockquote>
<p> As long as the <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> (or LMTP equivalent)
parameter is left at its implicit default value, and the
-<a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is less than 3, Postfix logs a warning each
+<a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is less than 3.6, Postfix logs a warning each
time the "fingerprint" security level is used to specify matching "md5"
digests of trusted server certificates or public keys: </p>
<p> Sites that don't care about backwards compatibility may set
"<a href="postconf.5.html#compatibility_level">compatibility_level</a> = 9999" at their own risk. </p>
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: this also introduces support for the "<level",
+"<=level", and other operators to compare compatibility levels.
+With the standard operators "<", "<=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
</body>
</html>
<p> The digest algorithm used to compute the client certificate
fingerprints is specified with the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a>
parameter. The default algorithm is <b>sha256</b> with Postfix ≥
-3.6 and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher. With
+3.6 and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With
Postfix ≤ 3.5, the default algorithm is <b>md5</b>. The
best-practice algorithm is now <b>sha256</b>. Recent advances in hash
function cryptanalysis have led to md5 and sha1 being deprecated in
digits. </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher; with Postfix
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher; with Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The
best-practice algorithm is now <b>sha256</b>. Recent advances in hash
function cryptanalysis have led to md5 and sha1 being deprecated in
<i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
for <b>dbm</b> databases.
+ <b>fail</b> A table that reliably fails all requests. The lookup ta-
+ ble name is used for logging only. This table exists to
+ simplify Postfix error tests.
+
<b>hash</b> The output is a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>. This is
available on systems with support for <b>db</b> databases.
- <b>fail</b> A table that reliably fails all requests. The lookup ta-
- ble name is used for logging only. This table exists to
- simplify Postfix error tests.
+ <b>lmdb</b> The output is a btree-based file, named <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
+ <b>lmdb</b> supports concurrent writes and reads from different
+ processes, unlike other supported file-based tables.
+ This is available on systems with support for <b>lmdb</b> data-
+ bases.
<b>sdbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
<i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
A prefix that is prepended to the process name in syslog
records, so that, for example, "smtpd" becomes "prefix/smtpd".
+ Available in Postfix 2.11 and later:
+
+ <b><a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (16777216)</b>
+ The initial OpenLDAP LMDB database size limit in bytes.
+
<b>STANDARDS</b>
<a href="http://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
</pre>
</blockquote>
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: this also introduces support for the "<level",
+"<=level", and other operators to compare compatibility levels.
+With the standard operators "<", "<=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
<p> This feature is available in Postfix 3.0 and later. </p>
(default: no)</b></DT><DD>
<p> Enable non-delivery notifications (bounce messages) that link
-to the original message by including a References: and In-Reply_to:
+to the original message by including a References: and In-Reply-To:
header with the original Message-ID value. There are advantages and
disadvantages to consider. </p>
Postfix version 2.5). </dd>
<dd> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher. With Postfix
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
Postfix version 2.5). </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher. With Postfix
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
certificate (or public/private key-pair) that has the same fingerprint. </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher. With Postfix
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. </p>
<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
2.2 and later. </dd>
<dd> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher. With Postfix
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
= yes" and is available with Postfix version 2.2 and later.</dd>
<dd> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher. With Postfix
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
<b><a href="postconf.5.html#check_ccert_access">check_ccert_access</a></b> and <b><a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a></b>. </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3 or higher. With Postfix
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. </p>
<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
<i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
for <b>dbm</b> databases.
+ <b>fail</b> A table that reliably fails all requests. The lookup ta-
+ ble name is used for logging only. This table exists to
+ simplify Postfix error tests.
+
<b>hash</b> The output file is a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
This is available on systems with support for <b>db</b> data-
bases.
- <b>fail</b> A table that reliably fails all requests. The lookup ta-
- ble name is used for logging only. This table exists to
- simplify Postfix error tests.
+ <b>lmdb</b> The output is a btree-based file, named <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
+ <b>lmdb</b> supports concurrent writes and reads from different
+ processes, unlike other supported file-based tables.
+ This is available on systems with support for <b>lmdb</b> data-
+ bases.
<b>sdbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
<i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
A prefix that is prepended to the process name in syslog
records, so that, for example, "smtpd" becomes "prefix/smtpd".
+ Available in Postfix 2.11 and later:
+
+ <b><a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (16777216)</b>
+ The initial OpenLDAP LMDB database size limit in bytes.
+
<b>SEE ALSO</b>
<a href="postalias.1.html">postalias(1)</a>, create/update/query alias database
<a href="postconf.1.html">postconf(1)</a>, supported database types
The output consists of two files, named \fIfile_name\fB.pag\fR and
\fIfile_name\fB.dir\fR.
This is available on systems with support for \fBdbm\fR databases.
-.IP \fBhash\fR
-The output is a hashed file, named \fIfile_name\fB.db\fR.
-This is available on systems with support for \fBdb\fR databases.
.IP \fBfail\fR
A table that reliably fails all requests. The lookup table
name is used for logging only. This table exists to simplify
Postfix error tests.
+.IP \fBhash\fR
+The output is a hashed file, named \fIfile_name\fB.db\fR.
+This is available on systems with support for \fBdb\fR databases.
+.IP \fBlmdb\fR
+The output is a btree\-based file, named \fIfile_name\fB.lmdb\fR.
+\fBlmdb\fR supports concurrent writes and reads from different
+processes, unlike other supported file\-based tables.
+This is available on systems with support for \fBlmdb\fR databases.
.IP \fBsdbm\fR
The output consists of two files, named \fIfile_name\fB.pag\fR and
\fIfile_name\fB.dir\fR.
.IP "\fBsyslog_name (see 'postconf -d' output)\fR"
A prefix that is prepended to the process name in syslog
records, so that, for example, "smtpd" becomes "prefix/smtpd".
+.PP
+Available in Postfix 2.11 and later:
+.IP "\fBlmdb_map_size (16777216)\fR"
+The initial OpenLDAP LMDB database size limit in bytes.
.SH "STANDARDS"
.na
.nf
The output consists of two files, named \fIfile_name\fB.pag\fR and
\fIfile_name\fB.dir\fR.
This is available on systems with support for \fBdbm\fR databases.
-.IP \fBhash\fR
-The output file is a hashed file, named \fIfile_name\fB.db\fR.
-This is available on systems with support for \fBdb\fR databases.
.IP \fBfail\fR
A table that reliably fails all requests. The lookup table
name is used for logging only. This table exists to simplify
Postfix error tests.
+.IP \fBhash\fR
+The output file is a hashed file, named \fIfile_name\fB.db\fR.
+This is available on systems with support for \fBdb\fR databases.
+.IP \fBlmdb\fR
+The output is a btree\-based file, named \fIfile_name\fB.lmdb\fR.
+\fBlmdb\fR supports concurrent writes and reads from different
+processes, unlike other supported file\-based tables.
+This is available on systems with support for \fBlmdb\fR databases.
.IP \fBsdbm\fR
The output consists of two files, named \fIfile_name\fB.pag\fR and
\fIfile_name\fB.dir\fR.
.IP "\fBsyslog_name (see 'postconf -d' output)\fR"
A prefix that is prepended to the process name in syslog
records, so that, for example, "smtpd" becomes "prefix/smtpd".
+.PP
+Available in Postfix 2.11 and later:
+.IP "\fBlmdb_map_size (16777216)\fR"
+The initial OpenLDAP LMDB database size limit in bytes.
.SH "SEE ALSO"
.na
.nf
.ft R
.in -4
.PP
+Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+\fImajor.minor.patch\fR, where \fIpatch\fR is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2.
+.PP
+NOTE: this also introduces support for the "<level",
+"<=level", and other operators to compare compatibility levels.
+With the standard operators "<", "<=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable.
+.PP
This feature is available in Postfix 3.0 and later.
.SH config_directory (default: see "postconf \-d" output)
The default location of the Postfix main.cf and master.cf
address.
.SH enable_threaded_bounces (default: no)
Enable non\-delivery notifications (bounce messages) that link
-to the original message by including a References: and In\-Reply_to:
+to the original message by including a References: and In\-Reply\-To:
header with the original Message\-ID value. There are advantages and
disadvantages to consider.
.IP "\fB advantage \fR"
Postfix version 2.5).
.br
The default algorithm is \fBsha256\fR with Postfix >= 3.6
-and the \fBcompatibility_level\fR set to 3 or higher. With Postfix
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
algorithm is now \fBsha256\fR. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
Postfix version 2.5).
.PP
The default algorithm is \fBsha256\fR with Postfix >= 3.6
-and the \fBcompatibility_level\fR set to 3 or higher. With Postfix
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
algorithm is now \fBsha256\fR. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
certificate (or public/private key\-pair) that has the same fingerprint.
.PP
The default algorithm is \fBsha256\fR with Postfix >= 3.6
-and the \fBcompatibility_level\fR set to 3 or higher. With Postfix
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
<= 3.5, the default algorithm is \fBmd5\fR.
.PP
The best\-practice algorithm is now \fBsha256\fR. Recent advances in hash
2.2 and later.
.br
The default algorithm is \fBsha256\fR with Postfix >= 3.6
-and the \fBcompatibility_level\fR set to 3 or higher. With Postfix
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
algorithm is now \fBsha256\fR. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
= yes" and is available with Postfix version 2.2 and later.
.br
The default algorithm is \fBsha256\fR with Postfix >= 3.6
-and the \fBcompatibility_level\fR set to 3 or higher. With Postfix
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
algorithm is now \fBsha256\fR. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
\fBcheck_ccert_access\fR and \fBpermit_tls_clientcerts\fR.
.PP
The default algorithm is \fBsha256\fR with Postfix >= 3.6
-and the \fBcompatibility_level\fR set to 3 or higher. With Postfix
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
<= 3.5, the default algorithm is \fBmd5\fR.
.PP
The best\-practice algorithm is now \fBsha256\fR. Recent advances in hash
<p> As long as the smtpd_tls_fingerprint_digest parameter is left at its
implicit default value, and the compatibility_level setting is less than
-3, Postfix logs a warning each time a client certificate or public key
+3.6, Postfix logs a warning each time a client certificate or public key
fingerprint is (potentially) used for access control: </p>
<blockquote>
<p> As long as the smtp_tls_fingerprint_digest (or LMTP equivalent)
parameter is left at its implicit default value, and the
-compatibility_level setting is less than 3, Postfix logs a warning each
+compatibility_level setting is less than 3.6, Postfix logs a warning each
time the "fingerprint" security level is used to specify matching "md5"
digests of trusted server certificates or public keys: </p>
<p> Sites that don't care about backwards compatibility may set
"compatibility_level = 9999" at their own risk. </p>
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: this also introduces support for the "<level",
+"<=level", and other operators to compare compatibility levels.
+With the standard operators "<", "<=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
</body>
</html>
<p> The digest algorithm used to compute the client certificate
fingerprints is specified with the main.cf smtpd_tls_fingerprint_digest
parameter. The default algorithm is <b>sha256</b> with Postfix ≥
-3.6 and the <b>compatibility_level</b> set to 3 or higher. With
+3.6 and the <b>compatibility_level</b> set to 3.6 or higher. With
Postfix ≤ 3.5, the default algorithm is <b>md5</b>. The
best-practice algorithm is now <b>sha256</b>. Recent advances in hash
function cryptanalysis have led to md5 and sha1 being deprecated in
digits. </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b>compatibility_level</b> set to 3 or higher; with Postfix
+and the <b>compatibility_level</b> set to 3.6 or higher; with Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The
best-practice algorithm is now <b>sha256</b>. Recent advances in hash
function cryptanalysis have led to md5 and sha1 being deprecated in
2.2 and later. </dd>
<dd> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b>compatibility_level</b> set to 3 or higher. With Postfix
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
= yes" and is available with Postfix version 2.2 and later.</dd>
<dd> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b>compatibility_level</b> set to 3 or higher. With Postfix
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
Postfix version 2.5). </dd>
<dd> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b>compatibility_level</b> set to 3 or higher. With Postfix
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
Postfix version 2.5). </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b>compatibility_level</b> set to 3 or higher. With Postfix
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. The best-practice
algorithm is now <b>sha256</b>. Recent advances in hash function
cryptanalysis have led to md5 and sha1 being deprecated in favor of
certificate (or public/private key-pair) that has the same fingerprint. </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b>compatibility_level</b> set to 3 or higher. With Postfix
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. </p>
<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
<b>check_ccert_access</b> and <b>permit_tls_clientcerts</b>. </p>
<p> The default algorithm is <b>sha256</b> with Postfix ≥ 3.6
-and the <b>compatibility_level</b> set to 3 or higher. With Postfix
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
≤ 3.5, the default algorithm is <b>md5</b>. </p>
<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
</pre>
</blockquote>
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: this also introduces support for the "<level",
+"<=level", and other operators to compare compatibility levels.
+With the standard operators "<", "<=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
<p> This feature is available in Postfix 3.0 and later. </p>
%PARAM message_drop_headers bcc, content-length, resent-bcc, return-path
%PARAM enable_threaded_bounces no
<p> Enable non-delivery notifications (bounce messages) that link
-to the original message by including a References: and In-Reply_to:
+to the original message by including a References: and In-Reply-To:
header with the original Message-ID value. There are advantages and
disadvantages to consider. </p>
mail_addr_form.c quote_flags.c maillog_client.c \
normalize_mailhost_addr.c map_search.c reject_deliver_request.c \
info_log_addr_form.c sasl_mech_filter.c login_sender_match.c \
- test_main.c
+ test_main.c compat_level.c
OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \
clnt_stream.o conv_time.o db_common.o debug_peer.o debug_process.o \
$(NON_PLUGIN_MAP_OBJ) mail_addr_form.o quote_flags.o maillog_client.o \
normalize_mailhost_addr.o map_search.o reject_deliver_request.o \
info_log_addr_form.o sasl_mech_filter.o login_sender_match.o \
- test_main.o
+ test_main.o compat_level.o
# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
# When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
# otherwise it sets the PLUGIN_* macros.
attr_override.h mail_parm_split.h midna_adomain.h mail_addr_form.h \
maillog_client.h normalize_mailhost_addr.h map_search.h \
info_log_addr_form.h sasl_mech_filter.h login_sender_match.h \
- test_main.h
+ test_main.h compat_level.h
TESTSRC = rec2stream.c stream2rec.c recdump.c
DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
CFLAGS = $(DEBUG) $(OPT) $(DEFS)
data_redirect addr_match_list safe_ultostr verify_sender_addr \
mail_version mail_dict server_acl uxtext mail_parm_split \
fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \
- haproxy_srvr map_search delivered_hdr login_sender_match
+ haproxy_srvr map_search delivered_hdr login_sender_match \
+ compat_level
LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
LIB_DIR = ../../lib
login_sender_match: login_sender_match.c $(LIB) $(LIBS)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+compat_level: compat_level.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
tests: tok822_test mime_tests strip_addr_test tok822_limit_test \
xtext_test scache_multi_test ehlo_mask_test \
namadr_list_test mail_conf_time_test header_body_checks_tests \
smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \
mail_addr_find_test mail_addr_map_test quote_822_local_test \
normalize_mailhost_addr_test haproxy_srvr_test map_search_test \
- delivered_hdr_test login_sender_match_test
+ delivered_hdr_test login_sender_match_test compat_level_test
mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \
mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4
diff login_sender_match.ref login_sender_match.tmp
rm -f login_sender_match.tmp
+compat_level_test: compat_level_expand_test compat_level_convert_test
+
+compat_level_expand_test: update compat_level compat_level_expand.in \
+ compat_level_expand.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./compat_level -x <compat_level_expand.in >compat_level_expand.tmp 2>&1
+ diff compat_level_expand.ref compat_level_expand.tmp
+ rm -f compat_level_expand.tmp
+
+compat_level_convert_test: update compat_level compat_level_convert.in \
+ compat_level_convert.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./compat_level -c <compat_level_convert.in >compat_level_convert.tmp 2>&1
+ diff compat_level_convert.ref compat_level_convert.tmp
+ rm -f compat_level_convert.tmp
+
printfck: $(OBJS) $(PROG)
rm -rf printfck
mkdir printfck
clnt_stream.o: clnt_stream.h
clnt_stream.o: mail_params.h
clnt_stream.o: mail_proto.h
+compat_level.o: ../../include/check_arg.h
+compat_level.o: ../../include/mac_expand.h
+compat_level.o: ../../include/mac_parse.h
+compat_level.o: ../../include/msg.h
+compat_level.o: ../../include/sys_defs.h
+compat_level.o: ../../include/vbuf.h
+compat_level.o: ../../include/vstring.h
+compat_level.o: compat_level.c
+compat_level.o: compat_level.h
conv_time.o: ../../include/msg.h
conv_time.o: ../../include/sys_defs.h
conv_time.o: conv_time.c
mail_params.o: ../../include/vstream.h
mail_params.o: ../../include/vstring.h
mail_params.o: ../../include/vstring_vstream.h
+mail_params.o: compat_level.h
mail_params.o: mail_conf.h
mail_params.o: mail_params.c
mail_params.o: mail_params.h
--- /dev/null
+/*++
+/* NAME
+/* compat_level 3
+/* SUMMARY
+/* compatibility_level support
+/* SYNOPSIS
+/* #include <compat_level.h>
+/*
+/* void compat_level_relop_register(void)
+/*
+/* long compat_level_from_string(
+/* const char *str,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* long compat_level_from_numbers(
+/* long major,
+/* long minor,
+/* long patch,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* const char *compat_level_to_string(
+/* long compat_level,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/* AUXULIARY FUNCTIONS
+/* long compat_level_from_major_minor(
+/* long major,
+/* long minor,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* long compat_level_from_major(
+/* long major,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/* DESCRIPTION
+/* This module supports compatibility level syntax with
+/* "major.minor.patch" but will also accept the shorter forms
+/* "major.minor" and "major" (missing members default to zero).
+/* Compatibility levels with multiple numbers cannot be compared
+/* as strings or as floating-point numbers (for example, "3.10"
+/* would be smaller than "3.9").
+/*
+/* The major number can range from [0..2047] inclusive (11
+/* bits) or more, while the minor and patch numbers can range
+/* from [0..1023] inclusive (10 bits).
+/*
+/* compat_level_from_string() converts a compatibility level
+/* from string form to numerical form for easy comparison.
+/* Valid input results in a non-negative result. In case of
+/* error, compat_level_from_string() reports the problem with
+/* the provided function, and returns -1 if that function does
+/* not terminate execution.
+/*
+/* compat_level_from_numbers() creates an internal-form
+/* compatibility level from distinct numbers. Valid input
+/* results in a non-negative result. In case of error,
+/* compat_level_from_numbers() reports the problem with the
+/* provided function, and returns -1 if that function does not
+/* terminate execution.
+/*
+/* The functions compat_level_from_major_minor() and
+/* compat_level_from_major() are helpers that default the missing
+/* information to zeroes.
+/*
+/* compat_level_to_string() converts a compatibility level
+/* from numerical form to canonical string form. Valid input
+/* results in a non-null result. In case of error,
+/* compat_level_to_string() reports the problem with the
+/* provided function, and returns a null pointer if that
+/* function does not terminate execution.
+/*
+/* compat_level_relop_register() registers a mac_expand() callback
+/* that registers operators such as <=level, >level, that compare
+/* compatibility levels. This function should be called before
+/* loading parameter settings from main.cf.
+/* DIAGNOSTICS
+/* info, .., panic: bad compatibility_level syntax.
+/* BUGS
+/* The patch and minor fields range from 0..1023 (10 bits) while
+/* the major field ranges from 0..COMPAT_MAJOR_SHIFT47 or more
+/* (11 bits or more).
+/*
+/* This would be a great use case for fucntions returning
+/* StatusOr<compat_level_t> or StatusOr<string>, but is it a bit
+/* late for a port to C++.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+
+ /*
+ * Utility library.
+ */
+#include <mac_expand.h>
+#include <msg.h>
+
+ /*
+ * For easy comparison we convert a three-number compatibility level into
+ * just one number, using different bit ranges for the major version, minor
+ * version, and patch level.
+ *
+ * We use long integers because standard C guarantees that long has at last 32
+ * bits instead of int which may have only 16 bits (though it is unlikely
+ * that Postfix would run on such systems). That gives us 11 or more bits
+ * for the major version, and 10 bits for minor the version and patchlevel.
+ *
+ * Below are all the encoding details in one place. This is easier to verify
+ * than wading through code.
+ */
+#define COMPAT_MAJOR_SHIFT \
+ (COMPAT_MINOR_SHIFT + COMPAT_MINOR_WIDTH)
+
+#define COMPAT_MINOR_SHIFT COMPAT_PATCH_WIDTH
+#define COMPAT_MINOR_BITS 0x3ff
+#define COMPAT_MINOR_WIDTH 10
+
+#define COMPAT_PATCH_BITS 0x3ff
+#define COMPAT_PATCH_WIDTH 10
+
+#define GOOD_MAJOR(m) ((m) >= 0 && (m) <= (LONG_MAX >> COMPAT_MAJOR_SHIFT))
+#define GOOD_MINOR(m) ((m) >= 0 && (m) <= COMPAT_MINOR_BITS)
+#define GOOD_PATCH(p) ((p) >= 0 && (p) <= COMPAT_PATCH_BITS)
+
+#define ENCODE_MAJOR(m) ((m) << COMPAT_MAJOR_SHIFT)
+#define ENCODE_MINOR(m) ((m) << COMPAT_MINOR_SHIFT)
+#define ENCODE_PATCH(p) (p)
+
+#define DECODE_MAJOR(l) ((l) >> COMPAT_MAJOR_SHIFT)
+#define DECODE_MINOR(l) (((l) >> COMPAT_MINOR_SHIFT) & COMPAT_MINOR_BITS)
+#define DECODE_PATCH(l) ((l) & COMPAT_PATCH_BITS)
+
+ /*
+ * Global library.
+ */
+#include <compat_level.h>
+
+/* compat_level_from_string - convert major[.minor] to comparable type */
+
+long compat_level_from_string(const char *str,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ long major, minor, patch, res = 0;
+ const char *start;
+ char *remainder;
+
+ start = str;
+ major = strtol(start, &remainder, 10);
+ if (start < remainder && (*remainder == 0 || *remainder == '.')
+ && errno != ERANGE && GOOD_MAJOR(major)) {
+ res = ENCODE_MAJOR(major);
+ if (*remainder == 0)
+ return res;
+ start = remainder + 1;
+ minor = strtol(start, &remainder, 10);
+ if (start < remainder && (*remainder == 0 || *remainder == '.')
+ && errno != ERANGE && GOOD_MINOR(minor)) {
+ res |= ENCODE_MINOR(minor);
+ if (*remainder == 0)
+ return (res);
+ start = remainder + 1;
+ patch = strtol(start, &remainder, 10);
+ if (start < remainder && *remainder == 0 && errno != ERANGE
+ && GOOD_PATCH(patch)) {
+ return (res | ENCODE_PATCH(patch));
+ }
+ }
+ }
+ msg_fn("malformed compatibility level syntax: \"%s\"", str);
+ return (-1);
+}
+
+/* compat_level_from_numbers - internal form from numbers */
+
+long compat_level_from_numbers(long major, long minor, long patch,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ const char myname[] = "compat_level_from_numbers";
+
+ /*
+ * Sanity checks.
+ */
+ if (!GOOD_MAJOR(major)) {
+ msg_fn("%s: bad major version: %ld", myname, major);
+ return (-1);
+ }
+ if (!GOOD_MINOR(minor)) {
+ msg_fn("%s: bad minor version: %ld", myname, minor);
+ return (-1);
+ }
+ if (!GOOD_PATCH(patch)) {
+ msg_fn("%s: bad patch level: %ld", myname, patch);
+ return (-1);
+ }
+
+ /*
+ * Conversion.
+ */
+ return (ENCODE_MAJOR(major) | ENCODE_MINOR(minor) | ENCODE_PATCH(patch));
+}
+
+/* compat_level_to_string - pretty-print a compatibility level */
+
+const char *compat_level_to_string(long compat_level,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ const char myname[] = "compat_level_to_string";
+ static VSTRING *buf;
+ long major;
+ long minor;
+ long patch;
+
+ /*
+ * Sanity check.
+ */
+ if (compat_level < 0) {
+ msg_fn("%s: bad compatibility level: %ld", myname, compat_level);
+ return (0);
+ }
+
+ /*
+ * Compatibility levels 0..2 have no minor or patch level.
+ */
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ major = DECODE_MAJOR(compat_level);
+ if (!GOOD_MAJOR(major)) {
+ msg_fn("%s: bad compatibility major level: %ld", myname, compat_level);
+ return (0);
+ }
+ vstring_sprintf(buf, "%ld", major);
+ if (major > 2) {
+
+ /*
+ * Expect that major.minor will be common.
+ */
+ minor = DECODE_MINOR(compat_level);
+ vstring_sprintf_append(buf, ".%ld", minor);
+
+ /*
+ * Expect that major.minor.patch will be rare.
+ */
+ patch = DECODE_PATCH(compat_level);
+ if (patch)
+ vstring_sprintf_append(buf, ".%ld", patch);
+ }
+ return (vstring_str(buf));
+}
+
+/* compat_relop_eval - mac_expand callback */
+
+static MAC_EXP_OP_RES compat_relop_eval(const char *left_str, int relop,
+ const char *rite_str)
+{
+ const char myname[] = "compat_relop_eval";
+ long left_val, rite_val;
+
+ /*
+ * Negative result means error.
+ */
+ if ((left_val = compat_level_from_string(left_str, msg_warn)) < 0
+ || (rite_val = compat_level_from_string(rite_str, msg_warn)) < 0)
+ return (MAC_EXP_OP_RES_ERROR);
+
+ /*
+ * Valid result. The difference between non-negative numbers will no
+ * overflow.
+ */
+ long delta = left_val - rite_val;
+
+ switch (relop) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, relop);
+ }
+}
+
+/* compat_level_register - register comparison operators */
+
+void compat_level_relop_register(void)
+{
+ int compat_level_relops[] = {
+ MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
+ 0,
+ };
+
+ mac_expand_add_relop(compat_level_relops, "level", compat_relop_eval);
+}
+
+#ifdef TEST
+#include <unistd.h>
+
+#include <htable.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static const char *lookup(const char *name, int unused_mode, void *context)
+{
+ HTABLE *table = (HTABLE *) context;
+
+ return (htable_find(table, name));
+}
+
+static void test_expand(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *result = vstring_alloc(100);
+ char *cp;
+ char *name;
+ char *value;
+ HTABLE *table;
+ int stat;
+
+ /*
+ * Add relops that compare string lengths instead of content.
+ */
+ compat_level_relop_register();
+
+ /*
+ * Loop over the inputs.
+ */
+ while (!vstream_feof(VSTREAM_IN)) {
+
+ table = htable_create(0);
+
+ /*
+ * Read a block of definitions, terminated with an empty line.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ name = mystrtok(&cp, CHARS_SPACE "=");
+ value = mystrtok(&cp, CHARS_SPACE "=");
+ htable_enter(table, name, value ? mystrdup(value) : 0);
+ }
+
+ /*
+ * Read a block of patterns, terminated with an empty line or EOF.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ VSTRING_RESET(result);
+ stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
+ (char *) 0, lookup, (void *) table);
+ vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ htable_free(table, myfree);
+ vstream_printf("\n");
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+ vstring_free(result);
+}
+
+static void test_convert(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ long compat_level;
+ const char *as_string;
+
+ /*
+ * Read compatibility level.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ if ((compat_level = compat_level_from_string(vstring_str(buf),
+ msg_warn)) < 0)
+ continue;
+ msg_info("%s -> 0x%lx", vstring_str(buf), compat_level);
+ if ((as_string = compat_level_to_string(compat_level,
+ msg_warn)) == 0)
+ continue;
+ msg_info("0x%lx->%s", compat_level, as_string);
+ }
+ vstring_free(buf);
+}
+
+static NORETURN usage(char **argv)
+{
+ msg_fatal("usage: %s option\n-c (convert)\n-c (expand)", argv[0]);
+}
+
+int main(int argc, char **argv)
+{
+ int ch;
+ int mode = 0;
+
+#define MODE_EXPAND (1<<0)
+#define MODE_CONVERT (1<<1)
+
+ while ((ch = GETOPT(argc, argv, "cx")) > 0) {
+ switch (ch) {
+ case 'c':
+ mode |= MODE_CONVERT;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'x':
+ mode |= MODE_EXPAND;
+ break;
+ default:
+ usage(argv);
+ }
+ }
+ switch (mode) {
+ case MODE_CONVERT:
+ test_convert();
+ break;
+ case MODE_EXPAND:
+ test_expand();
+ break;
+ default:
+ usage(argv);
+ }
+ exit(0);
+}
+
+#endif
--- /dev/null
+#ifndef _COMPAT_LEVEL_H_INCLUDED_
+#define _COMPAT_LEVEL_H_INCLUDED_
+
+/*++
+/* NAME
+/* compat_level 3h
+/* SUMMARY
+/* compatibility_level support
+/* SYNOPSIS
+/* #include <compat_level.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void compat_level_relop_register(void);
+extern long compat_level_from_string(const char *,
+ void PRINTFLIKE(1, 2) (*) (const char *,...));
+extern long compat_level_from_numbers(long, long, long,
+ void PRINTFLIKE(1, 2) (*) (const char *,...));
+extern const char *compat_level_to_string(long,
+ void PRINTFLIKE(1, 2) (*) (const char *,...));
+
+#define compat_level_from_major(major, msg_fn) \
+ compat_level_from_major_minor((major), 0, (msg_fn))
+#define compat_level_from_major_minor(major, minor, msg_fn) \
+ compat_level_from_numbers((major), (minor), 0, (msg_fn))
+
+#
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
--- /dev/null
+3.2.1
+3.2.1.0
+3a.2.1
+a3.2.1
+3.a2.1
+3.2a.1
+3.2a.a1
+3.2a.1a
+3
+3.
+3.2.
+3.1.1.
+2
+1
+0
+3.1023
+3.1024
+3.2.1023
+3.2.1024
+-3
+3.-2.1
+3.2.-1
--- /dev/null
+unknown: 3.2.1 -> 0x300801
+unknown: 0x300801->3.2.1
+unknown: warning: malformed compatibility level syntax: "3.2.1.0"
+unknown: warning: malformed compatibility level syntax: "3a.2.1"
+unknown: warning: malformed compatibility level syntax: "a3.2.1"
+unknown: warning: malformed compatibility level syntax: "3.a2.1"
+unknown: warning: malformed compatibility level syntax: "3.2a.1"
+unknown: warning: malformed compatibility level syntax: "3.2a.a1"
+unknown: warning: malformed compatibility level syntax: "3.2a.1a"
+unknown: 3 -> 0x300000
+unknown: 0x300000->3.0
+unknown: warning: malformed compatibility level syntax: "3."
+unknown: warning: malformed compatibility level syntax: "3.2."
+unknown: warning: malformed compatibility level syntax: "3.1.1."
+unknown: 2 -> 0x200000
+unknown: 0x200000->2
+unknown: 1 -> 0x100000
+unknown: 0x100000->1
+unknown: 0 -> 0x0
+unknown: 0x0->0
+unknown: 3.1023 -> 0x3ffc00
+unknown: 0x3ffc00->3.1023
+unknown: warning: malformed compatibility level syntax: "3.1024"
+unknown: 3.2.1023 -> 0x300bff
+unknown: 0x300bff->3.2.1023
+unknown: warning: malformed compatibility level syntax: "3.2.1024"
+unknown: warning: malformed compatibility level syntax: "-3"
+unknown: warning: malformed compatibility level syntax: "3.-2.1"
+unknown: warning: malformed compatibility level syntax: "3.2.-1"
--- /dev/null
+compatibility_level = 3
+
+${ {$compatibility_level} ==level {3.0} ? {good} : {bad} }
+${ {$compatibility_level} !=level {3.0} ? {bad} : {good} }
+${ {$compatibility_level} ==level {3.10} ? {bad} : {good} }
+${ {$compatibility_level} !=level {3.10} ? {good} : {bad} }
+${ {$compatibility_level} <level {4} ? {good} : {bad} }
+${ {$compatibility_level} <=level {4} ? {good} : {bad} }
+${ {$compatibility_level} <level {2} ? {bad} : {good} }
+${ {$compatibility_level} <=level {2} ? {bad} : {good} }
+${ {$compatibility_level} <=level {3.0} ? {good} : {bad} }
+${ {$compatibility_level} >level {4} ? {bad} : {good} }
+${ {$compatibility_level} >=level {4} ? {bad} : {good} }
+${ {$compatibility_level} >level {2} ? {good} : {bad} }
+${ {$compatibility_level} >=level {2} ? {good} : {bad} }
+${ {$compatibility_level} >=level {3.0} ? {good} : {bad} }
+${ {$compatibility_level} >=level {3A} ? {error} : {error} }
+${ {$compatibility_level} >=level {3.} ? {error} : {error} }
+${ {$compatibility_level} >=level {.1} ? {error} : {error} }
+${ {3} > {3.2} ? {bad} : {good} }
+${ {3} >level {3.2} ? {bad} : {good} }
+${ {3} < {3.2} ? {good} : {bad} }
+${ {3} <level {3.2} ? {good} : {bad} }
+${ {3.10} > {3.2} ? {bad} : {good} }
+${ {3.10} >level {3.2} ? {good} : {bad} }
+${ {3.10} < {3.2} ? {good} : {bad} }
+${ {3.10} <level {3.2} ? {bad} : {good} }
--- /dev/null
+<< compatibility_level = 3
+<<
+<< ${ {$compatibility_level} ==level {3.0} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} !=level {3.0} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} ==level {3.10} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} !=level {3.10} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} <level {4} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} <=level {4} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} <level {2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} <=level {2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} <=level {3.0} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >level {4} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {4} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} >level {2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {3.0} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {3A} ? {error} : {error} }
+unknown: warning: malformed compatibility level syntax: "3A"
+stat=1 result=
+<< ${ {$compatibility_level} >=level {3.} ? {error} : {error} }
+unknown: warning: malformed compatibility level syntax: "3."
+stat=1 result=
+<< ${ {$compatibility_level} >=level {.1} ? {error} : {error} }
+unknown: warning: malformed compatibility level syntax: ".1"
+stat=1 result=
+<< ${ {3} > {3.2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {3} >level {3.2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {3} < {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3} <level {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3.10} > {3.2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {3.10} >level {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3.10} < {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3.10} <level {3.2} ? {bad} : {good} }
+stat=0 result=good
/* int var_strict_smtputf8;
/* char *var_smtputf8_autoclass;
/* int var_idna2003_compat;
-/* int var_compat_level;
+/* char *var_compatibility_level;
/* char *var_drop_hdrs;
/* char *var_info_log_addr_form;
/* bool var_enable_orcpt;
/*
/* const char null_format_string[1];
/*
+/* long compatibility_level;
+/*
/* int warn_compat_break_app_dot_mydomain;
/* int warn_compat_break_smtputf8_enable;
/* int warn_compat_break_chroot;
#include <verp_sender.h>
#include <own_inet_addr.h>
#include <mail_params.h>
+#include <compat_level.h>
/*
* Special configuration variables.
int var_strict_smtputf8;
char *var_smtputf8_autoclass;
int var_idna2003_compat;
-int var_compat_level;
+char *var_compatibility_level;
char *var_drop_hdrs;
char *var_info_log_addr_form;
bool var_enable_orcpt;
int warn_compat_break_chroot;
int warn_compat_break_relay_restrictions;
+ /*
+ * Parsed from var_compatibility_level;
+ */
+long compat_level;
+
/* check_myhostname - lookup hostname and validate */
static const char *check_myhostname(void)
/*
* Look for specific parameters whose default changed when the
- * compatibility level changed to 3.
+ * compatibility level changed to 3.6.
*/
- if (var_compat_level < 3) {
+ if (compat_level < compat_level_from_string(COMPAT_LEVEL_3_6, msg_panic)) {
if (mail_conf_lookup(VAR_SMTPD_TLS_FPT_DGST) == 0)
warn_compat_break_smtpd_tls_fpt_dgst = 1;
if (mail_conf_lookup(VAR_SMTP_TLS_FPT_DGST) == 0)
* Look for specific parameters whose default changed when the
* compatibility level changed to 2.
*/
- if (var_compat_level < 2) {
+ if (compat_level < compat_level_from_string(COMPAT_LEVEL_2, msg_panic)) {
if (mail_conf_lookup(VAR_RELAY_DOMAINS) == 0) {
warn_compat_break_relay_domains = 1;
if (mail_conf_lookup(VAR_FFLUSH_DOMAINS) == 0)
* Look for specific parameters whose default changed when the
* compatibility level changed from 0 to 1.
*/
- if (var_compat_level < 1) {
+ if (compat_level < compat_level_from_string(COMPAT_LEVEL_1, msg_panic)) {
if (mail_conf_lookup(VAR_APP_DOT_MYDOMAIN) == 0)
warn_compat_break_app_dot_mydomain = 1;
void mail_params_init()
{
- static const CONFIG_INT_TABLE first_int_defaults[] = {
- VAR_COMPAT_LEVEL, DEF_COMPAT_LEVEL, &var_compat_level, 0, 0,
+ static const CONFIG_STR_TABLE compat_level_defaults[] = {
+ VAR_COMPAT_LEVEL, DEF_COMPAT_LEVEL, &var_compatibility_level, 0, 0,
0,
};
static const CONFIG_STR_TABLE first_str_defaults[] = {
* Extract compatibility level first, so that we can determine what
* parameters of interest are left at their legacy defaults.
*/
- get_mail_conf_int_table(first_int_defaults);
+ compat_level_relop_register();
+ get_mail_conf_str_table(compat_level_defaults);
+ compat_level = compat_level_from_string(var_compatibility_level, msg_fatal);
check_legacy_defaults();
/*
/*
* Compatibility level and migration support. Update postconf(5),
- * COMPATIBILITY_README, and conf/main.cf when updating the current
- * compatibility level.
+ * COMPATIBILITY_README, global/mail_params.[hc] and conf/main.cf when
+ * updating the current compatibility level.
*/
+#define COMPAT_LEVEL_0 "0"
+#define COMPAT_LEVEL_1 "1"
+#define COMPAT_LEVEL_2 "2"
+#define COMPAT_LEVEL_3_6 "3.6"
+#define LAST_COMPAT_LEVEL COMPAT_LEVEL_3_6
+
#define VAR_COMPAT_LEVEL "compatibility_level"
-#define DEF_COMPAT_LEVEL 0
-#define CUR_COMPAT_LEVEL 3
-extern int var_compat_level;
+#define DEF_COMPAT_LEVEL COMPAT_LEVEL_0
+extern char *var_compatibility_level;
extern int warn_compat_break_app_dot_mydomain;
extern int warn_compat_break_smtputf8_enable;
extern int warn_compat_break_smtp_tls_fpt_dgst;
extern int warn_compat_break_lmtp_tls_fpt_dgst;
+extern long compat_level;
+
/*
* What problem classes should be reported to the postmaster via email.
* Default is bad problems only. See mail_error(3). Even when mail notices
extern bool var_append_at_myorigin;
#define VAR_APP_DOT_MYDOMAIN "append_dot_mydomain"
-#define DEF_APP_DOT_MYDOMAIN "${{$compatibility_level} < {1} ? " \
+#define DEF_APP_DOT_MYDOMAIN "${{$compatibility_level} <level {1} ? " \
"{yes} : {no}}"
extern bool var_append_dot_mydomain;
extern char *var_smtpd_tls_mand_excl;
#define VAR_SMTPD_TLS_FPT_DGST "smtpd_tls_fingerprint_digest"
-#define DEF_SMTPD_TLS_FPT_DGST "${{$compatibility_level} < {3} ? " \
+#define DEF_SMTPD_TLS_FPT_DGST "${{$compatibility_level} <level {3.6} ? " \
"{md5} : {sha256}}"
extern char *var_smtpd_tls_fpt_dgst;
extern char *var_smtp_tls_mand_excl;
#define VAR_SMTP_TLS_FPT_DGST "smtp_tls_fingerprint_digest"
-#define DEF_SMTP_TLS_FPT_DGST "${{$compatibility_level} < {3} ? " \
+#define DEF_SMTP_TLS_FPT_DGST "${{$compatibility_level} <level {3.6} ? " \
"{md5} : {sha256}}"
#define VAR_LMTP_TLS_FPT_DGST "lmtp_tls_fingerprint_digest"
-#define DEF_LMTP_TLS_FPT_DGST "${{$compatibility_level} < {3} ? " \
+#define DEF_LMTP_TLS_FPT_DGST "${{$compatibility_level} <level {3.6} ? " \
"{md5} : {sha256}}"
extern char *var_smtp_tls_fpt_dgst;
extern char *var_mynetworks;
#define VAR_MYNETWORKS_STYLE "mynetworks_style"
-#define DEF_MYNETWORKS_STYLE "${{$compatibility_level} < {2} ? " \
+#define DEF_MYNETWORKS_STYLE "${{$compatibility_level} <level {2} ? " \
"{" MYNETWORKS_STYLE_SUBNET "} : " \
"{" MYNETWORKS_STYLE_HOST "}}"
extern char *var_mynetworks_style;
#define MYNETWORKS_STYLE_HOST "host"
#define VAR_RELAY_DOMAINS "relay_domains"
-#define DEF_RELAY_DOMAINS "${{$compatibility_level} < {2} ? " \
+#define DEF_RELAY_DOMAINS "${{$compatibility_level} <level {2} ? " \
"{$mydestination} : {}}"
extern char *var_relay_domains;
extern char *var_mail_checks;
#define VAR_RELAY_CHECKS "smtpd_relay_restrictions"
-#define DEF_RELAY_CHECKS "${{$compatibility_level} < {1} ? " \
+#define DEF_RELAY_CHECKS "${{$compatibility_level} <level {1} ? " \
"{} : {" PERMIT_MYNETWORKS ", " \
PERMIT_SASL_AUTH ", " \
DEFER_UNAUTH_DEST "}}"
*/
#define VAR_SMTPUTF8_ENABLE "smtputf8_enable"
#ifndef DEF_SMTPUTF8_ENABLE
-#define DEF_SMTPUTF8_ENABLE "${{$compatibility_level} < {1} ? " \
+#define DEF_SMTPUTF8_ENABLE "${{$compatibility_level} <level {1} ? " \
"{no} : {yes}}"
#endif
extern int var_smtputf8_enable;
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20201212"
+#define MAIL_RELEASE_DATE "20210109"
#define MAIL_VERSION_NUMBER "3.6"
#ifdef SNAPSHOT
master_ent.o: ../../include/argv.h
master_ent.o: ../../include/attr.h
master_ent.o: ../../include/check_arg.h
+master_ent.o: ../../include/compat_level.h
master_ent.o: ../../include/host_port.h
master_ent.o: ../../include/htable.h
master_ent.o: ../../include/inet_addr_host.h
#include <own_inet_addr.h>
#include <wildcard_inet_addr.h>
#include <mail_conf.h>
+#include <compat_level.h>
/* Local stuff. */
* XXX Chroot cannot imply unprivileged service (for example, the pickup
* service runs chrooted but needs privileges to open files as the user).
*/
- chroot = get_bool_ent(&bufp, "chroot", var_compat_level < 1 ? "y" : "n");
+ chroot = get_bool_ent(&bufp, "chroot", compat_level
+ < compat_level_from_string(COMPAT_LEVEL_1, msg_panic) ?
+ "y" : "n");
/*
* Wakeup timer. XXX should we require that var_proc_limit == 1? Right
/* The output consists of two files, named \fIfile_name\fB.pag\fR and
/* \fIfile_name\fB.dir\fR.
/* This is available on systems with support for \fBdbm\fR databases.
-/* .IP \fBhash\fR
-/* The output is a hashed file, named \fIfile_name\fB.db\fR.
-/* This is available on systems with support for \fBdb\fR databases.
/* .IP \fBfail\fR
/* A table that reliably fails all requests. The lookup table
/* name is used for logging only. This table exists to simplify
/* Postfix error tests.
+/* .IP \fBhash\fR
+/* The output is a hashed file, named \fIfile_name\fB.db\fR.
+/* This is available on systems with support for \fBdb\fR databases.
+/* .IP \fBlmdb\fR
+/* The output is a btree-based file, named \fIfile_name\fB.lmdb\fR.
+/* \fBlmdb\fR supports concurrent writes and reads from different
+/* processes, unlike other supported file-based tables.
+/* This is available on systems with support for \fBlmdb\fR databases.
/* .IP \fBsdbm\fR
/* The output consists of two files, named \fIfile_name\fB.pag\fR and
/* \fIfile_name\fB.dir\fR.
/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
/* A prefix that is prepended to the process name in syslog
/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix 2.11 and later:
+/* .IP "\fBlmdb_map_size (16777216)\fR"
+/* The initial OpenLDAP LMDB database size limit in bytes.
/* STANDARDS
/* RFC 822 (ARPA Internet Text Messages)
/* SEE ALSO
# do not edit below this line - it is generated by 'make depend'
postconf.o: ../../include/argv.h
postconf.o: ../../include/check_arg.h
+postconf.o: ../../include/compat_level.h
postconf.o: ../../include/dict.h
postconf.o: ../../include/htable.h
postconf.o: ../../include/mail_conf.h
#include <mail_version.h>
#include <mail_run.h>
#include <mail_dict.h>
+#include <compat_level.h>
/* Application-specific. */
}
}
+ /*
+ * For consistency with mail_params_init().
+ */
+ compat_level_relop_register();
+
/*
* We don't enforce import_environment consistency in this program.
*
postfix.o: ../../include/argv.h
postfix.o: ../../include/check_arg.h
postfix.o: ../../include/clean_env.h
+postfix.o: ../../include/compat_level.h
postfix.o: ../../include/mail_conf.h
postfix.o: ../../include/mail_params.h
postfix.o: ../../include/mail_parm_split.h
#include <mail_version.h>
#include <mail_parm_split.h>
#include <maillog_client.h>
+#include <compat_level.h>
/* Additional installation parameters. */
* Alert the sysadmin that the backwards-compatible settings are still in
* effect.
*/
- if (var_compat_level < CUR_COMPAT_LEVEL) {
+ if (compat_level < compat_level_from_string(LAST_COMPAT_LEVEL, msg_panic)) {
msg_info("Postfix is running with backwards-compatible default "
"settings");
msg_info("See http://www.postfix.org/COMPATIBILITY_README.html "
"for details");
msg_info("To disable backwards compatibility use \"postconf "
- VAR_COMPAT_LEVEL "=%d\" and \"postfix reload\"",
- CUR_COMPAT_LEVEL);
+ VAR_COMPAT_LEVEL "=%s\" and \"postfix reload\"",
+ LAST_COMPAT_LEVEL);
}
check_setenv("PATH", ROOT_PATH); /* sys_defs.h */
check_setenv(CONF_ENV_PATH, var_config_dir);/* mail_conf.h */
/* The output consists of two files, named \fIfile_name\fB.pag\fR and
/* \fIfile_name\fB.dir\fR.
/* This is available on systems with support for \fBdbm\fR databases.
-/* .IP \fBhash\fR
-/* The output file is a hashed file, named \fIfile_name\fB.db\fR.
-/* This is available on systems with support for \fBdb\fR databases.
/* .IP \fBfail\fR
/* A table that reliably fails all requests. The lookup table
/* name is used for logging only. This table exists to simplify
/* Postfix error tests.
+/* .IP \fBhash\fR
+/* The output file is a hashed file, named \fIfile_name\fB.db\fR.
+/* This is available on systems with support for \fBdb\fR databases.
+/* .IP \fBlmdb\fR
+/* The output is a btree-based file, named \fIfile_name\fB.lmdb\fR.
+/* \fBlmdb\fR supports concurrent writes and reads from different
+/* processes, unlike other supported file-based tables.
+/* This is available on systems with support for \fBlmdb\fR databases.
/* .IP \fBsdbm\fR
/* The output consists of two files, named \fIfile_name\fB.pag\fR and
/* \fIfile_name\fB.dir\fR.
/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
/* A prefix that is prepended to the process name in syslog
/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix 2.11 and later:
+/* .IP "\fBlmdb_map_size (16777216)\fR"
+/* The initial OpenLDAP LMDB database size limit in bytes.
/* SEE ALSO
/* postalias(1), create/update/query alias database
/* postconf(1), supported database types
load_file.o: vstream.h
load_file.o: warn_stat.h
load_lib.o: load_lib.c
+load_lib.o: load_lib.h
+load_lib.o: msg.h
load_lib.o: sys_defs.h
logwriter.o: check_arg.h
logwriter.o: iostuff.h
lstat_as.o: sys_defs.h
lstat_as.o: warn_stat.h
mac_expand.o: check_arg.h
+mac_expand.o: htable.h
mac_expand.o: mac_expand.c
mac_expand.o: mac_expand.h
mac_expand.o: mac_parse.h
/*
/* int alldig(string)
/* const char *string;
+/*
+/* int allalnum(string)
+/* const char *string;
/* DESCRIPTION
/* alldig() determines if its argument is an all-numerical string.
+/*
+/* allalnum() determines if its argument is an all-alphanumerical
+/* string.
/* SEE ALSO
/* An alldig() routine appears in Brian W. Kernighan, P.J. Plauger:
/* "Software Tools", Addison-Wesley 1976.
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
/*--*/
/* System library. */
return (0);
return (1);
}
+
+/* allalnum - return true if string is all alphanum */
+
+int allalnum(const char *string)
+{
+ const char *cp;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; *cp != 0; cp++)
+ if (!ISALNUM(*cp))
+ return (0);
+ return (1);
+}
/* const char *filter;
/* const char *lookup(const char *key, int mode, void *context)
/* void *context;
+/* AUXILIARY FUNCTIONS
+/* typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (
+/* const char *left,
+/* int tok_val,
+/* const char *rite)
+/*
+/* void mac_expand_add_relop(
+/* int *tok_list,
+/* const char *suffix,
+/* MAC_EXPAND_RELOP_FN relop_eval)
+/*
+/* MAC_EXP_OP_RES mac_exp_op_res_bool[2];
/* DESCRIPTION
/* This module implements parameter-less named attribute
/* expansions, both conditional and unconditional. As of Postfix
/* result value means that the requested attribute was not defined.
/* .IP context
/* Caller context that is passed on to the attribute lookup routine.
+/* .PP
+/* mac_expand_add_relop() registers a function that implements
+/* support for custom relational operators. Custom operator names
+/* such as "==xxx" have two parts: a prefix that is identical to
+/* a built-in operator such as "==", and an application-specified
+/* suffix such as "xxx".
+/*
+/* Arguments:
+/* .IP tok_list
+/* A null-terminated list of MAC_EXP_OP_TOK_* values that support
+/* the custom operator suffix.
+/* .IP suffix
+/* A null-terminated alphanumeric string that specifies the custom
+/* operator suffix.
+/* .IP relop_eval
+/* A function that compares two strings according to the
+/* MAC_EXP_OP_TOK_* value specified with the tok_val argument,
+/* and that returns non-zero if the custom operator evaluates to
+/* true, zero otherwise.
+/*
+/* mac_exp_op_res_bool provides an array that converts a boolean
+/* value (0 or 1) to the corresponding MAX_EXP_OP_RES_TRUE or
+/* MAX_EXP_OP_RES_FALSE value.
/* DIAGNOSTICS
/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable
/* recursion depth.
/* Utility library. */
#include <msg.h>
+#include <htable.h>
#include <vstring.h>
#include <mymalloc.h>
#include <stringops.h>
#include <mac_parse.h>
#include <mac_expand.h>
+ /*
+ * Simplifies the return of common relational operator results.
+ */
+MAC_EXP_OP_RES mac_exp_op_res_bool[2] = {
+ MAC_EXP_OP_RES_FALSE,
+ MAC_EXP_OP_RES_TRUE
+};
+
/*
* Little helper structure.
*/
#define MAC_EXP_BVAL_FALSE ""
/*
- * Relational operators.
+ * Relational operators. The MAC_EXP_OP_TOK_* are defined in the header
+ * file.
*/
#define MAC_EXP_OP_STR_EQ "=="
#define MAC_EXP_OP_STR_NE "!="
"\" or \"" MAC_EXP_OP_STR_GE "\"" \
"\" or \"" MAC_EXP_OP_STR_GT "\""
-#define MAC_EXP_OP_TOK_NONE 0
-#define MAC_EXP_OP_TOK_EQ 1
-#define MAC_EXP_OP_TOK_NE 2
-#define MAC_EXP_OP_TOK_LT 3
-#define MAC_EXP_OP_TOK_LE 4
-#define MAC_EXP_OP_TOK_GE 5
-#define MAC_EXP_OP_TOK_GT 6
-
static const NAME_CODE mac_exp_op_table[] =
{
MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ,
*/
#define MAC_EXP_WHITESPACE CHARS_SPACE
+ /*
+ * Support for operator extensions.
+ */
+static HTABLE *mac_exp_ext_table;
+static VSTRING *mac_exp_ext_key;
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
/* atol_or_die - convert or die */
static long atol_or_die(const char *strval)
/* mac_exp_eval - evaluate binary expression */
-static int mac_exp_eval(const char *left, int tok_val,
- const char *rite)
+static MAC_EXP_OP_RES mac_exp_eval(const char *left, int tok_val,
+ const char *rite)
{
static const char myname[] = "mac_exp_eval";
long delta;
}
switch (tok_val) {
case MAC_EXP_OP_TOK_EQ:
- return (delta == 0);
+ return (mac_exp_op_res_bool[delta == 0]);
case MAC_EXP_OP_TOK_NE:
- return (delta != 0);
+ return (mac_exp_op_res_bool[delta != 0]);
case MAC_EXP_OP_TOK_LT:
- return (delta < 0);
+ return (mac_exp_op_res_bool[delta < 0]);
case MAC_EXP_OP_TOK_LE:
- return (delta <= 0);
+ return (mac_exp_op_res_bool[delta <= 0]);
case MAC_EXP_OP_TOK_GE:
- return (delta >= 0);
+ return (mac_exp_op_res_bool[delta >= 0]);
case MAC_EXP_OP_TOK_GT:
- return (delta > 0);
+ return (mac_exp_op_res_bool[delta > 0]);
default:
msg_panic("%s: unknown operator: %d",
myname, tok_val);
int op_tokval;
int op_result;
size_t tmp_len;
+ char *type_pos;
+ size_t type_len;
+ MAC_EXPAND_RELOP_FN relop_eval;
/*
* Left operand. The caller is expected to skip leading whitespace before
MAC_EXP_OP_STR_ANY, left_op_strval, cp);
cp += op_len;
+ /*
+ * Custom operator suffix.
+ */
+ if (mac_exp_ext_table && ISALNUM(*cp)) {
+ type_pos = cp;
+ for (type_len = 1; ISALNUM(cp[type_len]); type_len++)
+ /* void */ ;
+ cp += type_len;
+ vstring_sprintf(mac_exp_ext_key, "%.*s",
+ (int) (op_len + type_len), op_pos);
+ if ((relop_eval = (MAC_EXPAND_RELOP_FN) htable_find(mac_exp_ext_table,
+ STR(mac_exp_ext_key))) == 0)
+ MAC_EXP_ERR_RETURN(mc, "bad operator suffix at: \"...%.*s>>>%.*s\"",
+ (int) op_len, op_pos, (int) type_len, type_pos);
+ } else {
+ relop_eval = mac_exp_eval;
+ }
+
/*
* Right operand. Todo: syntax may depend on operator.
*/
mc->status |=
mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval,
mc->flags, mc->filter, mc->lookup, mc->context);
- op_result = mac_exp_eval(vstring_str(left_op_buf), op_tokval,
- vstring_str(rite_op_buf));
+ if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0
+ && (op_result = relop_eval(vstring_str(left_op_buf), op_tokval,
+ vstring_str(rite_op_buf))) == MAC_EXP_OP_RES_ERROR)
+ mc->status |= MAC_PARSE_ERROR;
vstring_free(left_op_buf);
vstring_free(rite_op_buf);
if (mc->status & MAC_PARSE_ERROR)
* for compatibility with the historical code that looks named parameter
* values.
*/
- *lookup = (op_result ? MAC_EXP_BVAL_TRUE : MAC_EXP_BVAL_FALSE);
+ if (mc->flags & MAC_EXP_FLAG_SCAN) {
+ *lookup = 0;
+ } else {
+ switch (op_result) {
+ case MAC_EXP_OP_RES_TRUE:
+ *lookup = MAC_EXP_BVAL_TRUE;
+ break;
+ case MAC_EXP_OP_RES_FALSE:
+ *lookup = MAC_EXP_BVAL_FALSE;
+ break;
+ default:
+ msg_panic("mac_expand: unexpected operator result: %d", op_result);
+ }
+ }
*bp = cp;
return (0);
}
+/* mac_expand_add_relop - register operator extensions */
+
+void mac_expand_add_relop(int *tok_list, const char *suffix,
+ MAC_EXPAND_RELOP_FN relop_eval)
+{
+ const char myname[] = "mac_expand_add_relop";
+ const char *tok_name;
+ int *tp;
+
+ /*
+ * Sanity checks.
+ */
+ if (!allalnum(suffix))
+ msg_panic("%s: bad operator suffix: %s", myname, suffix);
+
+ /*
+ * One-time initialization.
+ */
+ if (mac_exp_ext_table == 0) {
+ mac_exp_ext_table = htable_create(10);
+ mac_exp_ext_key = vstring_alloc(10);
+ }
+ for (tp = tok_list; *tp; tp++) {
+ if ((tok_name = str_name_code(mac_exp_op_table, *tp)) == 0)
+ msg_panic("%s: unknown token code: %d", myname, *tp);
+ vstring_sprintf(mac_exp_ext_key, "%s%s", tok_name, suffix);
+ if (htable_locate(mac_exp_ext_table, STR(mac_exp_ext_key)) != 0)
+ msg_panic("%s: duplicate key: %s", myname, STR(mac_exp_ext_key));
+ (void) htable_enter(mac_exp_ext_table,
+ STR(mac_exp_ext_key), (void *) relop_eval);
+ }
+}
+
/* mac_expand_callback - callback for mac_parse */
static int mac_expand_callback(int type, VSTRING *buf, void *ptr)
/*
* This code certainly deserves a stand-alone test program.
*/
-#include <stdlib.h>
#include <stringops.h>
#include <htable.h>
#include <vstream.h>
return (htable_find(table, name));
}
-int main(int unused_argc, char **unused_argv)
+static MAC_EXP_OP_RES length_relop_eval(const char *left, int relop,
+ const char *rite)
+{
+ const char myname[] = "length_relop_eval";
+ ssize_t delta = strlen(left) - strlen(rite);
+
+ switch (relop) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, relop);
+ }
+}
+
+int main(int unused_argc, char **argv)
{
VSTRING *buf = vstring_alloc(100);
VSTRING *result = vstring_alloc(100);
char *value;
HTABLE *table;
int stat;
+ int length_relops[] = {
+ MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
+ 0,
+ };
+ /*
+ * Add relops that compare string lengths instead of content.
+ */
+ mac_expand_add_relop(length_relops, "length", length_relop_eval);
+
+ /*
+ * Loop over the inputs.
+ */
while (!vstream_feof(VSTREAM_IN)) {
table = htable_create(0);
#define MAC_EXP_FLAG_SCAN (1<<2)
#define MAC_EXP_FLAG_PRINTABLE (1<<3)
+ /*
+ * Token codes, public so tha they are available to mac_expand_add_relop()
+ */
+#define MAC_EXP_OP_TOK_NONE 0 /* Sentinel */
+#define MAC_EXP_OP_TOK_EQ 1 /* == */
+#define MAC_EXP_OP_TOK_NE 2 /* != */
+#define MAC_EXP_OP_TOK_LT 3 /* < */
+#define MAC_EXP_OP_TOK_LE 4 /* <= */
+#define MAC_EXP_OP_TOK_GE 5 /* >= */
+#define MAC_EXP_OP_TOK_GT 6 /* > */
+
+ /*
+ * Relational operator results. An enum to discourage asuming that 0 is
+ * false, !0 is true.
+ */
+typedef enum MAC_EXP_OP_RES {
+ MAC_EXP_OP_RES_TRUE,
+ MAC_EXP_OP_RES_FALSE,
+ MAC_EXP_OP_RES_ERROR,
+} MAC_EXP_OP_RES;
+
+
+extern MAC_EXP_OP_RES mac_exp_op_res_bool[2];
+
/*
* Real lookup or just a test?
*/
#define MAC_EXP_MODE_USE (1)
typedef const char *(*MAC_EXP_LOOKUP_FN) (const char *, int, void *);
+typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (const char *, int, const char *);
extern int mac_expand(VSTRING *, const char *, int, const char *, MAC_EXP_LOOKUP_FN, void *);
+void mac_expand_add_relop(int *, const char *, MAC_EXPAND_RELOP_FN);
/* LICENSE
/* .ad
${{a} <= {b}}
${{a} >= {b}}
${{a} > {b}}
+
+name1 = foo
+
+${{$name1} >=blah {bar}}
+${{aaa} == {bbb}}
+${{aaa} ==length {bbb}}
+${{aaa} <=length {bbb}}
+${{aaa} >=length {bbb}}
+${{aaa} != {bbb}}
+${{aaa} !=length {bbb}}
+${{aaa} > {bb}}
+${{aaa} >length {bb}}
+${{aaa} >= {bb}}
+${{aaa} >=length {bb}}
+${{aaa} < {bb}}
+${{aaa} <length {bb}}
+${{aaa} <= {bb}}
+${{aaa} <=length {bb}}
stat=0 result=
<< ${{a} > {b}}
stat=0 result=
+<<
+
+<< name1 = foo
+<<
+<< ${{$name1} >=blah {bar}}
+unknown: warning: bad operator suffix at: "...>=>>>blah"
+stat=1 result=
+<< ${{aaa} == {bbb}}
+stat=0 result=
+<< ${{aaa} ==length {bbb}}
+stat=0 result=true
+<< ${{aaa} <=length {bbb}}
+stat=0 result=true
+<< ${{aaa} >=length {bbb}}
+stat=0 result=true
+<< ${{aaa} != {bbb}}
+stat=0 result=true
+<< ${{aaa} !=length {bbb}}
+stat=0 result=
+<< ${{aaa} > {bb}}
+stat=0 result=
+<< ${{aaa} >length {bb}}
+stat=0 result=true
+<< ${{aaa} >= {bb}}
+stat=0 result=
+<< ${{aaa} >=length {bb}}
+stat=0 result=true
+<< ${{aaa} < {bb}}
+stat=0 result=true
+<< ${{aaa} <length {bb}}
+stat=0 result=
+<< ${{aaa} <= {bb}}
+stat=0 result=true
+<< ${{aaa} <=length {bb}}
+stat=0 result=
extern VSTRING *unescape(VSTRING *, const char *);
extern VSTRING *escape(VSTRING *, const char *, ssize_t);
extern int alldig(const char *);
+extern int allalnum(const char *);
extern int allprint(const char *);
extern int allspace(const char *);
extern int allascii_len(const char *, ssize_t);