]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.11-20140104
authorWietse Venema <wietse@porcupine.org>
Sat, 4 Jan 2014 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Sun, 5 Jan 2014 22:22:34 +0000 (17:22 -0500)
21 files changed:
postfix/HISTORY
postfix/README_FILES/FORWARD_SECRECY_README
postfix/README_FILES/SASL_README
postfix/conf/post-install
postfix/html/FORWARD_SECRECY_README.html
postfix/html/SASL_README.html
postfix/proto/FORWARD_SECRECY_README.html
postfix/src/global/mail_version.h
postfix/src/smtp/smtp.h
postfix/src/smtp/smtp_chat.c
postfix/src/smtp/smtp_connect.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_rcpt.c
postfix/src/smtp/smtp_reuse.c
postfix/src/smtp/smtp_sasl_auth_cache.c
postfix/src/smtp/smtp_sasl_glue.c
postfix/src/smtp/smtp_session.c
postfix/src/tls/Makefile.in
postfix/src/util/dict_cache.c
postfix/src/util/slmdb.c
postfix/src/util/slmdb.h

index eca39850f28f53eb5ebb502c499f4354d1913b77..77a10aeffe9f109309069a6b7df0a8658ed22568 100644 (file)
@@ -19462,10 +19462,33 @@ Apologies for any names omitted.
        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.
index 785c572acabef6edbfeb100b0d2f908b88c0d80d..d59c5a6827043d07e256db1ed39011ec8fff0611 100644 (file)
@@ -82,20 +82,21 @@ element of that group called a "generator". Presently, there are two flavors of
 
   * 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
@@ -141,9 +142,8 @@ configured overrides.
     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
 
@@ -198,50 +198,60 @@ a case-by-case basis via the TLS policy table.
 
 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?
 
index 9e93b2326773d589c0dda012d27bc3a02b1a4251..dc0ab41e0957898468b4f9ee312896d19ea8627c 100644 (file)
@@ -1,5 +1,3 @@
-X
-
 P\bPo\bos\bst\btf\bfi\bix\bx S\bSA\bAS\bSL\bL H\bHo\bow\bwt\bto\bo
 
 -------------------------------------------------------------------------------
index 91ff4a6772ff6ecb9a467ce57248eb3914cc36c9..7e79c92cd14b1cb3507934f087d77d98d6204a7d 100644 (file)
 #      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
@@ -437,91 +444,96 @@ test -n "$override" && {
 # 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.
index 42e132041e8ac61356b205f40269bab4cf07a545..40613f1ffa1fb7d4b2a8b1e7767ccb692febbb9a 100644 (file)
@@ -125,24 +125,26 @@ Presently, there are two flavors of "groups" that work with PFS: </p>
 
 <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 &ge; 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,
@@ -200,7 +202,7 @@ parameter file and the prime need not actually be 1024 bits long
 </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 &ge; 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>
 
@@ -269,10 +271,12 @@ href="TLS_README.html#client_tls_policy">TLS policy</a> table. </p>
 
 <h2><a name="quick-start">Getting started, quick and dirty</a></h2>
 
-<ul>
+<h3> EECDH Client and server support (Postfix &ge; 2.6 with OpenSSL
+&ge; 1.0.0) </h3>
 
-<li> <p> Postfix 2.6 and 2.7: Enable elliptic-curve support. This
-is the default with Postfix &ge; 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
+&ge; 2.8.
 
 <blockquote>
 <pre>
@@ -282,12 +286,16 @@ is the default with Postfix &ge; 2.8.
 </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 &ge; 2.2) </h3>
+
+<p> This space intentionally left blank. </p>
+
+<h3> EDH Server support (Postfix &ge; 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
+&ge; 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>
@@ -295,6 +303,7 @@ 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
@@ -302,9 +311,14 @@ few seconds to a few minutes): </p>
 </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>
 
