]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.7-20210605
authorWietse Venema <wietse@porcupine.org>
Sat, 5 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)
33 files changed:
postfix/HISTORY
postfix/RELEASE_NOTES
postfix/WISHLIST
postfix/html/cidr_table.5.html
postfix/html/pcre_table.5.html
postfix/html/regexp_table.5.html
postfix/man/man5/cidr_table.5
postfix/man/man5/pcre_table.5
postfix/man/man5/regexp_table.5
postfix/proto/cidr_table
postfix/proto/pcre_table
postfix/proto/regexp_table
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/postmap/Makefile.in
postfix/src/postmap/lmdb_abb [new file with mode: 0644]
postfix/src/postmap/lmdb_abb.ref [new file with mode: 0644]
postfix/src/util/Makefile.in
postfix/src/util/dict.h
postfix/src/util/dict_cidr.c
postfix/src/util/dict_fail.c
postfix/src/util/dict_inline_cidr.ref [new file with mode: 0644]
postfix/src/util/dict_inline_pcre.ref [new file with mode: 0644]
postfix/src/util/dict_inline_regexp.ref [new file with mode: 0644]
postfix/src/util/dict_lmdb.c
postfix/src/util/dict_pcre.c
postfix/src/util/dict_regexp.c
postfix/src/util/dict_stream.c [new file with mode: 0644]
postfix/src/util/dict_stream.ref [new file with mode: 0644]
postfix/src/util/extpar.c
postfix/src/util/slmdb.c
postfix/src/util/vstream.c
postfix/src/util/vstream.h

index 71bdbd73c93789811123b628414e3c9c6b948a97..f722a7b6ea2b026c6bc1e8780dc63f41bf7b4d9c 100644 (file)
@@ -25572,3 +25572,28 @@ Apologies for any names omitted.
        Howard Chu. In addition, "postmap lmdb:/file/name" forgot
        entries stored up to and including the duplicate key. File:
        util/slmdb.c.
+
+20210605
+
+       Fixed a few more potential dangling pointer cases in the
+       LMDB client, future-proofing code paths that sofar aren't
+       used. File: util/slmdb.c.
+
+       Added LMDB integration tests using the postmmap command.
+       Files: postmap/Makefile.in, postmap/lmdb_abb, postmap/lmdb_abb.ref.
+
+       Cleanup: reset errno in the fail: database methods for
+       consistent error messages. File: util/dict_fail.c.
+
+       Cleanup: new vstream_control() option to give a memory stream
+       ownership of the underlying VSTRING. This simplifies resource
+       management for read-only streams. Files: util/vstream.[hc].
+
+       Cleanup: extpar() returns an error in case of a missing
+       initial '{', instead of aborting. This simplifies the
+       implementation of some callers. File: util/extpar.c.
+
+       Feature: inline pcre, regexp, and cidr table definition in main.cf
+       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.
index 172832534cca5fdafaf41857802c9769191eb89e..b7726304dff673b0416f044c51154d9a87bb3778 100644 (file)
@@ -24,3 +24,34 @@ historical IBM Public License 1.0, it is now also distributed with the
 more recent Eclipse Public License 2.0. Recipients can choose to take
 the software under the license of their choice. Those who are more
 comfortable with the IPL can continue with that license.
+
+Major changes with snapshot 20210605
+====================================
+
+Support to inline the content of small cidr, pcre, and regexp tables.
+
+Example:
+
+    smtpd_forbidden_commands =
+       CONNECT GET POST regexp:{{/^[^A-Z]/ Thrash}}
+
+The basic syntax is:
+
+/etc/postfix/main.cf:
+    parameter = .. map-type:{ { rule-1 }, { rule-2 } .. } ..
+
+/etc/postfix/master.cf:
+    .. -o { parameter = .. map-type:{ { rule-1 }, { rule-2 } .. } .. } ..
+
+Postfix ignores whitespace after '{' and before '}', and writes each
+rule as one text line to an in-memory file: 
+
+in-memory file:
+    rule-1
+    rule-2
+    ..
+
+Postfix parses the result as if it is a file in /etc/postfix.
+
+Note: if a rule contains $, specify $$, to keep Postfix from trying to
+do $name expansion as it evaluates the parameter value.
index 3c6c7fbd220e619ec630c330f447c995abd29f1e..46cad79d52d6c414918d75cd018c28256803110f 100644 (file)
@@ -1,5 +1,8 @@
 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 e4569cfeb2f0e66bae1452a8be5dbc464c14d58f..457afbb56f36bc49ac2feca878133b7876331c13 100644 (file)
@@ -98,6 +98,26 @@ CIDR_TABLE(5)                                                    CIDR_TABLE(5)
        Note:  address information may be enclosed inside "[]" but this form is
        not required.
 
+<b>INLINE SPECIFICATION</b>
+       The contents of a table may be specified in the table name.  The  basic
+       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>
+
+       <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>
+
+       Postfix  ignores  whitespace  after '{' and before '}', and writes each
+       <i>rule</i> as one text line to an in-memory file:
+
+       in-memory file:
+           rule-1
+           rule-2
+           ..
+
+       Postfix parses the result as if it is a file in /etc/postfix.
+
 <b>EXAMPLE SMTPD ACCESS MAP</b>
        /etc/postfix/<a href="postconf.5.html">main.cf</a>:
            <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> = ... <a href="cidr_table.5.html">cidr</a>:/etc/postfix/client.cidr ...
index 9f159127a10c7321d6ac272e5d11d9e4ba147fb9..b692fcff7fe92f7468646f9970adca7dfbab1876 100644 (file)
@@ -164,6 +164,29 @@ PCRE_TABLE(5)                                                    PCRE_TABLE(5)
        the  expression  does  not  match,  substitutions are not available for
        negated patterns.
 
+<b>INLINE SPECIFICATION</b>
+       The contents of a table may be specified in the table name.  The  basic
+       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>
+
+       <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>
+
+       Postfix  ignores  whitespace  after '{' and before '}', and writes each
+       <i>rule</i> as one text line to an in-memory file:
+
+       in-memory file:
+           rule-1
+           rule-2
+           ..
+
+       Postfix parses the result as if it is a file in /etc/postfix.
+
+       Note: if a rule contains <b>$</b>, specify <b>$$</b> to keep Postfix from  trying  to
+       do <i>$name</i> expansion as it evaluates a parameter value.
+
 <b>EXAMPLE SMTPD ACCESS MAP</b>
        # Protect your outgoing majordomo exploders
        /^(?!owner-)(.*)-outgoing@(.*)/ 550 Use ${1}@${2} instead
