]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.7-20210612
authorWietse Venema <wietse@porcupine.org>
Sat, 12 Jun 2021 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Wed, 19 Jan 2022 06:36:07 +0000 (01:36 -0500)
23 files changed:
postfix/HISTORY
postfix/README_FILES/BDAT_README
postfix/WISHLIST
postfix/conf/master.cf
postfix/html/BDAT_README.html
postfix/html/cidr_table.5.html
postfix/html/postconf.5.html
postfix/html/regexp_table.5.html
postfix/html/smtpd.8.html
postfix/man/man5/cidr_table.5
postfix/man/man5/postconf.5
postfix/man/man5/regexp_table.5
postfix/man/man8/smtpd.8
postfix/proto/BDAT_README.html
postfix/proto/cidr_table
postfix/proto/postconf.proto
postfix/proto/regexp_table
postfix/src/global/mail_version.h
postfix/src/postfix/postfix.c
postfix/src/postmap/Makefile.in
postfix/src/postscreen/postscreen_smtpd.c
postfix/src/smtpd/smtpd.c
postfix/src/util/slmdb.c

index f722a7b6ea2b026c6bc1e8780dc63f41bf7b4d9c..29457aefc0bed28a761af121e6616ec8a8edbd68 100644 (file)
@@ -25597,3 +25597,30 @@ Apologies for any names omitted.
        or master.cf, to improve their usability in matchlists. Files:
        util/dict_stream.c, util/dict.h, util/dict_pcre.c,
        util/dict_regexp.c, util/dict_cidr.c, and test files.
+
+       The smtpd_forbidden_commands default setting now also inludes
+       a regular expression regexp:{{/^[^A-Z]/ Bogus}} for bogus inputs.
+       File: global/mail_params.h.
+
+20210606
+
+       Cleanup: "Postfix is running with backwards-compatible..."
+       did not make sense when Postfix is down. File: postfix/postfix.c.
+
+       Cleanup: the postscreen BDAT handler now replies with "need
+       MAIL command" when the client did not provide a sender address.
+       File: postscreen/postscreen_smtpd.c.
+
+       Typo: silent_discard should be silent-discard.  File:
+       proto/BDAT_README.html.
+
+20210610
+
+       Cleanup: escape non-printable characters in non-SMTP commands,
+       instead of replacing them with '?'. File: smtpd/smtpd.c.
+
+       Misc typofixes by Viktor Dukhovni. Files: conf/master.cf,
+       proto/regexp_table, proto/cidr_table.
+
+       Cleanup: simplify the LMDB error recovery code. File:
+       util/slmdb.c.
index 2dc1df35ce65f01f1462f82393f46c80ae16fb02..1248804099f623ef99d421a00c9074317a73f98a 100644 (file)
@@ -23,7 +23,7 @@ BDAT support is enabled by default. To disable BDAT support globally:
         # The logging alternative:
         smtpd_discard_ehlo_keywords = chunking
         # The non-logging alternative:
-        smtpd_discard_ehlo_keywords = chunking, silent_discard
+        smtpd_discard_ehlo_keywords = chunking, silent-discard
 
 Specify '-o smtpd_discard_ehlo_keywords=' in master.cf for the submission and
 smtps services, if you have clients that benefit from CHUNKING support.
index 46cad79d52d6c414918d75cd018c28256803110f..3c6c7fbd220e619ec630c330f447c995abd29f1e 100644 (file)
@@ -1,8 +1,5 @@
 Wish list:
 
-       Make postscreen drop the connection after a non-SMTP command,
-       just like the real Postfix SMTP daemon.
-
        Add verp=+= to the qmgr "from=" logging.
 
        Make smtpd_relay_before_recipient_restrictions settable
index 4220165db688e8ab78573c56b2292b9e7c61eefb..83fc6fdf7f1caf91fd0030e5825dad767edfa3ea 100644 (file)
@@ -32,9 +32,9 @@ smtp      inet  n       -       n       -       -       smtpd
 #  -o smtpd_relay_restrictions=
 #  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
 #  -o milter_macro_daemon_name=ORIGINATING
