Cleanup: DANE support: test script. Viktor Dukhovni. File
tls/tls_dane.sh.
- LMDB will not be supported in the stable Postfix 2.11 release.
+ Debugging: test driver for LMDB debugging and stress testing.
+ Shockingly, LMDB terminates the postscreen daemon without
+ logfile record. File: util/dict_cache.c.
- Debugging: test driver to speed up LMDB debugging and stress
- testing. Shockingly, LMDB terminates the postcreen daemon
- without logfile record. Fixing this will require changes
- in LMDB or changes in the way Postfix can use LMDB. File:
- util/dict_cache.c.
+ Because of the above behavior, LMDB cannot be supported in
+ the stable Postfix 2.11 release.
+
+20140102
+
+ Bugfix: close the LMDB database cursor's read transaction
+ before writing with MDB_NOLOCK and before changing the
+ database memory map size. File: util/slmdb.c.
+
+20140103
+
+ Cleanup: eliminated data duplication from the new SMTP_ITERATOR
+ structure to the old SMTP_SESSION structure. The SMTP_ITERATOR
+ structure now maintains the sole copy. Files: smtp/smtp.h,
+ smtp_sasl_auth_cache.c, smtp_reuse.c, smtp_sasl_glue.c,
+ smtp_rcpt.c, smtp_session.c, smtp_chat.c, smtp_proto.c,
+ smtp_connect.c.
+
+20130104
+
+ Feature: support for optional configuration files
+ "$daemon-directory/postfix-files.d/*". These are processed
+ in sorted order after "$daemon-directory/postfix-files",
+ This avoids breaking "postfix set-permissions" etc. when a
+ Postfix distributions comes in multiple packages. File:
+ conf/post-install.
* P\bPr\bri\bim\bme\be-\b-f\bfi\bie\bel\bld\bd g\bgr\bro\bou\bup\bps\bs (\b(E\bED\bDH\bH)\b):\b: The server needs to be configured with a
suitably-large prime and a corresponding "generator". The acronym for
- forward secrecy over prime fields is EDH or Ephemeral Diffie-Hellman
- (sometimes also abbreviated as DHE).
+ forward secrecy over prime fields is EDH for Ephemeral Diffie-Hellman (also
+ abbreviated as DHE for Diffie-Hellman Exchange).
* E\bEl\bll\bli\bip\bpt\bti\bic\bc-\b-c\bcu\bur\brv\bve\be g\bgr\bro\bou\bup\bps\bs (\b(E\bEE\bEC\bCD\bDH\bH)\b):\b: The server needs to be configured with a
"named curve". These offer better security at lower computational cost than
prime field groups, but are not as widely implemented. The acronym for the
elliptic curve version is EECDH which is short for Ephemeral Elliptic Curve
- Diffie-Hellman.
+ Diffie-Hellman (also abbreviated as ECDHE for Elliptic Curve Diffie-Hellman
+ Exchange).
It is not essential to know what these are, but one does need to know that
-OpenSSL only supports EECDH as of version 1.0.0. Thus the configuration
-parameters related to Elliptic Curve forward secrecy are only available when
-Postfix is linked with OpenSSL 1.0.0 or later (provided EC support has not been
-disabled by the vendor, as in some versions of RedHat Linux).
+OpenSSL supports EECDH with version 1.0.0 or later. Thus the configuration
+parameters related to Elliptic-Curve forward secrecy are available when Postfix
+is linked with OpenSSL >= 1.0.0 (provided EC support has not been disabled by
+the vendor, as in some versions of RedHat Linux).
Elliptic curves used in cryptography are typically identified by a "name" that
stands for a set of well-known parameter values, and it is these "names" (or
1024 bits long (see the quick-start section for details).
It turns out that (inadvisably-patched in some Debian releases) Exim SMTP
-clients enforce a minimum 2048-bit length for the non-export prime. See the
-quick-start section for the recommended configuration to work around this
-issue.
+clients require a >= 2048-bit length for the non-export prime. See the quick-
+start section for the recommended configuration to work around this issue.
E\bEE\bEC\bCD\bDH\bH S\bSe\ber\brv\bve\ber\br s\bsu\bup\bpp\bpo\bor\brt\bt
G\bGe\bet\btt\bti\bin\bng\bg s\bst\bta\bar\brt\bte\bed\bd,\b, q\bqu\bui\bic\bck\bk a\ban\bnd\bd d\bdi\bir\brt\bty\by
- * Postfix 2.6 and 2.7: Enable elliptic-curve support. This is the default
- with Postfix >= 2.8.
-
- /etc/postfix/main.cf:
- # Postfix 2.6 or 2.7 only. This is default with Postfix 2.8 and
- later.
- smtpd_tls_eecdh_grade = strong
-
- * Optionally generate non-default EDH parameters for improved security
- against pre-computation attacks and for compatibility with Debian-patched
- EXIM SMTP clients (these require a minimum 2048-bit length for the non-
- export prime). The parameter files are not secret, after all these
- parameters are sent to all SMTP clients in the clear. Mode 0644 is fine.
-
- Execute as root (prime group generation can take a few seconds to a few
- minutes):
-
- # cd /etc/postfix
- # openssl dhparam -out dh512.tmp 512 && mv dh512.tmp dh512.pem
- # openssl dhparam -out dh1024.tmp 1024 && mv dh1024.tmp dh1024.pem
- # openssl dhparam -out dh2048.tmp 2048 && mv dh2048.tmp dh2048.pem
- # chmod 644 dh512.pem dh1024.pem dh2048.pem
-
- You can improve security against pre-computation attacks further by
- regenerating the EDH parameters periodically (an hourly or daily cron job
- running as root can automate this task).
-
- Once the parameters are in place, update main.cf as follows:
-
- /etc/postfix/main.cf:
- smtpd_tls_dh1024_param_file = ${config_directory}/dh2048.pem
- smtpd_tls_dh512_param_file = ${config_directory}/dh512.pem
-
- If some of your MSA clients don't support 2048-bit EDH, you may need to
- adjust the submission entry in master.cf accordingly:
-
- /etc/postfix/master.cf:
- submission inet n - n - - smtpd
- # Some submission clients may not yet do 2048-bit EDH, if such
- # clients use your MSA, configure 1024-bit EDH instead:
- -o smtpd_tls_dh1024_param_file=${config_directory}/dh1024.pem
- -o smtpd_tls_security_level=encrypt
- -o smtpd_sasl_auth_enable=yes
- ...
+E\bEE\bEC\bCD\bDH\bH C\bCl\bli\bie\ben\bnt\bt a\ban\bnd\bd s\bse\ber\brv\bve\ber\br s\bsu\bup\bpp\bpo\bor\brt\bt (\b(P\bPo\bos\bst\btf\bfi\bix\bx >\b>=\b= 2\b2.\b.6\b6 w\bwi\bit\bth\bh O\bOp\bpe\ben\bnS\bSS\bSL\bL >\b>=\b= 1\b1.\b.0\b0.\b.0\b0)\b)
+
+With Postfix 2.6 and 2.7, enable elliptic-curve support in the Postfix SMTP
+client and server. This is the default with Postfix >= 2.8.
+
+ /etc/postfix/main.cf:
+ # Postfix 2.6 or 2.7 only. This is default with Postfix 2.8 and later.
+ smtpd_tls_eecdh_grade = strong
+
+E\bED\bDH\bH C\bCl\bli\bie\ben\bnt\bt s\bsu\bup\bpp\bpo\bor\brt\bt (\b(P\bPo\bos\bst\btf\bfi\bix\bx >\b>=\b= 2\b2.\b.2\b2)\b)
+
+This space intentionally left blank.
+
+E\bED\bDH\bH S\bSe\ber\brv\bve\ber\br s\bsu\bup\bpp\bpo\bor\brt\bt (\b(P\bPo\bos\bst\btf\bfi\bix\bx >\b>=\b= 2\b2.\b.2\b2)\b)
+
+Optionally generate non-default Postfix SMTP server EDH parameters for improved
+security against pre-computation attacks and for compatibility with Debian-
+patched Exim SMTP clients that require a >= 2048-bit length for the non-export
+prime.
+
+Execute as root (prime group generation can take a few seconds to a few
+minutes):
+
+ # cd /etc/postfix
+ # umask 022
+ # openssl dhparam -out dh512.tmp 512 && mv dh512.tmp dh512.pem
+ # openssl dhparam -out dh1024.tmp 1024 && mv dh1024.tmp dh1024.pem
+ # openssl dhparam -out dh2048.tmp 2048 && mv dh2048.tmp dh2048.pem
+ # chmod 644 dh512.pem dh1024.pem dh2048.pem
+
+The Postfix SMTP server EDH parameter files are not secret, after all these
+parameters are sent to all remote SMTP clients in the clear. Mode 0644 is fine.
+
+You can improve security against pre-computation attacks further by
+regenerating the Postfix SMTP server EDH parameters periodically (an hourly or
+daily cron job running the above commands as root can automate this task).
+
+Once the parameters are in place, update main.cf as follows:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_dh1024_param_file = ${config_directory}/dh2048.pem
+ smtpd_tls_dh512_param_file = ${config_directory}/dh512.pem
+
+If some of your MSA clients don't support 2048-bit EDH, you may need to adjust
+the submission entry in master.cf accordingly:
+
+ /etc/postfix/master.cf:
+ submission inet n - n - - smtpd
+ # Some submission clients may not yet do 2048-bit EDH, if such
+ # clients use your MSA, configure 1024-bit EDH instead:
+ -o smtpd_tls_dh1024_param_file=${config_directory}/dh1024.pem
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ ...
H\bHo\bow\bw c\bca\ban\bn I\bI s\bse\bee\be t\bth\bha\bat\bt a\ba c\bco\bon\bnn\bne\bec\bct\bti\bio\bon\bn h\bha\bas\bs f\bfo\bor\brw\bwa\bar\brd\bd s\bse\bec\bcr\bre\bec\bcy\by?\b?
-X
-
P\bPo\bos\bst\btf\bfi\bix\bx S\bSA\bAS\bSL\bL H\bHo\bow\bwt\bto\bo
-------------------------------------------------------------------------------
# Arguments
# .IP create-missing
# Create missing queue directories with ownerships and permissions
-# according to the contents of $daemon_directory/postfix-files, using
-# the mail_owner and setgid_group parameter settings from the command
-# line, process environment or from the installed main.cf file.
+# according to the contents of $daemon_directory/postfix-files
+# and optionally in $daemon_directory/postfix-files.d/*, using
+# the mail_owner and setgid_group parameter settings from the
+# command line, process environment or from the installed
+# main.cf file.
#
# This is required at Postfix start-up time.
# .IP set-permissions
# Set all file/directory ownerships and permissions according to the
-# contents of $daemon_directory/postfix-files, using the mail_owner
-# and setgid_group parameter settings from the command line, process
-# environment or from the installed main.cf file. Implies create-missing.
+# contents of $daemon_directory/postfix-files and optionally
+# in $daemon_directory/postfix-files.d/*, using the mail_owner
+# and setgid_group parameter settings from the command line,
+# process environment or from the installed main.cf file.
+# Implies create-missing.
#
# This is required when installing Postfix from a pre-built package,
# or when changing the mail_owner or setgid_group installation parameter
# settings after Postfix is already installed.
# .IP upgrade-permissions
# Update ownership and permission of existing files/directories as
-# specified in $daemon_directory/postfix-files, using the mail_owner
-# and setgid_group parameter settings from the command line, process
-# environment or from the installed main.cf file. Implies create-missing.
+# specified in $daemon_directory/postfix-files and optionally
+# in $daemon_directory/postfix-files.d/*, using the mail_owner
+# and setgid_group parameter settings from the command line,
+# process environment or from the installed main.cf file.
+# Implies create-missing.
#
# This is required when upgrading an existing Postfix instance.
# .IP upgrade-configuration
# FILES
# $config_directory/main.cf, Postfix installation parameters.
# $daemon_directory/postfix-files, installation control file.
+# $daemon_directory/postfix-files.d/*, optional control files.
# $config_directory/install.cf, obsolete configuration file.
# LICENSE
# .ad
# Use file/directory status information in $daemon_directory/postfix-files.
test -n "$create" && {
- exec <$daemon_directory/postfix-files || exit 1
- while IFS=: read path type owner group mode flags junk
+ postfix_files_d=$daemon_directory/postfix-files.d
+ for postfix_file in $daemon_directory/postfix-files \
+ `test -d $postfix_files_d && { find $postfix_files_d -type f | sort; }`
do
- IFS="$BACKUP_IFS"
- set_permission=
- # Skip comments. Skip shared files, if updating a secondary instance.
- case $path in
- [$]*) case "$update_shared_files" in
- 1) $debug keep non-shared or shared $path;;
- *) non_shared=
- for name in $NON_SHARED
- do
- case $path in
- "\$$name"*) non_shared=1; break;;
- esac
- done
- case "$non_shared" in
- 1) $debug keep non-shared $path;;
- *) $debug skip shared $path; continue;;
- esac;;
- esac;;
- *) continue;;
- esac
- # Skip hard links and symbolic links.
- case $type in
- [hl]) continue;;
- [df]) ;;
- *) echo unknown type $type for $path in $daemon_directory/postfix-files1>&2; exit 1;;
- esac
- # Expand $name, and canonicalize null fields.
- for name in path owner group flags
+ exec <$postfix_file || exit 1
+ while IFS=: read path type owner group mode flags junk
do
- eval junk=\${$name}
- case $junk in
- [$]*) eval $name=$junk;;
- -) eval $name=;;
- *) ;;
+ IFS="$BACKUP_IFS"
+ set_permission=
+ # Skip comments. Skip shared files, if updating a secondary instance.
+ case $path in
+ [$]*) case "$update_shared_files" in
+ 1) $debug keep non-shared or shared $path;;
+ *) non_shared=
+ for name in $NON_SHARED
+ do
+ case $path in
+ "\$$name"*) non_shared=1; break;;
+ esac
+ done
+ case "$non_shared" in
+ 1) $debug keep non-shared $path;;
+ *) $debug skip shared $path; continue;;
+ esac;;
+ esac;;
+ *) continue;;
esac
- done
- # Skip uninstalled files.
- case $path in
- no|no/*) continue;;
- esac
- # Pick up the flags.
- case $flags in *u*) upgrade_flag=1;; *) upgrade_flag=;; esac
- case $flags in *c*) create_flag=1;; *) create_flag=;; esac
- case $flags in *r*) recursive="-R";; *) recursive=;; esac
- case $flags in *o*) obsolete_flag=1;; *) obsolete_flag=;; esac
- case $flags in *[1i]*) test ! -r "$path" -a "$config_directory" != \
- "$def_config_directory" && continue;; esac
- # Flag obsolete objects. XXX Solaris 2..9 does not have "test -e".
- if [ -n "$obsolete_flag" ]
- then
- test -r $path -a "$type" != "d" && obsolete="$obsolete $path"
- continue;
- else
- keep_list="$keep_list $path"
- fi
- # Create missing directories with proper owner/group/mode settings.
- if [ -n "$create" -a "$type" = "d" -a -n "$create_flag" -a ! -d "$path" ]
- then
- mkdir $path || exit 1
- set_permission=1
- # Update all owner/group/mode settings.
- elif [ -n "$set_perms" ]
- then
- set_permission=1
- # Update obsolete owner/group/mode settings.
- elif [ -n "$upgrade_perms" -a -n "$upgrade_flag" ]
- then
- set_permission=1
- fi
- test -n "$set_permission" && {
- chown $recursive $owner $path || exit 1
- test -z "$group" || chgrp $recursive $group $path || exit 1
- # Don't "chmod -R"; queue file status is encoded in mode bits.
- if [ "$type" = "d" -a -n "$recursive" ]
+ # Skip hard links and symbolic links.
+ case $type in
+ [hl]) continue;;
+ [df]) ;;
+ *) echo unknown type $type for $path in $postfix_file 1>&2; exit 1;;
+ esac
+ # Expand $name, and canonicalize null fields.
+ for name in path owner group flags
+ do
+ eval junk=\${$name}
+ case $junk in
+ [$]*) eval $name=$junk;;
+ -) eval $name=;;
+ *) ;;
+ esac
+ done
+ # Skip uninstalled files.
+ case $path in
+ no|no/*) continue;;
+ esac
+ # Pick up the flags.
+ case $flags in *u*) upgrade_flag=1;; *) upgrade_flag=;; esac
+ case $flags in *c*) create_flag=1;; *) create_flag=;; esac
+ case $flags in *r*) recursive="-R";; *) recursive=;; esac
+ case $flags in *o*) obsolete_flag=1;; *) obsolete_flag=;; esac
+ case $flags in *[1i]*) test ! -r "$path" -a "$config_directory" != \
+ "$def_config_directory" && continue;; esac
+ # Flag obsolete objects. XXX Solaris 2..9 does not have "test -e".
+ if [ -n "$obsolete_flag" ]
then
- find $path -type d -exec chmod $mode "{}" ";"
+ test -r $path -a "$type" != "d" && obsolete="$obsolete $path"
+ continue;
else
- chmod $mode $path
- fi || exit 1
- }
+ keep_list="$keep_list $path"
+ fi
+ # Create missing directories with proper owner/group/mode settings.
+ if [ -n "$create" -a "$type" = "d" -a -n "$create_flag" -a ! -d "$path" ]
+ then
+ mkdir $path || exit 1
+ set_permission=1
+ # Update all owner/group/mode settings.
+ elif [ -n "$set_perms" ]
+ then
+ set_permission=1
+ # Update obsolete owner/group/mode settings.
+ elif [ -n "$upgrade_perms" -a -n "$upgrade_flag" ]
+ then
+ set_permission=1
+ fi
+ test -n "$set_permission" && {
+ chown $recursive $owner $path || exit 1
+ test -z "$group" || chgrp $recursive $group $path || exit 1
+ # Don't "chmod -R"; queue file status is encoded in mode bits.
+ if [ "$type" = "d" -a -n "$recursive" ]
+ then
+ find $path -type d -exec chmod $mode "{}" ";"
+ else
+ chmod $mode $path
+ fi || exit 1
+ }
+ done
+ IFS="$BACKUP_IFS"
done
- IFS="$BACKUP_IFS"
}
# Upgrade existing Postfix configuration files if necessary.
<li> <p> <b> Prime-field groups (EDH):</b> The server needs to be
configured with a suitably-large prime and a corresponding "generator".
-The acronym for forward secrecy over prime fields is EDH or Ephemeral
-Diffie-Hellman (sometimes also abbreviated as DHE). </p>
+The acronym for forward secrecy over prime fields is EDH for Ephemeral
+Diffie-Hellman (also abbreviated as DHE for Diffie-Hellman Exchange).
+</p>
<li> <p> <b> Elliptic-curve groups (EECDH): </b> The server needs
to be configured with a "named curve". These offer better security
at lower computational cost than prime field groups, but are not
as widely implemented. The acronym for the elliptic curve version
-is EECDH which is short for Ephemeral Elliptic Curve Diffie-Hellman.
-</p>
+is EECDH which is short for Ephemeral Elliptic Curve Diffie-Hellman
+(also abbreviated as ECDHE for Elliptic Curve Diffie-Hellman
+Exchange). </p>
</ul>
<p> It is not essential to know what these are, but one does need
-to know that OpenSSL only supports EECDH as of version 1.0.0. Thus
-the configuration parameters related to Elliptic Curve forward secrecy
-are only available when Postfix is linked with OpenSSL 1.0.0 or
-later (provided EC support has not been disabled by the vendor, as
-in some versions of RedHat Linux). </p>
+to know that OpenSSL supports EECDH with version 1.0.0 or later.
+Thus the configuration parameters related to Elliptic-Curve forward
+secrecy are available when Postfix is linked with OpenSSL ≥ 1.0.0
+(provided EC support has not been disabled by the vendor, as in
+some versions of RedHat Linux). </p>
<p> Elliptic curves used in cryptography are typically identified
by a "name" that stands for a set of well-known parameter values,
</ul>
<p> It turns out that (inadvisably-patched in some Debian releases)
-Exim SMTP clients enforce a minimum 2048-bit length for the non-export
+Exim SMTP clients require a ≥ 2048-bit length for the non-export
prime. See the <a href="#quick-start">quick-start</a> section for
the recommended configuration to work around this issue. </p>
<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
-<ul>
+<h3> EECDH Client and server support (Postfix ≥ 2.6 with OpenSSL
+≥ 1.0.0) </h3>
-<li> <p> Postfix 2.6 and 2.7: Enable elliptic-curve support. This
-is the default with Postfix ≥ 2.8.
+<p> With Postfix 2.6 and 2.7, enable elliptic-curve support in the
+Postfix SMTP client and server. This is the default with Postfix
+≥ 2.8.
<blockquote>
<pre>
</pre>
</blockquote>
-<li> <p> Optionally generate non-default EDH parameters for improved
-security against pre-computation attacks and for compatibility with
-Debian-patched EXIM SMTP clients (these require a minimum 2048-bit
-length for the non-export prime). The parameter files are not
-secret, after all these parameters are sent to all SMTP clients in
-the clear. Mode 0644 is fine. </p>
+<h3> EDH Client support (Postfix ≥ 2.2) </h3>
+
+<p> This space intentionally left blank. </p>
+
+<h3> EDH Server support (Postfix ≥ 2.2) </h3>
+
+<p> Optionally generate non-default Postfix SMTP server EDH parameters
+for improved security against pre-computation attacks and for
+compatibility with Debian-patched Exim SMTP clients that require a
+≥ 2048-bit length for the non-export prime. </p>
<p> Execute as root (prime group generation can take a
few seconds to a few minutes): </p>
<blockquote>
<pre>
# cd /etc/postfix
+# umask 022
# openssl dhparam -out dh512.tmp 512 && mv dh512.tmp dh512.pem
# openssl dhparam -out dh1024.tmp 1024 && mv dh1024.tmp dh1024.pem
# openssl dhparam -out dh2048.tmp 2048 && mv dh2048.tmp dh2048.pem
</pre>
</blockquote>
+<p> The Postfix SMTP server EDH parameter files are not secret,
+after all these parameters are sent to all remote SMTP clients in
+the clear. Mode 0644 is fine. </p>
+
<p> You can improve security against pre-computation attacks further
-by regenerating the EDH parameters periodically (an hourly or daily
-cron job running as root can automate this task). </p>
+by regenerating the Postfix SMTP server EDH parameters periodically
+(an hourly or daily cron job running the above commands as root can
+automate this task). </p>
<p> Once the parameters are in place, update <a href="postconf.5.html">main.cf</a> as follows: </p>
</pre>
</blockquote>
-</ul>
-
<h2><a name="test">How can I see that a connection has forward
secrecy? </a> </h2>
order. It excludes null ciphers that only authenticate and don't
encrypt, together with export and low-grade ciphers whose encryption
is too weak to offer meaningful secrecy. The first column shows the
-cipher name, and the second shows the key exchange method. </p>
+cipher name, and the second shows the key exchange method. </p>
<blockquote>
<pre>
-X<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<head>
<li> <p> <b> Prime-field groups (EDH):</b> The server needs to be
configured with a suitably-large prime and a corresponding "generator".
-The acronym for forward secrecy over prime fields is EDH or Ephemeral
-Diffie-Hellman (sometimes also abbreviated as DHE). </p>
+The acronym for forward secrecy over prime fields is EDH for Ephemeral
+Diffie-Hellman (also abbreviated as DHE for Diffie-Hellman Exchange).
+</p>
<li> <p> <b> Elliptic-curve groups (EECDH): </b> The server needs
to be configured with a "named curve". These offer better security
at lower computational cost than prime field groups, but are not
as widely implemented. The acronym for the elliptic curve version
-is EECDH which is short for Ephemeral Elliptic Curve Diffie-Hellman.
-</p>
+is EECDH which is short for Ephemeral Elliptic Curve Diffie-Hellman
+(also abbreviated as ECDHE for Elliptic Curve Diffie-Hellman
+Exchange). </p>
</ul>
<p> It is not essential to know what these are, but one does need
-to know that OpenSSL only supports EECDH as of version 1.0.0. Thus
-the configuration parameters related to Elliptic Curve forward secrecy
-are only available when Postfix is linked with OpenSSL 1.0.0 or
-later (provided EC support has not been disabled by the vendor, as
-in some versions of RedHat Linux). </p>
+to know that OpenSSL supports EECDH with version 1.0.0 or later.
+Thus the configuration parameters related to Elliptic-Curve forward
+secrecy are available when Postfix is linked with OpenSSL ≥ 1.0.0
+(provided EC support has not been disabled by the vendor, as in
+some versions of RedHat Linux). </p>
<p> Elliptic curves used in cryptography are typically identified
by a "name" that stands for a set of well-known parameter values,
</ul>
<p> It turns out that (inadvisably-patched in some Debian releases)
-Exim SMTP clients enforce a minimum 2048-bit length for the non-export
+Exim SMTP clients require a ≥ 2048-bit length for the non-export
prime. See the <a href="#quick-start">quick-start</a> section for
the recommended configuration to work around this issue. </p>
<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
-<ul>
+<h3> EECDH Client and server support (Postfix ≥ 2.6 with OpenSSL
+≥ 1.0.0) </h3>
-<li> <p> Postfix 2.6 and 2.7: Enable elliptic-curve support. This
-is the default with Postfix ≥ 2.8.
+<p> With Postfix 2.6 and 2.7, enable elliptic-curve support in the
+Postfix SMTP client and server. This is the default with Postfix
+≥ 2.8.
<blockquote>
<pre>
</pre>
</blockquote>
-<li> <p> Optionally generate non-default EDH parameters for improved
-security against pre-computation attacks and for compatibility with
-Debian-patched EXIM SMTP clients (these require a minimum 2048-bit
-length for the non-export prime). The parameter files are not
-secret, after all these parameters are sent to all SMTP clients in
-the clear. Mode 0644 is fine. </p>
+<h3> EDH Client support (Postfix ≥ 2.2) </h3>
+
+<p> This space intentionally left blank. </p>
+
+<h3> EDH Server support (Postfix ≥ 2.2) </h3>
+
+<p> Optionally generate non-default Postfix SMTP server EDH parameters
+for improved security against pre-computation attacks and for
+compatibility with Debian-patched Exim SMTP clients that require a
+≥ 2048-bit length for the non-export prime. </p>
<p> Execute as root (prime group generation can take a
few seconds to a few minutes): </p>
<blockquote>
<pre>
# cd /etc/postfix
+# umask 022
# openssl dhparam -out dh512.tmp 512 && mv dh512.tmp dh512.pem
# openssl dhparam -out dh1024.tmp 1024 && mv dh1024.tmp dh1024.pem
# openssl dhparam -out dh2048.tmp 2048 && mv dh2048.tmp dh2048.pem
</pre>
</blockquote>
+<p> The Postfix SMTP server EDH parameter files are not secret,
+after all these parameters are sent to all remote SMTP clients in
+the clear. Mode 0644 is fine. </p>
+
<p> You can improve security against pre-computation attacks further
-by regenerating the EDH parameters periodically (an hourly or daily
-cron job running as root can automate this task). </p>
+by regenerating the Postfix SMTP server EDH parameters periodically
+(an hourly or daily cron job running the above commands as root can
+automate this task). </p>
<p> Once the parameters are in place, update main.cf as follows: </p>
</pre>
</blockquote>
-</ul>
-
<h2><a name="test">How can I see that a connection has forward
secrecy? </a> </h2>
order. It excludes null ciphers that only authenticate and don't
encrypt, together with export and low-grade ciphers whose encryption
is too weak to offer meaningful secrecy. The first column shows the
-cipher name, and the second shows the key exchange method. </p>
+cipher name, and the second shows the key exchange method. </p>
<blockquote>
<pre>
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20131228"
+#define MAIL_RELEASE_DATE "20140104"
#define MAIL_VERSION_NUMBER "2.11"
#ifdef SNAPSHOT
typedef struct SMTP_SESSION {
VSTREAM *stream; /* network connection */
- char *dest; /* nexthop or fallback */
- char *host; /* mail exchanger */
- char *addr; /* mail exchanger */
+ SMTP_ITERATOR *iterator; /* dest, host, addr, port */
char *namaddr; /* mail exchanger */
char *helo; /* helo response */
unsigned port; /* network byte order */
session->namaddrport, STR(session->buffer));
if (var_helpful_warnings)
msg_warn("to prevent loss of mail, turn off command pipelining "
- "for %s with the %s parameter", session->addr,
+ "for %s with the %s parameter",
+ STR(session->iterator->addr),
SMTP_X(EHLO_DIS_MAPS));
}
}
if (msg_verbose)
msg_info("%s: trying: %s...", myname, addr);
- return (smtp_connect_sock(sock, (struct sockaddr *) & sock_un,
+ return (smtp_connect_sock(sock, (struct sockaddr *) &sock_un,
sizeof(sock_un), iter, why, sess_flags));
}
{
const char *myname = "smtp_connect_addr";
struct sockaddr_storage ss; /* remote */
- struct sockaddr *sa = (struct sockaddr *) & ss;
+ struct sockaddr *sa = (struct sockaddr *) &ss;
SOCKADDR_SIZE salen = sizeof(ss);
MAI_HOSTADDR_STR hostaddr;
DNS_RR *addr = iter->rr;
/* smtp_connect_sock - connect a socket over some transport */
-static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr * sa,
+static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa,
int salen,
SMTP_ITERATOR *iter,
DSN_BUF *why,
if (*addr_list && SMTP_RCPT_LEFT(state) > 0
&& (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) != 0) {
session_count = 1;
- smtp_update_addr_list(addr_list, session->addr, session_count);
+ smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
&& *addr_list == 0)
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
SMTP_KEY_MASK_SCACHE_ENDP_LABEL)) != 0) {
session->features |= SMTP_FEATURE_BEST_MX;
session_count += 1;
- smtp_update_addr_list(addr_list, session->addr, session_count);
+ smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
if (*addr_list == 0)
next = 0;
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
const char *myname = "smtp_helo";
SMTP_SESSION *session = state->session;
DELIVER_REQUEST *request = state->request;
+ SMTP_ITERATOR *iter = state->iterator;
SMTP_RESP *resp;
SMTP_RESP fake;
int except;
STR(resp->dsn_buf)[0] = '4';
/* FALLTHROUGH */
default:
- return (smtp_site_fail(state, session->host, resp,
+ return (smtp_site_fail(state, STR(iter->host), resp,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
if (smtp_pix_bug_maps != 0
&& (pix_bug_words =
maps_find(smtp_pix_bug_maps,
- state->session->addr, 0)) != 0) {
+ STR(iter->addr), 0)) != 0) {
pix_bug_source = SMTP_X(PIX_BUG_MAPS);
} else {
pix_bug_words = var_smtp_pix_bug_words;
smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name);
if ((resp = smtp_chat_resp(session))->code / 100 != 2) {
if (resp->code == 421)
- return (smtp_site_fail(state, session->host, resp,
+ return (smtp_site_fail(state, STR(iter->host), resp,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
where = "performing the HELO handshake";
smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name);
if ((resp = smtp_chat_resp(session))->code / 100 != 2)
- return (smtp_site_fail(state, session->host, resp,
+ return (smtp_site_fail(state, STR(iter->host), resp,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
where = "performing the LHLO handshake";
smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name);
if ((resp = smtp_chat_resp(session))->code / 100 != 2)
- return (smtp_site_fail(state, session->host, resp,
+ return (smtp_site_fail(state, STR(iter->host), resp,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
*/
if (smtp_ehlo_dis_maps == 0
|| (ehlo_words = maps_find(smtp_ehlo_dis_maps,
- state->session->addr, 0)) == 0)
+ STR(iter->addr), 0)) == 0)
ehlo_words = var_smtp_ehlo_dis_words;
if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) {
msg_warn("%s: %s map lookup error for %s",
session->state->request->queue_id,
- smtp_ehlo_dis_maps->title, state->session->addr);
+ smtp_ehlo_dis_maps->title, STR(iter->addr));
vstream_longjmp(session->stream, SMTP_ERR_DATA);
}
discard_mask = ehlo_mask(ehlo_words);
if ((session->features & SMTP_FEATURE_STARTTLS) &&
var_smtp_tls_note_starttls_offer &&
session->tls->level <= TLS_LEV_NONE)
- msg_info("Host offered STARTTLS: [%s]", session->host);
+ msg_info("Host offered STARTTLS: [%s]", STR(iter->host));
/*
* Decide whether or not to send STARTTLS.
*/
session->features &= ~SMTP_FEATURE_STARTTLS;
if (TLS_REQUIRED(session->tls->level))
- return (smtp_site_fail(state, session->host, resp,
+ return (smtp_site_fail(state, STR(iter->host), resp,
"TLS is required, but host %s refused to start TLS: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
static int smtp_start_tls(SMTP_STATE *state)
{
SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
TLS_CLIENT_START_PROPS tls_props;
VSTRING *serverid;
SMTP_RESP fake;
timeout = var_smtp_starttls_tmout,
tls_level = session->tls->level,
nexthop = session->tls_nexthop,
- host = session->host,
+ host = STR(iter->host),
namaddr = session->namaddrport,
serverid = vstring_str(serverid),
helo = session->helo,
const char *myname = "smtp_loop";
DELIVER_REQUEST *request = state->request;
SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
SMTP_RESP *resp;
RECIPIENT *rcpt;
VSTRING *next_command = vstring_alloc(100);
*/
case SMTP_STATE_MAIL:
if (resp->code / 100 != 2) {
- smtp_mesg_fail(state, session->host, resp,
+ smtp_mesg_fail(state, STR(iter->host), resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
smtp_rcpt_done(state, resp, rcpt);
}
} else {
- smtp_rcpt_fail(state, rcpt, session->host, resp,
+ smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
case SMTP_STATE_DATA:
if (resp->code / 100 != 3) {
if (nrcpt > 0)
- smtp_mesg_fail(state, session->host, resp,
+ smtp_mesg_fail(state, STR(iter->host), resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
if (smtp_mode) {
if (nrcpt > 0) {
if (resp->code / 100 != 2) {
- smtp_mesg_fail(state, session->host, resp,
+ smtp_mesg_fail(state, STR(iter->host), resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
rcpt = request->rcpt_list.info
+ survivors[recv_done++];
if (resp->code / 100 != 2) {
- smtp_rcpt_fail(state, rcpt, session->host, resp,
+ smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
{
DELIVER_REQUEST *request = state->request;
SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
DSN_BUF *why = state->why;
const char *dsn_action = "relayed";
int status;
*
* Note: the DSN action is ignored in case of address probes.
*/
- dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, session->host,
+ dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host),
DSB_DTYPE_SMTP, resp->str, "%s", resp->str);
status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
const char *label)
{
const char *myname = "smtp_reuse_common";
+ SMTP_ITERATOR *iter = state->iterator;
SMTP_SESSION *session;
/*
/*
* Update the list of used cached addresses.
*/
- htable_enter(state->cache_used, session->addr, (char *) 0);
+ htable_enter(state->cache_used, STR(iter->addr), (char *) 0);
return (session);
}
int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache,
const SMTP_SESSION *session)
{
+ SMTP_ITERATOR *iter = session->iterator;
char *key;
const char *entry;
int valid = 0;
- key = smtp_sasl_auth_cache_make_key(session->host, session->sasl_username);
+ key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
if ((entry = dict_get(auth_cache->dict, key)) != 0)
if ((valid = smtp_sasl_auth_cache_valid_value(auth_cache, entry,
session->sasl_passwd)) == 0)
const SMTP_SESSION *session,
const SMTP_RESP *resp)
{
+ SMTP_ITERATOR *iter = session->iterator;
char *key;
char *value;
- key = smtp_sasl_auth_cache_make_key(session->host, session->sasl_username);
+ key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
value = smtp_sasl_auth_cache_make_value(session->sasl_passwd,
resp->dsn, resp->str);
dict_put(auth_cache->dict, key, value);
{
const char *myname = "smtp_sasl_passwd_lookup";
SMTP_STATE *state = session->state;
+ SMTP_ITERATOR *iter = session->iterator;
const char *value;
char *passwd;
state->request->sender, (char **) 0)) != 0)
|| (smtp_sasl_passwd_map->error == 0
&& (value = maps_find(smtp_sasl_passwd_map,
- session->host, 0)) != 0)
+ STR(iter->host), 0)) != 0)
|| (smtp_sasl_passwd_map->error == 0
&& (value = maps_find(smtp_sasl_passwd_map,
- session->dest, 0)) != 0)) {
+ STR(iter->dest), 0)) != 0)) {
if (session->sasl_username)
myfree(session->sasl_username);
session->sasl_username = mystrdup(value);
session->sasl_passwd = mystrdup(passwd ? passwd : "");
if (msg_verbose)
msg_info("%s: host `%s' user `%s' pass `%s'",
- myname, session->host,
+ myname, STR(iter->host),
session->sasl_username, session->sasl_passwd);
return (1);
} else if (smtp_sasl_passwd_map->error) {
} else {
if (msg_verbose)
msg_info("%s: no auth info found (sender=`%s', host=`%s')",
- myname, state->request->sender, session->host);
+ myname, state->request->sender, STR(iter->host));
return (0);
}
}
const char *sasl_opts_val)
{
XSASL_CLIENT_CREATE_ARGS create_args;
+ SMTP_ITERATOR *iter = session->iterator;
if (msg_verbose)
msg_info("starting new SASL client");
XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args,
stream = session->stream,
service = var_procname,
- server_name = session->host,
+ server_name = STR(iter->host),
security_options = sasl_opts_val)) == 0)
msg_fatal("SASL per-connection initialization failed");
session->sasl_reply = vstring_alloc(20);
int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
{
const char *myname = "smtp_sasl_authenticate";
+ SMTP_ITERATOR *iter = session->iterator;
SMTP_RESP *resp;
const char *mechanism;
int result;
if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
resp_dsn[0] = '4';
dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
- session->host, var_procname, resp_str,
+ STR(iter->host), var_procname, resp_str,
"SASL [CACHED] authentication failed; server %s said: %s",
- session->host, resp_str);
+ STR(iter->host), resp_str);
return (0);
}
#endif
if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
STR(resp->dsn_buf)[0] = '4';
dsb_update(why, resp->dsn, DSB_DEF_ACTION,
- DSB_MTYPE_DNS, session->host,
+ DSB_MTYPE_DNS, STR(iter->host),
var_procname, resp->str,
"SASL authentication failed; server %s said: %s",
session->namaddr, resp->str);
time_t start, int flags)
{
SMTP_SESSION *session;
- const char *dest = STR(iter->dest);
const char *host = STR(iter->host);
const char *addr = STR(iter->addr);
unsigned port = iter->port;
session = (SMTP_SESSION *) mymalloc(sizeof(*session));
session->stream = stream;
- session->dest = mystrdup(dest);
- session->host = mystrdup(host);
- session->addr = mystrdup(addr);
+ session->iterator = iter;
session->namaddr = concatenate(host, "[", addr, "]", (char *) 0);
session->helo = 0;
session->port = port;
#endif
if (session->stream)
vstream_fclose(session->stream);
- myfree(session->dest);
- myfree(session->host);
- myfree(session->addr);
myfree(session->namaddr);
myfree(session->namaddrport);
if (session->helo)
int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
VSTRING *endp_prop)
{
+ SMTP_ITERATOR *iter = session->iterator;
int fd;
/*
*
*/
vstring_sprintf(dest_prop, "%s\n%s\n%s\n%u",
- session->dest, session->host, session->addr,
+ STR(iter->dest), STR(iter->host), STR(iter->addr),
session->features & SMTP_FEATURE_DESTINATION_MASK);
/*
LIB = libtls.a
TESTPROG= tls_dh tls_mgr tls_rsa tls_dane
-LIBS = ../../lib/libglobal.a ../../lib/libutil.a ../../lib/libdns.a
+LIBS = ../../lib/libdns.a ../../lib/libglobal.a ../../lib/libutil.a
LIB_DIR = ../../lib
INC_DIR = ../../include
MAKES =
"\n\treset (discard pending requests)" \
"\n\trun (execute pending requests in interleaved order)" \
"\n\n\tTo add a pending request:" \
- "\n\tquery <key-prefix> <count> (negative to reverse order)" \
- "\n\tupdate <key-prefix> <count> (negative to reverse order)" \
- "\n\tdelete <key-prefix> <count> (negative to reverse order)" \
- "\n\tpurge <key-prefix>" \
- "\n\tcount <key-prefix>"
+ "\n\tquery <key-suffix> <count> (negative to reverse order)" \
+ "\n\tupdate <key-suffix> <count> (negative to reverse order)" \
+ "\n\tdelete <key-suffix> <count> (negative to reverse order)" \
+ "\n\tpurge <key-suffix>" \
+ "\n\tcount <key-suffix>"
/*
* For realism, open the cache with the same flags as postscreen(8) and
int flags; /* per-request: reverse, purge */
char *cmd; /* command for status report */
void (*action) (struct DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
- char *prefix; /* key prefix */
+ char *suffix; /* key suffix */
int done; /* progress indicator */
int todo; /* number of entries to process */
int first_next; /* first/next */
msg_panic("make_tagged_key: bad done count: %d", cp->done);
if (cp->todo < 1)
msg_panic("make_tagged_key: bad todo count: %d", cp->todo);
- vstring_sprintf(bp, "%s-%d", cp->prefix,
+ vstring_sprintf(bp, "%d-%s",
(cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
- cp->todo - cp->done - 1 : cp->done);
+ cp->todo - cp->done - 1 : cp->done, cp->suffix);
}
/* create_requests - create request list */
cp->flags = 0;
cp->cmd = 0;
cp->action = 0;
- cp->prefix = 0;
+ cp->suffix = 0;
cp->todo = 0;
cp->first_next = DICT_SEQ_FUN_FIRST;
}
cp->cmd = 0;
}
cp->action = 0;
- if (cp->prefix) {
- myfree(cp->prefix);
- cp->prefix = 0;
+ if (cp->suffix) {
+ myfree(cp->suffix);
+ cp->suffix = 0;
}
cp->todo = 0;
cp->first_next = DICT_SEQ_FUN_FIRST;
#endif
vstream_printf("cache\t%s\n", dp ? dp->name : "(none)");
- vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n",
- "cmd", "dir", "prefix", "count", "done", "first/next");
+ if (tp->used == 0)
+ vstream_printf("No pending requests\n");
+ else
+ vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n",
+ "cmd", "dir", "suffix", "count", "done", "first/next");
for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++)
if (cp->todo > 0)
cp->cmd,
(cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
"reverse" : "forward",
- cp->prefix ? cp->prefix : "(null)", cp->todo,
+ cp->suffix ? cp->suffix : "(null)", cp->todo,
cp->done, cp->first_next);
}
cp->done += 1;
}
-/* iter_action - iterate over cache and act on entries with given prefix */
+/* iter_action - iterate over cache and act on entries with given suffix */
static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
{
const char *cache_key;
const char *cache_val;
const char *what;
- int len;
+ const char *suffix;
if (dict_cache_sequence(dp, cp->first_next, &cache_key, &cache_val) == 0) {
if (strcmp(cache_key, cache_val) != 0)
msg_warn("value \"%s\" differs from key \"%s\"",
cache_val, cache_key);
- len = strlen(cp->prefix);
- if (strncmp(cache_key, cp->prefix, len) == 0 && cache_key[len] == '-') {
+ suffix = cache_key + strspn(cache_key, "0123456789");
+ if (suffix[0] == '-' && strcmp(suffix + 1, cp->suffix) == 0) {
cp->done += 1;
cp->todo = cp->done + 1; /* XXX */
if ((cp->flags & DICT_CACHE_SREQ_FLAG_PURGE)
if (dp->error)
msg_warn("%s error after %d: %m", what, cp->done);
else
- vstream_printf("prefix=%s %s=%d\n", cp->prefix, what, cp->done);
+ vstream_printf("suffix=%s %s=%d\n", cp->suffix, what, cp->done);
cp->todo = 0;
}
}
int req_flags;
int count;
char *cmd = argv->argv[0];
- char *prefix = (argv->argc > 1 ? argv->argv[1] : 0);
+ char *suffix = (argv->argc > 1 ? argv->argv[1] : 0);
char *todo = (argv->argc > 2 ? argv->argv[2] : "1"); /* XXX */
if (tp->used >= tp->size) {
cp = tp->job_list + tp->used;
cp->cmd = mystrdup(cmd);
cp->action = rp->action;
- if (prefix)
- cp->prefix = mystrdup(prefix);
+ if (suffix)
+ cp->suffix = mystrdup(suffix);
cp->done = 0;
cp->flags = req_flags;
cp->todo = count;
/*
/* slmdb_cursor_get() is an mdb_cursor_get() wrapper with
/* automatic error recovery. The result value is an LMDB
-/* status code (zero in case of success).
+/* status code (zero in case of success). This wrapper supports
+/* only one cursor per database.
/*
/* slmdb_fd() returns the file descriptor for the specified
/* database. This may be used for file status queries or
#include <unistd.h>
#include <limits.h>
#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
/* Application-specific. */
return (status); \
} while (0)
+ /*
+ * We must close the cursor's read transaction before writing to the
+ * database with MDB_NOLOCK, and before changing the memory map size. Our
+ * database iterator saves the key under the last cursor position, and
+ * restores the cursor if needed. This supports only one cursor per
+ * database.
+ */
+
+/* slmdb_cursor_close - close cursor and its read transaction */
+
+static void slmdb_cursor_close(SLMDB *slmdb)
+{
+ MDB_txn *txn;
+
+ /*
+ * Close the cursor and its read transaction. We can restore it later
+ * from the saved key information.
+ */
+ txn = mdb_cursor_txn(slmdb->cursor);
+ mdb_cursor_close(slmdb->cursor);
+ slmdb->cursor = 0;
+ mdb_txn_abort(txn);
+}
+
+/* slmdb_saved_key_init - initialize saved key info */
+
+static void slmdb_saved_key_init(SLMDB *slmdb)
+{
+ slmdb->saved_key.mv_data = 0;
+ slmdb->saved_key_size = 0;
+}
+
+/* slmdb_saved_key_free - destroy saved key info */
+
+static void slmdb_saved_key_free(SLMDB *slmdb)
+{
+ free(slmdb->saved_key.mv_data);
+ slmdb->saved_key.mv_data = 0;
+ slmdb->saved_key_size = 0;
+}
+
+#define HAVE_SLMDB_SAVED_KEY(s) ((s)->saved_key.mv_data != 0)
+
+/* slmdb_saved_key_assign - copy the saved key */
+
+static int slmdb_saved_key_assign(SLMDB *slmdb, MDB_val *key_val)
+{
+
+ /*
+ * Extend the buffer to fit the key, so that we can avoid malloc()
+ * overhead most of the time.
+ */
+ if (slmdb->saved_key_size < key_val->mv_size) {
+ if (slmdb->saved_key.mv_data == 0)
+ slmdb->saved_key.mv_data = malloc(key_val->mv_size);
+ else
+ slmdb->saved_key.mv_data =
+ realloc(slmdb->saved_key.mv_data, key_val->mv_size);
+ if (slmdb->saved_key.mv_data == 0) {
+ slmdb->saved_key_size = 0;
+ return (ENOMEM);
+ } else {
+ slmdb->saved_key_size = key_val->mv_size;
+ }
+ }
+
+ /*
+ * Copy the key under the cursor.
+ */
+ memcpy(slmdb->saved_key.mv_data, key_val->mv_data, key_val->mv_size);
+ slmdb->saved_key.mv_size = key_val->mv_size;
+ return (0);
+}
+
/* slmdb_prepare - LMDB-specific (re)initialization before actual access */
static int slmdb_prepare(SLMDB *slmdb)
{
MDB_envinfo info;
+ /*
+ * Close the cursor and its read transaction before changing the memory
+ * map size. We can restore it later with the saved key information.
+ */
+ if (slmdb->cursor != 0)
+ slmdb_cursor_close(slmdb);
+
/*
* Recover bulk transactions only if they can be restarted. Limit the
* number of recovery attempts per slmdb(3) API request.
else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
SLMDB_API_RETURN(slmdb, status);
+ /*
+ * Before doing a non-bulk write transaction in MDB_NOLOCK mode, close a
+ * cursor and its read transaction. We can restore it later with the
+ * saved key information.
+ */
+ if (slmdb->cursor != 0 && slmdb->txn == 0
+ && (slmdb->lmdb_flags & MDB_NOLOCK))
+ slmdb_cursor_close(slmdb);
+
/*
* Do the update.
*/
else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
SLMDB_API_RETURN(slmdb, status);
+ /*
+ * Before doing a non-bulk write transaction in MDB_NOLOCK mode, close a
+ * cursor and its read transaction. We can restore it later with the
+ * saved key information.
+ */
+ if (slmdb->cursor != 0 && slmdb->txn == 0
+ && (slmdb->lmdb_flags & MDB_NOLOCK))
+ slmdb_cursor_close(slmdb);
+
/*
* Do the update.
*/
* Open a read transaction and cursor if needed.
*/
if (slmdb->cursor == 0) {
- slmdb_txn_begin(slmdb, MDB_RDONLY, &txn);
+ if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
if ((status = mdb_cursor_open(txn, slmdb->dbi, &slmdb->cursor)) != 0) {
mdb_txn_abort(txn);
if ((status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
SLMDB_API_RETURN(slmdb, status);
}
+
+ /*
+ * Restore the cursor to the saved key position.
+ */
+ if (HAVE_SLMDB_SAVED_KEY(slmdb) && op != MDB_FIRST) {
+ if ((status = mdb_cursor_get(slmdb->cursor, &slmdb->saved_key,
+ (MDB_val *) 0, MDB_SET)) != 0) {
+ slmdb_cursor_close(slmdb);
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
+ SLMDB_API_RETURN(slmdb, status);
+ }
+ }
}
/*
*/
status = mdb_cursor_get(slmdb->cursor, mdb_key, mdb_value, op);
+ /*
+ * Save the cursor position. This can fail only with ENOMEM.
+ */
+ if (status == 0)
+ status = slmdb_saved_key_assign(slmdb, mdb_key);
+
/*
* Handle end-of-database or other error.
*/
- if (status != 0) {
+ else {
if (status == MDB_NOTFOUND) {
- txn = mdb_cursor_txn(slmdb->cursor);
- mdb_cursor_close(slmdb->cursor);
- mdb_txn_abort(txn);
- slmdb->cursor = 0;
+ slmdb_cursor_close(slmdb);
+ if (HAVE_SLMDB_SAVED_KEY(slmdb))
+ slmdb_saved_key_free(slmdb);
} else {
if ((status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
/*
* Clean up after an unfinished sequence() operation.
*/
- if (slmdb->cursor) {
- MDB_txn *txn = mdb_cursor_txn(slmdb->cursor);
+ if (slmdb->cursor != 0)
+ slmdb_cursor_close(slmdb);
- mdb_cursor_close(slmdb->cursor);
- mdb_txn_abort(txn);
- }
mdb_env_close(slmdb->env);
+ /*
+ * Clean up the saved key position.
+ */
+ if (HAVE_SLMDB_SAVED_KEY(slmdb))
+ slmdb_saved_key_free(slmdb);
+
SLMDB_API_RETURN(slmdb, status);
}
slmdb->dbi = dbi;
slmdb->db_fd = db_fd;
slmdb->cursor = 0;
+ slmdb_saved_key_init(slmdb);
slmdb->api_retry_count = 0;
slmdb->bulk_retry_count = 0;
slmdb->api_retry_limit = SLMDB_DEF_API_RETRY_LIMIT;
return (status);
}
+#endif
+
+ /*
+ * Implementation-dependent workaround to debug LMDB assert() failures. The
+ * code below prevents daemons from disappearing without logfile record.
+ */
+#ifdef LMDB_ASSERT_WORKAROUND
+
+#include <assert.h>
+
+void __assert(const char *func, const char *file, int line, const char *text)
+{
+ msg_panic("Assertion failed: %s, function %s, file %s, line %d.",
+ text, func, file, line);
+}
+
#endif
#define SLMDB_JMP_BUF sigjmp_buf
#endif
+ /*
+ * All data structure members are private.
+ */
typedef struct {
size_t curr_limit; /* database soft size limit */
int size_incr; /* database expansion factor */
MDB_txn *txn; /* bulk transaction */
int db_fd; /* database file handle */
MDB_cursor *cursor; /* iterator */
+ MDB_val saved_key; /* saved cursor key buffer */
+ size_t saved_key_size; /* saved cursor key buffer size */
void (*longjmp_fn) (void *, int);/* exception handling */
void (*notify_fn) (void *, int,...); /* workaround notification */
void *cb_context; /* call-back context */