index 1bfc3e73fa9285b2f8d90e3ce32337843d171478..3d53436e1518e643f1bfd28f52f1a61647c45522 100644 (file)
@@ -127,6 +127,29 @@ REGEXP_TABLE(5)                                                REGEXP_TABLE(5)
        the  expression  does  not  match,  substitutions are not available for
        negated patterns.
 
+<b>INLINE SPECIFICATION</b>
+       The contents of a table may be specified in the table name.  The  basic
+       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>
+
+       <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>
+
+       Postfix  ignores  whitespace  after '{' and before '}', and writes each
+       <i>rule</i> as one text line to an in-memory file:
+
+       in-memory file:
+           rule-1
+           rule-2
+           ..
+
+       Postfix parses the result as if it is a file in /etc/postfix.
+
+       Note: if a rule contains <b>$</b>, specify <b>$$</b> to keep Postfix from  trying  to
+       do <i>$name</i> expansion as it evaluates a parameter value.
+
 <b>EXAMPLE SMTPD ACCESS MAP</b>
        # Disallow sender-specified routing. This is a must if you relay mail
        # for other domains.
index 8666858786c1b80f94286568b3bf2d83184d131b..a8754fc8f9574074356ca8f130507572cabdd045 100644 (file)
@@ -110,6 +110,34 @@ an IPv4 address octet indicates octal notation).
 
 Note: address information may be enclosed inside "[]" but
 this form is not required.