-# Choose one: enable submssions for loopback clients only, or for any client.
-#127.0.0.1:submssions inet n  -       n       -       -       smtpd
-#submssions     inet  n       -       n       -       -       smtpd
+# Choose one: enable submissions for loopback clients only, or for any client.
+#127.0.0.1:submissions inet n  -       n       -       -       smtpd
+#submissions     inet  n       -       n       -       -       smtpd
 #  -o syslog_name=postfix/submissions
 #  -o smtpd_tls_wrappermode=yes
 #  -o smtpd_sasl_auth_enable=yes
index e96fe9348068b14476bc44cdaf74d48ab3d10d6c..cb97755411c34bac6ad8c7caa02524bce9331823 100644 (file)
@@ -51,7 +51,7 @@ globally: </p>
     # The logging alternative:
     <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = chunking
     # The non-logging alternative:
-    <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = chunking, silent_discard
+    <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = chunking, silent-discard
 </pre>
 </blockquote>
 
index 457afbb56f36bc49ac2feca878133b7876331c13..cd90384c8b5ddc228fdedd9ea009076e1e2ee191 100644 (file)
@@ -103,10 +103,10 @@ CIDR_TABLE(5)                                                    CIDR_TABLE(5)
        syntax is:
 
        <a href="postconf.5.html">main.cf</a>:
-           <i>parameter</i> <b>= .. <a href="pcre_table.5.html">pcre</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } ..</b>
+           <i>parameter</i> <b>= .. <a href="cidr_table.5.html">cidr</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } ..</b>
 
        <a href="master.5.html">master.cf</a>:
-           <b>.. -o {</b> <i>parameter</i> <b>= .. <a href="pcre_table.5.html">pcre</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } .. } ..</b>
+           <b>.. -o {</b> <i>parameter</i> <b>= .. <a href="cidr_table.5.html">cidr</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } .. } ..</b>
 
        Postfix  ignores  whitespace  after '{' and before '}', and writes each
        <i>rule</i> as one text line to an in-memory file:
index 4cce2fbd141f0f722be0ee834841111846eb7358..5b0691c36d4f236133fc9e6eb5f3b37031699fa6 100644 (file)
@@ -15300,20 +15300,26 @@ This feature is available in Postfix 2.0 and later.
 </DD>
 
 <DT><b><a name="smtpd_forbidden_commands">smtpd_forbidden_commands</a>
-(default: CONNECT, GET, POST)</b></DT><DD>
+(default: CONNECT GET POST <a href="regexp_table.5.html">regexp</a>:{{/^[^A-Z]/ Bogus}})</b></DT><DD>
 
 <p>
 List of commands that cause the Postfix SMTP server to immediately
 terminate the session with a 221 code. This can be used to disconnect
 clients that obviously attempt to abuse the system. In addition to the
 commands listed in this parameter, commands that follow the "Label:"
-format of message headers will also cause a disconnect.
+format of message headers will also cause a disconnect. With Postfix
+versions 3.6 and earlier, the default value is "CONNECT GET POST".
 </p>
 
 <p>
 This feature is available in Postfix 2.2 and later.
 </p>
 
+<p>
+Support for inline regular expressions was added in Postfix version
+3.7. See <a href="regexp_table.5.html">regexp_table(5)</a> for a description of the syntax and features.
+</p>
+
 
 </DD>
 
index 3d53436e1518e643f1bfd28f52f1a61647c45522..b0e8648191de8934a8694335839bd6f221a973e2 100644 (file)
@@ -132,10 +132,10 @@ REGEXP_TABLE(5)                                                REGEXP_TABLE(5)
        syntax is:
 
        <a href="postconf.5.html">main.cf</a>:
-           <i>parameter</i> <b>= .. <a href="pcre_table.5.html">pcre</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } ..</b>
+           <i>parameter</i> <b>= .. <a href="regexp_table.5.html">regexp</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } ..</b>
 
        <a href="master.5.html">master.cf</a>:
-           <b>.. -o {</b> <i>parameter</i> <b>= .. <a href="pcre_table.5.html">pcre</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } .. } ..</b>
+           <b>.. -o {</b> <i>parameter</i> <b>= .. <a href="regexp_table.5.html">regexp</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } .. } ..</b>
 
        Postfix  ignores  whitespace  after '{' and before '}', and writes each
        <i>rule</i> as one text line to an in-memory file:
index ef9d7fd37a316497527d2071bbc452bbbf30c2dd..4dbc75661d446ee20f7507a9579fa4740035a928 100644 (file)
@@ -1318,7 +1318,7 @@ SMTPD(8)                                                              SMTPD(8)
 
        Available in Postfix version 2.2 and later:
 
-       <b><a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a> (CONNECT, GET, POST)</b>
+       <b><a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a> (CONNECT GET POST <a href="regexp_table.5.html">regexp</a>:{{/^[^A-Z]/ Bogus}})</b>
               List of commands that cause the Postfix SMTP server  to  immedi-
               ately terminate the session with a 221 code.
 
index a8754fc8f9574074356ca8f130507572cabdd045..2e182ee5be46fa30bcf9d75444fc5c970674f954 100644 (file)
@@ -120,10 +120,10 @@ The basic syntax is:
 
 .nf
 main.cf:
-    \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+    \fIparameter\fR \fB= .. cidr:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
 
 master.cf:
-    \fB.. \-o { \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+    \fB.. \-o { \fIparameter\fR \fB= .. cidr:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
 .fi
 
 Postfix ignores whitespace after '{' and before '}', and
index 151afdfbce8e51361c71b1b80f5c5079f232a25a..f9c0263a3fe3839f68b5e3e52c582012463e114c 100644 (file)
@@ -10413,14 +10413,18 @@ The smtpd_expansion_filter value is not subject to Postfix configuration
 parameter $name expansion.
 .PP
 This feature is available in Postfix 2.0 and later.
-.SH smtpd_forbidden_commands (default: CONNECT, GET, POST)
+.SH smtpd_forbidden_commands (default: CONNECT GET POST regexp:{{/^[^A\-Z]/ Bogus}})
 List of commands that cause the Postfix SMTP server to immediately
 terminate the session with a 221 code. This can be used to disconnect
 clients that obviously attempt to abuse the system. In addition to the
 commands listed in this parameter, commands that follow the "Label:"
-format of message headers will also cause a disconnect.
+format of message headers will also cause a disconnect. With Postfix
+versions 3.6 and earlier, the default value is "CONNECT GET POST".
 .PP
 This feature is available in Postfix 2.2 and later.
+.PP
+Support for inline regular expressions was added in Postfix version
+3.7. See \fBregexp_table\fR(5) for a description of the syntax and features.
 .SH smtpd_hard_error_limit (default: normal: 20, overload: 1)
 The maximal number of errors a remote SMTP client is allowed to
 make without delivering mail. The Postfix SMTP server disconnects
index 9ebc6a6c34bc707b75a3b6353f7846d1f1e2d425..7b51921966706b08a879fd62bdfa489c010aab8f 100644 (file)
@@ -143,10 +143,10 @@ The basic syntax is:
 
 .nf
 main.cf:
-    \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+    \fIparameter\fR \fB= .. regexp:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
 
 master.cf:
-    \fB.. \-o { \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+    \fB.. \-o { \fIparameter\fR \fB= .. regexp:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
 .fi
 
 Postfix ignores whitespace after '{' and before '}', and
index f016451c72a802507cd31ef336c630664b353fa9..419fd0d462e00edf8f7dbb6ff6767993409f96d2 100644 (file)
@@ -1138,7 +1138,7 @@ A prefix that is prepended to the process name in syslog
 records, so that, for example, "smtpd" becomes "prefix/smtpd".
 .PP
 Available in Postfix version 2.2 and later:
-.IP "\fBsmtpd_forbidden_commands (CONNECT, GET, POST)\fR"
+.IP "\fBsmtpd_forbidden_commands (CONNECT GET POST regexp:{{/^[^A\-Z]/ Bogus}})\fR"
 List of commands that cause the Postfix SMTP server to immediately
 terminate the session with a 221 code.
 .PP
index ace54928f8b2f0b65d895552832d836d511c27ab..85adeac00c36cbfa9869a9702e3fed28cde476d2 100644 (file)
@@ -51,7 +51,7 @@ globally: </p>
     # The logging alternative:
     smtpd_discard_ehlo_keywords = chunking
     # The non-logging alternative:
-    smtpd_discard_ehlo_keywords = chunking, silent_discard
+    smtpd_discard_ehlo_keywords = chunking, silent-discard
 </pre>
 </blockquote>
 
index d96fa554a697c37ff45f32ff8a1efa368f9eb1e3..ca03813994232984949beaeb06fcf86b59ac20e5 100644 (file)
 #
 # .nf
 #      main.cf:
-#          \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#          \fIparameter\fR \fB= .. cidr:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
 #
 #      master.cf:
-#          \fB.. -o { \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+#          \fB.. -o { \fIparameter\fR \fB= .. cidr:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
 # .fi
 #
 #      Postfix ignores whitespace after '{' and before '}', and
index 9a04208483c25c3e1cd32579ce561d7b090f5212..59aecf5e7291a59e9e75a640b65622d3a1a475ee 100644 (file)
@@ -5680,20 +5680,26 @@ parameter $name expansion.
 This feature is available in Postfix 2.0 and later.
 </p>
 
-%PARAM smtpd_forbidden_commands CONNECT, GET, POST
+%PARAM smtpd_forbidden_commands CONNECT GET POST regexp:{{/^[^A-Z]/ Bogus}}
 
 <p>
 List of commands that cause the Postfix SMTP server to immediately
 terminate the session with a 221 code. This can be used to disconnect
 clients that obviously attempt to abuse the system. In addition to the
 commands listed in this parameter, commands that follow the "Label:"
-format of message headers will also cause a disconnect.
+format of message headers will also cause a disconnect. With Postfix
+versions 3.6 and earlier, the default value is "CONNECT GET POST".
 </p>
 
 <p>
 This feature is available in Postfix 2.2 and later.
 </p>
 
+<p>
+Support for inline regular expressions was added in Postfix version
+3.7. See regexp_table(5) for a description of the syntax and features.
+</p>
+
 %PARAM smtpd_helo_required no
 
 <p>
index 31d9902b2d605920c31df8fddcf4280315a8f534..969b8a9f8a12eac70afec53fd7456064e803fc16 100644 (file)
 #
 # .nf
 #      main.cf:
-#          \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#          \fIparameter\fR \fB= .. regexp:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
 #
 #      master.cf:
-#          \fB.. -o { \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+#          \fB.. -o { \fIparameter\fR \fB= .. regexp:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
 # .fi
 #
 #      Postfix ignores whitespace after '{' and before '}', and
index 9a8db34c35d44bb64373a8160354b2ac0b469a86..f57b8a1ef7862d21d59989a876d2490d9717f132 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      "20210605"
+#define MAIL_RELEASE_DATE      "20210612"
 #define MAIL_VERSION_NUMBER    "3.7"
 
 #ifdef SNAPSHOT
index 357124ff5c579103273ff0e5f254b3ff50f85a4f..0c4b6de504ce2ab76fdd62a1418cd24707606417 100644 (file)
@@ -587,8 +587,7 @@ int     main(int argc, char **argv)
      * effect.
      */
     if (compat_level < compat_level_from_string(LAST_COMPAT_LEVEL, msg_panic)) {
-       msg_info("Postfix is running with backwards-compatible default "
-                "settings");
+       msg_info("Postfix is using backwards-compatible default settings");
        msg_info("See http://www.postfix.org/COMPATIBILITY_README.html "
                 "for details");
        msg_info("To disable backwards compatibility use \"postconf "
index 18f94ea53c01094aa59acd3ed14f5841497b4ef7..c28af2aea7c64531563b003309740c8aefd53bb4 100644 (file)
@@ -26,7 +26,8 @@ update: ../../bin/$(PROG)
 ../../bin/$(PROG): $(PROG)
        cp $(PROG) ../../bin
 
-tests: test1 test2 fail_test quote_test file_test lmdb_abb_test lmdb_retry_test
+tests: test1 test2 fail_test quote_test file_test lmdb_abb_test \
+       lmdb_bulk_test lmdb_incr_test
 
 root_tests:
 
@@ -80,17 +81,28 @@ lmdb_abb_test: $(PROG) lmdb_abb lmdb_abb.ref
        diff lmdb_abb.ref lmdb_abb.tmp
        rm -f lmdb_abb.tmp lmdb_abb.lmdb
 
-lmdb_retry_test: $(PROG)
+lmdb_bulk_test: $(PROG)
        rm -f lmdb_retry.lmdb main.cf
        tr A-Z a-z < /usr/share/dict/words| \
            sed -e 's/.*/&      &/' -e 10000q| LANG=C sort -u >lmdb_retry
-       echo lmdb_map_size=1000 >main.cf
+       echo lmdb_map_size=10000 >main.cf
        ($(SHLIB_ENV) $(VALGRIND) ./postmap -c . lmdb:lmdb_retry; \
        $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_retry | \
            LANG=C sort > lmdb_retry.tmp)
        cmp lmdb_retry lmdb_retry.tmp
        rm -f lmdb_retry lmdb_retry.tmp lmdb_retry.lmdb main.cf
 
+lmdb_incr_test: $(PROG)
+       rm -f lmdb_retry.lmdb main.cf
+       tr A-Z a-z < /usr/share/dict/words| \
+           sed -e 's/.*/&      &/' -e 10000q| LANG=C sort -u >lmdb_retry
+       echo lmdb_map_size=10000 >main.cf
+       ($(SHLIB_ENV) $(VALGRIND) ./postmap -ic . lmdb:lmdb_retry <lmdb_retry; \
+       $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_retry | \
+           LANG=C sort > lmdb_retry.tmp)
+       cmp lmdb_retry lmdb_retry.tmp
+       rm -f lmdb_retry lmdb_retry.tmp lmdb_retry.lmdb main.cf
+
 printfck: $(OBJS) $(PROG)
        rm -rf printfck
        mkdir printfck
index ca7088803bad247bd2894b3d58822d211a6bda38..70183e7ee6c60d40bc9d4ddb2366ae8e5be4b6ff 100644 (file)
@@ -637,7 +637,7 @@ static int psc_bdat_cmd(PSC_STATE *state, char *args)
     else if (state->sender == 0)
        PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
                                           psc_smtpd_time_event,
-                                 "554 5.5.1 Error: need RCPT command\r\n");
+                                 "554 5.5.1 Error: need MAIL command\r\n");
     else
        PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
                                           psc_smtpd_time_event,
index 067b9b2d224402dc226a2967100268ec66336a9e..3f7b20167e10c783d6515aadbb8771356e206d8a 100644 (file)
 /*     records, so that, for example, "smtpd" becomes "prefix/smtpd".
 /* .PP
 /*     Available in Postfix version 2.2 and later:
-/* .IP "\fBsmtpd_forbidden_commands (CONNECT, GET, POST)\fR"
+/* .IP "\fBsmtpd_forbidden_commands (CONNECT GET POST regexp:{{/^[^A-Z]/ Bogus}})\fR"
 /*     List of commands that cause the Postfix SMTP server to immediately
 /*     terminate the session with a 221 code.
 /* .PP
@@ -5693,9 +5693,15 @@ static void smtpd_proto(SMTPD_STATE *state)
                if (is_header(argv[0].strval)
                    || (*var_smtpd_forbid_cmds
                 && string_list_match(smtpd_forbid_cmds, argv[0].strval))) {
+                   VSTRING *escape_buf = vstring_alloc(100);
+
                    msg_warn("non-SMTP command from %s: %.100s",
-                            state->namaddr, vstring_str(state->buffer));
+                            state->namaddr,
+                            vstring_str(escape(escape_buf,
+                                               vstring_str(state->buffer),
+                                             VSTRING_LEN(state->buffer))));
                    smtpd_chat_reply(state, "221 2.7.0 Error: I can break rules, too. Goodbye.");
+                   vstring_free(escape_buf);
                    break;
                }
            }
index 37aa30d76385699c9fe189f6f069c14ae9fee8f7..ac1594ff300fae69ea5d65bafad1360c909f3c92 100644 (file)
@@ -410,7 +410,6 @@ static int slmdb_prepare(SLMDB *slmdb)
 static int slmdb_recover(SLMDB *slmdb, int status)
 {
     MDB_envinfo info;
-    int     recover_bulk_transaction = 0;
 
     /*
      * This may be needed in non-MDB_NOLOCK mode. Recovery is rare enough
@@ -420,24 +419,19 @@ static int slmdb_recover(SLMDB *slmdb, int status)
        slmdb_cursor_close(slmdb);
 
     /*
-     * Recover bulk transactions only if they can be restarted. Limit the
-     * number of recovery attempts per slmdb(3) API request.
-     * 
-     * slmdb->txn must be either null (non-bulk transaction error), or an
-     * aborted bulk-mode transaction.
+     * Limit the number of recovery attempts per slmdb(3) API request.
      */
-    if ((slmdb->txn != 0 && slmdb->longjmp_fn == 0)
-       || ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit))
+    if ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit)
        return (status);
 
     /*
-     * Limit the number of recovery attempts per bulk transaction failure.
+     * Recover bulk transactions only if they can be restarted, but limit the
+     * number of recovery attempts.
      */
-    if (slmdb->txn != 0 && slmdb->longjmp_fn != 0) {
-       if ((slmdb->bulk_retry_count += 1) > slmdb->bulk_retry_limit)
-           return (status);
-       recover_bulk_transaction = 1;
-    }
+    if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0
+       && (slmdb->longjmp_fn == 0
+           || (slmdb->bulk_retry_count += 1) > slmdb->bulk_retry_limit))
+       return (status);
 
     /*
      * If we can recover from the error, we clear the error condition and the
@@ -516,7 +510,7 @@ static int slmdb_recover(SLMDB *slmdb, int status)
      * upgrade the lock to "exclusive", because the failed write transaction
      * has no side effects.
      */
-    if (status == 0 && recover_bulk_transaction != 0
+    if (status == 0 && (slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0
        && (status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0,
                                   slmdb->lmdb_flags & MDB_RDONLY,
                                   &slmdb->txn)) == 0
@@ -559,6 +553,8 @@ int     slmdb_get(SLMDB *slmdb, MDB_val *mdb_key, MDB_val *mdb_value)
     if ((status = mdb_get(txn, slmdb->dbi, mdb_key, mdb_value)) != 0
        && status != MDB_NOTFOUND) {
        mdb_txn_abort(txn);
+       if (txn == slmdb->txn)
+           slmdb->txn = 0;
        if ((status = slmdb_recover(slmdb, status)) == 0)
            status = slmdb_get(slmdb, mdb_key, mdb_value);
        SLMDB_API_RETURN(slmdb, status);
@@ -595,6 +591,8 @@ int     slmdb_put(SLMDB *slmdb, MDB_val *mdb_key,
     if ((status = mdb_put(txn, slmdb->dbi, mdb_key, mdb_value, flags)) != 0) {
        if (status != MDB_KEYEXIST) {
            mdb_txn_abort(txn);
+           if (txn == slmdb->txn)
+               slmdb->txn = 0;
            if ((status = slmdb_recover(slmdb, status)) == 0)
                status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
            SLMDB_API_RETURN(slmdb, status);
@@ -636,6 +634,8 @@ int     slmdb_del(SLMDB *slmdb, MDB_val *mdb_key)
     if ((status = mdb_del(txn, slmdb->dbi, mdb_key, (MDB_val *) 0)) != 0) {
        if (status != MDB_NOTFOUND) {
            mdb_txn_abort(txn);
+           if (txn == slmdb->txn)
+               slmdb->txn = 0;
            if ((status = slmdb_recover(slmdb, status)) == 0)
                status = slmdb_del(slmdb, mdb_key);
            SLMDB_API_RETURN(slmdb, status);
@@ -664,6 +664,12 @@ int     slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key,
     MDB_txn *txn;
     int     status = 0;
 
+    /*
+     * TODO: figure how we would recover a failing bulk transaction.
+     */
+    if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0)
+       return (MDB_PANIC);
+
     /*
      * Open a read transaction and cursor if needed.
      */
@@ -786,7 +792,7 @@ int     slmdb_close(SLMDB *slmdb)
      * Finish an open bulk transaction. If slmdb_recover() returns after a
      * bulk-transaction error, then it was unable to recover.
      */
-    if (slmdb->txn != 0
+    if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0 && slmdb->txn != 0
        && (status = mdb_txn_commit(slmdb->txn)) != 0)
        status = slmdb_recover(slmdb, status);