@@ -332,8 +346,6 @@ need to adjust the submission entry in <a href="master.5.html">master.cf</a> acc
 </pre>
 </blockquote>
 
-</ul>
-
 <h2><a name="test">How can I see that a connection has forward
 secrecy? </a> </h2>
 
@@ -392,7 +404,7 @@ OpenSSL 1.0.1e.  The list is sorted in the default Postfix preference
 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>
index e3a7eca4124cef46dd7115ffefd8c15b3e4cd5cb..37b374740702b400d8fcce94ee772b24cbf41ae8 100644 (file)
@@ -1,4 +1,4 @@
-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>
index 6b33f61ada7dd9ff6e190a61a0a9910de35f439d..4aa9012fe890ae77dba5b8cd22fdbf69bf958d41 100644 (file)
@@ -125,24 +125,26 @@ Presently, there are two flavors of "groups" that work with PFS: </p>
 
 <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 &ge; 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,
@@ -200,7 +202,7 @@ parameter file and the prime need not actually be 1024 bits long
 </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 &ge; 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>
 
@@ -269,10 +271,12 @@ href="TLS_README.html#client_tls_policy">TLS policy</a> table. </p>
 
 <h2><a name="quick-start">Getting started, quick and dirty</a></h2>
 
-<ul>
+<h3> EECDH Client and server support (Postfix &ge; 2.6 with OpenSSL
+&ge; 1.0.0) </h3>
 
-<li> <p> Postfix 2.6 and 2.7: Enable elliptic-curve support. This
-is the default with Postfix &ge; 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
+&ge; 2.8.
 
 <blockquote>
 <pre>
@@ -282,12 +286,16 @@ is the default with Postfix &ge; 2.8.
 </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 &ge; 2.2) </h3>
+
+<p> This space intentionally left blank. </p>
+
+<h3> EDH Server support (Postfix &ge; 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
+&ge; 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>
@@ -295,6 +303,7 @@ 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
@@ -302,9 +311,14 @@ few seconds to a few minutes): </p>
 </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>
 
@@ -332,8 +346,6 @@ need to adjust the submission entry in master.cf accordingly: </p>
 </pre>
 </blockquote>
 
-</ul>
-
 <h2><a name="test">How can I see that a connection has forward
 secrecy? </a> </h2>
 
@@ -392,7 +404,7 @@ OpenSSL 1.0.1e.  The list is sorted in the default Postfix preference
 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>
index c4e730f26e306483f8acc8a0fba4fa48f05ed192..8254d0187a9523345e60fdadd4795a16da507796 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20131228"
+#define MAIL_RELEASE_DATE      "20140104"
 #define MAIL_VERSION_NUMBER    "2.11"
 
 #ifdef SNAPSHOT
index c694708304ee42db0a88aeda552b04362a658f9f..336a4f47fad4182b0a9f151ab65cc7c245faaec3 100644 (file)
@@ -301,9 +301,7 @@ extern HBC_CHECKS *smtp_body_checks;        /* limited body checks */
 
 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 */
index 11dc909989e1d81fd39432a750e5ec6fe5eabcbf..5e2f82c7d9bc4b59a0ee3f07ef6a50fdd669b56a 100644 (file)
@@ -363,7 +363,8 @@ SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session)
                     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));
        }
     }
index 70e8dc56db02a5839260fc8d10d9c75d21e096ec..ff278c1ff028e553ba6027a324182d012e55db60 100644 (file)
@@ -159,7 +159,7 @@ static SMTP_SESSION *smtp_connect_unix(SMTP_ITERATOR *iter, DSN_BUF *why,
     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));
 }
 