+.SH "INLINE SPECIFICATION"
+.na
+.nf
+.ad
+.fi
+The contents of a table may be specified in the table name.
+The basic syntax is:
+
+.nf
+main.cf:
+    \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+
+master.cf:
+    \fB.. \-o { \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+.fi
+
+Postfix ignores whitespace after '{' and before '}', and
+writes each \fIrule\fR as one text line to an in\-memory
+file:
+
+.nf
+in\-memory file:
+    rule\-1
+    rule\-2
+    ..
+.fi
+
+Postfix parses the result as if it is a file in /etc/postfix.
 .SH "EXAMPLE SMTPD ACCESS MAP"
 .na
 .nf
index 349034d09fb5db398bc3c58f14aaeb08230f03af..b56dd09ce300a967297bd4823dcfc2ffdb87a46d 100644 (file)
@@ -169,6 +169,38 @@ ${n} or $(n) if they aren't followed by whitespace.
 Note: since negated patterns (those preceded by \fB!\fR) return a
 result when the expression does not match, substitutions are not
 available for negated patterns.
+.SH "INLINE SPECIFICATION"
+.na
+.nf
+.ad
+.fi
+The contents of a table may be specified in the table name.
+The basic syntax is:
+
+.nf
+main.cf:
+    \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+
+master.cf:
+    \fB.. \-o { \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+.fi
+
+Postfix ignores whitespace after '{' and before '}', and
+writes each \fIrule\fR as one text line to an in\-memory
+file:
+
+.nf
+in\-memory file:
+    rule\-1
+    rule\-2
+    ..
+.fi
+
+Postfix parses the result as if it is a file in /etc/postfix.
+
+Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+Postfix from trying to do \fI$name\fR expansion as it
+evaluates a parameter value.
 .SH "EXAMPLE SMTPD ACCESS MAP"
 .na
 .nf
index 7e60a26f05ec2e458189a7dda41269146b23f4a7..9ebc6a6c34bc707b75a3b6353f7846d1f1e2d425 100644 (file)
@@ -133,6 +133,38 @@ ${n} or $(n) if they aren't followed by whitespace.
 Note: since negated patterns (those preceded by \fB!\fR) return a
 result when the expression does not match, substitutions are not
 available for negated patterns.
+.SH "INLINE SPECIFICATION"
+.na
+.nf
+.ad
+.fi
+The contents of a table may be specified in the table name.
+The basic syntax is:
+
+.nf
+main.cf:
+    \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+
+master.cf:
+    \fB.. \-o { \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+.fi
+
+Postfix ignores whitespace after '{' and before '}', and
+writes each \fIrule\fR as one text line to an in\-memory
+file:
+
+.nf
+in\-memory file:
+    rule\-1
+    rule\-2
+    ..
+.fi
+
+Postfix parses the result as if it is a file in /etc/postfix.
+
+Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+Postfix from trying to do \fI$name\fR expansion as it
+evaluates a parameter value.
 .SH "EXAMPLE SMTPD ACCESS MAP"
 .na
 .nf
index 81964a764c590644a3b5734721dbe7e6a98dff83..d96fa554a697c37ff45f32ff8a1efa368f9eb1e3 100644 (file)
 #
 #      Note: address information may be enclosed inside "[]" but
 #      this form is not required.
+# INLINE SPECIFICATION
+# .ad
+# .fi
+#      The contents of a table may be specified in the table name.
+#      The basic syntax is:
+#
+# .nf
+#      main.cf:
+#          \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#
+#      master.cf:
+#          \fB.. -o { \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+# .fi
+#
+#      Postfix ignores whitespace after '{' and before '}', and
+#      writes each \fIrule\fR as one text line to an in-memory
+#      file:
+#
+# .nf
+#      in-memory file:
+#          rule-1
+#          rule-2
+#          ..
+# .fi
+#
+#      Postfix parses the result as if it is a file in /etc/postfix.
 # EXAMPLE SMTPD ACCESS MAP
 # .nf
 #      /etc/postfix/main.cf:
index 7f15a8197c56c5ce2a5de04a58693f160c2abf8b..506f37453d2551e352c64b282a4b1c22406a0e09 100644 (file)
 #      Note: since negated patterns (those preceded by \fB!\fR) return a
 #      result when the expression does not match, substitutions are not
 #      available for negated patterns.
+# INLINE SPECIFICATION
+# .ad
+# .fi
+#      The contents of a table may be specified in the table name.
+#      The basic syntax is:
+#
+# .nf
+#      main.cf:
+#          \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#
+#      master.cf:
+#          \fB.. -o { \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+# .fi
+#
+#      Postfix ignores whitespace after '{' and before '}', and
+#      writes each \fIrule\fR as one text line to an in-memory
+#      file:
+#
+# .nf
+#      in-memory file:
+#          rule-1
+#          rule-2
+#          ..
+# .fi
+#
+#      Postfix parses the result as if it is a file in /etc/postfix.
+#
+#      Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+#      Postfix from trying to do \fI$name\fR expansion as it
+#      evaluates a parameter value.
 # EXAMPLE SMTPD ACCESS MAP
 #      # Protect your outgoing majordomo exploders
 #      /^(?!owner-)(.*)-outgoing@(.*)/ 550 Use ${1}@${2} instead
index 88fcdedd67460b659593720c4c8d9c274bc47657..31d9902b2d605920c31df8fddcf4280315a8f534 100644 (file)
@@ -47,7 +47,7 @@
 #      input string against the patterns between \fBif\fR and
 #      \fBendif\fR.  The \fBif\fR..\fBendif\fR can nest.
 # .sp
-#       Note: do not prepend whitespace to patterns inside
+#      Note: do not prepend whitespace to patterns inside
 #      \fBif\fR..\fBendif\fR.
 # .sp
 #      This feature is available in Postfix 2.1 and later.
@@ -57,7 +57,7 @@
 #      match that input string against the patterns between \fBif\fR
 #      and \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
 # .sp
-#       Note: do not prepend whitespace to patterns inside
+#      Note: do not prepend whitespace to patterns inside
 #      \fBif\fR..\fBendif\fR.
 # .sp
 #      This feature is available in Postfix 2.1 and later.
 #      Note: since negated patterns (those preceded by \fB!\fR) return a
 #      result when the expression does not match, substitutions are not
 #      available for negated patterns.
+# INLINE SPECIFICATION
+# .ad
+# .fi
+#      The contents of a table may be specified in the table name.
+#      The basic syntax is:
+#
+# .nf
+#      main.cf:
+#          \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#
+#      master.cf:
+#          \fB.. -o { \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+# .fi
+#
+#      Postfix ignores whitespace after '{' and before '}', and
+#      writes each \fIrule\fR as one text line to an in-memory
+#      file:
+#
+# .nf
+#      in-memory file:
+#          rule-1
+#          rule-2
+#          ..
+# .fi
+#
+#      Postfix parses the result as if it is a file in /etc/postfix.
+#
+#      Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+#      Postfix from trying to do \fI$name\fR expansion as it
+#      evaluates a parameter value.
 # EXAMPLE SMTPD ACCESS MAP
 #      # Disallow sender-specified routing. This is a must if you relay mail
 #      # for other domains.
index ed99874eb01e176f2a9a1350ce408eb5943be087..b3af01b99ad71ab41880acf85688a1904d13edd2 100644 (file)
@@ -1280,7 +1280,7 @@ extern int var_smtpd_hist_thrsh;
 extern char *var_smtpd_noop_cmds;
 
 #define VAR_SMTPD_FORBID_CMDS  "smtpd_forbidden_commands"
-#define DEF_SMTPD_FORBID_CMDS  "CONNECT GET POST"
+#define DEF_SMTPD_FORBID_CMDS  "CONNECT GET POST regexp:{{/^[^A-Z]/ Bogus}}"
 extern char *var_smtpd_forbid_cmds;
 
 #define VAR_SMTPD_CMD_FILTER   "smtpd_command_filter"
index bf8a0fa7274261e8ba640f3a6b28793869f86f84..9a8db34c35d44bb64373a8160354b2ac0b469a86 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      "20210529"
+#define MAIL_RELEASE_DATE      "20210605"
 #define MAIL_VERSION_NUMBER    "3.7"
 
 #ifdef SNAPSHOT
index 3667bb65b42f763f01a033a81fab4a730c74d427..18f94ea53c01094aa59acd3ed14f5841497b4ef7 100644 (file)
@@ -26,40 +26,38 @@ update: ../../bin/$(PROG)
 ../../bin/$(PROG): $(PROG)
        cp $(PROG) ../../bin
 
-tests: test1 test2 fail_test quote_test file_test
+tests: test1 test2 fail_test quote_test file_test lmdb_abb_test lmdb_retry_test
 
 root_tests:
 
 test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-uABC1.ref
-       $(SHLIB_ENV) ./$(PROG) map.in
+       $(SHLIB_ENV) $(VALGRIND) ./$(PROG) map.in
        for key in abc ghi; \
        do \
-           $(SHLIB_ENV) ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \
+           $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \
        done
-       $(SHLIB_ENV) ./$(PROG) -f map.in
+       $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -f map.in
        for key in ABC; \
        do \
-           $(SHLIB_ENV) ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \
+           $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \
        done
        rm -f map.in.db
 
 test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-uABC2.ref
-       $(SHLIB_ENV) ./$(PROG) map.in
+       $(SHLIB_ENV) $(VALGRIND) ./$(PROG) map.in
        for key in abc ghi; \
        do \
-           echo $${key} | $(SHLIB_ENV) ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \
+           echo $${key} | $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \
        done
-       $(SHLIB_ENV) ./$(PROG) -f map.in
+       $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -f map.in
        for key in ABC; \
        do \
-           echo $${key} | $(SHLIB_ENV) ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \
+           echo $${key} | $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \
        done
        rm -f map.in.db
 
 fail_test: $(PROG) aliases fail_test.in fail_test.ref
-       -($(SHLIB_ENV) sh fail_test.in || exit 0) 2>&1 | \
-           sed -e 's/No error:/Unknown error:/' \
-               -e 's/Success/Unknown error: 0/' > fail_test.tmp
+       -($(SHLIB_ENV) sh fail_test.in || exit 0) >fail_test.tmp 2>&1
        diff fail_test.ref fail_test.tmp
        rm -f fail_test.tmp
 
@@ -75,6 +73,24 @@ file_test: $(PROG) file_test.in file_test.ref
        diff file_test.ref file_test.tmp
        rm -f file_test.tmp file_test_map.* postmap-file-1 postmap-file-2
 
+lmdb_abb_test: $(PROG) lmdb_abb lmdb_abb.ref
+       rm -f lmdb_abb.lmdb
+       ($(SHLIB_ENV) $(VALGRIND) ./postmap lmdb:lmdb_abb; \
+       $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_abb | sort) >lmdb_abb.tmp 2>&1
+       diff lmdb_abb.ref lmdb_abb.tmp
+       rm -f lmdb_abb.tmp lmdb_abb.lmdb
+
+lmdb_retry_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
+       ($(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
+
 printfck: $(OBJS) $(PROG)
        rm -rf printfck
        mkdir printfck
diff --git a/postfix/src/postmap/lmdb_abb b/postfix/src/postmap/lmdb_abb
new file mode 100644 (file)
index 0000000..7ea8d03
--- /dev/null
@@ -0,0 +1,3 @@
+a      1
+b      2
+b      3
diff --git a/postfix/src/postmap/lmdb_abb.ref b/postfix/src/postmap/lmdb_abb.ref
new file mode 100644 (file)
index 0000000..ea66c71
--- /dev/null
@@ -0,0 +1,3 @@
+postmap: warning: lmdb:lmdb_abb: duplicate entry: "b"
+a      1
+b      2
index 449eae4601cc0d2a407490e277b39e8fa3510733..3b2e55c048b43e04f18f3332039790af7af58066 100644 (file)
@@ -42,7 +42,7 @@ SRCS  = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
        extpar.c dict_inline.c casefold.c dict_utf8.c strcasecmp_utf8.c \
        split_qnameval.c argv_attr_print.c argv_attr_scan.c dict_file.c \
        msg_logger.c logwriter.c unix_dgram_connect.c unix_dgram_listen.c \
-       byte_mask.c known_tcp_ports.c argv_split_at.c
+       byte_mask.c known_tcp_ports.c argv_split_at.c dict_stream.c
 OBJS   = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
        attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
        attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
@@ -86,7 +86,7 @@ OBJS  = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
        extpar.o dict_inline.o casefold.o dict_utf8.o strcasecmp_utf8.o \
        split_qnameval.o argv_attr_print.o argv_attr_scan.o dict_file.o \
        msg_logger.o logwriter.o unix_dgram_connect.o unix_dgram_listen.o \
-       byte_mask.o known_tcp_ports.o argv_split_at.o
+       byte_mask.o known_tcp_ports.o argv_split_at.o dict_stream.o
 # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
 # When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
 # otherwise it sets the PLUGIN_* macros.
@@ -138,7 +138,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
        valid_utf8_string ip_match base32_code msg_rate_delay netstring \
        vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \
        vbuf_print split_qnameval vstream msg_logger byte_mask \
-       known_tcp_ports
+       known_tcp_ports dict_stream
 PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX)
 
 LIB_DIR        = ../../lib
@@ -554,6 +554,11 @@ known_tcp_ports: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
+dict_stream: $(LIB)
+       mv $@.o junk
+       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+       mv junk $@.o
+
 tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        hex_quote_test ctable_test inet_addr_list_test base64_code_test \
        attr_scan64_test attr_scan0_test dict_pcre_test host_port_test \
@@ -567,7 +572,8 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        vstring_test vstream_test dict_pcre_file_test dict_regexp_file_test \
        dict_cidr_file_test dict_static_file_test dict_random_test \
        dict_random_file_test dict_inline_file_test byte_mask_tests \
-       mystrtok_test known_tcp_ports_test
+       mystrtok_test known_tcp_ports_test dict_stream_test \
+       dict_inline_pcre_test dict_inline_regexp_test dict_inline_cidr_test
 
 root_tests:
 
@@ -975,6 +981,33 @@ known_tcp_ports_test: known_tcp_ports known_tcp_ports.ref
        diff known_tcp_ports.ref known_tcp_ports.tmp
        rm -f known_tcp_ports.tmp
 
+dict_stream_test: dict_stream dict_stream.ref
+       $(SHLIB_ENV) ${VALGRIND} ./dict_stream >dict_stream.tmp 2>&1
+       diff dict_stream.ref dict_stream.tmp
+       rm -f dict_stream.tmp
+
+dict_inline_pcre_test: dict_open dict_inline_pcre.ref
+       (echo get foo; echo get bar) | \
+           $(SHLIB_ENV) ${VALGRIND} ./dict_open 'pcre:{{/foo/ got foo}}' \
+           read 2>&1 | grep -v uid= >dict_inline_pcre.tmp
+       diff dict_inline_pcre.ref dict_inline_pcre.tmp
+       rm -f dict_inline_pcre.tmp
+
+dict_inline_regexp_test: dict_open dict_inline_regexp.ref
+       (echo get foo; echo get bar) | \
+           $(SHLIB_ENV) ${VALGRIND} ./dict_open 'regexp:{{/foo/ got foo}}' \
+           read 2>&1 | grep -v uid= >dict_inline_regexp.tmp
+       diff dict_inline_regexp.ref dict_inline_regexp.tmp
+       rm -f dict_inline_regexp.tmp
+
+dict_inline_cidr_test: dict_open dict_inline_cidr.ref
+       (echo get 1.2.3.4; echo get 4.3.2.1) | \
+           $(SHLIB_ENV) ${VALGRIND} ./dict_open \
+               'cidr:{{1.2.3.4 got 1.2.3.4}}' \
+               read 2>&1 | grep -v uid= >dict_inline_cidr.tmp
+       diff dict_inline_cidr.ref dict_inline_cidr.tmp
+       rm -f dict_inline_cidr.tmp
+
 depend: $(MAKES)
        (sed '1,/^# do not edit/!d' Makefile.in; \
        set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -1621,6 +1654,18 @@ dict_static.o: sys_defs.h
 dict_static.o: vbuf.h
 dict_static.o: vstream.h
 dict_static.o: vstring.h
+dict_stream.o: argv.h
+dict_stream.o: check_arg.h
+dict_stream.o: dict.h
+dict_stream.o: dict_stream.c
+dict_stream.o: msg.h
+dict_stream.o: myflock.h
+dict_stream.o: mymalloc.h
+dict_stream.o: stringops.h
+dict_stream.o: sys_defs.h
+dict_stream.o: vbuf.h
+dict_stream.o: vstream.h
+dict_stream.o: vstring.h
 dict_surrogate.o: argv.h
 dict_surrogate.o: check_arg.h
 dict_surrogate.o: compat_va_copy.h
@@ -1772,7 +1817,6 @@ exec_command.o: msg.h
 exec_command.o: sys_defs.h
 extpar.o: check_arg.h
 extpar.o: extpar.c
-extpar.o: msg.h
 extpar.o: stringops.h
 extpar.o: sys_defs.h
 extpar.o: vbuf.h
index 1381bcba18654a799685d9b05e0706e4481c119d..4f0cab81ad42c5e93bdde3445041a1307ac96fb2 100644 (file)
@@ -14,6 +14,7 @@
  /*
   * System library.
   */
+#include <sys/stat.h>
 #include <fcntl.h>
 #include <setjmp.h>
 
@@ -314,6 +315,12 @@ extern char *dict_file_get_error(DICT *);
 extern void dict_file_purge_buffers(DICT *);
 extern const char *dict_file_lookup(DICT *dict, const char *);
 
+ /*
+  * dict_stream(3)
+  */
+extern VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
+            int open_flags, int dict_flags, struct stat * st, VSTRING **why);
+
 /* LICENSE
 /* .ad
 /* .fi
index 3376dcd4dde87dbb2ffd30dea59472c0925b291d..d29a2bab84f23744f70b59f1c8399c90359a6332 100644 (file)
@@ -289,13 +289,11 @@ DICT   *dict_cidr_open(const char *mapname, int open_flags, int dict_flags)
     /*
      * Open the configuration file.
      */
-    if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0)
+    if ((map_fp = dict_stream_open(DICT_TYPE_CIDR, mapname, O_RDONLY,
+                                  dict_flags, &st, &why)) == 0)
        DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
                                             open_flags, dict_flags,
-                                            "open %s: %m", mapname));
-    if (fstat(vstream_fileno(map_fp), &st) < 0)
-       msg_fatal("fstat %s: %m", mapname);
-
+                                            "%s", vstring_str(why)));
     line_buffer = vstring_alloc(100);
     why = vstring_alloc(100);
 
