Bugfix (introduced: 19970309): reset errno before calling
readdir(), in order to distinguish between end-of-directory and
an error condition. File: scandir,c,
+
+20150426
+
+ Cleanup: when transmitting an attribute-value sequence
+ between Postfix processes, a hash table may now appear at
+ any position instead of only at the end. Files:
+ util/attr_scan{0,64,plain}.c, util/attr_print{0,64,plain}.c,
+ util/attr_scan{0,64,plain}.ref.
+
+ Feature: milter_macro_defaults, an optional list of macro
+ name=value pairs that specify default values for Milter
+ macros. When a macro is to be sent to a Milter application,
+ Postfix will send its default value when no value is available
+ from the mail delivery context. For example, with
+ "milter_macro_defaults = auth_type=TLS", Postfix will send
+ an auth_type of "TLS" unless a remote client authenticates
+ with SASL. Files: mantools/postlink, proto/MILTER_README.html,
+ proto/postconf.proto, cleanup/cleanup.c, cleanup/cleanup_init.c,
+ cleanup/cleanup_milter.c, global/mail_params.h, milter/milter.c,
+ milter/milter.h, smtpd/smtpd.c, smtpd/smtpd_milter.c.
+
+20150501
+
+ Support for Linux 4.*, and some simplification for future
+ makedefs files. Files: makedefs, util/sys_defs.h.
+
+20150502
+
+ Cleanup: updated the examples in MILTER_README. File:
+ proto/MILTER_README.html
The reason for adding Milter support to Postfix is that there exists a large
collection of applications, not only to block unwanted mail, but also to verify
-authenticity (examples: OpenDKIM, DomainKeys Identified Mail (DKIM),
-SenderID+SPF and DomainKeys) or to digitally sign mail (examples: OpenDKIM,
-DomainKeys Identified Mail (DKIM), DomainKeys). Having yet another Postfix-
-specific version of all that software is a poor use of human and system
-resources.
+authenticity (examples: OpenDKIM and DMARC) or to digitally sign mail (example:
+OpenDKIM). Having yet another Postfix-specific version of all that software is
+a poor use of human and system resources.
The Milter protocol has evolved over time, and different Postfix versions
implement different feature sets. See the workarounds and limitations sections
implements the Sendmail 8 Milter protocol. Postfix currently does not provide
such a library, but Sendmail does.
- * The first option is to use a pre-compiled library. Some systems install the
- Sendmail libmilter library by default. With other systems, libmilter may be
- provided by a package (called "sendmail-devel" on some Linux systems).
+Some systems install the Sendmail libmilter library by default. With other
+systems, libmilter may be provided by a package (called "sendmail-devel" on
+some Linux systems).
- Once libmilter is installed, applications such as OpenDKIM, dkim-milter and
- sid-milter build out of the box without requiring any tinkering:
+Once libmilter is installed, applications such as OpenDKIM and OpenDMARC build
+out of the box without requiring any tinkering:
- $ g\bgz\bzc\bca\bat\bt o\bop\bpe\ben\bnd\bdk\bki\bim\bm-\b-x\bx.\b.y\by.\b.z\bz.\b.t\bta\bar\br.\b.g\bgz\bz |\b| t\bta\bar\br x\bxf\bf -\b-
- $ c\bcd\bd o\bop\bpe\ben\bnd\bdk\bki\bim\bm-\b-x\bx.\b.y\by.\b.z\bz
- $ .\b./\b/c\bco\bon\bnf\bfi\big\bgu\bur\bre\be .\b..\b..\b.o\bop\bpt\bti\bio\bon\bns\bs.\b..\b..\b.
- $ m\bma\bak\bke\be
- [...lots of output omitted...]
- $ m\bma\bak\bke\be i\bin\bns\bst\bta\bal\bll\bl
-
- $ g\bgz\bzc\bca\bat\bt d\bdk\bki\bim\bm-\b-m\bmi\bil\blt\bte\ber\br-\b-x\bx.\b.y\by.\b.z\bz.\b.t\bta\bar\br.\b.g\bgz\bz |\b| t\bta\bar\br x\bxf\bf -\b-
- $ c\bcd\bd d\bdk\bki\bim\bm-\b-m\bmi\bil\blt\bte\ber\br-\b-x\bx.\b.y\by.\b.z\bz
- $ m\bma\bak\bke\be
- [...lots of output omitted...]
-
- * The other option is to build the libmilter library from Sendmail source
- code:
-
- $ g\bgz\bzc\bca\bat\bt s\bse\ben\bnd\bdm\bma\bai\bil\bl-\b-x\bx.\b.y\by.\b.z\bz.\b.t\bta\bar\br.\b.g\bgz\bz |\b| t\bta\bar\br x\bxf\bf -\b-
- $ c\bcd\bd s\bse\ben\bnd\bdm\bma\bai\bil\bl-\b-x\bx.\b.y\by.\b.z\bz/\b/l\bli\bib\bbm\bmi\bil\blt\bte\ber\br
- $ m\bma\bak\bke\be
- [...lots of output omitted...]
-
- After building your own libmilter library, follow the installation
- instructions in the Milter application source distribution to specify the
- location of the libmilter include files and object library. Typically,
- these settings are configured in a file named sid-filter/Makefile.m4 or
- similar:
-
- APPENDDEF(`confINCDIRS', `-I/some/where/sendmail-x.y.z/include')
- APPENDDEF(`confLIBDIRS', `-L/some/where/sendmail-x.y.z/obj.systemtype/
- libmilter')
-
- Then build the Milter application.
+ $ g\bgz\bzc\bca\bat\bt o\bop\bpe\ben\bnd\bdk\bki\bim\bm-\b-x\bx.\b.y\by.\b.z\bz.\b.t\bta\bar\br.\b.g\bgz\bz |\b| t\bta\bar\br x\bxf\bf -\b-
+ $ c\bcd\bd o\bop\bpe\ben\bnd\bdk\bki\bim\bm-\b-x\bx.\b.y\by.\b.z\bz
+ $ .\b./\b/c\bco\bon\bnf\bfi\big\bgu\bur\bre\be .\b..\b..\b.o\bop\bpt\bti\bio\bon\bns\bs.\b..\b..\b.
+ $ m\bma\bak\bke\be
+ [...lots of output omitted...]
+ $ m\bma\bak\bke\be i\bin\bns\bst\bta\bal\bll\bl
R\bRu\bun\bnn\bni\bin\bng\bg M\bMi\bil\blt\bte\ber\br a\bap\bpp\bpl\bli\bic\bca\bat\bti\bio\bon\bns\bs
To run a Milter application, see the documentation of the filter for options. A
typical command looks like this:
- # /\b/s\bso\bom\bme\be/\b/w\bwh\bhe\ber\bre\be/\b/d\bdk\bki\bim\bm-\b-f\bfi\bil\blt\bte\ber\br -\b-u\bu u\bus\bse\ber\bri\bid\bd -\b-p\bp i\bin\bne\bet\bt:\b:p\bpo\bor\brt\btn\bnu\bum\bmb\bbe\ber\br@\b@l\blo\boc\bca\bal\blh\bho\bos\bst\bt .\b..\b..\b.o\bot\bth\bhe\ber\br
+ # /\b/s\bso\bom\bme\be/\b/w\bwh\bhe\ber\bre\be/\b/o\bop\bpe\ben\bnd\bdk\bki\bim\bm -\b-l\bl -\b-u\bu u\bus\bse\ber\bri\bid\bd -\b-p\bp i\bin\bne\bet\bt:\b:p\bpo\bor\brt\btn\bnu\bum\bmb\bbe\ber\br@\b@l\blo\boc\bca\bal\blh\bho\bos\bst\bt .\b..\b..\b.o\bot\bth\bhe\ber\br
o\bop\bpt\bti\bio\bon\bns\bs.\b..\b..\b.
Please specify a userid value that isn't used for other applications (not
* Milter protocol timeouts
* Different settings for different Milter applications
* Sendmail macro emulation
+ * What macros will Postfix send to Milters?
S\bSM\bMT\bTP\bP-\b-O\bOn\bnl\bly\by M\bMi\bil\blt\bte\ber\br a\bap\bpp\bpl\bli\bic\bca\bat\bti\bio\bon\bns\bs
server is not filtered by the non-SMTP filters that are described in the next
section.
-NOTE: Do not use the header_checks(5) IGNORE action to remove Postfix's own
-Received: message header. This causes problems with mail signing filters.
-Instead, keep Postfix's own Received: message header and use the header_checks
-(5) REPLACE action to sanitize information.
+ NOTE for Postfix versions that have a mail_release_date before 20141018: do
+ not use the header_checks(5) IGNORE action to remove Postfix's own
+ Received: message header. This causes problems with mail signing filters.
+ Instead, keep Postfix's own Received: message header and use the
+ header_checks(5) REPLACE action to sanitize information.
You specify SMTP-only Milter applications (there can be more than one) with the
smtpd_milters parameter. Each Milter application is identified by the name of
* Line 3: The remainder of the list contains per-Milter settings. These
settings override global main.cf parameters, and have the same name as
- those parameters, without the "milter_" prefix.
+ those parameters, without the "milter_" prefix. The per-Milter settings
+ that are supported as of Postfix 3.0 are command_timeout, connect_timeout,
+ content_timeout, default_action, and protocol.
Inside the list, syntax is similar to what we already know from main.cf: items
separated by space or comma. There is one difference: y\byo\bou\bu m\bmu\bus\bst\bt e\ben\bnc\bcl\blo\bos\bse\be a\ba
|v |Always |value of milter_macro_v |
|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+W\bWh\bha\bat\bt m\bma\bac\bcr\bro\bos\bs w\bwi\bil\bll\bl P\bPo\bos\bst\btf\bfi\bix\bx s\bse\ben\bnd\bd t\bto\bo M\bMi\bil\blt\bte\ber\brs\bs?\b?
+
Postfix sends specific sets of macros at different Milter protocol stages. The
-sets are configured with the parameters as described in the table (EOH = end of
-headers; EOM = end of message). The protocol version is a number that Postfix
-sends at the beginning of the Milter protocol handshake.
+sets are configured with the parameters as shown in the table below (EOH = end
+of headers; EOM = end of message). The protocol version is a number that
+Postfix sends at the beginning of the Milter protocol handshake.
As of Sendmail 8.14.0, Milter applications can specify what macros they want to
receive at different Milter protocol stages. An application-specified list
|milter_unknown_command_macros|3 or higher |unknown command |
|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+By default, Postfix will send only macros whose values have been updated with
+information from main.cf or master.cf, from an SMTP session (for example; SASL
+login, or TLS certificates) or from a Mail delivery transaction (for example;
+queue ID, sender, or recipient).
+
+To force a macro to be sent even when its value has not been updated, you may
+specify macro default values with the milter_macro_defaults parameter. Specify
+zero or more name=value pairs separated by comma or whitespace; you may even
+specify macro names that Postfix does know about!
+
W\bWo\bor\brk\bka\bar\bro\bou\bun\bnd\bds\bs
* To avoid breaking DKIM etc. signatures with an SMTP-based content filter,
Things to do after the stable release:
+ TLS certificate provenance: indicate whether a subject
+ name/issuer are verified or not (for example, change the
+ attribute name to unverified_ccert_subject etc.). This is
+ relevant only for fingerprint-based authentication including
+ DANE, and affects logging, SMTPD policy, and Milters.
+
+ Exploit GCC 3.4+ __attribute__((warn_unused_result)) to
+ warn about unused function result values.
+
Generalize the daemon '-S' stand-alone mode, so that it can
be used with custom configuration files for request/reply
regression testing.
+ Update the list of Sendmail macros that Postfix can send
+ to Milters (auth_ssf and TLS-related).
+
replace str*casecmp() calls with _utf8() equivalents
for trivial-rewrite lookups.
<p> The reason for adding Milter support to Postfix is that there
exists a large collection of applications, not only to block unwanted
mail, but also to verify authenticity (examples: <a
-href="http://www.opendkim.org/">OpenDKIM</a>, <a
-href="http://sourceforge.net/projects/dkim-milter/">DomainKeys
-Identified Mail (DKIM)</a>, <a
-href="http://sourceforge.net/projects/sid-milter/">SenderID+SPF</a> and
-<a href="http://sourceforge.net/projects/dk-milter/">DomainKeys</a>)
-or to digitally sign mail (examples: <a
-href="http://www.opendkim.org/">OpenDKIM</a>, <a
-href="http://sourceforge.net/projects/dkim-milter/">DomainKeys
-Identified Mail (DKIM)</a>, <a
-href="http://sourceforge.net/projects/dk-milter/">DomainKeys</a>).
+href="http://www.opendkim.org/">OpenDKIM</a> and <a
+href="http://www.trusteddomain.org/opendmarc/">DMARC </a>)
+or to digitally sign mail (example: <a
+href="http://www.opendkim.org/">OpenDKIM</a>).
Having yet another Postfix-specific version of all that software
is a poor use of human and system resources. </p>
Postfix currently does not provide such a library, but Sendmail
does. </p>
-<ul>
-
-<li> <p> The first option is to use a pre-compiled library. Some
+<p> Some
systems install the Sendmail libmilter library by default. With
other systems, libmilter may be provided by a package (called
"sendmail-devel" on some Linux systems). </p>
<p> Once libmilter is installed, applications such as <a
-href="http://www.opendkim.org/">OpenDKIM</a>, <a
-href="http://sourceforge.net/projects/dkim-milter/">dkim-milter</a> and
-<a href="http://sourceforge.net/projects/sid-milter/">sid-milter</a>
+href="http://www.opendkim.org/">OpenDKIM</a> and
+<a href="http://www.trusteddomain.org/opendmarc/">OpenDMARC</a>
build out of the box without requiring any tinkering:</p>
<blockquote>
</pre>
</blockquote>
-<blockquote>
-<pre>
-$ <b>gzcat dkim-milter-<i>x.y.z</i>.tar.gz | tar xf -</b>
-$ <b>cd dkim-milter-<i>x.y.z</i></b>
-$ <b>make</b>
-[...<i>lots of output omitted</i>...]
-</pre>
-</blockquote>
-
-<li> <p> The other option is to build the libmilter library from
-Sendmail source code: </p>
-
-<blockquote>
-<pre>
-$ <b>gzcat sendmail-<i>x.y.z</i>.tar.gz | tar xf -</b>
-$ <b>cd sendmail-<i>x.y.z</i>/libmilter</b>
-$ <b>make</b>
-[...<i>lots of output omitted</i>...]
-</pre>
-</blockquote>
-
-<p> After building your own libmilter library, follow the installation
-instructions in the Milter application source distribution to specify
-the location of the libmilter include files and object library.
-Typically, these settings are configured in a file named
-<tt>sid-filter/Makefile.m4</tt> or similar:
-
-<blockquote>
-<pre>
-APPENDDEF(`confINCDIRS', `-I/some/where/sendmail-x.y.z/include')
-APPENDDEF(`confLIBDIRS', `-L/some/where/sendmail-x.y.z/obj.<i>systemtype</i>/libmilter')
-</pre>
-</blockquote>
-
-<p>Then build the Milter application. </p>
-
-</ul>
-
<h2><a name="running">Running Milter applications</a></h2>
<p> To run a Milter application, see the documentation of the filter
<blockquote>
<pre>
-# <b>/some/where/dkim-filter -u <i>userid</i> -p inet:<i>portnumber</i>@localhost ...<i>other options</i>...</b>
+# <b>/some/where/opendkim -l -u <i>userid</i> -p inet:<i>portnumber</i>@localhost ...<i>other options</i>...</b>
</pre>
</blockquote>
<li><a href="#macros">Sendmail macro emulation</a>
+<li><a href="#send-macros">What macros will Postfix send to Milters?</a>
+
</ul>
<h3><a name="smtp-only-milters">SMTP-Only Milter applications</a></h3>
that arrives via the Postfix <a href="smtpd.8.html">smtpd(8)</a> server is not filtered by the
non-SMTP filters that are described in the next section. </p>
-<p> NOTE: Do not use the <a href="header_checks.5.html">header_checks(5)</a> IGNORE action to remove
+<blockquote> NOTE for Postfix versions that have a <a href="postconf.5.html#mail_release_date">mail_release_date</a>
+before 20141018: do not use the <a href="header_checks.5.html">header_checks(5)</a> IGNORE action to remove
Postfix's own Received: message header. This causes problems with
mail signing filters. Instead, keep Postfix's own Received: message
header and use the <a href="header_checks.5.html">header_checks(5)</a> REPLACE action to sanitize
-information. </p>
+information. </blockquote>
<p> You specify SMTP-only Milter applications (there can be more
than one) with the <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> parameter. Each Milter application
<li> <p> Line 3: The remainder of the list contains per-Milter
settings. These settings override global <a href="postconf.5.html">main.cf</a> parameters, and
have the same name as those parameters, without the "milter_" prefix.
-</p>
+The per-Milter settings that are supported as of Postfix 3.0 are
+command_timeout, connect_timeout, content_timeout, default_action,
+and protocol. </p>
</ul>
</blockquote>
+<h3><a name="send-macros">What macros will Postfix send to Milters?</a></h3>
+
<p> Postfix sends specific sets of macros at different Milter protocol
-stages. The sets are configured with the parameters as described
-in the table (EOH = end of headers; EOM = end of message). The
+stages. The sets are configured with the parameters as shown in the
+table below (EOH = end of headers; EOM = end of message). The
protocol version is a number that Postfix sends at the beginning
of the Milter protocol handshake. </p>
</blockquote>
+<p> By default, Postfix will send only macros whose values have been
+updated with information from <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a>, from an SMTP session
+(for example; SASL login, or TLS certificates) or from a Mail delivery
+transaction (for example; queue ID, sender, or recipient). </p>
+
+<p> To force a macro to be sent even when its value has not been updated,
+you may specify macro default values with the <a href="postconf.5.html#milter_macro_defaults">milter_macro_defaults</a>
+parameter. Specify zero or more <i>name=value</i> pairs separated by
+comma or whitespace; you may even specify macro names that Postfix does
+know about! </p>
+
<h2><a name="workarounds">Workarounds</a></h2>
<ul>
Optional lookup tables for content inspection of message headers
that are produced by Milter applications.
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#milter_macro_defaults">milter_macro_defaults</a> (empty)</b>
+ Optional list of <i>name=value</i> pairs that specify default values
+ for arbitrary macros that Postfix may send to Milter applica-
+ tions.
+
<b>MIME PROCESSING CONTROLS</b>
Available in Postfix version 2.0 and later:
<p> This feature is available in Postfix 2.3 and later. </p>
+</DD>
+
+<DT><b><a name="milter_macro_defaults">milter_macro_defaults</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional list of <i>name=value</i> pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications. These defaults are used when there is no corresponding
+information from the message delivery context. </p>
+
+<p> Specify <i>name=value</i> or <i>{name}=value</i> pairs separated
+by comma or whitespace. Enclose a pair in "{}" when a value contains
+comma or whitespace (this form ignores whitespace after the enclosing
+"{", around the "=", and before the enclosing "}"). </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+
</DD>
<DT><b><a name="milter_macro_v">milter_macro_v</a>
The macros that are sent to Milter (mail filter) applications
after the message end-of-data.
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#milter_macro_defaults">milter_macro_defaults</a> (empty)</b>
+ Optional list of <i>name=value</i> pairs that specify default values
+ for arbitrary macros that Postfix may send to Milter applica-
+ tions.
+
<b>GENERAL CONTENT INSPECTION CONTROLS</b>
The following parameters are applicable for both built-in and external
content filters.
# Officially supported usage.
0) SYSTEM=`(uname -s) 2>/dev/null`
RELEASE=`(uname -r) 2>/dev/null`
+ # No ${x%%y} support in Solaris 11 /bin/sh
+ RELEASE_MAJOR=`expr "$RELEASE" : '\([0-9]*\)'` || exit 1
VERSION=`(uname -v) 2>/dev/null`
case "$VERSION" in
dcosx*) SYSTEM=$VERSION;;
: ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
: ${PLUGIN_LD="${CC-gcc} -shared"}
;;
- Linux.3*) SYSTYPE=LINUX3
+ Linux.[34].*) SYSTYPE=LINUX$RELEASE_MAJOR
case "$CCARGS" in
*-DNO_DB*) ;;
*-DHAS_DB*) ;;
meanings.
.PP
This feature is available in Postfix 2.3 and later.
+.SH milter_macro_defaults (default: empty)
+Optional list of \fIname=value\fR pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications. These defaults are used when there is no corresponding
+information from the message delivery context.
+.PP
+Specify \fIname=value\fR or \fI{name}=value\fR pairs separated
+by comma or whitespace. Enclose a pair in "{}" when a value contains
+comma or whitespace (this form ignores whitespace after the enclosing
+"{", around the "=", and before the enclosing "}").
+.PP
+This feature is available in Postfix 3.1 and later.
.SH milter_macro_v (default: $mail_name $mail_version)
The {v} macro value for Milter (mail filter) applications.
See MILTER_README for a list of available macro names and their
.IP "\fBmilter_header_checks (empty)\fR"
Optional lookup tables for content inspection of message headers
that are produced by Milter applications.
+.PP
+Available in Postfix version 3.1 and later:
+.IP "\fBmilter_macro_defaults (empty)\fR"
+Optional list of \fIname=value\fR pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications.
.SH "MIME PROCESSING CONTROLS"
.na
.nf
.IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR"
The macros that are sent to Milter (mail filter) applications
after the message end\-of\-data.
+.PP
+Available in Postfix version 3.1 and later:
+.IP "\fBmilter_macro_defaults (empty)\fR"
+Optional list of \fIname=value\fR pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications.
.SH "GENERAL CONTENT INSPECTION CONTROLS"
.na
.nf
s;\bmilter_end_of_data_macros\b;<a href="postconf.5.html#milter_end_of_data_macros">$&</a>;g;
s;\bmilter_end_of_header_macros\b;<a href="postconf.5.html#milter_end_of_header_macros">$&</a>;g;
s;\bmilter_header_checks\b;<a href="postconf.5.html#milter_header_checks">$&</a>;g;
+ s;\bmilter_macro_defaults\b;<a href="postconf.5.html#milter_macro_defaults">$&</a>;g;
# Multi-instance support
s;\bmulti_instance_directo[-</bB>]*\n*[ <bB>]*ries\b;<a href="postconf.5.html#multi_instance_directories">$&</a>;g;
<p> The reason for adding Milter support to Postfix is that there
exists a large collection of applications, not only to block unwanted
mail, but also to verify authenticity (examples: <a
-href="http://www.opendkim.org/">OpenDKIM</a>, <a
-href="http://sourceforge.net/projects/dkim-milter/">DomainKeys
-Identified Mail (DKIM)</a>, <a
-href="http://sourceforge.net/projects/sid-milter/">SenderID+SPF</a> and
-<a href="http://sourceforge.net/projects/dk-milter/">DomainKeys</a>)
-or to digitally sign mail (examples: <a
-href="http://www.opendkim.org/">OpenDKIM</a>, <a
-href="http://sourceforge.net/projects/dkim-milter/">DomainKeys
-Identified Mail (DKIM)</a>, <a
-href="http://sourceforge.net/projects/dk-milter/">DomainKeys</a>).
+href="http://www.opendkim.org/">OpenDKIM</a> and <a
+href="http://www.trusteddomain.org/opendmarc/">DMARC </a>)
+or to digitally sign mail (example: <a
+href="http://www.opendkim.org/">OpenDKIM</a>).
Having yet another Postfix-specific version of all that software
is a poor use of human and system resources. </p>
Postfix currently does not provide such a library, but Sendmail
does. </p>
-<ul>
-
-<li> <p> The first option is to use a pre-compiled library. Some
+<p> Some
systems install the Sendmail libmilter library by default. With
other systems, libmilter may be provided by a package (called
"sendmail-devel" on some Linux systems). </p>
<p> Once libmilter is installed, applications such as <a
-href="http://www.opendkim.org/">OpenDKIM</a>, <a
-href="http://sourceforge.net/projects/dkim-milter/">dkim-milter</a> and
-<a href="http://sourceforge.net/projects/sid-milter/">sid-milter</a>
+href="http://www.opendkim.org/">OpenDKIM</a> and
+<a href="http://www.trusteddomain.org/opendmarc/">OpenDMARC</a>
build out of the box without requiring any tinkering:</p>
<blockquote>
</pre>
</blockquote>
-<blockquote>
-<pre>
-$ <b>gzcat dkim-milter-<i>x.y.z</i>.tar.gz | tar xf -</b>
-$ <b>cd dkim-milter-<i>x.y.z</i></b>
-$ <b>make</b>
-[...<i>lots of output omitted</i>...]
-</pre>
-</blockquote>
-
-<li> <p> The other option is to build the libmilter library from
-Sendmail source code: </p>
-
-<blockquote>
-<pre>
-$ <b>gzcat sendmail-<i>x.y.z</i>.tar.gz | tar xf -</b>
-$ <b>cd sendmail-<i>x.y.z</i>/libmilter</b>
-$ <b>make</b>
-[...<i>lots of output omitted</i>...]
-</pre>
-</blockquote>
-
-<p> After building your own libmilter library, follow the installation
-instructions in the Milter application source distribution to specify
-the location of the libmilter include files and object library.
-Typically, these settings are configured in a file named
-<tt>sid-filter/Makefile.m4</tt> or similar:
-
-<blockquote>
-<pre>
-APPENDDEF(`confINCDIRS', `-I/some/where/sendmail-x.y.z/include')
-APPENDDEF(`confLIBDIRS', `-L/some/where/sendmail-x.y.z/obj.<i>systemtype</i>/libmilter')
-</pre>
-</blockquote>
-
-<p>Then build the Milter application. </p>
-
-</ul>
-
<h2><a name="running">Running Milter applications</a></h2>
<p> To run a Milter application, see the documentation of the filter
<blockquote>
<pre>
-# <b>/some/where/dkim-filter -u <i>userid</i> -p inet:<i>portnumber</i>@localhost ...<i>other options</i>...</b>
+# <b>/some/where/opendkim -l -u <i>userid</i> -p inet:<i>portnumber</i>@localhost ...<i>other options</i>...</b>
</pre>
</blockquote>
<li><a href="#macros">Sendmail macro emulation</a>
+<li><a href="#send-macros">What macros will Postfix send to Milters?</a>
+
</ul>
<h3><a name="smtp-only-milters">SMTP-Only Milter applications</a></h3>
that arrives via the Postfix smtpd(8) server is not filtered by the
non-SMTP filters that are described in the next section. </p>
-<p> NOTE: Do not use the header_checks(5) IGNORE action to remove
+<blockquote> NOTE for Postfix versions that have a mail_release_date
+before 20141018: do not use the header_checks(5) IGNORE action to remove
Postfix's own Received: message header. This causes problems with
mail signing filters. Instead, keep Postfix's own Received: message
header and use the header_checks(5) REPLACE action to sanitize
-information. </p>
+information. </blockquote>
<p> You specify SMTP-only Milter applications (there can be more
than one) with the smtpd_milters parameter. Each Milter application
<li> <p> Line 3: The remainder of the list contains per-Milter
settings. These settings override global main.cf parameters, and
have the same name as those parameters, without the "milter_" prefix.
-</p>
+The per-Milter settings that are supported as of Postfix 3.0 are
+command_timeout, connect_timeout, content_timeout, default_action,
+and protocol. </p>
</ul>
</blockquote>
+<h3><a name="send-macros">What macros will Postfix send to Milters?</a></h3>
+
<p> Postfix sends specific sets of macros at different Milter protocol
-stages. The sets are configured with the parameters as described
-in the table (EOH = end of headers; EOM = end of message). The
+stages. The sets are configured with the parameters as shown in the
+table below (EOH = end of headers; EOM = end of message). The
protocol version is a number that Postfix sends at the beginning
of the Milter protocol handshake. </p>
</blockquote>
+<p> By default, Postfix will send only macros whose values have been
+updated with information from main.cf or master.cf, from an SMTP session
+(for example; SASL login, or TLS certificates) or from a Mail delivery
+transaction (for example; queue ID, sender, or recipient). </p>
+
+<p> To force a macro to be sent even when its value has not been updated,
+you may specify macro default values with the milter_macro_defaults
+parameter. Specify zero or more <i>name=value</i> pairs separated by
+comma or whitespace; you may even specify macro names that Postfix does
+know about! </p>
+
<h2><a name="workarounds">Workarounds</a></h2>
<ul>
<p> This feature is available in Postfix 2.3 and later. </p>
+%PARAM milter_macro_defaults
+
+<p> Optional list of <i>name=value</i> pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications. These defaults are used when there is no corresponding
+information from the message delivery context. </p>
+
+<p> Specify <i>name=value</i> or <i>{name}=value</i> pairs separated
+by comma or whitespace. Enclose a pair in "{}" when a value contains
+comma or whitespace (this form ignores whitespace after the enclosing
+"{", around the "=", and before the enclosing "}"). </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
%PARAM milter_macro_v $mail_name $mail_version
<p> The {v} macro value for Milter (mail filter) applications.
/* .IP "\fBmilter_header_checks (empty)\fR"
/* Optional lookup tables for content inspection of message headers
/* that are produced by Milter applications.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBmilter_macro_defaults (empty)\fR"
+/* Optional list of \fIname=value\fR pairs that specify default
+/* values for arbitrary macros that Postfix may send to Milter
+/* applications.
/* MIME PROCESSING CONTROLS
/* .ad
/* .fi
char *var_milt_unk_macros; /* unknown command macros */
char *var_cleanup_milters; /* non-SMTP mail */
char *var_milt_head_checks; /* post-Milter header checks */
+char *var_milt_macro_deflts; /* default macro settings */
int var_auto_8bit_enc_hdr; /* auto-detect 8bit encoding header */
int var_always_add_hdrs; /* always add missing headers */
int var_virt_addrlen_limit; /* stop exponential growth */
VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0,
VAR_CLEANUP_MILTERS, DEF_CLEANUP_MILTERS, &var_cleanup_milters, 0, 0,
VAR_MILT_HEAD_CHECKS, DEF_MILT_HEAD_CHECKS, &var_milt_head_checks, 0, 0,
+ VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0,
0,
};
var_milt_data_macros,
var_milt_eoh_macros,
var_milt_eod_macros,
- var_milt_unk_macros);
+ var_milt_unk_macros,
+ var_milt_macro_deflts);
flush_init();
}
* that we forward all Sendmail macros via XFORWARD.
*/
- /*
- * Canonicalize the name.
- */
- if (*name != '{') { /* } */
- vstring_sprintf(state->temp1, "{%s}", name);
- name = STR(state->temp1);
- }
-
/*
* System macros.
*/
#define DEF_MILT_HEAD_CHECKS ""
extern char *var_milt_head_checks;
+#define VAR_MILT_MACRO_DEFLTS "milter_macro_defaults"
+#define DEF_MILT_MACRO_DEFLTS ""
+extern char *var_milt_macro_deflts;
+
/*
* What internal mail do we inspect/stamp/etc.? This is not yet safe enough
* to enable world-wide.
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20150421"
+#define MAIL_RELEASE_DATE "20150523"
#define MAIL_VERSION_NUMBER "3.1"
#ifdef SNAPSHOT
/* conn_macros, helo_macros,
/* mail_macros, rcpt_macros,
/* data_macros, eoh_macros,
-/* eod_macros, unk_macros)
+/* eod_macros, unk_macros,
+/* macro_deflts)
/* const char *milter_names;
/* int conn_timeout;
/* int cmd_timeout;
/* const char *eoh_macros;
/* const char *eod_macros;
/* const char *unk_macros;
+/* const char *macro_deflts;
/*
/* void milter_free(milters)
/* MILTERS *milters;
/* milter_create() instantiates the milter clients specified
/* with the milter_names argument. The conn_macros etc.
/* arguments specify the names of macros that are sent to the
-/* mail filter applications upon a connect etc. event. This
+/* mail filter applications upon a connect etc. event, and the
+/* macro_deflts argument specifies macro defaults that will be used
+/* only if the application's lookup call-back returns null. This
/* function should be called during process initialization,
/* before entering a chroot jail. The timeout parameters specify
/* time limits for the completion of the specified request
#include <stringops.h>
#include <argv.h>
#include <attr.h>
+#include <htable.h>
/* Global library. */
*/
#define STR(x) vstring_str(x)
+/* milter_macro_defaults_create - parse default macro entries */
+
+HTABLE *milter_macro_defaults_create(const char *macro_defaults)
+{
+ const char myname[] = "milter_macro_defaults_create";
+ char *saved_defaults = mystrdup(macro_defaults);
+ char *cp = saved_defaults;
+ HTABLE *table = 0;
+ VSTRING *canon_buf = 0;
+ char *nameval;
+
+ while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ const char *err;
+ char *name;
+ char *value;
+
+ /*
+ * Split the input into (name, value) pairs. Allow the forms
+ * name=value and { name = value }, where the last form ignores
+ * whitespace after the opening "{", around the "=", and before the
+ * closing "}". A name may also be specified as {name}.
+ *
+ * Use the form {name} for table lookups, because that is the form of
+ * the S8_MAC_* macro names.
+ */
+ if (*nameval == CHARS_BRACE[0]
+ && nameval[balpar(nameval, CHARS_BRACE)] != '='
+ && (err = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("malformed default macro entry: %s in \"%s\"",
+ err, macro_defaults);
+ if ((err = split_nameval(nameval, &name, &value)) != 0)
+ msg_fatal("malformed default macro entry: %s in \"%s\"",
+ err, macro_defaults);
+ if (*name != '{') /* } */
+ name = STR(vstring_sprintf(canon_buf ? canon_buf :
+ (canon_buf = vstring_alloc(20)), "{%s}", name));
+ if (table == 0)
+ table = htable_create(1);
+ if (htable_find(table, name) != 0) {
+ msg_warn("ignoring multiple default macro entries for %s in \"%s\"",
+ name, macro_defaults);
+ } else {
+ (void) htable_enter(table, name, mystrdup(value));
+ if (msg_verbose)
+ msg_info("%s: add name=%s default=%s", myname, name, value);
+ }
+ }
+ myfree(saved_defaults);
+ if (canon_buf)
+ vstring_free(canon_buf);
+ return (table);
+}
+
/* milter_macro_lookup - look up macros */
static ARGV *milter_macro_lookup(MILTERS *milters, const char *macro_names)
char *saved_names = mystrdup(macro_names);
char *cp = saved_names;
ARGV *argv = argv_alloc(10);
+ VSTRING *canon_buf = vstring_alloc(20);
const char *value;
const char *name;
while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
if (msg_verbose)
msg_info("%s: \"%s\"", myname, name);
+ if (*name != '{') /* } */
+ name = STR(vstring_sprintf(canon_buf, "{%s}", name));
if ((value = milters->mac_lookup(name, milters->mac_context)) != 0) {
if (msg_verbose)
msg_info("%s: result \"%s\"", myname, value);
argv_add(argv, name, value, (char *) 0);
+ } else if (milters->macro_defaults != 0
+ && (value = htable_find(milters->macro_defaults, name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: using default \"%s\"", myname, value);
+ argv_add(argv, name, value, (char *) 0);
}
}
myfree(saved_names);
+ vstring_free(canon_buf);
return (argv);
}
int msg_timeout,
const char *protocol,
const char *def_action,
- MILTER_MACROS *macros)
+ MILTER_MACROS *macros,
+ HTABLE *macro_defaults)
{
MILTERS *milters;
MILTER *head = 0;
milters->mac_lookup = 0;
milters->mac_context = 0;
milters->macros = macros;
+ milters->macro_defaults = macro_defaults;
milters->add_header = 0;
milters->upd_header = milters->ins_header = 0;
milters->del_header = 0;
next = m->next, m->free(m);
if (milters->macros)
milter_macros_free(milters->macros);
+ if (milters->macro_defaults)
+ htable_free(milters->macro_defaults, myfree);
myfree((void *) milters);
}
(void *) milters->macros),
ATTR_TYPE_END);
+ /*
+ * Send the filter macro defaults.
+ */
+ count = milters->macro_defaults ? milters->macro_defaults->used : 0;
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_INT(MAIL_ATTR_SIZE, count),
+ ATTR_TYPE_END);
+ if (count > 0)
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_HASH(milters->macro_defaults),
+ ATTR_TYPE_END);
+
/*
* Send the filter instances.
*/
MILTER *head = 0;
MILTER *tail = 0;
MILTER *milter = 0;
+ int macro_default_count;
if (msg_verbose)
msg_info("receive %d milters", count);
#define NO_PROTOCOL ((char *) 0)
#define NO_ACTION ((char *) 0)
#define NO_MACROS ((MILTER_MACROS *) 0)
+#define NO_MACRO_DEFLTS ((HTABLE *) 0)
milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION,
- NO_MACROS);
+ NO_MACROS, NO_MACRO_DEFLTS);
/*
* XXX Optimization: don't send or receive further information when there
return (0);
}
+ /*
+ * Receive the filter macro defaults.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(MAIL_ATTR_SIZE, ¯o_default_count),
+ ATTR_TYPE_END) != 1
+ || (macro_default_count > 0
+ && attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_HASH(milters->macro_defaults
+ = htable_create(1)),
+ ATTR_TYPE_END) != macro_default_count)) {
+ milter_free(milters);
+ return (0);
+ }
+
/*
* Receive the filters.
*/
MILTERS *milters = 0;
char *conn_macros, *helo_macros, *mail_macros, *rcpt_macros;
char *data_macros, *eoh_macros, *eod_macros, *unk_macros;
+ char *macro_deflts;
VSTRING *inbuf = vstring_alloc(100);
char *bufp;
char *cmd;
int istty = isatty(vstream_fileno(VSTREAM_IN));
conn_macros = helo_macros = mail_macros = rcpt_macros = data_macros
- = eoh_macros = eod_macros = unk_macros = "";
+ = eoh_macros = eod_macros = unk_macros = macro_deflts = "";
msg_vstream_init(argv[0], VSTREAM_ERR);
while ((ch = GETOPT(argc, argv, "V:v")) > 0) {
var_milt_protocol, var_milt_def_action,
conn_macros, helo_macros, mail_macros,
rcpt_macros, data_macros, eoh_macros,
- eod_macros, unk_macros);
+ eod_macros, unk_macros, macro_deflts);
} else if (strcmp(cmd, "free") == 0 && argv->argc == 0) {
if (milters == 0) {
msg_warn("no milters");
#define MILTER_MACROS_ALLOC_ZERO 1 /* null pointer */
#define MILTER_MACROS_ALLOC_EMPTY 2 /* mystrdup(""); */
+ /*
+ * Helper to parse list of name=value default macro settings.
+ */
+extern struct HTABLE *milter_macro_defaults_create(const char *);
+
/*
* A bunch of Milters.
*/
MILTER_MAC_LOOKUP_FN mac_lookup;
void *mac_context; /* macro lookup context */
struct MILTER_MACROS *macros;
+ struct HTABLE *macro_defaults;
void *chg_context; /* context for queue file changes */
MILTER_ADD_HEADER_FN add_header;
MILTER_EDIT_HEADER_FN upd_header;
#define milter_create(milter_names, conn_timeout, cmd_timeout, msg_timeout, \
protocol, def_action, conn_macros, helo_macros, \
mail_macros, rcpt_macros, data_macros, eoh_macros, \
- eod_macros, unk_macros) \
+ eod_macros, unk_macros, macro_deflts) \
milter_new(milter_names, conn_timeout, cmd_timeout, msg_timeout, \
protocol, def_action, milter_macros_create(conn_macros, \
helo_macros, mail_macros, rcpt_macros, data_macros, \
- eoh_macros, eod_macros, unk_macros))
+ eoh_macros, eod_macros, unk_macros), \
+ milter_macro_defaults_create(macro_deflts))
extern MILTERS *milter_new(const char *, int, int, int, const char *,
- const char *, MILTER_MACROS *);
+ const char *, MILTER_MACROS *,
+ struct HTABLE *);
extern void milter_macro_callback(MILTERS *, MILTER_MAC_LOOKUP_FN, void *);
extern void milter_edit_callback(MILTERS *milters, MILTER_ADD_HEADER_FN,
MILTER_EDIT_HEADER_FN, MILTER_EDIT_HEADER_FN,
/* .IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR"
/* The macros that are sent to Milter (mail filter) applications
/* after the message end-of-data.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBmilter_macro_defaults (empty)\fR"
+/* Optional list of \fIname=value\fR pairs that specify default
+/* values for arbitrary macros that Postfix may send to Milter
+/* applications.
/* GENERAL CONTENT INSPECTION CONTROLS
/* .ad
/* .fi
char *var_milt_eoh_macros;
char *var_milt_eod_macros;
char *var_milt_unk_macros;
+char *var_milt_macro_deflts;
bool var_smtpd_client_port_log;
char *var_stress;
var_milt_data_macros,
var_milt_eoh_macros,
var_milt_eod_macros,
- var_milt_unk_macros);
+ var_milt_unk_macros,
+ var_milt_macro_deflts);
else
smtpd_input_transp_mask |= INPUT_TRANSP_MILTER;
}
VAR_MILT_DEF_ACTION, DEF_MILT_DEF_ACTION, &var_milt_def_action, 1, 0,
VAR_MILT_DAEMON_NAME, DEF_MILT_DAEMON_NAME, &var_milt_daemon_name, 1, 0,
VAR_MILT_V, DEF_MILT_V, &var_milt_v, 1, 0,
+ VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0,
VAR_STRESS, DEF_STRESS, &var_stress, 0, 0,
VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why, 0, 0,
VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why, 0, 0,
if (state->expand_buf == 0)
state->expand_buf = vstring_alloc(10);
- /*
- * Canonicalize the name.
- */
- if (*name != '{') { /* } */
- vstring_sprintf(state->expand_buf, "{%s}", name);
- name = STR(state->expand_buf);
- }
-
/*
* System macros.
*/
/*
* MAIL FROM macros.
*/
-#define IF_SASL_ENABLED(s) (smtpd_sasl_is_active(state) && (s) ? (s) : 0)
+#define IF_SASL_ENABLED(s) ((s) ? (s) : 0)
if (strcmp(name, S8_MAC_I) == 0)
return (state->queue_id);
#define ATTR_TYPE_DATA 5 /* Binary data */
#define ATTR_TYPE_FUNC 6 /* Function pointer */
+ /*
+ * Optional sender-specified grouping for hash or nameval tables.
+ */
+#define ATTR_TYPE_OPEN '{'
+#define ATTR_TYPE_CLOSE '}'
+#define ATTR_NAME_OPEN "{"
+#define ATTR_NAME_CLOSE "}"
+
#define ATTR_HASH_LIMIT 1024 /* Size of hash table */
/*
print_fn(attr_print0, fp, flags | ATTR_FLAG_MORE, print_arg);
break;
case ATTR_TYPE_HASH:
+ vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN));
ht_info_list = htable_list(va_arg(ap, HTABLE *));
for (ht = ht_info_list; *ht; ht++) {
vstream_fwrite(fp, ht[0]->key, strlen(ht[0]->key) + 1);
ht[0]->key, (char *) ht[0]->value);
}
myfree((void *) ht_info_list);
+ vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE));
break;
default:
msg_panic("%s: unknown type code: %d", myname, attr_type);
SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
ATTR_TYPE_END);
attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE,
SEND_ATTR_INT(ATTR_NAME_INT, 4711),
print_fn(attr_print64, fp, flags | ATTR_FLAG_MORE, print_arg);
break;
case ATTR_TYPE_HASH:
+ attr_print64_str(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN) - 1);
+ VSTREAM_PUTC('\n', fp);
ht_info_list = htable_list(va_arg(ap, HTABLE *));
for (ht = ht_info_list; *ht; ht++) {
attr_print64_str(fp, ht[0]->key, strlen(ht[0]->key));
ht[0]->key, (char *) ht[0]->value);
}
myfree((void *) ht_info_list);
+ attr_print64_str(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE) - 1);
+ VSTREAM_PUTC('\n', fp);
break;
default:
msg_panic("%s: unknown type code: %d", myname, attr_type);
SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
ATTR_TYPE_END);
attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE,
SEND_ATTR_INT(ATTR_NAME_INT, 4711),
print_fn(attr_print_plain, fp, flags | ATTR_FLAG_MORE, print_arg);
break;
case ATTR_TYPE_HASH:
+ vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN));
+ VSTREAM_PUTC('\n', fp);
ht_info_list = htable_list(va_arg(ap, HTABLE *));
for (ht = ht_info_list; *ht; ht++) {
vstream_fprintf(fp, "%s=%s\n", ht[0]->key, (char *) ht[0]->value);
ht[0]->key, (char *) ht[0]->value);
}
myfree((void *) ht_info_list);
+ vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE));
+ VSTREAM_PUTC('\n', fp);
break;
default:
msg_panic("%s: unknown type code: %d", myname, attr_type);
SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
ATTR_TYPE_END);
attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
SEND_ATTR_INT(ATTR_NAME_INT, 4711),
/* (item1 | item2) stands for choice:
/*
/* .in +5
-/* attr-list :== simple-attr* null
+/* attr-list :== (simple-attr | multi-attr)* null
+/* .br
+/* multi-attr :== "{" null simple-attr* "}" null
/* .br
/* simple-attr :== attr-name null attr-value null
/* .br
/* error.
/* .IP "RECV_ATTR_HASH(HTABLE *table)"
/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
-/* All further input attributes are processed as string attributes.
-/* No specific attribute sequence is enforced.
-/* All attributes up to the attribute list terminator are read,
-/* but only the first instance of each attribute is stored.
+/* Receive a sequence of attribute names and string values.
/* There can be no more than 1024 attributes in a hash table.
/* .sp
/* The attribute string values are stored in the hash table under
/* Values from the input stream are added to the hash table. Existing
/* hash table entries are not replaced.
/* .sp
-/* N.B. This construct must be followed by an ATTR_TYPE_END argument.
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
/* .IP ATTR_TYPE_END
/* This argument terminates the requested attribute list.
/* .RE
* from the input stream instead. This is secure only when the
* resulting table is queried with known to be good attribute names.
*/
- if (wanted_type != ATTR_TYPE_HASH) {
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
wanted_type = va_arg(ap, int);
if (wanted_type == ATTR_TYPE_END) {
if ((flags & ATTR_FLAG_MORE) != 0)
} else if (wanted_type == ATTR_TYPE_HASH) {
wanted_name = "(any attribute name or list terminator)";
hash_table = va_arg(ap, HTABLE *);
- if (va_arg(ap, int) !=ATTR_TYPE_END)
- msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END",
- myname);
} else if (wanted_type != ATTR_TYPE_FUNC) {
wanted_name = va_arg(ap, char *);
}
/*
* See if the caller asks for this attribute.
*/
+ if (wanted_type == ATTR_TYPE_HASH
+ && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
|| (wanted_type != ATTR_TYPE_END
&& strcmp(wanted_name, STR(name_buf)) == 0))
break;
return (-1);
break;
case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
if ((ch = attr_scan0_string(fp, str_buf,
"input attribute value")) < 0)
return (-1);
mystrdup(STR(str_buf)));
}
break;
+ case -1:
+ conversions -= 1;
+ break;
default:
msg_panic("%s: unknown type code: %d", myname, wanted_type);
}
HTABLE_INFO **ht;
int int_val;
long long_val;
+ long long_val2;
int ret;
msg_verbose = 1;
RECV_ATTR_STR(ATTR_NAME_STR, str_val),
RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
ATTR_TYPE_END)) > 4) {
vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
for (ht = ht_info_list; *ht; ht++)
vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
} else {
vstream_printf("return: %d\n", ret);
}
./attr_print0: send attr data = [data 7 bytes]
./attr_print0: send attr name foo-name value foo-value
./attr_print0: send attr name bar-name value bar-value
+./attr_print0: send attr long_number = 4321
./attr_print0: send attr number = 4711
./attr_print0: send attr long_number = 1234
./attr_print0: send attr string = whoopee
./attr_scan0: input attribute name: data
./attr_scan0: input attribute value: d2hvb3BlZQ==
./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan0: input attribute name: {
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan0: input attribute name: foo-name
./attr_scan0: input attribute value: foo-value
-./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan0: input attribute name: bar-name
./attr_scan0: input attribute value: bar-value
-./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: }
+./attr_scan0: unknown_stream: wanted attribute: long_number
+./attr_scan0: input attribute name: long_number
+./attr_scan0: input attribute value: 4321
+./attr_scan0: unknown_stream: wanted attribute: (list terminator)
./attr_scan0: input attribute name: (end)
./attr_scan0: unknown_stream: wanted attribute: number
./attr_scan0: input attribute name: number
data whoopee
(hash) foo-name foo-value
(hash) bar-name bar-value
+long_number 4321
number 4711
long_number 1234
string whoopee
/* (item1 | item2) stands for choice:
/*
/* .in +5
-/* attr-list :== simple-attr* newline
+/* attr-list :== (simple-attr | multi-attr)* newline
+/* .br
+/* multi-attr :== "{" newline simple-attr* "}" newline
/* .br
/* simple-attr :== attr-name colon attr-value newline
/* .br
/* error.
/* .IP "RECV_ATTR_HASH(HTABLE *table)"
/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
-/* All further input attributes are processed as string attributes.
-/* No specific attribute sequence is enforced.
-/* All attributes up to the attribute list terminator are read,
-/* but only the first instance of each attribute is stored.
+/* Receive a sequence of attribute names and string values.
/* There can be no more than 1024 attributes in a hash table.
/* .sp
/* The attribute string values are stored in the hash table under
/* Values from the input stream are added to the hash table. Existing
/* hash table entries are not replaced.
/* .sp
-/* N.B. This construct must be followed by an ATTR_TYPE_END argument.
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
/* .IP ATTR_TYPE_END
/* This argument terminates the requested attribute list.
/* .RE
* from the input stream instead. This is secure only when the
* resulting table is queried with known to be good attribute names.
*/
- if (wanted_type != ATTR_TYPE_HASH) {
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
wanted_type = va_arg(ap, int);
if (wanted_type == ATTR_TYPE_END) {
if ((flags & ATTR_FLAG_MORE) != 0)
} else if (wanted_type == ATTR_TYPE_HASH) {
wanted_name = "(any attribute name or list terminator)";
hash_table = va_arg(ap, HTABLE *);
- if (va_arg(ap, int) !=ATTR_TYPE_END)
- msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END",
- myname);
} else if (wanted_type != ATTR_TYPE_FUNC) {
wanted_name = va_arg(ap, char *);
}
/*
* See if the caller asks for this attribute.
*/
+ if (wanted_type == ATTR_TYPE_HASH
+ && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
|| (wanted_type != ATTR_TYPE_END
&& strcmp(wanted_name, STR(name_buf)) == 0))
break;
return (-1);
break;
case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
if (ch != ':') {
msg_warn("missing value for string attribute %s from %s",
STR(name_buf), VSTREAM_PATH(fp));
mystrdup(STR(str_buf)));
}
break;
+ case -1:
+ conversions -= 1;
+ break;
default:
msg_panic("%s: unknown type code: %d", myname, wanted_type);
}
HTABLE_INFO **ht;
int int_val;
long long_val;
+ long long_val2;
int ret;
msg_verbose = 1;
RECV_ATTR_STR(ATTR_NAME_STR, str_val),
RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
ATTR_TYPE_END)) > 4) {
vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
for (ht = ht_info_list; *ht; ht++)
vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
} else {
vstream_printf("return: %d\n", ret);
}
./attr_print64: send attr data = [data 7 bytes]
./attr_print64: send attr name foo-name value foo-value
./attr_print64: send attr name bar-name value bar-value
+./attr_print64: send attr long_number = 4321
./attr_print64: send attr number = 4711
./attr_print64: send attr long_number = 1234
./attr_print64: send attr string = whoopee
./attr_scan64: input attribute name: data
./attr_scan64: input attribute value: whoopee
./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan64: input attribute name: {
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan64: input attribute name: foo-name
./attr_scan64: input attribute value: foo-value
-./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan64: input attribute name: bar-name
./attr_scan64: input attribute value: bar-value
-./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: }
+./attr_scan64: unknown_stream: wanted attribute: long_number
+./attr_scan64: input attribute name: long_number
+./attr_scan64: input attribute value: 4321
+./attr_scan64: unknown_stream: wanted attribute: (list terminator)
./attr_scan64: input attribute name: (end)
./attr_scan64: unknown_stream: wanted attribute: number
./attr_scan64: input attribute name: number
data whoopee
(hash) foo-name foo-value
(hash) bar-name bar-value
+long_number 4321
number 4711
long_number 1234
string whoopee
/* (item1 | item2) stands for choice:
/*
/* .in +5
-/* attr-list :== simple-attr* newline
+/* attr-list :== (simple-attr | multi-attr)* newline
+/* .br
+/* multi-attr :== "{" newline simple-attr* "}" newline
/* .br
/* simple-attr :== attr-name "=" attr-value newline
/* .br
/* error.
/* .IP "RECV_ATTR_HASH(HTABLE *table)"
/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
-/* All further input attributes are processed as string attributes.
-/* No specific attribute sequence is enforced.
-/* All attributes up to the attribute list terminator are read,
-/* but only the first instance of each attribute is stored.
+/* Receive a sequence of attribute names and string values.
/* There can be no more than 1024 attributes in a hash table.
/* .sp
/* The attribute string values are stored in the hash table under
/* Values from the input stream are added to the hash table. Existing
/* hash table entries are not replaced.
/* .sp
-/* N.B. This construct must be followed by an ATTR_TYPE_END argument.
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
/* .IP ATTR_TYPE_END
/* This argument terminates the requested attribute list.
/* .RE
* from the input stream instead. This is secure only when the
* resulting table is queried with known to be good attribute names.
*/
- if (wanted_type != ATTR_TYPE_HASH) {
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
wanted_type = va_arg(ap, int);
if (wanted_type == ATTR_TYPE_END) {
if ((flags & ATTR_FLAG_MORE) != 0)
} else if (wanted_type == ATTR_TYPE_HASH) {
wanted_name = "(any attribute name or list terminator)";
hash_table = va_arg(ap, HTABLE *);
- if (va_arg(ap, int) !=ATTR_TYPE_END)
- msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END",
- myname);
} else if (wanted_type != ATTR_TYPE_FUNC) {
wanted_name = va_arg(ap, char *);
}
/*
* See if the caller asks for this attribute.
*/
+ if (wanted_type == ATTR_TYPE_HASH
+ && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
|| (wanted_type != ATTR_TYPE_END
&& strcmp(wanted_name, STR(name_buf)) == 0))
break;
return (-1);
break;
case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
if (ch != '=') {
msg_warn("missing value for string attribute %s from %s",
STR(name_buf), VSTREAM_PATH(fp));
mystrdup(STR(str_buf)));
}
break;
+ case -1:
+ conversions -= 1;
+ break;
default:
msg_panic("%s: unknown type code: %d", myname, wanted_type);
}
HTABLE_INFO **ht;
int int_val;
long long_val;
+ long long_val2;
int ret;
msg_verbose = 1;
RECV_ATTR_STR(ATTR_NAME_STR, str_val),
RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
ATTR_TYPE_END)) > 4) {
vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
for (ht = ht_info_list; *ht; ht++)
vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
} else {
vstream_printf("return: %d\n", ret);
}
./attr_print_plain: send attr data = [data 7 bytes]
./attr_print_plain: send attr name foo-name value foo-value
./attr_print_plain: send attr name bar-name value bar-value
+./attr_print_plain: send attr long_number = 4321
./attr_print_plain: send attr number = 4711
./attr_print_plain: send attr long_number = 1234
./attr_print_plain: send attr string = whoopee
./attr_scan_plain: input attribute name: data
./attr_scan_plain: input attribute value: d2hvb3BlZQ==
./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: input attribute name: {
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan_plain: input attribute name: foo-name
./attr_scan_plain: input attribute value: foo-value
-./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan_plain: input attribute name: bar-name
./attr_scan_plain: input attribute value: bar-value
-./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: }
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 4321
+./attr_scan_plain: unknown_stream: wanted attribute: (list terminator)
./attr_scan_plain: input attribute name: (end)
./attr_scan_plain: unknown_stream: wanted attribute: number
./attr_scan_plain: input attribute name: number
data whoopee
(hash) foo-name foo-value
(hash) bar-name bar-value
+long_number 4321
number 4711
long_number 1234
string whoopee
/*
* LINUX.
*/
-#if defined(LINUX2) || defined(LINUX3)
+#if defined(LINUX2) || defined(LINUX3) || defined(LINUX4)
#define SUPPORTED
#include <sys/types.h>
#define UINT32_TYPE unsigned int