@@ -170,7 +170,7 @@ static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why,
 {
     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;
@@ -274,7 +274,7 @@ static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why,
 
 /* 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,
@@ -668,7 +668,7 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
     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;
@@ -726,7 +726,7 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
                                   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)
index 5d442df4116cdcccb41099f42c35a55a3741a426..1f9759d38acc1dfe2edce5cc8cba7964daec17c5 100644 (file)
@@ -261,6 +261,7 @@ int     smtp_helo(SMTP_STATE *state)
     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;
@@ -322,7 +323,7 @@ int     smtp_helo(SMTP_STATE *state)
                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", " ")));
@@ -350,7 +351,7 @@ int     smtp_helo(SMTP_STATE *state)
            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;
@@ -415,7 +416,7 @@ int     smtp_helo(SMTP_STATE *state)
            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", " ")));
@@ -427,7 +428,7 @@ int     smtp_helo(SMTP_STATE *state)
            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", " ")));
@@ -436,7 +437,7 @@ int     smtp_helo(SMTP_STATE *state)
        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", " ")));
@@ -454,12 +455,12 @@ int     smtp_helo(SMTP_STATE *state)
         */
        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);
@@ -643,7 +644,7 @@ int     smtp_helo(SMTP_STATE *state)
        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.
@@ -690,7 +691,7 @@ int     smtp_helo(SMTP_STATE *state)
             */
            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", " ")));
@@ -739,6 +740,7 @@ int     smtp_helo(SMTP_STATE *state)
 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;
@@ -805,7 +807,7 @@ static int smtp_start_tls(SMTP_STATE *state)
                         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,
@@ -1142,6 +1144,7 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
     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);
@@ -1652,7 +1655,7 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
                     */
                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", " "),
@@ -1724,7 +1727,7 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
                                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", " "),
@@ -1746,7 +1749,7 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
                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", " "),
@@ -1770,7 +1773,7 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
                    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", " "),
@@ -1797,7 +1800,7 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
                            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", " "),
index ec6d2a47c4fd786198ec1bcb3fd3dd59f2cb806b..acb125236bea9293e65ff77d989d46888a73b826 100644 (file)
@@ -132,6 +132,7 @@ void    smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt)
 {
     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;
@@ -162,7 +163,7 @@ void    smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt)
      * 
      * 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),
index eeadbb20d4678445516843ad6d0ee048c05d4abe..3f1270678ced68cfffe16e6de218cf93a2165ea0 100644 (file)
@@ -155,6 +155,7 @@ static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd,
                                               const char *label)
 {
     const char *myname = "smtp_reuse_common";
+    SMTP_ITERATOR *iter = state->iterator;
     SMTP_SESSION *session;
 
     /*
@@ -200,7 +201,7 @@ static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd,
     /*
      * 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);
 }
index 562d6f3a5a85fe78121d4453eae8269c28b989e1..520f8b6e5f61c5e2bfaddcee0da9a58dc14dd53f 100644 (file)
@@ -230,11 +230,12 @@ static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache,
 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)
@@ -255,10 +256,11 @@ void    smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache,
                                           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);
index 1cf6adac90bcdca408243959dd169b07827b6769..5254341d58128a5637c1f4a4d71f863ac77adf96 100644 (file)
@@ -158,6 +158,7 @@ int     smtp_sasl_passwd_lookup(SMTP_SESSION *session)
 {
     const char *myname = "smtp_sasl_passwd_lookup";
     SMTP_STATE *state = session->state;
+    SMTP_ITERATOR *iter = session->iterator;
     const char *value;
     char   *passwd;
 
@@ -187,10 +188,10 @@ int     smtp_sasl_passwd_lookup(SMTP_SESSION *session)
                                 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);
@@ -200,7 +201,7 @@ int     smtp_sasl_passwd_lookup(SMTP_SESSION *session)
        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) {
@@ -210,7 +211,7 @@ int     smtp_sasl_passwd_lookup(SMTP_SESSION *session)
     } 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);
     }
 }
@@ -284,6 +285,7 @@ void    smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
                                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");
@@ -291,7 +293,7 @@ void    smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
         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);
@@ -302,6 +304,7 @@ void    smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
 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;
@@ -330,9 +333,9 @@ int     smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
        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
@@ -416,7 +419,7 @@ int     smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
        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);
index 2336f0b0a7d2545a2a34f2c51e9d59928f4dad0c..535e8b6289b6f00e0180ccaceb53da133dcb785e 100644 (file)
@@ -123,16 +123,13 @@ SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, SMTP_ITERATOR *iter,
                                         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;
@@ -191,9 +188,6 @@ void    smtp_session_free(SMTP_SESSION *session)
 #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)
@@ -221,6 +215,7 @@ void    smtp_session_free(SMTP_SESSION *session)
 int     smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
                                       VSTRING *endp_prop)
 {
+    SMTP_ITERATOR *iter = session->iterator;
     int     fd;
 
     /*
@@ -238,7 +233,7 @@ int     smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
      * 
      */
     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);
 
     /*
index 26e5d5a45a530bac2e8caa9923f1f8cd6bcb89a6..1af94190268abe0b086699a1a3343e8f03eaaa1d 100644 (file)
@@ -19,7 +19,7 @@ INCL  =
 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  =
index 40cfced391ddcdb1ac4c3f7aff76b2c245642084..7a8178c936a3efdbdd3c10d87f01d9f49dbf7759 100644 (file)
@@ -705,11 +705,11 @@ const char *dict_cache_name(DICT_CACHE *cp)
                "\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
@@ -725,7 +725,7 @@ typedef struct DICT_CACHE_SREQ {
     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 */