index 06c10f50c820532d5fdf95f4fca73da54a779d66..c8d9a195fd9cb624b95f2cf6fb0ca482d8fe78b3 100644 (file)
@@ -29,6 +29,7 @@
 /* System library. */
 
 #include <sys_defs.h>
+#include <errno.h>
 
 /* Utility library. */
 
@@ -51,6 +52,7 @@ static int dict_fail_sequence(DICT *dict, int unused_func,
 {
     DICT_FAIL *dp = (DICT_FAIL *) dict;
 
+    errno = 0;
     DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
 }
 
@@ -61,6 +63,7 @@ static int dict_fail_update(DICT *dict, const char *unused_name,
 {
     DICT_FAIL *dp = (DICT_FAIL *) dict;
 
+    errno = 0;
     DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
 }
 
@@ -70,6 +73,7 @@ static const char *dict_fail_lookup(DICT *dict, const char *unused_name)
 {
     DICT_FAIL *dp = (DICT_FAIL *) dict;
 
+    errno = 0;
     DICT_ERR_VAL_RETURN(dict, dp->dict_errno, (char *) 0);
 }
 
@@ -79,6 +83,7 @@ static int dict_fail_delete(DICT *dict, const char *unused_name)
 {
     DICT_FAIL *dp = (DICT_FAIL *) dict;
 
+    errno = 0;
     DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
 }
 
diff --git a/postfix/src/util/dict_inline_cidr.ref b/postfix/src/util/dict_inline_cidr.ref
new file mode 100644 (file)
index 0000000..56c00c5
--- /dev/null
@@ -0,0 +1,4 @@
+> get 1.2.3.4
+1.2.3.4=got 1.2.3.4
+> get 4.3.2.1
+4.3.2.1: not found
diff --git a/postfix/src/util/dict_inline_pcre.ref b/postfix/src/util/dict_inline_pcre.ref
new file mode 100644 (file)
index 0000000..0c381f1
--- /dev/null
@@ -0,0 +1,4 @@
+> get foo
+foo=got foo
+> get bar
+bar: not found
diff --git a/postfix/src/util/dict_inline_regexp.ref b/postfix/src/util/dict_inline_regexp.ref
new file mode 100644 (file)
index 0000000..0c381f1
--- /dev/null
@@ -0,0 +1,4 @@
+> get foo
+foo=got foo
+> get bar
+bar: not found
index f508e704e604df99c28881d667b3cd9b22f7a4f6..bed20e0c450c1571b4fd33a6d389ffb753774fc8 100644 (file)
 /* DIAGNOSTICS
 /*     Fatal errors: cannot open file, file write error, out of
 /*     memory.
+/*
+/*     If a jump buffer is specified with dict_setjmp(), then the LMDB
+/*     client will call dict_longjmp() to return to that execution
+/*     context after a recoverable error.
 /* BUGS
 /*     The on-the-fly map resize operations require no concurrent
 /*     activity in the same database by other threads in the same
index 38c0ec3b2bde2db7c5abbe8e977b20a86e8fc1e5..f4ddb2ede3cbeabe54b4a459ad396a80c2616f97 100644 (file)
@@ -812,6 +812,7 @@ DICT   *dict_pcre_open(const char *mapname, int open_flags, int dict_flags)
     DICT_PCRE *dict_pcre;
     VSTREAM *map_fp = 0;
     struct stat st;
+    VSTRING *why = 0;
     VSTRING *line_buffer = 0;
     DICT_PCRE_RULE *last_rule = 0;
     DICT_PCRE_RULE *rule;
@@ -831,6 +832,8 @@ DICT   *dict_pcre_open(const char *mapname, int open_flags, int dict_flags)
            vstream_fclose(map_fp); \
        if (line_buffer != 0) \
            vstring_free(line_buffer); \
+       if (why != 0) \
+          vstring_free(why); \
        return (__d); \
     } while (0)
 
@@ -846,13 +849,11 @@ DICT   *dict_pcre_open(const char *mapname, int open_flags, int dict_flags)
     /*
      * Open the configuration file.
      */
-    if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0)
+    if ((map_fp = dict_stream_open(DICT_TYPE_PCRE, mapname, O_RDONLY,
+                                  dict_flags, &st, &why)) == 0)
        DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname,
                                             open_flags, dict_flags,
-                                            "open %s: %m", mapname));
-    if (fstat(vstream_fileno(map_fp), &st) < 0)
-       msg_fatal("fstat %s: %m", mapname);
-
+                                            "%s", vstring_str(why)));
     line_buffer = vstring_alloc(100);
 
     dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname,