@@ -772,9 +772,9 @@ static void make_tagged_key(VSTRING *bp, DICT_CACHE_SREQ *cp)
        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 */
@@ -793,7 +793,7 @@ static DICT_CACHE_TEST *create_requests(int count)
        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;
     }
@@ -815,9 +815,9 @@ static void reset_requests(DICT_CACHE_TEST *tp)
            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;
@@ -876,8 +876,11 @@ static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp)
 #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)
@@ -885,7 +888,7 @@ static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp)
                           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);
 }
 
@@ -936,21 +939,21 @@ static void delete_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
     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)
@@ -967,7 +970,7 @@ static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
        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;
     }
 }
@@ -1001,7 +1004,7 @@ static void add_request(DICT_CACHE_TEST *tp, ARGV *argv)
     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) {
@@ -1034,8 +1037,8 @@ static void add_request(DICT_CACHE_TEST *tp, ARGV *argv)
     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;
index 5259d3a7643293b6a7a771e8c9e4f6b0cd05b84c..abb5eeef9ef5d9658cbf9d79ec14ca92b0ba2e6c 100644 (file)
@@ -82,7 +82,8 @@
 /*
 /*     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)
@@ -295,6 +372,13 @@ static int slmdb_recover(SLMDB *slmdb, int status)
 {
     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.
@@ -456,6 +540,15 @@ int     slmdb_put(SLMDB *slmdb, MDB_val *mdb_key,
     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.
      */
@@ -493,6 +586,15 @@ int     slmdb_del(SLMDB *slmdb, MDB_val *mdb_key)
     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.
      */
@@ -527,13 +629,27 @@ int     slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key,
      * 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);
+           }
+       }
     }
 
     /*
@@ -541,15 +657,20 @@ int     slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key,
      */
     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);
@@ -613,14 +734,17 @@ int     slmdb_close(SLMDB *slmdb)
     /*
      * 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);
 }
 
@@ -703,6 +827,7 @@ int     slmdb_open(SLMDB *slmdb, const char *path, int open_flags,
     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;
@@ -718,4 +843,20 @@ int     slmdb_open(SLMDB *slmdb, const char *path, int open_flags,
     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
index 40fb1aeb6153d912b3bc12c22650f7a3562a6644..6f5ad5cfaa26479bfc1c649076ec0cb1b5a0c5f1 100644 (file)
@@ -31,6 +31,9 @@
 #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 */
@@ -43,6 +46,8 @@ typedef struct {
     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 */