index 81cc6dd73a308259135c2d6a86c5cb7fef173a1e..e5e95cf177a42a045d038d0773ab3f8b8f3b6c89 100644 (file)
@@ -750,6 +750,7 @@ DICT   *dict_regexp_open(const char *mapname, int open_flags, int dict_flags)
     DICT_REGEXP *dict_regexp;
     VSTREAM *map_fp = 0;
     struct stat st;
+    VSTRING *why = 0;
     VSTRING *line_buffer = 0;
     DICT_REGEXP_RULE *rule;
     DICT_REGEXP_RULE *last_rule = 0;
@@ -770,6 +771,8 @@ DICT   *dict_regexp_open(const char *mapname, int open_flags, int dict_flags)
            vstring_free(line_buffer); \
        if (map_fp != 0) \
            vstream_fclose(map_fp); \
+       if (why != 0) \
+          vstring_free(why); \
        return (__d); \
     } while (0)
 
@@ -785,13 +788,11 @@ DICT   *dict_regexp_open(const char *mapname, int open_flags, int dict_flags)
     /*
      * Open the configuration file.
      */
-    if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0)
+    if ((map_fp = dict_stream_open(DICT_TYPE_REGEXP, mapname, O_RDONLY,
+                                  dict_flags, &st, &why)) == 0)
        DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP, mapname,
                                               open_flags, dict_flags,
-                                              "open %s: %m", mapname));
-    if (fstat(vstream_fileno(map_fp), &st) < 0)
-       msg_fatal("fstat %s: %m", mapname);
-
+                                              "%s", vstring_str(why)));
     line_buffer = vstring_alloc(100);
 
     dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, mapname,
diff --git a/postfix/src/util/dict_stream.c b/postfix/src/util/dict_stream.c
new file mode 100644 (file)
index 0000000..e28ad71
--- /dev/null
@@ -0,0 +1,274 @@
+/*++
+/* NAME
+/*     dict_stream 3
+/* SUMMARY
+/*
+/* SYNOPSIS
+/*     #include <dict.h>
+/*
+/*     VSTREAM *dict_stream_open(
+/*     const char *dict_type,
+/*     const char *mapname,
+/*     int     open_flags,
+/*     int     dict_flags,
+/*     struct stat * st,
+/*     VSTRING **why)
+/* DESCRIPTION
+/*     dict_stream_open() opens a dictionary, which can be specified
+/*     as a file name, or as inline text enclosed with {}. If successful,
+/*     dict_stream_open() returns a non-null VSTREAM pointer. Otherwise,
+/*     it returns an error text through the why argument, allocating
+/*     storage for the error text if the why argument points to a
+/*     null pointer.
+/*
+/*     When the dictionary file is specified inline, dict_stream_open()
+/*     removes the outer {} from the mapname value, and removes leading
+/*     or trailing comma or whitespace from the result. It then expects
+/*     to find zero or more rules enclosed in {}, separated by comma
+/*     and/or whitespace. dict_stream() writes each rule as one text
+/*     line to an in-memory stream, without its enclosing {} and without
+/*     leading or trailing whitespace. The result value is a VSTREAM
+/*     pointer for the in-memory stream that can be read as a regular
+/*     file.
+/* .sp
+/*     inline-file = "{" 0*(0*wsp-comma rule-spec) 0*wsp-comma "}"
+/* .sp
+/*     rule-spec = "{" 0*wsp rule-text 0*wsp "}"
+/* .sp
+/*     rule-text = any text containing zero or more balanced {}
+/* .sp
+/*     wsp-comma = wsp | ","
+/* .sp
+/*     wsp = whitespace
+/*
+/*     Arguments:
+/* .IP dict_type
+/* .IP open_flags
+/* .IP dict_flags
+/*     The same as with dict_open(3).
+/* .IP mapname
+/*     Pathname of a file with dictionary content, or inline dictionary
+/*     content as specified above.
+/* .IP st
+/*     File metadata with the file owner, or fake metadata with the
+/*     real UID and GID of the dict_stream_open() caller. This is 
+/*     used for "taint" tracking (zero=trusted, non-zero=untrusted).
+/* IP why
+/*     Pointer to pointer to error message storage. dict_stream_open()
+/*     updates this storage when reporting an error, and allocates
+/*     memory if why points to a null pointer.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_inline_to_multiline - convert inline map spec to multiline text */
+
+static char *dict_inline_to_multiline(VSTRING *vp, const char *mapname)
+{
+    char   *saved_name = mystrdup(mapname);
+    char   *bp = saved_name;
+    char   *cp;
+    char   *err = 0;
+
+    VSTRING_RESET(vp);
+    /* Strip the {} from the map "name". */
+    err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_NONE);
+    /* Extract zero or more rules inside {}. */
+    while (err == 0 && (cp = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0)
+       if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0)
+           /* Write rule to in-memory file. */
+           vstring_sprintf_append(vp, "%s\n", cp);
+    VSTRING_TERMINATE(vp);
+    myfree(saved_name);
+    return (err);
+}
+
+/* dict_stream_open - open inline configuration or configuration file */
+
+VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
+                                 int open_flags, int dict_flags,
+                                 struct stat * st, VSTRING **why)
+{
+    VSTRING *inline_buf = 0;
+    VSTREAM *map_fp;
+    char   *err = 0;
+
+#define RETURN_0_WITH_REASON(...) do { \
+       if (*why == 0) \
+           *why = vstring_alloc(100); \
+       vstring_sprintf(*why, __VA_ARGS__); \
+       if (inline_buf != 0) \
+          vstring_free(inline_buf); \
+       if (err != 0) \
+           myfree(err); \
+       return (0); \
+    } while (0)
+
+    if (mapname[0] == CHARS_BRACE[0]) {
+       inline_buf = vstring_alloc(100);
+       if ((err = dict_inline_to_multiline(inline_buf, mapname)) != 0)
+           RETURN_0_WITH_REASON("%s map: %s", dict_type, err);
+       map_fp = vstream_memopen(inline_buf, O_RDONLY);
+       vstream_control(map_fp, VSTREAM_CTL_OWN_VSTRING, VSTREAM_CTL_END);
+       st->st_uid = getuid();                  /* geteuid()? */
+       st->st_gid = getgid();                  /* getegid()? */
+       return (map_fp);
+    } else {
+       if ((map_fp = vstream_fopen(mapname, open_flags, 0)) == 0)
+           RETURN_0_WITH_REASON("open %s: %m", mapname);
+       if (fstat(vstream_fileno(map_fp), st) < 0)
+           msg_fatal("fstat %s: %m", mapname);
+       return (map_fp);
+    }
+}
+
+#ifdef TEST
+
+#include <string.h>
+
+int     main(int argc, char **argv)
+{
+    struct testcase {
+       const char *title;
+       const char *mapname;            /* starts with brace */
+       const char *expect_err;         /* null or message */
+       const char *expect_cont;        /* null or content */
+    };
+
+#define EXP_NOERR      0
+#define EXP_NOCONT     0
+
+#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null))
+#define DICT_TYPE_TEST "test"
+
+    const char rule_spec_error[] = DICT_TYPE_TEST " map: "
+    "syntax error after '}' in \"{blah blah}x\"";
+    const char inline_config_error[] = DICT_TYPE_TEST " map: "
+    "syntax error after '}' in \"{{foo bar}, {blah blah}}x\"";
+    struct testcase testcases[] = {
+       {"normal",
+           "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n"
+       },
+       {"trims leading/trailing wsp around rule-text",
+           "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n"
+       },
+       {"trims leading/trailing comma-wsp around rule-spec",
+           "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n"
+       },
+       {"empty inline-file",
+           "{, }", EXP_NOERR, ""
+       },
+       {"propagates extpar error for inline-file",
+           "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT
+       },
+       {"propagates extpar error for rule-spec",
+           "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT
+       },
+       0,
+    };
+    struct testcase *tp;
+    VSTRING *act_err = 0;
+    VSTRING *act_cont = vstring_alloc(100);
+    VSTREAM *fp;
+    struct stat st;
+    ssize_t exp_len;
+    ssize_t act_len;
+    int     pass;
+    int     fail;
+
+    for (pass = fail = 0, tp = testcases; tp->title; tp++) {
+       int     test_passed = 0;
+
+       msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
+
+#if 0
+       msg_info("title=%s", tp->title);
+       msg_info("mapname=%s", tp->mapname);
+       msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err));
+       msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont));
+#endif
+
+       if (act_err)
+           VSTRING_RESET(act_err);
+       fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY,
+                             0, &st, &act_err);
+       if (fp) {
+           if (tp->expect_err) {
+               msg_warn("test case %s: got stream, expected error", tp->title);
+           } else if (!tp->expect_err && act_err && LEN(act_err) > 0) {
+               msg_warn("test case %s: got error '%s', expected noerror",
+                        tp->title, STR(act_err));
+           } else if (!tp->expect_cont) {
+               msg_warn("test case %s: got stream, expected nostream",
+                        tp->title);
+           } else {
+               exp_len = strlen(tp->expect_cont);
+               if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) {
+                   msg_warn("test case %s: content read error", tp->title);
+               } else {
+                   VSTRING_TERMINATE(act_cont);
+                   if (strcmp(tp->expect_cont, STR(act_cont)) != 0) {
+                       msg_warn("test case %s: got content '%s', expected '%s'",
+                                tp->title, STR(act_cont), tp->expect_cont);
+                   } else {
+                       test_passed = 1;
+                   }
+               }
+           }
+       } else {
+           if (!tp->expect_err) {
+               msg_warn("test case %s: got nostream, expected noerror",
+                        tp->title);
+           } else if (tp->expect_cont) {
+               msg_warn("test case %s: got nostream, expected stream",
+                        tp->title);
+           } else if (strcmp(STR(act_err), tp->expect_err) != 0) {
+               msg_warn("test case %s: got error '%s', expected '%s'",
+                        tp->title, STR(act_err), tp->expect_err);
+           } else {
+               test_passed = 1;
+           }
+
+       }
+       if (test_passed) {
+           msg_info("PASS test %ld", (long) (tp - testcases));
+           pass++;
+       } else {
+           msg_info("FAIL test %ld", (long) (tp - testcases));
+           fail++;
+       }
+       if (fp)
+           vstream_fclose(fp);
+    }
+    if (act_err)
+       vstring_free(act_err);
+    vstring_free(act_cont);
+    msg_info("PASS=%d FAIL=%d", pass, fail);
+    return (fail > 0);
+}
+
+#endif                                 /* TEST */
diff --git a/postfix/src/util/dict_stream.ref b/postfix/src/util/dict_stream.ref
new file mode 100644 (file)
index 0000000..87c30e5
--- /dev/null
@@ -0,0 +1,13 @@
+unknown: RUN test case 0 normal
+unknown: PASS test 0
+unknown: RUN test case 1 trims leading/trailing wsp around rule-text
+unknown: PASS test 1
+unknown: RUN test case 2 trims leading/trailing comma-wsp around rule-spec
+unknown: PASS test 2
+unknown: RUN test case 3 empty inline-file
+unknown: PASS test 3
+unknown: RUN test case 4 propagates extpar error for inline-file
+unknown: PASS test 4
+unknown: RUN test case 5 propagates extpar error for rule-spec
+unknown: PASS test 5
+unknown: PASS=6 FAIL=0
index 773649f3eb78da7d3eaf3cd04bc97597e7fa2e0d..13e22c8d3ca74a6c529239dd6d1ca41ab4e08ec7 100644 (file)
 /*     whitespace before the closing parenthesis.
 /* .RE
 /* DIAGNOSTICS
-/*     panic: the input string does not start with the opening
-/*     parenthesis.
-/*
 /*     In case of error the result value is a dynamically-allocated
 /*     string with a description of the problem that includes a
 /*     copy of the offending input.  A non-null result value should
 /*     be destroyed with myfree(). The following describes the errors
 /*     and the state of the buffer and buffer pointer.
+/* .IP "no opening parenthesis at start of text"
+/*     The buffer pointer points to the input text.
 /* .IP "missing closing parenthesis"
 /*     The buffer pointer points to text as if a closing parenthesis
 /*     were present at the end of the input.
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
 /*--*/
 
  /*
@@ -69,7 +73,7 @@
  /*
   * Utility library.
   */
-#include <msg.h>
+#include <vstring.h>
 #include <stringops.h>
 
 /* extpar - extract text from parentheses */
@@ -80,9 +84,11 @@ char   *extpar(char **bp, const char *parens, int flags)
     char   *err = 0;
     size_t  len;
 
-    if (cp[0] != parens[0])
-       msg_panic("extpar: no '%c' at start of text: \"%s\"", parens[0], cp);
-    if ((len = balpar(cp, parens)) == 0) {
+    if (cp[0] != parens[0]) {
+       err = vstring_export(vstring_sprintf(vstring_alloc(100),
+                     "no '%c' at start of text in \"%s\"", parens[0], cp));
+       len = 0;
+    } else if ((len = balpar(cp, parens)) == 0) {
        err = concatenate("missing '", parens + 1, "' in \"",
                          cp, "\"", (char *) 0);
        cp += 1;
index f817f8f4b0c67dcc370e36225100b6a1b758fb12..37aa30d76385699c9fe189f6f069c14ae9fee8f7 100644 (file)
@@ -386,15 +386,18 @@ static int slmdb_prepare(SLMDB *slmdb)
      * - With a bulk-mode transaction we commit when the database is closed.
      */
     if (slmdb->open_flags & O_TRUNC) {
-       if ((status = mdb_drop(slmdb->txn, slmdb->dbi, 0)) != 0)
+       if ((status = mdb_drop(slmdb->txn, slmdb->dbi, 0)) != 0) {
+           mdb_txn_abort(slmdb->txn);
+           slmdb->txn = 0;
            return (status);
+       }
        if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) {
-           if ((status = mdb_txn_commit(slmdb->txn)) != 0)
-               return (status);
+           status = mdb_txn_commit(slmdb->txn);
            slmdb->txn = 0;
+           if (status != 0)
+               return (status);
        }
-    } else if ((slmdb->lmdb_flags & MDB_RDONLY) != 0
-              || (slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) {
+    } else if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) {
        mdb_txn_abort(slmdb->txn);
        slmdb->txn = 0;
     }
@@ -407,6 +410,7 @@ 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
@@ -418,19 +422,28 @@ static int slmdb_recover(SLMDB *slmdb, int status)
     /*
      * 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.
      */
     if ((slmdb->txn != 0 && slmdb->longjmp_fn == 0)
        || ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit))
        return (status);
 
+    /*
+     * Limit the number of recovery attempts per bulk transaction failure.
+     */
+    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 we can recover from the error, we clear the error condition and the
      * caller should retry the failed operation immediately. Otherwise, the
      * caller should terminate with a fatal run-time error and the program
      * should be re-run later.
-     * 
-     * slmdb->txn must be either null (non-bulk transaction error), or an
-     * aborted bulk-mode transaction.
      */
     switch (status) {
 
@@ -503,14 +516,12 @@ static int slmdb_recover(SLMDB *slmdb, int status)
      * upgrade the lock to "exclusive", because the failed write transaction
      * has no side effects.
      */
-    if (slmdb->txn != 0 && status == 0 && slmdb->longjmp_fn != 0
-       && (slmdb->bulk_retry_count += 1) <= slmdb->bulk_retry_limit) {
-       if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0,
-                                   slmdb->lmdb_flags & MDB_RDONLY,
-                                   &slmdb->txn)) == 0
-           && (status = slmdb_prepare(slmdb)) == 0)
-           slmdb->longjmp_fn(slmdb->cb_context, 1);
-    }
+    if (status == 0 && recover_bulk_transaction != 0
+       && (status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0,
+                                  slmdb->lmdb_flags & MDB_RDONLY,
+                                  &slmdb->txn)) == 0
+       && (status = slmdb_prepare(slmdb)) == 0)
+       slmdb->longjmp_fn(slmdb->cb_context, 1);
     return (status);
 }
 
@@ -588,7 +599,7 @@ int     slmdb_put(SLMDB *slmdb, MDB_val *mdb_key,
                status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
            SLMDB_API_RETURN(slmdb, status);
        } else {
-           /* Key exists, abort non-bulk transaction only. */
+           /* Abort non-bulk transaction only. */
            if (slmdb->txn == 0)
                mdb_txn_abort(txn);
        }
@@ -623,11 +634,15 @@ int     slmdb_del(SLMDB *slmdb, MDB_val *mdb_key)
      * Do the update.
      */
     if ((status = mdb_del(txn, slmdb->dbi, mdb_key, (MDB_val *) 0)) != 0) {
-       mdb_txn_abort(txn);
        if (status != MDB_NOTFOUND) {
+           mdb_txn_abort(txn);
            if ((status = slmdb_recover(slmdb, status)) == 0)
                status = slmdb_del(slmdb, mdb_key);
            SLMDB_API_RETURN(slmdb, status);
+       } else {
+           /* Abort non-bulk transaction only. */
+           if (slmdb->txn == 0)
+               mdb_txn_abort(txn);
        }
     }
 
index 2869b7e9f3fad2b328354b28c1e242f3e9bd64b9..eb56c897917908063f144bdc104ef6766b3e3b6d 100644 (file)
 /*     Revert VSTREAM_CTL_TIMEOUT behavior to the default, i.e.
 /*     a time limit for individual file descriptor read or write
 /*     operations.
+/* .IP CA_VSTREAM_CTL_OWN_VSTRING (no arguments)
+/*     Transfer ownership of the VSTRING that was opened with
+/*     vstream_memopen() etc. to the stream, so that the VSTRING
+/*     is automatically destroyed when the stream is closed.
 /* .PP
 /*     vstream_fileno() gives access to the file handle associated with
 /*     a buffered stream. With streams that have separate read/write
 /*     The double-buffering feature is activated.
 /* .IP VSTREAM_FLAG_MEMORY
 /*     The stream is connected to a VSTRING buffer.
+/* .IP VSTREAM_FLAG_OWN_VSTRING
+/*     The stream 'owns' the VSTRING buffer, and is responsible
+/*     for cleaning up when the stream is closed.
 /* DIAGNOSTICS
 /*     Panics: interface violations. Fatal errors: out of memory.
 /* SEE ALSO
@@ -1417,6 +1424,8 @@ int     vstream_fclose(VSTREAM *stream)
        myfree(stream->path);
     if (stream->jbuf)
        myfree((void *) stream->jbuf);
+    if (stream->vstring && (stream->buf.flags & VSTREAM_FLAG_OWN_VSTRING))
+       vstring_free(stream->vstring);
     if (!VSTREAM_STATIC(stream))
        myfree((void *) stream);
     return (err ? VSTREAM_EOF : 0);
@@ -1530,7 +1539,8 @@ void    vstream_control(VSTREAM *stream, int name,...)
      */
     int     memory_ops =
     ((1 << VSTREAM_CTL_END) | (1 << VSTREAM_CTL_CONTEXT)
-     | (1 << VSTREAM_CTL_PATH) | (1 << VSTREAM_CTL_EXCEPT));
+     | (1 << VSTREAM_CTL_PATH) | (1 << VSTREAM_CTL_EXCEPT)
+     | (1 << VSTREAM_CTL_OWN_VSTRING));
 
     for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) {
        if ((stream->buf.flags & VSTREAM_FLAG_MEMORY)
@@ -1665,6 +1675,11 @@ void    vstream_control(VSTREAM *stream, int name,...)
            stream->time_limit.tv_sec = stream->timeout;
            stream->time_limit.tv_usec = 0;
            break;
+       case VSTREAM_CTL_OWN_VSTRING:
+           if ((stream->buf.flags |= VSTREAM_FLAG_MEMORY) == 0)
+               msg_panic("%s: operation on non-VSTRING stream", myname);
+           stream->buf.flags |= VSTREAM_FLAG_OWN_VSTRING;
+           break;
        default:
            msg_panic("%s: bad name %d", myname, name);
        }
index 6f99cf0dbb57eeacd83fa06867a926001c6865ba..3a363cc17a2e1deed4f129934253d5a2d3e7b86f 100644 (file)
@@ -87,6 +87,7 @@ extern VSTREAM vstream_fstd[];                /* pre-defined streams */
 #define VSTREAM_FLAG_DOUBLE    (1<<12) /* double buffer */
 #define VSTREAM_FLAG_DEADLINE  (1<<13) /* deadline active */
 #define VSTREAM_FLAG_MEMORY    (1<<14) /* internal stream */
+#define VSTREAM_FLAG_OWN_VSTRING (1<<15)/* owns VSTRING resource */
 
 #define VSTREAM_PURGE_READ     (1<<0)  /* flush unread data */
 #define VSTREAM_PURGE_WRITE    (1<<1)  /* flush unwritten data */
@@ -155,6 +156,7 @@ extern void vstream_control(VSTREAM *, int,...);
 #define VSTREAM_CTL_SWAP_FD    13
 #define VSTREAM_CTL_START_DEADLINE 14
 #define VSTREAM_CTL_STOP_DEADLINE 15
+#define VSTREAM_CTL_OWN_VSTRING        16
 
 /* Safer API: type-checked arguments, external use. */
 #define CA_VSTREAM_CTL_END             VSTREAM_CTL_END
@@ -175,6 +177,7 @@ extern void vstream_control(VSTREAM *, int,...);
 #define CA_VSTREAM_CTL_SWAP_FD(v)      VSTREAM_CTL_SWAP_FD, CHECK_PTR(VSTREAM_CTL, VSTREAM, (v))
 #define CA_VSTREAM_CTL_START_DEADLINE  VSTREAM_CTL_START_DEADLINE
 #define CA_VSTREAM_CTL_STOP_DEADLINE   VSTREAM_CTL_STOP_DEADLINE
+#define CA_VSTREAM_CTL_OWN_VSTRING     VSTREAM_CTL_OWN_VSTRING
 
 CHECK_VAL_HELPER_DCL(VSTREAM_CTL, ssize_t);
 CHECK_VAL_HELPER_DCL(VSTREAM_CTL, int);